diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 6befc34b95950..fa601a813c84c 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1432,7 +1432,7 @@ pub struct PipCompileArgs { /// Include optional dependencies from the specified extra name; may be provided more than once. /// /// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources. - #[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] + #[arg(long, value_delimiter = ',', conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] pub extra: Option>, /// Include all optional dependencies. @@ -1789,7 +1789,7 @@ pub struct PipSyncArgs { /// Include optional dependencies from the specified extra name; may be provided more than once. /// /// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources. - #[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] + #[arg(long, value_delimiter = ',', conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] pub extra: Option>, /// Include all optional dependencies. @@ -2158,7 +2158,7 @@ pub struct PipInstallArgs { /// Include optional dependencies from the specified extra name; may be provided more than once. /// /// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources. - #[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] + #[arg(long, value_delimiter = ',', conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] pub extra: Option>, /// Include all optional dependencies. @@ -3459,6 +3459,7 @@ pub struct RunArgs { long, conflicts_with = "all_extras", conflicts_with = "only_group", + value_delimiter = ',', value_parser = extra_name_with_clap_error, value_hint = ValueHint::Other, )] @@ -3786,6 +3787,7 @@ pub struct SyncArgs { long, conflicts_with = "all_extras", conflicts_with = "only_group", + value_delimiter = ',', value_parser = extra_name_with_clap_error, value_hint = ValueHint::Other, )] @@ -4793,7 +4795,7 @@ pub struct ExportArgs { /// Include optional dependencies from the specified extra name. /// /// May be provided more than once. - #[arg(long, conflicts_with = "all_extras", conflicts_with = "only_group", value_parser = extra_name_with_clap_error)] + #[arg(long, value_delimiter = ',', conflicts_with = "all_extras", conflicts_with = "only_group", value_parser = extra_name_with_clap_error)] pub extra: Option>, /// Include all optional dependencies. diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index cdcdeb3628acf..25638b81217b6 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -9296,6 +9296,45 @@ fn sync_all_extras() -> Result<()> { Ok(()) } +#[test] +fn sync_extra_comma_separated() -> 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 = [] + + [project.optional-dependencies] + types = ["typing-extensions>=4"] + async = ["anyio>3"] + "#, + )?; + + context.lock().assert().success(); + + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("types,async"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + + typing-extensions==4.10.0 + "#); + + Ok(()) +} + /// Sync all members in a workspace with dynamic extras. #[test] fn sync_all_extras_dynamic() -> Result<()> {