mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-14 09:45:48 +00:00
Add support for package-level conflicts in workspaces (#14906)
Some checks are pending
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 / 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 / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | aarch64 (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 libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (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 / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (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 | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (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 rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (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 | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | conda3.8 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 x86-64 (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 x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
zizmor / Run zizmor (push) Waiting to run
Some checks are pending
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 / 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 / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | aarch64 (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 libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (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 / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (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 | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (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 rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (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 | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | conda3.8 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 x86-64 (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 x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
zizmor / Run zizmor (push) Waiting to run
Revives https://github.com/astral-sh/uv/pull/9130 Previously, we allowed scoping conflicting extras or groups to specific packages, e.g. ,`{ package = "foo", extra = "bar" }` for a conflict in `foo[bar]`. Now, we allow dropping the `extra` or `group` bit and using `{ package = "foo" }` directly which declares a conflict with `foo`'s production dependencies. This means you can declare conflicts between workspace members, e.g.: ``` [tool.uv] conflicts = [[{ package = "foo" }, { package = "bar" }]] ``` would not allow `foo` and `bar` to be installed at the same time. Similarly, a conflict can be declared between a package and a group: ``` [tool.uv] conflicts = [[{ package = "foo" }, { group = "lint" }]] ``` which would mean, e.g., that `--only-group lint` would be required for the invocation. As with our existing support for conflicting extras, there are edge-cases here where the resolver will _not_ fail even if there are conflicts that render a particular install target unusable. There's test coverage for some of these. We'll still error at install-time when the conflicting groups are selected. Due to the likelihood of bugs in this feature, I've marked it as a preview feature. I would not recommend reading the commits as there's some slop from not wanting to rebase Andrew's branch. --------- Co-authored-by: Andrew Gallant <andrew@astral.sh>
This commit is contained in:
parent
a9302906ce
commit
8f71d239f8
20 changed files with 1913 additions and 240 deletions
|
|
@ -14,7 +14,8 @@ bitflags::bitflags! {
|
||||||
const JSON_OUTPUT = 1 << 2;
|
const JSON_OUTPUT = 1 << 2;
|
||||||
const PYLOCK = 1 << 3;
|
const PYLOCK = 1 << 3;
|
||||||
const ADD_BOUNDS = 1 << 4;
|
const ADD_BOUNDS = 1 << 4;
|
||||||
const EXTRA_BUILD_DEPENDENCIES = 1 << 5;
|
const PACKAGE_CONFLICTS = 1 << 5;
|
||||||
|
const EXTRA_BUILD_DEPENDENCIES = 1 << 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,6 +30,7 @@ impl PreviewFeatures {
|
||||||
Self::JSON_OUTPUT => "json-output",
|
Self::JSON_OUTPUT => "json-output",
|
||||||
Self::PYLOCK => "pylock",
|
Self::PYLOCK => "pylock",
|
||||||
Self::ADD_BOUNDS => "add-bounds",
|
Self::ADD_BOUNDS => "add-bounds",
|
||||||
|
Self::PACKAGE_CONFLICTS => "package-conflicts",
|
||||||
Self::EXTRA_BUILD_DEPENDENCIES => "extra-build-dependencies",
|
Self::EXTRA_BUILD_DEPENDENCIES => "extra-build-dependencies",
|
||||||
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
|
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +74,7 @@ impl FromStr for PreviewFeatures {
|
||||||
"json-output" => Self::JSON_OUTPUT,
|
"json-output" => Self::JSON_OUTPUT,
|
||||||
"pylock" => Self::PYLOCK,
|
"pylock" => Self::PYLOCK,
|
||||||
"add-bounds" => Self::ADD_BOUNDS,
|
"add-bounds" => Self::ADD_BOUNDS,
|
||||||
|
"package-conflicts" => Self::PACKAGE_CONFLICTS,
|
||||||
"extra-build-dependencies" => Self::EXTRA_BUILD_DEPENDENCIES,
|
"extra-build-dependencies" => Self::EXTRA_BUILD_DEPENDENCIES,
|
||||||
_ => {
|
_ => {
|
||||||
warn_user_once!("Unknown preview feature: `{part}`");
|
warn_user_once!("Unknown preview feature: `{part}`");
|
||||||
|
|
@ -235,6 +238,10 @@ mod tests {
|
||||||
assert_eq!(PreviewFeatures::JSON_OUTPUT.flag_as_str(), "json-output");
|
assert_eq!(PreviewFeatures::JSON_OUTPUT.flag_as_str(), "json-output");
|
||||||
assert_eq!(PreviewFeatures::PYLOCK.flag_as_str(), "pylock");
|
assert_eq!(PreviewFeatures::PYLOCK.flag_as_str(), "pylock");
|
||||||
assert_eq!(PreviewFeatures::ADD_BOUNDS.flag_as_str(), "add-bounds");
|
assert_eq!(PreviewFeatures::ADD_BOUNDS.flag_as_str(), "add-bounds");
|
||||||
|
assert_eq!(
|
||||||
|
PreviewFeatures::PACKAGE_CONFLICTS.flag_as_str(),
|
||||||
|
"package-conflicts"
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES.flag_as_str(),
|
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES.flag_as_str(),
|
||||||
"extra-build-dependencies"
|
"extra-build-dependencies"
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,10 @@ impl Conflicts {
|
||||||
pub fn contains<'a>(
|
pub fn contains<'a>(
|
||||||
&self,
|
&self,
|
||||||
package: &PackageName,
|
package: &PackageName,
|
||||||
conflict: impl Into<ConflictPackageRef<'a>>,
|
kind: impl Into<ConflictKindRef<'a>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let conflict = conflict.into();
|
let kind = kind.into();
|
||||||
self.iter().any(|set| set.contains(package, conflict))
|
self.iter().any(|set| set.contains(package, kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if there are no conflicts.
|
/// Returns true if there are no conflicts.
|
||||||
|
|
@ -106,7 +106,7 @@ impl Conflicts {
|
||||||
for set in &self.0 {
|
for set in &self.0 {
|
||||||
direct_conflict_sets.insert(set);
|
direct_conflict_sets.insert(set);
|
||||||
for item in set.iter() {
|
for item in set.iter() {
|
||||||
let ConflictPackage::Group(group) = &item.conflict else {
|
let ConflictKind::Group(group) = &item.kind else {
|
||||||
// TODO(john): Do we also want to handle extras here?
|
// TODO(john): Do we also want to handle extras here?
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
@ -129,7 +129,7 @@ impl Conflicts {
|
||||||
}
|
}
|
||||||
let group_conflict_item = ConflictItem {
|
let group_conflict_item = ConflictItem {
|
||||||
package: package.clone(),
|
package: package.clone(),
|
||||||
conflict: ConflictPackage::Group(group.clone()),
|
kind: ConflictKind::Group(group.clone()),
|
||||||
};
|
};
|
||||||
let node_id = graph.add_node(FxHashSet::default());
|
let node_id = graph.add_node(FxHashSet::default());
|
||||||
group_node_idxs.insert(group, node_id);
|
group_node_idxs.insert(group, node_id);
|
||||||
|
|
@ -242,11 +242,11 @@ impl ConflictSet {
|
||||||
pub fn contains<'a>(
|
pub fn contains<'a>(
|
||||||
&self,
|
&self,
|
||||||
package: &PackageName,
|
package: &PackageName,
|
||||||
conflict: impl Into<ConflictPackageRef<'a>>,
|
kind: impl Into<ConflictKindRef<'a>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let conflict = conflict.into();
|
let kind = kind.into();
|
||||||
self.iter()
|
self.iter()
|
||||||
.any(|set| set.package() == package && *set.conflict() == conflict)
|
.any(|set| set.package() == package && *set.kind() == kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if these conflicts contain any set that contains the given
|
/// Returns true if these conflicts contain any set that contains the given
|
||||||
|
|
@ -326,7 +326,7 @@ impl TryFrom<Vec<ConflictItem>> for ConflictSet {
|
||||||
)]
|
)]
|
||||||
pub struct ConflictItem {
|
pub struct ConflictItem {
|
||||||
package: PackageName,
|
package: PackageName,
|
||||||
conflict: ConflictPackage,
|
kind: ConflictKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConflictItem {
|
impl ConflictItem {
|
||||||
|
|
@ -338,40 +338,47 @@ impl ConflictItem {
|
||||||
/// Returns the package-specific conflict.
|
/// Returns the package-specific conflict.
|
||||||
///
|
///
|
||||||
/// i.e., Either an extra or a group name.
|
/// i.e., Either an extra or a group name.
|
||||||
pub fn conflict(&self) -> &ConflictPackage {
|
pub fn kind(&self) -> &ConflictKind {
|
||||||
&self.conflict
|
&self.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the extra name of this conflicting item.
|
/// Returns the extra name of this conflicting item.
|
||||||
pub fn extra(&self) -> Option<&ExtraName> {
|
pub fn extra(&self) -> Option<&ExtraName> {
|
||||||
self.conflict.extra()
|
self.kind.extra()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the group name of this conflicting item.
|
/// Returns the group name of this conflicting item.
|
||||||
pub fn group(&self) -> Option<&GroupName> {
|
pub fn group(&self) -> Option<&GroupName> {
|
||||||
self.conflict.group()
|
self.kind.group()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns this item as a new type with its fields borrowed.
|
/// Returns this item as a new type with its fields borrowed.
|
||||||
pub fn as_ref(&self) -> ConflictItemRef<'_> {
|
pub fn as_ref(&self) -> ConflictItemRef<'_> {
|
||||||
ConflictItemRef {
|
ConflictItemRef {
|
||||||
package: self.package(),
|
package: self.package(),
|
||||||
conflict: self.conflict.as_ref(),
|
kind: self.kind.as_ref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<PackageName> for ConflictItem {
|
||||||
|
fn from(package: PackageName) -> Self {
|
||||||
|
let kind = ConflictKind::Project;
|
||||||
|
Self { package, kind }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<(PackageName, ExtraName)> for ConflictItem {
|
impl From<(PackageName, ExtraName)> for ConflictItem {
|
||||||
fn from((package, extra): (PackageName, ExtraName)) -> Self {
|
fn from((package, extra): (PackageName, ExtraName)) -> Self {
|
||||||
let conflict = ConflictPackage::Extra(extra);
|
let kind = ConflictKind::Extra(extra);
|
||||||
Self { package, conflict }
|
Self { package, kind }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(PackageName, GroupName)> for ConflictItem {
|
impl From<(PackageName, GroupName)> for ConflictItem {
|
||||||
fn from((package, group): (PackageName, GroupName)) -> Self {
|
fn from((package, group): (PackageName, GroupName)) -> Self {
|
||||||
let conflict = ConflictPackage::Group(group);
|
let kind = ConflictKind::Group(group);
|
||||||
Self { package, conflict }
|
Self { package, kind }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -382,7 +389,7 @@ impl From<(PackageName, GroupName)> for ConflictItem {
|
||||||
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
pub struct ConflictItemRef<'a> {
|
pub struct ConflictItemRef<'a> {
|
||||||
package: &'a PackageName,
|
package: &'a PackageName,
|
||||||
conflict: ConflictPackageRef<'a>,
|
kind: ConflictKindRef<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ConflictItemRef<'a> {
|
impl<'a> ConflictItemRef<'a> {
|
||||||
|
|
@ -394,40 +401,47 @@ impl<'a> ConflictItemRef<'a> {
|
||||||
/// Returns the package-specific conflict.
|
/// Returns the package-specific conflict.
|
||||||
///
|
///
|
||||||
/// i.e., Either an extra or a group name.
|
/// i.e., Either an extra or a group name.
|
||||||
pub fn conflict(&self) -> ConflictPackageRef<'a> {
|
pub fn kind(&self) -> ConflictKindRef<'a> {
|
||||||
self.conflict
|
self.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the extra name of this conflicting item.
|
/// Returns the extra name of this conflicting item.
|
||||||
pub fn extra(&self) -> Option<&'a ExtraName> {
|
pub fn extra(&self) -> Option<&'a ExtraName> {
|
||||||
self.conflict.extra()
|
self.kind.extra()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the group name of this conflicting item.
|
/// Returns the group name of this conflicting item.
|
||||||
pub fn group(&self) -> Option<&'a GroupName> {
|
pub fn group(&self) -> Option<&'a GroupName> {
|
||||||
self.conflict.group()
|
self.kind.group()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts this borrowed conflicting item to its owned variant.
|
/// Converts this borrowed conflicting item to its owned variant.
|
||||||
pub fn to_owned(&self) -> ConflictItem {
|
pub fn to_owned(&self) -> ConflictItem {
|
||||||
ConflictItem {
|
ConflictItem {
|
||||||
package: self.package().clone(),
|
package: self.package().clone(),
|
||||||
conflict: self.conflict.to_owned(),
|
kind: self.kind.to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a PackageName> for ConflictItemRef<'a> {
|
||||||
|
fn from(package: &'a PackageName) -> Self {
|
||||||
|
let kind = ConflictKindRef::Project;
|
||||||
|
Self { package, kind }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> From<(&'a PackageName, &'a ExtraName)> for ConflictItemRef<'a> {
|
impl<'a> From<(&'a PackageName, &'a ExtraName)> for ConflictItemRef<'a> {
|
||||||
fn from((package, extra): (&'a PackageName, &'a ExtraName)) -> Self {
|
fn from((package, extra): (&'a PackageName, &'a ExtraName)) -> Self {
|
||||||
let conflict = ConflictPackageRef::Extra(extra);
|
let kind = ConflictKindRef::Extra(extra);
|
||||||
ConflictItemRef { package, conflict }
|
ConflictItemRef { package, kind }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<(&'a PackageName, &'a GroupName)> for ConflictItemRef<'a> {
|
impl<'a> From<(&'a PackageName, &'a GroupName)> for ConflictItemRef<'a> {
|
||||||
fn from((package, group): (&'a PackageName, &'a GroupName)) -> Self {
|
fn from((package, group): (&'a PackageName, &'a GroupName)) -> Self {
|
||||||
let conflict = ConflictPackageRef::Group(group);
|
let kind = ConflictKindRef::Group(group);
|
||||||
ConflictItemRef { package, conflict }
|
ConflictItemRef { package, kind }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,20 +453,22 @@ impl hashbrown::Equivalent<ConflictItem> for ConflictItemRef<'_> {
|
||||||
|
|
||||||
/// The actual conflicting data for a package.
|
/// The actual conflicting data for a package.
|
||||||
///
|
///
|
||||||
/// That is, either an extra or a group name.
|
/// That is, either an extra or a group name, or the entire project itself.
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
pub enum ConflictPackage {
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
pub enum ConflictKind {
|
||||||
Extra(ExtraName),
|
Extra(ExtraName),
|
||||||
Group(GroupName),
|
Group(GroupName),
|
||||||
|
Project,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConflictPackage {
|
impl ConflictKind {
|
||||||
/// If this conflict corresponds to an extra, then return the
|
/// If this conflict corresponds to an extra, then return the
|
||||||
/// extra name.
|
/// extra name.
|
||||||
pub fn extra(&self) -> Option<&ExtraName> {
|
pub fn extra(&self) -> Option<&ExtraName> {
|
||||||
match self {
|
match self {
|
||||||
Self::Extra(extra) => Some(extra),
|
Self::Extra(extra) => Some(extra),
|
||||||
Self::Group(_) => None,
|
Self::Group(_) | Self::Project => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -461,15 +477,16 @@ impl ConflictPackage {
|
||||||
pub fn group(&self) -> Option<&GroupName> {
|
pub fn group(&self) -> Option<&GroupName> {
|
||||||
match self {
|
match self {
|
||||||
Self::Group(group) => Some(group),
|
Self::Group(group) => Some(group),
|
||||||
Self::Extra(_) => None,
|
Self::Extra(_) | Self::Project => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns this conflict as a new type with its fields borrowed.
|
/// Returns this conflict as a new type with its fields borrowed.
|
||||||
pub fn as_ref(&self) -> ConflictPackageRef<'_> {
|
pub fn as_ref(&self) -> ConflictKindRef<'_> {
|
||||||
match self {
|
match self {
|
||||||
Self::Extra(extra) => ConflictPackageRef::Extra(extra),
|
Self::Extra(extra) => ConflictKindRef::Extra(extra),
|
||||||
Self::Group(group) => ConflictPackageRef::Group(group),
|
Self::Group(group) => ConflictKindRef::Group(group),
|
||||||
|
Self::Project => ConflictKindRef::Project,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -478,18 +495,19 @@ impl ConflictPackage {
|
||||||
///
|
///
|
||||||
/// That is, either a borrowed extra name or a borrowed group name.
|
/// That is, either a borrowed extra name or a borrowed group name.
|
||||||
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
pub enum ConflictPackageRef<'a> {
|
pub enum ConflictKindRef<'a> {
|
||||||
Extra(&'a ExtraName),
|
Extra(&'a ExtraName),
|
||||||
Group(&'a GroupName),
|
Group(&'a GroupName),
|
||||||
|
Project,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ConflictPackageRef<'a> {
|
impl<'a> ConflictKindRef<'a> {
|
||||||
/// If this conflict corresponds to an extra, then return the
|
/// If this conflict corresponds to an extra, then return the
|
||||||
/// extra name.
|
/// extra name.
|
||||||
pub fn extra(&self) -> Option<&'a ExtraName> {
|
pub fn extra(&self) -> Option<&'a ExtraName> {
|
||||||
match self {
|
match self {
|
||||||
Self::Extra(extra) => Some(extra),
|
Self::Extra(extra) => Some(extra),
|
||||||
Self::Group(_) => None,
|
Self::Group(_) | Self::Project => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -498,45 +516,46 @@ impl<'a> ConflictPackageRef<'a> {
|
||||||
pub fn group(&self) -> Option<&'a GroupName> {
|
pub fn group(&self) -> Option<&'a GroupName> {
|
||||||
match self {
|
match self {
|
||||||
Self::Group(group) => Some(group),
|
Self::Group(group) => Some(group),
|
||||||
Self::Extra(_) => None,
|
Self::Extra(_) | Self::Project => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts this borrowed conflict to its owned variant.
|
/// Converts this borrowed conflict to its owned variant.
|
||||||
pub fn to_owned(&self) -> ConflictPackage {
|
pub fn to_owned(&self) -> ConflictKind {
|
||||||
match *self {
|
match self {
|
||||||
Self::Extra(extra) => ConflictPackage::Extra(extra.clone()),
|
Self::Extra(extra) => ConflictKind::Extra((*extra).clone()),
|
||||||
Self::Group(group) => ConflictPackage::Group(group.clone()),
|
Self::Group(group) => ConflictKind::Group((*group).clone()),
|
||||||
|
Self::Project => ConflictKind::Project,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ExtraName> for ConflictPackageRef<'a> {
|
impl<'a> From<&'a ExtraName> for ConflictKindRef<'a> {
|
||||||
fn from(extra: &'a ExtraName) -> Self {
|
fn from(extra: &'a ExtraName) -> Self {
|
||||||
Self::Extra(extra)
|
Self::Extra(extra)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a GroupName> for ConflictPackageRef<'a> {
|
impl<'a> From<&'a GroupName> for ConflictKindRef<'a> {
|
||||||
fn from(group: &'a GroupName) -> Self {
|
fn from(group: &'a GroupName) -> Self {
|
||||||
Self::Group(group)
|
Self::Group(group)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<ConflictPackage> for ConflictPackageRef<'_> {
|
impl PartialEq<ConflictKind> for ConflictKindRef<'_> {
|
||||||
fn eq(&self, other: &ConflictPackage) -> bool {
|
fn eq(&self, other: &ConflictKind) -> bool {
|
||||||
other.as_ref() == *self
|
other.as_ref() == *self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PartialEq<ConflictPackageRef<'a>> for ConflictPackage {
|
impl<'a> PartialEq<ConflictKindRef<'a>> for ConflictKind {
|
||||||
fn eq(&self, other: &ConflictPackageRef<'a>) -> bool {
|
fn eq(&self, other: &ConflictKindRef<'a>) -> bool {
|
||||||
self.as_ref() == *other
|
self.as_ref() == *other
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl hashbrown::Equivalent<ConflictPackage> for ConflictPackageRef<'_> {
|
impl hashbrown::Equivalent<ConflictKind> for ConflictKindRef<'_> {
|
||||||
fn equivalent(&self, key: &ConflictPackage) -> bool {
|
fn equivalent(&self, key: &ConflictKind) -> bool {
|
||||||
key.as_ref() == *self
|
key.as_ref() == *self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -557,9 +576,9 @@ pub enum ConflictError {
|
||||||
/// optional.)
|
/// optional.)
|
||||||
#[error("Expected `package` field in conflicting entry")]
|
#[error("Expected `package` field in conflicting entry")]
|
||||||
MissingPackage,
|
MissingPackage,
|
||||||
/// An error that occurs when both `extra` and `group` are missing.
|
/// An error that occurs when all of `package`, `extra` and `group` are missing.
|
||||||
#[error("Expected `extra` or `group` field in conflicting entry")]
|
#[error("Expected `package`, `extra` or `group` field in conflicting entry")]
|
||||||
MissingExtraAndGroup,
|
MissingPackageAndExtraAndGroup,
|
||||||
/// An error that occurs when both `extra` and `group` are present.
|
/// An error that occurs when both `extra` and `group` are present.
|
||||||
#[error("Expected one of `extra` or `group` in conflicting entry, but found both")]
|
#[error("Expected one of `extra` or `group` in conflicting entry, but found both")]
|
||||||
FoundExtraAndGroup,
|
FoundExtraAndGroup,
|
||||||
|
|
@ -596,7 +615,7 @@ impl SchemaConflicts {
|
||||||
let package = item.package.clone().unwrap_or_else(|| package.clone());
|
let package = item.package.clone().unwrap_or_else(|| package.clone());
|
||||||
set.push(ConflictItem {
|
set.push(ConflictItem {
|
||||||
package: package.clone(),
|
package: package.clone(),
|
||||||
conflict: item.conflict.clone(),
|
kind: item.kind.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// OK because we guarantee that
|
// OK because we guarantee that
|
||||||
|
|
@ -635,7 +654,7 @@ pub struct SchemaConflictSet(Vec<SchemaConflictItem>);
|
||||||
)]
|
)]
|
||||||
pub struct SchemaConflictItem {
|
pub struct SchemaConflictItem {
|
||||||
package: Option<PackageName>,
|
package: Option<PackageName>,
|
||||||
conflict: ConflictPackage,
|
kind: ConflictKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "schemars")]
|
#[cfg(feature = "schemars")]
|
||||||
|
|
@ -695,8 +714,8 @@ impl TryFrom<ConflictItemWire> for ConflictItem {
|
||||||
return Err(ConflictError::MissingPackage);
|
return Err(ConflictError::MissingPackage);
|
||||||
};
|
};
|
||||||
match (wire.extra, wire.group) {
|
match (wire.extra, wire.group) {
|
||||||
(None, None) => Err(ConflictError::MissingExtraAndGroup),
|
|
||||||
(Some(_), Some(_)) => Err(ConflictError::FoundExtraAndGroup),
|
(Some(_), Some(_)) => Err(ConflictError::FoundExtraAndGroup),
|
||||||
|
(None, None) => Ok(Self::from(package)),
|
||||||
(Some(extra), None) => Ok(Self::from((package, extra))),
|
(Some(extra), None) => Ok(Self::from((package, extra))),
|
||||||
(None, Some(group)) => Ok(Self::from((package, group))),
|
(None, Some(group)) => Ok(Self::from((package, group))),
|
||||||
}
|
}
|
||||||
|
|
@ -705,17 +724,22 @@ impl TryFrom<ConflictItemWire> for ConflictItem {
|
||||||
|
|
||||||
impl From<ConflictItem> for ConflictItemWire {
|
impl From<ConflictItem> for ConflictItemWire {
|
||||||
fn from(item: ConflictItem) -> Self {
|
fn from(item: ConflictItem) -> Self {
|
||||||
match item.conflict {
|
match item.kind {
|
||||||
ConflictPackage::Extra(extra) => Self {
|
ConflictKind::Extra(extra) => Self {
|
||||||
package: Some(item.package),
|
package: Some(item.package),
|
||||||
extra: Some(extra),
|
extra: Some(extra),
|
||||||
group: None,
|
group: None,
|
||||||
},
|
},
|
||||||
ConflictPackage::Group(group) => Self {
|
ConflictKind::Group(group) => Self {
|
||||||
package: Some(item.package),
|
package: Some(item.package),
|
||||||
extra: None,
|
extra: None,
|
||||||
group: Some(group),
|
group: Some(group),
|
||||||
},
|
},
|
||||||
|
ConflictKind::Project => Self {
|
||||||
|
package: Some(item.package),
|
||||||
|
extra: None,
|
||||||
|
group: None,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -726,15 +750,23 @@ impl TryFrom<ConflictItemWire> for SchemaConflictItem {
|
||||||
fn try_from(wire: ConflictItemWire) -> Result<Self, ConflictError> {
|
fn try_from(wire: ConflictItemWire) -> Result<Self, ConflictError> {
|
||||||
let package = wire.package;
|
let package = wire.package;
|
||||||
match (wire.extra, wire.group) {
|
match (wire.extra, wire.group) {
|
||||||
(None, None) => Err(ConflictError::MissingExtraAndGroup),
|
|
||||||
(Some(_), Some(_)) => Err(ConflictError::FoundExtraAndGroup),
|
(Some(_), Some(_)) => Err(ConflictError::FoundExtraAndGroup),
|
||||||
|
(None, None) => {
|
||||||
|
let Some(package) = package else {
|
||||||
|
return Err(ConflictError::MissingPackageAndExtraAndGroup);
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
package: Some(package),
|
||||||
|
kind: ConflictKind::Project,
|
||||||
|
})
|
||||||
|
}
|
||||||
(Some(extra), None) => Ok(Self {
|
(Some(extra), None) => Ok(Self {
|
||||||
package,
|
package,
|
||||||
conflict: ConflictPackage::Extra(extra),
|
kind: ConflictKind::Extra(extra),
|
||||||
}),
|
}),
|
||||||
(None, Some(group)) => Ok(Self {
|
(None, Some(group)) => Ok(Self {
|
||||||
package,
|
package,
|
||||||
conflict: ConflictPackage::Group(group),
|
kind: ConflictKind::Group(group),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -742,17 +774,22 @@ impl TryFrom<ConflictItemWire> for SchemaConflictItem {
|
||||||
|
|
||||||
impl From<SchemaConflictItem> for ConflictItemWire {
|
impl From<SchemaConflictItem> for ConflictItemWire {
|
||||||
fn from(item: SchemaConflictItem) -> Self {
|
fn from(item: SchemaConflictItem) -> Self {
|
||||||
match item.conflict {
|
match item.kind {
|
||||||
ConflictPackage::Extra(extra) => Self {
|
ConflictKind::Extra(extra) => Self {
|
||||||
package: item.package,
|
package: item.package,
|
||||||
extra: Some(extra),
|
extra: Some(extra),
|
||||||
group: None,
|
group: None,
|
||||||
},
|
},
|
||||||
ConflictPackage::Group(group) => Self {
|
ConflictKind::Group(group) => Self {
|
||||||
package: item.package,
|
package: item.package,
|
||||||
extra: None,
|
extra: None,
|
||||||
group: Some(group),
|
group: Some(group),
|
||||||
},
|
},
|
||||||
|
ConflictKind::Project => Self {
|
||||||
|
package: item.package,
|
||||||
|
extra: None,
|
||||||
|
group: None,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ pub(crate) fn simplify_conflict_markers(
|
||||||
let mut new_set = BTreeSet::default();
|
let mut new_set = BTreeSet::default();
|
||||||
for item in set {
|
for item in set {
|
||||||
for conflict_set in conflicts.iter() {
|
for conflict_set in conflicts.iter() {
|
||||||
if !conflict_set.contains(item.package(), item.as_ref().conflict()) {
|
if !conflict_set.contains(item.package(), item.as_ref().kind()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for conflict_item in conflict_set.iter() {
|
for conflict_item in conflict_set.iter() {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ pub trait Installable<'lock> {
|
||||||
|
|
||||||
let mut queue: VecDeque<(&Package, Option<&ExtraName>)> = VecDeque::new();
|
let mut queue: VecDeque<(&Package, Option<&ExtraName>)> = VecDeque::new();
|
||||||
let mut seen = FxHashSet::default();
|
let mut seen = FxHashSet::default();
|
||||||
|
let mut activated_projects: Vec<&PackageName> = vec![];
|
||||||
let mut activated_extras: Vec<(&PackageName, &ExtraName)> = vec![];
|
let mut activated_extras: Vec<(&PackageName, &ExtraName)> = vec![];
|
||||||
let mut activated_groups: Vec<(&PackageName, &GroupName)> = vec![];
|
let mut activated_groups: Vec<(&PackageName, &GroupName)> = vec![];
|
||||||
|
|
||||||
|
|
@ -74,6 +75,7 @@ pub trait Installable<'lock> {
|
||||||
|
|
||||||
// Track the activated extras.
|
// Track the activated extras.
|
||||||
if dev.prod() {
|
if dev.prod() {
|
||||||
|
activated_projects.push(&dist.id.name);
|
||||||
for extra in extras.extra_names(dist.optional_dependencies.keys()) {
|
for extra in extras.extra_names(dist.optional_dependencies.keys()) {
|
||||||
activated_extras.push((&dist.id.name, extra));
|
activated_extras.push((&dist.id.name, extra));
|
||||||
}
|
}
|
||||||
|
|
@ -143,6 +145,7 @@ pub trait Installable<'lock> {
|
||||||
{
|
{
|
||||||
if !dep.complexified_marker.evaluate(
|
if !dep.complexified_marker.evaluate(
|
||||||
marker_env,
|
marker_env,
|
||||||
|
activated_projects.iter().copied(),
|
||||||
activated_extras.iter().copied(),
|
activated_extras.iter().copied(),
|
||||||
activated_groups.iter().copied(),
|
activated_groups.iter().copied(),
|
||||||
) {
|
) {
|
||||||
|
|
@ -367,6 +370,7 @@ pub trait Installable<'lock> {
|
||||||
}
|
}
|
||||||
if !dep.complexified_marker.evaluate(
|
if !dep.complexified_marker.evaluate(
|
||||||
marker_env,
|
marker_env,
|
||||||
|
activated_projects.iter().copied(),
|
||||||
activated_extras
|
activated_extras
|
||||||
.iter()
|
.iter()
|
||||||
.chain(additional_activated_extras.iter())
|
.chain(additional_activated_extras.iter())
|
||||||
|
|
@ -454,6 +458,7 @@ pub trait Installable<'lock> {
|
||||||
for dep in deps {
|
for dep in deps {
|
||||||
if !dep.complexified_marker.evaluate(
|
if !dep.complexified_marker.evaluate(
|
||||||
marker_env,
|
marker_env,
|
||||||
|
activated_projects.iter().copied(),
|
||||||
activated_extras.iter().copied(),
|
activated_extras.iter().copied(),
|
||||||
activated_groups.iter().copied(),
|
activated_groups.iter().copied(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ use uv_platform_tags::{
|
||||||
AbiTag, IncompatibleTag, LanguageTag, PlatformTag, TagCompatibility, TagPriority, Tags,
|
AbiTag, IncompatibleTag, LanguageTag, PlatformTag, TagCompatibility, TagPriority, Tags,
|
||||||
};
|
};
|
||||||
use uv_pypi_types::{
|
use uv_pypi_types::{
|
||||||
ConflictPackage, Conflicts, HashAlgorithm, HashDigest, HashDigests, Hashes, ParsedArchiveUrl,
|
ConflictKind, Conflicts, HashAlgorithm, HashDigest, HashDigests, Hashes, ParsedArchiveUrl,
|
||||||
ParsedGitUrl,
|
ParsedGitUrl,
|
||||||
};
|
};
|
||||||
use uv_redacted::DisplaySafeUrl;
|
use uv_redacted::DisplaySafeUrl;
|
||||||
|
|
@ -1026,11 +1026,12 @@ impl Lock {
|
||||||
list.push(each_element_on_its_line_array(set.iter().map(|item| {
|
list.push(each_element_on_its_line_array(set.iter().map(|item| {
|
||||||
let mut table = InlineTable::new();
|
let mut table = InlineTable::new();
|
||||||
table.insert("package", Value::from(item.package().to_string()));
|
table.insert("package", Value::from(item.package().to_string()));
|
||||||
match item.conflict() {
|
match item.kind() {
|
||||||
ConflictPackage::Extra(extra) => {
|
ConflictKind::Project => {}
|
||||||
|
ConflictKind::Extra(extra) => {
|
||||||
table.insert("extra", Value::from(extra.to_string()));
|
table.insert("extra", Value::from(extra.to_string()));
|
||||||
}
|
}
|
||||||
ConflictPackage::Group(group) => {
|
ConflictKind::Group(group) => {
|
||||||
table.insert("group", Value::from(group.to_string()));
|
table.insert("group", Value::from(group.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3107,6 +3108,21 @@ impl Package {
|
||||||
pub fn dependency_groups(&self) -> &BTreeMap<GroupName, BTreeSet<Requirement>> {
|
pub fn dependency_groups(&self) -> &BTreeMap<GroupName, BTreeSet<Requirement>> {
|
||||||
&self.metadata.dependency_groups
|
&self.metadata.dependency_groups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the dependencies of the package.
|
||||||
|
pub fn dependencies(&self) -> &[Dependency] {
|
||||||
|
&self.dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional dependencies of the package.
|
||||||
|
pub fn optional_dependencies(&self) -> &BTreeMap<ExtraName, Vec<Dependency>> {
|
||||||
|
&self.optional_dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the resolved PEP 735 dependency groups of the package.
|
||||||
|
pub fn resolved_dependency_groups(&self) -> &BTreeMap<GroupName, Vec<Dependency>> {
|
||||||
|
&self.dependency_groups
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to construct a `VerbatimUrl` from the given normalized `Path`.
|
/// Attempts to construct a `VerbatimUrl` from the given normalized `Path`.
|
||||||
|
|
@ -4657,7 +4673,7 @@ impl TryFrom<WheelWire> for Wheel {
|
||||||
|
|
||||||
/// A single dependency of a package in a lockfile.
|
/// A single dependency of a package in a lockfile.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
struct Dependency {
|
pub struct Dependency {
|
||||||
package_id: PackageId,
|
package_id: PackageId,
|
||||||
extra: BTreeSet<ExtraName>,
|
extra: BTreeSet<ExtraName>,
|
||||||
/// A marker simplified from the PEP 508 marker in `complexified_marker`
|
/// A marker simplified from the PEP 508 marker in `complexified_marker`
|
||||||
|
|
@ -4742,6 +4758,16 @@ impl Dependency {
|
||||||
|
|
||||||
table
|
table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the package name of this dependency.
|
||||||
|
pub fn package_name(&self) -> &PackageName {
|
||||||
|
&self.package_id.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the extras specified on this dependency.
|
||||||
|
pub fn extra(&self) -> &BTreeSet<ExtraName> {
|
||||||
|
&self.extra
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Dependency {
|
impl Display for Dependency {
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ use uv_distribution_types::{Requirement, RequirementSource};
|
||||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||||
use uv_pep440::{Version, VersionSpecifiers};
|
use uv_pep440::{Version, VersionSpecifiers};
|
||||||
use uv_pypi_types::{
|
use uv_pypi_types::{
|
||||||
Conflicts, ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl,
|
ConflictItemRef, Conflicts, ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl,
|
||||||
VerbatimParsedUrl,
|
ParsedUrl, VerbatimParsedUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner};
|
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner};
|
||||||
|
|
@ -19,6 +19,21 @@ pub(crate) struct PubGrubDependency {
|
||||||
pub(crate) package: PubGrubPackage,
|
pub(crate) package: PubGrubPackage,
|
||||||
pub(crate) version: Ranges<Version>,
|
pub(crate) version: Ranges<Version>,
|
||||||
|
|
||||||
|
/// When the parent that created this dependency is a "normal" package
|
||||||
|
/// (non-extra non-group), this corresponds to its name.
|
||||||
|
///
|
||||||
|
/// This is used to create project-level `ConflictItemRef` for a specific
|
||||||
|
/// package. In effect, this lets us "delay" filtering of project
|
||||||
|
/// dependencies when a conflict is declared between the project and a
|
||||||
|
/// group.
|
||||||
|
///
|
||||||
|
/// The main problem with dealing with project level conflicts is that if you
|
||||||
|
/// declare a conflict between a package and a group, we represent that
|
||||||
|
/// group as a dependency of that package. So if you filter out the package
|
||||||
|
/// in a fork due to a conflict, you also filter out the group. Therefore,
|
||||||
|
/// we introduce this parent field to enable "delayed" filtering.
|
||||||
|
pub(crate) parent: Option<PackageName>,
|
||||||
|
|
||||||
/// This field is set if the [`Requirement`] had a URL. We still use a URL from [`Urls`]
|
/// This field is set if the [`Requirement`] had a URL. We still use a URL from [`Urls`]
|
||||||
/// even if this field is None where there is an override with a URL or there is a different
|
/// even if this field is None where there is an override with a URL or there is a different
|
||||||
/// requirement or constraint for the same package that has a URL.
|
/// requirement or constraint for the same package that has a URL.
|
||||||
|
|
@ -30,8 +45,12 @@ impl PubGrubDependency {
|
||||||
conflicts: &Conflicts,
|
conflicts: &Conflicts,
|
||||||
requirement: Cow<'a, Requirement>,
|
requirement: Cow<'a, Requirement>,
|
||||||
dev: Option<&'a GroupName>,
|
dev: Option<&'a GroupName>,
|
||||||
source_name: Option<&'a PackageName>,
|
parent_package: Option<&'a PubGrubPackage>,
|
||||||
) -> impl Iterator<Item = Self> + 'a {
|
) -> impl Iterator<Item = Self> + 'a {
|
||||||
|
let parent_name = parent_package.and_then(|package| package.name_no_root());
|
||||||
|
let is_normal_parent = parent_package
|
||||||
|
.map(|pp| pp.extra().is_none() && pp.dev().is_none())
|
||||||
|
.unwrap_or(false);
|
||||||
let iter = if !requirement.extras.is_empty() {
|
let iter = if !requirement.extras.is_empty() {
|
||||||
// This is crazy subtle, but if any of the extras in the
|
// This is crazy subtle, but if any of the extras in the
|
||||||
// requirement are part of a declared conflict, then we
|
// requirement are part of a declared conflict, then we
|
||||||
|
|
@ -80,50 +99,59 @@ impl PubGrubDependency {
|
||||||
|
|
||||||
// Add the package, plus any extra variants.
|
// Add the package, plus any extra variants.
|
||||||
iter.map(move |(extra, group)| {
|
iter.map(move |(extra, group)| {
|
||||||
PubGrubRequirement::from_requirement(&requirement, extra, group)
|
let pubgrub_requirement =
|
||||||
})
|
PubGrubRequirement::from_requirement(&requirement, extra, group);
|
||||||
.map(move |requirement| {
|
|
||||||
let PubGrubRequirement {
|
let PubGrubRequirement {
|
||||||
package,
|
package,
|
||||||
version,
|
version,
|
||||||
url,
|
url,
|
||||||
} = requirement;
|
} = pubgrub_requirement;
|
||||||
match &*package {
|
match &*package {
|
||||||
PubGrubPackageInner::Package { .. } => Self {
|
PubGrubPackageInner::Package { .. } => Self {
|
||||||
package,
|
package,
|
||||||
version,
|
version,
|
||||||
|
parent: if is_normal_parent {
|
||||||
|
parent_name.cloned()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
url,
|
url,
|
||||||
},
|
},
|
||||||
PubGrubPackageInner::Marker { .. } => Self {
|
PubGrubPackageInner::Marker { .. } => Self {
|
||||||
package,
|
package,
|
||||||
version,
|
version,
|
||||||
|
parent: if is_normal_parent {
|
||||||
|
parent_name.cloned()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
url,
|
url,
|
||||||
},
|
},
|
||||||
PubGrubPackageInner::Extra { name, .. } => {
|
PubGrubPackageInner::Extra { name, .. } => {
|
||||||
// Detect self-dependencies.
|
|
||||||
if dev.is_none() {
|
if dev.is_none() {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
source_name.is_none_or(|source_name| source_name != name),
|
parent_name.is_none_or(|parent_name| parent_name != name),
|
||||||
"extras not flattened for {name}"
|
"extras not flattened for {name}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Self {
|
Self {
|
||||||
package,
|
package,
|
||||||
version,
|
version,
|
||||||
|
parent: None,
|
||||||
url,
|
url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PubGrubPackageInner::Dev { name, .. } => {
|
PubGrubPackageInner::Dev { name, .. } => {
|
||||||
// Detect self-dependencies.
|
|
||||||
if dev.is_none() {
|
if dev.is_none() {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
source_name.is_none_or(|source_name| source_name != name),
|
parent_name.is_none_or(|parent_name| parent_name != name),
|
||||||
"group not flattened for {name}"
|
"group not flattened for {name}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Self {
|
Self {
|
||||||
package,
|
package,
|
||||||
version,
|
version,
|
||||||
|
parent: None,
|
||||||
url,
|
url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +163,14 @@ impl PubGrubDependency {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extracts a possible conflicting item from this dependency.
|
||||||
|
///
|
||||||
|
/// If this package can't possibly be classified as conflicting, then this
|
||||||
|
/// returns `None`.
|
||||||
|
pub(crate) fn conflicting_item(&self) -> Option<ConflictItemRef<'_>> {
|
||||||
|
self.package.conflicting_item()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A PubGrub-compatible package and version range.
|
/// A PubGrub-compatible package and version range.
|
||||||
|
|
|
||||||
|
|
@ -214,14 +214,14 @@ impl PubGrubPackage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts a possible conflicting group from this package.
|
/// Extracts a possible conflicting item from this package.
|
||||||
///
|
///
|
||||||
/// If this package can't possibly be classified as a conflicting group,
|
/// If this package can't possibly be classified as conflicting, then
|
||||||
/// then this returns `None`.
|
/// this returns `None`.
|
||||||
pub(crate) fn conflicting_item(&self) -> Option<ConflictItemRef<'_>> {
|
pub(crate) fn conflicting_item(&self) -> Option<ConflictItemRef<'_>> {
|
||||||
let package = self.name_no_root()?;
|
let package = self.name_no_root()?;
|
||||||
match (self.extra(), self.dev()) {
|
match (self.extra(), self.dev()) {
|
||||||
(None, None) => None,
|
(None, None) => Some(ConflictItemRef::from(package)),
|
||||||
(Some(extra), None) => Some(ConflictItemRef::from((package, extra))),
|
(Some(extra), None) => Some(ConflictItemRef::from((package, extra))),
|
||||||
(None, Some(group)) => Some(ConflictItemRef::from((package, group))),
|
(None, Some(group)) => Some(ConflictItemRef::from((package, group))),
|
||||||
(Some(extra), Some(group)) => {
|
(Some(extra), Some(group)) => {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use tracing::trace;
|
||||||
use uv_distribution_types::{RequiresPython, RequiresPythonRange};
|
use uv_distribution_types::{RequiresPython, RequiresPythonRange};
|
||||||
use uv_pep440::VersionSpecifiers;
|
use uv_pep440::VersionSpecifiers;
|
||||||
use uv_pep508::{MarkerEnvironment, MarkerTree};
|
use uv_pep508::{MarkerEnvironment, MarkerTree};
|
||||||
use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictPackage, ResolverMarkerEnvironment};
|
use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKind, ResolverMarkerEnvironment};
|
||||||
|
|
||||||
use crate::pubgrub::{PubGrubDependency, PubGrubPackage};
|
use crate::pubgrub::{PubGrubDependency, PubGrubPackage};
|
||||||
use crate::resolver::ForkState;
|
use crate::resolver::ForkState;
|
||||||
|
|
@ -391,11 +391,12 @@ impl ResolverEnvironment {
|
||||||
format!(
|
format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
conflict_item.package(),
|
conflict_item.package(),
|
||||||
match conflict_item.conflict() {
|
match conflict_item.kind() {
|
||||||
ConflictPackage::Extra(extra) => format!("[{extra}]"),
|
ConflictKind::Extra(extra) => format!("[{extra}]"),
|
||||||
ConflictPackage::Group(group) => {
|
ConflictKind::Group(group) => {
|
||||||
format!("[group:{group}]")
|
format!("[group:{group}]")
|
||||||
}
|
}
|
||||||
|
ConflictKind::Project => String::new(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ use uv_pep508::{
|
||||||
MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
|
MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
|
||||||
};
|
};
|
||||||
use uv_platform_tags::Tags;
|
use uv_platform_tags::Tags;
|
||||||
use uv_pypi_types::{ConflictItem, ConflictItemRef, Conflicts, VerbatimParsedUrl};
|
use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
|
||||||
use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
|
use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
|
||||||
use uv_warnings::warn_user_once;
|
use uv_warnings::warn_user_once;
|
||||||
|
|
||||||
|
|
@ -946,6 +946,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
let PubGrubDependency {
|
let PubGrubDependency {
|
||||||
package,
|
package,
|
||||||
version: _,
|
version: _,
|
||||||
|
parent: _,
|
||||||
url: _,
|
url: _,
|
||||||
} = dependency;
|
} = dependency;
|
||||||
let url = package.name().and_then(|name| state.fork_urls.get(name));
|
let url = package.name().and_then(|name| state.fork_urls.get(name));
|
||||||
|
|
@ -1750,7 +1751,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
&self.conflicts,
|
&self.conflicts,
|
||||||
requirement,
|
requirement,
|
||||||
None,
|
None,
|
||||||
None,
|
Some(package),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
@ -1866,7 +1867,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
&self.conflicts,
|
&self.conflicts,
|
||||||
requirement,
|
requirement,
|
||||||
dev.as_ref(),
|
dev.as_ref(),
|
||||||
Some(name),
|
Some(package),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.chain(system_dependencies)
|
.chain(system_dependencies)
|
||||||
|
|
@ -1890,6 +1891,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
marker,
|
marker,
|
||||||
}),
|
}),
|
||||||
version: Range::singleton(version.clone()),
|
version: Range::singleton(version.clone()),
|
||||||
|
parent: None,
|
||||||
url: None,
|
url: None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
@ -1917,6 +1919,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
marker,
|
marker,
|
||||||
}),
|
}),
|
||||||
version: Range::singleton(version.clone()),
|
version: Range::singleton(version.clone()),
|
||||||
|
parent: None,
|
||||||
url: None,
|
url: None,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -1938,6 +1941,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
marker,
|
marker,
|
||||||
}),
|
}),
|
||||||
version: Range::singleton(version.clone()),
|
version: Range::singleton(version.clone()),
|
||||||
|
parent: None,
|
||||||
url: None,
|
url: None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
@ -2801,6 +2805,7 @@ impl ForkState {
|
||||||
let PubGrubDependency {
|
let PubGrubDependency {
|
||||||
package,
|
package,
|
||||||
version,
|
version,
|
||||||
|
parent: _,
|
||||||
url,
|
url,
|
||||||
} = dependency;
|
} = dependency;
|
||||||
|
|
||||||
|
|
@ -2872,6 +2877,7 @@ impl ForkState {
|
||||||
let PubGrubDependency {
|
let PubGrubDependency {
|
||||||
package,
|
package,
|
||||||
version,
|
version,
|
||||||
|
parent: _,
|
||||||
url: _,
|
url: _,
|
||||||
} = dependency;
|
} = dependency;
|
||||||
(package, version)
|
(package, version)
|
||||||
|
|
@ -3654,7 +3660,7 @@ impl Forks {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a fork that excludes ALL extras.
|
// Create a fork that excludes ALL conflicts.
|
||||||
if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
|
if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
|
||||||
new.push(fork_none);
|
new.push(fork_none);
|
||||||
}
|
}
|
||||||
|
|
@ -3740,7 +3746,7 @@ impl Fork {
|
||||||
|
|
||||||
/// Add a dependency to this fork.
|
/// Add a dependency to this fork.
|
||||||
fn add_dependency(&mut self, dep: PubGrubDependency) {
|
fn add_dependency(&mut self, dep: PubGrubDependency) {
|
||||||
if let Some(conflicting_item) = dep.package.conflicting_item() {
|
if let Some(conflicting_item) = dep.conflicting_item() {
|
||||||
self.conflicts.insert(conflicting_item.to_owned());
|
self.conflicts.insert(conflicting_item.to_owned());
|
||||||
}
|
}
|
||||||
self.dependencies.push(dep);
|
self.dependencies.push(dep);
|
||||||
|
|
@ -3757,7 +3763,7 @@ impl Fork {
|
||||||
if self.env.included_by_marker(marker) {
|
if self.env.included_by_marker(marker) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if let Some(conflicting_item) = dep.package.conflicting_item() {
|
if let Some(conflicting_item) = dep.conflicting_item() {
|
||||||
self.conflicts.remove(&conflicting_item);
|
self.conflicts.remove(&conflicting_item);
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
|
|
@ -3782,12 +3788,23 @@ impl Fork {
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
self.env = self.env.filter_by_group(rules)?;
|
self.env = self.env.filter_by_group(rules)?;
|
||||||
self.dependencies.retain(|dep| {
|
self.dependencies.retain(|dep| {
|
||||||
let Some(conflicting_item) = dep.package.conflicting_item() else {
|
let Some(conflicting_item) = dep.conflicting_item() else {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if self.env.included_by_group(conflicting_item) {
|
if self.env.included_by_group(conflicting_item) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
match conflicting_item.kind() {
|
||||||
|
// We should not filter entire projects unless they're a top-level dependency
|
||||||
|
// Otherwise, we'll fail to solve for children of the project, like extras
|
||||||
|
ConflictKindRef::Project => {
|
||||||
|
if dep.parent.is_some() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConflictKindRef::Group(_) => {}
|
||||||
|
ConflictKindRef::Extra(_) => {}
|
||||||
|
}
|
||||||
self.conflicts.remove(&conflicting_item);
|
self.conflicts.remove(&conflicting_item);
|
||||||
false
|
false
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ impl From<SystemDependency> for PubGrubDependency {
|
||||||
Self {
|
Self {
|
||||||
package: PubGrubPackage::from(PubGrubPackageInner::System(value.name)),
|
package: PubGrubPackage::from(PubGrubPackageInner::System(value.name)),
|
||||||
version: Ranges::singleton(value.version),
|
version: Ranges::singleton(value.version),
|
||||||
|
parent: None,
|
||||||
url: None,
|
url: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||||
use uv_pep508::{ExtraOperator, MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree};
|
use uv_pep508::{ExtraOperator, MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree};
|
||||||
use uv_pypi_types::{ConflictItem, ConflictPackage, Conflicts, Inference};
|
use uv_pypi_types::{ConflictItem, ConflictKind, Conflicts, Inference};
|
||||||
|
|
||||||
use crate::ResolveError;
|
use crate::ResolveError;
|
||||||
|
|
||||||
|
|
@ -173,9 +173,10 @@ impl UniversalMarker {
|
||||||
/// This may simplify the conflicting marker component of this universal
|
/// This may simplify the conflicting marker component of this universal
|
||||||
/// marker.
|
/// marker.
|
||||||
pub(crate) fn assume_conflict_item(&mut self, item: &ConflictItem) {
|
pub(crate) fn assume_conflict_item(&mut self, item: &ConflictItem) {
|
||||||
match *item.conflict() {
|
match *item.kind() {
|
||||||
ConflictPackage::Extra(ref extra) => self.assume_extra(item.package(), extra),
|
ConflictKind::Extra(ref extra) => self.assume_extra(item.package(), extra),
|
||||||
ConflictPackage::Group(ref group) => self.assume_group(item.package(), group),
|
ConflictKind::Group(ref group) => self.assume_group(item.package(), group),
|
||||||
|
ConflictKind::Project => self.assume_project(item.package()),
|
||||||
}
|
}
|
||||||
self.pep508 = self.marker.without_extras();
|
self.pep508 = self.marker.without_extras();
|
||||||
}
|
}
|
||||||
|
|
@ -186,18 +187,45 @@ impl UniversalMarker {
|
||||||
/// This may simplify the conflicting marker component of this universal
|
/// This may simplify the conflicting marker component of this universal
|
||||||
/// marker.
|
/// marker.
|
||||||
pub(crate) fn assume_not_conflict_item(&mut self, item: &ConflictItem) {
|
pub(crate) fn assume_not_conflict_item(&mut self, item: &ConflictItem) {
|
||||||
match *item.conflict() {
|
match *item.kind() {
|
||||||
ConflictPackage::Extra(ref extra) => self.assume_not_extra(item.package(), extra),
|
ConflictKind::Extra(ref extra) => self.assume_not_extra(item.package(), extra),
|
||||||
ConflictPackage::Group(ref group) => self.assume_not_group(item.package(), group),
|
ConflictKind::Group(ref group) => self.assume_not_group(item.package(), group),
|
||||||
|
ConflictKind::Project => self.assume_not_project(item.package()),
|
||||||
}
|
}
|
||||||
self.pep508 = self.marker.without_extras();
|
self.pep508 = self.marker.without_extras();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assumes that the "production" dependencies for the given project are
|
||||||
|
/// activated.
|
||||||
|
///
|
||||||
|
/// This may simplify the conflicting marker component of this universal
|
||||||
|
/// marker.
|
||||||
|
fn assume_project(&mut self, package: &PackageName) {
|
||||||
|
let extra = encode_project(package);
|
||||||
|
self.marker = self
|
||||||
|
.marker
|
||||||
|
.simplify_extras_with(|candidate| *candidate == extra);
|
||||||
|
self.pep508 = self.marker.without_extras();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assumes that the "production" dependencies for the given project are
|
||||||
|
/// not activated.
|
||||||
|
///
|
||||||
|
/// This may simplify the conflicting marker component of this universal
|
||||||
|
/// marker.
|
||||||
|
fn assume_not_project(&mut self, package: &PackageName) {
|
||||||
|
let extra = encode_project(package);
|
||||||
|
self.marker = self
|
||||||
|
.marker
|
||||||
|
.simplify_not_extras_with(|candidate| *candidate == extra);
|
||||||
|
self.pep508 = self.marker.without_extras();
|
||||||
|
}
|
||||||
|
|
||||||
/// Assumes that a given extra for the given package is activated.
|
/// Assumes that a given extra for the given package is activated.
|
||||||
///
|
///
|
||||||
/// This may simplify the conflicting marker component of this universal
|
/// This may simplify the conflicting marker component of this universal
|
||||||
/// marker.
|
/// marker.
|
||||||
pub(crate) fn assume_extra(&mut self, package: &PackageName, extra: &ExtraName) {
|
fn assume_extra(&mut self, package: &PackageName, extra: &ExtraName) {
|
||||||
let extra = encode_package_extra(package, extra);
|
let extra = encode_package_extra(package, extra);
|
||||||
self.marker = self
|
self.marker = self
|
||||||
.marker
|
.marker
|
||||||
|
|
@ -209,7 +237,7 @@ impl UniversalMarker {
|
||||||
///
|
///
|
||||||
/// This may simplify the conflicting marker component of this universal
|
/// This may simplify the conflicting marker component of this universal
|
||||||
/// marker.
|
/// marker.
|
||||||
pub(crate) fn assume_not_extra(&mut self, package: &PackageName, extra: &ExtraName) {
|
fn assume_not_extra(&mut self, package: &PackageName, extra: &ExtraName) {
|
||||||
let extra = encode_package_extra(package, extra);
|
let extra = encode_package_extra(package, extra);
|
||||||
self.marker = self
|
self.marker = self
|
||||||
.marker
|
.marker
|
||||||
|
|
@ -221,7 +249,7 @@ impl UniversalMarker {
|
||||||
///
|
///
|
||||||
/// This may simplify the conflicting marker component of this universal
|
/// This may simplify the conflicting marker component of this universal
|
||||||
/// marker.
|
/// marker.
|
||||||
pub(crate) fn assume_group(&mut self, package: &PackageName, group: &GroupName) {
|
fn assume_group(&mut self, package: &PackageName, group: &GroupName) {
|
||||||
let extra = encode_package_group(package, group);
|
let extra = encode_package_group(package, group);
|
||||||
self.marker = self
|
self.marker = self
|
||||||
.marker
|
.marker
|
||||||
|
|
@ -233,7 +261,7 @@ impl UniversalMarker {
|
||||||
///
|
///
|
||||||
/// This may simplify the conflicting marker component of this universal
|
/// This may simplify the conflicting marker component of this universal
|
||||||
/// marker.
|
/// marker.
|
||||||
pub(crate) fn assume_not_group(&mut self, package: &PackageName, group: &GroupName) {
|
fn assume_not_group(&mut self, package: &PackageName, group: &GroupName) {
|
||||||
let extra = encode_package_group(package, group);
|
let extra = encode_package_group(package, group);
|
||||||
self.marker = self
|
self.marker = self
|
||||||
.marker
|
.marker
|
||||||
|
|
@ -277,6 +305,7 @@ impl UniversalMarker {
|
||||||
pub(crate) fn evaluate<P, E, G>(
|
pub(crate) fn evaluate<P, E, G>(
|
||||||
self,
|
self,
|
||||||
env: &MarkerEnvironment,
|
env: &MarkerEnvironment,
|
||||||
|
projects: impl Iterator<Item = P>,
|
||||||
extras: impl Iterator<Item = (P, E)>,
|
extras: impl Iterator<Item = (P, E)>,
|
||||||
groups: impl Iterator<Item = (P, G)>,
|
groups: impl Iterator<Item = (P, G)>,
|
||||||
) -> bool
|
) -> bool
|
||||||
|
|
@ -285,12 +314,18 @@ impl UniversalMarker {
|
||||||
E: Borrow<ExtraName>,
|
E: Borrow<ExtraName>,
|
||||||
G: Borrow<GroupName>,
|
G: Borrow<GroupName>,
|
||||||
{
|
{
|
||||||
|
let projects = projects.map(|package| encode_project(package.borrow()));
|
||||||
let extras =
|
let extras =
|
||||||
extras.map(|(package, extra)| encode_package_extra(package.borrow(), extra.borrow()));
|
extras.map(|(package, extra)| encode_package_extra(package.borrow(), extra.borrow()));
|
||||||
let groups =
|
let groups =
|
||||||
groups.map(|(package, group)| encode_package_group(package.borrow(), group.borrow()));
|
groups.map(|(package, group)| encode_package_group(package.borrow(), group.borrow()));
|
||||||
self.marker
|
self.marker.evaluate(
|
||||||
.evaluate(env, &extras.chain(groups).collect::<Vec<ExtraName>>())
|
env,
|
||||||
|
&projects
|
||||||
|
.chain(extras)
|
||||||
|
.chain(groups)
|
||||||
|
.collect::<Vec<ExtraName>>(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the marker always evaluates to true if the given set of extras is activated.
|
/// Returns true if the marker always evaluates to true if the given set of extras is activated.
|
||||||
|
|
@ -392,12 +427,23 @@ impl ConflictMarker {
|
||||||
/// Create a conflict marker that is true only when the given extra or
|
/// Create a conflict marker that is true only when the given extra or
|
||||||
/// group (for a specific package) is activated.
|
/// group (for a specific package) is activated.
|
||||||
pub fn from_conflict_item(item: &ConflictItem) -> Self {
|
pub fn from_conflict_item(item: &ConflictItem) -> Self {
|
||||||
match *item.conflict() {
|
match *item.kind() {
|
||||||
ConflictPackage::Extra(ref extra) => Self::extra(item.package(), extra),
|
ConflictKind::Extra(ref extra) => Self::extra(item.package(), extra),
|
||||||
ConflictPackage::Group(ref group) => Self::group(item.package(), group),
|
ConflictKind::Group(ref group) => Self::group(item.package(), group),
|
||||||
|
ConflictKind::Project => Self::project(item.package()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a conflict marker that is true only when the production
|
||||||
|
/// dependencies for the given package are activated.
|
||||||
|
pub fn project(package: &PackageName) -> Self {
|
||||||
|
let operator = uv_pep508::ExtraOperator::Equal;
|
||||||
|
let name = uv_pep508::MarkerValueExtra::Extra(encode_project(package));
|
||||||
|
let expr = uv_pep508::MarkerExpression::Extra { operator, name };
|
||||||
|
let marker = MarkerTree::expression(expr);
|
||||||
|
Self { marker }
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a conflict marker that is true only when the given extra for the
|
/// Create a conflict marker that is true only when the given extra for the
|
||||||
/// given package is activated.
|
/// given package is activated.
|
||||||
pub fn extra(package: &PackageName, extra: &ExtraName) -> Self {
|
pub fn extra(package: &PackageName, extra: &ExtraName) -> Self {
|
||||||
|
|
@ -504,9 +550,10 @@ impl std::fmt::Debug for ConflictMarker {
|
||||||
|
|
||||||
/// Encodes the given conflict into a valid `extra` value in a PEP 508 marker.
|
/// Encodes the given conflict into a valid `extra` value in a PEP 508 marker.
|
||||||
fn encode_conflict_item(conflict: &ConflictItem) -> ExtraName {
|
fn encode_conflict_item(conflict: &ConflictItem) -> ExtraName {
|
||||||
match conflict.conflict() {
|
match conflict.kind() {
|
||||||
ConflictPackage::Extra(extra) => encode_package_extra(conflict.package(), extra),
|
ConflictKind::Extra(extra) => encode_package_extra(conflict.package(), extra),
|
||||||
ConflictPackage::Group(group) => encode_package_group(conflict.package(), group),
|
ConflictKind::Group(group) => encode_package_group(conflict.package(), group),
|
||||||
|
ConflictKind::Project => encode_project(conflict.package()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -535,8 +582,17 @@ fn encode_package_group(package: &PackageName, group: &GroupName) -> ExtraName {
|
||||||
ExtraName::from_owned(format!("group-{package_len}-{package}-{group}")).unwrap()
|
ExtraName::from_owned(format!("group-{package_len}-{package}-{group}")).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encodes the given project package name into a valid `extra` value in a PEP
|
||||||
|
/// 508 marker.
|
||||||
|
fn encode_project(package: &PackageName) -> ExtraName {
|
||||||
|
// See `encode_package_extra`, the same considerations apply here.
|
||||||
|
let package_len = package.as_str().len();
|
||||||
|
ExtraName::from_owned(format!("project-{package_len}-{package}")).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ParsedRawExtra<'a> {
|
enum ParsedRawExtra<'a> {
|
||||||
|
Project { package: &'a str },
|
||||||
Extra { package: &'a str, extra: &'a str },
|
Extra { package: &'a str, extra: &'a str },
|
||||||
Group { package: &'a str, group: &'a str },
|
Group { package: &'a str, group: &'a str },
|
||||||
}
|
}
|
||||||
|
|
@ -553,13 +609,13 @@ impl<'a> ParsedRawExtra<'a> {
|
||||||
let Some((kind, tail)) = raw.split_once('-') else {
|
let Some((kind, tail)) = raw.split_once('-') else {
|
||||||
return Err(mkerr(
|
return Err(mkerr(
|
||||||
raw_extra,
|
raw_extra,
|
||||||
"expected to find leading `extra-` or `group-`",
|
"expected to find leading `package`, `extra-` or `group-`",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let Some((len, tail)) = tail.split_once('-') else {
|
let Some((len, tail)) = tail.split_once('-') else {
|
||||||
return Err(mkerr(
|
return Err(mkerr(
|
||||||
raw_extra,
|
raw_extra,
|
||||||
"expected to find `{number}-` after leading `extra-` or `group-`",
|
"expected to find `{number}-` after leading `package-`, `extra-` or `group-`",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let len = len.parse::<usize>().map_err(|_| {
|
let len = len.parse::<usize>().map_err(|_| {
|
||||||
|
|
@ -577,22 +633,28 @@ impl<'a> ParsedRawExtra<'a> {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
if !tail.starts_with('-') {
|
|
||||||
return Err(mkerr(
|
|
||||||
raw_extra,
|
|
||||||
format!("expected `-` after package name `{package}`"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let tail = &tail[1..];
|
|
||||||
match kind {
|
match kind {
|
||||||
"extra" => Ok(ParsedRawExtra::Extra {
|
"project" => Ok(ParsedRawExtra::Project { package }),
|
||||||
package,
|
"extra" | "group" => {
|
||||||
extra: tail,
|
if !tail.starts_with('-') {
|
||||||
}),
|
return Err(mkerr(
|
||||||
"group" => Ok(ParsedRawExtra::Group {
|
raw_extra,
|
||||||
package,
|
format!("expected `-` after package name `{package}`"),
|
||||||
group: tail,
|
));
|
||||||
}),
|
}
|
||||||
|
let tail = &tail[1..];
|
||||||
|
if kind == "extra" {
|
||||||
|
Ok(ParsedRawExtra::Extra {
|
||||||
|
package,
|
||||||
|
extra: tail,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(ParsedRawExtra::Group {
|
||||||
|
package,
|
||||||
|
group: tail,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => Err(mkerr(
|
_ => Err(mkerr(
|
||||||
raw_extra,
|
raw_extra,
|
||||||
format!("unrecognized kind `{kind}` (must be `extra` or `group`)"),
|
format!("unrecognized kind `{kind}` (must be `extra` or `group`)"),
|
||||||
|
|
@ -608,6 +670,7 @@ impl<'a> ParsedRawExtra<'a> {
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
match self {
|
match self {
|
||||||
|
Self::Project { .. } => Ok(ConflictItem::from(package)),
|
||||||
Self::Extra { extra, .. } => {
|
Self::Extra { extra, .. } => {
|
||||||
let extra = ExtraName::from_str(extra).map_err(|name_error| {
|
let extra = ExtraName::from_str(extra).map_err(|name_error| {
|
||||||
ResolveError::InvalidValueInConflictMarker {
|
ResolveError::InvalidValueInConflictMarker {
|
||||||
|
|
@ -631,6 +694,7 @@ impl<'a> ParsedRawExtra<'a> {
|
||||||
|
|
||||||
fn package(&self) -> &'a str {
|
fn package(&self) -> &'a str {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Project { package, .. } => package,
|
||||||
Self::Extra { package, .. } => package,
|
Self::Extra { package, .. } => package,
|
||||||
Self::Group { package, .. } => package,
|
Self::Group { package, .. } => package,
|
||||||
}
|
}
|
||||||
|
|
@ -719,6 +783,26 @@ pub(crate) fn resolve_conflicts(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search for the conflict item as a project.
|
||||||
|
if conflict_item.extra().is_none() && conflict_item.group().is_none() {
|
||||||
|
let package = conflict_item.package();
|
||||||
|
let encoded = encode_project(package);
|
||||||
|
if encoded == *name {
|
||||||
|
match operator {
|
||||||
|
ExtraOperator::Equal => {
|
||||||
|
or.and(*conflict_marker);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ExtraOperator::NotEqual => {
|
||||||
|
or.and(conflict_marker.negate());
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we didn't find the marker in the list of known conflicts, assume it's always
|
// If we didn't find the marker in the list of known conflicts, assume it's always
|
||||||
|
|
|
||||||
|
|
@ -210,9 +210,6 @@ pub(crate) async fn export(
|
||||||
Err(err) => return Err(err.into()),
|
Err(err) => return Err(err.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validate that the set of requested extras and development groups are compatible.
|
|
||||||
detect_conflicts(&lock, &extras, &groups)?;
|
|
||||||
|
|
||||||
// Identify the installation target.
|
// Identify the installation target.
|
||||||
let target = match &target {
|
let target = match &target {
|
||||||
ExportTarget::Project(VirtualProject::Project(project)) => {
|
ExportTarget::Project(VirtualProject::Project(project)) => {
|
||||||
|
|
@ -262,6 +259,9 @@ pub(crate) async fn export(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Validate that the set of requested extras and development groups are compatible.
|
||||||
|
detect_conflicts(&target, &extras, &groups)?;
|
||||||
|
|
||||||
// Validate that the set of requested extras and development groups are defined in the lockfile.
|
// Validate that the set of requested extras and development groups are defined in the lockfile.
|
||||||
target.validate_extras(&extras)?;
|
target.validate_extras(&extras)?;
|
||||||
target.validate_groups(&groups)?;
|
target.validate_groups(&groups)?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
@ -7,7 +8,7 @@ use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use uv_configuration::{Constraints, DependencyGroupsWithDefaults, ExtrasSpecification};
|
use uv_configuration::{Constraints, DependencyGroupsWithDefaults, ExtrasSpecification};
|
||||||
use uv_distribution_types::Index;
|
use uv_distribution_types::Index;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
use uv_pypi_types::{DependencyGroupSpecifier, LenientRequirement, VerbatimParsedUrl};
|
use uv_pypi_types::{DependencyGroupSpecifier, LenientRequirement, VerbatimParsedUrl};
|
||||||
use uv_resolver::{Installable, Lock, Package};
|
use uv_resolver::{Installable, Lock, Package};
|
||||||
use uv_scripts::Pep723Script;
|
use uv_scripts::Pep723Script;
|
||||||
|
|
@ -369,4 +370,107 @@ impl<'lock> InstallTarget<'lock> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the names of all packages in the workspace that will be installed.
|
||||||
|
///
|
||||||
|
/// Note this only includes workspace members.
|
||||||
|
pub(crate) fn packages(
|
||||||
|
&self,
|
||||||
|
extras: &ExtrasSpecification,
|
||||||
|
groups: &DependencyGroupsWithDefaults,
|
||||||
|
) -> BTreeSet<&PackageName> {
|
||||||
|
match self {
|
||||||
|
Self::Project { name, lock, .. } => {
|
||||||
|
// Collect the packages by name for efficient lookup
|
||||||
|
let packages = lock
|
||||||
|
.packages()
|
||||||
|
.iter()
|
||||||
|
.map(|p| (p.name(), p))
|
||||||
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
|
||||||
|
// We'll include the project itself
|
||||||
|
let mut required_members = BTreeSet::new();
|
||||||
|
required_members.insert(*name);
|
||||||
|
|
||||||
|
// Find all workspace member dependencies recursively
|
||||||
|
let mut queue: VecDeque<(&PackageName, Option<&ExtraName>)> = VecDeque::new();
|
||||||
|
let mut seen: FxHashSet<(&PackageName, Option<&ExtraName>)> = FxHashSet::default();
|
||||||
|
|
||||||
|
let Some(root_package) = packages.get(name) else {
|
||||||
|
return required_members;
|
||||||
|
};
|
||||||
|
|
||||||
|
if groups.prod() {
|
||||||
|
// Add the root package
|
||||||
|
queue.push_back((name, None));
|
||||||
|
seen.insert((name, None));
|
||||||
|
|
||||||
|
// Add explicitly activated extras for the root package
|
||||||
|
for extra in extras.extra_names(root_package.optional_dependencies().keys()) {
|
||||||
|
if seen.insert((name, Some(extra))) {
|
||||||
|
queue.push_back((name, Some(extra)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add activated dependency groups for the root package
|
||||||
|
for (group_name, dependencies) in root_package.resolved_dependency_groups() {
|
||||||
|
if !groups.contains(group_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for dependency in dependencies {
|
||||||
|
let name = dependency.package_name();
|
||||||
|
queue.push_back((name, None));
|
||||||
|
for extra in dependency.extra() {
|
||||||
|
queue.push_back((name, Some(extra)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((pkg_name, extra)) = queue.pop_front() {
|
||||||
|
if lock.members().contains(pkg_name) {
|
||||||
|
required_members.insert(pkg_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(package) = packages.get(pkg_name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(dependencies) = extra
|
||||||
|
.map(|extra_name| {
|
||||||
|
package
|
||||||
|
.optional_dependencies()
|
||||||
|
.get(extra_name)
|
||||||
|
.map(Vec::as_slice)
|
||||||
|
})
|
||||||
|
.unwrap_or(Some(package.dependencies()))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
for dependency in dependencies {
|
||||||
|
let name = dependency.package_name();
|
||||||
|
if seen.insert((name, None)) {
|
||||||
|
queue.push_back((name, None));
|
||||||
|
}
|
||||||
|
for extra in dependency.extra() {
|
||||||
|
if seen.insert((name, Some(extra))) {
|
||||||
|
queue.push_back((name, Some(extra)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required_members
|
||||||
|
}
|
||||||
|
Self::Workspace { lock, .. } | Self::NonProjectWorkspace { lock, .. } => {
|
||||||
|
// Return all workspace members
|
||||||
|
lock.members().iter().collect()
|
||||||
|
}
|
||||||
|
Self::Script { .. } => {
|
||||||
|
// Scripts don't have workspace members
|
||||||
|
BTreeSet::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ use uv_distribution_types::{
|
||||||
use uv_git::ResolvedRepositoryReference;
|
use uv_git::ResolvedRepositoryReference;
|
||||||
use uv_normalize::{GroupName, PackageName};
|
use uv_normalize::{GroupName, PackageName};
|
||||||
use uv_pep440::Version;
|
use uv_pep440::Version;
|
||||||
use uv_pypi_types::{Conflicts, SupportedEnvironments};
|
use uv_pypi_types::{ConflictKind, Conflicts, SupportedEnvironments};
|
||||||
use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
|
use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
|
||||||
use uv_requirements::ExtrasResolver;
|
use uv_requirements::ExtrasResolver;
|
||||||
use uv_requirements::upgrade::{LockedRequirements, read_lock_requirements};
|
use uv_requirements::upgrade::{LockedRequirements, read_lock_requirements};
|
||||||
|
|
@ -487,6 +487,19 @@ async fn do_lock(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if any conflicts contain project-level conflicts
|
||||||
|
if !preview.is_enabled(PreviewFeatures::PACKAGE_CONFLICTS)
|
||||||
|
&& conflicts.iter().any(|set| {
|
||||||
|
set.iter()
|
||||||
|
.any(|item| matches!(item.kind(), ConflictKind::Project))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
warn_user_once!(
|
||||||
|
"Declaring conflicts for packages (`package = ...`) is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
|
||||||
|
PreviewFeatures::PACKAGE_CONFLICTS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Collect the list of supported environments.
|
// Collect the list of supported environments.
|
||||||
let environments = {
|
let environments = {
|
||||||
let environments = target.environments();
|
let environments = target.environments();
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ use uv_installer::{SatisfiesResult, SitePackages};
|
||||||
use uv_normalize::{DEV_DEPENDENCIES, DefaultGroups, ExtraName, GroupName, PackageName};
|
use uv_normalize::{DEV_DEPENDENCIES, DefaultGroups, ExtraName, GroupName, PackageName};
|
||||||
use uv_pep440::{TildeVersionSpecifier, Version, VersionSpecifiers};
|
use uv_pep440::{TildeVersionSpecifier, Version, VersionSpecifiers};
|
||||||
use uv_pep508::MarkerTreeContents;
|
use uv_pep508::MarkerTreeContents;
|
||||||
use uv_pypi_types::{ConflictPackage, ConflictSet, Conflicts};
|
use uv_pypi_types::{ConflictItem, ConflictKind, ConflictSet, Conflicts};
|
||||||
use uv_python::{
|
use uv_python::{
|
||||||
EnvironmentPreference, Interpreter, InvalidEnvironmentKind, PythonDownloads, PythonEnvironment,
|
EnvironmentPreference, Interpreter, InvalidEnvironmentKind, PythonDownloads, PythonEnvironment,
|
||||||
PythonInstallation, PythonPreference, PythonRequest, PythonSource, PythonVariant,
|
PythonInstallation, PythonPreference, PythonRequest, PythonSource, PythonVariant,
|
||||||
|
|
@ -36,8 +36,8 @@ use uv_python::{
|
||||||
use uv_requirements::upgrade::{LockedRequirements, read_lock_requirements};
|
use uv_requirements::upgrade::{LockedRequirements, read_lock_requirements};
|
||||||
use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification};
|
use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification};
|
||||||
use uv_resolver::{
|
use uv_resolver::{
|
||||||
FlatIndex, Lock, OptionsBuilder, Preference, PythonRequirement, ResolverEnvironment,
|
FlatIndex, Installable, Lock, OptionsBuilder, Preference, PythonRequirement,
|
||||||
ResolverOutput,
|
ResolverEnvironment, ResolverOutput,
|
||||||
};
|
};
|
||||||
use uv_scripts::Pep723ItemRef;
|
use uv_scripts::Pep723ItemRef;
|
||||||
use uv_settings::PythonInstallMirrors;
|
use uv_settings::PythonInstallMirrors;
|
||||||
|
|
@ -52,6 +52,7 @@ use uv_workspace::{RequiresPythonSources, Workspace, WorkspaceCache};
|
||||||
|
|
||||||
use crate::commands::pip::loggers::{InstallLogger, ResolveLogger};
|
use crate::commands::pip::loggers::{InstallLogger, ResolveLogger};
|
||||||
use crate::commands::pip::operations::{Changelog, Modifications};
|
use crate::commands::pip::operations::{Changelog, Modifications};
|
||||||
|
use crate::commands::project::install_target::InstallTarget;
|
||||||
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
|
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
|
||||||
use crate::commands::{capitalize, conjunction, pip};
|
use crate::commands::{capitalize, conjunction, pip};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
@ -274,7 +275,7 @@ pub(crate) struct ConflictError {
|
||||||
/// The set from which the conflict was derived.
|
/// The set from which the conflict was derived.
|
||||||
pub(crate) set: ConflictSet,
|
pub(crate) set: ConflictSet,
|
||||||
/// The items from the set that were enabled, and thus create the conflict.
|
/// The items from the set that were enabled, and thus create the conflict.
|
||||||
pub(crate) conflicts: Vec<ConflictPackage>,
|
pub(crate) conflicts: Vec<ConflictItem>,
|
||||||
/// Enabled dependency groups with defaults applied.
|
/// Enabled dependency groups with defaults applied.
|
||||||
pub(crate) groups: DependencyGroupsWithDefaults,
|
pub(crate) groups: DependencyGroupsWithDefaults,
|
||||||
}
|
}
|
||||||
|
|
@ -285,9 +286,10 @@ impl std::fmt::Display for ConflictError {
|
||||||
let set = self
|
let set = self
|
||||||
.set
|
.set
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| match item.conflict() {
|
.map(|item| match item.kind() {
|
||||||
ConflictPackage::Extra(extra) => format!("`{}[{}]`", item.package(), extra),
|
ConflictKind::Project => format!("{}", item.package()),
|
||||||
ConflictPackage::Group(group) => format!("`{}:{}`", item.package(), group),
|
ConflictKind::Extra(extra) => format!("`{}[{}]`", item.package(), extra),
|
||||||
|
ConflictKind::Group(group) => format!("`{}:{}`", item.package(), group),
|
||||||
})
|
})
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
|
|
@ -295,7 +297,7 @@ impl std::fmt::Display for ConflictError {
|
||||||
if self
|
if self
|
||||||
.conflicts
|
.conflicts
|
||||||
.iter()
|
.iter()
|
||||||
.all(|conflict| matches!(conflict, ConflictPackage::Extra(..)))
|
.all(|conflict| matches!(conflict.kind(), ConflictKind::Extra(..)))
|
||||||
{
|
{
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
|
|
@ -303,9 +305,9 @@ impl std::fmt::Display for ConflictError {
|
||||||
conjunction(
|
conjunction(
|
||||||
self.conflicts
|
self.conflicts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|conflict| match conflict {
|
.map(|conflict| match conflict.kind() {
|
||||||
ConflictPackage::Extra(extra) => format!("`{extra}`"),
|
ConflictKind::Extra(extra) => format!("`{extra}`"),
|
||||||
ConflictPackage::Group(..) => unreachable!(),
|
ConflictKind::Group(..) | ConflictKind::Project => unreachable!(),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
)
|
)
|
||||||
|
|
@ -313,7 +315,7 @@ impl std::fmt::Display for ConflictError {
|
||||||
} else if self
|
} else if self
|
||||||
.conflicts
|
.conflicts
|
||||||
.iter()
|
.iter()
|
||||||
.all(|conflict| matches!(conflict, ConflictPackage::Group(..)))
|
.all(|conflict| matches!(conflict.kind(), ConflictKind::Group(..)))
|
||||||
{
|
{
|
||||||
let conflict_source = if self.set.is_inferred_conflict() {
|
let conflict_source = if self.set.is_inferred_conflict() {
|
||||||
"transitively inferred"
|
"transitively inferred"
|
||||||
|
|
@ -326,12 +328,12 @@ impl std::fmt::Display for ConflictError {
|
||||||
conjunction(
|
conjunction(
|
||||||
self.conflicts
|
self.conflicts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|conflict| match conflict {
|
.map(|conflict| match conflict.kind() {
|
||||||
ConflictPackage::Group(group)
|
ConflictKind::Group(group)
|
||||||
if self.groups.contains_because_default(group) =>
|
if self.groups.contains_because_default(group) =>
|
||||||
format!("`{group}` (enabled by default)"),
|
format!("`{group}` (enabled by default)"),
|
||||||
ConflictPackage::Group(group) => format!("`{group}`"),
|
ConflictKind::Group(group) => format!("`{group}`"),
|
||||||
ConflictPackage::Extra(..) => unreachable!(),
|
ConflictKind::Extra(..) | ConflictKind::Project => unreachable!(),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
)
|
)
|
||||||
|
|
@ -345,14 +347,17 @@ impl std::fmt::Display for ConflictError {
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, conflict)| {
|
.map(|(i, conflict)| {
|
||||||
let conflict = match conflict {
|
let conflict = match conflict.kind() {
|
||||||
ConflictPackage::Extra(extra) => format!("extra `{extra}`"),
|
ConflictKind::Project => {
|
||||||
ConflictPackage::Group(group)
|
format!("package `{}`", conflict.package())
|
||||||
|
}
|
||||||
|
ConflictKind::Extra(extra) => format!("extra `{extra}`"),
|
||||||
|
ConflictKind::Group(group)
|
||||||
if self.groups.contains_because_default(group) =>
|
if self.groups.contains_because_default(group) =>
|
||||||
{
|
{
|
||||||
format!("group `{group}` (enabled by default)")
|
format!("group `{group}` (enabled by default)")
|
||||||
}
|
}
|
||||||
ConflictPackage::Group(group) => format!("group `{group}`"),
|
ConflictKind::Group(group) => format!("group `{group}`"),
|
||||||
};
|
};
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
capitalize(&conflict)
|
capitalize(&conflict)
|
||||||
|
|
@ -2526,31 +2531,33 @@ pub(crate) fn default_dependency_groups(
|
||||||
/// are declared as conflicting.
|
/// are declared as conflicting.
|
||||||
#[allow(clippy::result_large_err)]
|
#[allow(clippy::result_large_err)]
|
||||||
pub(crate) fn detect_conflicts(
|
pub(crate) fn detect_conflicts(
|
||||||
lock: &Lock,
|
target: &InstallTarget,
|
||||||
extras: &ExtrasSpecification,
|
extras: &ExtrasSpecification,
|
||||||
groups: &DependencyGroupsWithDefaults,
|
groups: &DependencyGroupsWithDefaults,
|
||||||
) -> Result<(), ProjectError> {
|
) -> Result<(), ProjectError> {
|
||||||
// Note that we need to collect all extras and groups that match in
|
// Validate that we aren't trying to install extras or groups that
|
||||||
// a particular set, since extras can be declared as conflicting with
|
// are declared as conflicting. Note that we need to collect all
|
||||||
// groups. So if extra `x` and group `g` are declared as conflicting,
|
// extras and groups that match in a particular set, since extras
|
||||||
// then enabling both of those should result in an error.
|
// can be declared as conflicting with groups. So if extra `x` and
|
||||||
|
// group `g` are declared as conflicting, then enabling both of
|
||||||
|
// those should result in an error.
|
||||||
|
let lock = target.lock();
|
||||||
|
let packages = target.packages(extras, groups);
|
||||||
let conflicts = lock.conflicts();
|
let conflicts = lock.conflicts();
|
||||||
for set in conflicts.iter() {
|
for set in conflicts.iter() {
|
||||||
let mut conflicts: Vec<ConflictPackage> = vec![];
|
let mut conflicts: Vec<ConflictItem> = vec![];
|
||||||
for item in set.iter() {
|
for item in set.iter() {
|
||||||
if item
|
if !packages.contains(item.package()) {
|
||||||
.extra()
|
// Ignore items that are not in the install targets
|
||||||
.map(|extra| extras.contains(extra))
|
continue;
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
conflicts.push(item.conflict().clone());
|
|
||||||
}
|
}
|
||||||
if item
|
let is_conflicting = match item.kind() {
|
||||||
.group()
|
ConflictKind::Project => groups.prod(),
|
||||||
.map(|group| groups.contains(group))
|
ConflictKind::Extra(extra) => extras.contains(extra),
|
||||||
.unwrap_or(false)
|
ConflictKind::Group(group1) => groups.contains(group1),
|
||||||
{
|
};
|
||||||
conflicts.push(item.conflict().clone());
|
if is_conflicting {
|
||||||
|
conflicts.push(item.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if conflicts.len() >= 2 {
|
if conflicts.len() >= 2 {
|
||||||
|
|
|
||||||
|
|
@ -663,7 +663,7 @@ pub(super) async fn do_sync(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that the set of requested extras and development groups are compatible.
|
// Validate that the set of requested extras and development groups are compatible.
|
||||||
detect_conflicts(target.lock(), extras, groups)?;
|
detect_conflicts(&target, extras, groups)?;
|
||||||
|
|
||||||
// Validate that the set of requested extras and development groups are defined in the lockfile.
|
// Validate that the set of requested extras and development groups are defined in the lockfile.
|
||||||
target.validate_extras(extras)?;
|
target.validate_extras(extras)?;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1049,24 +1049,24 @@ fn extra_unconditional() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 6 packages in [TIME]
|
Resolved 6 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
// This should error since we're enabling two conflicting extras.
|
// This should error since we're enabling two conflicting extras.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: Found conflicting extras `proxy1[extra1]` and `proxy1[extra2]` enabled simultaneously
|
error: Found conflicting extras `proxy1[extra1]` and `proxy1[extra2]` enabled simultaneously
|
||||||
"###);
|
");
|
||||||
|
|
||||||
root_pyproject_toml.write_str(
|
root_pyproject_toml.write_str(
|
||||||
r#"
|
r#"
|
||||||
|
|
@ -1085,14 +1085,14 @@ fn extra_unconditional() -> Result<()> {
|
||||||
proxy1 = { workspace = true }
|
proxy1 = { workspace = true }
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 6 packages in [TIME]
|
Resolved 6 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
// This is fine because we are only enabling one
|
// This is fine because we are only enabling one
|
||||||
// extra, and thus, there is no conflict.
|
// extra, and thus, there is no conflict.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r"
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r"
|
||||||
|
|
@ -1127,17 +1127,17 @@ fn extra_unconditional() -> Result<()> {
|
||||||
proxy1 = { workspace = true }
|
proxy1 = { workspace = true }
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 6 packages in [TIME]
|
Resolved 6 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
// This is fine because we are only enabling one
|
// This is fine because we are only enabling one
|
||||||
// extra, and thus, there is no conflict.
|
// extra, and thus, there is no conflict.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -1148,7 +1148,7 @@ fn extra_unconditional() -> Result<()> {
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- anyio==4.1.0
|
- anyio==4.1.0
|
||||||
+ anyio==4.2.0
|
+ anyio==4.2.0
|
||||||
"###);
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -1203,14 +1203,14 @@ fn extra_unconditional_non_conflicting() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 5 packages in [TIME]
|
Resolved 5 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
// This *should* install `anyio==4.1.0`, but when this
|
// This *should* install `anyio==4.1.0`, but when this
|
||||||
// test was initially written, it didn't. This was because
|
// test was initially written, it didn't. This was because
|
||||||
|
|
@ -1426,26 +1426,26 @@ fn extra_unconditional_non_local_conflict() -> Result<()> {
|
||||||
// that can never be installed! Namely, because two different
|
// that can never be installed! Namely, because two different
|
||||||
// conflicting extras are enabled unconditionally in all
|
// conflicting extras are enabled unconditionally in all
|
||||||
// configurations.
|
// configurations.
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 6 packages in [TIME]
|
Resolved 6 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
// This should fail. If it doesn't and we generated a lock
|
// This should fail. If it doesn't and we generated a lock
|
||||||
// file above, then this will likely result in the installation
|
// file above, then this will likely result in the installation
|
||||||
// of two different versions of the same package.
|
// of two different versions of the same package.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: Found conflicting extras `c[x1]` and `c[x2]` enabled simultaneously
|
error: Found conflicting extras `c[x1]` and `c[x2]` enabled simultaneously
|
||||||
"###);
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -1955,14 +1955,14 @@ fn group_basic() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 3 packages in [TIME]
|
Resolved 3 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let lock = context.read("uv.lock");
|
let lock = context.read("uv.lock");
|
||||||
|
|
||||||
|
|
@ -2110,14 +2110,14 @@ fn group_default() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 3 packages in [TIME]
|
Resolved 3 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let lock = context.read("uv.lock");
|
let lock = context.read("uv.lock");
|
||||||
|
|
||||||
|
|
@ -2642,14 +2642,14 @@ fn multiple_sources_index_disjoint_groups() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||||
|
|
||||||
|
|
@ -3125,7 +3125,7 @@ fn non_optional_dependency_extra() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -3135,7 +3135,7 @@ fn non_optional_dependency_extra() -> Result<()> {
|
||||||
Prepared 1 package in [TIME]
|
Prepared 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -3172,7 +3172,7 @@ fn non_optional_dependency_group() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -3182,7 +3182,7 @@ fn non_optional_dependency_group() -> Result<()> {
|
||||||
Prepared 1 package in [TIME]
|
Prepared 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -3222,7 +3222,7 @@ fn non_optional_dependency_mixed() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -3232,7 +3232,7 @@ fn non_optional_dependency_mixed() -> Result<()> {
|
||||||
Prepared 1 package in [TIME]
|
Prepared 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -3422,7 +3422,7 @@ fn shared_optional_dependency_group1() -> Result<()> {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// This shouldn't install two versions of `idna`, only one, `idna==3.5`.
|
// This shouldn't install two versions of `idna`, only one, `idna==3.5`.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--group=baz").arg("--group=foo"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--group=baz").arg("--group=foo"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -3434,7 +3434,7 @@ fn shared_optional_dependency_group1() -> Result<()> {
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.5
|
+ idna==3.5
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
|
|
@ -3849,7 +3849,7 @@ fn shared_optional_dependency_group2() -> Result<()> {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// This shouldn't install two versions of `idna`, only one, `idna==3.5`.
|
// This shouldn't install two versions of `idna`, only one, `idna==3.5`.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--group=bar"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--group=bar"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -3861,7 +3861,7 @@ fn shared_optional_dependency_group2() -> Result<()> {
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
|
|
@ -4139,7 +4139,7 @@ fn shared_dependency_extra() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -4151,7 +4151,7 @@ fn shared_dependency_extra() -> Result<()> {
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
|
|
@ -4240,7 +4240,7 @@ fn shared_dependency_extra() -> Result<()> {
|
||||||
|
|
||||||
// This shouldn't install two versions of `idna`, only one, `idna==3.5`.
|
// This shouldn't install two versions of `idna`, only one, `idna==3.5`.
|
||||||
// So this should remove `idna==3.6` installed above.
|
// So this should remove `idna==3.6` installed above.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--extra=foo"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--extra=foo"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -4252,9 +4252,9 @@ fn shared_dependency_extra() -> Result<()> {
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- idna==3.6
|
- idna==3.6
|
||||||
+ idna==3.5
|
+ idna==3.5
|
||||||
"###);
|
");
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--extra=bar"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--extra=bar"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -4265,9 +4265,9 @@ fn shared_dependency_extra() -> Result<()> {
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- idna==3.5
|
- idna==3.5
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
"###);
|
");
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -4275,7 +4275,7 @@ fn shared_dependency_extra() -> Result<()> {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 5 packages in [TIME]
|
Resolved 5 packages in [TIME]
|
||||||
Audited 3 packages in [TIME]
|
Audited 3 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -4314,7 +4314,7 @@ fn shared_dependency_group() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -4326,7 +4326,7 @@ fn shared_dependency_group() -> Result<()> {
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
|
|
@ -4490,7 +4490,7 @@ fn shared_dependency_mixed() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -4502,7 +4502,7 @@ fn shared_dependency_mixed() -> Result<()> {
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
|
|
@ -4595,7 +4595,7 @@ fn shared_dependency_mixed() -> Result<()> {
|
||||||
|
|
||||||
// This shouldn't install two versions of `idna`, only one, `idna==3.5`.
|
// This shouldn't install two versions of `idna`, only one, `idna==3.5`.
|
||||||
// So this should remove `idna==3.6` installed above.
|
// So this should remove `idna==3.6` installed above.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--extra=foo"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--extra=foo"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -4607,9 +4607,9 @@ fn shared_dependency_mixed() -> Result<()> {
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- idna==3.6
|
- idna==3.6
|
||||||
+ idna==3.5
|
+ idna==3.5
|
||||||
"###);
|
");
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--group=bar"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--group=bar"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -4620,9 +4620,9 @@ fn shared_dependency_mixed() -> Result<()> {
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- idna==3.5
|
- idna==3.5
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
"###);
|
");
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -4630,7 +4630,7 @@ fn shared_dependency_mixed() -> Result<()> {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 5 packages in [TIME]
|
Resolved 5 packages in [TIME]
|
||||||
Audited 3 packages in [TIME]
|
Audited 3 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7720,7 +7720,7 @@ fn preview_features() {
|
||||||
show_settings: true,
|
show_settings: true,
|
||||||
preview: Preview {
|
preview: Preview {
|
||||||
flags: PreviewFeatures(
|
flags: PreviewFeatures(
|
||||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | EXTRA_BUILD_DEPENDENCIES,
|
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
python_preference: Managed,
|
python_preference: Managed,
|
||||||
|
|
@ -7946,7 +7946,7 @@ fn preview_features() {
|
||||||
show_settings: true,
|
show_settings: true,
|
||||||
preview: Preview {
|
preview: Preview {
|
||||||
flags: PreviewFeatures(
|
flags: PreviewFeatures(
|
||||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | EXTRA_BUILD_DEPENDENCIES,
|
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
python_preference: Managed,
|
python_preference: Managed,
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ The following preview features are available:
|
||||||
- `add-bounds`: Allows configuring the
|
- `add-bounds`: Allows configuring the
|
||||||
[default bounds for `uv add`](../reference/settings.md#add-bounds) invocations.
|
[default bounds for `uv add`](../reference/settings.md#add-bounds) invocations.
|
||||||
- `json-output`: Allows `--output-format json` for various uv commands.
|
- `json-output`: Allows `--output-format json` for various uv commands.
|
||||||
|
- `package-conflicts`: Allows defining workspace conflicts at the package level.
|
||||||
- `pylock`: Allows installing from `pylock.toml` files.
|
- `pylock`: Allows installing from `pylock.toml` files.
|
||||||
- `python-install-default`: Allows
|
- `python-install-default`: Allows
|
||||||
[installing `python` and `python3` executables](./python-versions.md#installing-python-executables).
|
[installing `python` and `python3` executables](./python-versions.md#installing-python-executables).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue