diff --git a/crates/distribution-types/src/error.rs b/crates/distribution-types/src/error.rs index 12a846f26..8ddcba0cb 100644 --- a/crates/distribution-types/src/error.rs +++ b/crates/distribution-types/src/error.rs @@ -10,4 +10,7 @@ pub enum Error { #[error("Unable to extract filename from URL: {0}")] UrlFilename(Url), + + #[error("Unable to locate distribution at: {0}")] + NotFound(Url, #[source] std::io::Error), } diff --git a/crates/distribution-types/src/lib.rs b/crates/distribution-types/src/lib.rs index d6762b02b..916cfa72f 100644 --- a/crates/distribution-types/src/lib.rs +++ b/crates/distribution-types/src/lib.rs @@ -198,7 +198,8 @@ impl Dist { let path = url .to_file_path() .map_err(|()| Error::UrlFilename(url.clone()))? - .canonicalize()?; + .canonicalize() + .map_err(|err| Error::NotFound(url.clone(), err))?; return if path .extension() .is_some_and(|ext| ext.eq_ignore_ascii_case("whl")) diff --git a/crates/puffin-cli/tests/pip_compile.rs b/crates/puffin-cli/tests/pip_compile.rs index 1a49b4cb5..b946c9801 100644 --- a/crates/puffin-cli/tests/pip_compile.rs +++ b/crates/puffin-cli/tests/pip_compile.rs @@ -1477,3 +1477,34 @@ fn compile_source_distribution_path_dependency() -> Result<()> { Ok(()) } + +/// Resolve a local path dependency to a non-existent file. +#[test] +fn compile_wheel_path_dependency_missing() -> Result<()> { + let temp_dir = TempDir::new()?; + let cache_dir = TempDir::new()?; + let venv = create_venv_py312(&temp_dir, &cache_dir); + + let requirements_in = temp_dir.child("requirements.in"); + requirements_in.write_str("flask @ file:///path/to/flask-3.0.0-py3-none-any.whl")?; + + // In addition to the standard filters, remove the temporary directory from the snapshot. + let mut filters = INSTA_FILTERS.to_vec(); + filters.push((r"file://.*/", "file://[TEMP_DIR]/")); + + insta::with_settings!({ + filters => filters + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-compile") + .arg("requirements.in") + .arg("--cache-dir") + .arg(cache_dir.path()) + .arg("--exclude-newer") + .arg(EXCLUDE_NEWER) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Ok(()) +} diff --git a/crates/puffin-cli/tests/snapshots/pip_compile__compile_git_mismatched_name.snap b/crates/puffin-cli/tests/snapshots/pip_compile__compile_git_mismatched_name.snap index 2b63ce972..c1380befb 100644 --- a/crates/puffin-cli/tests/snapshots/pip_compile__compile_git_mismatched_name.snap +++ b/crates/puffin-cli/tests/snapshots/pip_compile__compile_git_mismatched_name.snap @@ -6,17 +6,17 @@ info: - pip-compile - requirements.in - "--cache-dir" - - /tmp/.tmpR1rZex + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpUI3Kjp - "--exclude-newer" - "2023-11-18T12:00:00Z" env: - VIRTUAL_ENV: /tmp/.tmpy0g4rP/.venv + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpDZkiLP/.venv --- success: false exit_code: 2 ----- stdout ----- ----- stderr ----- -error: Failed to download and build dask @ git+https://github.com/pallets/flask.git@3.0.0 +error: Failed to download and build: dask @ git+https://github.com/pallets/flask.git@3.0.0 Caused by: Package metadata name `flask` does not match given name `dask` diff --git a/crates/puffin-cli/tests/snapshots/pip_compile__compile_wheel_path_dependency_missing.snap b/crates/puffin-cli/tests/snapshots/pip_compile__compile_wheel_path_dependency_missing.snap new file mode 100644 index 000000000..e55c28204 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_compile__compile_wheel_path_dependency_missing.snap @@ -0,0 +1,22 @@ +--- +source: crates/puffin-cli/tests/pip_compile.rs +info: + program: puffin + args: + - pip-compile + - requirements.in + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpI36Udp + - "--exclude-newer" + - "2023-11-18T12:00:00Z" + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpuH6gF4/.venv +--- +success: false +exit_code: 2 +----- stdout ----- + +----- stderr ----- +error: Unable to locate distribution at: file://[TEMP_DIR]/flask-3.0.0-py3-none-any.whl + Caused by: No such file or directory (os error 2) + diff --git a/crates/puffin-client/src/cached_client.rs b/crates/puffin-client/src/cached_client.rs index 2ed3d5047..eda4f893b 100644 --- a/crates/puffin-client/src/cached_client.rs +++ b/crates/puffin-client/src/cached_client.rs @@ -125,7 +125,7 @@ impl CachedClient { serde_json::to_vec(&data_with_cache_policy).map_err(crate::Error::from)?, ) .await - .map_err(crate::Error::from)?; + .map_err(crate::Error::CacheWrite)?; Ok(data_with_cache_policy.data) } CachedResponse::ModifiedOrNew(res, cache_policy) => { @@ -136,12 +136,12 @@ impl CachedClient { let data_with_cache_policy = DataWithCachePolicy { data, cache_policy }; fs_err::tokio::create_dir_all(&cache_entry.dir) .await - .map_err(crate::Error::from)?; + .map_err(crate::Error::CacheWrite)?; let data = serde_json::to_vec(&data_with_cache_policy).map_err(crate::Error::from)?; write_atomic(cache_entry.path(), data) .await - .map_err(crate::Error::from)?; + .map_err(crate::Error::CacheWrite)?; Ok(data_with_cache_policy.data) } else { Ok(data) diff --git a/crates/puffin-client/src/error.rs b/crates/puffin-client/src/error.rs index 8515cabcd..8b53aeab8 100644 --- a/crates/puffin-client/src/error.rs +++ b/crates/puffin-client/src/error.rs @@ -68,7 +68,10 @@ pub enum Error { Zip(WheelFilename, #[source] ZipError), #[error("Failed to write to the client cache")] - IO(#[from] io::Error), + CacheWrite(#[source] io::Error), + + #[error(transparent)] + Io(#[from] io::Error), /// An [`io::Error`] with a filename attached #[error(transparent)] diff --git a/crates/puffin-client/src/registry_client.rs b/crates/puffin-client/src/registry_client.rs index 733bed395..ae0849a68 100644 --- a/crates/puffin-client/src/registry_client.rs +++ b/crates/puffin-client/src/registry_client.rs @@ -8,7 +8,7 @@ use futures::TryStreamExt; use reqwest::{Client, ClientBuilder, Response, StatusCode}; use reqwest_retry::policies::ExponentialBackoff; use reqwest_retry::RetryTransientMiddleware; -use tempfile::tempfile; +use tempfile::tempfile_in; use tokio::io::BufWriter; use tokio_util::compat::FuturesAsyncReadCompatExt; use tracing::{debug, trace}; @@ -318,10 +318,12 @@ impl RegistryClient { // you host your wheels for some reasons doesn't support range requests // (tbh we should probably warn here and tell users to get a better registry because // their current one makes resolution unnecessary slow) - let temp_download = tempfile()?; + let temp_download = tempfile_in(cache_shard.wheel_dir()).map_err(Error::CacheWrite)?; let mut writer = BufWriter::new(tokio::fs::File::from_std(temp_download)); let mut reader = self.stream_external(url).await?.compat(); - tokio::io::copy(&mut reader, &mut writer).await?; + tokio::io::copy(&mut reader, &mut writer) + .await + .map_err(Error::CacheWrite)?; let reader = writer.into_inner(); Self::metadata_from_async_read(filename, url.to_string(), reader).await diff --git a/crates/puffin-distribution/src/source_dist.rs b/crates/puffin-distribution/src/source_dist.rs index 23d561558..cd165c265 100644 --- a/crates/puffin-distribution/src/source_dist.rs +++ b/crates/puffin-distribution/src/source_dist.rs @@ -25,7 +25,7 @@ use distribution_types::{GitSourceDist, Identifier, RemoteSource, SourceDist}; use install_wheel_rs::read_dist_info; use platform_tags::Tags; use puffin_cache::{digest, write_atomic, CacheBucket, CacheEntry, CanonicalUrl, WheelCache}; -use puffin_client::{CachedClient, CachedClientError, DataWithCachePolicy}; +use puffin_client::{CachedClient, CachedClientError, DataWithCachePolicy, Error}; use puffin_git::{Fetch, GitSource}; use puffin_normalize::PackageName; use puffin_traits::BuildContext; @@ -471,11 +471,17 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> { // Download the source distribution. let cache_dir = self.build_context.cache().bucket(CacheBucket::BuiltWheels); - fs::create_dir_all(&cache_dir).await?; - let temp_dir = tempfile::tempdir_in(cache_dir)?; + fs::create_dir_all(&cache_dir) + .await + .map_err(Error::CacheWrite)?; + let temp_dir = tempfile::tempdir_in(cache_dir).map_err(Error::CacheWrite)?; let sdist_file = temp_dir.path().join(source_dist_filename); - let mut writer = tokio::fs::File::create(&sdist_file).await?; - tokio::io::copy(&mut reader, &mut writer).await?; + let mut writer = tokio::fs::File::create(&sdist_file) + .await + .map_err(Error::CacheWrite)?; + tokio::io::copy(&mut reader, &mut writer) + .await + .map_err(Error::CacheWrite)?; Ok((Some(temp_dir), sdist_file)) } diff --git a/crates/puffin-resolver/src/error.rs b/crates/puffin-resolver/src/error.rs index 60fe32298..39996c3e1 100644 --- a/crates/puffin-resolver/src/error.rs +++ b/crates/puffin-resolver/src/error.rs @@ -5,7 +5,7 @@ use pubgrub::report::{DefaultStringReporter, Reporter}; use thiserror::Error; use url::Url; -use distribution_types::{BuiltDist, SourceDist}; +use distribution_types::{BuiltDist, PathBuiltDist, PathSourceDist, SourceDist}; use pep508_rs::Requirement; use puffin_distribution::DistributionDatabaseError; use puffin_normalize::PackageName; @@ -54,11 +54,17 @@ pub enum ResolveError { #[error(transparent)] DistributionType(#[from] distribution_types::Error), - #[error("Failed to download {0}")] + #[error("Failed to download: {0}")] Fetch(Box, #[source] DistributionDatabaseError), - #[error("Failed to download and build {0}")] + #[error("Failed to download and build: {0}")] FetchAndBuild(Box, #[source] DistributionDatabaseError), + + #[error("Failed to read: {0}")] + Read(Box, #[source] DistributionDatabaseError), + + #[error("Failed to build: {0}")] + Build(Box, #[source] DistributionDatabaseError), } impl From> for ResolveError { diff --git a/crates/puffin-resolver/src/resolver.rs b/crates/puffin-resolver/src/resolver.rs index 13f195833..7337927d3 100644 --- a/crates/puffin-resolver/src/resolver.rs +++ b/crates/puffin-resolver/src/resolver.rs @@ -17,7 +17,7 @@ use url::Url; use waitmap::WaitMap; use distribution_filename::WheelFilename; -use distribution_types::{Dist, Identifier, Metadata, SourceDist, VersionOrUrl}; +use distribution_types::{BuiltDist, Dist, Identifier, Metadata, SourceDist, VersionOrUrl}; use pep508_rs::{MarkerEnvironment, Requirement}; use platform_tags::Tags; use puffin_cache::CanonicalUrl; @@ -610,6 +610,12 @@ impl<'a, Context: BuildContext + Send + Sync> Resolver<'a, Context> { .get_or_build_wheel_metadata(&dist) .await .map_err(|err| match dist.clone() { + Dist::Built(BuiltDist::Path(built_dist)) => { + ResolveError::Read(Box::new(built_dist), err) + } + Dist::Source(SourceDist::Path(source_dist)) => { + ResolveError::Build(Box::new(source_dist), err) + } Dist::Built(built_dist) => ResolveError::Fetch(Box::new(built_dist), err), Dist::Source(source_dist) => { ResolveError::FetchAndBuild(Box::new(source_dist), err)