Avoid error for `--group` defined in non-root workspace member (#8734)

## Summary

Closes https://github.com/astral-sh/uv/issues/8722.
This commit is contained in:
Charlie Marsh 2024-10-31 16:11:22 -04:00 committed by GitHub
parent 24ad43e79c
commit 5a46b5e52c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 69 additions and 14 deletions

View File

@ -73,7 +73,7 @@ pub(crate) async fn export(
}; };
// Determine the default groups to include. // Determine the default groups to include.
validate_dependency_groups(project.pyproject_toml(), &dev)?; validate_dependency_groups(&project, &dev)?;
let defaults = default_dependency_groups(project.pyproject_toml())?; let defaults = default_dependency_groups(project.pyproject_toml())?;
let VirtualProject::Project(project) = project else { let VirtualProject::Project(project) = project else {

View File

@ -38,7 +38,7 @@ use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::{warn_user, warn_user_once}; use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::dependency_groups::DependencyGroupError; use uv_workspace::dependency_groups::DependencyGroupError;
use uv_workspace::pyproject::PyProjectToml; use uv_workspace::pyproject::PyProjectToml;
use uv_workspace::Workspace; use uv_workspace::{VirtualProject, Workspace};
use crate::commands::pip::loggers::{InstallLogger, ResolveLogger}; use crate::commands::pip::loggers::{InstallLogger, ResolveLogger};
use crate::commands::pip::operations::{Changelog, Modifications}; use crate::commands::pip::operations::{Changelog, Modifications};
@ -126,7 +126,10 @@ pub(crate) enum ProjectError {
), ),
#[error("Group `{0}` is not defined in the project's `dependency-group` table")] #[error("Group `{0}` is not defined in the project's `dependency-group` table")]
MissingGroup(GroupName), MissingGroupProject(GroupName),
#[error("Group `{0}` is not defined in any project's `dependency-group` table")]
MissingGroupWorkspace(GroupName),
#[error("Default group `{0}` (from `tool.uv.default-groups`) is not defined in the project's `dependency-group` table")] #[error("Default group `{0}` (from `tool.uv.default-groups`) is not defined in the project's `dependency-group` table")]
MissingDefaultGroup(GroupName), MissingDefaultGroup(GroupName),
@ -1366,7 +1369,7 @@ pub(crate) async fn script_python_requirement(
/// Validate the dependency groups requested by the [`DevGroupsSpecification`]. /// Validate the dependency groups requested by the [`DevGroupsSpecification`].
#[allow(clippy::result_large_err)] #[allow(clippy::result_large_err)]
pub(crate) fn validate_dependency_groups( pub(crate) fn validate_dependency_groups(
pyproject_toml: &PyProjectToml, project: &VirtualProject,
dev: &DevGroupsSpecification, dev: &DevGroupsSpecification,
) -> Result<(), ProjectError> { ) -> Result<(), ProjectError> {
for group in dev for group in dev
@ -1374,12 +1377,38 @@ pub(crate) fn validate_dependency_groups(
.into_iter() .into_iter()
.flat_map(GroupsSpecification::names) .flat_map(GroupsSpecification::names)
{ {
if !pyproject_toml match project {
.dependency_groups VirtualProject::Project(project) => {
.as_ref() // The group must be defined in the target project.
.is_some_and(|groups| groups.contains_key(group)) if !project
{ .current_project()
return Err(ProjectError::MissingGroup(group.clone())); .pyproject_toml()
.dependency_groups
.as_ref()
.is_some_and(|groups| groups.contains_key(group))
{
return Err(ProjectError::MissingGroupProject(group.clone()));
}
}
VirtualProject::NonProject(workspace) => {
// The group must be defined in at least one workspace package.
if !workspace
.pyproject_toml()
.dependency_groups
.as_ref()
.is_some_and(|groups| groups.contains_key(group))
{
if workspace.packages().values().all(|package| {
!package
.pyproject_toml()
.dependency_groups
.as_ref()
.is_some_and(|groups| groups.contains_key(group))
}) {
return Err(ProjectError::MissingGroupWorkspace(group.clone()));
}
}
}
} }
} }
Ok(()) Ok(())

View File

@ -551,7 +551,7 @@ pub(crate) async fn run(
} }
} else { } else {
// Determine the default groups to include. // Determine the default groups to include.
validate_dependency_groups(project.pyproject_toml(), &dev)?; validate_dependency_groups(&project, &dev)?;
let defaults = default_dependency_groups(project.pyproject_toml())?; let defaults = default_dependency_groups(project.pyproject_toml())?;
// Determine the lock mode. // Determine the lock mode.

View File

@ -96,7 +96,7 @@ pub(crate) async fn sync(
} }
// Determine the default groups to include. // Determine the default groups to include.
validate_dependency_groups(project.pyproject_toml(), &dev)?; validate_dependency_groups(&project, &dev)?;
let defaults = default_dependency_groups(project.pyproject_toml())?; let defaults = default_dependency_groups(project.pyproject_toml())?;
// Discover or create the virtual environment. // Discover or create the virtual environment.

View File

@ -9,7 +9,7 @@ use uv_configuration::{Concurrency, DevGroupsSpecification, LowerBound, TargetTr
use uv_pep508::PackageName; use uv_pep508::PackageName;
use uv_python::{PythonDownloads, PythonPreference, PythonRequest, PythonVersion}; use uv_python::{PythonDownloads, PythonPreference, PythonRequest, PythonVersion};
use uv_resolver::TreeDisplay; use uv_resolver::TreeDisplay;
use uv_workspace::{DiscoveryOptions, Workspace}; use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace};
use crate::commands::pip::loggers::DefaultResolveLogger; use crate::commands::pip::loggers::DefaultResolveLogger;
use crate::commands::pip::resolution_markers; use crate::commands::pip::resolution_markers;
@ -50,7 +50,7 @@ pub(crate) async fn tree(
let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?; let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?;
// Determine the default groups to include. // Determine the default groups to include.
validate_dependency_groups(workspace.pyproject_toml(), &dev)?; validate_dependency_groups(&VirtualProject::NonProject(workspace.clone()), &dev)?;
let defaults = default_dependency_groups(workspace.pyproject_toml())?; let defaults = default_dependency_groups(workspace.pyproject_toml())?;
// Find an interpreter for the project, unless `--frozen` and `--universal` are both set. // Find an interpreter for the project, unless `--frozen` and `--universal` are both set.

View File

@ -502,6 +502,9 @@ fn sync_legacy_non_project_group() -> Result<()> {
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = ["iniconfig>1"] dependencies = ["iniconfig>1"]
[dependency-groups]
baz = ["typing-extensions"]
[build-system] [build-system]
requires = ["setuptools>=42"] requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
@ -559,6 +562,29 @@ fn sync_legacy_non_project_group() -> Result<()> {
+ typing-extensions==4.10.0 + typing-extensions==4.10.0
"###); "###);
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Uninstalled 1 package in [TIME]
Installed 2 packages in [TIME]
+ child==0.1.0 (from file://[TEMP_DIR]/child)
+ iniconfig==2.0.0
- typing-extensions==4.10.0
"###);
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bop"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Group `bop` is not defined in any project's `dependency-group` table
"###);
Ok(()) Ok(())
} }