Skip to content

Commit

Permalink
uv-workspace: make package name option in conflicting-groups schema
Browse files Browse the repository at this point in the history
In order to support this, we define a nearly duplicative set of types
just for `pyproject.toml` deserialization. We also permit the conflicts
to be specified in workspace member `pyproject.toml` files. So now we
collect all of them and merged them into one giant set to feed to the
resolver. When a package name isn't specified, it is implicitly assumed
to be the name of the project that defined the conflicts.

Note that this also removes support for specifying conflicts in
`uv.toml`. I think I didn't mean to add that originally. We could add it
back, but in that context, we would need the package name I believe.
  • Loading branch information
BurntSushi committed Nov 13, 2024
1 parent f49d6b4 commit bdd40ef
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 85 deletions.
106 changes: 106 additions & 0 deletions crates/uv-pypi-types/src/conflicting_groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ impl ConflictingGroupList {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

/// Appends the given list to this one. This drains all elements
/// from the list given, such that after this call, it is empty.
pub fn append(&mut self, other: &mut ConflictingGroupList) {
self.0.append(&mut other.0);
}
}

/// A single set of package-extra pairs that conflict with one another.
Expand Down Expand Up @@ -193,3 +199,103 @@ pub enum ConflictingGroupError {
#[error("Each set of conflicting groups must have at least two entries, but found only one")]
OneGroup,
}

/// Like [`ConflictingGroupList`], but for deserialization in `pyproject.toml`.
///
/// The schema format is different from the in-memory format. Specifically, the
/// schema format does not allow specifying the package name (or will make it
/// optional in the future), where as the in-memory format needs the package
/// name.
///
/// N.B. `ConflictingGroupList` is still used for (de)serialization.
/// Specifically, in the lock file, where the package name is required.
#[derive(
Debug, Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,
)]
pub struct SchemaConflictingGroupList(Vec<SchemaConflictingGroups>);

impl SchemaConflictingGroupList {
/// Convert the public schema "conflicting" type to our internal fully
/// resolved type. Effectively, this pairs the corresponding package name
/// with each conflict.
///
/// 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
/// used when there is no explicit package name written.
pub fn to_conflicting_with_package_name(&self, package: &PackageName) -> ConflictingGroupList {
let mut conflicting = ConflictingGroupList::empty();
for tool_uv_set in &self.0 {
let mut set = vec![];
for item in &tool_uv_set.0 {
let package = item.package.clone().unwrap_or_else(|| package.clone());
set.push(ConflictingGroup::from((package, item.extra.clone())));
}
// OK because we guarantee that
// `SchemaConflictingGroupList` is valid and there aren't
// any new errors that can occur here.
let set = ConflictingGroups::try_from(set).unwrap();
conflicting.push(set);
}
conflicting
}
}

/// Like [`ConflictingGroups`], but for deserialization in `pyproject.toml`.
///
/// The schema format is different from the in-memory format. Specifically, the
/// schema format does not allow specifying the package name (or will make it
/// optional in the future), where as the in-memory format needs the package
/// name.
#[derive(Debug, Default, Clone, Eq, PartialEq, serde::Serialize, schemars::JsonSchema)]
pub struct SchemaConflictingGroups(Vec<SchemaConflictingGroup>);

/// Like [`ConflictingGroup`], but for deserialization in `pyproject.toml`.
///
/// The schema format is different from the in-memory format. Specifically, the
/// schema format does not allow specifying the package name (or will make it
/// optional in the future), where as the in-memory format needs the package
/// name.
#[derive(
Debug,
Default,
Clone,
Eq,
Hash,
PartialEq,
PartialOrd,
Ord,
serde::Deserialize,
serde::Serialize,
schemars::JsonSchema,
)]
#[serde(deny_unknown_fields)]
pub struct SchemaConflictingGroup {
#[serde(default)]
package: Option<PackageName>,
extra: ExtraName,
}

impl<'de> serde::Deserialize<'de> for SchemaConflictingGroups {
fn deserialize<D>(deserializer: D) -> Result<SchemaConflictingGroups, D::Error>
where
D: serde::Deserializer<'de>,
{
let items = Vec::<SchemaConflictingGroup>::deserialize(deserializer)?;
Self::try_from(items).map_err(serde::de::Error::custom)
}
}

impl TryFrom<Vec<SchemaConflictingGroup>> for SchemaConflictingGroups {
type Error = ConflictingGroupError;

fn try_from(
items: Vec<SchemaConflictingGroup>,
) -> Result<SchemaConflictingGroups, ConflictingGroupError> {
match items.len() {
0 => return Err(ConflictingGroupError::ZeroGroups),
1 => return Err(ConflictingGroupError::OneGroup),
_ => {}
}
Ok(SchemaConflictingGroups(items))
}
}
4 changes: 2 additions & 2 deletions crates/uv-settings/src/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use uv_configuration::{
};
use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex};
use uv_install_wheel::linker::LinkMode;
use uv_pypi_types::{ConflictingGroupList, SupportedEnvironments};
use uv_pypi_types::{SchemaConflictingGroupList, SupportedEnvironments};
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
use uv_resolver::{AnnotationStyle, ExcludeNewer, PrereleaseMode, ResolutionMode};

Expand Down Expand Up @@ -90,7 +90,7 @@ impl_combine_or!(PythonVersion);
impl_combine_or!(ResolutionMode);
impl_combine_or!(String);
impl_combine_or!(SupportedEnvironments);
impl_combine_or!(ConflictingGroupList);
impl_combine_or!(SchemaConflictingGroupList);
impl_combine_or!(TargetTriple);
impl_combine_or!(TrustedPublishing);
impl_combine_or!(Url);
Expand Down
10 changes: 5 additions & 5 deletions crates/uv-settings/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use uv_install_wheel::linker::LinkMode;
use uv_macros::{CombineOptions, OptionsMetadata};
use uv_normalize::{ExtraName, PackageName};
use uv_pep508::Requirement;
use uv_pypi_types::{ConflictingGroupList, SupportedEnvironments, VerbatimParsedUrl};
use uv_pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
use uv_resolver::{AnnotationStyle, ExcludeNewer, PrereleaseMode, ResolutionMode};

Expand Down Expand Up @@ -97,12 +97,12 @@ pub struct Options {
#[cfg_attr(feature = "schemars", schemars(skip))]
pub environments: Option<SupportedEnvironments>,

#[cfg_attr(feature = "schemars", schemars(skip))]
pub conflicting_groups: Option<ConflictingGroupList>,

// NOTE(charlie): These fields should be kept in-sync with `ToolUv` in
// `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.
#[cfg_attr(feature = "schemars", schemars(skip))]
pub conflicting_groups: Option<serde::de::IgnoredAny>,

#[cfg_attr(feature = "schemars", schemars(skip))]
pub workspace: Option<serde::de::IgnoredAny>,

Expand Down Expand Up @@ -1558,11 +1558,11 @@ pub struct OptionsWire {
override_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
constraint_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
environments: Option<SupportedEnvironments>,
conflicting_groups: Option<ConflictingGroupList>,

// NOTE(charlie): These fields should be kept in-sync with `ToolUv` in
// `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.
conflicting_groups: Option<serde::de::IgnoredAny>,
workspace: Option<serde::de::IgnoredAny>,
sources: Option<serde::de::IgnoredAny>,
managed: Option<serde::de::IgnoredAny>,
Expand Down
23 changes: 21 additions & 2 deletions crates/uv-workspace/src/pyproject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::{Version, VersionSpecifiers};
use uv_pep508::MarkerTree;
use uv_pypi_types::{
ConflictingGroupList, RequirementSource, SupportedEnvironments, VerbatimParsedUrl,
ConflictingGroupList, RequirementSource, SchemaConflictingGroupList, SupportedEnvironments,
VerbatimParsedUrl,
};

#[derive(Error, Debug)]
Expand Down Expand Up @@ -100,6 +101,24 @@ impl PyProjectToml {
false
}
}

/// Returns the set of conflicts for the project.
pub fn conflicting_groups(&self) -> ConflictingGroupList {
let empty = ConflictingGroupList::empty();
let Some(project) = self.project.as_ref() else {
return empty;
};
let Some(tool) = self.tool.as_ref() else {
return empty;
};
let Some(tooluv) = tool.uv.as_ref() else {
return empty;
};
let Some(conflicting) = tooluv.conflicting_groups.as_ref() else {
return empty;
};
conflicting.to_conflicting_with_package_name(&project.name)
}
}

// Ignore raw document in comparison.
Expand Down Expand Up @@ -480,7 +499,7 @@ pub struct ToolUv {
]
"#
)]
pub conflicting_groups: Option<ConflictingGroupList>,
pub conflicting_groups: Option<SchemaConflictingGroupList>,
}

#[derive(Default, Debug, Clone, PartialEq, Eq)]
Expand Down
13 changes: 6 additions & 7 deletions crates/uv-workspace/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,14 +392,13 @@ impl Workspace {
.and_then(|uv| uv.environments.as_ref())
}

/// Returns the set of supported environments for the workspace.
/// Returns the set of conflicts for the workspace.
pub fn conflicting_groups(&self) -> ConflictingGroupList {
self.pyproject_toml
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.conflicting_groups.clone())
.unwrap_or_else(ConflictingGroupList::empty)
let mut conflicting = ConflictingGroupList::empty();
for member in self.packages.values() {
conflicting.append(&mut member.pyproject_toml.conflicting_groups());
}
conflicting
}

/// Returns the set of constraints for the workspace.
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLeve
#[cfg(feature = "self-update")]
use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs};
use uv_fs::CWD;
use uv_pypi_types::ConflictingGroupList;
use uv_requirements::RequirementsSource;
use uv_scripts::{Pep723Item, Pep723Metadata, Pep723Script};
use uv_settings::{Combine, FilesystemOptions, Options};
Expand Down Expand Up @@ -332,7 +333,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.constraints_from_workspace,
args.overrides_from_workspace,
args.environments,
args.conflicting_groups,
ConflictingGroupList::empty(),
args.settings.extras,
args.settings.output_file.as_deref(),
args.settings.resolution,
Expand Down
10 changes: 1 addition & 9 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl}
use uv_install_wheel::linker::LinkMode;
use uv_normalize::PackageName;
use uv_pep508::{ExtraName, RequirementOrigin};
use uv_pypi_types::{ConflictingGroupList, Requirement, SupportedEnvironments};
use uv_pypi_types::{Requirement, SupportedEnvironments};
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
use uv_resolver::{AnnotationStyle, DependencyMode, ExcludeNewer, PrereleaseMode, ResolutionMode};
use uv_settings::{
Expand Down Expand Up @@ -1240,7 +1240,6 @@ pub(crate) struct PipCompileSettings {
pub(crate) constraints_from_workspace: Vec<Requirement>,
pub(crate) overrides_from_workspace: Vec<Requirement>,
pub(crate) environments: SupportedEnvironments,
pub(crate) conflicting_groups: ConflictingGroupList,
pub(crate) refresh: Refresh,
pub(crate) settings: PipSettings,
}
Expand Down Expand Up @@ -1332,12 +1331,6 @@ impl PipCompileSettings {
SupportedEnvironments::default()
};

let conflicting_groups = if let Some(configuration) = &filesystem {
configuration.conflicting_groups.clone().unwrap_or_default()
} else {
ConflictingGroupList::empty()
};

Self {
src_file,
constraint: constraint
Expand All @@ -1355,7 +1348,6 @@ impl PipCompileSettings {
constraints_from_workspace,
overrides_from_workspace,
environments,
conflicting_groups,
refresh: Refresh::from(refresh),
settings: PipSettings::combine(
PipOptions {
Expand Down
Loading

0 comments on commit bdd40ef

Please sign in to comment.