Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to mark platforms as "required" for wheel coverage #10067

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions crates/uv-distribution-types/src/known_platform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use std::fmt::{Display, Formatter};

use uv_pep508::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString};

/// A platform for which the resolver is solving.
#[derive(Debug, Clone, Copy)]
pub enum KnownPlatform {
Linux,
Windows,
MacOS,
}

impl KnownPlatform {
/// Return the platform's `sys.platform` value.
pub fn sys_platform(self) -> &'static str {
match self {
KnownPlatform::Linux => "linux",
KnownPlatform::Windows => "win32",
KnownPlatform::MacOS => "darwin",
}
}

/// Return a [`MarkerTree`] for the platform.
pub fn marker(self) -> MarkerTree {
MarkerTree::expression(MarkerExpression::String {
key: MarkerValueString::SysPlatform,
operator: MarkerOperator::Equal,
value: self.sys_platform().to_string(),
})
}

/// Determine the [`KnownPlatform`] from a marker tree.
pub fn from_marker(marker: MarkerTree) -> Option<KnownPlatform> {
if marker == KnownPlatform::Linux.marker() {
Some(KnownPlatform::Linux)
} else if marker == KnownPlatform::Windows.marker() {
Some(KnownPlatform::Windows)
} else if marker == KnownPlatform::MacOS.marker() {
Some(KnownPlatform::MacOS)
} else {
None
}
}
}

impl Display for KnownPlatform {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
KnownPlatform::Linux => write!(f, "Linux"),
KnownPlatform::Windows => write!(f, "Windows"),
KnownPlatform::MacOS => write!(f, "macOS"),
}
}
}
2 changes: 2 additions & 0 deletions crates/uv-distribution-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub use crate::index::*;
pub use crate::index_name::*;
pub use crate::index_url::*;
pub use crate::installed::*;
pub use crate::known_platform::*;
pub use crate::origin::*;
pub use crate::pip_index::*;
pub use crate::prioritized_distribution::*;
Expand All @@ -87,6 +88,7 @@ mod index;
mod index_name;
mod index_url;
mod installed;
mod known_platform;
mod origin;
mod pip_index;
mod prioritized_distribution;
Expand Down
43 changes: 35 additions & 8 deletions crates/uv-distribution-types/src/prioritized_distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use uv_platform_tags::{IncompatibleTag, TagPriority};
use uv_pypi_types::{HashDigest, Yanked};

use crate::{
InstalledDist, RegistryBuiltDist, RegistryBuiltWheel, RegistrySourceDist, ResolvedDistRef,
InstalledDist, KnownPlatform, RegistryBuiltDist, RegistryBuiltWheel, RegistrySourceDist,
ResolvedDistRef,
};

/// A collection of distributions that have been filtered by relevance.
Expand Down Expand Up @@ -119,6 +120,7 @@ impl IncompatibleDist {
None => format!("has {self}"),
},
IncompatibleWheel::RequiresPython(..) => format!("requires {self}"),
IncompatibleWheel::MissingPlatform(_) => format!("has {self}"),
},
Self::Source(incompatibility) => match incompatibility {
IncompatibleSource::NoBuild => format!("has {self}"),
Expand Down Expand Up @@ -146,6 +148,7 @@ impl IncompatibleDist {
None => format!("have {self}"),
},
IncompatibleWheel::RequiresPython(..) => format!("require {self}"),
IncompatibleWheel::MissingPlatform(_) => format!("have {self}"),
},
Self::Source(incompatibility) => match incompatibility {
IncompatibleSource::NoBuild => format!("have {self}"),
Expand Down Expand Up @@ -196,6 +199,15 @@ impl Display for IncompatibleDist {
IncompatibleWheel::RequiresPython(python, _) => {
write!(f, "Python {python}")
}
IncompatibleWheel::MissingPlatform(marker) => {
if let Some(platform) = KnownPlatform::from_marker(*marker) {
write!(f, "no {platform}-compatible wheels")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

} else if let Some(marker) = marker.try_to_string() {
write!(f, "no `{marker}`-compatible wheels")
} else {
write!(f, "no compatible wheels")
}
}
},
Self::Source(incompatibility) => match incompatibility {
IncompatibleSource::NoBuild => f.write_str("no usable wheels"),
Expand Down Expand Up @@ -248,6 +260,8 @@ pub enum IncompatibleWheel {
Yanked(Yanked),
/// The use of binary wheels is disabled.
NoBinary,
/// Wheels are not available for the current platform.
MissingPlatform(MarkerTree),
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -607,28 +621,41 @@ impl IncompatibleWheel {
timestamp_other < timestamp_self
}
},
Self::NoBinary | Self::RequiresPython(_, _) | Self::Tag(_) | Self::Yanked(_) => {
true
}
Self::MissingPlatform(_)
| Self::NoBinary
| Self::RequiresPython(_, _)
| Self::Tag(_)
| Self::Yanked(_) => true,
},
Self::Tag(tag_self) => match other {
Self::ExcludeNewer(_) => false,
Self::Tag(tag_other) => tag_self > tag_other,
Self::NoBinary | Self::RequiresPython(_, _) | Self::Yanked(_) => true,
Self::MissingPlatform(_)
| Self::NoBinary
| Self::RequiresPython(_, _)
| Self::Yanked(_) => true,
},
Self::RequiresPython(_, _) => match other {
Self::ExcludeNewer(_) | Self::Tag(_) => false,
// Version specifiers cannot be reasonably compared
Self::RequiresPython(_, _) => false,
Self::NoBinary | Self::Yanked(_) => true,
Self::MissingPlatform(_) | Self::NoBinary | Self::Yanked(_) => true,
},
Self::Yanked(_) => match other {
Self::ExcludeNewer(_) | Self::Tag(_) | Self::RequiresPython(_, _) => false,
// Yanks with a reason are more helpful for errors
Self::Yanked(yanked_other) => matches!(yanked_other, Yanked::Reason(_)),
Self::NoBinary => true,
Self::MissingPlatform(_) | Self::NoBinary => true,
},
Self::NoBinary => match other {
Self::ExcludeNewer(_)
| Self::Tag(_)
| Self::RequiresPython(_, _)
| Self::Yanked(_) => false,
Self::NoBinary => false,
Self::MissingPlatform(_) => true,
},
Self::NoBinary => false,
Self::MissingPlatform(_) => false,
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions crates/uv-pypi-types/src/supported_environments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ use uv_pep508::MarkerTree;
pub struct SupportedEnvironments(Vec<MarkerTree>);

impl SupportedEnvironments {
/// Create a new [`SupportedEnvironments`] struct from a list of marker trees.
pub fn from_markers(markers: Vec<MarkerTree>) -> Self {
SupportedEnvironments(markers)
}

/// Return the list of marker trees.
pub fn as_markers(&self) -> &[MarkerTree] {
&self.0
Expand All @@ -18,6 +23,19 @@ impl SupportedEnvironments {
pub fn into_markers(self) -> Vec<MarkerTree> {
self.0
}

/// Returns an iterator over the marker trees.
pub fn iter(&self) -> std::slice::Iter<MarkerTree> {
self.0.iter()
}
}

impl<'a> IntoIterator for &'a SupportedEnvironments {
type IntoIter = std::slice::Iter<'a, MarkerTree>;
type Item = &'a MarkerTree;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}

/// Serialize a [`SupportedEnvironments`] struct into a list of marker strings.
Expand Down
51 changes: 50 additions & 1 deletion crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ pub struct Lock {
conflicts: Conflicts,
/// The list of supported environments specified by the user.
supported_environments: Vec<MarkerTree>,
/// The list of required platforms specified by the user.
required_platforms: Vec<MarkerTree>,
/// The range of supported Python versions.
requires_python: RequiresPython,
/// We discard the lockfile if these options don't match.
Expand Down Expand Up @@ -242,6 +244,7 @@ impl Lock {
ResolverManifest::default(),
Conflicts::empty(),
vec![],
vec![],
resolution.fork_markers.clone(),
)?;
Ok(lock)
Expand Down Expand Up @@ -314,6 +317,7 @@ impl Lock {
manifest: ResolverManifest,
conflicts: Conflicts,
supported_environments: Vec<MarkerTree>,
required_platforms: Vec<MarkerTree>,
fork_markers: Vec<UniversalMarker>,
) -> Result<Self, LockError> {
// Put all dependencies for each package in a canonical order and
Expand Down Expand Up @@ -464,6 +468,7 @@ impl Lock {
fork_markers,
conflicts,
supported_environments,
required_platforms,
requires_python,
options,
packages,
Expand Down Expand Up @@ -506,6 +511,16 @@ impl Lock {
self
}

/// Record the required platforms that were used to generate this lock.
#[must_use]
pub fn with_required_platforms(mut self, required_platforms: Vec<MarkerTree>) -> Self {
self.required_platforms = required_platforms
.into_iter()
.map(|marker| self.requires_python.complexify_markers(marker))
.collect();
self
}

/// Returns the lockfile version.
pub fn version(&self) -> u32 {
self.version
Expand Down Expand Up @@ -561,6 +576,11 @@ impl Lock {
&self.supported_environments
}

/// Returns the required platforms that were used to generate this lock.
pub fn required_platforms(&self) -> &[MarkerTree] {
&self.required_platforms
}

/// Returns the workspace members that were used to generate this lock.
pub fn members(&self) -> &BTreeSet<PackageName> {
&self.manifest.members
Expand Down Expand Up @@ -593,6 +613,16 @@ impl Lock {
.collect()
}

/// Returns the required platforms that were used to generate this
/// lock.
pub fn simplified_required_platforms(&self) -> Vec<MarkerTree> {
self.required_platforms()
.iter()
.copied()
.map(|marker| self.simplify_environment(marker))
.collect()
}

/// Simplify the given marker environment with respect to the lockfile's
/// `requires-python` setting.
pub fn simplify_environment(&self, marker: MarkerTree) -> MarkerTree {
Expand Down Expand Up @@ -630,11 +660,22 @@ impl Lock {
.iter()
.copied()
.map(|marker| SimplifiedMarkerTree::new(&self.requires_python, marker))
.filter_map(super::requires_python::SimplifiedMarkerTree::try_to_string),
.filter_map(SimplifiedMarkerTree::try_to_string),
);
doc.insert("supported-markers", value(supported_environments));
}

if !self.required_platforms.is_empty() {
let required_platforms = each_element_on_its_line_array(
self.required_platforms
.iter()
.copied()
.map(|marker| SimplifiedMarkerTree::new(&self.requires_python, marker))
.filter_map(SimplifiedMarkerTree::try_to_string),
);
doc.insert("required-markers", value(required_platforms));
}

if !self.conflicts.is_empty() {
let mut list = Array::new();
for set in self.conflicts.iter() {
Expand Down Expand Up @@ -1402,6 +1443,8 @@ struct LockWire {
fork_markers: Vec<SimplifiedMarkerTree>,
#[serde(rename = "supported-markers", default)]
supported_environments: Vec<SimplifiedMarkerTree>,
#[serde(rename = "required-markers", default)]
required_platforms: Vec<SimplifiedMarkerTree>,
#[serde(rename = "conflicts", default)]
conflicts: Option<Conflicts>,
/// We discard the lockfile if these options match.
Expand Down Expand Up @@ -1444,6 +1487,11 @@ impl TryFrom<LockWire> for Lock {
.into_iter()
.map(|simplified_marker| simplified_marker.into_marker(&wire.requires_python))
.collect();
let required_platforms = wire
.required_platforms
.into_iter()
.map(|simplified_marker| simplified_marker.into_marker(&wire.requires_python))
.collect();
let fork_markers = wire
.fork_markers
.into_iter()
Expand All @@ -1462,6 +1510,7 @@ impl TryFrom<LockWire> for Lock {
wire.manifest,
wire.conflicts.unwrap_or_else(Conflicts::empty),
supported_environments,
required_platforms,
fork_markers,
)?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Ok(
[],
),
supported_environments: [],
required_platforms: [],
requires_python: RequiresPython {
specifiers: VersionSpecifiers(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Ok(
[],
),
supported_environments: [],
required_platforms: [],
requires_python: RequiresPython {
specifiers: VersionSpecifiers(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Ok(
[],
),
supported_environments: [],
required_platforms: [],
requires_python: RequiresPython {
specifiers: VersionSpecifiers(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Ok(
[],
),
supported_environments: [],
required_platforms: [],
requires_python: RequiresPython {
specifiers: VersionSpecifiers(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Ok(
[],
),
supported_environments: [],
required_platforms: [],
requires_python: RequiresPython {
specifiers: VersionSpecifiers(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Ok(
[],
),
supported_environments: [],
required_platforms: [],
requires_python: RequiresPython {
specifiers: VersionSpecifiers(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Ok(
[],
),
supported_environments: [],
required_platforms: [],
requires_python: RequiresPython {
specifiers: VersionSpecifiers(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Ok(
[],
),
supported_environments: [],
required_platforms: [],
requires_python: RequiresPython {
specifiers: VersionSpecifiers(
[
Expand Down
Loading
Loading