mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-03 18:38:21 +00:00
Use separate types to represent raw vs. resolver markers (#6646)
## Summary This is similar to https://github.com/astral-sh/uv/pull/6171 but more expansive... _Anywhere_ that we test requirements for platform compatibility, we _need_ to respect the resolver-friendly markers. In fixing the motivating issue (#6621), I also realized that we had a bunch of bugs here around `pip install` with `--python-platform` and `--python-version`, because we always performed our `satisfy` and `Plan` operations on the interpreter's markers, not the adjusted markers! Closes https://github.com/astral-sh/uv/issues/6621.
This commit is contained in:
parent
6220532373
commit
a7850d2a1c
37 changed files with 422 additions and 247 deletions
|
@ -88,6 +88,7 @@ mod resolver {
|
|||
use pep440_rs::Version;
|
||||
use pep508_rs::{MarkerEnvironment, MarkerEnvironmentBuilder};
|
||||
use platform_tags::{Arch, Os, Platform, Tags};
|
||||
use pypi_types::ResolverMarkerEnvironment;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::RegistryClient;
|
||||
use uv_configuration::{
|
||||
|
@ -192,7 +193,7 @@ mod resolver {
|
|||
let markers = if universal {
|
||||
ResolverMarkers::universal(vec![])
|
||||
} else {
|
||||
ResolverMarkers::specific_environment(MARKERS.clone())
|
||||
ResolverMarkers::specific_environment(ResolverMarkerEnvironment::from(MARKERS.clone()))
|
||||
};
|
||||
|
||||
let resolver = Resolver::new(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
pub use base_url::*;
|
||||
pub use direct_url::*;
|
||||
pub use lenient_requirement::*;
|
||||
pub use marker_environment::*;
|
||||
pub use metadata::*;
|
||||
pub use parsed_url::*;
|
||||
pub use requirement::*;
|
||||
|
@ -10,6 +11,7 @@ pub use simple_json::*;
|
|||
mod base_url;
|
||||
mod direct_url;
|
||||
mod lenient_requirement;
|
||||
mod marker_environment;
|
||||
mod metadata;
|
||||
mod parsed_url;
|
||||
mod requirement;
|
||||
|
|
53
crates/pypi-types/src/marker_environment.rs
Normal file
53
crates/pypi-types/src/marker_environment.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use tracing::debug;
|
||||
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
|
||||
/// A wrapper type around [`MarkerEnvironment`] that ensures the Python version markers are
|
||||
/// release-only, to match the resolver's semantics.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResolverMarkerEnvironment(MarkerEnvironment);
|
||||
|
||||
impl ResolverMarkerEnvironment {
|
||||
/// Returns the underlying [`MarkerEnvironment`].
|
||||
pub fn markers(&self) -> &MarkerEnvironment {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MarkerEnvironment> for ResolverMarkerEnvironment {
|
||||
fn from(value: MarkerEnvironment) -> Self {
|
||||
// Strip `python_version`.
|
||||
let python_version = value.python_version().only_release();
|
||||
let value = if python_version == **value.python_version() {
|
||||
value
|
||||
} else {
|
||||
debug!(
|
||||
"Stripping pre-release from `python_version`: {}",
|
||||
value.python_version()
|
||||
);
|
||||
value.with_python_version(python_version)
|
||||
};
|
||||
|
||||
// Strip `python_full_version`.
|
||||
let python_full_version = value.python_full_version().only_release();
|
||||
let value = if python_full_version == **value.python_full_version() {
|
||||
value
|
||||
} else {
|
||||
debug!(
|
||||
"Stripping pre-release from `python_full_version`: {}",
|
||||
value.python_full_version()
|
||||
);
|
||||
value.with_python_full_version(python_full_version)
|
||||
};
|
||||
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ResolverMarkerEnvironment {
|
||||
type Target = MarkerEnvironment;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
|
@ -138,7 +138,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
|||
|
||||
async fn resolve<'data>(&'data self, requirements: &'data [Requirement]) -> Result<Resolution> {
|
||||
let python_requirement = PythonRequirement::from_interpreter(self.interpreter);
|
||||
let markers = self.interpreter.markers();
|
||||
let markers = self.interpreter.resolver_markers();
|
||||
let tags = self.interpreter.tags()?;
|
||||
|
||||
let resolver = Resolver::new(
|
||||
|
@ -148,7 +148,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
|||
.index_strategy(self.index_strategy)
|
||||
.build(),
|
||||
&python_requirement,
|
||||
ResolverMarkers::specific_environment(markers.clone()),
|
||||
ResolverMarkers::specific_environment(markers),
|
||||
Some(tags),
|
||||
self.flat_index,
|
||||
self.index,
|
||||
|
@ -189,6 +189,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
|||
|
||||
// Determine the current environment markers.
|
||||
let tags = self.interpreter.tags()?;
|
||||
let markers = self.interpreter.resolver_markers();
|
||||
|
||||
// Determine the set of installed packages.
|
||||
let site_packages = SitePackages::from_environment(venv)?;
|
||||
|
@ -208,6 +209,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
|||
self.index_locations,
|
||||
self.cache(),
|
||||
venv,
|
||||
&markers,
|
||||
tags,
|
||||
)?;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ use distribution_types::{
|
|||
PathSourceDist, RemoteSource, Verbatim,
|
||||
};
|
||||
use platform_tags::Tags;
|
||||
use pypi_types::{Requirement, RequirementSource};
|
||||
use pypi_types::{Requirement, RequirementSource, ResolverMarkerEnvironment};
|
||||
use uv_cache::{ArchiveTimestamp, Cache, CacheBucket, WheelCache};
|
||||
use uv_configuration::{BuildOptions, Reinstall};
|
||||
use uv_distribution::{
|
||||
|
@ -58,6 +58,7 @@ impl<'a> Planner<'a> {
|
|||
index_locations: &IndexLocations,
|
||||
cache: &Cache,
|
||||
venv: &PythonEnvironment,
|
||||
markers: &ResolverMarkerEnvironment,
|
||||
tags: &Tags,
|
||||
) -> Result<Plan> {
|
||||
// Index all the already-downloaded wheels in the cache.
|
||||
|
@ -72,7 +73,7 @@ impl<'a> Planner<'a> {
|
|||
|
||||
for requirement in self.requirements {
|
||||
// Filter out incompatible requirements.
|
||||
if !requirement.evaluate_markers(Some(venv.interpreter().markers()), &[]) {
|
||||
if !requirement.evaluate_markers(Some(markers), &[]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use distribution_types::{
|
|||
Diagnostic, InstalledDist, Name, UnresolvedRequirement, UnresolvedRequirementSpecification,
|
||||
};
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pypi_types::{Requirement, VerbatimParsedUrl};
|
||||
use pypi_types::{Requirement, ResolverMarkerEnvironment, VerbatimParsedUrl};
|
||||
use uv_fs::Simplified;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_python::{Interpreter, PythonEnvironment};
|
||||
|
@ -181,7 +181,10 @@ impl SitePackages {
|
|||
}
|
||||
|
||||
/// Validate the installed packages in the virtual environment.
|
||||
pub fn diagnostics(&self) -> Result<Vec<SitePackagesDiagnostic>> {
|
||||
pub fn diagnostics(
|
||||
&self,
|
||||
markers: &ResolverMarkerEnvironment,
|
||||
) -> Result<Vec<SitePackagesDiagnostic>> {
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
for (package, indexes) in &self.by_name {
|
||||
|
@ -220,7 +223,7 @@ impl SitePackages {
|
|||
|
||||
// Verify that the package is compatible with the current Python version.
|
||||
if let Some(requires_python) = metadata.requires_python.as_ref() {
|
||||
if !requires_python.contains(self.interpreter.python_version()) {
|
||||
if !requires_python.contains(markers.python_full_version()) {
|
||||
diagnostics.push(SitePackagesDiagnostic::IncompatiblePythonVersion {
|
||||
package: package.clone(),
|
||||
version: self.interpreter.python_version().clone(),
|
||||
|
@ -231,7 +234,7 @@ impl SitePackages {
|
|||
|
||||
// Verify that the dependencies are installed.
|
||||
for dependency in &metadata.requires_dist {
|
||||
if !dependency.evaluate_markers(self.interpreter.markers(), &[]) {
|
||||
if !dependency.evaluate_markers(markers, &[]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -281,6 +284,7 @@ impl SitePackages {
|
|||
&self,
|
||||
requirements: &[UnresolvedRequirementSpecification],
|
||||
constraints: &[Requirement],
|
||||
markers: &ResolverMarkerEnvironment,
|
||||
) -> Result<SatisfiesResult> {
|
||||
// Collect the constraints.
|
||||
let constraints: FxHashMap<&PackageName, Vec<&Requirement>> =
|
||||
|
@ -299,10 +303,7 @@ impl SitePackages {
|
|||
|
||||
// Add the direct requirements to the queue.
|
||||
for entry in requirements {
|
||||
if entry
|
||||
.requirement
|
||||
.evaluate_markers(Some(self.interpreter.markers()), &[])
|
||||
{
|
||||
if entry.requirement.evaluate_markers(Some(markers), &[]) {
|
||||
if seen.insert(entry.clone()) {
|
||||
stack.push(entry.clone());
|
||||
}
|
||||
|
@ -353,10 +354,7 @@ impl SitePackages {
|
|||
|
||||
// Add the dependencies to the queue.
|
||||
for dependency in metadata.requires_dist {
|
||||
if dependency.evaluate_markers(
|
||||
self.interpreter.markers(),
|
||||
entry.requirement.extras(),
|
||||
) {
|
||||
if dependency.evaluate_markers(markers, entry.requirement.extras()) {
|
||||
let dependency = UnresolvedRequirementSpecification {
|
||||
requirement: UnresolvedRequirement::Named(Requirement::from(
|
||||
dependency,
|
||||
|
|
|
@ -17,7 +17,7 @@ use pep440_rs::Version;
|
|||
use pep508_rs::{MarkerEnvironment, StringVersion};
|
||||
use platform_tags::Platform;
|
||||
use platform_tags::{Tags, TagsError};
|
||||
use pypi_types::Scheme;
|
||||
use pypi_types::{ResolverMarkerEnvironment, Scheme};
|
||||
use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness, Timestamp};
|
||||
use uv_fs::{write_atomic_sync, PythonExt, Simplified};
|
||||
|
||||
|
@ -142,6 +142,11 @@ impl Interpreter {
|
|||
&self.markers
|
||||
}
|
||||
|
||||
/// Return the [`ResolverMarkerEnvironment`] for this Python executable.
|
||||
pub fn resolver_markers(&self) -> ResolverMarkerEnvironment {
|
||||
ResolverMarkerEnvironment::from(self.markers().clone())
|
||||
}
|
||||
|
||||
/// Returns the [`PythonInstallationKey`] for this interpreter.
|
||||
pub fn key(&self) -> PythonInstallationKey {
|
||||
PythonInstallationKey::new(
|
||||
|
|
|
@ -84,7 +84,7 @@ impl PythonVersion {
|
|||
///
|
||||
/// The returned [`MarkerEnvironment`] will preserve the base environment's platform markers,
|
||||
/// but override its Python version markers.
|
||||
pub fn markers(self, base: &MarkerEnvironment) -> MarkerEnvironment {
|
||||
pub fn markers(&self, base: &MarkerEnvironment) -> MarkerEnvironment {
|
||||
let mut markers = base.clone();
|
||||
|
||||
// Ex) `implementation_version == "3.12.0"`
|
||||
|
|
|
@ -6,7 +6,7 @@ use tracing::{debug, trace};
|
|||
use distribution_types::{CompatibleDist, IncompatibleDist, IncompatibleSource};
|
||||
use distribution_types::{DistributionMetadata, IncompatibleWheel, Name, PrioritizedDist};
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::{MarkerEnvironment, MarkerTree};
|
||||
use pep508_rs::MarkerTree;
|
||||
use uv_configuration::IndexStrategy;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_types::InstalledPackagesProvider;
|
||||
|
@ -30,7 +30,7 @@ impl CandidateSelector {
|
|||
pub(crate) fn for_resolution(
|
||||
options: Options,
|
||||
manifest: &Manifest,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
markers: &ResolverMarkers,
|
||||
) -> Self {
|
||||
Self {
|
||||
resolution_strategy: ResolutionStrategy::from_mode(
|
||||
|
|
|
@ -27,7 +27,7 @@ use pep508_rs::{split_scheme, MarkerEnvironment, MarkerTree, VerbatimUrl, Verbat
|
|||
use platform_tags::{TagCompatibility, TagPriority, Tags};
|
||||
use pypi_types::{
|
||||
redact_git_credentials, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement,
|
||||
RequirementSource,
|
||||
RequirementSource, ResolverMarkerEnvironment,
|
||||
};
|
||||
use uv_configuration::ExtrasSpecification;
|
||||
use uv_distribution::DistributionDatabase;
|
||||
|
@ -419,7 +419,7 @@ impl Lock {
|
|||
pub fn to_resolution(
|
||||
&self,
|
||||
project: &VirtualProject,
|
||||
marker_env: &MarkerEnvironment,
|
||||
marker_env: &ResolverMarkerEnvironment,
|
||||
tags: &Tags,
|
||||
extras: &ExtrasSpecification,
|
||||
dev: &[GroupName],
|
||||
|
@ -3641,7 +3641,7 @@ impl<'env> TreeDisplay<'env> {
|
|||
/// Create a new [`DisplayDependencyGraph`] for the set of installed packages.
|
||||
pub fn new(
|
||||
lock: &'env Lock,
|
||||
markers: Option<&'env MarkerEnvironment>,
|
||||
markers: Option<&'env ResolverMarkerEnvironment>,
|
||||
depth: usize,
|
||||
prune: Vec<PackageName>,
|
||||
package: Vec<PackageName>,
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use either::Either;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use either::Either;
|
||||
|
||||
use pypi_types::Requirement;
|
||||
use uv_configuration::{Constraints, Overrides};
|
||||
use uv_normalize::{GroupName, PackageName};
|
||||
use uv_types::RequestedRequirements;
|
||||
|
||||
use crate::preferences::Preferences;
|
||||
use crate::{DependencyMode, Exclusions};
|
||||
use crate::{DependencyMode, Exclusions, ResolverMarkers};
|
||||
|
||||
/// A manifest of requirements, constraints, and preferences.
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -109,7 +109,7 @@ impl Manifest {
|
|||
/// - Determining which requirements should allow local version specifiers (e.g., `torch==2.2.0+cpu`).
|
||||
pub fn requirements<'a>(
|
||||
&'a self,
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
markers: &'a ResolverMarkers,
|
||||
mode: DependencyMode,
|
||||
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
|
||||
self.requirements_no_overrides(markers, mode)
|
||||
|
@ -119,39 +119,48 @@ impl Manifest {
|
|||
/// Like [`Self::requirements`], but without the overrides.
|
||||
pub fn requirements_no_overrides<'a>(
|
||||
&'a self,
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
markers: &'a ResolverMarkers,
|
||||
mode: DependencyMode,
|
||||
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
|
||||
match mode {
|
||||
// Include all direct and transitive requirements, with constraints and overrides applied.
|
||||
DependencyMode::Transitive => Either::Left(
|
||||
self.lookaheads
|
||||
.iter()
|
||||
.flat_map(move |lookahead| {
|
||||
self.overrides
|
||||
.apply(lookahead.requirements())
|
||||
.filter(move |requirement| {
|
||||
requirement.evaluate_markers(markers, lookahead.extras())
|
||||
})
|
||||
})
|
||||
.chain(
|
||||
self.overrides
|
||||
.apply(&self.requirements)
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
|
||||
)
|
||||
.chain(
|
||||
self.constraints
|
||||
.requirements()
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.map(Cow::Borrowed),
|
||||
),
|
||||
),
|
||||
DependencyMode::Transitive => {
|
||||
Either::Left(
|
||||
self.lookaheads
|
||||
.iter()
|
||||
.flat_map(move |lookahead| {
|
||||
self.overrides.apply(lookahead.requirements()).filter(
|
||||
move |requirement| {
|
||||
requirement.evaluate_markers(
|
||||
markers.marker_environment(),
|
||||
lookahead.extras(),
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.chain(self.overrides.apply(&self.requirements).filter(
|
||||
move |requirement| {
|
||||
requirement.evaluate_markers(markers.marker_environment(), &[])
|
||||
},
|
||||
))
|
||||
.chain(
|
||||
self.constraints
|
||||
.requirements()
|
||||
.filter(move |requirement| {
|
||||
requirement.evaluate_markers(markers.marker_environment(), &[])
|
||||
})
|
||||
.map(Cow::Borrowed),
|
||||
),
|
||||
)
|
||||
}
|
||||
// Include direct requirements, with constraints and overrides applied.
|
||||
DependencyMode::Direct => Either::Right(
|
||||
self.overrides
|
||||
.apply(&self.requirements)
|
||||
.chain(self.constraints.requirements().map(Cow::Borrowed))
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
|
||||
.filter(move |requirement| {
|
||||
requirement.evaluate_markers(markers.marker_environment(), &[])
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +168,7 @@ impl Manifest {
|
|||
/// Only the overrides from [`Self::requirements`].
|
||||
pub fn overrides<'a>(
|
||||
&'a self,
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
markers: &'a ResolverMarkers,
|
||||
mode: DependencyMode,
|
||||
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
|
||||
match mode {
|
||||
|
@ -167,14 +176,18 @@ impl Manifest {
|
|||
DependencyMode::Transitive => Either::Left(
|
||||
self.overrides
|
||||
.requirements()
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.filter(move |requirement| {
|
||||
requirement.evaluate_markers(markers.marker_environment(), &[])
|
||||
})
|
||||
.map(Cow::Borrowed),
|
||||
),
|
||||
// Include direct requirements, with constraints and overrides applied.
|
||||
DependencyMode::Direct => Either::Right(
|
||||
self.overrides
|
||||
.requirements()
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.filter(move |requirement| {
|
||||
requirement.evaluate_markers(markers.marker_environment(), &[])
|
||||
})
|
||||
.map(Cow::Borrowed),
|
||||
),
|
||||
}
|
||||
|
@ -192,36 +205,43 @@ impl Manifest {
|
|||
/// the `lowest-direct` strategy is in use.
|
||||
pub fn user_requirements<'a>(
|
||||
&'a self,
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
markers: &'a ResolverMarkers,
|
||||
mode: DependencyMode,
|
||||
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
|
||||
match mode {
|
||||
// Include direct requirements, dependencies of editables, and transitive dependencies
|
||||
// of local packages.
|
||||
DependencyMode::Transitive => Either::Left(
|
||||
self.lookaheads
|
||||
.iter()
|
||||
.filter(|lookahead| lookahead.direct())
|
||||
.flat_map(move |lookahead| {
|
||||
self.overrides
|
||||
.apply(lookahead.requirements())
|
||||
.filter(move |requirement| {
|
||||
requirement.evaluate_markers(markers, lookahead.extras())
|
||||
})
|
||||
})
|
||||
.chain(
|
||||
self.overrides
|
||||
.apply(&self.requirements)
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
|
||||
),
|
||||
),
|
||||
DependencyMode::Transitive => {
|
||||
Either::Left(
|
||||
self.lookaheads
|
||||
.iter()
|
||||
.filter(|lookahead| lookahead.direct())
|
||||
.flat_map(move |lookahead| {
|
||||
self.overrides.apply(lookahead.requirements()).filter(
|
||||
move |requirement| {
|
||||
requirement.evaluate_markers(
|
||||
markers.marker_environment(),
|
||||
lookahead.extras(),
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.chain(self.overrides.apply(&self.requirements).filter(
|
||||
move |requirement| {
|
||||
requirement.evaluate_markers(markers.marker_environment(), &[])
|
||||
},
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
// Restrict to the direct requirements.
|
||||
DependencyMode::Direct => Either::Right(
|
||||
self.overrides
|
||||
.apply(self.requirements.iter())
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
|
||||
),
|
||||
DependencyMode::Direct => {
|
||||
Either::Right(self.overrides.apply(self.requirements.iter()).filter(
|
||||
move |requirement| {
|
||||
requirement.evaluate_markers(markers.marker_environment(), &[])
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,11 +252,13 @@ impl Manifest {
|
|||
/// resolution (assuming the user enabled development dependencies).
|
||||
pub fn direct_requirements<'a>(
|
||||
&'a self,
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
markers: &'a ResolverMarkers,
|
||||
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
|
||||
self.overrides
|
||||
.apply(self.requirements.iter())
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.filter(move |requirement| {
|
||||
requirement.evaluate_markers(markers.marker_environment(), &[])
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply the overrides and constraints to a set of requirements.
|
||||
|
|
|
@ -5,11 +5,13 @@ use tracing::trace;
|
|||
|
||||
use distribution_types::{InstalledDist, InstalledMetadata, InstalledVersion, Name};
|
||||
use pep440_rs::{Operator, Version};
|
||||
use pep508_rs::{MarkerEnvironment, MarkerTree, VersionOrUrl};
|
||||
use pep508_rs::{MarkerTree, VersionOrUrl};
|
||||
use pypi_types::{HashDigest, HashError};
|
||||
use requirements_txt::{RequirementEntry, RequirementsTxtRequirement};
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::ResolverMarkers;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum PreferenceError {
|
||||
#[error(transparent)]
|
||||
|
@ -121,12 +123,12 @@ impl Preferences {
|
|||
/// to an applicable subset.
|
||||
pub fn from_iter<PreferenceIterator: IntoIterator<Item = Preference>>(
|
||||
preferences: PreferenceIterator,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
markers: &ResolverMarkers,
|
||||
) -> Self {
|
||||
let mut slf = Self::default();
|
||||
for preference in preferences {
|
||||
// Filter non-matching preferences when resolving for an environment.
|
||||
if let Some(markers) = markers {
|
||||
if let Some(markers) = markers.marker_environment() {
|
||||
if !preference.marker.evaluate(markers, &[]) {
|
||||
trace!("Excluding {preference} from preferences due to unmatched markers");
|
||||
continue;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use pypi_types::RequirementSource;
|
||||
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::resolver::ForkSet;
|
||||
|
@ -68,7 +67,7 @@ impl PrereleaseStrategy {
|
|||
pub(crate) fn from_mode(
|
||||
mode: PrereleaseMode,
|
||||
manifest: &Manifest,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
markers: &ResolverMarkers,
|
||||
dependencies: DependencyMode,
|
||||
) -> Self {
|
||||
let mut packages = ForkSet::default();
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use rustc_hash::FxHashSet;
|
||||
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::{DependencyMode, Manifest};
|
||||
use crate::{DependencyMode, Manifest, ResolverMarkers};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
|
@ -47,7 +46,7 @@ impl ResolutionStrategy {
|
|||
pub(crate) fn from_mode(
|
||||
mode: ResolutionMode,
|
||||
manifest: &Manifest,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
markers: &ResolverMarkers,
|
||||
dependencies: DependencyMode,
|
||||
) -> Self {
|
||||
match mode {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use rustc_hash::FxHashMap;
|
||||
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use uv_normalize::{GroupName, PackageName};
|
||||
|
||||
use crate::Manifest;
|
||||
use crate::{Manifest, ResolverMarkers};
|
||||
|
||||
/// A map of package names to their activated dependency groups.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
@ -11,7 +10,7 @@ pub(crate) struct Groups(FxHashMap<PackageName, Vec<GroupName>>);
|
|||
|
||||
impl Groups {
|
||||
/// Determine the set of enabled dependency groups in the [`Manifest`].
|
||||
pub(crate) fn from_manifest(manifest: &Manifest, markers: Option<&MarkerEnvironment>) -> Self {
|
||||
pub(crate) fn from_manifest(manifest: &Manifest, markers: &ResolverMarkers) -> Self {
|
||||
let mut groups = FxHashMap::default();
|
||||
|
||||
// Enable the groups for all direct dependencies. In practice, this tends to mean: when
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::str::FromStr;
|
|||
use distribution_filename::{SourceDistFilename, WheelFilename};
|
||||
use distribution_types::RemoteSource;
|
||||
use pep440_rs::{Operator, Version, VersionSpecifier, VersionSpecifierBuildError};
|
||||
use pep508_rs::{MarkerEnvironment, PackageName};
|
||||
use pep508_rs::PackageName;
|
||||
use pypi_types::RequirementSource;
|
||||
|
||||
use crate::resolver::ForkMap;
|
||||
|
@ -17,7 +17,7 @@ impl Locals {
|
|||
/// Determine the set of permitted local versions in the [`Manifest`].
|
||||
pub(crate) fn from_manifest(
|
||||
manifest: &Manifest,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
markers: &ResolverMarkers,
|
||||
dependencies: DependencyMode,
|
||||
) -> Self {
|
||||
let mut locals = ForkMap::default();
|
||||
|
|
|
@ -157,11 +157,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
|
|||
python_requirement
|
||||
.target()
|
||||
.and_then(|target| target.as_requires_python()),
|
||||
AllowedYanks::from_manifest(
|
||||
&manifest,
|
||||
markers.marker_environment(),
|
||||
options.dependency_mode,
|
||||
),
|
||||
AllowedYanks::from_manifest(&manifest, &markers, options.dependency_mode),
|
||||
hasher,
|
||||
options.exclude_newer,
|
||||
build_context.build_options(),
|
||||
|
@ -199,24 +195,11 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
|||
let state = ResolverState {
|
||||
index: index.clone(),
|
||||
git: git.clone(),
|
||||
selector: CandidateSelector::for_resolution(
|
||||
options,
|
||||
&manifest,
|
||||
markers.marker_environment(),
|
||||
),
|
||||
selector: CandidateSelector::for_resolution(options, &manifest, &markers),
|
||||
dependency_mode: options.dependency_mode,
|
||||
urls: Urls::from_manifest(
|
||||
&manifest,
|
||||
markers.marker_environment(),
|
||||
git,
|
||||
options.dependency_mode,
|
||||
)?,
|
||||
locals: Locals::from_manifest(
|
||||
&manifest,
|
||||
markers.marker_environment(),
|
||||
options.dependency_mode,
|
||||
),
|
||||
groups: Groups::from_manifest(&manifest, markers.marker_environment()),
|
||||
urls: Urls::from_manifest(&manifest, &markers, git, options.dependency_mode)?,
|
||||
locals: Locals::from_manifest(&manifest, &markers, options.dependency_mode),
|
||||
groups: Groups::from_manifest(&manifest, &markers),
|
||||
project: manifest.project,
|
||||
workspace_members: manifest.workspace_members,
|
||||
requirements: manifest.requirements,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
use tracing::debug;
|
||||
|
||||
use pep508_rs::{MarkerEnvironment, MarkerTree};
|
||||
use pypi_types::ResolverMarkerEnvironment;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Whether we're solving for a specific environment, universally or for a specific fork.
|
||||
|
@ -20,8 +20,8 @@ pub enum ResolverMarkers {
|
|||
|
||||
impl ResolverMarkers {
|
||||
/// Set the resolver to perform a resolution for a specific environment.
|
||||
pub fn specific_environment(markers: MarkerEnvironment) -> Self {
|
||||
Self::SpecificEnvironment(ResolverMarkerEnvironment::from(markers))
|
||||
pub fn specific_environment(markers: ResolverMarkerEnvironment) -> Self {
|
||||
Self::SpecificEnvironment(markers)
|
||||
}
|
||||
|
||||
/// Set the resolver to perform a universal resolution.
|
||||
|
@ -71,46 +71,3 @@ impl Display for ResolverMarkers {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper type around [`MarkerEnvironment`] that ensures the Python version markers are
|
||||
/// release-only, to match the resolver's semantics.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResolverMarkerEnvironment(MarkerEnvironment);
|
||||
|
||||
impl From<MarkerEnvironment> for ResolverMarkerEnvironment {
|
||||
fn from(value: MarkerEnvironment) -> Self {
|
||||
// Strip `python_version`.
|
||||
let python_version = value.python_version().only_release();
|
||||
let value = if python_version == **value.python_version() {
|
||||
value
|
||||
} else {
|
||||
debug!(
|
||||
"Stripping pre-release from `python_version`: {}",
|
||||
value.python_version()
|
||||
);
|
||||
value.with_python_version(python_version)
|
||||
};
|
||||
|
||||
// Strip `python_full_version`.
|
||||
let python_full_version = value.python_full_version().only_release();
|
||||
let value = if python_full_version == **value.python_full_version() {
|
||||
value
|
||||
} else {
|
||||
debug!(
|
||||
"Stripping pre-release from `python_full_version`: {}",
|
||||
value.python_full_version()
|
||||
);
|
||||
value.with_python_full_version(python_full_version)
|
||||
};
|
||||
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ResolverMarkerEnvironment {
|
||||
type Target = MarkerEnvironment;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,12 @@ use tracing::debug;
|
|||
|
||||
use cache_key::CanonicalUrl;
|
||||
use distribution_types::Verbatim;
|
||||
use pep508_rs::{MarkerEnvironment, VerbatimUrl};
|
||||
use pep508_rs::VerbatimUrl;
|
||||
use pypi_types::{ParsedDirectoryUrl, ParsedUrl, VerbatimParsedUrl};
|
||||
use uv_git::GitResolver;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::{DependencyMode, Manifest, ResolveError};
|
||||
use crate::{DependencyMode, Manifest, ResolveError, ResolverMarkers};
|
||||
|
||||
/// The URLs that are allowed for packages.
|
||||
///
|
||||
|
@ -36,7 +36,7 @@ pub(crate) struct Urls {
|
|||
impl Urls {
|
||||
pub(crate) fn from_manifest(
|
||||
manifest: &Manifest,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
markers: &ResolverMarkers,
|
||||
git: &GitResolver,
|
||||
dependencies: DependencyMode,
|
||||
) -> Result<Self, ResolveError> {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use pypi_types::RequirementSource;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use pypi_types::RequirementSource;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::{DependencyMode, Manifest};
|
||||
use crate::{DependencyMode, Manifest, ResolverMarkers};
|
||||
|
||||
/// A set of package versions that are permitted, even if they're marked as yanked by the
|
||||
/// relevant index.
|
||||
|
@ -16,7 +16,7 @@ pub struct AllowedYanks(Arc<FxHashMap<PackageName, FxHashSet<Version>>>);
|
|||
impl AllowedYanks {
|
||||
pub fn from_manifest(
|
||||
manifest: &Manifest,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
markers: &ResolverMarkers,
|
||||
dependencies: DependencyMode,
|
||||
) -> Self {
|
||||
let mut allowed_yanks = FxHashMap::<PackageName, FxHashSet<Version>>::default();
|
||||
|
|
|
@ -7,8 +7,9 @@ use distribution_types::{
|
|||
DistributionMetadata, HashPolicy, Name, Resolution, UnresolvedRequirement, VersionId,
|
||||
};
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use pypi_types::{HashDigest, HashError, Requirement, RequirementSource};
|
||||
use pypi_types::{
|
||||
HashDigest, HashError, Requirement, RequirementSource, ResolverMarkerEnvironment,
|
||||
};
|
||||
use uv_configuration::HashCheckingMode;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
|
@ -125,7 +126,7 @@ impl HashStrategy {
|
|||
/// to "only evaluate marker expressions that reference an extra name.")
|
||||
pub fn from_requirements<'a>(
|
||||
requirements: impl Iterator<Item = (&'a UnresolvedRequirement, &'a [String])>,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
marker_env: Option<&ResolverMarkerEnvironment>,
|
||||
mode: HashCheckingMode,
|
||||
) -> Result<Self, HashStrategyError> {
|
||||
let mut hashes = FxHashMap::<VersionId, Vec<HashDigest>>::default();
|
||||
|
@ -133,7 +134,9 @@ impl HashStrategy {
|
|||
// For each requirement, map from name to allowed hashes. We use the last entry for each
|
||||
// package.
|
||||
for (requirement, digests) in requirements {
|
||||
if !requirement.evaluate_markers(markers, &[]) {
|
||||
if !requirement
|
||||
.evaluate_markers(marker_env.map(ResolverMarkerEnvironment::markers), &[])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,8 +52,12 @@ pub(crate) fn pip_check(
|
|||
.dimmed()
|
||||
)?;
|
||||
|
||||
// Determine the markers to use for resolution.
|
||||
let markers = environment.interpreter().resolver_markers();
|
||||
|
||||
// Run the diagnostics.
|
||||
let diagnostics: Vec<SitePackagesDiagnostic> =
|
||||
site_packages.diagnostics()?.into_iter().collect();
|
||||
site_packages.diagnostics(&markers)?.into_iter().collect();
|
||||
|
||||
if diagnostics.is_empty() {
|
||||
writeln!(
|
||||
|
|
|
@ -248,10 +248,7 @@ pub(crate) async fn pip_compile(
|
|||
} else {
|
||||
let (tags, markers) =
|
||||
resolution_environment(python_version, python_platform, &interpreter)?;
|
||||
(
|
||||
Some(tags),
|
||||
ResolverMarkers::specific_environment((*markers).clone()),
|
||||
)
|
||||
(Some(tags), ResolverMarkers::specific_environment(markers))
|
||||
};
|
||||
|
||||
// Generate, but don't enforce hashes for the requirements.
|
||||
|
|
|
@ -68,7 +68,10 @@ pub(crate) fn pip_freeze(
|
|||
|
||||
// Validate that the environment is consistent.
|
||||
if strict {
|
||||
for diagnostic in site_packages.diagnostics()? {
|
||||
// Determine the markers to use for resolution.
|
||||
let markers = environment.interpreter().resolver_markers();
|
||||
|
||||
for diagnostic in site_packages.diagnostics(&markers)? {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}{} {}",
|
||||
|
|
|
@ -32,7 +32,7 @@ use uv_types::{BuildIsolation, HashStrategy};
|
|||
|
||||
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger};
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::pip::{operations, resolution_environment};
|
||||
use crate::commands::pip::{operations, resolution_markers, resolution_tags};
|
||||
use crate::commands::{ExitStatus, SharedState};
|
||||
use crate::printer::Printer;
|
||||
|
||||
|
@ -183,6 +183,14 @@ pub(crate) async fn pip_install(
|
|||
|
||||
let _lock = environment.lock()?;
|
||||
|
||||
// Determine the markers to use for the resolution.
|
||||
let interpreter = environment.interpreter();
|
||||
let markers = resolution_markers(
|
||||
python_version.as_ref(),
|
||||
python_platform.as_ref(),
|
||||
interpreter,
|
||||
);
|
||||
|
||||
// Determine the set of installed packages.
|
||||
let site_packages = SitePackages::from_environment(&environment)?;
|
||||
|
||||
|
@ -190,7 +198,7 @@ pub(crate) async fn pip_install(
|
|||
// Ideally, the resolver would be fast enough to let us remove this check. But right now, for large environments,
|
||||
// it's an order of magnitude faster to validate the environment than to resolve the requirements.
|
||||
if reinstall.is_none() && upgrade.is_none() && source_trees.is_empty() && overrides.is_empty() {
|
||||
match site_packages.satisfies(&requirements, &constraints)? {
|
||||
match site_packages.satisfies(&requirements, &constraints, &markers)? {
|
||||
// If the requirements are already satisfied, we're done.
|
||||
SatisfiesResult::Fresh {
|
||||
recursive_requirements,
|
||||
|
@ -216,8 +224,6 @@ pub(crate) async fn pip_install(
|
|||
}
|
||||
}
|
||||
|
||||
let interpreter = environment.interpreter();
|
||||
|
||||
// Determine the Python requirement, if the user requested a specific version.
|
||||
let python_requirement = if let Some(python_version) = python_version.as_ref() {
|
||||
PythonRequirement::from_python_version(interpreter, python_version)
|
||||
|
@ -225,8 +231,12 @@ pub(crate) async fn pip_install(
|
|||
PythonRequirement::from_interpreter(interpreter)
|
||||
};
|
||||
|
||||
// Determine the environment for the resolution.
|
||||
let (tags, markers) = resolution_environment(python_version, python_platform, interpreter)?;
|
||||
// Determine the tags to use for the resolution.
|
||||
let tags = resolution_tags(
|
||||
python_version.as_ref(),
|
||||
python_platform.as_ref(),
|
||||
interpreter,
|
||||
)?;
|
||||
|
||||
// Collect the set of required hashes.
|
||||
let hasher = if let Some(hash_checking) = hash_checking {
|
||||
|
@ -262,7 +272,7 @@ pub(crate) async fn pip_install(
|
|||
.cache(cache.clone())
|
||||
.index_urls(index_locations.index_urls())
|
||||
.index_strategy(index_strategy)
|
||||
.markers(&markers)
|
||||
.markers(interpreter.markers())
|
||||
.platform(interpreter.platform())
|
||||
.build();
|
||||
|
||||
|
@ -333,7 +343,7 @@ pub(crate) async fn pip_install(
|
|||
&reinstall,
|
||||
&upgrade,
|
||||
Some(&tags),
|
||||
ResolverMarkers::specific_environment((*markers).clone()),
|
||||
ResolverMarkers::specific_environment(markers.clone()),
|
||||
python_requirement,
|
||||
&client,
|
||||
&flat_index,
|
||||
|
@ -366,6 +376,7 @@ pub(crate) async fn pip_install(
|
|||
compile,
|
||||
&index_locations,
|
||||
&hasher,
|
||||
&markers,
|
||||
&tags,
|
||||
&client,
|
||||
&state.in_flight,
|
||||
|
@ -384,7 +395,7 @@ pub(crate) async fn pip_install(
|
|||
|
||||
// Notify the user of any environment diagnostics.
|
||||
if strict && !dry_run {
|
||||
operations::diagnose_environment(&resolution, &environment, printer)?;
|
||||
operations::diagnose_environment(&resolution, &environment, &markers, printer)?;
|
||||
}
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
|
|
|
@ -119,7 +119,10 @@ pub(crate) fn pip_list(
|
|||
|
||||
// Validate that the environment is consistent.
|
||||
if strict {
|
||||
for diagnostic in site_packages.diagnostics()? {
|
||||
// Determine the markers to use for resolution.
|
||||
let markers = environment.interpreter().resolver_markers();
|
||||
|
||||
for diagnostic in site_packages.diagnostics(&markers)? {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}{} {}",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use platform_tags::{Tags, TagsError};
|
||||
use pypi_types::ResolverMarkerEnvironment;
|
||||
use uv_configuration::TargetTriple;
|
||||
use uv_python::{Interpreter, PythonVersion};
|
||||
|
||||
|
@ -17,12 +17,65 @@ pub(crate) mod sync;
|
|||
pub(crate) mod tree;
|
||||
pub(crate) mod uninstall;
|
||||
|
||||
pub(crate) fn resolution_markers(
|
||||
python_version: Option<&PythonVersion>,
|
||||
python_platform: Option<&TargetTriple>,
|
||||
interpreter: &Interpreter,
|
||||
) -> ResolverMarkerEnvironment {
|
||||
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_markers(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolution_tags<'env>(
|
||||
python_version: Option<&PythonVersion>,
|
||||
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(),
|
||||
interpreter.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(),
|
||||
interpreter.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()?),
|
||||
})
|
||||
}
|
||||
|
||||
/// 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>, Cow<'_, MarkerEnvironment>), TagsError> {
|
||||
) -> 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(
|
||||
&python_platform.platform(),
|
||||
|
@ -53,12 +106,16 @@ pub(crate) fn resolution_environment(
|
|||
|
||||
// Apply the platform tags to the markers.
|
||||
let markers = match (python_platform, python_version) {
|
||||
(Some(python_platform), Some(python_version)) => {
|
||||
Cow::Owned(python_version.markers(&python_platform.markers(interpreter.markers())))
|
||||
(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()))
|
||||
}
|
||||
(Some(python_platform), None) => Cow::Owned(python_platform.markers(interpreter.markers())),
|
||||
(None, Some(python_version)) => Cow::Owned(python_version.markers(interpreter.markers())),
|
||||
(None, None) => Cow::Borrowed(interpreter.markers()),
|
||||
(None, Some(python_version)) => {
|
||||
ResolverMarkerEnvironment::from(python_version.markers(interpreter.markers()))
|
||||
}
|
||||
(None, None) => interpreter.resolver_markers(),
|
||||
};
|
||||
|
||||
Ok((tags, markers))
|
||||
|
|
|
@ -17,7 +17,7 @@ use distribution_types::{
|
|||
};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use platform_tags::Tags;
|
||||
use pypi_types::Requirement;
|
||||
use pypi_types::{Requirement, ResolverMarkerEnvironment};
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, RegistryClient};
|
||||
use uv_configuration::{
|
||||
|
@ -199,7 +199,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
|||
.chain(upgrade.constraints().cloned()),
|
||||
);
|
||||
let overrides = Overrides::from_requirements(overrides);
|
||||
let preferences = Preferences::from_iter(preferences, markers.marker_environment());
|
||||
let preferences = Preferences::from_iter(preferences, &markers);
|
||||
|
||||
// Determine any lookahead requirements.
|
||||
let lookaheads = match options.dependency_mode {
|
||||
|
@ -349,6 +349,7 @@ pub(crate) async fn install(
|
|||
compile: bool,
|
||||
index_urls: &IndexLocations,
|
||||
hasher: &HashStrategy,
|
||||
markers: &ResolverMarkerEnvironment,
|
||||
tags: &Tags,
|
||||
client: &RegistryClient,
|
||||
in_flight: &InFlight,
|
||||
|
@ -376,6 +377,7 @@ pub(crate) async fn install(
|
|||
index_urls,
|
||||
cache,
|
||||
venv,
|
||||
markers,
|
||||
tags,
|
||||
)
|
||||
.context("Failed to determine installation plan")?;
|
||||
|
@ -661,10 +663,11 @@ pub(crate) fn diagnose_resolution(
|
|||
pub(crate) fn diagnose_environment(
|
||||
resolution: &Resolution,
|
||||
venv: &PythonEnvironment,
|
||||
markers: &ResolverMarkerEnvironment,
|
||||
printer: Printer,
|
||||
) -> Result<(), Error> {
|
||||
let site_packages = SitePackages::from_environment(venv)?;
|
||||
for diagnostic in site_packages.diagnostics()? {
|
||||
for diagnostic in site_packages.diagnostics(markers)? {
|
||||
// Only surface diagnostics that are "relevant" to the current resolution.
|
||||
if resolution
|
||||
.packages()
|
||||
|
|
|
@ -55,7 +55,7 @@ pub(crate) fn pip_show(
|
|||
let site_packages = SitePackages::from_environment(&environment)?;
|
||||
|
||||
// Determine the markers to use for resolution.
|
||||
let markers = environment.interpreter().markers();
|
||||
let markers = environment.interpreter().resolver_markers();
|
||||
|
||||
// Sort and deduplicate the packages, which are keyed by name.
|
||||
packages.sort_unstable();
|
||||
|
@ -101,7 +101,7 @@ pub(crate) fn pip_show(
|
|||
metadata
|
||||
.requires_dist
|
||||
.into_iter()
|
||||
.filter(|req| req.evaluate_markers(markers, &[]))
|
||||
.filter(|req| req.evaluate_markers(&markers, &[]))
|
||||
.map(|req| req.name)
|
||||
.sorted_unstable()
|
||||
.dedup()
|
||||
|
@ -119,7 +119,7 @@ pub(crate) fn pip_show(
|
|||
let requires = metadata
|
||||
.requires_dist
|
||||
.into_iter()
|
||||
.filter(|req| req.evaluate_markers(markers, &[]))
|
||||
.filter(|req| req.evaluate_markers(&markers, &[]))
|
||||
.map(|req| req.name)
|
||||
.collect_vec();
|
||||
if !requires.is_empty() {
|
||||
|
@ -192,7 +192,7 @@ pub(crate) fn pip_show(
|
|||
|
||||
// Validate that the environment is consistent.
|
||||
if strict {
|
||||
for diagnostic in site_packages.diagnostics()? {
|
||||
for diagnostic in site_packages.diagnostics(&markers)? {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}{} {}",
|
||||
|
|
|
@ -31,7 +31,7 @@ use uv_types::{BuildIsolation, HashStrategy};
|
|||
|
||||
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger};
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::pip::{operations, resolution_environment};
|
||||
use crate::commands::pip::{operations, resolution_markers, resolution_tags};
|
||||
use crate::commands::{ExitStatus, SharedState};
|
||||
use crate::printer::Printer;
|
||||
|
||||
|
@ -183,8 +183,17 @@ pub(crate) async fn pip_sync(
|
|||
PythonRequirement::from_interpreter(interpreter)
|
||||
};
|
||||
|
||||
// Determine the environment for the resolution.
|
||||
let (tags, markers) = resolution_environment(python_version, python_platform, interpreter)?;
|
||||
// Determine the markers and tags to use for resolution.
|
||||
let markers = resolution_markers(
|
||||
python_version.as_ref(),
|
||||
python_platform.as_ref(),
|
||||
interpreter,
|
||||
);
|
||||
let tags = resolution_tags(
|
||||
python_version.as_ref(),
|
||||
python_platform.as_ref(),
|
||||
interpreter,
|
||||
)?;
|
||||
|
||||
// Collect the set of required hashes.
|
||||
let hasher = if let Some(hash_checking) = hash_checking {
|
||||
|
@ -213,7 +222,7 @@ pub(crate) async fn pip_sync(
|
|||
.cache(cache.clone())
|
||||
.index_urls(index_locations.index_urls())
|
||||
.index_strategy(index_strategy)
|
||||
.markers(&markers)
|
||||
.markers(interpreter.markers())
|
||||
.platform(interpreter.platform())
|
||||
.build();
|
||||
|
||||
|
@ -292,7 +301,7 @@ pub(crate) async fn pip_sync(
|
|||
&reinstall,
|
||||
&upgrade,
|
||||
Some(&tags),
|
||||
ResolverMarkers::specific_environment((*markers).clone()),
|
||||
ResolverMarkers::specific_environment(markers.clone()),
|
||||
python_requirement,
|
||||
&client,
|
||||
&flat_index,
|
||||
|
@ -325,6 +334,7 @@ pub(crate) async fn pip_sync(
|
|||
compile,
|
||||
&index_locations,
|
||||
&hasher,
|
||||
&markers,
|
||||
&tags,
|
||||
&client,
|
||||
&state.in_flight,
|
||||
|
@ -343,7 +353,7 @@ pub(crate) async fn pip_sync(
|
|||
|
||||
// Notify the user of any environment diagnostics.
|
||||
if strict && !dry_run {
|
||||
operations::diagnose_environment(&resolution, &environment, printer)?;
|
||||
operations::diagnose_environment(&resolution, &environment, &markers, printer)?;
|
||||
}
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
|
|
|
@ -7,8 +7,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
|||
use tracing::debug;
|
||||
|
||||
use distribution_types::{Diagnostic, Name};
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use pypi_types::RequirementSource;
|
||||
use pypi_types::{RequirementSource, ResolverMarkerEnvironment};
|
||||
use uv_cache::Cache;
|
||||
use uv_distribution::Metadata;
|
||||
use uv_fs::Simplified;
|
||||
|
@ -60,6 +59,9 @@ pub(crate) fn pip_tree(
|
|||
.push(metadata);
|
||||
}
|
||||
|
||||
// Determine the markers to use for the resolution.
|
||||
let markers = environment.interpreter().resolver_markers();
|
||||
|
||||
// Render the tree.
|
||||
let rendered_tree = DisplayDependencyGraph::new(
|
||||
depth.into(),
|
||||
|
@ -68,7 +70,7 @@ pub(crate) fn pip_tree(
|
|||
no_dedupe,
|
||||
invert,
|
||||
show_version_specifiers,
|
||||
environment.interpreter().markers(),
|
||||
&markers,
|
||||
packages,
|
||||
)
|
||||
.render()
|
||||
|
@ -87,7 +89,7 @@ pub(crate) fn pip_tree(
|
|||
|
||||
// Validate that the environment is consistent.
|
||||
if strict {
|
||||
for diagnostic in site_packages.diagnostics()? {
|
||||
for diagnostic in site_packages.diagnostics(&markers)? {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}{} {}",
|
||||
|
@ -129,7 +131,7 @@ impl DisplayDependencyGraph {
|
|||
no_dedupe: bool,
|
||||
invert: bool,
|
||||
show_version_specifiers: bool,
|
||||
markers: &MarkerEnvironment,
|
||||
markers: &ResolverMarkerEnvironment,
|
||||
packages: IndexMap<PackageName, Vec<Metadata>>,
|
||||
) -> Self {
|
||||
let mut requirements: FxHashMap<_, Vec<_>> = FxHashMap::default();
|
||||
|
|
|
@ -534,7 +534,7 @@ pub(crate) async fn resolve_environment<'a>(
|
|||
|
||||
// Determine the tags, markers, and interpreter to use for resolution.
|
||||
let tags = interpreter.tags()?;
|
||||
let markers = interpreter.markers();
|
||||
let markers = interpreter.resolver_markers();
|
||||
let python_requirement = PythonRequirement::from_interpreter(interpreter);
|
||||
|
||||
// Add all authenticated sources to the cache.
|
||||
|
@ -549,7 +549,7 @@ pub(crate) async fn resolve_environment<'a>(
|
|||
.index_urls(index_locations.index_urls())
|
||||
.index_strategy(index_strategy)
|
||||
.keyring(keyring_provider)
|
||||
.markers(markers)
|
||||
.markers(interpreter.markers())
|
||||
.platform(interpreter.platform())
|
||||
.build();
|
||||
|
||||
|
@ -629,7 +629,7 @@ pub(crate) async fn resolve_environment<'a>(
|
|||
&reinstall,
|
||||
&upgrade,
|
||||
Some(tags),
|
||||
ResolverMarkers::specific_environment(markers.clone()),
|
||||
ResolverMarkers::specific_environment(markers),
|
||||
python_requirement,
|
||||
&client,
|
||||
&flat_index,
|
||||
|
@ -673,10 +673,10 @@ pub(crate) async fn sync_environment(
|
|||
|
||||
let site_packages = SitePackages::from_environment(&venv)?;
|
||||
|
||||
// Determine the tags, markers, and interpreter to use for resolution.
|
||||
// Determine the markers tags to use for resolution.
|
||||
let interpreter = venv.interpreter();
|
||||
let tags = venv.interpreter().tags()?;
|
||||
let markers = venv.interpreter().markers();
|
||||
let markers = interpreter.resolver_markers();
|
||||
|
||||
// Add all authenticated sources to the cache.
|
||||
for url in index_locations.urls() {
|
||||
|
@ -690,7 +690,7 @@ pub(crate) async fn sync_environment(
|
|||
.index_urls(index_locations.index_urls())
|
||||
.index_strategy(index_strategy)
|
||||
.keyring(keyring_provider)
|
||||
.markers(markers)
|
||||
.markers(interpreter.markers())
|
||||
.platform(interpreter.platform())
|
||||
.build();
|
||||
|
||||
|
@ -748,6 +748,7 @@ pub(crate) async fn sync_environment(
|
|||
compile_bytecode,
|
||||
index_locations,
|
||||
&hasher,
|
||||
&markers,
|
||||
tags,
|
||||
&client,
|
||||
&state.in_flight,
|
||||
|
@ -827,10 +828,14 @@ pub(crate) async fn update_environment(
|
|||
..
|
||||
} = spec;
|
||||
|
||||
// Determine markers to use for resolution.
|
||||
let interpreter = venv.interpreter();
|
||||
let markers = venv.interpreter().resolver_markers();
|
||||
|
||||
// Check if the current environment satisfies the requirements
|
||||
let site_packages = SitePackages::from_environment(&venv)?;
|
||||
if source_trees.is_empty() && reinstall.is_none() && upgrade.is_none() && overrides.is_empty() {
|
||||
match site_packages.satisfies(&requirements, &constraints)? {
|
||||
match site_packages.satisfies(&requirements, &constraints, &markers)? {
|
||||
// If the requirements are already satisfied, we're done.
|
||||
SatisfiesResult::Fresh {
|
||||
recursive_requirements,
|
||||
|
@ -854,12 +859,6 @@ pub(crate) async fn update_environment(
|
|||
}
|
||||
}
|
||||
|
||||
// Determine the tags, markers, and interpreter to use for resolution.
|
||||
let interpreter = venv.interpreter();
|
||||
let tags = venv.interpreter().tags()?;
|
||||
let markers = venv.interpreter().markers();
|
||||
let python_requirement = PythonRequirement::from_interpreter(interpreter);
|
||||
|
||||
// Add all authenticated sources to the cache.
|
||||
for url in index_locations.urls() {
|
||||
store_credentials_from_url(url);
|
||||
|
@ -872,7 +871,7 @@ pub(crate) async fn update_environment(
|
|||
.index_urls(index_locations.index_urls())
|
||||
.index_strategy(*index_strategy)
|
||||
.keyring(*keyring_provider)
|
||||
.markers(markers)
|
||||
.markers(interpreter.markers())
|
||||
.platform(interpreter.platform())
|
||||
.build();
|
||||
|
||||
|
@ -901,6 +900,10 @@ pub(crate) async fn update_environment(
|
|||
let hasher = HashStrategy::default();
|
||||
let preferences = Vec::default();
|
||||
|
||||
// Determine the tags to use for resolution.
|
||||
let tags = venv.interpreter().tags()?;
|
||||
let python_requirement = PythonRequirement::from_interpreter(interpreter);
|
||||
|
||||
// Resolve the flat indexes from `--find-links`.
|
||||
let flat_index = {
|
||||
let client = FlatIndexClient::new(&client, cache);
|
||||
|
@ -973,6 +976,7 @@ pub(crate) async fn update_environment(
|
|||
*compile_bytecode,
|
||||
index_locations,
|
||||
&hasher,
|
||||
&markers,
|
||||
tags,
|
||||
&client,
|
||||
&state.in_flight,
|
||||
|
|
|
@ -11,7 +11,6 @@ use itertools::Itertools;
|
|||
use owo_colors::OwoColorize;
|
||||
use tokio::process::Command;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use uv_cache::Cache;
|
||||
use uv_cli::ExternalCommand;
|
||||
use uv_client::{BaseClientBuilder, Connectivity};
|
||||
|
@ -692,7 +691,11 @@ fn can_skip_ephemeral(
|
|||
return false;
|
||||
}
|
||||
|
||||
match site_packages.satisfies(&spec.requirements, &spec.constraints) {
|
||||
match site_packages.satisfies(
|
||||
&spec.requirements,
|
||||
&spec.constraints,
|
||||
&base_interpreter.resolver_markers(),
|
||||
) {
|
||||
// If the requirements are already satisfied, we're done.
|
||||
Ok(SatisfiesResult::Fresh {
|
||||
recursive_requirements,
|
||||
|
|
|
@ -173,11 +173,13 @@ pub(super) async fn do_sync(
|
|||
}
|
||||
}
|
||||
|
||||
// Determine the markers to use for resolution.
|
||||
let markers = venv.interpreter().resolver_markers();
|
||||
|
||||
// Validate that the platform is supported by the lockfile.
|
||||
let environments = lock.supported_environments();
|
||||
if !environments.is_empty() {
|
||||
let platform = venv.interpreter().markers();
|
||||
if !environments.iter().any(|env| env.evaluate(platform, &[])) {
|
||||
if !environments.iter().any(|env| env.evaluate(&markers, &[])) {
|
||||
return Err(ProjectError::LockedPlatformIncompatibility(
|
||||
environments
|
||||
.iter()
|
||||
|
@ -195,11 +197,11 @@ pub(super) async fn do_sync(
|
|||
vec![]
|
||||
};
|
||||
|
||||
let markers = venv.interpreter().markers();
|
||||
// Determine the tags to use for resolution.
|
||||
let tags = venv.interpreter().tags()?;
|
||||
|
||||
// Read the lockfile.
|
||||
let resolution = lock.to_resolution(project, markers, tags, extras, &dev)?;
|
||||
let resolution = lock.to_resolution(project, &markers, tags, extras, &dev)?;
|
||||
|
||||
// If `--no-install-project` is set, remove the project itself.
|
||||
let resolution = apply_no_install_project(no_install_project, resolution, project);
|
||||
|
@ -222,7 +224,7 @@ pub(super) async fn do_sync(
|
|||
.index_urls(index_locations.index_urls())
|
||||
.index_strategy(index_strategy)
|
||||
.keyring(keyring_provider)
|
||||
.markers(markers)
|
||||
.markers(venv.interpreter().markers())
|
||||
.platform(venv.interpreter().platform())
|
||||
.build();
|
||||
|
||||
|
@ -284,6 +286,7 @@ pub(super) async fn do_sync(
|
|||
compile_bytecode,
|
||||
index_locations,
|
||||
&hasher,
|
||||
&markers,
|
||||
tags,
|
||||
&client,
|
||||
&state.in_flight,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
|
@ -13,6 +12,7 @@ use uv_resolver::TreeDisplay;
|
|||
use uv_workspace::{DiscoveryOptions, Workspace};
|
||||
|
||||
use crate::commands::pip::loggers::DefaultResolveLogger;
|
||||
use crate::commands::pip::resolution_markers;
|
||||
use crate::commands::project::FoundInterpreter;
|
||||
use crate::commands::{project, ExitStatus};
|
||||
use crate::printer::Printer;
|
||||
|
@ -75,20 +75,17 @@ pub(crate) async fn tree(
|
|||
.await?
|
||||
.into_lock();
|
||||
|
||||
// Apply the platform tags to the markers.
|
||||
let markers = match (python_platform, python_version) {
|
||||
(Some(python_platform), Some(python_version)) => {
|
||||
Cow::Owned(python_version.markers(&python_platform.markers(interpreter.markers())))
|
||||
}
|
||||
(Some(python_platform), None) => Cow::Owned(python_platform.markers(interpreter.markers())),
|
||||
(None, Some(python_version)) => Cow::Owned(python_version.markers(interpreter.markers())),
|
||||
(None, None) => Cow::Borrowed(interpreter.markers()),
|
||||
};
|
||||
// Determine the markers to use for resolution.
|
||||
let markers = resolution_markers(
|
||||
python_version.as_ref(),
|
||||
python_platform.as_ref(),
|
||||
&interpreter,
|
||||
);
|
||||
|
||||
// Render the tree.
|
||||
let tree = TreeDisplay::new(
|
||||
&lock,
|
||||
(!universal).then(|| markers.as_ref()),
|
||||
(!universal).then_some(&markers),
|
||||
depth.into(),
|
||||
prune,
|
||||
package,
|
||||
|
|
|
@ -514,7 +514,11 @@ async fn get_or_create_environment(
|
|||
let constraints = [];
|
||||
|
||||
if matches!(
|
||||
site_packages.satisfies(&requirements, &constraints),
|
||||
site_packages.satisfies(
|
||||
&requirements,
|
||||
&constraints,
|
||||
&interpreter.resolver_markers()
|
||||
),
|
||||
Ok(SatisfiesResult::Fresh { .. })
|
||||
) {
|
||||
debug!("Using existing tool `{}`", from.name);
|
||||
|
|
|
@ -6338,3 +6338,51 @@ fn no_extension() {
|
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
"###);
|
||||
}
|
||||
|
||||
/// Regression test for: <https://github.com/astral-sh/uv/pull/6646>
|
||||
#[test]
|
||||
fn switch_platform() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("iniconfig ; python_version == '3.12'")?;
|
||||
|
||||
// Install `iniconfig`.
|
||||
uv_snapshot!(context.pip_install()
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
"###);
|
||||
|
||||
requirements_txt
|
||||
.write_str("iniconfig ; python_version == '3.12'\nanyio ; python_version < '3.12'")?;
|
||||
|
||||
// Add `anyio`, though it's only installed because of `--python-version`.
|
||||
uv_snapshot!(context.pip_install()
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("--python-version")
|
||||
.arg("3.11"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Prepared 3 packages in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
+ anyio==4.3.0
|
||||
+ idna==3.6
|
||||
+ sniffio==1.3.1
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue