Skip to content

Commit ed3f99a

Browse files
authored
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
1 parent 2b0407e commit ed3f99a

File tree

5 files changed

+79
-25
lines changed

5 files changed

+79
-25
lines changed

crates/uv-resolver/src/lock/export/pylock_toml.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,13 +1144,13 @@ impl<'lock> PylockToml {
11441144
kind: Box::new(PylockTomlErrorKind::IncompatibleWheelOnly(
11451145
package.name.clone(),
11461146
)),
1147-
hint: package.tag_hint(tags),
1147+
hint: package.tag_hint(tags, markers),
11481148
}),
11491149
(false, false) => Err(PylockTomlError {
11501150
kind: Box::new(PylockTomlErrorKind::NeitherSourceDistNorWheel(
11511151
package.name.clone(),
11521152
)),
1153-
hint: package.tag_hint(tags),
1153+
hint: package.tag_hint(tags, markers),
11541154
}),
11551155
};
11561156
};
@@ -1279,15 +1279,15 @@ impl PylockTomlPackage {
12791279
}
12801280

12811281
/// Generate a [`WheelTagHint`] based on wheel-tag incompatibilities.
1282-
fn tag_hint(&self, tags: &Tags) -> Option<WheelTagHint> {
1282+
fn tag_hint(&self, tags: &Tags, markers: &MarkerEnvironment) -> Option<WheelTagHint> {
12831283
let filenames = self
12841284
.wheels
12851285
.iter()
12861286
.flatten()
12871287
.filter_map(|wheel| wheel.filename(&self.name).ok())
12881288
.collect::<Vec<_>>();
12891289
let filenames = filenames.iter().map(Cow::as_ref).collect::<Vec<_>>();
1290-
WheelTagHint::from_wheels(&self.name, self.version.as_ref(), &filenames, tags)
1290+
WheelTagHint::from_wheels(&self.name, self.version.as_ref(), &filenames, tags, markers)
12911291
}
12921292

12931293
/// Returns the [`ResolvedRepositoryReference`] for the package, if it is a Git source.

crates/uv-resolver/src/lock/installable.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ pub trait Installable<'lock> {
107107

108108
// Add the workspace package to the graph.
109109
let index = petgraph.add_node(if groups.prod() {
110-
self.package_to_node(dist, tags, build_options, install_options)?
110+
self.package_to_node(dist, tags, build_options, install_options, marker_env)?
111111
} else {
112-
self.non_installable_node(dist, tags)?
112+
self.non_installable_node(dist, tags, marker_env)?
113113
});
114114
inverse.insert(&dist.id, index);
115115

@@ -162,6 +162,7 @@ pub trait Installable<'lock> {
162162
tags,
163163
build_options,
164164
install_options,
165+
marker_env,
165166
)?);
166167
entry.insert(index);
167168
index
@@ -178,6 +179,7 @@ pub trait Installable<'lock> {
178179
tags,
179180
build_options,
180181
install_options,
182+
marker_env,
181183
)?;
182184
}
183185
index
@@ -226,9 +228,9 @@ pub trait Installable<'lock> {
226228

227229
// Add the package to the graph.
228230
let index = petgraph.add_node(if groups.prod() {
229-
self.package_to_node(dist, tags, build_options, install_options)?
231+
self.package_to_node(dist, tags, build_options, install_options, marker_env)?
230232
} else {
231-
self.non_installable_node(dist, tags)?
233+
self.non_installable_node(dist, tags, marker_env)?
232234
});
233235
inverse.insert(&dist.id, index);
234236

@@ -284,6 +286,7 @@ pub trait Installable<'lock> {
284286
tags,
285287
build_options,
286288
install_options,
289+
marker_env,
287290
)?);
288291
entry.insert(index);
289292
index
@@ -295,7 +298,13 @@ pub trait Installable<'lock> {
295298
let index = *entry.get();
296299
let node = &mut petgraph[index];
297300
if !groups.prod() {
298-
*node = self.package_to_node(dist, tags, build_options, install_options)?;
301+
*node = self.package_to_node(
302+
dist,
303+
tags,
304+
build_options,
305+
install_options,
306+
marker_env,
307+
)?;
299308
}
300309
index
301310
}
@@ -475,6 +484,7 @@ pub trait Installable<'lock> {
475484
tags,
476485
build_options,
477486
install_options,
487+
marker_env,
478488
)?);
479489
entry.insert(index);
480490
index
@@ -514,12 +524,14 @@ pub trait Installable<'lock> {
514524
&self,
515525
package: &Package,
516526
tags: &Tags,
527+
marker_env: &ResolverMarkerEnvironment,
517528
build_options: &BuildOptions,
518529
) -> Result<Node, LockError> {
519530
let dist = package.to_dist(
520531
self.install_path(),
521532
TagPolicy::Required(tags),
522533
build_options,
534+
marker_env,
523535
)?;
524536
let version = package.version().cloned();
525537
let dist = ResolvedDist::Installable {
@@ -535,11 +547,17 @@ pub trait Installable<'lock> {
535547
}
536548

537549
/// Create a non-installable [`Node`] from a [`Package`].
538-
fn non_installable_node(&self, package: &Package, tags: &Tags) -> Result<Node, LockError> {
550+
fn non_installable_node(
551+
&self,
552+
package: &Package,
553+
tags: &Tags,
554+
marker_env: &ResolverMarkerEnvironment,
555+
) -> Result<Node, LockError> {
539556
let dist = package.to_dist(
540557
self.install_path(),
541558
TagPolicy::Preferred(tags),
542559
&BuildOptions::default(),
560+
marker_env,
543561
)?;
544562
let version = package.version().cloned();
545563
let dist = ResolvedDist::Installable {
@@ -561,15 +579,16 @@ pub trait Installable<'lock> {
561579
tags: &Tags,
562580
build_options: &BuildOptions,
563581
install_options: &InstallOptions,
582+
marker_env: &ResolverMarkerEnvironment,
564583
) -> Result<Node, LockError> {
565584
if install_options.include_package(
566585
package.as_install_target(),
567586
self.project_name(),
568587
self.lock().members(),
569588
) {
570-
self.installable_node(package, tags, build_options)
589+
self.installable_node(package, tags, marker_env, build_options)
571590
} else {
572-
self.non_installable_node(package, tags)
591+
self.non_installable_node(package, tags, marker_env)
573592
}
574593
}
575594
}

crates/uv-resolver/src/lock/mod.rs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,7 @@ impl Lock {
14521452
dependency_metadata: &DependencyMetadata,
14531453
indexes: Option<&IndexLocations>,
14541454
tags: &Tags,
1455+
markers: &MarkerEnvironment,
14551456
hasher: &HashStrategy,
14561457
index: &InMemoryIndex,
14571458
database: &DistributionDatabase<'_, Context>,
@@ -1724,8 +1725,12 @@ impl Lock {
17241725

17251726
if let Some(version) = package.id.version.as_ref() {
17261727
// For a non-dynamic package, fetch the metadata from the distribution database.
1727-
let dist =
1728-
package.to_dist(root, TagPolicy::Preferred(tags), &BuildOptions::default())?;
1728+
let dist = package.to_dist(
1729+
root,
1730+
TagPolicy::Preferred(tags),
1731+
&BuildOptions::default(),
1732+
markers,
1733+
)?;
17291734

17301735
let metadata = {
17311736
let id = dist.version_id();
@@ -1875,6 +1880,7 @@ impl Lock {
18751880
root,
18761881
TagPolicy::Preferred(tags),
18771882
&BuildOptions::default(),
1883+
markers,
18781884
)?;
18791885

18801886
let metadata = {
@@ -2511,6 +2517,7 @@ impl Package {
25112517
workspace_root: &Path,
25122518
tag_policy: TagPolicy<'_>,
25132519
build_options: &BuildOptions,
2520+
markers: &MarkerEnvironment,
25142521
) -> Result<Dist, LockError> {
25152522
let no_binary = build_options.no_binary_package(&self.id.name);
25162523
let no_build = build_options.no_build_package(&self.id.name);
@@ -2613,19 +2620,23 @@ impl Package {
26132620
kind: Box::new(LockErrorKind::IncompatibleWheelOnly {
26142621
id: self.id.clone(),
26152622
}),
2616-
hint: self.tag_hint(tag_policy),
2623+
hint: self.tag_hint(tag_policy, markers),
26172624
}),
26182625
(false, false) => Err(LockError {
26192626
kind: Box::new(LockErrorKind::NeitherSourceDistNorWheel {
26202627
id: self.id.clone(),
26212628
}),
2622-
hint: self.tag_hint(tag_policy),
2629+
hint: self.tag_hint(tag_policy, markers),
26232630
}),
26242631
}
26252632
}
26262633

26272634
/// Generate a [`WheelTagHint`] based on wheel-tag incompatibilities.
2628-
fn tag_hint(&self, tag_policy: TagPolicy<'_>) -> Option<WheelTagHint> {
2635+
fn tag_hint(
2636+
&self,
2637+
tag_policy: TagPolicy<'_>,
2638+
markers: &MarkerEnvironment,
2639+
) -> Option<WheelTagHint> {
26292640
let filenames = self
26302641
.wheels
26312642
.iter()
@@ -2636,6 +2647,7 @@ impl Package {
26362647
self.id.version.as_ref(),
26372648
&filenames,
26382649
tag_policy.tags(),
2650+
markers,
26392651
)
26402652
}
26412653

@@ -5260,6 +5272,7 @@ enum WheelTagHint {
52605272
version: Option<Version>,
52615273
tags: BTreeSet<PlatformTag>,
52625274
best: Option<PlatformTag>,
5275+
markers: MarkerEnvironment,
52635276
},
52645277
}
52655278

@@ -5270,6 +5283,7 @@ impl WheelTagHint {
52705283
version: Option<&Version>,
52715284
filenames: &[&WheelFilename],
52725285
tags: &Tags,
5286+
markers: &MarkerEnvironment,
52735287
) -> Option<Self> {
52745288
let incompatibility = filenames
52755289
.iter()
@@ -5322,17 +5336,18 @@ impl WheelTagHint {
53225336
}
53235337
TagCompatibility::Incompatible(IncompatibleTag::Platform) => {
53245338
let best = tags.platform_tag().cloned();
5325-
let tags = Self::platform_tags(filenames.iter().copied(), tags)
5339+
let incompatible_tags = Self::platform_tags(filenames.iter().copied(), tags)
53265340
.cloned()
53275341
.collect::<BTreeSet<_>>();
5328-
if tags.is_empty() {
5342+
if incompatible_tags.is_empty() {
53295343
None
53305344
} else {
53315345
Some(Self::PlatformTags {
53325346
package: name.clone(),
53335347
version: version.cloned(),
5334-
tags,
5348+
tags: incompatible_tags,
53355349
best,
5350+
markers: markers.clone(),
53365351
})
53375352
}
53385353
}
@@ -5373,6 +5388,18 @@ impl WheelTagHint {
53735388
}
53745389
})
53755390
}
5391+
5392+
fn suggest_environment_marker(markers: &MarkerEnvironment) -> String {
5393+
let sys_platform = markers.sys_platform();
5394+
let platform_machine = markers.platform_machine();
5395+
5396+
// Generate the marker string based on actual environment values
5397+
if platform_machine.is_empty() {
5398+
format!("sys_platform == '{sys_platform}'")
5399+
} else {
5400+
format!("sys_platform == '{sys_platform}' and platform_machine == '{platform_machine}'")
5401+
}
5402+
}
53765403
}
53775404

53785405
impl std::fmt::Display for WheelTagHint {
@@ -5517,9 +5544,11 @@ impl std::fmt::Display for WheelTagHint {
55175544
version,
55185545
tags,
55195546
best,
5547+
markers,
55205548
} => {
55215549
let s = if tags.len() == 1 { "" } else { "s" };
55225550
if let Some(best) = best {
5551+
let example_marker = Self::suggest_environment_marker(markers);
55235552
let best = if let Some(pretty) = best.pretty() {
55245553
format!("{} (`{}`)", pretty.cyan(), best.cyan())
55255554
} else {
@@ -5530,16 +5559,17 @@ impl std::fmt::Display for WheelTagHint {
55305559
} else {
55315560
format!("`{}`", package.cyan())
55325561
};
5533-
writeln!(
5562+
write!(
55345563
f,
5535-
"{}{} 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",
5564+
"{}{} 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",
55365565
"hint".bold().cyan(),
55375566
":".bold(),
55385567
best,
55395568
package_ref,
55405569
tags.iter()
55415570
.map(|tag| format!("`{}`", tag.cyan()))
55425571
.join(", "),
5572+
format!("\"{example_marker}\"").cyan(),
55435573
"tool.uv.required-environments".green()
55445574
)
55455575
} else {

crates/uv/src/commands/project/lock.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,7 @@ impl ValidatedLock {
12061206
dependency_metadata,
12071207
indexes,
12081208
interpreter.tags()?,
1209+
interpreter.markers(),
12091210
hasher,
12101211
index,
12111212
database,

crates/uv/tests/it/sync.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12077,8 +12077,12 @@ fn sync_required_environment_hint() -> Result<()> {
1207712077
r"You're on [^ ]+ \(`.*`\)",
1207812078
"You're on [PLATFORM] (`[TAG]`)",
1207912079
));
12080+
filters.push((
12081+
r"sys_platform == '[^']+' and platform_machine == '[^']+'",
12082+
"sys_platform == '[PLATFORM]' and platform_machine == '[MACHINE]'",
12083+
));
1208012084

12081-
uv_snapshot!(filters, context.sync().env_remove(EnvVars::UV_EXCLUDE_NEWER), @r"
12085+
uv_snapshot!(filters, context.sync().env_remove(EnvVars::UV_EXCLUDE_NEWER), @r#"
1208212086
success: false
1208312087
exit_code: 2
1208412088
----- stdout -----
@@ -12087,8 +12091,8 @@ fn sync_required_environment_hint() -> Result<()> {
1208712091
Resolved 2 packages in [TIME]
1208812092
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
1208912093
12090-
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
12091-
");
12094+
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
12095+
"#);
1209212096

1209312097
Ok(())
1209412098
}

0 commit comments

Comments
 (0)