From c7ccf88939b6fc16a046d2d304642fc6332a0bf8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 21 Oct 2024 22:08:31 -0400 Subject: [PATCH] Error when --group includes non-existent groups (#8394) ## Summary Part of https://github.com/astral-sh/uv/pull/8272. --- crates/uv-configuration/src/dev.rs | 2 + crates/uv-workspace/src/pyproject.rs | 5 +++ crates/uv/src/commands/project/export.rs | 14 +++++++ crates/uv/src/commands/project/run.rs | 14 +++++++ crates/uv/src/commands/project/sync.rs | 14 +++++++ crates/uv/tests/it/sync.rs | 47 ++++++++++++++++++++++++ 6 files changed, 96 insertions(+) diff --git a/crates/uv-configuration/src/dev.rs b/crates/uv-configuration/src/dev.rs index c482ec404..b82bc0959 100644 --- a/crates/uv-configuration/src/dev.rs +++ b/crates/uv-configuration/src/dev.rs @@ -163,10 +163,12 @@ impl DevGroupsSpecification { (self.dev.is_none() || self.dev.as_ref().is_some_and(DevMode::prod)) && self.groups.prod() } + /// Returns the flag that was used to request development dependencies. pub fn dev_mode(&self) -> Option<&DevMode> { self.dev.as_ref() } + /// Returns the list of groups to include. pub fn groups(&self) -> &GroupsSpecification { &self.groups } diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 24f2e0cec..0ac7b908b 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -555,6 +555,11 @@ impl DependencyGroups { self.0.get(group) } + /// Returns `true` if the dependency group is in the list. + pub fn contains_key(&self, group: &GroupName) -> bool { + self.0.contains_key(group) + } + /// Returns an iterator over the dependency groups. pub fn iter(&self) -> impl Iterator)> { self.0.iter() diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index a0cf290a3..9cb7b9b1b 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -70,6 +70,20 @@ pub(crate) async fn export( VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await? }; + // Validate the requested dependency groups. + for group in dev.groups().iter() { + if !project + .pyproject_toml() + .dependency_groups + .as_ref() + .is_some_and(|groups| groups.contains_key(group)) + { + return Err(anyhow::anyhow!( + "Group `{group}` is not defined in the project's `dependency-group` table" + )); + } + } + let VirtualProject::Project(project) = project else { return Err(anyhow::anyhow!("Legacy non-project roots are not supported in `uv export`; add a `[project]` table to your `pyproject.toml` to enable exports")); }; diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index f7a2041b0..9038cdf37 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -469,6 +469,20 @@ pub(crate) async fn run( ); } + // Validate the requested dependency groups. + for group in dev.groups().iter() { + if !project + .pyproject_toml() + .dependency_groups + .as_ref() + .is_some_and(|groups| groups.contains_key(group)) + { + return Err(anyhow::anyhow!( + "Group `{group}` is not defined in the project's `dependency-group` table" + )); + } + } + let venv = if isolated { debug!("Creating isolated virtual environment"); diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index aa4fe9994..01c388a1c 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -93,6 +93,20 @@ pub(crate) async fn sync( warn_user!("Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system`"); } + // Validate the requested dependency groups. + for group in dev.groups().iter() { + if !project + .pyproject_toml() + .dependency_groups + .as_ref() + .is_some_and(|groups| groups.contains_key(group)) + { + return Err(anyhow::anyhow!( + "Group `{group}` is not defined in the project's `dependency-group` table" + )); + } + } + // Discover or create the virtual environment. let venv = project::get_or_init_environment( target.workspace(), diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 2b8fb22f4..2fa4ae49e 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -1226,6 +1226,53 @@ fn sync_dev_group() -> Result<()> { Ok(()) } +#[test] +fn sync_non_existent_group() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["typing-extensions"] + + [dependency-groups] + foo = [] + bar = ["requests"] + "#, + )?; + + context.lock().assert().success(); + + // Requesting a non-existent group should fail. + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Group `baz` is not defined in the project's `dependency-group` table + "###); + + // Requesting an empty group should succeed. + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 7 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + typing-extensions==4.10.0 + "###); + + Ok(()) +} + /// Regression test for . /// /// Previously, we would read metadata statically from pyproject.toml and write that to `uv.lock`. In