Ignore tags in universal resolution (#4174)

## Summary

If a package lacks a source distribution, and we can't find a compatible
wheel for the current platform, we need to just _assume_ that the
package will have a valid wheel on all platforms on which it's
requested; if not, we raise an error at install time.

It's possible that we can be smarter about this over time. For example,
if the package was requested _only_ for macOS, we could verify that
there's at least one macOS-compatible wheel. See the linked issue for
more details.

Closes https://github.com/astral-sh/uv/issues/4139.
This commit is contained in:
Charlie Marsh 2024-06-10 05:38:21 -07:00 committed by GitHub
parent bbd961c251
commit 5269a0dba8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 42 additions and 40 deletions

View file

@ -162,7 +162,7 @@ mod resolver {
Options::default(), Options::default(),
&python_requirement, &python_requirement,
Some(&MARKERS), Some(&MARKERS),
&TAGS, Some(&TAGS),
&flat_index, &flat_index,
&index, &index,
&hashes, &hashes,

View file

@ -44,7 +44,7 @@ pub enum CompatibleDist<'a> {
/// The wheel that should be used. /// The wheel that should be used.
wheel: &'a RegistryBuiltWheel, wheel: &'a RegistryBuiltWheel,
/// The platform priority associated with the wheel. /// The platform priority associated with the wheel.
priority: TagPriority, priority: Option<TagPriority>,
/// The prioritized distribution that the wheel came from. /// The prioritized distribution that the wheel came from.
prioritized: &'a PrioritizedDist, prioritized: &'a PrioritizedDist,
}, },
@ -145,7 +145,7 @@ pub enum PythonRequirementKind {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum WheelCompatibility { pub enum WheelCompatibility {
Incompatible(IncompatibleWheel), Incompatible(IncompatibleWheel),
Compatible(HashComparison, TagPriority, Option<BuildTag>), Compatible(HashComparison, Option<TagPriority>, Option<BuildTag>),
} }
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]

View file

@ -158,7 +158,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
self.options, self.options,
&python_requirement, &python_requirement,
Some(markers), Some(markers),
tags, Some(tags),
self.flat_index, self.flat_index,
self.index, self.index,
&HashStrategy::None, &HashStrategy::None,

View file

@ -34,7 +34,7 @@ impl FlatIndex {
#[instrument(skip_all)] #[instrument(skip_all)]
pub fn from_entries( pub fn from_entries(
entries: FlatIndexEntries, entries: FlatIndexEntries,
tags: &Tags, tags: Option<&Tags>,
hasher: &HashStrategy, hasher: &HashStrategy,
no_build: &NoBuild, no_build: &NoBuild,
no_binary: &NoBinary, no_binary: &NoBinary,
@ -66,7 +66,7 @@ impl FlatIndex {
distributions: &mut FlatDistributions, distributions: &mut FlatDistributions,
file: File, file: File,
filename: DistFilename, filename: DistFilename,
tags: &Tags, tags: Option<&Tags>,
hasher: &HashStrategy, hasher: &HashStrategy,
no_build: &NoBuild, no_build: &NoBuild,
no_binary: &NoBinary, no_binary: &NoBinary,
@ -152,7 +152,7 @@ impl FlatIndex {
fn wheel_compatibility( fn wheel_compatibility(
filename: &WheelFilename, filename: &WheelFilename,
hashes: &[HashDigest], hashes: &[HashDigest],
tags: &Tags, tags: Option<&Tags>,
hasher: &HashStrategy, hasher: &HashStrategy,
no_binary: &NoBinary, no_binary: &NoBinary,
) -> WheelCompatibility { ) -> WheelCompatibility {
@ -168,11 +168,14 @@ impl FlatIndex {
} }
// Determine a compatibility for the wheel based on tags. // Determine a compatibility for the wheel based on tags.
let priority = match filename.compatibility(tags) { let priority = match tags {
TagCompatibility::Incompatible(tag) => { Some(tags) => match filename.compatibility(tags) {
return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag)) TagCompatibility::Incompatible(tag) => {
} return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag))
TagCompatibility::Compatible(priority) => priority, }
TagCompatibility::Compatible(priority) => Some(priority),
},
None => None,
}; };
// Check if hashes line up. // Check if hashes line up.

View file

@ -131,7 +131,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
options: Options, options: Options,
python_requirement: &'a PythonRequirement, python_requirement: &'a PythonRequirement,
markers: Option<&'a MarkerEnvironment>, markers: Option<&'a MarkerEnvironment>,
tags: &'a Tags, tags: Option<&'a Tags>,
flat_index: &'a FlatIndex, flat_index: &'a FlatIndex,
index: &'a InMemoryIndex, index: &'a InMemoryIndex,
hasher: &'a HashStrategy, hasher: &'a HashStrategy,

View file

@ -74,7 +74,7 @@ pub struct DefaultResolverProvider<'a, Context: BuildContext> {
fetcher: DistributionDatabase<'a, Context>, fetcher: DistributionDatabase<'a, Context>,
/// These are the entries from `--find-links` that act as overrides for index responses. /// These are the entries from `--find-links` that act as overrides for index responses.
flat_index: FlatIndex, flat_index: FlatIndex,
tags: Tags, tags: Option<Tags>,
python_requirement: PythonRequirement, python_requirement: PythonRequirement,
allowed_yanks: AllowedYanks, allowed_yanks: AllowedYanks,
hasher: HashStrategy, hasher: HashStrategy,
@ -89,7 +89,7 @@ impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> {
pub fn new( pub fn new(
fetcher: DistributionDatabase<'a, Context>, fetcher: DistributionDatabase<'a, Context>,
flat_index: &'a FlatIndex, flat_index: &'a FlatIndex,
tags: &'a Tags, tags: Option<&'a Tags>,
python_requirement: PythonRequirement, python_requirement: PythonRequirement,
allowed_yanks: AllowedYanks, allowed_yanks: AllowedYanks,
hasher: &'a HashStrategy, hasher: &'a HashStrategy,
@ -100,7 +100,7 @@ impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> {
Self { Self {
fetcher, fetcher,
flat_index: flat_index.clone(), flat_index: flat_index.clone(),
tags: tags.clone(), tags: tags.cloned(),
python_requirement, python_requirement,
allowed_yanks, allowed_yanks,
hasher: hasher.clone(), hasher: hasher.clone(),
@ -132,7 +132,7 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a,
metadata, metadata,
package_name, package_name,
&index, &index,
&self.tags, self.tags.as_ref(),
&self.python_requirement, &self.python_requirement,
&self.allowed_yanks, &self.allowed_yanks,
&self.hasher, &self.hasher,

View file

@ -45,7 +45,7 @@ impl VersionMap {
simple_metadata: OwnedArchive<SimpleMetadata>, simple_metadata: OwnedArchive<SimpleMetadata>,
package_name: &PackageName, package_name: &PackageName,
index: &IndexUrl, index: &IndexUrl,
tags: &Tags, tags: Option<&Tags>,
python_requirement: &PythonRequirement, python_requirement: &PythonRequirement,
allowed_yanks: &AllowedYanks, allowed_yanks: &AllowedYanks,
hasher: &HashStrategy, hasher: &HashStrategy,
@ -120,7 +120,7 @@ impl VersionMap {
no_binary, no_binary,
no_build, no_build,
index: index.clone(), index: index.clone(),
tags: tags.clone(), tags: tags.cloned(),
python_requirement: python_requirement.clone(), python_requirement: python_requirement.clone(),
exclude_newer: exclude_newer.copied(), exclude_newer: exclude_newer.copied(),
allowed_yanks, allowed_yanks,
@ -298,7 +298,7 @@ struct VersionMapLazy {
index: IndexUrl, index: IndexUrl,
/// The set of compatibility tags that determines whether a wheel is usable /// The set of compatibility tags that determines whether a wheel is usable
/// in the current environment. /// in the current environment.
tags: Tags, tags: Option<Tags>,
/// The version of Python active in the current environment. This is used /// The version of Python active in the current environment. This is used
/// to determine whether a package's Python version constraint (if one /// to determine whether a package's Python version constraint (if one
/// exists) is satisfied or not. /// exists) is satisfied or not.
@ -551,11 +551,14 @@ impl VersionMapLazy {
} }
// Determine a compatibility for the wheel based on tags. // Determine a compatibility for the wheel based on tags.
let priority = match filename.compatibility(&self.tags) { let priority = match &self.tags {
TagCompatibility::Incompatible(tag) => { Some(tags) => match filename.compatibility(tags) {
return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag)) TagCompatibility::Incompatible(tag) => {
} return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag))
TagCompatibility::Compatible(priority) => priority, }
TagCompatibility::Compatible(priority) => Some(priority),
},
None => None,
}; };
// Check if hashes line up. If hashes aren't required, they're considered matching. // Check if hashes line up. If hashes aren't required, they're considered matching.

View file

@ -291,7 +291,7 @@ pub(crate) async fn pip_compile(
let flat_index = { let flat_index = {
let client = FlatIndexClient::new(&client, &cache); let client = FlatIndexClient::new(&client, &cache);
let entries = client.fetch(index_locations.flat_index()).await?; let entries = client.fetch(index_locations.flat_index()).await?;
FlatIndex::from_entries(entries, &tags, &hasher, &no_build, &NoBinary::None) FlatIndex::from_entries(entries, Some(&tags), &hasher, &no_build, &NoBinary::None)
}; };
// Track in-flight downloads, builds, etc., across resolutions. // Track in-flight downloads, builds, etc., across resolutions.
@ -509,7 +509,7 @@ pub(crate) async fn pip_compile(
options, options,
&python_requirement, &python_requirement,
Some(&markers), Some(&markers),
&tags, Some(&tags),
&flat_index, &flat_index,
&top_level_index, &top_level_index,
&hasher, &hasher,

View file

@ -302,7 +302,7 @@ pub(crate) async fn pip_install(
let flat_index = { let flat_index = {
let client = FlatIndexClient::new(&client, &cache); let client = FlatIndexClient::new(&client, &cache);
let entries = client.fetch(index_locations.flat_index()).await?; let entries = client.fetch(index_locations.flat_index()).await?;
FlatIndex::from_entries(entries, &tags, &hasher, &no_build, &no_binary) FlatIndex::from_entries(entries, Some(&tags), &hasher, &no_build, &no_binary)
}; };
// Determine whether to enable build isolation. // Determine whether to enable build isolation.
@ -366,7 +366,7 @@ pub(crate) async fn pip_install(
&reinstall, &reinstall,
&upgrade, &upgrade,
interpreter, interpreter,
&tags, Some(&tags),
Some(&markers), Some(&markers),
None, None,
&client, &client,

View file

@ -88,7 +88,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
reinstall: &Reinstall, reinstall: &Reinstall,
upgrade: &Upgrade, upgrade: &Upgrade,
interpreter: &Interpreter, interpreter: &Interpreter,
tags: &Tags, tags: Option<&Tags>,
markers: Option<&MarkerEnvironment>, markers: Option<&MarkerEnvironment>,
requires_python: Option<&RequiresPython>, requires_python: Option<&RequiresPython>,
client: &RegistryClient, client: &RegistryClient,

View file

@ -245,7 +245,7 @@ pub(crate) async fn pip_sync(
let flat_index = { let flat_index = {
let client = FlatIndexClient::new(&client, &cache); let client = FlatIndexClient::new(&client, &cache);
let entries = client.fetch(index_locations.flat_index()).await?; let entries = client.fetch(index_locations.flat_index()).await?;
FlatIndex::from_entries(entries, &tags, &hasher, &no_build, &no_binary) FlatIndex::from_entries(entries, Some(&tags), &hasher, &no_build, &no_binary)
}; };
// Determine whether to enable build isolation. // Determine whether to enable build isolation.
@ -318,7 +318,7 @@ pub(crate) async fn pip_sync(
reinstall, reinstall,
&upgrade, &upgrade,
interpreter, interpreter,
&tags, Some(&tags),
Some(&markers), Some(&markers),
None, None,
&client, &client,

View file

@ -138,15 +138,11 @@ pub(super) async fn do_lock(
requires_python requires_python
}; };
// Determine the tags and markers to use for resolution.
let tags = interpreter.tags()?;
let markers = interpreter.markers();
// Initialize the registry client. // Initialize the registry client.
// TODO(zanieb): Support client options e.g. offline, tls, etc. // TODO(zanieb): Support client options e.g. offline, tls, etc.
let client = RegistryClientBuilder::new(cache.clone()) let client = RegistryClientBuilder::new(cache.clone())
.index_urls(index_locations.index_urls()) .index_urls(index_locations.index_urls())
.markers(markers) .markers(interpreter.markers())
.platform(interpreter.platform()) .platform(interpreter.platform())
.build(); .build();
@ -214,7 +210,7 @@ pub(super) async fn do_lock(
&reinstall, &reinstall,
&upgrade, &upgrade,
interpreter, interpreter,
tags, None,
None, None,
Some(&requires_python), Some(&requires_python),
&client, &client,

View file

@ -233,7 +233,7 @@ pub(crate) async fn update_environment(
&reinstall, &reinstall,
&upgrade, &upgrade,
interpreter, interpreter,
tags, Some(tags),
Some(markers), Some(markers),
None, None,
&client, &client,

View file

@ -180,7 +180,7 @@ async fn venv_impl(
.map_err(VenvError::FlatIndex)?; .map_err(VenvError::FlatIndex)?;
FlatIndex::from_entries( FlatIndex::from_entries(
entries, entries,
tags, Some(tags),
&HashStrategy::None, &HashStrategy::None,
&NoBuild::All, &NoBuild::All,
&NoBinary::None, &NoBinary::None,