mirror of https://github.com/astral-sh/uv
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
This commit is contained in:
parent
2b0407e277
commit
ed3f99a119
|
|
@ -1144,13 +1144,13 @@ impl<'lock> PylockToml {
|
||||||
kind: Box::new(PylockTomlErrorKind::IncompatibleWheelOnly(
|
kind: Box::new(PylockTomlErrorKind::IncompatibleWheelOnly(
|
||||||
package.name.clone(),
|
package.name.clone(),
|
||||||
)),
|
)),
|
||||||
hint: package.tag_hint(tags),
|
hint: package.tag_hint(tags, markers),
|
||||||
}),
|
}),
|
||||||
(false, false) => Err(PylockTomlError {
|
(false, false) => Err(PylockTomlError {
|
||||||
kind: Box::new(PylockTomlErrorKind::NeitherSourceDistNorWheel(
|
kind: Box::new(PylockTomlErrorKind::NeitherSourceDistNorWheel(
|
||||||
package.name.clone(),
|
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.
|
/// Generate a [`WheelTagHint`] based on wheel-tag incompatibilities.
|
||||||
fn tag_hint(&self, tags: &Tags) -> Option<WheelTagHint> {
|
fn tag_hint(&self, tags: &Tags, markers: &MarkerEnvironment) -> Option<WheelTagHint> {
|
||||||
let filenames = self
|
let filenames = self
|
||||||
.wheels
|
.wheels
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -1287,7 +1287,7 @@ impl PylockTomlPackage {
|
||||||
.filter_map(|wheel| wheel.filename(&self.name).ok())
|
.filter_map(|wheel| wheel.filename(&self.name).ok())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let filenames = filenames.iter().map(Cow::as_ref).collect::<Vec<_>>();
|
let filenames = filenames.iter().map(Cow::as_ref).collect::<Vec<_>>();
|
||||||
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.
|
/// Returns the [`ResolvedRepositoryReference`] for the package, if it is a Git source.
|
||||||
|
|
|
||||||
|
|
@ -107,9 +107,9 @@ pub trait Installable<'lock> {
|
||||||
|
|
||||||
// Add the workspace package to the graph.
|
// Add the workspace package to the graph.
|
||||||
let index = petgraph.add_node(if groups.prod() {
|
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 {
|
} else {
|
||||||
self.non_installable_node(dist, tags)?
|
self.non_installable_node(dist, tags, marker_env)?
|
||||||
});
|
});
|
||||||
inverse.insert(&dist.id, index);
|
inverse.insert(&dist.id, index);
|
||||||
|
|
||||||
|
|
@ -162,6 +162,7 @@ pub trait Installable<'lock> {
|
||||||
tags,
|
tags,
|
||||||
build_options,
|
build_options,
|
||||||
install_options,
|
install_options,
|
||||||
|
marker_env,
|
||||||
)?);
|
)?);
|
||||||
entry.insert(index);
|
entry.insert(index);
|
||||||
index
|
index
|
||||||
|
|
@ -178,6 +179,7 @@ pub trait Installable<'lock> {
|
||||||
tags,
|
tags,
|
||||||
build_options,
|
build_options,
|
||||||
install_options,
|
install_options,
|
||||||
|
marker_env,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
index
|
index
|
||||||
|
|
@ -226,9 +228,9 @@ pub trait Installable<'lock> {
|
||||||
|
|
||||||
// Add the package to the graph.
|
// Add the package to the graph.
|
||||||
let index = petgraph.add_node(if groups.prod() {
|
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 {
|
} else {
|
||||||
self.non_installable_node(dist, tags)?
|
self.non_installable_node(dist, tags, marker_env)?
|
||||||
});
|
});
|
||||||
inverse.insert(&dist.id, index);
|
inverse.insert(&dist.id, index);
|
||||||
|
|
||||||
|
|
@ -284,6 +286,7 @@ pub trait Installable<'lock> {
|
||||||
tags,
|
tags,
|
||||||
build_options,
|
build_options,
|
||||||
install_options,
|
install_options,
|
||||||
|
marker_env,
|
||||||
)?);
|
)?);
|
||||||
entry.insert(index);
|
entry.insert(index);
|
||||||
index
|
index
|
||||||
|
|
@ -295,7 +298,13 @@ pub trait Installable<'lock> {
|
||||||
let index = *entry.get();
|
let index = *entry.get();
|
||||||
let node = &mut petgraph[index];
|
let node = &mut petgraph[index];
|
||||||
if !groups.prod() {
|
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
|
index
|
||||||
}
|
}
|
||||||
|
|
@ -475,6 +484,7 @@ pub trait Installable<'lock> {
|
||||||
tags,
|
tags,
|
||||||
build_options,
|
build_options,
|
||||||
install_options,
|
install_options,
|
||||||
|
marker_env,
|
||||||
)?);
|
)?);
|
||||||
entry.insert(index);
|
entry.insert(index);
|
||||||
index
|
index
|
||||||
|
|
@ -514,12 +524,14 @@ pub trait Installable<'lock> {
|
||||||
&self,
|
&self,
|
||||||
package: &Package,
|
package: &Package,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
|
marker_env: &ResolverMarkerEnvironment,
|
||||||
build_options: &BuildOptions,
|
build_options: &BuildOptions,
|
||||||
) -> Result<Node, LockError> {
|
) -> Result<Node, LockError> {
|
||||||
let dist = package.to_dist(
|
let dist = package.to_dist(
|
||||||
self.install_path(),
|
self.install_path(),
|
||||||
TagPolicy::Required(tags),
|
TagPolicy::Required(tags),
|
||||||
build_options,
|
build_options,
|
||||||
|
marker_env,
|
||||||
)?;
|
)?;
|
||||||
let version = package.version().cloned();
|
let version = package.version().cloned();
|
||||||
let dist = ResolvedDist::Installable {
|
let dist = ResolvedDist::Installable {
|
||||||
|
|
@ -535,11 +547,17 @@ pub trait Installable<'lock> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a non-installable [`Node`] from a [`Package`].
|
/// Create a non-installable [`Node`] from a [`Package`].
|
||||||
fn non_installable_node(&self, package: &Package, tags: &Tags) -> Result<Node, LockError> {
|
fn non_installable_node(
|
||||||
|
&self,
|
||||||
|
package: &Package,
|
||||||
|
tags: &Tags,
|
||||||
|
marker_env: &ResolverMarkerEnvironment,
|
||||||
|
) -> Result<Node, LockError> {
|
||||||
let dist = package.to_dist(
|
let dist = package.to_dist(
|
||||||
self.install_path(),
|
self.install_path(),
|
||||||
TagPolicy::Preferred(tags),
|
TagPolicy::Preferred(tags),
|
||||||
&BuildOptions::default(),
|
&BuildOptions::default(),
|
||||||
|
marker_env,
|
||||||
)?;
|
)?;
|
||||||
let version = package.version().cloned();
|
let version = package.version().cloned();
|
||||||
let dist = ResolvedDist::Installable {
|
let dist = ResolvedDist::Installable {
|
||||||
|
|
@ -561,15 +579,16 @@ pub trait Installable<'lock> {
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
build_options: &BuildOptions,
|
build_options: &BuildOptions,
|
||||||
install_options: &InstallOptions,
|
install_options: &InstallOptions,
|
||||||
|
marker_env: &ResolverMarkerEnvironment,
|
||||||
) -> Result<Node, LockError> {
|
) -> Result<Node, LockError> {
|
||||||
if install_options.include_package(
|
if install_options.include_package(
|
||||||
package.as_install_target(),
|
package.as_install_target(),
|
||||||
self.project_name(),
|
self.project_name(),
|
||||||
self.lock().members(),
|
self.lock().members(),
|
||||||
) {
|
) {
|
||||||
self.installable_node(package, tags, build_options)
|
self.installable_node(package, tags, marker_env, build_options)
|
||||||
} else {
|
} else {
|
||||||
self.non_installable_node(package, tags)
|
self.non_installable_node(package, tags, marker_env)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1452,6 +1452,7 @@ impl Lock {
|
||||||
dependency_metadata: &DependencyMetadata,
|
dependency_metadata: &DependencyMetadata,
|
||||||
indexes: Option<&IndexLocations>,
|
indexes: Option<&IndexLocations>,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
|
markers: &MarkerEnvironment,
|
||||||
hasher: &HashStrategy,
|
hasher: &HashStrategy,
|
||||||
index: &InMemoryIndex,
|
index: &InMemoryIndex,
|
||||||
database: &DistributionDatabase<'_, Context>,
|
database: &DistributionDatabase<'_, Context>,
|
||||||
|
|
@ -1724,8 +1725,12 @@ impl Lock {
|
||||||
|
|
||||||
if let Some(version) = package.id.version.as_ref() {
|
if let Some(version) = package.id.version.as_ref() {
|
||||||
// For a non-dynamic package, fetch the metadata from the distribution database.
|
// For a non-dynamic package, fetch the metadata from the distribution database.
|
||||||
let dist =
|
let dist = package.to_dist(
|
||||||
package.to_dist(root, TagPolicy::Preferred(tags), &BuildOptions::default())?;
|
root,
|
||||||
|
TagPolicy::Preferred(tags),
|
||||||
|
&BuildOptions::default(),
|
||||||
|
markers,
|
||||||
|
)?;
|
||||||
|
|
||||||
let metadata = {
|
let metadata = {
|
||||||
let id = dist.version_id();
|
let id = dist.version_id();
|
||||||
|
|
@ -1875,6 +1880,7 @@ impl Lock {
|
||||||
root,
|
root,
|
||||||
TagPolicy::Preferred(tags),
|
TagPolicy::Preferred(tags),
|
||||||
&BuildOptions::default(),
|
&BuildOptions::default(),
|
||||||
|
markers,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let metadata = {
|
let metadata = {
|
||||||
|
|
@ -2511,6 +2517,7 @@ impl Package {
|
||||||
workspace_root: &Path,
|
workspace_root: &Path,
|
||||||
tag_policy: TagPolicy<'_>,
|
tag_policy: TagPolicy<'_>,
|
||||||
build_options: &BuildOptions,
|
build_options: &BuildOptions,
|
||||||
|
markers: &MarkerEnvironment,
|
||||||
) -> Result<Dist, LockError> {
|
) -> Result<Dist, LockError> {
|
||||||
let no_binary = build_options.no_binary_package(&self.id.name);
|
let no_binary = build_options.no_binary_package(&self.id.name);
|
||||||
let no_build = build_options.no_build_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 {
|
kind: Box::new(LockErrorKind::IncompatibleWheelOnly {
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
}),
|
}),
|
||||||
hint: self.tag_hint(tag_policy),
|
hint: self.tag_hint(tag_policy, markers),
|
||||||
}),
|
}),
|
||||||
(false, false) => Err(LockError {
|
(false, false) => Err(LockError {
|
||||||
kind: Box::new(LockErrorKind::NeitherSourceDistNorWheel {
|
kind: Box::new(LockErrorKind::NeitherSourceDistNorWheel {
|
||||||
id: self.id.clone(),
|
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.
|
/// Generate a [`WheelTagHint`] based on wheel-tag incompatibilities.
|
||||||
fn tag_hint(&self, tag_policy: TagPolicy<'_>) -> Option<WheelTagHint> {
|
fn tag_hint(
|
||||||
|
&self,
|
||||||
|
tag_policy: TagPolicy<'_>,
|
||||||
|
markers: &MarkerEnvironment,
|
||||||
|
) -> Option<WheelTagHint> {
|
||||||
let filenames = self
|
let filenames = self
|
||||||
.wheels
|
.wheels
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -2636,6 +2647,7 @@ impl Package {
|
||||||
self.id.version.as_ref(),
|
self.id.version.as_ref(),
|
||||||
&filenames,
|
&filenames,
|
||||||
tag_policy.tags(),
|
tag_policy.tags(),
|
||||||
|
markers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5260,6 +5272,7 @@ enum WheelTagHint {
|
||||||
version: Option<Version>,
|
version: Option<Version>,
|
||||||
tags: BTreeSet<PlatformTag>,
|
tags: BTreeSet<PlatformTag>,
|
||||||
best: Option<PlatformTag>,
|
best: Option<PlatformTag>,
|
||||||
|
markers: MarkerEnvironment,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5270,6 +5283,7 @@ impl WheelTagHint {
|
||||||
version: Option<&Version>,
|
version: Option<&Version>,
|
||||||
filenames: &[&WheelFilename],
|
filenames: &[&WheelFilename],
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
|
markers: &MarkerEnvironment,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let incompatibility = filenames
|
let incompatibility = filenames
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -5322,17 +5336,18 @@ impl WheelTagHint {
|
||||||
}
|
}
|
||||||
TagCompatibility::Incompatible(IncompatibleTag::Platform) => {
|
TagCompatibility::Incompatible(IncompatibleTag::Platform) => {
|
||||||
let best = tags.platform_tag().cloned();
|
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()
|
.cloned()
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
if tags.is_empty() {
|
if incompatible_tags.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(Self::PlatformTags {
|
Some(Self::PlatformTags {
|
||||||
package: name.clone(),
|
package: name.clone(),
|
||||||
version: version.cloned(),
|
version: version.cloned(),
|
||||||
tags,
|
tags: incompatible_tags,
|
||||||
best,
|
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 {
|
impl std::fmt::Display for WheelTagHint {
|
||||||
|
|
@ -5517,9 +5544,11 @@ impl std::fmt::Display for WheelTagHint {
|
||||||
version,
|
version,
|
||||||
tags,
|
tags,
|
||||||
best,
|
best,
|
||||||
|
markers,
|
||||||
} => {
|
} => {
|
||||||
let s = if tags.len() == 1 { "" } else { "s" };
|
let s = if tags.len() == 1 { "" } else { "s" };
|
||||||
if let Some(best) = best {
|
if let Some(best) = best {
|
||||||
|
let example_marker = Self::suggest_environment_marker(markers);
|
||||||
let best = if let Some(pretty) = best.pretty() {
|
let best = if let Some(pretty) = best.pretty() {
|
||||||
format!("{} (`{}`)", pretty.cyan(), best.cyan())
|
format!("{} (`{}`)", pretty.cyan(), best.cyan())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -5530,9 +5559,9 @@ impl std::fmt::Display for WheelTagHint {
|
||||||
} else {
|
} else {
|
||||||
format!("`{}`", package.cyan())
|
format!("`{}`", package.cyan())
|
||||||
};
|
};
|
||||||
writeln!(
|
write!(
|
||||||
f,
|
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(),
|
"hint".bold().cyan(),
|
||||||
":".bold(),
|
":".bold(),
|
||||||
best,
|
best,
|
||||||
|
|
@ -5540,6 +5569,7 @@ impl std::fmt::Display for WheelTagHint {
|
||||||
tags.iter()
|
tags.iter()
|
||||||
.map(|tag| format!("`{}`", tag.cyan()))
|
.map(|tag| format!("`{}`", tag.cyan()))
|
||||||
.join(", "),
|
.join(", "),
|
||||||
|
format!("\"{example_marker}\"").cyan(),
|
||||||
"tool.uv.required-environments".green()
|
"tool.uv.required-environments".green()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1206,6 +1206,7 @@ impl ValidatedLock {
|
||||||
dependency_metadata,
|
dependency_metadata,
|
||||||
indexes,
|
indexes,
|
||||||
interpreter.tags()?,
|
interpreter.tags()?,
|
||||||
|
interpreter.markers(),
|
||||||
hasher,
|
hasher,
|
||||||
index,
|
index,
|
||||||
database,
|
database,
|
||||||
|
|
|
||||||
|
|
@ -12077,8 +12077,12 @@ fn sync_required_environment_hint() -> Result<()> {
|
||||||
r"You're on [^ ]+ \(`.*`\)",
|
r"You're on [^ ]+ \(`.*`\)",
|
||||||
"You're on [PLATFORM] (`[TAG]`)",
|
"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
|
success: false
|
||||||
exit_code: 2
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -12087,8 +12091,8 @@ fn sync_required_environment_hint() -> Result<()> {
|
||||||
Resolved 2 packages in [TIME]
|
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
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue