From ed3f99a119cd0f64eea634aeb0b5e8d9f6e22ec3 Mon Sep 17 00:00:00 2001 From: Parham MohammadAlizadeh Date: Mon, 20 Oct 2025 12:08:10 +0100 Subject: [PATCH] Add required environment marker example to hint (#16244) ## Summary fixes issue #15938 - show platform wheel hint with a concrete `tool.uv.required-environments` example so users know how to configure compatibility - add `WheelTagHint::suggest_environment_marker` to pick a sensible environment marker based on the available wheel tags - update the `sync_required_environment_hint` integration snapshot to expect the new multi-line hint ## Test Plan cargo test --package uv --test it -- sync::sync_required_environment_hint --- .../src/lock/export/pylock_toml.rs | 8 +-- crates/uv-resolver/src/lock/installable.rs | 35 ++++++++++--- crates/uv-resolver/src/lock/mod.rs | 50 +++++++++++++++---- crates/uv/src/commands/project/lock.rs | 1 + crates/uv/tests/it/sync.rs | 10 ++-- 5 files changed, 79 insertions(+), 25 deletions(-) diff --git a/crates/uv-resolver/src/lock/export/pylock_toml.rs b/crates/uv-resolver/src/lock/export/pylock_toml.rs index 60df87642..2889842b5 100644 --- a/crates/uv-resolver/src/lock/export/pylock_toml.rs +++ b/crates/uv-resolver/src/lock/export/pylock_toml.rs @@ -1144,13 +1144,13 @@ impl<'lock> PylockToml { kind: Box::new(PylockTomlErrorKind::IncompatibleWheelOnly( package.name.clone(), )), - hint: package.tag_hint(tags), + hint: package.tag_hint(tags, markers), }), (false, false) => Err(PylockTomlError { kind: Box::new(PylockTomlErrorKind::NeitherSourceDistNorWheel( package.name.clone(), )), - hint: package.tag_hint(tags), + hint: package.tag_hint(tags, markers), }), }; }; @@ -1279,7 +1279,7 @@ impl PylockTomlPackage { } /// Generate a [`WheelTagHint`] based on wheel-tag incompatibilities. - fn tag_hint(&self, tags: &Tags) -> Option { + fn tag_hint(&self, tags: &Tags, markers: &MarkerEnvironment) -> Option { let filenames = self .wheels .iter() @@ -1287,7 +1287,7 @@ impl PylockTomlPackage { .filter_map(|wheel| wheel.filename(&self.name).ok()) .collect::>(); let filenames = filenames.iter().map(Cow::as_ref).collect::>(); - WheelTagHint::from_wheels(&self.name, self.version.as_ref(), &filenames, tags) + WheelTagHint::from_wheels(&self.name, self.version.as_ref(), &filenames, tags, markers) } /// Returns the [`ResolvedRepositoryReference`] for the package, if it is a Git source. diff --git a/crates/uv-resolver/src/lock/installable.rs b/crates/uv-resolver/src/lock/installable.rs index 884a111d4..b6d93904f 100644 --- a/crates/uv-resolver/src/lock/installable.rs +++ b/crates/uv-resolver/src/lock/installable.rs @@ -107,9 +107,9 @@ pub trait Installable<'lock> { // Add the workspace package to the graph. let index = petgraph.add_node(if groups.prod() { - self.package_to_node(dist, tags, build_options, install_options)? + self.package_to_node(dist, tags, build_options, install_options, marker_env)? } else { - self.non_installable_node(dist, tags)? + self.non_installable_node(dist, tags, marker_env)? }); inverse.insert(&dist.id, index); @@ -162,6 +162,7 @@ pub trait Installable<'lock> { tags, build_options, install_options, + marker_env, )?); entry.insert(index); index @@ -178,6 +179,7 @@ pub trait Installable<'lock> { tags, build_options, install_options, + marker_env, )?; } index @@ -226,9 +228,9 @@ pub trait Installable<'lock> { // Add the package to the graph. let index = petgraph.add_node(if groups.prod() { - self.package_to_node(dist, tags, build_options, install_options)? + self.package_to_node(dist, tags, build_options, install_options, marker_env)? } else { - self.non_installable_node(dist, tags)? + self.non_installable_node(dist, tags, marker_env)? }); inverse.insert(&dist.id, index); @@ -284,6 +286,7 @@ pub trait Installable<'lock> { tags, build_options, install_options, + marker_env, )?); entry.insert(index); index @@ -295,7 +298,13 @@ pub trait Installable<'lock> { let index = *entry.get(); let node = &mut petgraph[index]; if !groups.prod() { - *node = self.package_to_node(dist, tags, build_options, install_options)?; + *node = self.package_to_node( + dist, + tags, + build_options, + install_options, + marker_env, + )?; } index } @@ -475,6 +484,7 @@ pub trait Installable<'lock> { tags, build_options, install_options, + marker_env, )?); entry.insert(index); index @@ -514,12 +524,14 @@ pub trait Installable<'lock> { &self, package: &Package, tags: &Tags, + marker_env: &ResolverMarkerEnvironment, build_options: &BuildOptions, ) -> Result { let dist = package.to_dist( self.install_path(), TagPolicy::Required(tags), build_options, + marker_env, )?; let version = package.version().cloned(); let dist = ResolvedDist::Installable { @@ -535,11 +547,17 @@ pub trait Installable<'lock> { } /// Create a non-installable [`Node`] from a [`Package`]. - fn non_installable_node(&self, package: &Package, tags: &Tags) -> Result { + fn non_installable_node( + &self, + package: &Package, + tags: &Tags, + marker_env: &ResolverMarkerEnvironment, + ) -> Result { let dist = package.to_dist( self.install_path(), TagPolicy::Preferred(tags), &BuildOptions::default(), + marker_env, )?; let version = package.version().cloned(); let dist = ResolvedDist::Installable { @@ -561,15 +579,16 @@ pub trait Installable<'lock> { tags: &Tags, build_options: &BuildOptions, install_options: &InstallOptions, + marker_env: &ResolverMarkerEnvironment, ) -> Result { if install_options.include_package( package.as_install_target(), self.project_name(), self.lock().members(), ) { - self.installable_node(package, tags, build_options) + self.installable_node(package, tags, marker_env, build_options) } else { - self.non_installable_node(package, tags) + self.non_installable_node(package, tags, marker_env) } } } diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 2543d178f..b446ba1fa 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1452,6 +1452,7 @@ impl Lock { dependency_metadata: &DependencyMetadata, indexes: Option<&IndexLocations>, tags: &Tags, + markers: &MarkerEnvironment, hasher: &HashStrategy, index: &InMemoryIndex, database: &DistributionDatabase<'_, Context>, @@ -1724,8 +1725,12 @@ impl Lock { if let Some(version) = package.id.version.as_ref() { // For a non-dynamic package, fetch the metadata from the distribution database. - let dist = - package.to_dist(root, TagPolicy::Preferred(tags), &BuildOptions::default())?; + let dist = package.to_dist( + root, + TagPolicy::Preferred(tags), + &BuildOptions::default(), + markers, + )?; let metadata = { let id = dist.version_id(); @@ -1875,6 +1880,7 @@ impl Lock { root, TagPolicy::Preferred(tags), &BuildOptions::default(), + markers, )?; let metadata = { @@ -2511,6 +2517,7 @@ impl Package { workspace_root: &Path, tag_policy: TagPolicy<'_>, build_options: &BuildOptions, + markers: &MarkerEnvironment, ) -> Result { let no_binary = build_options.no_binary_package(&self.id.name); let no_build = build_options.no_build_package(&self.id.name); @@ -2613,19 +2620,23 @@ impl Package { kind: Box::new(LockErrorKind::IncompatibleWheelOnly { id: self.id.clone(), }), - hint: self.tag_hint(tag_policy), + hint: self.tag_hint(tag_policy, markers), }), (false, false) => Err(LockError { kind: Box::new(LockErrorKind::NeitherSourceDistNorWheel { id: self.id.clone(), }), - hint: self.tag_hint(tag_policy), + hint: self.tag_hint(tag_policy, markers), }), } } /// Generate a [`WheelTagHint`] based on wheel-tag incompatibilities. - fn tag_hint(&self, tag_policy: TagPolicy<'_>) -> Option { + fn tag_hint( + &self, + tag_policy: TagPolicy<'_>, + markers: &MarkerEnvironment, + ) -> Option { let filenames = self .wheels .iter() @@ -2636,6 +2647,7 @@ impl Package { self.id.version.as_ref(), &filenames, tag_policy.tags(), + markers, ) } @@ -5260,6 +5272,7 @@ enum WheelTagHint { version: Option, tags: BTreeSet, best: Option, + markers: MarkerEnvironment, }, } @@ -5270,6 +5283,7 @@ impl WheelTagHint { version: Option<&Version>, filenames: &[&WheelFilename], tags: &Tags, + markers: &MarkerEnvironment, ) -> Option { let incompatibility = filenames .iter() @@ -5322,17 +5336,18 @@ impl WheelTagHint { } TagCompatibility::Incompatible(IncompatibleTag::Platform) => { let best = tags.platform_tag().cloned(); - let tags = Self::platform_tags(filenames.iter().copied(), tags) + let incompatible_tags = Self::platform_tags(filenames.iter().copied(), tags) .cloned() .collect::>(); - if tags.is_empty() { + if incompatible_tags.is_empty() { None } else { Some(Self::PlatformTags { package: name.clone(), version: version.cloned(), - tags, + tags: incompatible_tags, best, + markers: markers.clone(), }) } } @@ -5373,6 +5388,18 @@ impl WheelTagHint { } }) } + + fn suggest_environment_marker(markers: &MarkerEnvironment) -> String { + let sys_platform = markers.sys_platform(); + let platform_machine = markers.platform_machine(); + + // Generate the marker string based on actual environment values + if platform_machine.is_empty() { + format!("sys_platform == '{sys_platform}'") + } else { + format!("sys_platform == '{sys_platform}' and platform_machine == '{platform_machine}'") + } + } } impl std::fmt::Display for WheelTagHint { @@ -5517,9 +5544,11 @@ impl std::fmt::Display for WheelTagHint { version, tags, best, + markers, } => { let s = if tags.len() == 1 { "" } else { "s" }; if let Some(best) = best { + let example_marker = Self::suggest_environment_marker(markers); let best = if let Some(pretty) = best.pretty() { format!("{} (`{}`)", pretty.cyan(), best.cyan()) } else { @@ -5530,9 +5559,9 @@ impl std::fmt::Display for WheelTagHint { } else { format!("`{}`", package.cyan()) }; - writeln!( + write!( f, - "{}{} You're on {}, but {} only has wheels for the following platform{s}: {}; consider adding your platform to `{}` to ensure uv resolves to a version with compatible wheels", + "{}{} You're on {}, but {} only has wheels for the following platform{s}: {}; consider adding {} to `{}` to ensure uv resolves to a version with compatible wheels", "hint".bold().cyan(), ":".bold(), best, @@ -5540,6 +5569,7 @@ impl std::fmt::Display for WheelTagHint { tags.iter() .map(|tag| format!("`{}`", tag.cyan())) .join(", "), + format!("\"{example_marker}\"").cyan(), "tool.uv.required-environments".green() ) } else { diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index b8a105522..9eff538a2 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -1206,6 +1206,7 @@ impl ValidatedLock { dependency_metadata, indexes, interpreter.tags()?, + interpreter.markers(), hasher, index, database, diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 7347d8fb6..566011a9e 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -12077,8 +12077,12 @@ fn sync_required_environment_hint() -> Result<()> { r"You're on [^ ]+ \(`.*`\)", "You're on [PLATFORM] (`[TAG]`)", )); + filters.push(( + r"sys_platform == '[^']+' and platform_machine == '[^']+'", + "sys_platform == '[PLATFORM]' and platform_machine == '[MACHINE]'", + )); - uv_snapshot!(filters, context.sync().env_remove(EnvVars::UV_EXCLUDE_NEWER), @r" + uv_snapshot!(filters, context.sync().env_remove(EnvVars::UV_EXCLUDE_NEWER), @r#" success: false exit_code: 2 ----- stdout ----- @@ -12087,8 +12091,8 @@ fn sync_required_environment_hint() -> Result<()> { Resolved 2 packages in [TIME] error: Distribution `no-sdist-no-wheels-with-matching-platform-a==1.0.0 @ registry+https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/` can't be installed because it doesn't have a source distribution or wheel for the current platform - hint: You're on [PLATFORM] (`[TAG]`), but `no-sdist-no-wheels-with-matching-platform-a` (v1.0.0) only has wheels for the following platform: `macosx_10_0_ppc64`; consider adding your platform to `tool.uv.required-environments` to ensure uv resolves to a version with compatible wheels - "); + hint: You're on [PLATFORM] (`[TAG]`), but `no-sdist-no-wheels-with-matching-platform-a` (v1.0.0) only has wheels for the following platform: `macosx_10_0_ppc64`; consider adding "sys_platform == '[PLATFORM]' and platform_machine == '[MACHINE]'" to `tool.uv.required-environments` to ensure uv resolves to a version with compatible wheels + "#); Ok(()) }