*: update "conflicting groups" terminology everywhere else

This commit is contained in:
Andrew Gallant 2024-11-13 10:15:47 -05:00 committed by Andrew Gallant
parent 19a044d4db
commit bb78e00a87
28 changed files with 134 additions and 142 deletions

View file

@ -163,7 +163,7 @@ mod resolver {
let options = OptionsBuilder::new().exclude_newer(exclude_newer).build(); let options = OptionsBuilder::new().exclude_newer(exclude_newer).build();
let sources = SourceStrategy::default(); let sources = SourceStrategy::default();
let dependency_metadata = DependencyMetadata::default(); let dependency_metadata = DependencyMetadata::default();
let conflicting_groups = Conflicts::empty(); let conflicts = Conflicts::empty();
let python_requirement = if universal { let python_requirement = if universal {
PythonRequirement::from_requires_python( PythonRequirement::from_requires_python(
@ -209,7 +209,7 @@ mod resolver {
options, options,
&python_requirement, &python_requirement,
markers, markers,
conflicting_groups, conflicts,
Some(&TAGS), Some(&TAGS),
&flat_index, &flat_index,
&index, &index,

View file

@ -1,6 +1,6 @@
use uv_normalize::{ExtraName, PackageName}; use uv_normalize::{ExtraName, PackageName};
/// A list of conflicting groups pre-defined by an end user. /// A list of conflicting sets of extras/groups pre-defined by an end user.
/// ///
/// This is useful to force the resolver to fork according to extras that have /// This is useful to force the resolver to fork according to extras that have
/// unavoidable conflicts with each other. (The alternative is that resolution /// unavoidable conflicts with each other. (The alternative is that resolution
@ -11,7 +11,7 @@ use uv_normalize::{ExtraName, PackageName};
pub struct Conflicts(Vec<ConflictSet>); pub struct Conflicts(Vec<ConflictSet>);
impl Conflicts { impl Conflicts {
/// Returns no conflicting groups. /// Returns no conflicts.
/// ///
/// This results in no effect on resolution. /// This results in no effect on resolution.
pub fn empty() -> Conflicts { pub fn empty() -> Conflicts {
@ -31,7 +31,7 @@ impl Conflicts {
/// Returns true if these conflicts contain any set that contains the given /// Returns true if these conflicts contain any set that contains the given
/// package and extra name pair. /// package and extra name pair.
pub fn contains(&self, package: &PackageName, extra: &ExtraName) -> bool { pub fn contains(&self, package: &PackageName, extra: &ExtraName) -> bool {
self.iter().any(|groups| groups.contains(package, extra)) self.iter().any(|set| set.contains(package, extra))
} }
/// Returns true if there are no conflicts. /// Returns true if there are no conflicts.
@ -78,7 +78,7 @@ impl ConflictSet {
/// extra name pair. /// extra name pair.
pub fn contains(&self, package: &PackageName, extra: &ExtraName) -> bool { pub fn contains(&self, package: &PackageName, extra: &ExtraName) -> bool {
self.iter() self.iter()
.any(|group| group.package() == package && group.extra() == extra) .any(|set| set.package() == package && set.extra() == extra)
} }
} }
@ -87,8 +87,8 @@ impl<'de> serde::Deserialize<'de> for ConflictSet {
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let groups = Vec::<ConflictItem>::deserialize(deserializer)?; let set = Vec::<ConflictItem>::deserialize(deserializer)?;
Self::try_from(groups).map_err(serde::de::Error::custom) Self::try_from(set).map_err(serde::de::Error::custom)
} }
} }
@ -193,10 +193,10 @@ impl<'a> From<(&'a PackageName, &'a ExtraName)> for ConflictItemRef<'a> {
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum ConflictError { pub enum ConflictError {
/// An error for when there are zero conflicting items. /// An error for when there are zero conflicting items.
#[error("Each set of conflicting groups must have at least two entries, but found none")] #[error("Each set of conflicts must have at least two entries, but found none")]
ZeroItems, ZeroItems,
/// An error for when there is one conflicting items. /// An error for when there is one conflicting items.
#[error("Each set of conflicting groups must have at least two entries, but found only one")] #[error("Each set of conflicts must have at least two entries, but found only one")]
OneItem, OneItem,
} }
@ -222,7 +222,7 @@ impl SchemaConflicts {
/// If a conflict has an explicit package name (written by the end user), /// If a conflict has an explicit package name (written by the end user),
/// then that takes precedence over the given package name, which is only /// then that takes precedence over the given package name, which is only
/// used when there is no explicit package name written. /// used when there is no explicit package name written.
pub fn to_conflicting_with_package_name(&self, package: &PackageName) -> Conflicts { pub fn to_conflicts_with_package_name(&self, package: &PackageName) -> Conflicts {
let mut conflicting = Conflicts::empty(); let mut conflicting = Conflicts::empty();
for tool_uv_set in &self.0 { for tool_uv_set in &self.0 {
let mut set = vec![]; let mut set = vec![];

View file

@ -82,7 +82,7 @@ pub struct Lock {
/// forks in the lockfile so we can recreate them in subsequent resolutions. /// forks in the lockfile so we can recreate them in subsequent resolutions.
fork_markers: Vec<MarkerTree>, fork_markers: Vec<MarkerTree>,
/// The conflicting groups/extras specified by the user. /// The conflicting groups/extras specified by the user.
conflicting_groups: Conflicts, conflicts: Conflicts,
/// The list of supported environments specified by the user. /// The list of supported environments specified by the user.
supported_environments: Vec<MarkerTree>, supported_environments: Vec<MarkerTree>,
/// The range of supported Python versions. /// The range of supported Python versions.
@ -315,7 +315,7 @@ impl Lock {
requires_python: RequiresPython, requires_python: RequiresPython,
options: ResolverOptions, options: ResolverOptions,
manifest: ResolverManifest, manifest: ResolverManifest,
conflicting_groups: Conflicts, conflicts: Conflicts,
supported_environments: Vec<MarkerTree>, supported_environments: Vec<MarkerTree>,
fork_markers: Vec<MarkerTree>, fork_markers: Vec<MarkerTree>,
) -> Result<Self, LockError> { ) -> Result<Self, LockError> {
@ -465,7 +465,7 @@ impl Lock {
let lock = Self { let lock = Self {
version, version,
fork_markers, fork_markers,
conflicting_groups, conflicts,
supported_environments, supported_environments,
requires_python, requires_python,
options, options,
@ -485,8 +485,8 @@ impl Lock {
/// Record the conflicting groups that were used to generate this lock. /// Record the conflicting groups that were used to generate this lock.
#[must_use] #[must_use]
pub fn with_conflicting_groups(mut self, conflicting_groups: Conflicts) -> Self { pub fn with_conflicts(mut self, conflicts: Conflicts) -> Self {
self.conflicting_groups = conflicting_groups; self.conflicts = conflicts;
self self
} }
@ -550,8 +550,8 @@ impl Lock {
} }
/// Returns the conflicting groups that were used to generate this lock. /// Returns the conflicting groups that were used to generate this lock.
pub fn conflicting_groups(&self) -> &Conflicts { pub fn conflicts(&self) -> &Conflicts {
&self.conflicting_groups &self.conflicts
} }
/// Returns the supported environments that were used to generate this lock. /// Returns the supported environments that were used to generate this lock.
@ -632,9 +632,9 @@ impl Lock {
doc.insert("supported-markers", value(supported_environments)); doc.insert("supported-markers", value(supported_environments));
} }
if !self.conflicting_groups.is_empty() { if !self.conflicts.is_empty() {
let mut list = Array::new(); let mut list = Array::new();
for groups in self.conflicting_groups.iter() { for groups in self.conflicts.iter() {
list.push(each_element_on_its_line_array(groups.iter().map(|group| { list.push(each_element_on_its_line_array(groups.iter().map(|group| {
let mut table = InlineTable::new(); let mut table = InlineTable::new();
table.insert("package", Value::from(group.package().to_string())); table.insert("package", Value::from(group.package().to_string()));
@ -642,7 +642,7 @@ impl Lock {
table table
}))); })));
} }
doc.insert("conflicting-groups", value(list)); doc.insert("conflicts", value(list));
} }
// Write the settings that were used to generate the resolution. // Write the settings that were used to generate the resolution.
@ -1383,8 +1383,8 @@ struct LockWire {
fork_markers: Vec<SimplifiedMarkerTree>, fork_markers: Vec<SimplifiedMarkerTree>,
#[serde(rename = "supported-markers", default)] #[serde(rename = "supported-markers", default)]
supported_environments: Vec<SimplifiedMarkerTree>, supported_environments: Vec<SimplifiedMarkerTree>,
#[serde(rename = "conflicting-groups", default)] #[serde(rename = "conflicts", default)]
conflicting_groups: Option<Conflicts>, conflicts: Option<Conflicts>,
/// We discard the lockfile if these options match. /// We discard the lockfile if these options match.
#[serde(default)] #[serde(default)]
options: ResolverOptions, options: ResolverOptions,
@ -1436,7 +1436,7 @@ impl TryFrom<LockWire> for Lock {
wire.requires_python, wire.requires_python,
wire.options, wire.options,
wire.manifest, wire.manifest,
wire.conflicting_groups.unwrap_or_else(Conflicts::empty), wire.conflicts.unwrap_or_else(Conflicts::empty),
supported_environments, supported_environments,
fork_markers, fork_markers,
)?; )?;

View file

@ -6,7 +6,7 @@ Ok(
Lock { Lock {
version: 1, version: 1,
fork_markers: [], fork_markers: [],
conflicting_groups: ConflictingGroupList( conflicts: Conflicts(
[], [],
), ),
supported_environments: [], supported_environments: [],

View file

@ -6,7 +6,7 @@ Ok(
Lock { Lock {
version: 1, version: 1,
fork_markers: [], fork_markers: [],
conflicting_groups: ConflictingGroupList( conflicts: Conflicts(
[], [],
), ),
supported_environments: [], supported_environments: [],

View file

@ -6,7 +6,7 @@ Ok(
Lock { Lock {
version: 1, version: 1,
fork_markers: [], fork_markers: [],
conflicting_groups: ConflictingGroupList( conflicts: Conflicts(
[], [],
), ),
supported_environments: [], supported_environments: [],

View file

@ -6,7 +6,7 @@ Ok(
Lock { Lock {
version: 1, version: 1,
fork_markers: [], fork_markers: [],
conflicting_groups: ConflictingGroupList( conflicts: Conflicts(
[], [],
), ),
supported_environments: [], supported_environments: [],

View file

@ -6,7 +6,7 @@ Ok(
Lock { Lock {
version: 1, version: 1,
fork_markers: [], fork_markers: [],
conflicting_groups: ConflictingGroupList( conflicts: Conflicts(
[], [],
), ),
supported_environments: [], supported_environments: [],

View file

@ -6,7 +6,7 @@ Ok(
Lock { Lock {
version: 1, version: 1,
fork_markers: [], fork_markers: [],
conflicting_groups: ConflictingGroupList( conflicts: Conflicts(
[], [],
), ),
supported_environments: [], supported_environments: [],

View file

@ -6,7 +6,7 @@ Ok(
Lock { Lock {
version: 1, version: 1,
fork_markers: [], fork_markers: [],
conflicting_groups: ConflictingGroupList( conflicts: Conflicts(
[], [],
), ),
supported_environments: [], supported_environments: [],

View file

@ -6,7 +6,7 @@ Ok(
Lock { Lock {
version: 1, version: 1,
fork_markers: [], fork_markers: [],
conflicting_groups: ConflictingGroupList( conflicts: Conflicts(
[], [],
), ),
supported_environments: [], supported_environments: [],

View file

@ -6,7 +6,7 @@ Ok(
Lock { Lock {
version: 1, version: 1,
fork_markers: [], fork_markers: [],
conflicting_groups: ConflictingGroupList( conflicts: Conflicts(
[], [],
), ),
supported_environments: [], supported_environments: [],

View file

@ -6,7 +6,7 @@ Ok(
Lock { Lock {
version: 1, version: 1,
fork_markers: [], fork_markers: [],
conflicting_groups: ConflictingGroupList( conflicts: Conflicts(
[], [],
), ),
supported_environments: [], supported_environments: [],

View file

@ -190,7 +190,7 @@ impl PubGrubPackage {
/// ///
/// If this package can't possibly be classified as a conflicting group, /// If this package can't possibly be classified as a conflicting group,
/// then this returns `None`. /// then this returns `None`.
pub(crate) fn conflicting_group(&self) -> Option<ConflictItemRef<'_>> { pub(crate) fn conflicting_item(&self) -> Option<ConflictItemRef<'_>> {
let package = self.name_no_root()?; let package = self.name_no_root()?;
let extra = self.extra()?; let extra = self.extra()?;
Some(ConflictItemRef::from((package, extra))) Some(ConflictItemRef::from((package, extra)))

View file

@ -103,7 +103,7 @@ impl ResolutionGraph {
index: &InMemoryIndex, index: &InMemoryIndex,
git: &GitResolver, git: &GitResolver,
python: &PythonRequirement, python: &PythonRequirement,
conflicting_groups: &Conflicts, conflicts: &Conflicts,
resolution_strategy: &ResolutionStrategy, resolution_strategy: &ResolutionStrategy,
options: Options, options: Options,
) -> Result<Self, ResolveError> { ) -> Result<Self, ResolveError> {
@ -251,7 +251,7 @@ impl ResolutionGraph {
// the same time. At which point, uv will report an error, // the same time. At which point, uv will report an error,
// thereby sidestepping the possibility of installing different // thereby sidestepping the possibility of installing different
// versions of the same package into the same virtualenv. ---AG // versions of the same package into the same virtualenv. ---AG
if conflicting_groups.is_empty() { if conflicts.is_empty() {
#[allow(unused_mut, reason = "Used in debug_assertions below")] #[allow(unused_mut, reason = "Used in debug_assertions below")]
let mut conflicting = graph.find_conflicting_distributions(); let mut conflicting = graph.find_conflicting_distributions();
if !conflicting.is_empty() { if !conflicting.is_empty() {

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use uv_normalize::{ExtraName, PackageName}; use uv_normalize::{ExtraName, PackageName};
use uv_pep508::{MarkerEnvironment, MarkerTree}; use uv_pep508::{MarkerEnvironment, MarkerTree};
use uv_pypi_types::{ConflictItem, ConflictItemRef, Conflicts, ResolverMarkerEnvironment}; use uv_pypi_types::{ConflictItem, ConflictItemRef, ResolverMarkerEnvironment};
use crate::pubgrub::{PubGrubDependency, PubGrubPackage}; use crate::pubgrub::{PubGrubDependency, PubGrubPackage};
use crate::requires_python::RequiresPythonRange; use crate::requires_python::RequiresPythonRange;
@ -425,7 +425,6 @@ impl<'d> Forker<'d> {
pub(crate) fn fork( pub(crate) fn fork(
&self, &self,
env: &ResolverEnvironment, env: &ResolverEnvironment,
_conflicting_groups: &Conflicts,
) -> Option<(Forker<'d>, Vec<ResolverEnvironment>)> { ) -> Option<(Forker<'d>, Vec<ResolverEnvironment>)> {
if !env.included_by_marker(&self.marker) { if !env.included_by_marker(&self.marker) {
return None; return None;

View file

@ -109,7 +109,7 @@ struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
hasher: HashStrategy, hasher: HashStrategy,
env: ResolverEnvironment, env: ResolverEnvironment,
python_requirement: PythonRequirement, python_requirement: PythonRequirement,
conflicting_groups: Conflicts, conflicts: Conflicts,
workspace_members: BTreeSet<PackageName>, workspace_members: BTreeSet<PackageName>,
selector: CandidateSelector, selector: CandidateSelector,
index: InMemoryIndex, index: InMemoryIndex,
@ -150,7 +150,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
options: Options, options: Options,
python_requirement: &'a PythonRequirement, python_requirement: &'a PythonRequirement,
env: ResolverEnvironment, env: ResolverEnvironment,
conflicting_groups: Conflicts, conflicts: Conflicts,
tags: Option<&'a Tags>, tags: Option<&'a Tags>,
flat_index: &'a FlatIndex, flat_index: &'a FlatIndex,
index: &'a InMemoryIndex, index: &'a InMemoryIndex,
@ -177,7 +177,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
hasher, hasher,
env, env,
python_requirement, python_requirement,
conflicting_groups, conflicts,
index, index,
build_context.git(), build_context.git(),
build_context.capabilities(), build_context.capabilities(),
@ -198,7 +198,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
hasher: &HashStrategy, hasher: &HashStrategy,
env: ResolverEnvironment, env: ResolverEnvironment,
python_requirement: &PythonRequirement, python_requirement: &PythonRequirement,
conflicting_groups: Conflicts, conflicts: Conflicts,
index: &InMemoryIndex, index: &InMemoryIndex,
git: &GitResolver, git: &GitResolver,
capabilities: &IndexCapabilities, capabilities: &IndexCapabilities,
@ -226,7 +226,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
locations: locations.clone(), locations: locations.clone(),
env, env,
python_requirement: python_requirement.clone(), python_requirement: python_requirement.clone(),
conflicting_groups, conflicts,
installed_packages, installed_packages,
unavailable_packages: DashMap::default(), unavailable_packages: DashMap::default(),
incomplete_packages: DashMap::default(), incomplete_packages: DashMap::default(),
@ -607,7 +607,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self.index, &self.index,
&self.git, &self.git,
&self.python_requirement, &self.python_requirement,
&self.conflicting_groups, &self.conflicts,
self.selector.resolution_strategy(), self.selector.resolution_strategy(),
self.options, self.options,
) )
@ -1207,7 +1207,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err), Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err),
}) })
} else { } else {
Ok(result?.fork(env, python_requirement, &self.conflicting_groups)) Ok(result?.fork(env, python_requirement, &self.conflicts))
} }
} }
@ -1387,15 +1387,12 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
} }
}; };
if let Some(err) = if let Some(err) = find_conflicting_extra(&self.conflicts, &metadata.requires_dist)
find_conflicting_extra(&self.conflicting_groups, &metadata.requires_dist)
{ {
return Err(err); return Err(err);
} }
for dependencies in metadata.dependency_groups.values() { for dependencies in metadata.dependency_groups.values() {
if let Some(err) = if let Some(err) = find_conflicting_extra(&self.conflicts, dependencies) {
find_conflicting_extra(&self.conflicting_groups, dependencies)
{
return Err(err); return Err(err);
} }
} }
@ -2694,7 +2691,7 @@ impl Dependencies {
self, self,
env: &ResolverEnvironment, env: &ResolverEnvironment,
python_requirement: &PythonRequirement, python_requirement: &PythonRequirement,
conflicting_groups: &Conflicts, conflicts: &Conflicts,
) -> ForkedDependencies { ) -> ForkedDependencies {
let deps = match self { let deps = match self {
Dependencies::Available(deps) => deps, Dependencies::Available(deps) => deps,
@ -2713,7 +2710,7 @@ impl Dependencies {
let Forks { let Forks {
mut forks, mut forks,
diverging_packages, diverging_packages,
} = Forks::new(name_to_deps, env, python_requirement, conflicting_groups); } = Forks::new(name_to_deps, env, python_requirement, conflicts);
if forks.is_empty() { if forks.is_empty() {
ForkedDependencies::Unforked(vec![]) ForkedDependencies::Unforked(vec![])
} else if forks.len() == 1 { } else if forks.len() == 1 {
@ -2775,7 +2772,7 @@ impl Forks {
name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>, name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
env: &ResolverEnvironment, env: &ResolverEnvironment,
python_requirement: &PythonRequirement, python_requirement: &PythonRequirement,
conflicting_groups: &Conflicts, conflicts: &Conflicts,
) -> Forks { ) -> Forks {
let python_marker = python_requirement.to_marker_tree(); let python_marker = python_requirement.to_marker_tree();
@ -2839,8 +2836,7 @@ impl Forks {
let mut new = vec![]; let mut new = vec![];
for fork in std::mem::take(&mut forks) { for fork in std::mem::take(&mut forks) {
let Some((remaining_forker, envs)) = forker.fork(&fork.env, conflicting_groups) let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
else {
new.push(fork); new.push(fork);
continue; continue;
}; };
@ -2880,12 +2876,12 @@ impl Forks {
// For example, if we have conflicting groups {x1, x2} and {x3, // For example, if we have conflicting groups {x1, x2} and {x3,
// x4}, we need to make sure the forks generated from one set // x4}, we need to make sure the forks generated from one set
// also account for the other set. // also account for the other set.
for groups in conflicting_groups.iter() { for groups in conflicts.iter() {
let mut new = vec![]; let mut new = vec![];
for fork in std::mem::take(&mut forks) { for fork in std::mem::take(&mut forks) {
let mut has_conflicting_dependency = false; let mut has_conflicting_dependency = false;
for group in groups.iter() { for group in groups.iter() {
if fork.contains_conflicting_group(group.as_ref()) { if fork.contains_conflicting_item(group.as_ref()) {
has_conflicting_dependency = true; has_conflicting_dependency = true;
break; break;
} }
@ -2954,7 +2950,7 @@ struct Fork {
/// This exists to make some access patterns more efficient. Namely, /// This exists to make some access patterns more efficient. Namely,
/// it makes it easy to check whether there's a dependency with a /// it makes it easy to check whether there's a dependency with a
/// particular conflicting group in this fork. /// particular conflicting group in this fork.
conflicting_groups: FxHashMap<PackageName, FxHashSet<ExtraName>>, conflicts: FxHashMap<PackageName, FxHashSet<ExtraName>>,
/// The resolver environment for this fork. /// The resolver environment for this fork.
/// ///
/// Principally, this corresponds to the markers in this for. So in the /// Principally, this corresponds to the markers in this for. So in the
@ -2975,18 +2971,18 @@ impl Fork {
fn new(env: ResolverEnvironment) -> Fork { fn new(env: ResolverEnvironment) -> Fork {
Fork { Fork {
dependencies: vec![], dependencies: vec![],
conflicting_groups: FxHashMap::default(), conflicts: FxHashMap::default(),
env, env,
} }
} }
/// 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_group) = dep.package.conflicting_group() { if let Some(conflicting_item) = dep.package.conflicting_item() {
self.conflicting_groups self.conflicts
.entry(conflicting_group.package().clone()) .entry(conflicting_item.package().clone())
.or_default() .or_default()
.insert(conflicting_group.extra().clone()); .insert(conflicting_item.extra().clone());
} }
self.dependencies.push(dep); self.dependencies.push(dep);
} }
@ -3004,9 +3000,9 @@ impl Fork {
if self.env.included_by_marker(markers) { if self.env.included_by_marker(markers) {
return true; return true;
} }
if let Some(conflicting_group) = dep.package.conflicting_group() { if let Some(conflicting_item) = dep.package.conflicting_item() {
if let Some(set) = self.conflicting_groups.get_mut(conflicting_group.package()) { if let Some(set) = self.conflicts.get_mut(conflicting_item.package()) {
set.remove(conflicting_group.extra()); set.remove(conflicting_item.extra());
} }
} }
false false
@ -3015,8 +3011,8 @@ impl Fork {
/// Returns true if any of the dependencies in this fork contain a /// Returns true if any of the dependencies in this fork contain a
/// dependency with the given package and extra values. /// dependency with the given package and extra values.
fn contains_conflicting_group(&self, group: ConflictItemRef<'_>) -> bool { fn contains_conflicting_item(&self, group: ConflictItemRef<'_>) -> bool {
self.conflicting_groups self.conflicts
.get(group.package()) .get(group.package())
.map(|set| set.contains(group.extra())) .map(|set| set.contains(group.extra()))
.unwrap_or(false) .unwrap_or(false)
@ -3028,15 +3024,15 @@ impl Fork {
fn exclude(mut self, groups: impl IntoIterator<Item = ConflictItem>) -> Fork { fn exclude(mut self, groups: impl IntoIterator<Item = ConflictItem>) -> Fork {
self.env = self.env.exclude_by_group(groups); self.env = self.env.exclude_by_group(groups);
self.dependencies.retain(|dep| { self.dependencies.retain(|dep| {
let Some(conflicting_group) = dep.package.conflicting_group() else { let Some(conflicting_item) = dep.package.conflicting_item() else {
return true; return true;
}; };
if self.env.included_by_group(conflicting_group) { if self.env.included_by_group(conflicting_item) {
return true; return true;
} }
if let Some(conflicting_group) = dep.package.conflicting_group() { if let Some(conflicting_item) = dep.package.conflicting_item() {
if let Some(set) = self.conflicting_groups.get_mut(conflicting_group.package()) { if let Some(set) = self.conflicts.get_mut(conflicting_item.package()) {
set.remove(conflicting_group.extra()); set.remove(conflicting_item.extra());
} }
} }
false false

View file

@ -105,7 +105,7 @@ pub struct Options {
// `crates/uv-workspace/src/pyproject.rs`. The documentation lives on that struct. // `crates/uv-workspace/src/pyproject.rs`. The documentation lives on that struct.
// They're only respected in `pyproject.toml` files, and should be rejected in `uv.toml` files. // They're only respected in `pyproject.toml` files, and should be rejected in `uv.toml` files.
#[cfg_attr(feature = "schemars", schemars(skip))] #[cfg_attr(feature = "schemars", schemars(skip))]
pub conflicting_groups: Option<serde::de::IgnoredAny>, pub conflicts: Option<serde::de::IgnoredAny>,
#[cfg_attr(feature = "schemars", schemars(skip))] #[cfg_attr(feature = "schemars", schemars(skip))]
pub workspace: Option<serde::de::IgnoredAny>, pub workspace: Option<serde::de::IgnoredAny>,
@ -1626,7 +1626,7 @@ pub struct OptionsWire {
// NOTE(charlie): These fields should be kept in-sync with `ToolUv` in // NOTE(charlie): These fields should be kept in-sync with `ToolUv` in
// `crates/uv-workspace/src/pyproject.rs`. The documentation lives on that struct. // `crates/uv-workspace/src/pyproject.rs`. The documentation lives on that struct.
// They're only respected in `pyproject.toml` files, and should be rejected in `uv.toml` files. // They're only respected in `pyproject.toml` files, and should be rejected in `uv.toml` files.
conflicting_groups: Option<serde::de::IgnoredAny>, conflicts: Option<serde::de::IgnoredAny>,
workspace: Option<serde::de::IgnoredAny>, workspace: Option<serde::de::IgnoredAny>,
sources: Option<serde::de::IgnoredAny>, sources: Option<serde::de::IgnoredAny>,
managed: Option<serde::de::IgnoredAny>, managed: Option<serde::de::IgnoredAny>,
@ -1681,7 +1681,7 @@ impl From<OptionsWire> for Options {
override_dependencies, override_dependencies,
constraint_dependencies, constraint_dependencies,
environments, environments,
conflicting_groups, conflicts,
publish_url, publish_url,
trusted_publishing, trusted_publishing,
workspace, workspace,
@ -1743,7 +1743,7 @@ impl From<OptionsWire> for Options {
python_install_mirror, python_install_mirror,
pypy_install_mirror, pypy_install_mirror,
), ),
conflicting_groups, conflicts,
publish: PublishOptions { publish: PublishOptions {
publish_url, publish_url,
trusted_publishing, trusted_publishing,

View file

@ -102,7 +102,7 @@ impl PyProjectToml {
} }
/// Returns the set of conflicts for the project. /// Returns the set of conflicts for the project.
pub fn conflicting_groups(&self) -> Conflicts { pub fn conflicts(&self) -> Conflicts {
let empty = Conflicts::empty(); let empty = Conflicts::empty();
let Some(project) = self.project.as_ref() else { let Some(project) = self.project.as_ref() else {
return empty; return empty;
@ -113,10 +113,10 @@ impl PyProjectToml {
let Some(tooluv) = tool.uv.as_ref() else { let Some(tooluv) = tool.uv.as_ref() else {
return empty; return empty;
}; };
let Some(conflicting) = tooluv.conflicting_groups.as_ref() else { let Some(conflicting) = tooluv.conflicts.as_ref() else {
return empty; return empty;
}; };
conflicting.to_conflicting_with_package_name(&project.name) conflicting.to_conflicts_with_package_name(&project.name)
} }
} }
@ -494,7 +494,7 @@ pub struct ToolUv {
# Require that `package[test1]` and `package[test2]` # Require that `package[test1]` and `package[test2]`
# requirements are resolved in different forks so that they # requirements are resolved in different forks so that they
# cannot conflict with one another. # cannot conflict with one another.
conflicting-groups = [ conflicts = [
[ [
{ extra = "test1" }, { extra = "test1" },
{ extra = "test2" }, { extra = "test2" },
@ -503,7 +503,7 @@ pub struct ToolUv {
"# "#
)] )]
*/ */
pub conflicting_groups: Option<SchemaConflicts>, pub conflicts: Option<SchemaConflicts>,
} }
#[derive(Default, Debug, Clone, PartialEq, Eq)] #[derive(Default, Debug, Clone, PartialEq, Eq)]

View file

@ -393,10 +393,10 @@ impl Workspace {
} }
/// Returns the set of conflicts for the workspace. /// Returns the set of conflicts for the workspace.
pub fn conflicting_groups(&self) -> Conflicts { pub fn conflicts(&self) -> Conflicts {
let mut conflicting = Conflicts::empty(); let mut conflicting = Conflicts::empty();
for member in self.packages.values() { for member in self.packages.values() {
conflicting.append(&mut member.pyproject_toml.conflicting_groups()); conflicting.append(&mut member.pyproject_toml.conflicts());
} }
conflicting conflicting
} }

View file

@ -242,7 +242,7 @@ async fn albatross_root_workspace() {
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"environments": null, "environments": null,
"conflicting-groups": null "conflicts": null
} }
}, },
"dependency-groups": null "dependency-groups": null
@ -334,7 +334,7 @@ async fn albatross_virtual_workspace() {
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"environments": null, "environments": null,
"conflicting-groups": null "conflicts": null
} }
}, },
"dependency-groups": null "dependency-groups": null
@ -540,7 +540,7 @@ async fn exclude_package() -> Result<()> {
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"environments": null, "environments": null,
"conflicting-groups": null "conflicts": null
} }
}, },
"dependency-groups": null "dependency-groups": null
@ -644,7 +644,7 @@ async fn exclude_package() -> Result<()> {
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"environments": null, "environments": null,
"conflicting-groups": null "conflicts": null
} }
}, },
"dependency-groups": null "dependency-groups": null
@ -761,7 +761,7 @@ async fn exclude_package() -> Result<()> {
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"environments": null, "environments": null,
"conflicting-groups": null "conflicts": null
} }
}, },
"dependency-groups": null "dependency-groups": null
@ -852,7 +852,7 @@ async fn exclude_package() -> Result<()> {
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"environments": null, "environments": null,
"conflicting-groups": null "conflicts": null
} }
}, },
"dependency-groups": null "dependency-groups": null

View file

@ -54,7 +54,7 @@ pub(crate) async fn pip_compile(
constraints_from_workspace: Vec<Requirement>, constraints_from_workspace: Vec<Requirement>,
overrides_from_workspace: Vec<Requirement>, overrides_from_workspace: Vec<Requirement>,
environments: SupportedEnvironments, environments: SupportedEnvironments,
conflicting_groups: Conflicts, conflicts: Conflicts,
extras: ExtrasSpecification, extras: ExtrasSpecification,
output_file: Option<&Path>, output_file: Option<&Path>,
resolution_mode: ResolutionMode, resolution_mode: ResolutionMode,
@ -256,7 +256,7 @@ pub(crate) async fn pip_compile(
( (
None, None,
ResolverEnvironment::universal(environments.into_markers()), ResolverEnvironment::universal(environments.into_markers()),
conflicting_groups, conflicts,
) )
} else { } else {
let (tags, marker_env) = let (tags, marker_env) =

View file

@ -104,7 +104,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
tags: Option<&Tags>, tags: Option<&Tags>,
resolver_env: ResolverEnvironment, resolver_env: ResolverEnvironment,
python_requirement: PythonRequirement, python_requirement: PythonRequirement,
conflicting_groups: Conflicts, conflicts: Conflicts,
client: &RegistryClient, client: &RegistryClient,
flat_index: &FlatIndex, flat_index: &FlatIndex,
index: &InMemoryIndex, index: &InMemoryIndex,
@ -291,7 +291,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
options, options,
&python_requirement, &python_requirement,
resolver_env, resolver_env,
conflicting_groups, conflicts,
tags, tags,
flat_index, flat_index,
index, index,

View file

@ -631,7 +631,7 @@ async fn do_lock(
None, None,
resolver_env, resolver_env,
python_requirement, python_requirement,
workspace.conflicting_groups(), workspace.conflicts(),
&client, &client,
&flat_index, &flat_index,
&state.index, &state.index,
@ -661,7 +661,7 @@ async fn do_lock(
let previous = existing_lock.map(ValidatedLock::into_lock); let previous = existing_lock.map(ValidatedLock::into_lock);
let lock = Lock::from_resolution_graph(&resolution, workspace.install_path())? let lock = Lock::from_resolution_graph(&resolution, workspace.install_path())?
.with_manifest(manifest) .with_manifest(manifest)
.with_conflicting_groups(workspace.conflicting_groups()) .with_conflicts(workspace.conflicts())
.with_supported_environments( .with_supported_environments(
environments environments
.cloned() .cloned()
@ -806,11 +806,11 @@ impl ValidatedLock {
} }
// If the conflicting group config has changed, we have to perform a clean resolution. // If the conflicting group config has changed, we have to perform a clean resolution.
if &workspace.conflicting_groups() != lock.conflicting_groups() { if &workspace.conflicts() != lock.conflicts() {
debug!( debug!(
"Ignoring existing lockfile due to change in conflicting groups: `{:?}` vs. `{:?}`", "Ignoring existing lockfile due to change in conflicting groups: `{:?}` vs. `{:?}`",
workspace.conflicting_groups(), workspace.conflicts(),
lock.conflicting_groups(), lock.conflicts(),
); );
return Ok(Self::Versions(lock)); return Ok(Self::Versions(lock));
} }

View file

@ -283,18 +283,15 @@ pub(super) async fn do_sync(
// Validate that we aren't trying to install extras that are // Validate that we aren't trying to install extras that are
// declared as conflicting. // declared as conflicting.
let conflicting_groups = target.lock().conflicting_groups(); let conflicts = target.lock().conflicts();
for groups in conflicting_groups.iter() { for set in conflicts.iter() {
let conflicting = groups let conflicting = set
.iter() .iter()
.filter(|group| extras.contains(group.extra())) .filter(|item| extras.contains(item.extra()))
.map(|group| group.extra().clone()) .map(|item| item.extra().clone())
.collect::<Vec<ExtraName>>(); .collect::<Vec<ExtraName>>();
if conflicting.len() >= 2 { if conflicting.len() >= 2 {
return Err(ProjectError::ExtraIncompatibility( return Err(ProjectError::ExtraIncompatibility(set.clone(), conflicting));
groups.clone(),
conflicting,
));
} }
} }

View file

@ -2220,7 +2220,7 @@ fn lock_conflicting_extra_basic() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[ [
{ extra = "project1" }, { extra = "project1" },
{ extra = "project2" }, { extra = "project2" },
@ -2257,7 +2257,7 @@ fn lock_conflicting_extra_basic() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
resolution-markers = [ resolution-markers = [
] ]
conflicting-groups = [[ conflicts = [[
{ package = "project", extra = "project1" }, { package = "project", extra = "project1" },
{ package = "project", extra = "project2" }, { package = "project", extra = "project2" },
]] ]]
@ -2417,7 +2417,7 @@ fn lock_conflicting_extra_basic_three_extras() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[ [
{ extra = "project1" }, { extra = "project1" },
{ extra = "project2" }, { extra = "project2" },
@ -2456,7 +2456,7 @@ fn lock_conflicting_extra_basic_three_extras() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
resolution-markers = [ resolution-markers = [
] ]
conflicting-groups = [[ conflicts = [[
{ package = "project", extra = "project1" }, { package = "project", extra = "project1" },
{ package = "project", extra = "project2" }, { package = "project", extra = "project2" },
{ package = "project", extra = "project3" }, { package = "project", extra = "project3" },
@ -2542,7 +2542,7 @@ fn lock_conflicting_extra_multiple_not_conflicting1() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[ [
{ extra = "project1" }, { extra = "project1" },
{ extra = "project2" }, { extra = "project2" },
@ -2712,7 +2712,7 @@ fn lock_conflicting_extra_multiple_not_conflicting2() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[ [
{ extra = "project1" }, { extra = "project1" },
{ extra = "project2" }, { extra = "project2" },
@ -2758,7 +2758,7 @@ fn lock_conflicting_extra_multiple_not_conflicting2() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[ [
{ extra = "project1" }, { extra = "project1" },
{ extra = "project2" }, { extra = "project2" },
@ -2808,7 +2808,7 @@ fn lock_conflicting_extra_multiple_not_conflicting2() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[ [
{ extra = "project1" }, { extra = "project1" },
{ extra = "project2" }, { extra = "project2" },
@ -2889,7 +2889,7 @@ fn lock_conflicting_extra_multiple_independent() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[ [
{ extra = "project3" }, { extra = "project3" },
{ extra = "project4" }, { extra = "project4" },
@ -2928,7 +2928,7 @@ fn lock_conflicting_extra_multiple_independent() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[ [
{ extra = "project1" }, { extra = "project1" },
{ extra = "project2" }, { extra = "project2" },
@ -2971,7 +2971,7 @@ fn lock_conflicting_extra_multiple_independent() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
resolution-markers = [ resolution-markers = [
] ]
conflicting-groups = [[ conflicts = [[
{ package = "project", extra = "project1" }, { package = "project", extra = "project1" },
{ package = "project", extra = "project2" }, { package = "project", extra = "project2" },
], [ ], [
@ -3098,7 +3098,7 @@ fn lock_conflicting_extra_config_change_ignore_lockfile() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[ [
{ extra = "project1" }, { extra = "project1" },
{ extra = "project2" }, { extra = "project2" },
@ -3133,7 +3133,7 @@ fn lock_conflicting_extra_config_change_ignore_lockfile() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
resolution-markers = [ resolution-markers = [
] ]
conflicting-groups = [[ conflicts = [[
{ package = "project", extra = "project1" }, { package = "project", extra = "project1" },
{ package = "project", extra = "project2" }, { package = "project", extra = "project2" },
]] ]]
@ -3272,7 +3272,7 @@ fn lock_conflicting_extra_unconditional() -> Result<()> {
project2 = ["anyio==4.2.0"] project2 = ["anyio==4.2.0"]
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[ [
{ extra = "project1" }, { extra = "project1" },
{ extra = "project2" }, { extra = "project2" },

View file

@ -191,7 +191,7 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> {
| |
2 | unknown = "field" 2 | unknown = "field"
| ^^^^^^^ | ^^^^^^^
unknown field `unknown`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `conflicting-groups`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies` unknown field `unknown`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies`
Resolved in [TIME] Resolved in [TIME]
Audited in [TIME] Audited in [TIME]
@ -7289,7 +7289,7 @@ fn sklearn() {
let filters = std::iter::once((r"exit code: 1", "exit status: 1")) let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
.chain(context.filters()) .chain(context.filters())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
uv_snapshot!(filters, context.pip_install().arg("sklearn"), @r#" uv_snapshot!(filters, context.pip_install().arg("sklearn"), @r###"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@ -7316,6 +7316,6 @@ fn sklearn() {
https://github.com/scikit-learn/sklearn-pypi-package https://github.com/scikit-learn/sklearn-pypi-package
help: `sklearn` is often confused for `scikit-learn` Did you mean to install `scikit-learn` instead? help: `sklearn` is often confused for `scikit-learn` Did you mean to install `scikit-learn` instead?
"# "###
); );
} }

View file

@ -3107,9 +3107,9 @@ fn resolve_both() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
/// Tests that errors when parsing `conflicting-groups` are reported. /// Tests that errors when parsing `conflicts` are reported.
#[test] #[test]
fn invalid_conflicting_groups() -> anyhow::Result<()> { fn invalid_conflicts() -> anyhow::Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.12");
let pyproject = context.temp_dir.child("pyproject.toml"); let pyproject = context.temp_dir.child("pyproject.toml");
@ -3121,7 +3121,7 @@ fn invalid_conflicting_groups() -> anyhow::Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[{extra = "dev"}], [{extra = "dev"}],
] ]
"#})?; "#})?;
@ -3134,11 +3134,11 @@ fn invalid_conflicting_groups() -> anyhow::Result<()> {
----- stderr ----- ----- stderr -----
error: Failed to parse: `pyproject.toml` error: Failed to parse: `pyproject.toml`
Caused by: TOML parse error at line 7, column 22 Caused by: TOML parse error at line 7, column 13
| |
7 | conflicting-groups = [ 7 | conflicts = [
| ^ | ^
Each set of conflicting groups must have at least two entries, but found only one Each set of conflicts must have at least two entries, but found only one
"### "###
); );
@ -3150,7 +3150,7 @@ fn invalid_conflicting_groups() -> anyhow::Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [[]] conflicts = [[]]
"#})?; "#})?;
// The file should be rejected for violating the schema. // The file should be rejected for violating the schema.
@ -3161,20 +3161,20 @@ fn invalid_conflicting_groups() -> anyhow::Result<()> {
----- stderr ----- ----- stderr -----
error: Failed to parse: `pyproject.toml` error: Failed to parse: `pyproject.toml`
Caused by: TOML parse error at line 7, column 22 Caused by: TOML parse error at line 7, column 13
| |
7 | conflicting-groups = [[]] 7 | conflicts = [[]]
| ^^^^ | ^^^^
Each set of conflicting groups must have at least two entries, but found none Each set of conflicts must have at least two entries, but found none
"### "###
); );
Ok(()) Ok(())
} }
/// Tests that valid `conflicting-groups` are parsed okay. /// Tests that valid `conflicts` are parsed okay.
#[test] #[test]
fn valid_conflicting_groups() -> anyhow::Result<()> { fn valid_conflicts() -> anyhow::Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.12");
let pyproject = context.temp_dir.child("pyproject.toml"); let pyproject = context.temp_dir.child("pyproject.toml");
@ -3186,7 +3186,7 @@ fn valid_conflicting_groups() -> anyhow::Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
[tool.uv] [tool.uv]
conflicting-groups = [ conflicts = [
[{extra = "x1"}, {extra = "x2"}], [{extra = "x1"}, {extra = "x2"}],
] ]
"#})?; "#})?;
@ -3405,7 +3405,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
| |
1 | [project] 1 | [project]
| ^^^^^^^ | ^^^^^^^
unknown field `project`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `conflicting-groups`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies` unknown field `project`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies`
"### "###
); );