Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,16 @@ pub struct PipCompileArgs {
#[arg(long, alias = "override", env = EnvVars::UV_OVERRIDE, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub overrides: Vec<Maybe<PathBuf>>,

/// Exclude packages from resolution using the given requirements files.
Comment thread
charliermarsh marked this conversation as resolved.
///
/// Excludes files are `requirements.txt`-like files that specify packages to exclude
/// from the resolution. When a package is excluded, it will be omitted from the
/// dependency list entirely and its own dependencies will be ignored during the resolution
/// phase. Excludes are unconditional in that requirement specifiers and markers are ignored;
/// any package listed in the provided file will be omitted from all resolved environments.
#[arg(long, alias = "exclude", env = EnvVars::UV_EXCLUDE, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub excludes: Vec<Maybe<PathBuf>>,

/// Constrain build dependencies using the given requirements files when building source
/// distributions.
///
Expand Down Expand Up @@ -1897,6 +1907,16 @@ pub struct PipInstallArgs {
#[arg(long, alias = "override", env = EnvVars::UV_OVERRIDE, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub overrides: Vec<Maybe<PathBuf>>,

/// Exclude packages from resolution using the given requirements files.
///
/// Excludes files are `requirements.txt`-like files that specify packages to exclude
/// from the resolution. When a package is excluded, it will be omitted from the
/// dependency list entirely and its own dependencies will be ignored during the resolution
/// phase. Excludes are unconditional in that requirement specifiers and markers are ignored;
/// any package listed in the provided file will be omitted from all resolved environments.
#[arg(long, alias = "exclude", env = EnvVars::UV_EXCLUDE, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub excludes: Vec<Maybe<PathBuf>>,

/// Constrain build dependencies using the given requirements files when building source
/// distributions.
///
Expand Down Expand Up @@ -4848,6 +4868,16 @@ pub struct ToolInstallArgs {
#[arg(long, alias = "override", env = EnvVars::UV_OVERRIDE, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub overrides: Vec<Maybe<PathBuf>>,

/// Exclude packages from resolution using the given requirements files.
///
/// Excludes files are `requirements.txt`-like files that specify packages to exclude
/// from the resolution. When a package is excluded, it will be omitted from the
/// dependency list entirely and its own dependencies will be ignored during the resolution
/// phase. Excludes are unconditional in that requirement specifiers and markers are ignored;
/// any package listed in the provided file will be omitted from all resolved environments.
#[arg(long, alias = "exclude", env = EnvVars::UV_EXCLUDE, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub excludes: Vec<Maybe<PathBuf>>,

/// Constrain build dependencies using the given requirements files when building source
/// distributions.
///
Expand Down
25 changes: 25 additions & 0 deletions crates/uv-configuration/src/excludes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use rustc_hash::FxHashSet;

use uv_normalize::PackageName;

/// A set of packages to exclude from resolution.
#[derive(Debug, Default, Clone)]
pub struct Excludes(FxHashSet<PackageName>);

impl Excludes {
/// Return an iterator over all package names in the exclusion set.
pub fn iter(&self) -> impl Iterator<Item = &PackageName> {
self.0.iter()
}

/// Check if a package is excluded.
pub fn contains(&self, name: &PackageName) -> bool {
self.0.contains(name)
}
}

impl FromIterator<PackageName> for Excludes {
fn from_iter<I: IntoIterator<Item = PackageName>>(iter: I) -> Self {
Self(iter.into_iter().collect())
}
}
2 changes: 2 additions & 0 deletions crates/uv-configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub use dependency_groups::*;
pub use dry_run::*;
pub use editable::*;
pub use env_file::*;
pub use excludes::*;
pub use export_format::*;
pub use extras::*;
pub use hash::*;
Expand All @@ -30,6 +31,7 @@ mod dependency_groups;
mod dry_run;
mod editable;
mod env_file;
mod excludes;
mod export_format;
mod extras;
mod hash;
Expand Down
37 changes: 36 additions & 1 deletion crates/uv-requirements/src/specification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ pub struct RequirementsSpecification {
pub constraints: Vec<NameRequirementSpecification>,
/// The overrides for the project.
pub overrides: Vec<UnresolvedRequirementSpecification>,
/// The excludes for the project.
pub excludes: Vec<PackageName>,
/// The `pylock.toml` file from which to extract the resolution.
pub pylock: Option<PathBuf>,
/// The source trees from which to extract requirements.
Expand Down Expand Up @@ -345,6 +347,7 @@ impl RequirementsSpecification {
requirements: &[RequirementsSource],
constraints: &[RequirementsSource],
overrides: &[RequirementsSource],
excludes: &[RequirementsSource],
groups: Option<&GroupsSpecification>,
client_builder: &BaseClientBuilder<'_>,
) -> Result<Self> {
Expand Down Expand Up @@ -378,6 +381,20 @@ impl RequirementsSpecification {
));
}

// Disallow `pylock.toml` files as excludes.
if let Some(pylock_toml) = excludes.iter().find_map(|source| {
if let RequirementsSource::PylockToml(path) = source {
Some(path)
} else {
None
}
}) {
return Err(anyhow::anyhow!(
"Cannot use `{}` as an exclude file",
pylock_toml.user_display()
));
}

// If we have a `pylock.toml`, don't allow additional requirements, constraints, or
// overrides.
if let Some(pylock_toml) = requirements.iter().find_map(|source| {
Expand Down Expand Up @@ -582,6 +599,24 @@ impl RequirementsSpecification {
spec.no_build.extend(source.no_build);
}

// Collect excludes.
for source in excludes {
let source = Self::from_source(source, client_builder).await?;
for req_spec in source.requirements {
match req_spec.requirement {
UnresolvedRequirement::Named(requirement) => {
spec.excludes.push(requirement.name);
}
UnresolvedRequirement::Unnamed(requirement) => {
return Err(anyhow::anyhow!(
"Unnamed requirements are not allowed as exclusions (found: `{requirement}`)"
));
}
}
}
spec.excludes.extend(source.excludes.into_iter());
}

Ok(spec)
}

Expand All @@ -597,7 +632,7 @@ impl RequirementsSpecification {
requirements: &[RequirementsSource],
client_builder: &BaseClientBuilder<'_>,
) -> Result<Self> {
Self::from_sources(requirements, &[], &[], None, client_builder).await
Self::from_sources(requirements, &[], &[], &[], None, client_builder).await
}

/// Initialize a [`RequirementsSpecification`] from a list of [`Requirement`].
Expand Down
35 changes: 35 additions & 0 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,23 @@ impl Lock {
manifest_table.insert("overrides", value(overrides));
}

if !self.manifest.excludes.is_empty() {
let excludes = self
.manifest
.excludes
.iter()
.map(|name| {
serde::Serialize::serialize(&name, toml_edit::ser::ValueSerializer::new())
})
.collect::<Result<Vec<_>, _>>()?;
let excludes = match excludes.as_slice() {
[] => Array::new(),
[name] => Array::from_iter([name]),
excludes => each_element_on_its_line_array(excludes.iter()),
};
manifest_table.insert("excludes", value(excludes));
}

if !self.manifest.build_constraints.is_empty() {
let build_constraints = self
.manifest
Expand Down Expand Up @@ -1447,6 +1464,7 @@ impl Lock {
requirements: &[Requirement],
constraints: &[Requirement],
overrides: &[Requirement],
excludes: &[PackageName],
build_constraints: &[Requirement],
dependency_groups: &BTreeMap<GroupName, Vec<Requirement>>,
dependency_metadata: &DependencyMetadata,
Expand Down Expand Up @@ -1563,6 +1581,15 @@ impl Lock {
}
}

// Validate that the lockfile was generated with the same excludes.
{
let expected: BTreeSet<_> = excludes.iter().cloned().collect();
let actual: BTreeSet<_> = self.manifest.excludes.iter().cloned().collect();
if expected != actual {
return Ok(SatisfiesResult::MismatchedExcludes(expected, actual));
}
}

// Validate that the lockfile was generated with the same build constraints.
{
let expected: BTreeSet<_> = build_constraints
Expand Down Expand Up @@ -2049,6 +2076,8 @@ pub enum SatisfiesResult<'lock> {
MismatchedConstraints(BTreeSet<Requirement>, BTreeSet<Requirement>),
/// The lockfile uses a different set of overrides.
MismatchedOverrides(BTreeSet<Requirement>, BTreeSet<Requirement>),
/// The lockfile uses a different set of excludes.
MismatchedExcludes(BTreeSet<PackageName>, BTreeSet<PackageName>),
/// The lockfile uses a different set of build constraints.
MismatchedBuildConstraints(BTreeSet<Requirement>, BTreeSet<Requirement>),
/// The lockfile uses a different set of dependency groups.
Expand Down Expand Up @@ -2148,6 +2177,9 @@ pub struct ResolverManifest {
/// The overrides provided to the resolver.
#[serde(default)]
overrides: BTreeSet<Requirement>,
/// The excludes provided to the resolver.
#[serde(default)]
excludes: BTreeSet<PackageName>,
/// The build constraints provided to the resolver.
#[serde(default)]
build_constraints: BTreeSet<Requirement>,
Expand All @@ -2164,6 +2196,7 @@ impl ResolverManifest {
requirements: impl IntoIterator<Item = Requirement>,
constraints: impl IntoIterator<Item = Requirement>,
overrides: impl IntoIterator<Item = Requirement>,
excludes: impl IntoIterator<Item = PackageName>,
build_constraints: impl IntoIterator<Item = Requirement>,
dependency_groups: impl IntoIterator<Item = (GroupName, Vec<Requirement>)>,
dependency_metadata: impl IntoIterator<Item = StaticMetadata>,
Expand All @@ -2173,6 +2206,7 @@ impl ResolverManifest {
requirements: requirements.into_iter().collect(),
constraints: constraints.into_iter().collect(),
overrides: overrides.into_iter().collect(),
excludes: excludes.into_iter().collect(),
build_constraints: build_constraints.into_iter().collect(),
dependency_groups: dependency_groups
.into_iter()
Expand Down Expand Up @@ -2201,6 +2235,7 @@ impl ResolverManifest {
.into_iter()
.map(|requirement| requirement.relative_to(root))
.collect::<Result<BTreeSet<_>, _>>()?,
excludes: self.excludes,
build_constraints: self
.build_constraints
.into_iter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Ok(
dependency_groups: {},
constraints: {},
overrides: {},
excludes: {},
build_constraints: {},
dependency_metadata: {},
},
Expand Down
Loading
Loading