Skip to content
Merged
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
2 changes: 1 addition & 1 deletion crates/uv-bench/benches/uv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ mod resolver {
);

static TAGS: LazyLock<Tags> = LazyLock::new(|| {
Tags::from_env(&PLATFORM, (3, 11), "cpython", (3, 11), false, false).unwrap()
Tags::from_env(&PLATFORM, (3, 11), "cpython", (3, 11), false, false, false).unwrap()
});

pub(crate) async fn resolve(
Expand Down
21 changes: 21 additions & 0 deletions crates/uv-distribution/src/distribution_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,27 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
.boxed_local()
.await?;

// Check that the wheel is compatible with its install target.
//
// When building a build dependency for a cross-install, the build dependency needs
// to install and run on the host instead of the target. In this case the `tags` are already
// for the host instead of the target, so this check passes.
if !built_wheel.filename.is_compatible(tags) {
return if tags.is_cross() {
Err(Error::BuiltWheelIncompatibleTargetPlatform {
filename: built_wheel.filename,
python_platform: tags.python_platform().clone(),
python_version: tags.python_version(),
})
} else {
Err(Error::BuiltWheelIncompatibleHostPlatform {
filename: built_wheel.filename,
python_platform: tags.python_platform().clone(),
python_version: tags.python_version(),
})
};
}

// Acquire the advisory lock.
#[cfg(windows)]
let _lock = {
Expand Down
32 changes: 31 additions & 1 deletion crates/uv-distribution/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ use zip::result::ZipError;

use crate::metadata::MetadataError;
use uv_client::WrappedReqwestError;
use uv_distribution_filename::WheelFilenameError;
use uv_distribution_filename::{WheelFilename, WheelFilenameError};
use uv_distribution_types::{InstalledDist, InstalledDistError, IsBuildBackendError};
use uv_fs::Simplified;
use uv_git::GitError;
use uv_normalize::PackageName;
use uv_pep440::{Version, VersionSpecifiers};
use uv_platform_tags::Platform;
use uv_pypi_types::{HashAlgorithm, HashDigest};
use uv_redacted::DisplaySafeUrl;
use uv_types::AnyErrorBuild;
Expand Down Expand Up @@ -75,6 +76,35 @@ pub enum Error {
filename: Version,
metadata: Version,
},
/// This shouldn't happen, it's a bug in the build backend.
#[error(
"The built wheel `{}` is not compatible with the current Python {}.{} on {} {}",
filename,
python_version.0,
python_version.1,
python_platform.os(),
python_platform.arch(),
)]
BuiltWheelIncompatibleHostPlatform {
filename: WheelFilename,
python_platform: Platform,
python_version: (u8, u8),
},
/// This may happen when trying to cross-install native dependencies without their build backend
/// being aware that the target is a cross-install.
#[error(
"The built wheel `{}` is not compatible with the target Python {}.{} on {} {}. Consider using `--no-build` to disable building wheels.",
filename,
python_version.0,
python_version.1,
python_platform.os(),
python_platform.arch(),
)]
BuiltWheelIncompatibleTargetPlatform {
filename: WheelFilename,
python_platform: Platform,
python_version: (u8, u8),
},
#[error("Failed to parse metadata from built wheel")]
Metadata(#[from] uv_pypi_types::MetadataError),
#[error("Failed to read metadata: `{}`", _0.user_display())]
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-distribution/src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2575,7 +2575,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.await
.map_err(Error::CacheWrite)?;

debug!("Finished building: {source}");
debug!("Built `{source}` into `{disk_filename}`");
Ok((disk_filename, filename, metadata))
}

Expand Down
35 changes: 33 additions & 2 deletions crates/uv-platform-tags/src/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,26 @@ pub struct Tags {
map: Arc<FxHashMap<LanguageTag, FxHashMap<AbiTag, FxHashMap<PlatformTag, TagPriority>>>>,
/// The highest-priority tag for the Python version and platform.
best: Option<(LanguageTag, AbiTag, PlatformTag)>,
/// Python platform used to generate the tags, for error messages.
python_platform: Platform,
/// Python version used to generate the tags, for error messages.
python_version: (u8, u8),
/// Whether the tags are for a different Python interpreter than the current one, for error
/// messages.
is_cross: bool,
}

impl Tags {
/// Create a new set of tags.
///
/// Tags are prioritized based on their position in the given vector. Specifically, tags that
/// appear earlier in the vector are given higher priority than tags that appear later.
pub fn new(tags: Vec<(LanguageTag, AbiTag, PlatformTag)>) -> Self {
fn new(
tags: Vec<(LanguageTag, AbiTag, PlatformTag)>,
python_platform: Platform,
python_version: (u8, u8),
is_cross: bool,
) -> Self {
// Store the highest-priority tag for each component.
let best = tags.first().cloned();

Expand All @@ -104,6 +116,9 @@ impl Tags {
Self {
map: Arc::new(map),
best,
python_platform,
python_version,
is_cross,
}
}

Expand All @@ -116,6 +131,7 @@ impl Tags {
implementation_version: (u8, u8),
manylinux_compatible: bool,
gil_disabled: bool,
is_cross: bool,
) -> Result<Self, TagsError> {
let implementation = Implementation::parse(implementation_name, gil_disabled)?;

Expand Down Expand Up @@ -219,7 +235,7 @@ impl Tags {
));
}
}
Ok(Self::new(tags))
Ok(Self::new(tags, platform.clone(), python_version, is_cross))
}

/// Returns true when there exists at least one tag for this platform
Expand Down Expand Up @@ -320,6 +336,18 @@ impl Tags {
.map(|abis| abis.contains_key(&abi_tag))
.unwrap_or(false)
}

pub fn python_platform(&self) -> &Platform {
&self.python_platform
}

pub fn python_version(&self) -> (u8, u8) {
self.python_version
}

pub fn is_cross(&self) -> bool {
self.is_cross
}
}

/// The priority of a platform tag.
Expand Down Expand Up @@ -1467,6 +1495,7 @@ mod tests {
(3, 9),
false,
false,
false,
)
.unwrap();
assert_snapshot!(
Expand Down Expand Up @@ -1530,6 +1559,7 @@ mod tests {
(3, 9),
true,
false,
false,
)
.unwrap();
assert_snapshot!(
Expand Down Expand Up @@ -2154,6 +2184,7 @@ mod tests {
(3, 9),
false,
false,
false,
)
.unwrap();
assert_snapshot!(
Expand Down
1 change: 1 addition & 0 deletions crates/uv-python/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ impl Interpreter {
self.implementation_tuple(),
self.manylinux_compatible,
self.gil_disabled,
false,
)?;
self.tags.set(tags).expect("tags should not be set");
}
Expand Down
14 changes: 11 additions & 3 deletions crates/uv/src/commands/pip/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ use uv_workspace::WorkspaceCache;
use uv_workspace::pyproject::ExtraBuildDependencies;

use crate::commands::pip::loggers::DefaultResolveLogger;
use crate::commands::pip::{operations, resolution_environment};
use crate::commands::pip::{operations, resolution_markers, resolution_tags};
use crate::commands::{ExitStatus, OutputWriter, diagnostics};
use crate::printer::Printer;

Expand Down Expand Up @@ -392,8 +392,16 @@ pub(crate) async fn pip_compile(
ResolverEnvironment::universal(environments.into_markers()),
)
} else {
let (tags, marker_env) =
resolution_environment(python_version, python_platform, &interpreter)?;
let tags = resolution_tags(
python_version.as_ref(),
python_platform.as_ref(),
&interpreter,
)?;
let marker_env = resolution_markers(
python_version.as_ref(),
python_platform.as_ref(),
&interpreter,
);
(Some(tags), ResolverEnvironment::specific(marker_env))
};

Expand Down
93 changes: 22 additions & 71 deletions crates/uv/src/commands/pip/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,82 +42,33 @@ pub(crate) fn resolution_tags<'env>(
python_platform: Option<&TargetTriple>,
interpreter: &'env Interpreter,
) -> Result<Cow<'env, Tags>, TagsError> {
Ok(match (python_platform, python_version.as_ref()) {
(Some(python_platform), Some(python_version)) => Cow::Owned(Tags::from_env(
&python_platform.platform(),
(python_version.major(), python_version.minor()),
interpreter.implementation_name(),
interpreter.implementation_tuple(),
python_platform.manylinux_compatible(),
interpreter.gil_disabled(),
)?),
(Some(python_platform), None) => Cow::Owned(Tags::from_env(
&python_platform.platform(),
interpreter.python_tuple(),
interpreter.implementation_name(),
interpreter.implementation_tuple(),
python_platform.manylinux_compatible(),
interpreter.gil_disabled(),
)?),
(None, Some(python_version)) => Cow::Owned(Tags::from_env(
interpreter.platform(),
(python_version.major(), python_version.minor()),
interpreter.implementation_name(),
interpreter.implementation_tuple(),
interpreter.manylinux_compatible(),
interpreter.gil_disabled(),
)?),
(None, None) => Cow::Borrowed(interpreter.tags()?),
})
}
if python_platform.is_none() && python_version.is_none() {
return Ok(Cow::Borrowed(interpreter.tags()?));
}

/// Determine the tags, markers, and interpreter to use for resolution.
pub(crate) fn resolution_environment(
python_version: Option<PythonVersion>,
python_platform: Option<TargetTriple>,
interpreter: &Interpreter,
) -> Result<(Cow<'_, Tags>, ResolverMarkerEnvironment), TagsError> {
let tags = match (python_platform, python_version.as_ref()) {
(Some(python_platform), Some(python_version)) => Cow::Owned(Tags::from_env(
let (platform, manylinux_compatible) = if let Some(python_platform) = python_platform {
(
&python_platform.platform(),
(python_version.major(), python_version.minor()),
interpreter.implementation_name(),
interpreter.implementation_tuple(),
python_platform.manylinux_compatible(),
interpreter.gil_disabled(),
)?),
(Some(python_platform), None) => Cow::Owned(Tags::from_env(
&python_platform.platform(),
interpreter.python_tuple(),
interpreter.implementation_name(),
interpreter.implementation_tuple(),
python_platform.manylinux_compatible(),
interpreter.gil_disabled(),
)?),
(None, Some(python_version)) => Cow::Owned(Tags::from_env(
interpreter.platform(),
(python_version.major(), python_version.minor()),
interpreter.implementation_name(),
interpreter.implementation_tuple(),
interpreter.manylinux_compatible(),
interpreter.gil_disabled(),
)?),
(None, None) => Cow::Borrowed(interpreter.tags()?),
)
} else {
(interpreter.platform(), interpreter.manylinux_compatible())
};

// Apply the platform tags to the markers.
let markers = match (python_platform, python_version) {
(Some(python_platform), Some(python_version)) => ResolverMarkerEnvironment::from(
python_version.markers(&python_platform.markers(interpreter.markers())),
),
(Some(python_platform), None) => {
ResolverMarkerEnvironment::from(python_platform.markers(interpreter.markers()))
}
(None, Some(python_version)) => {
ResolverMarkerEnvironment::from(python_version.markers(interpreter.markers()))
}
(None, None) => interpreter.resolver_marker_environment(),
let version_tuple = if let Some(python_version) = python_version {
(python_version.major(), python_version.minor())
} else {
interpreter.python_tuple()
};

Ok((tags, markers))
let tags = Tags::from_env(
platform,
version_tuple,
interpreter.implementation_name(),
interpreter.implementation_tuple(),
manylinux_compatible,
interpreter.gil_disabled(),
true,
)?;
Ok(Cow::Owned(tags))
}
4 changes: 2 additions & 2 deletions crates/uv/tests/it/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,8 +560,8 @@ impl TestContext {
}

/// Add a custom filter to the `TestContext`.
pub fn with_filter(mut self, filter: (String, String)) -> Self {
self.filters.push(filter);
pub fn with_filter(mut self, filter: (impl Into<String>, impl Into<String>)) -> Self {
self.filters.push((filter.0.into(), filter.1.into()));
self
}

Expand Down
Loading
Loading