Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for --no-extra flag and setting #9387

Merged
merged 9 commits into from
Nov 24, 2024
Merged
18 changes: 18 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2599,6 +2599,12 @@ pub struct RunArgs {
#[arg(long, conflicts_with = "extra")]
pub all_extras: bool,

/// Exclude the specified optional dependencies if `--all-extras` is supplied.
///
/// May be provided multiple times.
#[arg(long)]
pub no_extra: Vec<ExtraName>,

#[arg(long, overrides_with("all_extras"), hide = true)]
pub no_all_extras: bool,

Expand Down Expand Up @@ -2836,6 +2842,12 @@ pub struct SyncArgs {
#[arg(long, conflicts_with = "extra")]
pub all_extras: bool,

/// Exclude the specified optional dependencies if `--all-extras` is supplied.
///
/// May be provided multiple times.
#[arg(long)]
pub no_extra: Vec<ExtraName>,

#[arg(long, overrides_with("all_extras"), hide = true)]
pub no_all_extras: bool,

Expand Down Expand Up @@ -3411,6 +3423,12 @@ pub struct ExportArgs {
#[arg(long, conflicts_with = "extra")]
pub all_extras: bool,

/// Exclude the specified optional dependencies if `--all-extras` is supplied.
///
/// May be provided multiple times.
#[arg(long)]
pub no_extra: Vec<ExtraName>,

#[arg(long, overrides_with("all_extras"), hide = true)]
pub no_all_extras: bool,

Expand Down
126 changes: 124 additions & 2 deletions crates/uv-configuration/src/extras.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashSet;

use uv_normalize::ExtraName;

#[derive(Debug, Default, Clone)]
Expand All @@ -6,12 +8,15 @@ pub enum ExtrasSpecification {
None,
All,
Some(Vec<ExtraName>),
Exclude(HashSet<ExtraName>),
}

impl ExtrasSpecification {
/// Determine the extras specification to use based on the command-line arguments.
pub fn from_args(all_extras: bool, extra: Vec<ExtraName>) -> Self {
if all_extras {
pub fn from_args(all_extras: bool, no_extra: Vec<ExtraName>, extra: Vec<ExtraName>) -> Self {
if all_extras && !no_extra.is_empty() {
ExtrasSpecification::Exclude(HashSet::from_iter(no_extra))
} else if all_extras {
ExtrasSpecification::All
} else if extra.is_empty() {
ExtrasSpecification::None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we filter extra in the else branch here, to remove anything in no_extra?

Expand All @@ -26,10 +31,127 @@ impl ExtrasSpecification {
ExtrasSpecification::All => true,
ExtrasSpecification::None => false,
ExtrasSpecification::Some(extras) => extras.contains(name),
ExtrasSpecification::Exclude(excluded) => !excluded.contains(name),
}
}

pub fn is_empty(&self) -> bool {
matches!(self, ExtrasSpecification::None)
}

pub fn extra_names<'a, Names>(&'a self, all_names: Names) -> ExtrasIter<'a, Names>
where
Names: Iterator<Item = &'a ExtraName>,
{
match self {
ExtrasSpecification::All => ExtrasIter::All(all_names),
ExtrasSpecification::None => ExtrasIter::None,
ExtrasSpecification::Some(extras) => ExtrasIter::Some(extras.iter()),
ExtrasSpecification::Exclude(excluded) => ExtrasIter::Exclude(all_names, excluded),
}
}
}

pub enum ExtrasIter<'a, Names: Iterator<Item = &'a ExtraName>> {
None,
All(Names),
Some(std::slice::Iter<'a, ExtraName>),
Exclude(Names, &'a HashSet<ExtraName>),
}

impl<'a, Names: Iterator<Item = &'a ExtraName>> Iterator for ExtrasIter<'a, Names> {
type Item = &'a ExtraName;

fn next(&mut self) -> Option<Self::Item> {
match self {
Self::All(names) => names.next(),
Self::None => None,
Self::Some(extras) => extras.next(),
Self::Exclude(names, excluded) => {
for name in names.by_ref() {
if !excluded.contains(name) {
return Some(name);
}
}
None
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;

macro_rules! extras {
() => (
Vec::new()
);
($($x:expr),+ $(,)?) => (
vec![$(ExtraName::new($x.into()).unwrap()),+]
)
}

#[test]
fn test_no_extra_full() {
let pkg_extras = extras!["dev", "docs", "extra-1", "extra-2"];
let no_extra = extras!["dev", "docs", "extra-1", "extra-2"];
let spec = ExtrasSpecification::from_args(true, no_extra, vec![]);
let result: Vec<_> = spec.extra_names(pkg_extras.iter()).cloned().collect();
assert_eq!(result, extras![]);
}

#[test]
fn test_no_extra_partial() {
let pkg_extras = extras!["dev", "docs", "extra-1", "extra-2"];
let no_extra = extras!["extra-1", "extra-2"];
let spec = ExtrasSpecification::from_args(true, no_extra, vec![]);
let result: Vec<_> = spec.extra_names(pkg_extras.iter()).cloned().collect();
assert_eq!(result, extras!["dev", "docs"]);
}

#[test]
fn test_no_extra_empty() {
let pkg_extras = extras!["dev", "docs", "extra-1", "extra-2"];
let no_extra = extras![];
let spec = ExtrasSpecification::from_args(true, no_extra, vec![]);
let result: Vec<_> = spec.extra_names(pkg_extras.iter()).cloned().collect();
assert_eq!(result, extras!["dev", "docs", "extra-1", "extra-2"]);
}

#[test]
fn test_no_extra_excessive() {
let pkg_extras = extras!["dev", "docs", "extra-1", "extra-2"];
let no_extra = extras!["does-not-exists"];
let spec = ExtrasSpecification::from_args(true, no_extra, vec![]);
let result: Vec<_> = spec.extra_names(pkg_extras.iter()).cloned().collect();
assert_eq!(result, extras!["dev", "docs", "extra-1", "extra-2"]);
}

#[test]
fn test_no_extra_without_all_extras() {
let pkg_extras = extras!["dev", "docs", "extra-1", "extra-2"];
let no_extra = extras!["extra-1", "extra-2"];
let spec = ExtrasSpecification::from_args(false, no_extra, vec![]);
let result: Vec<_> = spec.extra_names(pkg_extras.iter()).cloned().collect();
assert_eq!(result, extras![]);
}

#[test]
fn test_no_extra_without_package_extras() {
let pkg_extras = extras![];
let no_extra = extras!["extra-1", "extra-2"];
let spec = ExtrasSpecification::from_args(true, no_extra, vec![]);
let result: Vec<_> = spec.extra_names(pkg_extras.iter()).cloned().collect();
assert_eq!(result, extras![]);
}

#[test]
fn test_no_extra_duplicates() {
let pkg_extras = extras!["dev", "docs", "extra-1", "extra-1", "extra-2"];
let no_extra = extras!["extra-1", "extra-2"];
let spec = ExtrasSpecification::from_args(true, no_extra, vec![]);
let result: Vec<_> = spec.extra_names(pkg_extras.iter()).cloned().collect();
assert_eq!(result, extras!["dev", "docs"]);
}
}
12 changes: 6 additions & 6 deletions crates/uv-requirements/src/source_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
let origin = RequirementOrigin::Project(path.to_path_buf(), metadata.name.clone());

// Determine the extras to include when resolving the requirements.
let extras = match self.extras {
ExtrasSpecification::All => metadata.provides_extras.as_slice(),
ExtrasSpecification::None => &[],
ExtrasSpecification::Some(extras) => extras,
};
let extras: Vec<_> = self
.extras
.extra_names(metadata.provides_extras.iter())
.cloned()
.collect();

// Determine the appropriate requirements to return based on the extras. This involves
// evaluating the `extras` expression in any markers, but preserving the remaining marker
Expand All @@ -103,7 +103,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
.into_iter()
.map(|requirement| Requirement {
origin: Some(origin.clone()),
marker: requirement.marker.simplify_extras(extras),
marker: requirement.marker.simplify_extras(&extras),
..requirement
})
.collect();
Expand Down
14 changes: 2 additions & 12 deletions crates/uv-resolver/src/lock/requirements_txt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,8 @@ impl<'lock> RequirementsTxtExport<'lock> {

// Push its dependencies on the queue.
queue.push_back((dist, None));
match extras {
ExtrasSpecification::None => {}
ExtrasSpecification::All => {
for extra in dist.optional_dependencies.keys() {
queue.push_back((dist, Some(extra)));
}
}
ExtrasSpecification::Some(extras) => {
for extra in extras {
queue.push_back((dist, Some(extra)));
}
}
for extra in extras.extra_names(dist.optional_dependencies.keys()) {
queue.push_back((dist, Some(extra)));
}
}

Expand Down
14 changes: 2 additions & 12 deletions crates/uv-resolver/src/lock/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,18 +193,8 @@ impl<'env> InstallTarget<'env> {
if dev.prod() {
// Push its dependencies on the queue.
queue.push_back((dist, None));
match extras {
ExtrasSpecification::None => {}
ExtrasSpecification::All => {
for extra in dist.optional_dependencies.keys() {
queue.push_back((dist, Some(extra)));
}
}
ExtrasSpecification::Some(extras) => {
for extra in extras {
queue.push_back((dist, Some(extra)));
}
}
for extra in extras.extra_names(dist.optional_dependencies.keys()) {
queue.push_back((dist, Some(extra)));
}
}

Expand Down
10 changes: 10 additions & 0 deletions crates/uv-settings/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,16 @@ pub struct PipOptions {
"#
)]
pub all_extras: Option<bool>,
/// Exclude the specified optional dependencies if `all-extras` is supplied.
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
all-extras = true
no-extra = ["dev", "docs"]
"#
)]
pub no_extra: Option<Vec<ExtraName>>,
/// Ignore package dependencies, instead only add those packages explicitly listed
/// on the command line to the resulting the requirements file.
#[option(
Expand Down
8 changes: 8 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ impl RunSettings {
let RunArgs {
extra,
all_extras,
no_extra,
no_all_extras,
dev,
no_dev,
Expand Down Expand Up @@ -323,6 +324,7 @@ impl RunSettings {
frozen,
extras: ExtrasSpecification::from_args(
flag(all_extras, no_all_extras).unwrap_or_default(),
no_extra,
extra.unwrap_or_default(),
),
dev: DevGroupsSpecification::from_args(
Expand Down Expand Up @@ -884,6 +886,7 @@ impl SyncSettings {
let SyncArgs {
extra,
all_extras,
no_extra,
no_all_extras,
dev,
no_dev,
Expand Down Expand Up @@ -922,6 +925,7 @@ impl SyncSettings {
frozen,
extras: ExtrasSpecification::from_args(
flag(all_extras, no_all_extras).unwrap_or_default(),
no_extra,
extra.unwrap_or_default(),
),
dev: DevGroupsSpecification::from_args(
Expand Down Expand Up @@ -1304,6 +1308,7 @@ impl ExportSettings {
package,
extra,
all_extras,
no_extra,
no_all_extras,
dev,
no_dev,
Expand Down Expand Up @@ -1339,6 +1344,7 @@ impl ExportSettings {
package,
extras: ExtrasSpecification::from_args(
flag(all_extras, no_all_extras).unwrap_or_default(),
no_extra,
extra.unwrap_or_default(),
),
dev: DevGroupsSpecification::from_args(
Expand Down Expand Up @@ -2487,6 +2493,7 @@ impl PipSettings {
strict,
extra,
all_extras,
no_extra,
no_deps,
allow_empty_requirements,
resolution,
Expand Down Expand Up @@ -2598,6 +2605,7 @@ impl PipSettings {
),
extras: ExtrasSpecification::from_args(
args.all_extras.combine(all_extras).unwrap_or_default(),
args.no_extra.combine(no_extra).unwrap_or_default(),
args.extra.combine(extra).unwrap_or_default(),
),
dependency_mode: if args.no_deps.combine(no_deps).unwrap_or_default() {
Expand Down
12 changes: 12 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ uv run [OPTIONS] [COMMAND]
</dd><dt><code>--no-env-file</code></dt><dd><p>Avoid reading environment variables from a <code>.env</code> file</p>

<p>May also be set with the <code>UV_NO_ENV_FILE</code> environment variable.</p>
</dd><dt><code>--no-extra</code> <i>no-extra</i></dt><dd><p>Exclude the specified optional dependencies if <code>--all-extras</code> is supplied.</p>

<p>May be provided multiple times.</p>

</dd><dt><code>--no-group</code> <i>no-group</i></dt><dd><p>Exclude dependencies from the specified dependency group.</p>

<p>May be provided multiple times.</p>
Expand Down Expand Up @@ -1612,6 +1616,10 @@ uv sync [OPTIONS]

</dd><dt><code>--no-editable</code></dt><dd><p>Install any editable dependencies, including the project and any workspace members, as non-editable</p>

</dd><dt><code>--no-extra</code> <i>no-extra</i></dt><dd><p>Exclude the specified optional dependencies if <code>--all-extras</code> is supplied.</p>

<p>May be provided multiple times.</p>

</dd><dt><code>--no-group</code> <i>no-group</i></dt><dd><p>Exclude dependencies from the specified dependency group.</p>

<p>May be provided multiple times.</p>
Expand Down Expand Up @@ -2286,6 +2294,10 @@ uv export [OPTIONS]

<p>By default, all workspace members and their dependencies are included in the exported requirements file, with all of their dependencies. The <code>--no-emit-workspace</code> option allows exclusion of all the workspace members while retaining their dependencies.</p>

</dd><dt><code>--no-extra</code> <i>no-extra</i></dt><dd><p>Exclude the specified optional dependencies if <code>--all-extras</code> is supplied.</p>

<p>May be provided multiple times.</p>

</dd><dt><code>--no-group</code> <i>no-group</i></dt><dd><p>Exclude dependencies from the specified dependency group.</p>

<p>May be provided multiple times.</p>
Expand Down
Loading
Loading