diff --git a/crates/aiken-project/src/deps/downloader.rs b/crates/aiken-project/src/deps/downloader.rs index ec15cd45..5dd82d17 100644 --- a/crates/aiken-project/src/deps/downloader.rs +++ b/crates/aiken-project/src/deps/downloader.rs @@ -3,7 +3,11 @@ use std::{io::Cursor, path::Path}; use futures::future; use reqwest::Client; -use crate::{config::PackageName, error::Error, paths}; +use crate::{ + config::PackageName, + error::Error, + paths::{self, CacheKey}, +}; use super::manifest::Package; @@ -41,15 +45,20 @@ impl<'a> Downloader<'a> { &self, package: &Package, ) -> Result { - self.ensure_package_downloaded(package).await?; - self.extract_package_from_cache(&package.name, &package.version) + let cache_key = paths::CacheKey::new(&self.http, package).await?; + self.ensure_package_downloaded(package, &cache_key).await?; + self.extract_package_from_cache(&package.name, &cache_key) .await } - pub async fn ensure_package_downloaded(&self, package: &Package) -> Result { + pub async fn ensure_package_downloaded( + &self, + package: &Package, + cache_key: &CacheKey, + ) -> Result { let packages_cache_path = paths::packages_cache(); - let zipball_path = - paths::package_cache_zipball(&package.name, &package.version.to_string()); + + let zipball_path = paths::package_cache_zipball(cache_key); if !packages_cache_path.exists() { tokio::fs::create_dir_all(packages_cache_path).await?; @@ -83,7 +92,7 @@ impl<'a> Downloader<'a> { pub async fn extract_package_from_cache( &self, name: &PackageName, - version: &str, + cache_key: &CacheKey, ) -> Result { let destination = self.root_path.join(paths::build_deps_package(name)); @@ -94,9 +103,7 @@ impl<'a> Downloader<'a> { tokio::fs::create_dir_all(&destination).await?; - let zipball_path = self - .root_path - .join(paths::package_cache_zipball(name, version)); + let zipball_path = self.root_path.join(paths::package_cache_zipball(cache_key)); let zipball = tokio::fs::read(zipball_path).await?; diff --git a/crates/aiken-project/src/deps/manifest.rs b/crates/aiken-project/src/deps/manifest.rs index e0e53ced..0b22b83f 100644 --- a/crates/aiken-project/src/deps/manifest.rs +++ b/crates/aiken-project/src/deps/manifest.rs @@ -87,7 +87,7 @@ impl Manifest { } } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Clone)] pub struct Package { pub name: PackageName, pub version: String, diff --git a/crates/aiken-project/src/error.rs b/crates/aiken-project/src/error.rs index 3c853d5b..b0b5c7ab 100644 --- a/crates/aiken-project/src/error.rs +++ b/crates/aiken-project/src/error.rs @@ -1,4 +1,4 @@ -use crate::{pretty, script::EvalHint}; +use crate::{deps::manifest::Package, pretty, script::EvalHint}; use aiken_lang::{ ast::{BinOp, Span}, parser::error::ParseError, @@ -106,6 +106,14 @@ pub enum Error { src: String, evaluation_hint: Option, }, + + #[error( + "I was unable to resolve '{}' for {}/{}", + package.version, + package.name.owner, + package.name.repo + )] + UnknownPackageVersion { package: Package }, } impl Error { @@ -187,6 +195,7 @@ impl Error { Error::Http(_) => None, Error::ZipExtract(_) => None, Error::JoinError(_) => None, + Error::UnknownPackageVersion { .. } => None, } } @@ -208,6 +217,7 @@ impl Error { Error::Http(_) => None, Error::ZipExtract(_) => None, Error::JoinError(_) => None, + Error::UnknownPackageVersion { .. } => None, } } } @@ -257,6 +267,7 @@ impl Diagnostic for Error { Error::Http(_) => Some(Box::new("aiken::deps")), Error::ZipExtract(_) => None, Error::JoinError(_) => None, + Error::UnknownPackageVersion { .. } => Some(Box::new("aiken::deps")), } } @@ -312,6 +323,7 @@ impl Diagnostic for Error { Error::Http(_) => None, Error::ZipExtract(_) => None, Error::JoinError(_) => None, + Error::UnknownPackageVersion{..} => None, } } @@ -345,6 +357,7 @@ impl Diagnostic for Error { Error::Http(_) => None, Error::ZipExtract(_) => None, Error::JoinError(_) => None, + Error::UnknownPackageVersion { .. } => None, } } @@ -366,6 +379,7 @@ impl Diagnostic for Error { Error::Http(_) => None, Error::ZipExtract(_) => None, Error::JoinError(_) => None, + Error::UnknownPackageVersion { .. } => None, } } @@ -387,6 +401,7 @@ impl Diagnostic for Error { Error::Http { .. } => None, Error::ZipExtract { .. } => None, Error::JoinError { .. } => None, + Error::UnknownPackageVersion { .. } => None, } } } diff --git a/crates/aiken-project/src/paths.rs b/crates/aiken-project/src/paths.rs index a5ac9332..4f514e90 100644 --- a/crates/aiken-project/src/paths.rs +++ b/crates/aiken-project/src/paths.rs @@ -1,7 +1,8 @@ +use crate::deps::manifest::Package; +use crate::{config::PackageName, error::Error}; +use reqwest::Client; use std::path::PathBuf; -use crate::config::PackageName; - pub fn manifest() -> PathBuf { PathBuf::from("aiken.lock") } @@ -22,11 +23,8 @@ pub fn build_deps_package(package_name: &PackageName) -> PathBuf { packages().join(format!("{}-{}", package_name.owner, package_name.repo)) } -pub fn package_cache_zipball(package_name: &PackageName, version: &str) -> PathBuf { - packages_cache().join(format!( - "{}-{}-{}.zip", - package_name.owner, package_name.repo, version - )) +pub fn package_cache_zipball(cache_key: &CacheKey) -> PathBuf { + packages_cache().join(cache_key.get_key()) } pub fn packages_cache() -> PathBuf { @@ -38,3 +36,46 @@ pub fn default_aiken_cache() -> PathBuf { .expect("Failed to determine user cache directory") .join("aiken") } + +pub struct CacheKey { + key: String, +} + +impl CacheKey { + pub async fn new(http: &Client, package: &Package) -> Result { + let version = match hex::decode(&package.version) { + Ok(..) => Ok(package.version.to_string()), + Err(..) => { + let url = format!( + "https://api.github.com/repos/{}/{}/zipball/{}", + package.name.owner, package.name.repo, package.version + ); + let response = http + .head(url) + .header("User-Agent", "aiken-lang") + .send() + .await?; + let etag = response + .headers() + .get("etag") + .ok_or(Error::UnknownPackageVersion { + package: package.clone(), + })? + .to_str() + .unwrap() + .replace('"', ""); + Ok(format!("main@{etag}")) + } + }; + version.map(|version| CacheKey { + key: format!( + "{}-{}-{}.zip", + package.name.owner, package.name.repo, version + ), + }) + } + + pub fn get_key(&self) -> &str { + self.key.as_ref() + } +}