mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-09 13:28:02 +00:00
Make MarkerTree
Copy
(#9542)
Some checks are pending
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86_64 (push) Blocked by required conditions
CI / check system | python3.10 on windows (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
Some checks are pending
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86_64 (push) Blocked by required conditions
CI / check system | python3.10 on windows (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
## Summary It's just a `usize`. It seems simpler and perhaps even more performant (?) to make it `Copy`.
This commit is contained in:
parent
6bebf79ac3
commit
8126a5ed32
26 changed files with 171 additions and 189 deletions
|
@ -75,7 +75,7 @@ impl Constraints {
|
||||||
constraints.iter().cloned().map(move |constraint| {
|
constraints.iter().cloned().map(move |constraint| {
|
||||||
// Add the extra to the override marker.
|
// Add the extra to the override marker.
|
||||||
let mut joint_marker = MarkerTree::expression(extra_expression.clone());
|
let mut joint_marker = MarkerTree::expression(extra_expression.clone());
|
||||||
joint_marker.and(constraint.marker.clone());
|
joint_marker.and(constraint.marker);
|
||||||
Cow::Owned(Requirement {
|
Cow::Owned(Requirement {
|
||||||
marker: joint_marker,
|
marker: joint_marker,
|
||||||
..constraint
|
..constraint
|
||||||
|
|
|
@ -63,7 +63,7 @@ impl Overrides {
|
||||||
move |override_requirement| {
|
move |override_requirement| {
|
||||||
// Add the extra to the override marker.
|
// Add the extra to the override marker.
|
||||||
let mut joint_marker = MarkerTree::expression(extra_expression.clone());
|
let mut joint_marker = MarkerTree::expression(extra_expression.clone());
|
||||||
joint_marker.and(override_requirement.marker.clone());
|
joint_marker.and(override_requirement.marker);
|
||||||
Cow::Owned(Requirement {
|
Cow::Owned(Requirement {
|
||||||
marker: joint_marker,
|
marker: joint_marker,
|
||||||
..override_requirement.clone()
|
..override_requirement.clone()
|
||||||
|
|
|
@ -158,12 +158,12 @@ impl LoweredRequirement {
|
||||||
// Determine the space covered by the sources.
|
// Determine the space covered by the sources.
|
||||||
let mut total = MarkerTree::FALSE;
|
let mut total = MarkerTree::FALSE;
|
||||||
for source in sources.iter() {
|
for source in sources.iter() {
|
||||||
total.or(source.marker().clone());
|
total.or(source.marker());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the space covered by the requirement.
|
// Determine the space covered by the requirement.
|
||||||
let mut remaining = total.negate();
|
let mut remaining = total.negate();
|
||||||
remaining.and(requirement.marker.clone());
|
remaining.and(requirement.marker);
|
||||||
|
|
||||||
LoweredRequirement(Requirement {
|
LoweredRequirement(Requirement {
|
||||||
marker: remaining,
|
marker: remaining,
|
||||||
|
@ -349,7 +349,7 @@ impl LoweredRequirement {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
marker.and(requirement.marker.clone());
|
marker.and(requirement.marker);
|
||||||
|
|
||||||
Ok(Self(Requirement {
|
Ok(Self(Requirement {
|
||||||
name: requirement.name.clone(),
|
name: requirement.name.clone(),
|
||||||
|
@ -403,12 +403,12 @@ impl LoweredRequirement {
|
||||||
// Determine the space covered by the sources.
|
// Determine the space covered by the sources.
|
||||||
let mut total = MarkerTree::FALSE;
|
let mut total = MarkerTree::FALSE;
|
||||||
for source in source.iter() {
|
for source in source.iter() {
|
||||||
total.or(source.marker().clone());
|
total.or(source.marker());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the space covered by the requirement.
|
// Determine the space covered by the requirement.
|
||||||
let mut remaining = total.negate();
|
let mut remaining = total.negate();
|
||||||
remaining.and(requirement.marker.clone());
|
remaining.and(requirement.marker);
|
||||||
|
|
||||||
LoweredRequirement(Requirement {
|
LoweredRequirement(Requirement {
|
||||||
marker: remaining,
|
marker: remaining,
|
||||||
|
@ -501,7 +501,7 @@ impl LoweredRequirement {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
marker.and(requirement.marker.clone());
|
marker.and(requirement.marker);
|
||||||
|
|
||||||
Ok(Self(Requirement {
|
Ok(Self(Requirement {
|
||||||
name: requirement.name.clone(),
|
name: requirement.name.clone(),
|
||||||
|
|
|
@ -17,7 +17,7 @@ use crate::{ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerT
|
||||||
/// which can be used to create a CNF expression.
|
/// which can be used to create a CNF expression.
|
||||||
///
|
///
|
||||||
/// We choose DNF as it is easier to simplify for user-facing output.
|
/// We choose DNF as it is easier to simplify for user-facing output.
|
||||||
pub(crate) fn to_dnf(tree: &MarkerTree) -> Vec<Vec<MarkerExpression>> {
|
pub(crate) fn to_dnf(tree: MarkerTree) -> Vec<Vec<MarkerExpression>> {
|
||||||
let mut dnf = Vec::new();
|
let mut dnf = Vec::new();
|
||||||
collect_dnf(tree, &mut dnf, &mut Vec::new());
|
collect_dnf(tree, &mut dnf, &mut Vec::new());
|
||||||
simplify(&mut dnf);
|
simplify(&mut dnf);
|
||||||
|
@ -31,7 +31,7 @@ pub(crate) fn to_dnf(tree: &MarkerTree) -> Vec<Vec<MarkerExpression>> {
|
||||||
///
|
///
|
||||||
/// `path` is the list of marker expressions traversed on the current path.
|
/// `path` is the list of marker expressions traversed on the current path.
|
||||||
fn collect_dnf(
|
fn collect_dnf(
|
||||||
tree: &MarkerTree,
|
tree: MarkerTree,
|
||||||
dnf: &mut Vec<Vec<MarkerExpression>>,
|
dnf: &mut Vec<Vec<MarkerExpression>>,
|
||||||
path: &mut Vec<MarkerExpression>,
|
path: &mut Vec<MarkerExpression>,
|
||||||
) {
|
) {
|
||||||
|
@ -56,7 +56,7 @@ fn collect_dnf(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_dnf(&tree, dnf, path);
|
collect_dnf(tree, dnf, path);
|
||||||
path.truncate(current);
|
path.truncate(current);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ fn collect_dnf(
|
||||||
specifier,
|
specifier,
|
||||||
});
|
});
|
||||||
|
|
||||||
collect_dnf(&tree, dnf, path);
|
collect_dnf(tree, dnf, path);
|
||||||
path.pop();
|
path.pop();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ fn collect_dnf(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_dnf(&tree, dnf, path);
|
collect_dnf(tree, dnf, path);
|
||||||
path.truncate(current);
|
path.truncate(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ fn collect_dnf(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_dnf(&tree, dnf, path);
|
collect_dnf(tree, dnf, path);
|
||||||
path.truncate(current);
|
path.truncate(current);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ fn collect_dnf(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_dnf(&tree, dnf, path);
|
collect_dnf(tree, dnf, path);
|
||||||
path.truncate(current);
|
path.truncate(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ fn collect_dnf(
|
||||||
};
|
};
|
||||||
|
|
||||||
path.push(expr);
|
path.push(expr);
|
||||||
collect_dnf(&tree, dnf, path);
|
collect_dnf(tree, dnf, path);
|
||||||
path.pop();
|
path.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ fn collect_dnf(
|
||||||
};
|
};
|
||||||
|
|
||||||
path.push(expr);
|
path.push(expr);
|
||||||
collect_dnf(&tree, dnf, path);
|
collect_dnf(tree, dnf, path);
|
||||||
path.pop();
|
path.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ fn collect_dnf(
|
||||||
};
|
};
|
||||||
|
|
||||||
path.push(expr);
|
path.push(expr);
|
||||||
collect_dnf(&tree, dnf, path);
|
collect_dnf(tree, dnf, path);
|
||||||
path.pop();
|
path.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -611,8 +611,7 @@ impl Display for MarkerExpression {
|
||||||
/// Marker trees are canonical, meaning any two functionally equivalent markers
|
/// Marker trees are canonical, meaning any two functionally equivalent markers
|
||||||
/// will compare equally. Markers also support efficient polynomial-time operations,
|
/// will compare equally. Markers also support efficient polynomial-time operations,
|
||||||
/// such as conjunction and disjunction.
|
/// such as conjunction and disjunction.
|
||||||
// TODO(ibraheem): decide whether we want to implement `Copy` for marker trees
|
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||||
#[derive(Clone, Eq, Hash, PartialEq)]
|
|
||||||
pub struct MarkerTree(NodeId);
|
pub struct MarkerTree(NodeId);
|
||||||
|
|
||||||
impl Default for MarkerTree {
|
impl Default for MarkerTree {
|
||||||
|
@ -670,7 +669,7 @@ impl MarkerTree {
|
||||||
/// evaluate to `true` in any environment. However, this method may return false
|
/// evaluate to `true` in any environment. However, this method may return false
|
||||||
/// negatives, i.e. it may not be able to detect that a marker is always true for
|
/// negatives, i.e. it may not be able to detect that a marker is always true for
|
||||||
/// complex expressions.
|
/// complex expressions.
|
||||||
pub fn is_true(&self) -> bool {
|
pub fn is_true(self) -> bool {
|
||||||
self.0.is_true()
|
self.0.is_true()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -681,13 +680,13 @@ impl MarkerTree {
|
||||||
/// evaluate to `false` in any environment. However, this method may return false
|
/// evaluate to `false` in any environment. However, this method may return false
|
||||||
/// negatives, i.e. it may not be able to detect that a marker is unsatisfiable
|
/// negatives, i.e. it may not be able to detect that a marker is unsatisfiable
|
||||||
/// for complex expressions.
|
/// for complex expressions.
|
||||||
pub fn is_false(&self) -> bool {
|
pub fn is_false(self) -> bool {
|
||||||
self.0.is_false()
|
self.0.is_false()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new marker tree that is the negation of this one.
|
/// Returns a new marker tree that is the negation of this one.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn negate(&self) -> MarkerTree {
|
pub fn negate(self) -> MarkerTree {
|
||||||
MarkerTree(self.0.not())
|
MarkerTree(self.0.not())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -723,7 +722,7 @@ impl MarkerTree {
|
||||||
/// never both evaluate to `true` in a given environment. However, this method may return
|
/// never both evaluate to `true` in a given environment. However, this method may return
|
||||||
/// false negatives, i.e. it may not be able to detect that two markers are disjoint for
|
/// false negatives, i.e. it may not be able to detect that two markers are disjoint for
|
||||||
/// complex expressions.
|
/// complex expressions.
|
||||||
pub fn is_disjoint(&self, other: &MarkerTree) -> bool {
|
pub fn is_disjoint(self, other: MarkerTree) -> bool {
|
||||||
INTERNER.lock().is_disjoint(self.0, other.0)
|
INTERNER.lock().is_disjoint(self.0, other.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -733,12 +732,12 @@ impl MarkerTree {
|
||||||
/// If the marker is `false`, the marker is represented as the normalized expression, `python_version < '0'`.
|
/// If the marker is `false`, the marker is represented as the normalized expression, `python_version < '0'`.
|
||||||
///
|
///
|
||||||
/// The returned type implements [`Display`] and [`serde::Serialize`].
|
/// The returned type implements [`Display`] and [`serde::Serialize`].
|
||||||
pub fn contents(&self) -> Option<MarkerTreeContents> {
|
pub fn contents(self) -> Option<MarkerTreeContents> {
|
||||||
if self.is_true() {
|
if self.is_true() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(MarkerTreeContents(self.clone()))
|
Some(MarkerTreeContents(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a simplified string representation of this marker, if it contains at least one
|
/// Returns a simplified string representation of this marker, if it contains at least one
|
||||||
|
@ -746,7 +745,7 @@ impl MarkerTree {
|
||||||
///
|
///
|
||||||
/// If the marker is `true`, this method will return `None`.
|
/// If the marker is `true`, this method will return `None`.
|
||||||
/// If the marker is `false`, the marker is represented as the normalized expression, `python_version < '0'`.
|
/// If the marker is `false`, the marker is represented as the normalized expression, `python_version < '0'`.
|
||||||
pub fn try_to_string(&self) -> Option<String> {
|
pub fn try_to_string(self) -> Option<String> {
|
||||||
self.contents().map(|contents| contents.to_string())
|
self.contents().map(|contents| contents.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -818,12 +817,12 @@ impl MarkerTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a simplified DNF expression for this marker tree.
|
/// Returns a simplified DNF expression for this marker tree.
|
||||||
pub fn to_dnf(&self) -> Vec<Vec<MarkerExpression>> {
|
pub fn to_dnf(self) -> Vec<Vec<MarkerExpression>> {
|
||||||
simplify::to_dnf(self)
|
simplify::to_dnf(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does this marker apply in the given environment?
|
/// Does this marker apply in the given environment?
|
||||||
pub fn evaluate(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
pub fn evaluate(self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
||||||
self.evaluate_reporter_impl(env, extras, &mut TracingReporter)
|
self.evaluate_reporter_impl(env, extras, &mut TracingReporter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -835,7 +834,7 @@ impl MarkerTree {
|
||||||
/// independent marker evaluation. In practice, this means only the extras
|
/// independent marker evaluation. In practice, this means only the extras
|
||||||
/// are evaluated when an environment is not provided.
|
/// are evaluated when an environment is not provided.
|
||||||
pub fn evaluate_optional_environment(
|
pub fn evaluate_optional_environment(
|
||||||
&self,
|
self,
|
||||||
env: Option<&MarkerEnvironment>,
|
env: Option<&MarkerEnvironment>,
|
||||||
extras: &[ExtraName],
|
extras: &[ExtraName],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
@ -848,7 +847,7 @@ impl MarkerTree {
|
||||||
/// Same as [`Self::evaluate`], but instead of using logging to warn, you can pass your own
|
/// Same as [`Self::evaluate`], but instead of using logging to warn, you can pass your own
|
||||||
/// handler for warnings
|
/// handler for warnings
|
||||||
pub fn evaluate_reporter(
|
pub fn evaluate_reporter(
|
||||||
&self,
|
self,
|
||||||
env: &MarkerEnvironment,
|
env: &MarkerEnvironment,
|
||||||
extras: &[ExtraName],
|
extras: &[ExtraName],
|
||||||
reporter: &mut impl Reporter,
|
reporter: &mut impl Reporter,
|
||||||
|
@ -857,7 +856,7 @@ impl MarkerTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate_reporter_impl(
|
fn evaluate_reporter_impl(
|
||||||
&self,
|
self,
|
||||||
env: &MarkerEnvironment,
|
env: &MarkerEnvironment,
|
||||||
extras: &[ExtraName],
|
extras: &[ExtraName],
|
||||||
reporter: &mut impl Reporter,
|
reporter: &mut impl Reporter,
|
||||||
|
@ -924,7 +923,7 @@ impl MarkerTree {
|
||||||
/// Checks if the requirement should be activated with the given set of active extras without evaluating
|
/// Checks if the requirement should be activated with the given set of active extras without evaluating
|
||||||
/// the remaining environment markers, i.e. if there is potentially an environment that could activate this
|
/// the remaining environment markers, i.e. if there is potentially an environment that could activate this
|
||||||
/// requirement.
|
/// requirement.
|
||||||
pub fn evaluate_extras(&self, extras: &[ExtraName]) -> bool {
|
pub fn evaluate_extras(self, extras: &[ExtraName]) -> bool {
|
||||||
match self.kind() {
|
match self.kind() {
|
||||||
MarkerTreeKind::True => true,
|
MarkerTreeKind::True => true,
|
||||||
MarkerTreeKind::False => false,
|
MarkerTreeKind::False => false,
|
||||||
|
@ -950,7 +949,7 @@ impl MarkerTree {
|
||||||
///
|
///
|
||||||
/// ASSUMPTION: There is one `extra = "..."`, and it's either the only marker or part of the
|
/// ASSUMPTION: There is one `extra = "..."`, and it's either the only marker or part of the
|
||||||
/// main conjunction.
|
/// main conjunction.
|
||||||
pub fn top_level_extra(&self) -> Option<MarkerExpression> {
|
pub fn top_level_extra(self) -> Option<MarkerExpression> {
|
||||||
let mut extra_expression = None;
|
let mut extra_expression = None;
|
||||||
for conjunction in self.to_dnf() {
|
for conjunction in self.to_dnf() {
|
||||||
let found = conjunction.iter().find(|expression| {
|
let found = conjunction.iter().find(|expression| {
|
||||||
|
@ -983,7 +982,7 @@ impl MarkerTree {
|
||||||
///
|
///
|
||||||
/// ASSUMPTION: There is one `extra = "..."`, and it's either the only marker or part of the
|
/// ASSUMPTION: There is one `extra = "..."`, and it's either the only marker or part of the
|
||||||
/// main conjunction.
|
/// main conjunction.
|
||||||
pub fn top_level_extra_name(&self) -> Option<ExtraName> {
|
pub fn top_level_extra_name(self) -> Option<ExtraName> {
|
||||||
let extra_expression = self.top_level_extra()?;
|
let extra_expression = self.top_level_extra()?;
|
||||||
|
|
||||||
match extra_expression {
|
match extra_expression {
|
||||||
|
@ -1122,7 +1121,7 @@ impl MarkerTree {
|
||||||
MarkerTreeDebugRaw { marker: self }
|
MarkerTreeDebugRaw { marker: self }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_graph(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result {
|
fn fmt_graph(self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result {
|
||||||
match self.kind() {
|
match self.kind() {
|
||||||
MarkerTreeKind::True => return write!(f, "true"),
|
MarkerTreeKind::True => return write!(f, "true"),
|
||||||
MarkerTreeKind::False => return write!(f, "false"),
|
MarkerTreeKind::False => return write!(f, "false"),
|
||||||
|
@ -2044,9 +2043,7 @@ mod test {
|
||||||
|
|
||||||
// Given `os_name == "nt" and extra == "test"`, don't simplify.
|
// Given `os_name == "nt" and extra == "test"`, don't simplify.
|
||||||
let markers = MarkerTree::from_str(r#"os_name == "nt" and extra == "test""#).unwrap();
|
let markers = MarkerTree::from_str(r#"os_name == "nt" and extra == "test""#).unwrap();
|
||||||
let simplified = markers
|
let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
|
||||||
.clone()
|
|
||||||
.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
|
|
||||||
assert_eq!(simplified, markers);
|
assert_eq!(simplified, markers);
|
||||||
|
|
||||||
// Given `os_name == "nt" and (python_version == "3.7" or extra == "dev")`, simplify to
|
// Given `os_name == "nt" and (python_version == "3.7" or extra == "dev")`, simplify to
|
||||||
|
@ -2703,8 +2700,8 @@ mod test {
|
||||||
fn is_disjoint_commutative() {
|
fn is_disjoint_commutative() {
|
||||||
let m1 = m("extra == 'Linux' and extra != 'OSX'");
|
let m1 = m("extra == 'Linux' and extra != 'OSX'");
|
||||||
let m2 = m("extra == 'Linux'");
|
let m2 = m("extra == 'Linux'");
|
||||||
assert!(!m2.is_disjoint(&m1));
|
assert!(!m2.is_disjoint(m1));
|
||||||
assert!(!m1.is_disjoint(&m2));
|
assert!(!m1.is_disjoint(m2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2836,7 +2833,7 @@ mod test {
|
||||||
|
|
||||||
fn is_disjoint(left: impl AsRef<str>, right: impl AsRef<str>) -> bool {
|
fn is_disjoint(left: impl AsRef<str>, right: impl AsRef<str>) -> bool {
|
||||||
let (left, right) = (m(left.as_ref()), m(right.as_ref()));
|
let (left, right) = (m(left.as_ref()), m(right.as_ref()));
|
||||||
left.is_disjoint(&right) && right.is_disjoint(&left)
|
left.is_disjoint(right) && right.is_disjoint(left)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn implies(antecedent: &str, consequent: &str) -> bool {
|
fn implies(antecedent: &str, consequent: &str) -> bool {
|
||||||
|
|
|
@ -96,7 +96,7 @@ impl RequiresTxt {
|
||||||
|
|
||||||
// Add the markers and extra, if necessary.
|
// Add the markers and extra, if necessary.
|
||||||
requires_dist.push(Requirement {
|
requires_dist.push(Requirement {
|
||||||
marker: current_marker.clone(),
|
marker: current_marker,
|
||||||
..requirement
|
..requirement
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,11 +119,11 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
||||||
req.extras
|
req.extras
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|extra| (extra, req.marker.clone().simplify_extras(&extras)))
|
.map(|extra| (extra, req.marker.simplify_extras(&extras)))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
while let Some((extra, marker)) = queue.pop_front() {
|
while let Some((extra, marker)) = queue.pop_front() {
|
||||||
if !seen.insert((extra.clone(), marker.clone())) {
|
if !seen.insert((extra.clone(), marker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,8 +131,8 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
||||||
for requirement in &dependencies {
|
for requirement in &dependencies {
|
||||||
if requirement.marker.top_level_extra_name().as_ref() == Some(&extra) {
|
if requirement.marker.top_level_extra_name().as_ref() == Some(&extra) {
|
||||||
let requirement = {
|
let requirement = {
|
||||||
let mut marker = marker.clone();
|
let mut marker = marker;
|
||||||
marker.and(requirement.marker.clone());
|
marker.and(requirement.marker);
|
||||||
Requirement {
|
Requirement {
|
||||||
name: requirement.name.clone(),
|
name: requirement.name.clone(),
|
||||||
extras: requirement.extras.clone(),
|
extras: requirement.extras.clone(),
|
||||||
|
@ -148,7 +148,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
||||||
.extras
|
.extras
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|extra| (extra, requirement.marker.clone())),
|
.map(|extra| (extra, requirement.marker)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Add the requirements for that extra.
|
// Add the requirements for that extra.
|
||||||
|
|
|
@ -43,34 +43,34 @@ pub(crate) fn marker_reachability<T>(
|
||||||
fork_markers
|
fork_markers
|
||||||
.iter()
|
.iter()
|
||||||
.fold(UniversalMarker::FALSE, |mut acc, marker| {
|
.fold(UniversalMarker::FALSE, |mut acc, marker| {
|
||||||
acc.or(marker.clone());
|
acc.or(*marker);
|
||||||
acc
|
acc
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
for root_index in &queue {
|
for root_index in &queue {
|
||||||
reachability.insert(*root_index, root_markers.clone());
|
reachability.insert(*root_index, root_markers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate all markers through the graph, so that the eventual marker for each node is the
|
// Propagate all markers through the graph, so that the eventual marker for each node is the
|
||||||
// union of the markers of each path we can reach the node by.
|
// union of the markers of each path we can reach the node by.
|
||||||
while let Some(parent_index) = queue.pop() {
|
while let Some(parent_index) = queue.pop() {
|
||||||
let marker = reachability[&parent_index].clone();
|
let marker = reachability[&parent_index];
|
||||||
for child_edge in graph.edges_directed(parent_index, Direction::Outgoing) {
|
for child_edge in graph.edges_directed(parent_index, Direction::Outgoing) {
|
||||||
// The marker for all paths to the child through the parent.
|
// The marker for all paths to the child through the parent.
|
||||||
let mut child_marker = child_edge.weight().clone();
|
let mut child_marker = *child_edge.weight();
|
||||||
child_marker.and(marker.clone());
|
child_marker.and(marker);
|
||||||
match reachability.entry(child_edge.target()) {
|
match reachability.entry(child_edge.target()) {
|
||||||
Entry::Occupied(mut existing) => {
|
Entry::Occupied(mut existing) => {
|
||||||
// If the marker is a subset of the existing marker (A ⊆ B exactly if
|
// If the marker is a subset of the existing marker (A ⊆ B exactly if
|
||||||
// A ∪ B = A), updating the child wouldn't change child's marker.
|
// A ∪ B = A), updating the child wouldn't change child's marker.
|
||||||
child_marker.or(existing.get().clone());
|
child_marker.or(*existing.get());
|
||||||
if &child_marker != existing.get() {
|
if &child_marker != existing.get() {
|
||||||
existing.insert(child_marker);
|
existing.insert(child_marker);
|
||||||
queue.push(child_edge.target());
|
queue.push(child_edge.target());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Entry::Vacant(vacant) => {
|
Entry::Vacant(vacant) => {
|
||||||
vacant.insert(child_marker.clone());
|
vacant.insert(child_marker);
|
||||||
queue.push(child_edge.target());
|
queue.push(child_edge.target());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,7 +148,7 @@ impl Lock {
|
||||||
.fork_markers
|
.fork_markers
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|fork_markers| !fork_markers.is_disjoint(&dist.marker))
|
.filter(|fork_markers| !fork_markers.is_disjoint(&dist.marker))
|
||||||
.cloned()
|
.copied()
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
|
@ -163,7 +163,7 @@ impl Lock {
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let marker = edge.weight().clone();
|
let marker = *edge.weight();
|
||||||
package.add_dependency(&requires_python, dependency_dist, marker, root)?;
|
package.add_dependency(&requires_python, dependency_dist, marker, root)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ impl Lock {
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let marker = edge.weight().clone();
|
let marker = *edge.weight();
|
||||||
package.add_optional_dependency(
|
package.add_optional_dependency(
|
||||||
&requires_python,
|
&requires_python,
|
||||||
extra.clone(),
|
extra.clone(),
|
||||||
|
@ -221,7 +221,7 @@ impl Lock {
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let marker = edge.weight().clone();
|
let marker = *edge.weight();
|
||||||
package.add_group_dependency(
|
package.add_group_dependency(
|
||||||
&requires_python,
|
&requires_python,
|
||||||
group.clone(),
|
group.clone(),
|
||||||
|
@ -588,7 +588,7 @@ impl Lock {
|
||||||
pub fn simplified_supported_environments(&self) -> Vec<MarkerTree> {
|
pub fn simplified_supported_environments(&self) -> Vec<MarkerTree> {
|
||||||
self.supported_environments()
|
self.supported_environments()
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.copied()
|
||||||
.map(|marker| self.simplify_environment(marker))
|
.map(|marker| self.simplify_environment(marker))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -623,9 +623,9 @@ impl Lock {
|
||||||
// include conflicting marker info. In which case, we should serialize
|
// include conflicting marker info. In which case, we should serialize
|
||||||
// the entire `UniversalMarker` (taking care to still make the PEP 508
|
// the entire `UniversalMarker` (taking care to still make the PEP 508
|
||||||
// simplified).
|
// simplified).
|
||||||
SimplifiedMarkerTree::new(&self.requires_python, marker.pep508().clone())
|
SimplifiedMarkerTree::new(&self.requires_python, marker.pep508())
|
||||||
})
|
})
|
||||||
.filter_map(|marker| marker.try_to_string()),
|
.filter_map(super::requires_python::SimplifiedMarkerTree::try_to_string),
|
||||||
);
|
);
|
||||||
doc.insert("resolution-markers", value(fork_markers));
|
doc.insert("resolution-markers", value(fork_markers));
|
||||||
}
|
}
|
||||||
|
@ -634,8 +634,9 @@ impl Lock {
|
||||||
let supported_environments = each_element_on_its_line_array(
|
let supported_environments = each_element_on_its_line_array(
|
||||||
self.supported_environments
|
self.supported_environments
|
||||||
.iter()
|
.iter()
|
||||||
.map(|marker| SimplifiedMarkerTree::new(&self.requires_python, marker.clone()))
|
.copied()
|
||||||
.filter_map(|marker| marker.try_to_string()),
|
.map(|marker| SimplifiedMarkerTree::new(&self.requires_python, marker))
|
||||||
|
.filter_map(super::requires_python::SimplifiedMarkerTree::try_to_string),
|
||||||
);
|
);
|
||||||
doc.insert("supported-markers", value(supported_environments));
|
doc.insert("supported-markers", value(supported_environments));
|
||||||
}
|
}
|
||||||
|
@ -1974,10 +1975,8 @@ impl Package {
|
||||||
// include conflicting marker info. In which case, we should serialize
|
// include conflicting marker info. In which case, we should serialize
|
||||||
// the entire `UniversalMarker` (taking care to still make the PEP 508
|
// the entire `UniversalMarker` (taking care to still make the PEP 508
|
||||||
// simplified).
|
// simplified).
|
||||||
.map(|marker| {
|
.map(|marker| SimplifiedMarkerTree::new(requires_python, marker.pep508()))
|
||||||
SimplifiedMarkerTree::new(requires_python, marker.pep508().clone())
|
.filter_map(super::requires_python::SimplifiedMarkerTree::try_to_string),
|
||||||
})
|
|
||||||
.filter_map(|marker| marker.try_to_string()),
|
|
||||||
);
|
);
|
||||||
table.insert("resolution-markers", value(wheels));
|
table.insert("resolution-markers", value(wheels));
|
||||||
}
|
}
|
||||||
|
@ -3535,7 +3534,7 @@ impl Dependency {
|
||||||
complexified_marker: UniversalMarker,
|
complexified_marker: UniversalMarker,
|
||||||
) -> Dependency {
|
) -> Dependency {
|
||||||
let simplified_marker =
|
let simplified_marker =
|
||||||
SimplifiedMarkerTree::new(requires_python, complexified_marker.pep508().clone());
|
SimplifiedMarkerTree::new(requires_python, complexified_marker.pep508());
|
||||||
Dependency {
|
Dependency {
|
||||||
package_id,
|
package_id,
|
||||||
extra,
|
extra,
|
||||||
|
@ -3624,7 +3623,7 @@ impl DependencyWire {
|
||||||
requires_python: &RequiresPython,
|
requires_python: &RequiresPython,
|
||||||
unambiguous_package_ids: &FxHashMap<PackageName, PackageId>,
|
unambiguous_package_ids: &FxHashMap<PackageName, PackageId>,
|
||||||
) -> Result<Dependency, LockError> {
|
) -> Result<Dependency, LockError> {
|
||||||
let complexified_marker = self.marker.clone().into_marker(requires_python);
|
let complexified_marker = self.marker.into_marker(requires_python);
|
||||||
Ok(Dependency {
|
Ok(Dependency {
|
||||||
package_id: self.package_id.unwire(unambiguous_package_ids)?,
|
package_id: self.package_id.unwire(unambiguous_package_ids)?,
|
||||||
extra: self.extra,
|
extra: self.extra,
|
||||||
|
|
|
@ -114,7 +114,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
||||||
root,
|
root,
|
||||||
dep_index,
|
dep_index,
|
||||||
UniversalMarker::new(
|
UniversalMarker::new(
|
||||||
dep.simplified_marker.as_simplified_marker_tree().clone(),
|
dep.simplified_marker.as_simplified_marker_tree(),
|
||||||
// OK because we've verified above that we do not have any
|
// OK because we've verified above that we do not have any
|
||||||
// conflicting extras/groups.
|
// conflicting extras/groups.
|
||||||
//
|
//
|
||||||
|
@ -172,7 +172,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
||||||
index,
|
index,
|
||||||
dep_index,
|
dep_index,
|
||||||
UniversalMarker::new(
|
UniversalMarker::new(
|
||||||
dep.simplified_marker.as_simplified_marker_tree().clone(),
|
dep.simplified_marker.as_simplified_marker_tree(),
|
||||||
// See note above for other `UniversalMarker::new` for
|
// See note above for other `UniversalMarker::new` for
|
||||||
// why this is OK.
|
// why this is OK.
|
||||||
MarkerTree::TRUE,
|
MarkerTree::TRUE,
|
||||||
|
@ -211,11 +211,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
||||||
package,
|
package,
|
||||||
// As above, we've verified that there are no conflicting extras/groups
|
// As above, we've verified that there are no conflicting extras/groups
|
||||||
// specified, so it's safe to completely ignore the conflict marker.
|
// specified, so it's safe to completely ignore the conflict marker.
|
||||||
marker: reachability
|
marker: reachability.remove(&index).unwrap_or_default().pep508(),
|
||||||
.remove(&index)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.pep508()
|
|
||||||
.clone(),
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|
|
@ -257,7 +257,7 @@ impl<'env> InstallTarget<'env> {
|
||||||
//
|
//
|
||||||
// FIXME: Make the above true. We aren't actually checking
|
// FIXME: Make the above true. We aren't actually checking
|
||||||
// the conflict marker yet.
|
// the conflict marker yet.
|
||||||
Edge::Dev(group.clone(), dep.complexified_marker.pep508().clone()),
|
Edge::Dev(group.clone(), dep.complexified_marker.pep508()),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Push its dependencies on the queue.
|
// Push its dependencies on the queue.
|
||||||
|
@ -329,11 +329,7 @@ impl<'env> InstallTarget<'env> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the edge.
|
// Add the edge.
|
||||||
petgraph.add_edge(
|
petgraph.add_edge(root, index, Edge::Dev(group.clone(), dependency.marker));
|
||||||
root,
|
|
||||||
index,
|
|
||||||
Edge::Dev(group.clone(), dependency.marker.clone()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Push its dependencies on the queue.
|
// Push its dependencies on the queue.
|
||||||
if seen.insert((&dist.id, None)) {
|
if seen.insert((&dist.id, None)) {
|
||||||
|
@ -392,9 +388,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.pep508().clone())
|
Edge::Optional(extra.clone(), dep.complexified_marker.pep508())
|
||||||
} else {
|
} else {
|
||||||
Edge::Prod(dep.complexified_marker.pep508().clone())
|
Edge::Prod(dep.complexified_marker.pep508())
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ use uv_pep508::{CanonicalMarkerValueVersion, MarkerTree, MarkerTreeKind};
|
||||||
use crate::requires_python::{LowerBound, RequiresPythonRange, UpperBound};
|
use crate::requires_python::{LowerBound, RequiresPythonRange, UpperBound};
|
||||||
|
|
||||||
/// Returns the bounding Python versions that can satisfy the [`MarkerTree`], if it's constrained.
|
/// Returns the bounding Python versions that can satisfy the [`MarkerTree`], if it's constrained.
|
||||||
pub(crate) fn requires_python(tree: &MarkerTree) -> Option<RequiresPythonRange> {
|
pub(crate) fn requires_python(tree: MarkerTree) -> Option<RequiresPythonRange> {
|
||||||
fn collect_python_markers(tree: &MarkerTree, markers: &mut Vec<Range<Version>>) {
|
fn collect_python_markers(tree: MarkerTree, markers: &mut Vec<Range<Version>>) {
|
||||||
match tree.kind() {
|
match tree.kind() {
|
||||||
MarkerTreeKind::True | MarkerTreeKind::False => {}
|
MarkerTreeKind::True | MarkerTreeKind::False => {}
|
||||||
MarkerTreeKind::Version(marker) => match marker.key() {
|
MarkerTreeKind::Version(marker) => match marker.key() {
|
||||||
|
@ -19,28 +19,28 @@ pub(crate) fn requires_python(tree: &MarkerTree) -> Option<RequiresPythonRange>
|
||||||
}
|
}
|
||||||
CanonicalMarkerValueVersion::ImplementationVersion => {
|
CanonicalMarkerValueVersion::ImplementationVersion => {
|
||||||
for (_, tree) in marker.edges() {
|
for (_, tree) in marker.edges() {
|
||||||
collect_python_markers(&tree, markers);
|
collect_python_markers(tree, markers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MarkerTreeKind::String(marker) => {
|
MarkerTreeKind::String(marker) => {
|
||||||
for (_, tree) in marker.children() {
|
for (_, tree) in marker.children() {
|
||||||
collect_python_markers(&tree, markers);
|
collect_python_markers(tree, markers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MarkerTreeKind::In(marker) => {
|
MarkerTreeKind::In(marker) => {
|
||||||
for (_, tree) in marker.children() {
|
for (_, tree) in marker.children() {
|
||||||
collect_python_markers(&tree, markers);
|
collect_python_markers(tree, markers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MarkerTreeKind::Contains(marker) => {
|
MarkerTreeKind::Contains(marker) => {
|
||||||
for (_, tree) in marker.children() {
|
for (_, tree) in marker.children() {
|
||||||
collect_python_markers(&tree, markers);
|
collect_python_markers(tree, markers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MarkerTreeKind::Extra(marker) => {
|
MarkerTreeKind::Extra(marker) => {
|
||||||
for (_, tree) in marker.children() {
|
for (_, tree) in marker.children() {
|
||||||
collect_python_markers(&tree, markers);
|
collect_python_markers(tree, markers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,7 +156,7 @@ impl PubGrubRequirement {
|
||||||
package: PubGrubPackage::from_package(
|
package: PubGrubPackage::from_package(
|
||||||
requirement.name.clone(),
|
requirement.name.clone(),
|
||||||
extra,
|
extra,
|
||||||
requirement.marker.clone(),
|
requirement.marker,
|
||||||
),
|
),
|
||||||
version: Ranges::full(),
|
version: Ranges::full(),
|
||||||
url: Some(VerbatimParsedUrl {
|
url: Some(VerbatimParsedUrl {
|
||||||
|
@ -175,7 +175,7 @@ impl PubGrubRequirement {
|
||||||
package: PubGrubPackage::from_package(
|
package: PubGrubPackage::from_package(
|
||||||
requirement.name.clone(),
|
requirement.name.clone(),
|
||||||
extra,
|
extra,
|
||||||
requirement.marker.clone(),
|
requirement.marker,
|
||||||
),
|
),
|
||||||
url: None,
|
url: None,
|
||||||
version: Ranges::from(specifier.clone()),
|
version: Ranges::from(specifier.clone()),
|
||||||
|
|
|
@ -152,15 +152,15 @@ impl PubGrubPackage {
|
||||||
|
|
||||||
/// Returns the marker expression associated with this PubGrub package, if
|
/// Returns the marker expression associated with this PubGrub package, if
|
||||||
/// it has one.
|
/// it has one.
|
||||||
pub(crate) fn marker(&self) -> Option<&MarkerTree> {
|
pub(crate) fn marker(&self) -> Option<MarkerTree> {
|
||||||
match &**self {
|
match &**self {
|
||||||
// A root can never be a dependency of another package, and a `Python` pubgrub
|
// A root can never be a dependency of another package, and a `Python` pubgrub
|
||||||
// package is never returned by `get_dependencies`. So these cases never occur.
|
// package is never returned by `get_dependencies`. So these cases never occur.
|
||||||
PubGrubPackageInner::Root(_) | PubGrubPackageInner::Python(_) => None,
|
PubGrubPackageInner::Root(_) | PubGrubPackageInner::Python(_) => None,
|
||||||
PubGrubPackageInner::Package { marker, .. }
|
PubGrubPackageInner::Package { marker, .. }
|
||||||
| PubGrubPackageInner::Extra { marker, .. }
|
| PubGrubPackageInner::Extra { marker, .. }
|
||||||
| PubGrubPackageInner::Dev { marker, .. } => Some(marker),
|
| PubGrubPackageInner::Dev { marker, .. } => Some(*marker),
|
||||||
PubGrubPackageInner::Marker { marker, .. } => Some(marker),
|
PubGrubPackageInner::Marker { marker, .. } => Some(*marker),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +253,7 @@ impl PubGrubPackage {
|
||||||
| PubGrubPackageInner::Extra { ref mut marker, .. }
|
| PubGrubPackageInner::Extra { ref mut marker, .. }
|
||||||
| PubGrubPackageInner::Dev { ref mut marker, .. }
|
| PubGrubPackageInner::Dev { ref mut marker, .. }
|
||||||
| PubGrubPackageInner::Marker { ref mut marker, .. } => {
|
| PubGrubPackageInner::Marker { ref mut marker, .. } => {
|
||||||
*marker = python_requirement.simplify_markers(marker.clone());
|
*marker = python_requirement.simplify_markers(*marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -542,7 +542,7 @@ impl From<RequiresPythonRange> for Range<Version> {
|
||||||
/// setting can be assumed. In order to get a "normal" marker out of
|
/// setting can be assumed. In order to get a "normal" marker out of
|
||||||
/// a simplified marker, one must re-contextualize it by adding the
|
/// a simplified marker, one must re-contextualize it by adding the
|
||||||
/// `requires-python` constraint back to the marker.
|
/// `requires-python` constraint back to the marker.
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, serde::Deserialize)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord, serde::Deserialize)]
|
||||||
pub(crate) struct SimplifiedMarkerTree(MarkerTree);
|
pub(crate) struct SimplifiedMarkerTree(MarkerTree);
|
||||||
|
|
||||||
impl SimplifiedMarkerTree {
|
impl SimplifiedMarkerTree {
|
||||||
|
@ -565,13 +565,13 @@ impl SimplifiedMarkerTree {
|
||||||
///
|
///
|
||||||
/// This only returns `None` when the underlying marker is always true,
|
/// This only returns `None` when the underlying marker is always true,
|
||||||
/// i.e., it matches all possible marker environments.
|
/// i.e., it matches all possible marker environments.
|
||||||
pub(crate) fn try_to_string(&self) -> Option<String> {
|
pub(crate) fn try_to_string(self) -> Option<String> {
|
||||||
self.0.try_to_string()
|
self.0.try_to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the underlying marker tree without re-complexifying them.
|
/// Returns the underlying marker tree without re-complexifying them.
|
||||||
pub(crate) fn as_simplified_marker_tree(&self) -> &MarkerTree {
|
pub(crate) fn as_simplified_marker_tree(self) -> MarkerTree {
|
||||||
&self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -332,9 +332,7 @@ type RequirementsTxtGraph<'dist> = Graph<RequirementsTxtDist<'dist>, (), Directe
|
||||||
/// We also remove the root node, to simplify the graph structure.
|
/// We also remove the root node, to simplify the graph structure.
|
||||||
fn combine_extras<'dist>(graph: &IntermediatePetGraph<'dist>) -> RequirementsTxtGraph<'dist> {
|
fn combine_extras<'dist>(graph: &IntermediatePetGraph<'dist>) -> RequirementsTxtGraph<'dist> {
|
||||||
/// Return the key for a node.
|
/// Return the key for a node.
|
||||||
fn version_marker<'dist>(
|
fn version_marker<'dist>(dist: &'dist RequirementsTxtDist) -> (&'dist PackageName, MarkerTree) {
|
||||||
dist: &'dist RequirementsTxtDist,
|
|
||||||
) -> (&'dist PackageName, &'dist MarkerTree) {
|
|
||||||
(dist.name(), dist.markers)
|
(dist.name(), dist.markers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,12 +147,12 @@ impl ResolverOutput {
|
||||||
// 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.
|
||||||
for edge in &resolution.edges {
|
for edge in &resolution.edges {
|
||||||
if !seen.insert((edge, marker.clone())) {
|
if !seen.insert((edge, marker)) {
|
||||||
// Insert each node only once.
|
// Insert each node only once.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::add_edge(&mut graph, &mut inverse, root_index, edge, marker.clone());
|
Self::add_edge(&mut graph, &mut inverse, root_index, edge, marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,32 +603,32 @@ impl ResolverOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add all marker parameters from the given tree to the given set.
|
/// Add all marker parameters from the given tree to the given set.
|
||||||
fn add_marker_params_from_tree(marker_tree: &MarkerTree, set: &mut IndexSet<MarkerParam>) {
|
fn add_marker_params_from_tree(marker_tree: MarkerTree, set: &mut IndexSet<MarkerParam>) {
|
||||||
match marker_tree.kind() {
|
match marker_tree.kind() {
|
||||||
MarkerTreeKind::True => {}
|
MarkerTreeKind::True => {}
|
||||||
MarkerTreeKind::False => {}
|
MarkerTreeKind::False => {}
|
||||||
MarkerTreeKind::Version(marker) => {
|
MarkerTreeKind::Version(marker) => {
|
||||||
set.insert(MarkerParam::Version(marker.key()));
|
set.insert(MarkerParam::Version(marker.key()));
|
||||||
for (_, tree) in marker.edges() {
|
for (_, tree) in marker.edges() {
|
||||||
add_marker_params_from_tree(&tree, set);
|
add_marker_params_from_tree(tree, set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MarkerTreeKind::String(marker) => {
|
MarkerTreeKind::String(marker) => {
|
||||||
set.insert(MarkerParam::String(marker.key()));
|
set.insert(MarkerParam::String(marker.key()));
|
||||||
for (_, tree) in marker.children() {
|
for (_, tree) in marker.children() {
|
||||||
add_marker_params_from_tree(&tree, set);
|
add_marker_params_from_tree(tree, set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MarkerTreeKind::In(marker) => {
|
MarkerTreeKind::In(marker) => {
|
||||||
set.insert(MarkerParam::String(marker.key()));
|
set.insert(MarkerParam::String(marker.key()));
|
||||||
for (_, tree) in marker.children() {
|
for (_, tree) in marker.children() {
|
||||||
add_marker_params_from_tree(&tree, set);
|
add_marker_params_from_tree(tree, set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MarkerTreeKind::Contains(marker) => {
|
MarkerTreeKind::Contains(marker) => {
|
||||||
set.insert(MarkerParam::String(marker.key()));
|
set.insert(MarkerParam::String(marker.key()));
|
||||||
for (_, tree) in marker.children() {
|
for (_, tree) in marker.children() {
|
||||||
add_marker_params_from_tree(&tree, set);
|
add_marker_params_from_tree(tree, set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We specifically don't care about these for the
|
// We specifically don't care about these for the
|
||||||
|
@ -638,7 +638,7 @@ impl ResolverOutput {
|
||||||
// interested in which markers are used.
|
// interested in which markers are used.
|
||||||
MarkerTreeKind::Extra(marker) => {
|
MarkerTreeKind::Extra(marker) => {
|
||||||
for (_, tree) in marker.children() {
|
for (_, tree) in marker.children() {
|
||||||
add_marker_params_from_tree(&tree, set);
|
add_marker_params_from_tree(tree, set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -669,7 +669,7 @@ impl ResolverOutput {
|
||||||
.constraints
|
.constraints
|
||||||
.apply(self.overrides.apply(archive.metadata.requires_dist.iter()))
|
.apply(self.overrides.apply(archive.metadata.requires_dist.iter()))
|
||||||
{
|
{
|
||||||
add_marker_params_from_tree(&req.marker, &mut seen_marker_values);
|
add_marker_params_from_tree(req.marker, &mut seen_marker_values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -678,7 +678,7 @@ impl ResolverOutput {
|
||||||
.constraints
|
.constraints
|
||||||
.apply(self.overrides.apply(self.requirements.iter()))
|
.apply(self.overrides.apply(self.requirements.iter()))
|
||||||
{
|
{
|
||||||
add_marker_params_from_tree(&direct_req.marker, &mut seen_marker_values);
|
add_marker_params_from_tree(direct_req.marker, &mut seen_marker_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the final marker expression as a conjunction of
|
// Generate the final marker expression as a conjunction of
|
||||||
|
@ -740,8 +740,8 @@ impl ResolverOutput {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
version1: (*version1).clone(),
|
version1: (*version1).clone(),
|
||||||
version2: (*version2).clone(),
|
version2: (*version2).clone(),
|
||||||
marker1: (*marker1).clone(),
|
marker1: *(*marker1),
|
||||||
marker2: (*marker2).clone(),
|
marker2: *(*marker2),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -841,7 +841,7 @@ impl From<ResolverOutput> for uv_distribution_types::Resolution {
|
||||||
// above that we aren't in universal mode. If we aren't in
|
// above that we aren't in universal mode. If we aren't in
|
||||||
// universal mode, then there can be no conflicts since
|
// universal mode, then there can be no conflicts since
|
||||||
// conflicts imply forks and forks imply universal mode.
|
// conflicts imply forks and forks imply universal mode.
|
||||||
let marker = graph[edge].pep508().clone();
|
let marker = graph[edge].pep508();
|
||||||
|
|
||||||
match (&graph[source], &graph[target]) {
|
match (&graph[source], &graph[target]) {
|
||||||
(ResolutionGraphNode::Root, ResolutionGraphNode::Dist(target_dist)) => {
|
(ResolutionGraphNode::Root, ResolutionGraphNode::Dist(target_dist)) => {
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub(crate) struct RequirementsTxtDist<'dist> {
|
||||||
pub(crate) dist: &'dist ResolvedDist,
|
pub(crate) dist: &'dist ResolvedDist,
|
||||||
pub(crate) version: &'dist Version,
|
pub(crate) version: &'dist Version,
|
||||||
pub(crate) hashes: &'dist [HashDigest],
|
pub(crate) hashes: &'dist [HashDigest],
|
||||||
pub(crate) markers: &'dist MarkerTree,
|
pub(crate) markers: MarkerTree,
|
||||||
pub(crate) extras: Vec<ExtraName>,
|
pub(crate) extras: Vec<ExtraName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ impl<'dist> RequirementsTxtDist<'dist> {
|
||||||
};
|
};
|
||||||
if let Some(given) = given {
|
if let Some(given) = given {
|
||||||
return if let Some(markers) =
|
return if let Some(markers) =
|
||||||
SimplifiedMarkerTree::new(requires_python, self.markers.clone())
|
SimplifiedMarkerTree::new(requires_python, self.markers)
|
||||||
.try_to_string()
|
.try_to_string()
|
||||||
.filter(|_| include_markers)
|
.filter(|_| include_markers)
|
||||||
{
|
{
|
||||||
|
@ -107,7 +107,7 @@ impl<'dist> RequirementsTxtDist<'dist> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.extras.is_empty() {
|
if self.extras.is_empty() {
|
||||||
if let Some(markers) = SimplifiedMarkerTree::new(requires_python, self.markers.clone())
|
if let Some(markers) = SimplifiedMarkerTree::new(requires_python, self.markers)
|
||||||
.try_to_string()
|
.try_to_string()
|
||||||
.filter(|_| include_markers)
|
.filter(|_| include_markers)
|
||||||
{
|
{
|
||||||
|
@ -119,7 +119,7 @@ impl<'dist> RequirementsTxtDist<'dist> {
|
||||||
let mut extras = self.extras.clone();
|
let mut extras = self.extras.clone();
|
||||||
extras.sort_unstable();
|
extras.sort_unstable();
|
||||||
extras.dedup();
|
extras.dedup();
|
||||||
if let Some(markers) = SimplifiedMarkerTree::new(requires_python, self.markers.clone())
|
if let Some(markers) = SimplifiedMarkerTree::new(requires_python, self.markers)
|
||||||
.try_to_string()
|
.try_to_string()
|
||||||
.filter(|_| include_markers)
|
.filter(|_| include_markers)
|
||||||
{
|
{
|
||||||
|
|
|
@ -168,7 +168,7 @@ impl ResolverEnvironment {
|
||||||
|
|
||||||
/// Returns `false` only when this environment is a fork and it is disjoint
|
/// Returns `false` only when this environment is a fork and it is disjoint
|
||||||
/// with the given marker.
|
/// with the given marker.
|
||||||
pub(crate) fn included_by_marker(&self, marker: &MarkerTree) -> bool {
|
pub(crate) fn included_by_marker(&self, marker: MarkerTree) -> bool {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Kind::Specific { .. } => true,
|
Kind::Specific { .. } => true,
|
||||||
Kind::Universal { ref markers, .. } => !markers.is_disjoint(marker),
|
Kind::Universal { ref markers, .. } => !markers.is_disjoint(marker),
|
||||||
|
@ -188,7 +188,7 @@ impl ResolverEnvironment {
|
||||||
/// resolver environment's marker, if it's constrained.
|
/// resolver environment's marker, if it's constrained.
|
||||||
pub(crate) fn requires_python(&self) -> Option<RequiresPythonRange> {
|
pub(crate) fn requires_python(&self) -> Option<RequiresPythonRange> {
|
||||||
let Kind::Universal {
|
let Kind::Universal {
|
||||||
markers: ref pep508_marker,
|
markers: pep508_marker,
|
||||||
..
|
..
|
||||||
} = self.kind
|
} = self.kind
|
||||||
else {
|
else {
|
||||||
|
@ -219,7 +219,7 @@ impl ResolverEnvironment {
|
||||||
ref include,
|
ref include,
|
||||||
ref exclude,
|
ref exclude,
|
||||||
} => {
|
} => {
|
||||||
let mut markers = lhs.clone();
|
let mut markers = *lhs;
|
||||||
markers.and(rhs);
|
markers.and(rhs);
|
||||||
let kind = Kind::Universal {
|
let kind = Kind::Universal {
|
||||||
initial_forks: Arc::clone(initial_forks),
|
initial_forks: Arc::clone(initial_forks),
|
||||||
|
@ -285,7 +285,7 @@ impl ResolverEnvironment {
|
||||||
}
|
}
|
||||||
let kind = Kind::Universal {
|
let kind = Kind::Universal {
|
||||||
initial_forks: Arc::clone(initial_forks),
|
initial_forks: Arc::clone(initial_forks),
|
||||||
markers: markers.clone(),
|
markers: *markers,
|
||||||
include: Arc::new(include),
|
include: Arc::new(include),
|
||||||
exclude: Arc::new(exclude),
|
exclude: Arc::new(exclude),
|
||||||
};
|
};
|
||||||
|
@ -320,7 +320,7 @@ impl ResolverEnvironment {
|
||||||
.rev()
|
.rev()
|
||||||
.map(|initial_fork| {
|
.map(|initial_fork| {
|
||||||
init.clone()
|
init.clone()
|
||||||
.with_env(self.narrow_environment(initial_fork.clone()))
|
.with_env(self.narrow_environment(*initial_fork))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -398,7 +398,7 @@ impl ResolverEnvironment {
|
||||||
conflict_marker.and(exclude_extra_marker);
|
conflict_marker.and(exclude_extra_marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(UniversalMarker::new(markers.clone(), conflict_marker))
|
Some(UniversalMarker::new(*markers, conflict_marker))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -445,7 +445,7 @@ impl<'d> ForkingPossibility<'d> {
|
||||||
env: &ResolverEnvironment,
|
env: &ResolverEnvironment,
|
||||||
dep: &'d PubGrubDependency,
|
dep: &'d PubGrubDependency,
|
||||||
) -> ForkingPossibility<'d> {
|
) -> ForkingPossibility<'d> {
|
||||||
let marker = dep.package.marker().unwrap_or(&MarkerTree::TRUE);
|
let marker = dep.package.marker().unwrap_or(MarkerTree::TRUE);
|
||||||
if !env.included_by_marker(marker) {
|
if !env.included_by_marker(marker) {
|
||||||
ForkingPossibility::DependencyAlwaysExcluded
|
ForkingPossibility::DependencyAlwaysExcluded
|
||||||
} else if marker.is_true() {
|
} else if marker.is_true() {
|
||||||
|
@ -453,7 +453,7 @@ impl<'d> ForkingPossibility<'d> {
|
||||||
} else {
|
} else {
|
||||||
let forker = Forker {
|
let forker = Forker {
|
||||||
package: &dep.package,
|
package: &dep.package,
|
||||||
marker: marker.clone(),
|
marker,
|
||||||
};
|
};
|
||||||
ForkingPossibility::Possible(forker)
|
ForkingPossibility::Possible(forker)
|
||||||
}
|
}
|
||||||
|
@ -479,7 +479,7 @@ impl<'d> Forker<'d> {
|
||||||
&self,
|
&self,
|
||||||
env: &ResolverEnvironment,
|
env: &ResolverEnvironment,
|
||||||
) -> Option<(Forker<'d>, Vec<ResolverEnvironment>)> {
|
) -> Option<(Forker<'d>, Vec<ResolverEnvironment>)> {
|
||||||
if !env.included_by_marker(&self.marker) {
|
if !env.included_by_marker(self.marker) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,7 +494,7 @@ impl<'d> Forker<'d> {
|
||||||
let mut envs = vec![];
|
let mut envs = vec![];
|
||||||
{
|
{
|
||||||
let not_marker = self.marker.negate();
|
let not_marker = self.marker.negate();
|
||||||
if !env_marker.is_disjoint(¬_marker) {
|
if !env_marker.is_disjoint(not_marker) {
|
||||||
envs.push(env.narrow_environment(not_marker));
|
envs.push(env.narrow_environment(not_marker));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -502,9 +502,9 @@ impl<'d> Forker<'d> {
|
||||||
// Changing the order of forks can change the output in some
|
// Changing the order of forks can change the output in some
|
||||||
// ways. While it's probably fine, we try to avoid changing the
|
// ways. While it's probably fine, we try to avoid changing the
|
||||||
// output.
|
// output.
|
||||||
envs.push(env.narrow_environment(self.marker.clone()));
|
envs.push(env.narrow_environment(self.marker));
|
||||||
|
|
||||||
let mut remaining_marker = self.marker.clone();
|
let mut remaining_marker = self.marker;
|
||||||
remaining_marker.and(env_marker.negate());
|
remaining_marker.and(env_marker.negate());
|
||||||
let remaining_forker = Forker {
|
let remaining_forker = Forker {
|
||||||
package: self.package,
|
package: self.package,
|
||||||
|
@ -516,7 +516,7 @@ impl<'d> Forker<'d> {
|
||||||
/// Returns true if the dependency represented by this forker may be
|
/// Returns true if the dependency represented by this forker may be
|
||||||
/// included in the given resolver environment.
|
/// included in the given resolver environment.
|
||||||
pub(crate) fn included(&self, env: &ResolverEnvironment) -> bool {
|
pub(crate) fn included(&self, env: &ResolverEnvironment) -> bool {
|
||||||
let marker = self.package.marker().unwrap_or(&MarkerTree::TRUE);
|
let marker = self.package.marker().unwrap_or(MarkerTree::TRUE);
|
||||||
env.included_by_marker(marker)
|
env.included_by_marker(marker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ impl<T> ForkMap<T> {
|
||||||
pub(crate) fn add(&mut self, requirement: &Requirement, value: T) {
|
pub(crate) fn add(&mut self, requirement: &Requirement, value: T) {
|
||||||
let entry = Entry {
|
let entry = Entry {
|
||||||
value,
|
value,
|
||||||
marker: requirement.marker.clone(),
|
marker: requirement.marker,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
|
@ -60,7 +60,7 @@ impl<T> ForkMap<T> {
|
||||||
};
|
};
|
||||||
values
|
values
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|entry| env.included_by_marker(&entry.marker))
|
.filter(|entry| env.included_by_marker(entry.marker))
|
||||||
.map(|entry| &entry.value)
|
.map(|entry| &entry.value)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1475,7 +1475,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
package: PubGrubPackage::from(PubGrubPackageInner::Dev {
|
package: PubGrubPackage::from(PubGrubPackageInner::Dev {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
dev: group.clone(),
|
dev: group.clone(),
|
||||||
marker: marker.clone(),
|
marker: *marker,
|
||||||
}),
|
}),
|
||||||
version: Range::singleton(version.clone()),
|
version: Range::singleton(version.clone()),
|
||||||
url: None,
|
url: None,
|
||||||
|
@ -1490,7 +1490,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
// Add a dependency on both the marker and base package.
|
// Add a dependency on both the marker and base package.
|
||||||
PubGrubPackageInner::Marker { name, marker } => {
|
PubGrubPackageInner::Marker { name, marker } => {
|
||||||
return Ok(Dependencies::Unforkable(
|
return Ok(Dependencies::Unforkable(
|
||||||
[MarkerTree::TRUE, marker.clone()]
|
[MarkerTree::TRUE, *marker]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |marker| PubGrubDependency {
|
.map(move |marker| PubGrubDependency {
|
||||||
package: PubGrubPackage::from(PubGrubPackageInner::Package {
|
package: PubGrubPackage::from(PubGrubPackageInner::Package {
|
||||||
|
@ -1524,7 +1524,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
extra: extra.cloned(),
|
extra: extra.cloned(),
|
||||||
dev: None,
|
dev: None,
|
||||||
marker: marker.clone(),
|
marker: *marker,
|
||||||
}),
|
}),
|
||||||
version: Range::singleton(version.clone()),
|
version: Range::singleton(version.clone()),
|
||||||
url: None,
|
url: None,
|
||||||
|
@ -1549,7 +1549,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
extra: None,
|
extra: None,
|
||||||
dev: dev.cloned(),
|
dev: dev.cloned(),
|
||||||
marker: marker.clone(),
|
marker: *marker,
|
||||||
}),
|
}),
|
||||||
version: Range::singleton(version.clone()),
|
version: Range::singleton(version.clone()),
|
||||||
url: None,
|
url: None,
|
||||||
|
@ -1607,15 +1607,10 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
let mut queue: VecDeque<_> = requirements
|
let mut queue: VecDeque<_> = requirements
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|req| name == Some(&req.name))
|
.filter(|req| name == Some(&req.name))
|
||||||
.flat_map(|req| {
|
.flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
|
||||||
req.extras
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.map(|extra| (extra, req.marker.clone()))
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
while let Some((extra, marker)) = queue.pop_front() {
|
while let Some((extra, marker)) = queue.pop_front() {
|
||||||
if !seen.insert((extra.clone(), marker.clone())) {
|
if !seen.insert((extra.clone(), marker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for requirement in
|
for requirement in
|
||||||
|
@ -1623,12 +1618,12 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
{
|
{
|
||||||
let requirement = match requirement {
|
let requirement = match requirement {
|
||||||
Cow::Owned(mut requirement) => {
|
Cow::Owned(mut requirement) => {
|
||||||
requirement.marker.and(marker.clone());
|
requirement.marker.and(marker);
|
||||||
requirement
|
requirement
|
||||||
}
|
}
|
||||||
Cow::Borrowed(requirement) => {
|
Cow::Borrowed(requirement) => {
|
||||||
let mut marker = marker.clone();
|
let mut marker = marker;
|
||||||
marker.and(requirement.marker.clone());
|
marker.and(requirement.marker);
|
||||||
Requirement {
|
Requirement {
|
||||||
name: requirement.name.clone(),
|
name: requirement.name.clone(),
|
||||||
extras: requirement.extras.clone(),
|
extras: requirement.extras.clone(),
|
||||||
|
@ -1645,7 +1640,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
.extras
|
.extras
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|extra| (extra, requirement.marker.clone())),
|
.map(|extra| (extra, requirement.marker)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Add the requirements for that extra.
|
// Add the requirements for that extra.
|
||||||
|
@ -1678,7 +1673,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
let python_marker = python_requirement.to_marker_tree();
|
let python_marker = python_requirement.to_marker_tree();
|
||||||
// If the requirement would not be selected with any Python version
|
// If the requirement would not be selected with any Python version
|
||||||
// supported by the root, skip it.
|
// supported by the root, skip it.
|
||||||
if python_marker.is_disjoint(&requirement.marker) {
|
if python_marker.is_disjoint(requirement.marker) {
|
||||||
trace!(
|
trace!(
|
||||||
"skipping {requirement} because of Requires-Python: {requires_python}",
|
"skipping {requirement} because of Requires-Python: {requires_python}",
|
||||||
requires_python = python_requirement.target(),
|
requires_python = python_requirement.target(),
|
||||||
|
@ -1688,7 +1683,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
|
|
||||||
// If we're in a fork in universal mode, ignore any dependency that isn't part of
|
// If we're in a fork in universal mode, ignore any dependency that isn't part of
|
||||||
// this fork (but will be part of another fork).
|
// this fork (but will be part of another fork).
|
||||||
if !env.included_by_marker(&requirement.marker) {
|
if !env.included_by_marker(requirement.marker) {
|
||||||
trace!("skipping {requirement} because of {env}");
|
trace!("skipping {requirement} because of {env}");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -1737,8 +1732,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
if requirement.marker.is_true() {
|
if requirement.marker.is_true() {
|
||||||
Cow::Borrowed(constraint)
|
Cow::Borrowed(constraint)
|
||||||
} else {
|
} else {
|
||||||
let mut marker = constraint.marker.clone();
|
let mut marker = constraint.marker;
|
||||||
marker.and(requirement.marker.clone());
|
marker.and(requirement.marker);
|
||||||
|
|
||||||
if marker.is_false() {
|
if marker.is_false() {
|
||||||
trace!(
|
trace!(
|
||||||
|
@ -1761,8 +1756,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
let requires_python = python_requirement.target();
|
let requires_python = python_requirement.target();
|
||||||
let python_marker = python_requirement.to_marker_tree();
|
let python_marker = python_requirement.to_marker_tree();
|
||||||
|
|
||||||
let mut marker = constraint.marker.clone();
|
let mut marker = constraint.marker;
|
||||||
marker.and(requirement.marker.clone());
|
marker.and(requirement.marker);
|
||||||
|
|
||||||
if marker.is_false() {
|
if marker.is_false() {
|
||||||
trace!(
|
trace!(
|
||||||
|
@ -1776,7 +1771,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
// Additionally, if the requirement is `requests ; sys_platform == 'darwin'`
|
// Additionally, if the requirement is `requests ; sys_platform == 'darwin'`
|
||||||
// and the constraint is `requests ; python_version == '3.6'`, the
|
// and the constraint is `requests ; python_version == '3.6'`, the
|
||||||
// constraint should only apply when _both_ markers are true.
|
// constraint should only apply when _both_ markers are true.
|
||||||
if python_marker.is_disjoint(&marker) {
|
if python_marker.is_disjoint(marker) {
|
||||||
trace!(
|
trace!(
|
||||||
"skipping constraint {requirement} because of Requires-Python: {requires_python}"
|
"skipping constraint {requirement} because of Requires-Python: {requires_python}"
|
||||||
);
|
);
|
||||||
|
@ -1798,7 +1793,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
|
|
||||||
// If we're in a fork in universal mode, ignore any dependency that isn't part of
|
// If we're in a fork in universal mode, ignore any dependency that isn't part of
|
||||||
// this fork (but will be part of another fork).
|
// this fork (but will be part of another fork).
|
||||||
if !env.included_by_marker(&constraint.marker) {
|
if !env.included_by_marker(constraint.marker) {
|
||||||
trace!("skipping {constraint} because of {env}");
|
trace!("skipping {constraint} because of {env}");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -2524,7 +2519,7 @@ impl ForkState {
|
||||||
to_index: to_index.cloned(),
|
to_index: to_index.cloned(),
|
||||||
to_extra: None,
|
to_extra: None,
|
||||||
to_dev: None,
|
to_dev: None,
|
||||||
marker: dependency_marker.clone(),
|
marker: *dependency_marker,
|
||||||
};
|
};
|
||||||
edges.insert(edge);
|
edges.insert(edge);
|
||||||
}
|
}
|
||||||
|
@ -2555,7 +2550,7 @@ impl ForkState {
|
||||||
to_index: to_index.cloned(),
|
to_index: to_index.cloned(),
|
||||||
to_extra: Some(dependency_extra.clone()),
|
to_extra: Some(dependency_extra.clone()),
|
||||||
to_dev: None,
|
to_dev: None,
|
||||||
marker: dependency_marker.clone(),
|
marker: *dependency_marker,
|
||||||
};
|
};
|
||||||
edges.insert(edge);
|
edges.insert(edge);
|
||||||
}
|
}
|
||||||
|
@ -2586,7 +2581,7 @@ impl ForkState {
|
||||||
to_index: to_index.cloned(),
|
to_index: to_index.cloned(),
|
||||||
to_extra: None,
|
to_extra: None,
|
||||||
to_dev: Some(dependency_dev.clone()),
|
to_dev: Some(dependency_dev.clone()),
|
||||||
marker: dependency_marker.clone(),
|
marker: *dependency_marker,
|
||||||
};
|
};
|
||||||
edges.insert(edge);
|
edges.insert(edge);
|
||||||
}
|
}
|
||||||
|
@ -2681,7 +2676,7 @@ pub(crate) struct ResolutionDependencyEdge {
|
||||||
impl ResolutionDependencyEdge {
|
impl ResolutionDependencyEdge {
|
||||||
pub(crate) fn universal_marker(&self) -> UniversalMarker {
|
pub(crate) fn universal_marker(&self) -> UniversalMarker {
|
||||||
// FIXME: Account for extras and groups here.
|
// FIXME: Account for extras and groups here.
|
||||||
UniversalMarker::new(self.marker.clone(), MarkerTree::TRUE)
|
UniversalMarker::new(self.marker, MarkerTree::TRUE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2919,9 +2914,9 @@ impl Forks {
|
||||||
.is_some_and(|bound| python_requirement.raises(&bound))
|
.is_some_and(|bound| python_requirement.raises(&bound))
|
||||||
{
|
{
|
||||||
let dep = deps.pop().unwrap();
|
let dep = deps.pop().unwrap();
|
||||||
let markers = dep.package.marker().cloned().unwrap_or(MarkerTree::TRUE);
|
let markers = dep.package.marker().unwrap_or(MarkerTree::TRUE);
|
||||||
for fork in &mut forks {
|
for fork in &mut forks {
|
||||||
if fork.env.included_by_marker(&markers) {
|
if fork.env.included_by_marker(markers) {
|
||||||
fork.add_dependency(dep.clone());
|
fork.add_dependency(dep.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2968,7 +2963,7 @@ impl Forks {
|
||||||
}
|
}
|
||||||
// Filter out any forks we created that are disjoint with our
|
// Filter out any forks we created that are disjoint with our
|
||||||
// Python requirement.
|
// Python requirement.
|
||||||
if new_fork.env.included_by_marker(&python_marker) {
|
if new_fork.env.included_by_marker(python_marker) {
|
||||||
new.push(new_fork);
|
new.push(new_fork);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use uv_pep508::{MarkerEnvironment, MarkerTree};
|
||||||
///
|
///
|
||||||
/// A universal marker evaluates to true only when *both* its PEP 508 marker
|
/// A universal marker evaluates to true only when *both* its PEP 508 marker
|
||||||
/// and its conflict marker evaluate to true.
|
/// and its conflict marker evaluate to true.
|
||||||
#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Default, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
pub struct UniversalMarker {
|
pub struct UniversalMarker {
|
||||||
pep508_marker: MarkerTree,
|
pep508_marker: MarkerTree,
|
||||||
conflict_marker: MarkerTree,
|
conflict_marker: MarkerTree,
|
||||||
|
@ -70,16 +70,16 @@ impl UniversalMarker {
|
||||||
///
|
///
|
||||||
/// Two universal markers are disjoint when it is impossible for them both
|
/// Two universal markers are disjoint when it is impossible for them both
|
||||||
/// to evaluate to `true` simultaneously.
|
/// to evaluate to `true` simultaneously.
|
||||||
pub(crate) fn is_disjoint(&self, other: &UniversalMarker) -> bool {
|
pub(crate) fn is_disjoint(self, other: &UniversalMarker) -> bool {
|
||||||
self.pep508_marker.is_disjoint(&other.pep508_marker)
|
self.pep508_marker.is_disjoint(other.pep508_marker)
|
||||||
|| self.conflict_marker.is_disjoint(&other.conflict_marker)
|
|| self.conflict_marker.is_disjoint(other.conflict_marker)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this universal marker is satisfied by the given
|
/// Returns true if this universal marker is satisfied by the given
|
||||||
/// marker environment and list of activated extras.
|
/// marker environment and list of activated extras.
|
||||||
///
|
///
|
||||||
/// FIXME: This also needs to accept a list of groups.
|
/// FIXME: This also needs to accept a list of groups.
|
||||||
pub(crate) fn evaluate(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
pub(crate) fn evaluate(self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
||||||
self.pep508_marker.evaluate(env, extras) && self.conflict_marker.evaluate(env, extras)
|
self.pep508_marker.evaluate(env, extras) && self.conflict_marker.evaluate(env, extras)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,8 +91,8 @@ impl UniversalMarker {
|
||||||
/// producing different versions of the same package), then one should
|
/// producing different versions of the same package), then one should
|
||||||
/// always use a universal marker since it accounts for all possible ways
|
/// always use a universal marker since it accounts for all possible ways
|
||||||
/// for a package to be installed.
|
/// for a package to be installed.
|
||||||
pub fn pep508(&self) -> &MarkerTree {
|
pub fn pep508(self) -> MarkerTree {
|
||||||
&self.pep508_marker
|
self.pep508_marker
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the non-PEP 508 marker expression that represents conflicting
|
/// Returns the non-PEP 508 marker expression that represents conflicting
|
||||||
|
@ -106,8 +106,8 @@ impl UniversalMarker {
|
||||||
/// of non-trivial conflict markers and fails if any are found. (Because
|
/// of non-trivial conflict markers and fails if any are found. (Because
|
||||||
/// conflict markers cannot be represented in the `requirements.txt`
|
/// conflict markers cannot be represented in the `requirements.txt`
|
||||||
/// format.)
|
/// format.)
|
||||||
pub fn conflict(&self) -> &MarkerTree {
|
pub fn conflict(self) -> MarkerTree {
|
||||||
&self.conflict_marker
|
self.conflict_marker
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -819,7 +819,7 @@ impl TryFrom<SourcesWire> for Sources {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut hint = lhs.negate();
|
let mut hint = lhs.negate();
|
||||||
hint.and(rhs.clone());
|
hint.and(rhs);
|
||||||
let hint = hint
|
let hint = hint
|
||||||
.contents()
|
.contents()
|
||||||
.map(|contents| contents.to_string())
|
.map(|contents| contents.to_string())
|
||||||
|
@ -1415,13 +1415,13 @@ impl Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`MarkerTree`] for the source.
|
/// Return the [`MarkerTree`] for the source.
|
||||||
pub fn marker(&self) -> &MarkerTree {
|
pub fn marker(&self) -> MarkerTree {
|
||||||
match self {
|
match self {
|
||||||
Source::Git { marker, .. } => marker,
|
Source::Git { marker, .. } => *marker,
|
||||||
Source::Url { marker, .. } => marker,
|
Source::Url { marker, .. } => *marker,
|
||||||
Source::Path { marker, .. } => marker,
|
Source::Path { marker, .. } => *marker,
|
||||||
Source::Registry { marker, .. } => marker,
|
Source::Registry { marker, .. } => *marker,
|
||||||
Source::Workspace { marker, .. } => marker,
|
Source::Workspace { marker, .. } => *marker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1105,7 +1105,7 @@ fn update_requirement(old: &mut Requirement, new: &Requirement, has_source: bool
|
||||||
|
|
||||||
// Update the marker expression.
|
// Update the marker expression.
|
||||||
if new.marker.contents().is_some() {
|
if new.marker.contents().is_some() {
|
||||||
old.marker = new.marker.clone();
|
old.marker = new.marker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -361,9 +361,9 @@ async fn do_lock(
|
||||||
.iter()
|
.iter()
|
||||||
.zip(environments.as_markers().iter().skip(1))
|
.zip(environments.as_markers().iter().skip(1))
|
||||||
{
|
{
|
||||||
if !lhs.is_disjoint(rhs) {
|
if !lhs.is_disjoint(*rhs) {
|
||||||
let mut hint = lhs.negate();
|
let mut hint = lhs.negate();
|
||||||
hint.and(rhs.clone());
|
hint.and(*rhs);
|
||||||
|
|
||||||
let lhs = lhs
|
let lhs = lhs
|
||||||
.contents()
|
.contents()
|
||||||
|
@ -413,6 +413,7 @@ async fn do_lock(
|
||||||
.map(SupportedEnvironments::as_markers)
|
.map(SupportedEnvironments::as_markers)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
.copied()
|
||||||
{
|
{
|
||||||
if requires_python.to_marker_tree().is_disjoint(environment) {
|
if requires_python.to_marker_tree().is_disjoint(environment) {
|
||||||
return if let Some(contents) = environment.contents() {
|
return if let Some(contents) = environment.contents() {
|
||||||
|
@ -604,8 +605,8 @@ async fn do_lock(
|
||||||
// `ResolverEnvironment`.
|
// `ResolverEnvironment`.
|
||||||
lock.fork_markers()
|
lock.fork_markers()
|
||||||
.iter()
|
.iter()
|
||||||
|
.copied()
|
||||||
.map(UniversalMarker::pep508)
|
.map(UniversalMarker::pep508)
|
||||||
.cloned()
|
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
|
@ -814,7 +815,7 @@ impl ValidatedLock {
|
||||||
.map(SupportedEnvironments::as_markers)
|
.map(SupportedEnvironments::as_markers)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.copied()
|
||||||
.map(|marker| lock.simplify_environment(marker))
|
.map(|marker| lock.simplify_environment(marker))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if expected != actual {
|
if expected != actual {
|
||||||
|
|
|
@ -325,7 +325,7 @@ pub(super) async fn do_sync(
|
||||||
target
|
target
|
||||||
.lock()
|
.lock()
|
||||||
.simplified_supported_environments()
|
.simplified_supported_environments()
|
||||||
.iter()
|
.into_iter()
|
||||||
.filter_map(MarkerTree::contents)
|
.filter_map(MarkerTree::contents)
|
||||||
.map(|env| format!("`{env}`"))
|
.map(|env| format!("`{env}`"))
|
||||||
.join(", "),
|
.join(", "),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue