Skip to content

Commit

Permalink
Add dependency-group cli flags to uv pip install and `uv pip compil…
Browse files Browse the repository at this point in the history
…e` (`--group`, `--no-group`, `--only-group`, `--all-groups`) (#10861)

Ultimately this is a lot of settings plumbing and a couple minor pieces
of Actual Logic (which are so simple I have to assume there's something
missing, but maybe not!).

Note this "needlessly" use DevDependencyGroup since it costs nothing, is
more futureproof, and lets us maintain one primary interface (we just
pass `false` for all the dev arguments).

Fixes #8590
Fixes #8969
  • Loading branch information
Gankra authored Jan 23, 2025
1 parent c3a5117 commit 53706a1
Show file tree
Hide file tree
Showing 19 changed files with 1,253 additions and 100 deletions.
84 changes: 76 additions & 8 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,40 @@ pub struct PipCompileArgs {
#[arg(long, overrides_with("all_extras"), hide = true)]
pub no_all_extras: bool,

/// Include dependencies from the specified dependency group.
///
/// Only applies to `pyproject.toml` sources.
///
/// May be provided multiple times.
#[arg(long, conflicts_with("only_group"))]
pub group: Vec<GroupName>,

/// Exclude dependencies from the specified dependency group.
///
/// Only applies to `pyproject.toml` sources.
///
/// May be provided multiple times.
#[arg(long)]
pub no_group: Vec<GroupName>,

/// Only include dependencies from the specified dependency group.
///
/// The project itself will also be omitted.
///
/// Only applies to `pyproject.toml` sources.
///
/// May be provided multiple times.
#[arg(long, conflicts_with("group"))]
pub only_group: Vec<GroupName>,

/// Include dependencies from all dependency groups.
///
/// Only applies to `pyproject.toml` sources.
///
/// `--no-group` can be used to exclude specific groups.
#[arg(long, conflicts_with_all = [ "group", "only_group" ])]
pub all_groups: bool,

#[command(flatten)]
pub resolver: ResolverArgs,

Expand Down Expand Up @@ -1572,6 +1606,40 @@ pub struct PipInstallArgs {
#[arg(long, overrides_with("all_extras"), hide = true)]
pub no_all_extras: bool,

/// Include dependencies from the specified dependency group.
///
/// Only applies to `pyproject.toml` sources.
///
/// May be provided multiple times.
#[arg(long, conflicts_with("only_group"))]
pub group: Vec<GroupName>,

/// Exclude dependencies from the specified dependency group.
///
/// Only applies to `pyproject.toml` sources.
///
/// May be provided multiple times.
#[arg(long)]
pub no_group: Vec<GroupName>,

/// Only include dependencies from the specified dependency group.
///
/// The project itself will also be omitted.
///
/// Only applies to `pyproject.toml` sources.
///
/// May be provided multiple times.
#[arg(long, conflicts_with("group"))]
pub only_group: Vec<GroupName>,

/// Include dependencies from all dependency groups.
///
/// Only applies to `pyproject.toml` sources.
///
/// `--no-group` can be used to exclude specific groups.
#[arg(long, conflicts_with_all = [ "group", "only_group" ])]
pub all_groups: bool,

#[command(flatten)]
pub installer: ResolverInstallerArgs,

Expand Down Expand Up @@ -2727,9 +2795,9 @@ pub struct RunArgs {

/// Only include dependencies from the specified dependency group.
///
/// May be provided multiple times.
///
/// The project itself will also be omitted.
///
/// May be provided multiple times.
#[arg(long, conflicts_with("group"))]
pub only_group: Vec<GroupName>,

Expand Down Expand Up @@ -2998,9 +3066,9 @@ pub struct SyncArgs {

/// Only include dependencies from the specified dependency group.
///
/// May be provided multiple times.
///
/// The project itself will also be omitted.
///
/// May be provided multiple times.
#[arg(long, conflicts_with("group"))]
pub only_group: Vec<GroupName>,

Expand Down Expand Up @@ -3449,9 +3517,9 @@ pub struct TreeArgs {

/// Only include dependencies from the specified dependency group.
///
/// May be provided multiple times.
///
/// The project itself will also be omitted.
///
/// May be provided multiple times.
#[arg(long, conflicts_with("group"))]
pub only_group: Vec<GroupName>,

Expand Down Expand Up @@ -3619,9 +3687,9 @@ pub struct ExportArgs {

/// Only include dependencies from the specified dependency group.
///
/// May be provided multiple times.
///
/// The project itself will also be omitted.
///
/// May be provided multiple times.
#[arg(long, conflicts_with("group"))]
pub only_group: Vec<GroupName>,

Expand Down
25 changes: 24 additions & 1 deletion crates/uv-configuration/src/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl GroupsSpecification {
}
}

/// Iterate over all groups referenced in the [`DevGroupsSpecification`].
/// Iterate over all groups referenced in the [`GroupsSpecification`].
pub fn names(&self) -> impl Iterator<Item = &GroupName> {
match self {
GroupsSpecification::Include { include, exclude } => {
Expand All @@ -157,6 +157,18 @@ impl GroupsSpecification {
GroupsSpecification::Explicit { include } => include.contains(group),
}
}

/// Returns `true` if the specification will have no effect.
pub fn is_empty(&self) -> bool {
let GroupsSpecification::Include {
include: IncludeGroups::Some(includes),
exclude,
} = self
else {
return false;
};
includes.is_empty() && exclude.is_empty()
}
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -316,6 +328,17 @@ impl DevGroupsSpecification {
.as_ref()
.is_some_and(|groups| groups.contains(group))
}

/// Returns `true` if the specification will have no effect.
pub fn is_empty(&self) -> bool {
let groups_empty = self
.groups
.as_ref()
.map(GroupsSpecification::is_empty)
.unwrap_or(true);
let dev_empty = self.dev_mode().is_none();
groups_empty && dev_empty
}
}

impl From<DevMode> for DevGroupsSpecification {
Expand Down
32 changes: 28 additions & 4 deletions crates/uv-requirements/src/source_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use futures::stream::FuturesOrdered;
use futures::TryStreamExt;
use url::Url;

use uv_configuration::ExtrasSpecification;
use uv_configuration::{DevGroupsSpecification, ExtrasSpecification};
use uv_distribution::{DistributionDatabase, FlatRequiresDist, Reporter, RequiresDist};
use uv_distribution_types::{
BuildableSource, DirectorySourceUrl, HashGeneration, HashPolicy, SourceUrl, VersionId,
Expand All @@ -18,7 +18,7 @@ use uv_pep508::RequirementOrigin;
use uv_pypi_types::Requirement;
use uv_resolver::{InMemoryIndex, MetadataResponse};
use uv_types::{BuildContext, HashStrategy};

use uv_warnings::warn_user_once;
#[derive(Debug, Clone)]
pub struct SourceTreeResolution {
/// The requirements sourced from the source trees.
Expand All @@ -36,6 +36,8 @@ pub struct SourceTreeResolution {
pub struct SourceTreeResolver<'a, Context: BuildContext> {
/// The extras to include when resolving requirements.
extras: &'a ExtrasSpecification,
/// The groups to include when resolving requirements.
groups: &'a DevGroupsSpecification,
/// The hash policy to enforce.
hasher: &'a HashStrategy,
/// The in-memory index for resolving dependencies.
Expand All @@ -48,12 +50,14 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
/// Instantiate a new [`SourceTreeResolver`] for a given set of `source_trees`.
pub fn new(
extras: &'a ExtrasSpecification,
groups: &'a DevGroupsSpecification,
hasher: &'a HashStrategy,
index: &'a InMemoryIndex,
database: DistributionDatabase<'a, Context>,
) -> Self {
Self {
extras,
groups,
hasher,
index,
database,
Expand Down Expand Up @@ -85,7 +89,6 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
/// Infer the dependencies for a directory dependency.
async fn resolve_source_tree(&self, path: &Path) -> Result<SourceTreeResolution> {
let metadata = self.resolve_requires_dist(path).await?;

let origin = RequirementOrigin::Project(path.to_path_buf(), metadata.name.clone());

// Determine the extras to include when resolving the requirements.
Expand All @@ -96,7 +99,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
.collect::<Vec<_>>();

// Flatten any transitive extras.
let requirements =
let mut requirements =
FlatRequiresDist::from_requirements(metadata.requires_dist, &metadata.name)
.into_iter()
.map(|requirement| Requirement {
Expand All @@ -106,6 +109,27 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
})
.collect::<Vec<_>>();

// Apply dependency-groups
for (group_name, group) in &metadata.dependency_groups {
if self.groups.contains(group_name) {
requirements.extend(group.iter().cloned());
}
}
// Complain if dependency groups are named that don't appear.
// This is only a warning because *technically* we support passing in
// multiple pyproject.tomls, but at this level of abstraction we can't see them all,
// so hard erroring on "no pyproject.toml mentions this" is a bit difficult.
if let Some(groups) = self.groups.groups() {
for name in groups.names() {
if !metadata.dependency_groups.contains_key(name) {
warn_user_once!(
"The dependency-group '{name}' is not defined in {}",
path.display()
);
}
}
}

let project = metadata.name;
let extras = metadata.provides_extras;

Expand Down
5 changes: 5 additions & 0 deletions crates/uv-requirements/src/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ impl RequirementsSource {
Self::PyprojectToml(_) | Self::SetupPy(_) | Self::SetupCfg(_)
)
}

/// Returns `true` if the source allows groups to be specified.
pub fn allows_groups(&self) -> bool {
matches!(self, Self::PyprojectToml(_))
}
}

impl std::fmt::Display for RequirementsSource {
Expand Down
46 changes: 45 additions & 1 deletion crates/uv-settings/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use uv_distribution_types::{
};
use uv_install_wheel::linker::LinkMode;
use uv_macros::{CombineOptions, OptionsMetadata};
use uv_normalize::{ExtraName, PackageName};
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep508::Requirement;
use uv_pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
Expand Down Expand Up @@ -1111,6 +1111,50 @@ pub struct PipOptions {
"#
)]
pub no_extra: Option<Vec<ExtraName>>,
/// Include optional dependencies from the specified group; may be provided more than once.
///
/// Only applies to `pyproject.toml` sources.
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
group = ["dev", "docs"]
"#
)]
pub group: Option<Vec<GroupName>>,
/// Exclude optional dependencies from the specified group if `all-groups` are supplied
///
/// Only applies to `pyproject.toml` sources.
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
no-group = ["dev", "docs"]
"#
)]
pub no_group: Option<Vec<GroupName>>,
/// Exclude only dependencies from the specified group.
///
/// Only applies to `pyproject.toml` sources.
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
only-group = ["dev", "docs"]
"#
)]
pub only_group: Option<Vec<GroupName>>,
/// Include all groups.
///
/// Only applies to `pyproject.toml` sources.
#[option(
default = "false",
value_type = "bool",
example = r#"
all-groups = true
"#
)]
pub all_groups: Option<bool>,
/// Ignore package dependencies, instead only add those packages explicitly listed
/// on the command line to the resulting the requirements file.
#[option(
Expand Down
7 changes: 5 additions & 2 deletions crates/uv/src/commands/pip/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ use tracing::debug;
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, ExtrasSpecification, IndexStrategy,
LowerBound, NoBinary, NoBuild, PreviewMode, Reinstall, SourceStrategy, TrustedHost, Upgrade,
BuildOptions, Concurrency, ConfigSettings, Constraints, DevGroupsSpecification,
ExtrasSpecification, IndexStrategy, LowerBound, NoBinary, NoBuild, PreviewMode, Reinstall,
SourceStrategy, TrustedHost, Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::{BuildDispatch, SharedState};
Expand Down Expand Up @@ -55,6 +56,7 @@ pub(crate) async fn pip_compile(
overrides_from_workspace: Vec<Requirement>,
environments: SupportedEnvironments,
extras: ExtrasSpecification,
groups: DevGroupsSpecification,
output_file: Option<&Path>,
resolution_mode: ResolutionMode,
prerelease_mode: PrereleaseMode,
Expand Down Expand Up @@ -379,6 +381,7 @@ pub(crate) async fn pip_compile(
project,
BTreeSet::default(),
&extras,
&groups,
preferences,
EmptyInstalledPackages,
&hasher,
Expand Down
8 changes: 6 additions & 2 deletions crates/uv/src/commands/pip/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use tracing::{debug, enabled, Level};
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, ExtrasSpecification, HashCheckingMode,
IndexStrategy, LowerBound, PreviewMode, Reinstall, SourceStrategy, TrustedHost, Upgrade,
BuildOptions, Concurrency, ConfigSettings, Constraints, DevGroupsSpecification,
ExtrasSpecification, HashCheckingMode, IndexStrategy, LowerBound, PreviewMode, Reinstall,
SourceStrategy, TrustedHost, Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::{BuildDispatch, SharedState};
Expand Down Expand Up @@ -50,6 +51,7 @@ pub(crate) async fn pip_install(
constraints_from_workspace: Vec<Requirement>,
overrides_from_workspace: Vec<Requirement>,
extras: &ExtrasSpecification,
groups: &DevGroupsSpecification,
resolution_mode: ResolutionMode,
prerelease_mode: PrereleaseMode,
dependency_mode: DependencyMode,
Expand Down Expand Up @@ -115,6 +117,7 @@ pub(crate) async fn pip_install(
constraints,
overrides,
extras,
groups,
&client_builder,
)
.await?;
Expand Down Expand Up @@ -406,6 +409,7 @@ pub(crate) async fn pip_install(
project,
BTreeSet::default(),
extras,
groups,
preferences,
site_packages.clone(),
&hasher,
Expand Down
Loading

0 comments on commit 53706a1

Please sign in to comment.