diff --git a/crates/uv-installer/src/site_packages.rs b/crates/uv-installer/src/site_packages.rs index e6ef1c61dbf2f..15e7ca5684dc0 100644 --- a/crates/uv-installer/src/site_packages.rs +++ b/crates/uv-installer/src/site_packages.rs @@ -8,9 +8,9 @@ use fs_err as fs; use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use uv_distribution_types::{ - ConfigSettings, Diagnostic, ExtraBuildRequires, ExtraBuildVariables, InstalledDist, - InstalledDistKind, Name, NameRequirementSpecification, PackageConfigSettings, Requirement, - UnresolvedRequirement, UnresolvedRequirementSpecification, + ConfigSettings, DependencyMetadata, Diagnostic, ExtraBuildRequires, ExtraBuildVariables, + InstalledDist, InstalledDistKind, Name, NameRequirementSpecification, PackageConfigSettings, + Requirement, UnresolvedRequirement, UnresolvedRequirementSpecification, }; use uv_fs::Simplified; use uv_normalize::PackageName; @@ -196,6 +196,7 @@ impl SitePackages { &self, markers: &ResolverMarkerEnvironment, tags: &Tags, + dependency_metadata: &DependencyMetadata, ) -> Result> { let mut diagnostics = Vec::new(); @@ -225,12 +226,19 @@ impl SitePackages { }; // Determine the dependencies for the given package. - let Ok(metadata) = distribution.read_metadata() else { - diagnostics.push(SitePackagesDiagnostic::MetadataUnavailable { - package: package.clone(), - path: distribution.install_path().to_owned(), - }); - continue; + let metadata = if let Some(metadata) = + dependency_metadata.get(package, Some(distribution.version())) + { + Cow::Owned(metadata) + } else { + let Ok(metadata) = distribution.read_metadata() else { + diagnostics.push(SitePackagesDiagnostic::MetadataUnavailable { + package: package.clone(), + path: distribution.install_path().to_owned(), + }); + continue; + }; + Cow::Borrowed(metadata) }; // Verify that the package is compatible with the current Python version. diff --git a/crates/uv/src/commands/pip/check.rs b/crates/uv/src/commands/pip/check.rs index 87b537494607d..2cddc024856a3 100644 --- a/crates/uv/src/commands/pip/check.rs +++ b/crates/uv/src/commands/pip/check.rs @@ -6,7 +6,7 @@ use owo_colors::OwoColorize; use uv_cache::Cache; use uv_configuration::TargetTriple; -use uv_distribution_types::{Diagnostic, InstalledDist}; +use uv_distribution_types::{DependencyMetadata, Diagnostic, InstalledDist}; use uv_installer::{SitePackages, SitePackagesDiagnostic}; use uv_preview::Preview; use uv_python::{ @@ -24,6 +24,7 @@ pub(crate) fn pip_check( system: bool, python_version: Option<&PythonVersion>, python_platform: Option<&TargetTriple>, + dependency_metadata: &DependencyMetadata, cache: &Cache, printer: Printer, preview: Preview, @@ -63,7 +64,7 @@ pub(crate) fn pip_check( // Run the diagnostics. let diagnostics: Vec = site_packages - .diagnostics(&markers, &tags)? + .diagnostics(&markers, &tags, dependency_metadata)? .into_iter() .collect(); diff --git a/crates/uv/src/commands/pip/freeze.rs b/crates/uv/src/commands/pip/freeze.rs index 3158bdb5f90bc..f12314e7b4151 100644 --- a/crates/uv/src/commands/pip/freeze.rs +++ b/crates/uv/src/commands/pip/freeze.rs @@ -8,7 +8,7 @@ use rustc_hash::FxHashSet; use tracing::debug; use uv_cache::Cache; -use uv_distribution_types::{Diagnostic, InstalledDistKind, Name}; +use uv_distribution_types::{DependencyMetadata, Diagnostic, InstalledDistKind, Name}; use uv_fs::Simplified; use uv_installer::SitePackages; use uv_normalize::PackageName; @@ -25,6 +25,7 @@ pub(crate) fn pip_freeze( exclude_editable: bool, exclude: &FxHashSet, strict: bool, + dependency_metadata: &DependencyMetadata, python: Option<&str>, system: bool, target: Option, @@ -124,7 +125,7 @@ pub(crate) fn pip_freeze( let tags = environment.interpreter().tags()?; for entry in site_packages { - for diagnostic in entry.diagnostics(&markers, tags)? { + for diagnostic in entry.diagnostics(&markers, tags, dependency_metadata)? { writeln!( printer.stderr(), "{}{} {}", diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 92bf7cd104633..3e71ef52d37f0 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -688,7 +688,14 @@ pub(crate) async fn pip_install( // Notify the user of any environment diagnostics. if strict && !dry_run.enabled() { - operations::diagnose_environment(&resolution, &environment, &marker_env, &tags, printer)?; + operations::diagnose_environment( + &resolution, + &environment, + &marker_env, + &tags, + &dependency_metadata, + printer, + )?; } Ok(ExitStatus::Success) diff --git a/crates/uv/src/commands/pip/list.rs b/crates/uv/src/commands/pip/list.rs index cf99065e41f72..097f5f3e94d68 100644 --- a/crates/uv/src/commands/pip/list.rs +++ b/crates/uv/src/commands/pip/list.rs @@ -17,7 +17,8 @@ use uv_client::{BaseClientBuilder, RegistryClientBuilder}; use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType}; use uv_distribution_filename::DistFilename; use uv_distribution_types::{ - Diagnostic, IndexCapabilities, IndexLocations, InstalledDist, Name, RequiresPython, + DependencyMetadata, Diagnostic, IndexCapabilities, IndexLocations, InstalledDist, Name, + RequiresPython, }; use uv_fs::Simplified; use uv_installer::SitePackages; @@ -48,6 +49,7 @@ pub(crate) async fn pip_list( concurrency: Concurrency, strict: bool, exclude_newer: ExcludeNewer, + dependency_metadata: &DependencyMetadata, python: Option<&str>, system: bool, target: Option, @@ -294,7 +296,7 @@ pub(crate) async fn pip_list( let markers = environment.interpreter().resolver_marker_environment(); let tags = environment.interpreter().tags()?; - for diagnostic in site_packages.diagnostics(&markers, tags)? { + for diagnostic in site_packages.diagnostics(&markers, tags, dependency_metadata)? { writeln!( printer.stderr(), "{}{} {}", diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index eedb8713964a1..fa8c12d12dcb1 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -19,7 +19,7 @@ use uv_configuration::{ use uv_dispatch::BuildDispatch; use uv_distribution::{DistributionDatabase, SourcedDependencyGroups}; use uv_distribution_types::{ - CachedDist, Diagnostic, Dist, InstalledDist, InstalledVersion, LocalDist, + CachedDist, DependencyMetadata, Diagnostic, Dist, InstalledDist, InstalledVersion, LocalDist, NameRequirementSpecification, Requirement, ResolutionDiagnostic, UnresolvedRequirement, UnresolvedRequirementSpecification, VersionOrUrlRef, }; @@ -1067,10 +1067,11 @@ pub(crate) fn diagnose_environment( venv: &PythonEnvironment, markers: &ResolverMarkerEnvironment, tags: &Tags, + dependency_metadata: &DependencyMetadata, printer: Printer, ) -> Result<(), Error> { let site_packages = SitePackages::from_environment(venv)?; - for diagnostic in site_packages.diagnostics(markers, tags)? { + for diagnostic in site_packages.diagnostics(markers, tags, dependency_metadata)? { // Only surface diagnostics that are "relevant" to the current resolution. if resolution .distributions() diff --git a/crates/uv/src/commands/pip/show.rs b/crates/uv/src/commands/pip/show.rs index 00b8550be1f98..a3d5186e8eea5 100644 --- a/crates/uv/src/commands/pip/show.rs +++ b/crates/uv/src/commands/pip/show.rs @@ -8,7 +8,7 @@ use rustc_hash::FxHashMap; use tracing::debug; use uv_cache::Cache; -use uv_distribution_types::{Diagnostic, Name}; +use uv_distribution_types::{DependencyMetadata, Diagnostic, Name}; use uv_fs::Simplified; use uv_install_wheel::read_record_file; use uv_installer::SitePackages; @@ -26,6 +26,7 @@ use crate::printer::Printer; pub(crate) fn pip_show( mut packages: Vec, strict: bool, + dependency_metadata: &DependencyMetadata, python: Option<&str>, system: bool, target: Option, @@ -227,7 +228,7 @@ pub(crate) fn pip_show( // Validate that the environment is consistent. if strict { - for diagnostic in site_packages.diagnostics(&markers, tags)? { + for diagnostic in site_packages.diagnostics(&markers, tags, dependency_metadata)? { writeln!( printer.stderr(), "{}{} {}", diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 73f83ed1398e3..c225bf60e55cb 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -579,7 +579,14 @@ pub(crate) async fn pip_sync( // Notify the user of any environment diagnostics. if strict && !dry_run.enabled() { - operations::diagnose_environment(&resolution, &environment, &marker_env, &tags, printer)?; + operations::diagnose_environment( + &resolution, + &environment, + &marker_env, + &tags, + &dependency_metadata, + printer, + )?; } Ok(ExitStatus::Success) diff --git a/crates/uv/src/commands/pip/tree.rs b/crates/uv/src/commands/pip/tree.rs index 9368cf3a43487..fe7857b0045c2 100644 --- a/crates/uv/src/commands/pip/tree.rs +++ b/crates/uv/src/commands/pip/tree.rs @@ -14,7 +14,9 @@ use uv_cache::{Cache, Refresh}; use uv_cache_info::Timestamp; use uv_client::{BaseClientBuilder, RegistryClientBuilder}; use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType}; -use uv_distribution_types::{Diagnostic, IndexCapabilities, IndexLocations, Name, RequiresPython}; +use uv_distribution_types::{ + DependencyMetadata, Diagnostic, IndexCapabilities, IndexLocations, Name, RequiresPython, +}; use uv_installer::SitePackages; use uv_normalize::PackageName; use uv_pep440::{Operator, Version, VersionSpecifier, VersionSpecifiers}; @@ -48,6 +50,7 @@ pub(crate) async fn pip_tree( concurrency: Concurrency, strict: bool, exclude_newer: ExcludeNewer, + dependency_metadata: &DependencyMetadata, python: Option<&str>, system: bool, cache: &Cache, @@ -175,7 +178,7 @@ pub(crate) async fn pip_tree( // Validate that the environment is consistent. if strict { - for diagnostic in site_packages.diagnostics(&markers, tags)? { + for diagnostic in site_packages.diagnostics(&markers, tags, dependency_metadata)? { writeln!( printer.stderr(), "{}{} {}", diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index bc347fb68b1c0..2c4b541fcd655 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1029,6 +1029,7 @@ async fn run(cli: Cli) -> Result { args.exclude_editable, &args.exclude, args.settings.strict, + &args.settings.dependency_metadata, args.settings.python.as_deref(), args.settings.system, args.settings.target, @@ -1064,6 +1065,7 @@ async fn run(cli: Cli) -> Result { globals.concurrency, args.settings.strict, args.settings.exclude_newer, + &args.settings.dependency_metadata, args.settings.python.as_deref(), args.settings.system, args.settings.target, @@ -1087,6 +1089,7 @@ async fn run(cli: Cli) -> Result { commands::pip_show( args.package, args.settings.strict, + &args.settings.dependency_metadata, args.settings.python.as_deref(), args.settings.system, args.settings.target, @@ -1122,6 +1125,7 @@ async fn run(cli: Cli) -> Result { globals.concurrency, args.settings.strict, args.settings.exclude_newer, + &args.settings.dependency_metadata, args.settings.python.as_deref(), args.settings.system, &cache, @@ -1145,6 +1149,7 @@ async fn run(cli: Cli) -> Result { args.settings.system, args.settings.python_version.as_ref(), args.settings.python_platform.as_ref(), + &args.settings.dependency_metadata, &cache, printer, globals.preview, diff --git a/crates/uv/tests/it/pip_check.rs b/crates/uv/tests/it/pip_check.rs index 20f24f7c00f4d..aecacbed373a9 100644 --- a/crates/uv/tests/it/pip_check.rs +++ b/crates/uv/tests/it/pip_check.rs @@ -219,3 +219,80 @@ fn check_python_version() { " ); } + +#[test] +fn check_dependency_metadata_from_config_file() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("requests==2.31.0")?; + + uv_snapshot!(context + .pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--strict"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + Prepared 5 packages in [TIME] + Installed 5 packages in [TIME] + + certifi==2024.2.2 + + charset-normalizer==3.3.2 + + idna==3.6 + + requests==2.31.0 + + urllib3==2.2.1 + " + ); + + let requirements_txt_idna = context.temp_dir.child("requirements_idna.txt"); + requirements_txt_idna.write_str("idna==2.4")?; + + uv_snapshot!(context + .pip_install() + .arg("-r") + .arg("requirements_idna.txt") + .arg("--strict"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - idna==3.6 + + idna==2.4 + warning: The package `requests` requires `idna>=2.5,<4`, but `2.4` is installed + " + ); + + let uv_toml = context.temp_dir.child("uv.toml"); + uv_toml.write_str( + r#" + dependency-metadata = [ + { name = "requests", version = "2.31.0", requires-dist = ["certifi>=2017.4.17", "charset-normalizer>=2,<4", "idna>=2.4,<4", "urllib3>=1.21.1,<3"] }, + ] + "#, + )?; + + uv_snapshot!(context + .pip_check() + .arg("--config-file") + .arg("uv.toml"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Checked 5 packages in [TIME] + All installed packages are compatible + " + ); + + Ok(()) +}