mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-01 09:32:18 +00:00
uv-resolver: introduce new UniversalMarker type
This effectively combines a PEP 508 marker and an as-yet-specified marker for expressing conflicts among extras and groups. This just defines the type and threads it through most of the various points in the code that previously used `MarkerTree` only. Some parts do still continue to use `MarkerTree` specifically, e.g., when dealing with non-universal resolution or exporting to `requirements.txt`. This doesn't change any behavior.
This commit is contained in:
parent
d7e5fcbf62
commit
dae584d49b
18 changed files with 376 additions and 92 deletions
|
@ -8,12 +8,12 @@ use uv_distribution_types::{CompatibleDist, IncompatibleDist, IncompatibleSource
|
||||||
use uv_distribution_types::{DistributionMetadata, IncompatibleWheel, Name, PrioritizedDist};
|
use uv_distribution_types::{DistributionMetadata, IncompatibleWheel, Name, PrioritizedDist};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pep440::Version;
|
use uv_pep440::Version;
|
||||||
use uv_pep508::MarkerTree;
|
|
||||||
use uv_types::InstalledPackagesProvider;
|
use uv_types::InstalledPackagesProvider;
|
||||||
|
|
||||||
use crate::preferences::Preferences;
|
use crate::preferences::Preferences;
|
||||||
use crate::prerelease::{AllowPrerelease, PrereleaseStrategy};
|
use crate::prerelease::{AllowPrerelease, PrereleaseStrategy};
|
||||||
use crate::resolution_mode::ResolutionStrategy;
|
use crate::resolution_mode::ResolutionStrategy;
|
||||||
|
use crate::universal_marker::UniversalMarker;
|
||||||
use crate::version_map::{VersionMap, VersionMapDistHandle};
|
use crate::version_map::{VersionMap, VersionMapDistHandle};
|
||||||
use crate::{Exclusions, Manifest, Options, ResolverEnvironment};
|
use crate::{Exclusions, Manifest, Options, ResolverEnvironment};
|
||||||
|
|
||||||
|
@ -140,10 +140,10 @@ impl CandidateSelector {
|
||||||
// first has the matching half and then the mismatching half.
|
// first has the matching half and then the mismatching half.
|
||||||
let preferences_match = preferences
|
let preferences_match = preferences
|
||||||
.get(package_name)
|
.get(package_name)
|
||||||
.filter(|(marker, _index, _version)| env.included_by_marker(marker));
|
.filter(|(marker, _index, _version)| env.included_by_marker(marker.pep508()));
|
||||||
let preferences_mismatch = preferences
|
let preferences_mismatch = preferences
|
||||||
.get(package_name)
|
.get(package_name)
|
||||||
.filter(|(marker, _index, _version)| !env.included_by_marker(marker));
|
.filter(|(marker, _index, _version)| !env.included_by_marker(marker.pep508()));
|
||||||
let preferences = preferences_match.chain(preferences_mismatch).filter_map(
|
let preferences = preferences_match.chain(preferences_mismatch).filter_map(
|
||||||
|(marker, source, version)| {
|
|(marker, source, version)| {
|
||||||
// If the package is mapped to an explicit index, only consider preferences that
|
// If the package is mapped to an explicit index, only consider preferences that
|
||||||
|
@ -167,7 +167,7 @@ impl CandidateSelector {
|
||||||
/// Return the first preference that satisfies the current range and is allowed.
|
/// Return the first preference that satisfies the current range and is allowed.
|
||||||
fn get_preferred_from_iter<'a, InstalledPackages: InstalledPackagesProvider>(
|
fn get_preferred_from_iter<'a, InstalledPackages: InstalledPackagesProvider>(
|
||||||
&'a self,
|
&'a self,
|
||||||
preferences: impl Iterator<Item = (&'a MarkerTree, &'a Version)>,
|
preferences: impl Iterator<Item = (&'a UniversalMarker, &'a Version)>,
|
||||||
package_name: &'a PackageName,
|
package_name: &'a PackageName,
|
||||||
range: &Range<Version>,
|
range: &Range<Version>,
|
||||||
version_maps: &'a [VersionMap],
|
version_maps: &'a [VersionMap],
|
||||||
|
|
|
@ -3,7 +3,8 @@ use petgraph::visit::EdgeRef;
|
||||||
use petgraph::{Direction, Graph};
|
use petgraph::{Direction, Graph};
|
||||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use uv_pep508::MarkerTree;
|
|
||||||
|
use crate::universal_marker::UniversalMarker;
|
||||||
|
|
||||||
/// Determine the markers under which a package is reachable in the dependency tree.
|
/// Determine the markers under which a package is reachable in the dependency tree.
|
||||||
///
|
///
|
||||||
|
@ -13,9 +14,9 @@ use uv_pep508::MarkerTree;
|
||||||
/// whenever we re-reach a node through a cycle the marker we have is a more
|
/// whenever we re-reach a node through a cycle the marker we have is a more
|
||||||
/// specific marker/longer path, so we don't update the node and don't re-queue it.
|
/// specific marker/longer path, so we don't update the node and don't re-queue it.
|
||||||
pub(crate) fn marker_reachability<T>(
|
pub(crate) fn marker_reachability<T>(
|
||||||
graph: &Graph<T, MarkerTree>,
|
graph: &Graph<T, UniversalMarker>,
|
||||||
fork_markers: &[MarkerTree],
|
fork_markers: &[UniversalMarker],
|
||||||
) -> FxHashMap<NodeIndex, MarkerTree> {
|
) -> FxHashMap<NodeIndex, UniversalMarker> {
|
||||||
// Note that we build including the virtual packages due to how we propagate markers through
|
// Note that we build including the virtual packages due to how we propagate markers through
|
||||||
// the graph, even though we then only read the markers for base packages.
|
// the graph, even though we then only read the markers for base packages.
|
||||||
let mut reachability = FxHashMap::with_capacity_and_hasher(graph.node_count(), FxBuildHasher);
|
let mut reachability = FxHashMap::with_capacity_and_hasher(graph.node_count(), FxBuildHasher);
|
||||||
|
@ -36,12 +37,12 @@ pub(crate) fn marker_reachability<T>(
|
||||||
|
|
||||||
// The root nodes are always applicable, unless the user has restricted resolver
|
// The root nodes are always applicable, unless the user has restricted resolver
|
||||||
// environments with `tool.uv.environments`.
|
// environments with `tool.uv.environments`.
|
||||||
let root_markers: MarkerTree = if fork_markers.is_empty() {
|
let root_markers = if fork_markers.is_empty() {
|
||||||
MarkerTree::TRUE
|
UniversalMarker::TRUE
|
||||||
} else {
|
} else {
|
||||||
fork_markers
|
fork_markers
|
||||||
.iter()
|
.iter()
|
||||||
.fold(MarkerTree::FALSE, |mut acc, marker| {
|
.fold(UniversalMarker::FALSE, |mut acc, marker| {
|
||||||
acc.or(marker.clone());
|
acc.or(marker.clone());
|
||||||
acc
|
acc
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,6 +22,7 @@ pub use resolver::{
|
||||||
PackageVersionsResult, Reporter as ResolverReporter, Resolver, ResolverEnvironment,
|
PackageVersionsResult, Reporter as ResolverReporter, Resolver, ResolverEnvironment,
|
||||||
ResolverProvider, VersionsResponse, WheelMetadataResult,
|
ResolverProvider, VersionsResponse, WheelMetadataResult,
|
||||||
};
|
};
|
||||||
|
pub use universal_marker::UniversalMarker;
|
||||||
pub use version_map::VersionMap;
|
pub use version_map::VersionMap;
|
||||||
pub use yanks::AllowedYanks;
|
pub use yanks::AllowedYanks;
|
||||||
|
|
||||||
|
@ -56,5 +57,6 @@ mod requires_python;
|
||||||
mod resolution;
|
mod resolution;
|
||||||
mod resolution_mode;
|
mod resolution_mode;
|
||||||
mod resolver;
|
mod resolver;
|
||||||
|
mod universal_marker;
|
||||||
mod version_map;
|
mod version_map;
|
||||||
mod yanks;
|
mod yanks;
|
||||||
|
|
|
@ -19,6 +19,7 @@ pub use crate::lock::target::InstallTarget;
|
||||||
pub use crate::lock::tree::TreeDisplay;
|
pub use crate::lock::tree::TreeDisplay;
|
||||||
use crate::requires_python::SimplifiedMarkerTree;
|
use crate::requires_python::SimplifiedMarkerTree;
|
||||||
use crate::resolution::{AnnotatedDist, ResolutionGraphNode};
|
use crate::resolution::{AnnotatedDist, ResolutionGraphNode};
|
||||||
|
use crate::universal_marker::UniversalMarker;
|
||||||
use crate::{
|
use crate::{
|
||||||
ExcludeNewer, InMemoryIndex, MetadataResponse, PrereleaseMode, RequiresPython, ResolutionMode,
|
ExcludeNewer, InMemoryIndex, MetadataResponse, PrereleaseMode, RequiresPython, ResolutionMode,
|
||||||
ResolverOutput,
|
ResolverOutput,
|
||||||
|
@ -55,23 +56,26 @@ mod tree;
|
||||||
/// The current version of the lockfile format.
|
/// The current version of the lockfile format.
|
||||||
pub const VERSION: u32 = 1;
|
pub const VERSION: u32 = 1;
|
||||||
|
|
||||||
static LINUX_MARKERS: LazyLock<MarkerTree> = LazyLock::new(|| {
|
static LINUX_MARKERS: LazyLock<UniversalMarker> = LazyLock::new(|| {
|
||||||
MarkerTree::from_str(
|
let pep508 = MarkerTree::from_str(
|
||||||
"platform_system == 'Linux' and os_name == 'posix' and sys_platform == 'linux'",
|
"platform_system == 'Linux' and os_name == 'posix' and sys_platform == 'linux'",
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
|
UniversalMarker::new(pep508, MarkerTree::TRUE)
|
||||||
});
|
});
|
||||||
static WINDOWS_MARKERS: LazyLock<MarkerTree> = LazyLock::new(|| {
|
static WINDOWS_MARKERS: LazyLock<UniversalMarker> = LazyLock::new(|| {
|
||||||
MarkerTree::from_str(
|
let pep508 = MarkerTree::from_str(
|
||||||
"platform_system == 'Windows' and os_name == 'nt' and sys_platform == 'win32'",
|
"platform_system == 'Windows' and os_name == 'nt' and sys_platform == 'win32'",
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
|
UniversalMarker::new(pep508, MarkerTree::TRUE)
|
||||||
});
|
});
|
||||||
static MAC_MARKERS: LazyLock<MarkerTree> = LazyLock::new(|| {
|
static MAC_MARKERS: LazyLock<UniversalMarker> = LazyLock::new(|| {
|
||||||
MarkerTree::from_str(
|
let pep508 = MarkerTree::from_str(
|
||||||
"platform_system == 'Darwin' and os_name == 'posix' and sys_platform == 'darwin'",
|
"platform_system == 'Darwin' and os_name == 'posix' and sys_platform == 'darwin'",
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
|
UniversalMarker::new(pep508, MarkerTree::TRUE)
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Deserialize)]
|
||||||
|
@ -80,7 +84,7 @@ pub struct Lock {
|
||||||
version: u32,
|
version: u32,
|
||||||
/// If this lockfile was built from a forking resolution with non-identical forks, store the
|
/// If this lockfile was built from a forking resolution with non-identical forks, store the
|
||||||
/// forks in the lockfile so we can recreate them in subsequent resolutions.
|
/// forks in the lockfile so we can recreate them in subsequent resolutions.
|
||||||
fork_markers: Vec<MarkerTree>,
|
fork_markers: Vec<UniversalMarker>,
|
||||||
/// The conflicting groups/extras specified by the user.
|
/// The conflicting groups/extras specified by the user.
|
||||||
conflicts: Conflicts,
|
conflicts: Conflicts,
|
||||||
/// The list of supported environments specified by the user.
|
/// The list of supported environments specified by the user.
|
||||||
|
@ -315,7 +319,7 @@ impl Lock {
|
||||||
manifest: ResolverManifest,
|
manifest: ResolverManifest,
|
||||||
conflicts: Conflicts,
|
conflicts: Conflicts,
|
||||||
supported_environments: Vec<MarkerTree>,
|
supported_environments: Vec<MarkerTree>,
|
||||||
fork_markers: Vec<MarkerTree>,
|
fork_markers: Vec<UniversalMarker>,
|
||||||
) -> Result<Self, LockError> {
|
) -> Result<Self, LockError> {
|
||||||
// Put all dependencies for each package in a canonical order and
|
// Put all dependencies for each package in a canonical order and
|
||||||
// check for duplicates.
|
// check for duplicates.
|
||||||
|
@ -597,7 +601,7 @@ impl Lock {
|
||||||
|
|
||||||
/// If this lockfile was built from a forking resolution with non-identical forks, return the
|
/// If this lockfile was built from a forking resolution with non-identical forks, return the
|
||||||
/// markers of those forks, otherwise `None`.
|
/// markers of those forks, otherwise `None`.
|
||||||
pub fn fork_markers(&self) -> &[MarkerTree] {
|
pub fn fork_markers(&self) -> &[UniversalMarker] {
|
||||||
self.fork_markers.as_slice()
|
self.fork_markers.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -614,7 +618,13 @@ impl Lock {
|
||||||
let fork_markers = each_element_on_its_line_array(
|
let fork_markers = each_element_on_its_line_array(
|
||||||
self.fork_markers
|
self.fork_markers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|marker| SimplifiedMarkerTree::new(&self.requires_python, marker.clone()))
|
.map(|marker| {
|
||||||
|
// TODO(ag): Consider whether `resolution-markers` should actually
|
||||||
|
// include conflicting marker info. In which case, we should serialize
|
||||||
|
// the entire `UniversalMarker` (taking care to still make the PEP 508
|
||||||
|
// simplified).
|
||||||
|
SimplifiedMarkerTree::new(&self.requires_python, marker.pep508().clone())
|
||||||
|
})
|
||||||
.filter_map(|marker| marker.try_to_string()),
|
.filter_map(|marker| marker.try_to_string()),
|
||||||
);
|
);
|
||||||
doc.insert("resolution-markers", value(fork_markers));
|
doc.insert("resolution-markers", value(fork_markers));
|
||||||
|
@ -1434,6 +1444,9 @@ impl TryFrom<LockWire> for Lock {
|
||||||
.fork_markers
|
.fork_markers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|simplified_marker| simplified_marker.into_marker(&wire.requires_python))
|
.map(|simplified_marker| simplified_marker.into_marker(&wire.requires_python))
|
||||||
|
// TODO(ag): Consider whether this should also deserialize a conflict marker.
|
||||||
|
// We currently aren't serializing. Dropping it completely is likely to be wrong.
|
||||||
|
.map(|complexified_marker| UniversalMarker::new(complexified_marker, MarkerTree::TRUE))
|
||||||
.collect();
|
.collect();
|
||||||
let lock = Lock::new(
|
let lock = Lock::new(
|
||||||
wire.version,
|
wire.version,
|
||||||
|
@ -1475,7 +1488,7 @@ pub struct Package {
|
||||||
/// the next resolution.
|
/// the next resolution.
|
||||||
///
|
///
|
||||||
/// Named `resolution-markers` in `uv.lock`.
|
/// Named `resolution-markers` in `uv.lock`.
|
||||||
fork_markers: Vec<MarkerTree>,
|
fork_markers: Vec<UniversalMarker>,
|
||||||
/// The resolved dependencies of the package.
|
/// The resolved dependencies of the package.
|
||||||
dependencies: Vec<Dependency>,
|
dependencies: Vec<Dependency>,
|
||||||
/// The resolved optional dependencies of the package.
|
/// The resolved optional dependencies of the package.
|
||||||
|
@ -1489,7 +1502,7 @@ pub struct Package {
|
||||||
impl Package {
|
impl Package {
|
||||||
fn from_annotated_dist(
|
fn from_annotated_dist(
|
||||||
annotated_dist: &AnnotatedDist,
|
annotated_dist: &AnnotatedDist,
|
||||||
fork_markers: Vec<MarkerTree>,
|
fork_markers: Vec<UniversalMarker>,
|
||||||
root: &Path,
|
root: &Path,
|
||||||
) -> Result<Self, LockError> {
|
) -> Result<Self, LockError> {
|
||||||
let id = PackageId::from_annotated_dist(annotated_dist, root)?;
|
let id = PackageId::from_annotated_dist(annotated_dist, root)?;
|
||||||
|
@ -1549,7 +1562,7 @@ impl Package {
|
||||||
&mut self,
|
&mut self,
|
||||||
requires_python: &RequiresPython,
|
requires_python: &RequiresPython,
|
||||||
annotated_dist: &AnnotatedDist,
|
annotated_dist: &AnnotatedDist,
|
||||||
marker: MarkerTree,
|
marker: UniversalMarker,
|
||||||
root: &Path,
|
root: &Path,
|
||||||
) -> Result<(), LockError> {
|
) -> Result<(), LockError> {
|
||||||
let new_dep =
|
let new_dep =
|
||||||
|
@ -1595,7 +1608,7 @@ impl Package {
|
||||||
requires_python: &RequiresPython,
|
requires_python: &RequiresPython,
|
||||||
extra: ExtraName,
|
extra: ExtraName,
|
||||||
annotated_dist: &AnnotatedDist,
|
annotated_dist: &AnnotatedDist,
|
||||||
marker: MarkerTree,
|
marker: UniversalMarker,
|
||||||
root: &Path,
|
root: &Path,
|
||||||
) -> Result<(), LockError> {
|
) -> Result<(), LockError> {
|
||||||
let dep = Dependency::from_annotated_dist(requires_python, annotated_dist, marker, root)?;
|
let dep = Dependency::from_annotated_dist(requires_python, annotated_dist, marker, root)?;
|
||||||
|
@ -1621,7 +1634,7 @@ impl Package {
|
||||||
requires_python: &RequiresPython,
|
requires_python: &RequiresPython,
|
||||||
group: GroupName,
|
group: GroupName,
|
||||||
annotated_dist: &AnnotatedDist,
|
annotated_dist: &AnnotatedDist,
|
||||||
marker: MarkerTree,
|
marker: UniversalMarker,
|
||||||
root: &Path,
|
root: &Path,
|
||||||
) -> Result<(), LockError> {
|
) -> Result<(), LockError> {
|
||||||
let dep = Dependency::from_annotated_dist(requires_python, annotated_dist, marker, root)?;
|
let dep = Dependency::from_annotated_dist(requires_python, annotated_dist, marker, root)?;
|
||||||
|
@ -1957,7 +1970,13 @@ impl Package {
|
||||||
let wheels = each_element_on_its_line_array(
|
let wheels = each_element_on_its_line_array(
|
||||||
self.fork_markers
|
self.fork_markers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|marker| SimplifiedMarkerTree::new(requires_python, marker.clone()))
|
// TODO(ag): Consider whether `resolution-markers` should actually
|
||||||
|
// include conflicting marker info. In which case, we should serialize
|
||||||
|
// the entire `UniversalMarker` (taking care to still make the PEP 508
|
||||||
|
// simplified).
|
||||||
|
.map(|marker| {
|
||||||
|
SimplifiedMarkerTree::new(requires_python, marker.pep508().clone())
|
||||||
|
})
|
||||||
.filter_map(|marker| marker.try_to_string()),
|
.filter_map(|marker| marker.try_to_string()),
|
||||||
);
|
);
|
||||||
table.insert("resolution-markers", value(wheels));
|
table.insert("resolution-markers", value(wheels));
|
||||||
|
@ -2112,7 +2131,7 @@ impl Package {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the fork markers for this package, if any.
|
/// Return the fork markers for this package, if any.
|
||||||
pub fn fork_markers(&self) -> &[MarkerTree] {
|
pub fn fork_markers(&self) -> &[UniversalMarker] {
|
||||||
self.fork_markers.as_slice()
|
self.fork_markers.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2223,6 +2242,11 @@ impl PackageWire {
|
||||||
.fork_markers
|
.fork_markers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|simplified_marker| simplified_marker.into_marker(requires_python))
|
.map(|simplified_marker| simplified_marker.into_marker(requires_python))
|
||||||
|
// TODO(ag): Consider whether this should also deserialize a conflict marker.
|
||||||
|
// We currently aren't serializing. Dropping it completely is likely to be wrong.
|
||||||
|
.map(|complexified_marker| {
|
||||||
|
UniversalMarker::new(complexified_marker, MarkerTree::TRUE)
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
dependencies: unwire_deps(self.dependencies)?,
|
dependencies: unwire_deps(self.dependencies)?,
|
||||||
optional_dependencies: self
|
optional_dependencies: self
|
||||||
|
@ -3477,8 +3501,9 @@ impl TryFrom<WheelWire> for Wheel {
|
||||||
struct Dependency {
|
struct Dependency {
|
||||||
package_id: PackageId,
|
package_id: PackageId,
|
||||||
extra: BTreeSet<ExtraName>,
|
extra: BTreeSet<ExtraName>,
|
||||||
/// A marker simplified by assuming `requires-python` is satisfied.
|
/// A marker simplified from the PEP 508 marker in `complexified_marker`
|
||||||
/// So if `requires-python = '>=3.8'`, then
|
/// by assuming `requires-python` is satisfied. So if
|
||||||
|
/// `requires-python = '>=3.8'`, then
|
||||||
/// `python_version >= '3.8' and python_version < '3.12'`
|
/// `python_version >= '3.8' and python_version < '3.12'`
|
||||||
/// gets simplfiied to `python_version < '3.12'`.
|
/// gets simplfiied to `python_version < '3.12'`.
|
||||||
///
|
///
|
||||||
|
@ -3496,10 +3521,10 @@ struct Dependency {
|
||||||
/// `requires-python` applies to the entire lock file, it's
|
/// `requires-python` applies to the entire lock file, it's
|
||||||
/// acceptable to do comparisons on the simplified form.
|
/// acceptable to do comparisons on the simplified form.
|
||||||
simplified_marker: SimplifiedMarkerTree,
|
simplified_marker: SimplifiedMarkerTree,
|
||||||
/// The "complexified" marker is a marker that can stand on its
|
/// The "complexified" marker is a universal marker whose PEP 508
|
||||||
/// own independent of `requires-python`. It can be safely used
|
/// marker can stand on its own independent of `requires-python`.
|
||||||
/// for any kind of marker algebra.
|
/// It can be safely used for any kind of marker algebra.
|
||||||
complexified_marker: MarkerTree,
|
complexified_marker: UniversalMarker,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dependency {
|
impl Dependency {
|
||||||
|
@ -3507,10 +3532,10 @@ impl Dependency {
|
||||||
requires_python: &RequiresPython,
|
requires_python: &RequiresPython,
|
||||||
package_id: PackageId,
|
package_id: PackageId,
|
||||||
extra: BTreeSet<ExtraName>,
|
extra: BTreeSet<ExtraName>,
|
||||||
complexified_marker: MarkerTree,
|
complexified_marker: UniversalMarker,
|
||||||
) -> Dependency {
|
) -> Dependency {
|
||||||
let simplified_marker =
|
let simplified_marker =
|
||||||
SimplifiedMarkerTree::new(requires_python, complexified_marker.clone());
|
SimplifiedMarkerTree::new(requires_python, complexified_marker.pep508().clone());
|
||||||
Dependency {
|
Dependency {
|
||||||
package_id,
|
package_id,
|
||||||
extra,
|
extra,
|
||||||
|
@ -3522,7 +3547,7 @@ impl Dependency {
|
||||||
fn from_annotated_dist(
|
fn from_annotated_dist(
|
||||||
requires_python: &RequiresPython,
|
requires_python: &RequiresPython,
|
||||||
annotated_dist: &AnnotatedDist,
|
annotated_dist: &AnnotatedDist,
|
||||||
complexified_marker: MarkerTree,
|
complexified_marker: UniversalMarker,
|
||||||
root: &Path,
|
root: &Path,
|
||||||
) -> Result<Dependency, LockError> {
|
) -> Result<Dependency, LockError> {
|
||||||
let package_id = PackageId::from_annotated_dist(annotated_dist, root)?;
|
let package_id = PackageId::from_annotated_dist(annotated_dist, root)?;
|
||||||
|
@ -3590,6 +3615,7 @@ struct DependencyWire {
|
||||||
extra: BTreeSet<ExtraName>,
|
extra: BTreeSet<ExtraName>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
marker: SimplifiedMarkerTree,
|
marker: SimplifiedMarkerTree,
|
||||||
|
// FIXME: Add support for representing conflict markers.
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DependencyWire {
|
impl DependencyWire {
|
||||||
|
@ -3603,7 +3629,8 @@ impl DependencyWire {
|
||||||
package_id: self.package_id.unwire(unambiguous_package_ids)?,
|
package_id: self.package_id.unwire(unambiguous_package_ids)?,
|
||||||
extra: self.extra,
|
extra: self.extra,
|
||||||
simplified_marker: self.marker,
|
simplified_marker: self.marker,
|
||||||
complexified_marker,
|
// FIXME: Support reading conflict markers.
|
||||||
|
complexified_marker: UniversalMarker::new(complexified_marker, MarkerTree::TRUE),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4103,6 +4130,11 @@ enum LockErrorKind {
|
||||||
#[source]
|
#[source]
|
||||||
err: DependencyGroupError,
|
err: DependencyGroupError,
|
||||||
},
|
},
|
||||||
|
/// An error that occurs when trying to export a `uv.lock` with
|
||||||
|
/// conflicting extras/groups specified to `requirements.txt`.
|
||||||
|
/// (Because `requirements.txt` cannot encode them.)
|
||||||
|
#[error("Cannot represent `uv.lock` with conflicting extras or groups as `requirements.txt`")]
|
||||||
|
ConflictsNotAllowedInRequirementsTxt,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error that occurs when a source string could not be parsed.
|
/// An error that occurs when a source string could not be parsed.
|
||||||
|
|
|
@ -19,7 +19,8 @@ use uv_pep508::MarkerTree;
|
||||||
use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl};
|
use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl};
|
||||||
|
|
||||||
use crate::graph_ops::marker_reachability;
|
use crate::graph_ops::marker_reachability;
|
||||||
use crate::lock::{Package, PackageId, Source};
|
use crate::lock::{LockErrorKind, Package, PackageId, Source};
|
||||||
|
use crate::universal_marker::UniversalMarker;
|
||||||
use crate::{InstallTarget, LockError};
|
use crate::{InstallTarget, LockError};
|
||||||
|
|
||||||
/// An export of a [`Lock`] that renders in `requirements.txt` format.
|
/// An export of a [`Lock`] that renders in `requirements.txt` format.
|
||||||
|
@ -39,6 +40,9 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
||||||
hashes: bool,
|
hashes: bool,
|
||||||
install_options: &'lock InstallOptions,
|
install_options: &'lock InstallOptions,
|
||||||
) -> Result<Self, LockError> {
|
) -> Result<Self, LockError> {
|
||||||
|
if !target.lock().conflicts().is_empty() {
|
||||||
|
return Err(LockErrorKind::ConflictsNotAllowedInRequirementsTxt.into());
|
||||||
|
}
|
||||||
let size_guess = target.lock().packages.len();
|
let size_guess = target.lock().packages.len();
|
||||||
let mut petgraph = Graph::with_capacity(size_guess, size_guess);
|
let mut petgraph = Graph::with_capacity(size_guess, size_guess);
|
||||||
let mut inverse = FxHashMap::with_capacity_and_hasher(size_guess, FxBuildHasher);
|
let mut inverse = FxHashMap::with_capacity_and_hasher(size_guess, FxBuildHasher);
|
||||||
|
@ -64,7 +68,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
||||||
|
|
||||||
// Add an edge from the root.
|
// Add an edge from the root.
|
||||||
let index = inverse[&dist.id];
|
let index = inverse[&dist.id];
|
||||||
petgraph.add_edge(root, index, MarkerTree::TRUE);
|
petgraph.add_edge(root, index, UniversalMarker::TRUE);
|
||||||
|
|
||||||
// Push its dependencies on the queue.
|
// Push its dependencies on the queue.
|
||||||
queue.push_back((dist, None));
|
queue.push_back((dist, None));
|
||||||
|
@ -110,7 +114,17 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
||||||
petgraph.add_edge(
|
petgraph.add_edge(
|
||||||
root,
|
root,
|
||||||
dep_index,
|
dep_index,
|
||||||
dep.simplified_marker.as_simplified_marker_tree().clone(),
|
UniversalMarker::new(
|
||||||
|
dep.simplified_marker.as_simplified_marker_tree().clone(),
|
||||||
|
// OK because we've verified above that we do not have any
|
||||||
|
// conflicting extras/groups.
|
||||||
|
//
|
||||||
|
// So why use `UniversalMarker`? Because that's what
|
||||||
|
// `marker_reachability` wants and it (probably) isn't
|
||||||
|
// worth inventing a new abstraction so that it can accept
|
||||||
|
// graphs with either `MarkerTree` or `UniversalMarker`.
|
||||||
|
MarkerTree::TRUE,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Push its dependencies on the queue.
|
// Push its dependencies on the queue.
|
||||||
|
@ -154,7 +168,12 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
||||||
petgraph.add_edge(
|
petgraph.add_edge(
|
||||||
index,
|
index,
|
||||||
dep_index,
|
dep_index,
|
||||||
dep.simplified_marker.as_simplified_marker_tree().clone(),
|
UniversalMarker::new(
|
||||||
|
dep.simplified_marker.as_simplified_marker_tree().clone(),
|
||||||
|
// See note above for other `UniversalMarker::new` for
|
||||||
|
// why this is OK.
|
||||||
|
MarkerTree::TRUE,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Push its dependencies on the queue.
|
// Push its dependencies on the queue.
|
||||||
|
@ -187,7 +206,13 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
||||||
})
|
})
|
||||||
.map(|(index, package)| Requirement {
|
.map(|(index, package)| Requirement {
|
||||||
package,
|
package,
|
||||||
marker: reachability.remove(&index).unwrap_or_default(),
|
// As above, we've verified that there are no conflicting extras/groups
|
||||||
|
// specified, so it's safe to completely ignore the conflict marker.
|
||||||
|
marker: reachability
|
||||||
|
.remove(&index)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.pep508()
|
||||||
|
.clone(),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/uv-resolver/src/lock/tests.rs
|
source: crates/uv-resolver/src/lock/mod.rs
|
||||||
expression: result
|
expression: result
|
||||||
---
|
---
|
||||||
Ok(
|
Ok(
|
||||||
|
@ -135,7 +135,10 @@ Ok(
|
||||||
simplified_marker: SimplifiedMarkerTree(
|
simplified_marker: SimplifiedMarkerTree(
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
complexified_marker: python_full_version >= '3.12',
|
complexified_marker: UniversalMarker {
|
||||||
|
pep508_marker: python_full_version >= '3.12',
|
||||||
|
conflict_marker: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
optional_dependencies: {},
|
optional_dependencies: {},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/uv-resolver/src/lock/tests.rs
|
source: crates/uv-resolver/src/lock/mod.rs
|
||||||
expression: result
|
expression: result
|
||||||
---
|
---
|
||||||
Ok(
|
Ok(
|
||||||
|
@ -135,7 +135,10 @@ Ok(
|
||||||
simplified_marker: SimplifiedMarkerTree(
|
simplified_marker: SimplifiedMarkerTree(
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
complexified_marker: python_full_version >= '3.12',
|
complexified_marker: UniversalMarker {
|
||||||
|
pep508_marker: python_full_version >= '3.12',
|
||||||
|
conflict_marker: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
optional_dependencies: {},
|
optional_dependencies: {},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/uv-resolver/src/lock/tests.rs
|
source: crates/uv-resolver/src/lock/mod.rs
|
||||||
expression: result
|
expression: result
|
||||||
---
|
---
|
||||||
Ok(
|
Ok(
|
||||||
|
@ -135,7 +135,10 @@ Ok(
|
||||||
simplified_marker: SimplifiedMarkerTree(
|
simplified_marker: SimplifiedMarkerTree(
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
complexified_marker: python_full_version >= '3.12',
|
complexified_marker: UniversalMarker {
|
||||||
|
pep508_marker: python_full_version >= '3.12',
|
||||||
|
conflict_marker: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
optional_dependencies: {},
|
optional_dependencies: {},
|
||||||
|
|
|
@ -248,7 +248,14 @@ impl<'env> InstallTarget<'env> {
|
||||||
petgraph.add_edge(
|
petgraph.add_edge(
|
||||||
index,
|
index,
|
||||||
dep_index,
|
dep_index,
|
||||||
Edge::Dev(group.clone(), dep.complexified_marker.clone()),
|
// This is OK because we are resolving to a resolution for
|
||||||
|
// a specific marker environment and set of extras/groups.
|
||||||
|
// So at this point, we know the extras/groups have been
|
||||||
|
// satisfied, so we can safely drop the conflict marker.
|
||||||
|
//
|
||||||
|
// FIXME: Make the above true. We aren't actually checking
|
||||||
|
// the conflict marker yet.
|
||||||
|
Edge::Dev(group.clone(), dep.complexified_marker.pep508().clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Push its dependencies on the queue.
|
// Push its dependencies on the queue.
|
||||||
|
@ -373,9 +380,9 @@ impl<'env> InstallTarget<'env> {
|
||||||
index,
|
index,
|
||||||
dep_index,
|
dep_index,
|
||||||
if let Some(extra) = extra {
|
if let Some(extra) = extra {
|
||||||
Edge::Optional(extra.clone(), dep.complexified_marker.clone())
|
Edge::Optional(extra.clone(), dep.complexified_marker.pep508().clone())
|
||||||
} else {
|
} else {
|
||||||
Edge::Prod(dep.complexified_marker.clone())
|
Edge::Prod(dep.complexified_marker.pep508().clone())
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ use uv_pep508::{MarkerTree, VersionOrUrl};
|
||||||
use uv_pypi_types::{HashDigest, HashError};
|
use uv_pypi_types::{HashDigest, HashError};
|
||||||
use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement};
|
use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement};
|
||||||
|
|
||||||
|
use crate::universal_marker::UniversalMarker;
|
||||||
use crate::{LockError, ResolverEnvironment};
|
use crate::{LockError, ResolverEnvironment};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
@ -30,7 +31,7 @@ pub struct Preference {
|
||||||
index: Option<IndexUrl>,
|
index: Option<IndexUrl>,
|
||||||
/// If coming from a package with diverging versions, the markers of the forks this preference
|
/// If coming from a package with diverging versions, the markers of the forks this preference
|
||||||
/// is part of, otherwise `None`.
|
/// is part of, otherwise `None`.
|
||||||
fork_markers: Vec<MarkerTree>,
|
fork_markers: Vec<UniversalMarker>,
|
||||||
hashes: Vec<HashDigest>,
|
hashes: Vec<HashDigest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +119,7 @@ impl Preference {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Entry {
|
struct Entry {
|
||||||
marker: MarkerTree,
|
marker: UniversalMarker,
|
||||||
index: Option<IndexUrl>,
|
index: Option<IndexUrl>,
|
||||||
pin: Pin,
|
pin: Pin,
|
||||||
}
|
}
|
||||||
|
@ -169,7 +170,7 @@ impl Preferences {
|
||||||
slf.insert(
|
slf.insert(
|
||||||
preference.name,
|
preference.name,
|
||||||
preference.index,
|
preference.index,
|
||||||
MarkerTree::TRUE,
|
UniversalMarker::TRUE,
|
||||||
Pin {
|
Pin {
|
||||||
version: preference.version,
|
version: preference.version,
|
||||||
hashes: preference.hashes,
|
hashes: preference.hashes,
|
||||||
|
@ -198,7 +199,7 @@ impl Preferences {
|
||||||
&mut self,
|
&mut self,
|
||||||
package_name: PackageName,
|
package_name: PackageName,
|
||||||
index: Option<IndexUrl>,
|
index: Option<IndexUrl>,
|
||||||
markers: MarkerTree,
|
markers: UniversalMarker,
|
||||||
pin: impl Into<Pin>,
|
pin: impl Into<Pin>,
|
||||||
) {
|
) {
|
||||||
self.0.entry(package_name).or_default().push(Entry {
|
self.0.entry(package_name).or_default().push(Entry {
|
||||||
|
@ -214,7 +215,7 @@ impl Preferences {
|
||||||
) -> impl Iterator<
|
) -> impl Iterator<
|
||||||
Item = (
|
Item = (
|
||||||
&PackageName,
|
&PackageName,
|
||||||
impl Iterator<Item = (&MarkerTree, Option<&IndexUrl>, &Version)>,
|
impl Iterator<Item = (&UniversalMarker, Option<&IndexUrl>, &Version)>,
|
||||||
),
|
),
|
||||||
> {
|
> {
|
||||||
self.0.iter().map(|(name, preferences)| {
|
self.0.iter().map(|(name, preferences)| {
|
||||||
|
@ -231,7 +232,7 @@ impl Preferences {
|
||||||
pub(crate) fn get(
|
pub(crate) fn get(
|
||||||
&self,
|
&self,
|
||||||
package_name: &PackageName,
|
package_name: &PackageName,
|
||||||
) -> impl Iterator<Item = (&MarkerTree, Option<&IndexUrl>, &Version)> {
|
) -> impl Iterator<Item = (&UniversalMarker, Option<&IndexUrl>, &Version)> {
|
||||||
self.0
|
self.0
|
||||||
.get(package_name)
|
.get(package_name)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
@ -46,6 +46,11 @@ enum DisplayResolutionGraphNode<'dist> {
|
||||||
|
|
||||||
impl<'a> DisplayResolutionGraph<'a> {
|
impl<'a> DisplayResolutionGraph<'a> {
|
||||||
/// Create a new [`DisplayResolutionGraph`] for the given graph.
|
/// Create a new [`DisplayResolutionGraph`] for the given graph.
|
||||||
|
///
|
||||||
|
/// Note that this panics if any of the forks in the given resolver
|
||||||
|
/// output contain non-empty conflicting groups. That is, when using `uv
|
||||||
|
/// pip compile`, specifying conflicts is not supported because their
|
||||||
|
/// conditional logic cannot be encoded into a `requirements.txt`.
|
||||||
#[allow(clippy::fn_params_excessive_bools)]
|
#[allow(clippy::fn_params_excessive_bools)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
underlying: &'a ResolverOutput,
|
underlying: &'a ResolverOutput,
|
||||||
|
@ -58,6 +63,13 @@ impl<'a> DisplayResolutionGraph<'a> {
|
||||||
include_index_annotation: bool,
|
include_index_annotation: bool,
|
||||||
annotation_style: AnnotationStyle,
|
annotation_style: AnnotationStyle,
|
||||||
) -> DisplayResolutionGraph<'a> {
|
) -> DisplayResolutionGraph<'a> {
|
||||||
|
for fork_marker in &underlying.fork_markers {
|
||||||
|
assert!(
|
||||||
|
fork_marker.conflict().is_true(),
|
||||||
|
"found fork marker {fork_marker} with non-trivial conflicting marker, \
|
||||||
|
cannot display resolver output with conflicts in requirements.txt format",
|
||||||
|
);
|
||||||
|
}
|
||||||
Self {
|
Self {
|
||||||
resolution: underlying,
|
resolution: underlying,
|
||||||
env,
|
env,
|
||||||
|
|
|
@ -7,13 +7,13 @@ use uv_distribution_types::{
|
||||||
};
|
};
|
||||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||||
use uv_pep440::Version;
|
use uv_pep440::Version;
|
||||||
use uv_pep508::MarkerTree;
|
|
||||||
use uv_pypi_types::HashDigest;
|
use uv_pypi_types::HashDigest;
|
||||||
|
|
||||||
pub use crate::resolution::display::{AnnotationStyle, DisplayResolutionGraph};
|
pub use crate::resolution::display::{AnnotationStyle, DisplayResolutionGraph};
|
||||||
pub(crate) use crate::resolution::output::ResolutionGraphNode;
|
pub(crate) use crate::resolution::output::ResolutionGraphNode;
|
||||||
pub use crate::resolution::output::{ConflictingDistributionError, ResolverOutput};
|
pub use crate::resolution::output::{ConflictingDistributionError, ResolverOutput};
|
||||||
pub(crate) use crate::resolution::requirements_txt::RequirementsTxtDist;
|
pub(crate) use crate::resolution::requirements_txt::RequirementsTxtDist;
|
||||||
|
use crate::universal_marker::UniversalMarker;
|
||||||
|
|
||||||
mod display;
|
mod display;
|
||||||
mod output;
|
mod output;
|
||||||
|
@ -36,7 +36,7 @@ pub(crate) struct AnnotatedDist {
|
||||||
/// That is, when doing a traversal over all of the distributions in a
|
/// That is, when doing a traversal over all of the distributions in a
|
||||||
/// resolution, this marker corresponds to the disjunction of all paths to
|
/// resolution, this marker corresponds to the disjunction of all paths to
|
||||||
/// this distribution in the resolution graph.
|
/// this distribution in the resolution graph.
|
||||||
pub(crate) marker: MarkerTree,
|
pub(crate) marker: UniversalMarker,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnnotatedDist {
|
impl AnnotatedDist {
|
||||||
|
|
|
@ -28,6 +28,7 @@ use crate::redirect::url_to_precise;
|
||||||
use crate::resolution::AnnotatedDist;
|
use crate::resolution::AnnotatedDist;
|
||||||
use crate::resolution_mode::ResolutionStrategy;
|
use crate::resolution_mode::ResolutionStrategy;
|
||||||
use crate::resolver::{Resolution, ResolutionDependencyEdge, ResolutionPackage};
|
use crate::resolver::{Resolution, ResolutionDependencyEdge, ResolutionPackage};
|
||||||
|
use crate::universal_marker::UniversalMarker;
|
||||||
use crate::{
|
use crate::{
|
||||||
InMemoryIndex, MetadataResponse, Options, PythonRequirement, RequiresPython, ResolveError,
|
InMemoryIndex, MetadataResponse, Options, PythonRequirement, RequiresPython, ResolveError,
|
||||||
VersionsResponse,
|
VersionsResponse,
|
||||||
|
@ -40,12 +41,12 @@ use crate::{
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ResolverOutput {
|
pub struct ResolverOutput {
|
||||||
/// The underlying graph.
|
/// The underlying graph.
|
||||||
pub(crate) graph: Graph<ResolutionGraphNode, MarkerTree, Directed>,
|
pub(crate) graph: Graph<ResolutionGraphNode, UniversalMarker, Directed>,
|
||||||
/// The range of supported Python versions.
|
/// The range of supported Python versions.
|
||||||
pub(crate) requires_python: RequiresPython,
|
pub(crate) requires_python: RequiresPython,
|
||||||
/// If the resolution had non-identical forks, store the forks in the lockfile so we can
|
/// If the resolution had non-identical forks, store the forks in the lockfile so we can
|
||||||
/// recreate them in subsequent resolutions.
|
/// recreate them in subsequent resolutions.
|
||||||
pub(crate) fork_markers: Vec<MarkerTree>,
|
pub(crate) fork_markers: Vec<UniversalMarker>,
|
||||||
/// Any diagnostics that were encountered while building the graph.
|
/// Any diagnostics that were encountered while building the graph.
|
||||||
pub(crate) diagnostics: Vec<ResolutionDiagnostic>,
|
pub(crate) diagnostics: Vec<ResolutionDiagnostic>,
|
||||||
/// The requirements that were used to build the graph.
|
/// The requirements that were used to build the graph.
|
||||||
|
@ -65,9 +66,9 @@ pub(crate) enum ResolutionGraphNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolutionGraphNode {
|
impl ResolutionGraphNode {
|
||||||
pub(crate) fn marker(&self) -> &MarkerTree {
|
pub(crate) fn marker(&self) -> &UniversalMarker {
|
||||||
match self {
|
match self {
|
||||||
ResolutionGraphNode::Root => &MarkerTree::TRUE,
|
ResolutionGraphNode::Root => &UniversalMarker::TRUE,
|
||||||
ResolutionGraphNode::Dist(dist) => &dist.marker,
|
ResolutionGraphNode::Dist(dist) => &dist.marker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +109,7 @@ impl ResolverOutput {
|
||||||
options: Options,
|
options: Options,
|
||||||
) -> Result<Self, ResolveError> {
|
) -> Result<Self, ResolveError> {
|
||||||
let size_guess = resolutions[0].nodes.len();
|
let size_guess = resolutions[0].nodes.len();
|
||||||
let mut graph: Graph<ResolutionGraphNode, MarkerTree, Directed> =
|
let mut graph: Graph<ResolutionGraphNode, UniversalMarker, Directed> =
|
||||||
Graph::with_capacity(size_guess, size_guess);
|
Graph::with_capacity(size_guess, size_guess);
|
||||||
let mut inverse: FxHashMap<PackageRef, NodeIndex<u32>> =
|
let mut inverse: FxHashMap<PackageRef, NodeIndex<u32>> =
|
||||||
FxHashMap::with_capacity_and_hasher(size_guess, FxBuildHasher);
|
FxHashMap::with_capacity_and_hasher(size_guess, FxBuildHasher);
|
||||||
|
@ -141,7 +142,7 @@ impl ResolverOutput {
|
||||||
|
|
||||||
let mut seen = FxHashSet::default();
|
let mut seen = FxHashSet::default();
|
||||||
for resolution in resolutions {
|
for resolution in resolutions {
|
||||||
let marker = resolution.env.try_markers().cloned().unwrap_or_default();
|
let marker = resolution.env.try_universal_markers().unwrap_or_default();
|
||||||
|
|
||||||
// Add every edge to the graph, propagating the marker for the current fork, if
|
// Add every edge to the graph, propagating the marker for the current fork, if
|
||||||
// necessary.
|
// necessary.
|
||||||
|
@ -158,12 +159,19 @@ impl ResolverOutput {
|
||||||
// Extract the `Requires-Python` range, if provided.
|
// Extract the `Requires-Python` range, if provided.
|
||||||
let requires_python = python.target().clone();
|
let requires_python = python.target().clone();
|
||||||
|
|
||||||
let fork_markers: Vec<MarkerTree> = if let [resolution] = resolutions {
|
let fork_markers: Vec<UniversalMarker> = if let [resolution] = resolutions {
|
||||||
resolution.env.try_markers().cloned().into_iter().collect()
|
resolution
|
||||||
|
.env
|
||||||
|
.try_markers()
|
||||||
|
.cloned()
|
||||||
|
.into_iter()
|
||||||
|
.map(|marker| UniversalMarker::new(marker, MarkerTree::TRUE))
|
||||||
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
resolutions
|
resolutions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|resolution| resolution.env.try_markers().cloned().unwrap_or_default())
|
.map(|resolution| resolution.env.try_markers().cloned().unwrap_or_default())
|
||||||
|
.map(|marker| UniversalMarker::new(marker, MarkerTree::TRUE))
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -206,6 +214,9 @@ impl ResolverOutput {
|
||||||
// the same time. At which point, uv will report an error,
|
// the same time. At which point, uv will report an error,
|
||||||
// thereby sidestepping the possibility of installing different
|
// thereby sidestepping the possibility of installing different
|
||||||
// versions of the same package into the same virtualenv. ---AG
|
// versions of the same package into the same virtualenv. ---AG
|
||||||
|
//
|
||||||
|
// FIXME: When `UniversalMarker` supports extras/groups, we can
|
||||||
|
// re-enable this.
|
||||||
if conflicts.is_empty() {
|
if conflicts.is_empty() {
|
||||||
#[allow(unused_mut, reason = "Used in debug_assertions below")]
|
#[allow(unused_mut, reason = "Used in debug_assertions below")]
|
||||||
let mut conflicting = output.find_conflicting_distributions();
|
let mut conflicting = output.find_conflicting_distributions();
|
||||||
|
@ -234,11 +245,11 @@ impl ResolverOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_edge(
|
fn add_edge(
|
||||||
graph: &mut Graph<ResolutionGraphNode, MarkerTree>,
|
graph: &mut Graph<ResolutionGraphNode, UniversalMarker>,
|
||||||
inverse: &mut FxHashMap<PackageRef<'_>, NodeIndex>,
|
inverse: &mut FxHashMap<PackageRef<'_>, NodeIndex>,
|
||||||
root_index: NodeIndex,
|
root_index: NodeIndex,
|
||||||
edge: &ResolutionDependencyEdge,
|
edge: &ResolutionDependencyEdge,
|
||||||
marker: MarkerTree,
|
marker: UniversalMarker,
|
||||||
) {
|
) {
|
||||||
let from_index = edge.from.as_ref().map_or(root_index, |from| {
|
let from_index = edge.from.as_ref().map_or(root_index, |from| {
|
||||||
inverse[&PackageRef {
|
inverse[&PackageRef {
|
||||||
|
@ -260,25 +271,25 @@ impl ResolverOutput {
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let edge_marker = {
|
let edge_marker = {
|
||||||
let mut edge_marker = edge.marker.clone();
|
let mut edge_marker = edge.universal_marker();
|
||||||
edge_marker.and(marker);
|
edge_marker.and(marker);
|
||||||
edge_marker
|
edge_marker
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(marker) = graph
|
if let Some(weight) = graph
|
||||||
.find_edge(from_index, to_index)
|
.find_edge(from_index, to_index)
|
||||||
.and_then(|edge| graph.edge_weight_mut(edge))
|
.and_then(|edge| graph.edge_weight_mut(edge))
|
||||||
{
|
{
|
||||||
// If either the existing marker or new marker is `true`, then the dependency is
|
// If either the existing marker or new marker is `true`, then the dependency is
|
||||||
// included unconditionally, and so the combined marker is `true`.
|
// included unconditionally, and so the combined marker is `true`.
|
||||||
marker.or(edge_marker);
|
weight.or(edge_marker);
|
||||||
} else {
|
} else {
|
||||||
graph.update_edge(from_index, to_index, edge_marker);
|
graph.update_edge(from_index, to_index, edge_marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_version<'a>(
|
fn add_version<'a>(
|
||||||
graph: &mut Graph<ResolutionGraphNode, MarkerTree>,
|
graph: &mut Graph<ResolutionGraphNode, UniversalMarker>,
|
||||||
inverse: &mut FxHashMap<PackageRef<'a>, NodeIndex>,
|
inverse: &mut FxHashMap<PackageRef<'a>, NodeIndex>,
|
||||||
diagnostics: &mut Vec<ResolutionDiagnostic>,
|
diagnostics: &mut Vec<ResolutionDiagnostic>,
|
||||||
preferences: &Preferences,
|
preferences: &Preferences,
|
||||||
|
@ -339,7 +350,7 @@ impl ResolverOutput {
|
||||||
dev: dev.clone(),
|
dev: dev.clone(),
|
||||||
hashes,
|
hashes,
|
||||||
metadata,
|
metadata,
|
||||||
marker: MarkerTree::TRUE,
|
marker: UniversalMarker::TRUE,
|
||||||
}));
|
}));
|
||||||
inverse.insert(
|
inverse.insert(
|
||||||
PackageRef {
|
PackageRef {
|
||||||
|
@ -703,7 +714,7 @@ impl ResolverOutput {
|
||||||
/// an installation in that marker environment could wind up trying to
|
/// an installation in that marker environment could wind up trying to
|
||||||
/// install different versions of the same package, which is not allowed.
|
/// install different versions of the same package, which is not allowed.
|
||||||
fn find_conflicting_distributions(&self) -> Vec<ConflictingDistributionError> {
|
fn find_conflicting_distributions(&self) -> Vec<ConflictingDistributionError> {
|
||||||
let mut name_to_markers: BTreeMap<&PackageName, Vec<(&Version, &MarkerTree)>> =
|
let mut name_to_markers: BTreeMap<&PackageName, Vec<(&Version, &UniversalMarker)>> =
|
||||||
BTreeMap::new();
|
BTreeMap::new();
|
||||||
for node in self.graph.node_weights() {
|
for node in self.graph.node_weights() {
|
||||||
let annotated_dist = match node {
|
let annotated_dist = match node {
|
||||||
|
@ -749,8 +760,8 @@ pub struct ConflictingDistributionError {
|
||||||
name: PackageName,
|
name: PackageName,
|
||||||
version1: Version,
|
version1: Version,
|
||||||
version2: Version,
|
version2: Version,
|
||||||
marker1: MarkerTree,
|
marker1: UniversalMarker,
|
||||||
marker2: MarkerTree,
|
marker2: UniversalMarker,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for ConflictingDistributionError {}
|
impl std::error::Error for ConflictingDistributionError {}
|
||||||
|
@ -767,8 +778,8 @@ impl Display for ConflictingDistributionError {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"found conflicting versions for package `{name}`:
|
"found conflicting versions for package `{name}`:
|
||||||
`{marker1:?}` (for version `{version1}`) is not disjoint with \
|
`{marker1}` (for version `{version1}`) is not disjoint with \
|
||||||
`{marker2:?}` (for version `{version2}`)",
|
`{marker2}` (for version `{version2}`)",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -824,7 +835,11 @@ impl From<ResolverOutput> for uv_distribution_types::Resolution {
|
||||||
// Re-add the edges to the reduced graph.
|
// Re-add the edges to the reduced graph.
|
||||||
for edge in graph.edge_indices() {
|
for edge in graph.edge_indices() {
|
||||||
let (source, target) = graph.edge_endpoints(edge).unwrap();
|
let (source, target) = graph.edge_endpoints(edge).unwrap();
|
||||||
let marker = graph[edge].clone();
|
// OK to ignore conflicting marker because we've asserted
|
||||||
|
// above that we aren't in universal mode. If we aren't in
|
||||||
|
// universal mode, then there can be no conflicts since
|
||||||
|
// conflicts imply forks and forks imply universal mode.
|
||||||
|
let marker = graph[edge].pep508().clone();
|
||||||
|
|
||||||
match (&graph[source], &graph[target]) {
|
match (&graph[source], &graph[target]) {
|
||||||
(ResolutionGraphNode::Root, ResolutionGraphNode::Dist(target_dist)) => {
|
(ResolutionGraphNode::Root, ResolutionGraphNode::Dist(target_dist)) => {
|
||||||
|
@ -860,7 +875,7 @@ impl From<ResolverOutput> for uv_distribution_types::Resolution {
|
||||||
|
|
||||||
/// Find any packages that don't have any lower bound on them when in resolution-lowest mode.
|
/// Find any packages that don't have any lower bound on them when in resolution-lowest mode.
|
||||||
fn report_missing_lower_bounds(
|
fn report_missing_lower_bounds(
|
||||||
graph: &Graph<ResolutionGraphNode, MarkerTree>,
|
graph: &Graph<ResolutionGraphNode, UniversalMarker>,
|
||||||
diagnostics: &mut Vec<ResolutionDiagnostic>,
|
diagnostics: &mut Vec<ResolutionDiagnostic>,
|
||||||
) {
|
) {
|
||||||
for node_index in graph.node_indices() {
|
for node_index in graph.node_indices() {
|
||||||
|
@ -887,7 +902,7 @@ fn report_missing_lower_bounds(
|
||||||
fn has_lower_bound(
|
fn has_lower_bound(
|
||||||
node_index: NodeIndex,
|
node_index: NodeIndex,
|
||||||
package_name: &PackageName,
|
package_name: &PackageName,
|
||||||
graph: &Graph<ResolutionGraphNode, MarkerTree>,
|
graph: &Graph<ResolutionGraphNode, UniversalMarker>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
for neighbor_index in graph.neighbors_directed(node_index, Direction::Incoming) {
|
for neighbor_index in graph.neighbors_directed(node_index, Direction::Incoming) {
|
||||||
let neighbor_dist = match graph.node_weight(neighbor_index).unwrap() {
|
let neighbor_dist = match graph.node_weight(neighbor_index).unwrap() {
|
||||||
|
|
|
@ -167,11 +167,20 @@ impl<'dist> RequirementsTxtDist<'dist> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_annotated_dist(annotated: &'dist AnnotatedDist) -> Self {
|
pub(crate) fn from_annotated_dist(annotated: &'dist AnnotatedDist) -> Self {
|
||||||
|
assert!(
|
||||||
|
annotated.marker.conflict().is_true(),
|
||||||
|
"found dist {annotated} with non-trivial conflicting marker {marker}, \
|
||||||
|
which cannot be represented in a `requirements.txt` format",
|
||||||
|
marker = annotated.marker,
|
||||||
|
);
|
||||||
Self {
|
Self {
|
||||||
dist: &annotated.dist,
|
dist: &annotated.dist,
|
||||||
version: &annotated.version,
|
version: &annotated.version,
|
||||||
hashes: &annotated.hashes,
|
hashes: &annotated.hashes,
|
||||||
markers: &annotated.marker,
|
// OK because we've asserted above that this dist
|
||||||
|
// does not have a non-trivial conflicting marker
|
||||||
|
// that we would otherwise need to care about.
|
||||||
|
markers: annotated.marker.pep508(),
|
||||||
extras: if let Some(extra) = annotated.extra.clone() {
|
extras: if let Some(extra) = annotated.extra.clone() {
|
||||||
vec![extra]
|
vec![extra]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,6 +6,7 @@ use uv_pypi_types::{ConflictItem, ConflictItemRef, ResolverMarkerEnvironment};
|
||||||
use crate::pubgrub::{PubGrubDependency, PubGrubPackage};
|
use crate::pubgrub::{PubGrubDependency, PubGrubPackage};
|
||||||
use crate::requires_python::RequiresPythonRange;
|
use crate::requires_python::RequiresPythonRange;
|
||||||
use crate::resolver::ForkState;
|
use crate::resolver::ForkState;
|
||||||
|
use crate::universal_marker::UniversalMarker;
|
||||||
use crate::PythonRequirement;
|
use crate::PythonRequirement;
|
||||||
|
|
||||||
/// Represents one or more marker environments for a resolution.
|
/// Represents one or more marker environments for a resolution.
|
||||||
|
@ -333,6 +334,26 @@ impl ResolverEnvironment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a universal marker expression corresponding to the fork that is
|
||||||
|
/// represented by this resolver environment. A universal marker includes
|
||||||
|
/// not just the standard PEP 508 marker, but also a marker based on
|
||||||
|
/// conflicting extras/groups.
|
||||||
|
///
|
||||||
|
/// This returns `None` when this does not correspond to a fork.
|
||||||
|
pub(crate) fn try_universal_markers(&self) -> Option<UniversalMarker> {
|
||||||
|
match self.kind {
|
||||||
|
Kind::Specific { .. } => None,
|
||||||
|
Kind::Universal { ref markers, .. } => {
|
||||||
|
if markers.is_true() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// FIXME: Support conflicts.
|
||||||
|
Some(UniversalMarker::new(markers.clone(), MarkerTree::TRUE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a requires-python version range derived from the marker
|
/// Returns a requires-python version range derived from the marker
|
||||||
/// expression describing this resolver environment.
|
/// expression describing this resolver environment.
|
||||||
///
|
///
|
||||||
|
|
|
@ -63,6 +63,7 @@ pub(crate) use crate::resolver::availability::{
|
||||||
};
|
};
|
||||||
use crate::resolver::batch_prefetch::BatchPrefetcher;
|
use crate::resolver::batch_prefetch::BatchPrefetcher;
|
||||||
pub use crate::resolver::derivation::DerivationChainBuilder;
|
pub use crate::resolver::derivation::DerivationChainBuilder;
|
||||||
|
use crate::universal_marker::UniversalMarker;
|
||||||
|
|
||||||
use crate::resolver::groups::Groups;
|
use crate::resolver::groups::Groups;
|
||||||
pub use crate::resolver::index::InMemoryIndex;
|
pub use crate::resolver::index::InMemoryIndex;
|
||||||
|
@ -384,9 +385,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
package.index.clone(),
|
package.index.clone(),
|
||||||
resolution
|
resolution
|
||||||
.env
|
.env
|
||||||
.try_markers()
|
.try_universal_markers()
|
||||||
.cloned()
|
.unwrap_or(UniversalMarker::TRUE),
|
||||||
.unwrap_or(MarkerTree::TRUE),
|
|
||||||
version.clone(),
|
version.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2612,6 +2612,13 @@ pub(crate) struct ResolutionDependencyEdge {
|
||||||
pub(crate) marker: MarkerTree,
|
pub(crate) marker: MarkerTree,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ResolutionDependencyEdge {
|
||||||
|
pub(crate) fn universal_marker(&self) -> UniversalMarker {
|
||||||
|
// FIXME: Account for extras and groups here.
|
||||||
|
UniversalMarker::new(self.marker.clone(), MarkerTree::TRUE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetch the metadata for an item
|
/// Fetch the metadata for an item
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
|
131
crates/uv-resolver/src/universal_marker.rs
Normal file
131
crates/uv-resolver/src/universal_marker.rs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
use uv_normalize::ExtraName;
|
||||||
|
use uv_pep508::{MarkerEnvironment, MarkerTree};
|
||||||
|
|
||||||
|
/// A representation of a marker for use in universal resolution.
|
||||||
|
///
|
||||||
|
/// (This also degrades gracefully to a standard PEP 508 marker in the case of
|
||||||
|
/// non-universal resolution.)
|
||||||
|
///
|
||||||
|
/// This universal marker is meant to combine both a PEP 508 marker and a
|
||||||
|
/// marker for conflicting extras/groups. The latter specifically expresses
|
||||||
|
/// whether a particular edge in a dependency graph should be followed
|
||||||
|
/// depending on the activated extras and groups.
|
||||||
|
///
|
||||||
|
/// A universal marker evaluates to true only when *both* its PEP 508 marker
|
||||||
|
/// and its conflict marker evaluate to true.
|
||||||
|
#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct UniversalMarker {
|
||||||
|
pep508_marker: MarkerTree,
|
||||||
|
conflict_marker: MarkerTree,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UniversalMarker {
|
||||||
|
/// A constant universal marker that always evaluates to `true`.
|
||||||
|
pub(crate) const TRUE: UniversalMarker = UniversalMarker {
|
||||||
|
pep508_marker: MarkerTree::TRUE,
|
||||||
|
conflict_marker: MarkerTree::TRUE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A constant universal marker that always evaluates to `false`.
|
||||||
|
pub(crate) const FALSE: UniversalMarker = UniversalMarker {
|
||||||
|
pep508_marker: MarkerTree::FALSE,
|
||||||
|
conflict_marker: MarkerTree::FALSE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates a new universal marker from its constituent pieces.
|
||||||
|
pub(crate) fn new(pep508_marker: MarkerTree, conflict_marker: MarkerTree) -> UniversalMarker {
|
||||||
|
UniversalMarker {
|
||||||
|
pep508_marker,
|
||||||
|
conflict_marker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combine this universal marker with the one given in a way that unions
|
||||||
|
/// them. That is, the updated marker will evaluate to `true` if `self` or
|
||||||
|
/// `other` evaluate to `true`.
|
||||||
|
pub(crate) fn or(&mut self, other: UniversalMarker) {
|
||||||
|
self.pep508_marker.or(other.pep508_marker);
|
||||||
|
self.conflict_marker.or(other.conflict_marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combine this universal marker with the one given in a way that
|
||||||
|
/// intersects them. That is, the updated marker will evaluate to `true` if
|
||||||
|
/// `self` and `other` evaluate to `true`.
|
||||||
|
pub(crate) fn and(&mut self, other: UniversalMarker) {
|
||||||
|
self.pep508_marker.and(other.pep508_marker);
|
||||||
|
self.conflict_marker.and(other.conflict_marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this universal marker will always evaluate to `true`.
|
||||||
|
pub(crate) fn is_true(&self) -> bool {
|
||||||
|
self.pep508_marker.is_true() && self.conflict_marker.is_true()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this universal marker will always evaluate to `false`.
|
||||||
|
pub(crate) fn is_false(&self) -> bool {
|
||||||
|
self.pep508_marker.is_false() || self.conflict_marker.is_false()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this universal marker is disjoint with the one given.
|
||||||
|
///
|
||||||
|
/// Two universal markers are disjoint when it is impossible for them both
|
||||||
|
/// to evaluate to `true` simultaneously.
|
||||||
|
pub(crate) fn is_disjoint(&self, other: &UniversalMarker) -> bool {
|
||||||
|
self.pep508_marker.is_disjoint(&other.pep508_marker)
|
||||||
|
|| self.conflict_marker.is_disjoint(&other.conflict_marker)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this universal marker is satisfied by the given
|
||||||
|
/// marker environment and list of activated extras.
|
||||||
|
///
|
||||||
|
/// FIXME: This also needs to accept a list of groups.
|
||||||
|
pub(crate) fn evaluate(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
||||||
|
self.pep508_marker.evaluate(env, extras) && self.conflict_marker.evaluate(env, extras)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the PEP 508 marker for this universal marker.
|
||||||
|
///
|
||||||
|
/// One should be cautious using this. Generally speaking, it should only
|
||||||
|
/// be used when one knows universal resolution isn't in effect. When
|
||||||
|
/// universal resolution is enabled (i.e., there may be multiple forks
|
||||||
|
/// producing different versions of the same package), then one should
|
||||||
|
/// always use a universal marker since it accounts for all possible ways
|
||||||
|
/// for a package to be installed.
|
||||||
|
pub fn pep508(&self) -> &MarkerTree {
|
||||||
|
&self.pep508_marker
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the non-PEP 508 marker expression that represents conflicting
|
||||||
|
/// extras/groups.
|
||||||
|
///
|
||||||
|
/// Like with `UniversalMarker::pep508`, one should be cautious when using
|
||||||
|
/// this. It is generally always wrong to consider conflicts in isolation
|
||||||
|
/// from PEP 508 markers. But this can be useful for detecting failure
|
||||||
|
/// cases. For example, the code for emitting a `ResolverOutput` (even a
|
||||||
|
/// universal one) in a `requirements.txt` format checks for the existence
|
||||||
|
/// of non-trivial conflict markers and fails if any are found. (Because
|
||||||
|
/// conflict markers cannot be represented in the `requirements.txt`
|
||||||
|
/// format.)
|
||||||
|
pub fn conflict(&self) -> &MarkerTree {
|
||||||
|
&self.conflict_marker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for UniversalMarker {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
if self.pep508_marker.is_false() || self.conflict_marker.is_false() {
|
||||||
|
return write!(f, "`false`");
|
||||||
|
}
|
||||||
|
match (
|
||||||
|
self.pep508_marker.contents(),
|
||||||
|
self.conflict_marker.contents(),
|
||||||
|
) {
|
||||||
|
(None, None) => write!(f, "`true`"),
|
||||||
|
(Some(pep508), None) => write!(f, "`{pep508}`"),
|
||||||
|
(None, Some(conflict)) => write!(f, "`true` (conflict marker: `{conflict}`"),
|
||||||
|
(Some(pep508), Some(conflict)) => {
|
||||||
|
write!(f, "`{pep508}` (conflict marker: `{conflict}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,8 @@ use uv_requirements::upgrade::{read_lock_requirements, LockedRequirements};
|
||||||
use uv_requirements::ExtrasResolver;
|
use uv_requirements::ExtrasResolver;
|
||||||
use uv_resolver::{
|
use uv_resolver::{
|
||||||
FlatIndex, InMemoryIndex, Lock, LockVersion, Options, OptionsBuilder, PythonRequirement,
|
FlatIndex, InMemoryIndex, Lock, LockVersion, Options, OptionsBuilder, PythonRequirement,
|
||||||
RequiresPython, ResolverEnvironment, ResolverManifest, SatisfiesResult, VERSION,
|
RequiresPython, ResolverEnvironment, ResolverManifest, SatisfiesResult, UniversalMarker,
|
||||||
|
VERSION,
|
||||||
};
|
};
|
||||||
use uv_settings::PythonInstallMirrors;
|
use uv_settings::PythonInstallMirrors;
|
||||||
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy};
|
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy};
|
||||||
|
@ -588,7 +589,18 @@ async fn do_lock(
|
||||||
// the environment changed, e.g. the python bound check above can lead to different forking.
|
// the environment changed, e.g. the python bound check above can lead to different forking.
|
||||||
let resolver_env = ResolverEnvironment::universal(
|
let resolver_env = ResolverEnvironment::universal(
|
||||||
forks_lock
|
forks_lock
|
||||||
.map(|lock| lock.fork_markers().to_vec())
|
.map(|lock| {
|
||||||
|
// TODO(ag): Consider whether we should be capturing
|
||||||
|
// conflicting extras/groups for every fork. If
|
||||||
|
// we did, then we'd be able to use them here,
|
||||||
|
// which would in turn flow into construction of
|
||||||
|
// `ResolverEnvironment`.
|
||||||
|
lock.fork_markers()
|
||||||
|
.iter()
|
||||||
|
.map(UniversalMarker::pep508)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
environments
|
environments
|
||||||
.cloned()
|
.cloned()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue