Skip to content

Commit

Permalink
Add support for uv run --all
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Nov 1, 2024
1 parent 1e997d5 commit 0a6d802
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 4 deletions.
12 changes: 11 additions & 1 deletion crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2740,10 +2740,20 @@ pub struct RunArgs {
#[command(flatten)]
pub refresh: RefreshArgs,

/// Run the command with all workspace members installed.
///
/// The workspace's environment (`.venv`) is updated to include all workspace
/// members.
///
/// Any extras or groups specified via `--extra`, `--group`, or related options
/// will be applied to all workspace members.
#[arg(long, conflicts_with = "package")]
pub all: bool,

/// Run the command in a specific package in the workspace.
///
/// If the workspace member does not exist, uv will exit with an error.
#[arg(long)]
#[arg(long, conflicts_with = "all")]
pub package: Option<PackageName>,

/// Avoid discovering the project or workspace.
Expand Down
16 changes: 14 additions & 2 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub(crate) async fn run(
frozen: bool,
no_sync: bool,
isolated: bool,
all: bool,
package: Option<PackageName>,
no_project: bool,
no_config: bool,
Expand Down Expand Up @@ -346,6 +347,11 @@ pub(crate) async fn run(
if let Some(flag) = dev.groups().and_then(GroupsSpecification::as_flag) {
warn_user!("`{flag}` is not supported for Python scripts with inline metadata");
}
if all {
warn_user!(
"`--all` is a no-op for Python scripts with inline metadata, which always run in isolation"
);
}
if package.is_some() {
warn_user!(
"`--package` is a no-op for Python scripts with inline metadata, which always run in isolation"
Expand Down Expand Up @@ -550,8 +556,14 @@ pub(crate) async fn run(
.flatten();
}
} else {
let target = if all {
InstallTarget::from_workspace(&project)
} else {
InstallTarget::from_project(&project)
};

// Determine the default groups to include.
validate_dependency_groups(InstallTarget::from_project(&project), &dev)?;
validate_dependency_groups(target, &dev)?;
let defaults = default_dependency_groups(project.pyproject_toml())?;

// Determine the lock mode.
Expand Down Expand Up @@ -607,7 +619,7 @@ pub(crate) async fn run(
let install_options = InstallOptions::default();

project::sync::do_sync(
InstallTarget::from_project(&project),
target,
&venv,
result.lock(),
&extras,
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,7 @@ async fn run_project(
args.frozen,
args.no_sync,
args.isolated,
args.all,
args.package,
args.no_project,
no_config,
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ pub(crate) struct RunSettings {
pub(crate) with_requirements: Vec<PathBuf>,
pub(crate) isolated: bool,
pub(crate) show_resolution: bool,
pub(crate) all: bool,
pub(crate) package: Option<PackageName>,
pub(crate) no_project: bool,
pub(crate) no_sync: bool,
Expand Down Expand Up @@ -271,6 +272,7 @@ impl RunSettings {
installer,
build,
refresh,
all,
package,
no_project,
python,
Expand All @@ -296,6 +298,7 @@ impl RunSettings {
.collect(),
isolated,
show_resolution,
all,
package,
no_project,
no_sync,
Expand Down
163 changes: 163 additions & 0 deletions crates/uv/tests/it/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,169 @@ fn run_with() -> Result<()> {
Ok(())
}

/// Sync all members in a workspace.
#[test]
fn run_in_workspace() -> 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 = ["anyio>3"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
[tool.uv.workspace]
members = ["child1", "child2"]
[tool.uv.sources]
child1 = { workspace = true }
child2 = { workspace = true }
"#,
)?;
context
.temp_dir
.child("src")
.child("project")
.child("__init__.py")
.touch()?;

let child1 = context.temp_dir.child("child1");
child1.child("pyproject.toml").write_str(
r#"
[project]
name = "child1"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig>1"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
child1
.child("src")
.child("child1")
.child("__init__.py")
.touch()?;

let child2 = context.temp_dir.child("child2");
child2.child("pyproject.toml").write_str(
r#"
[project]
name = "child2"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["typing-extensions>4"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
child2
.child("src")
.child("child2")
.child("__init__.py")
.touch()?;

let test_script = context.temp_dir.child("main.py");
test_script.write_str(indoc! { r"
import anyio
"
})?;

uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 8 packages in [TIME]
Prepared 4 packages in [TIME]
Installed 4 packages in [TIME]
+ anyio==4.3.0
+ idna==3.6
+ project==0.1.0 (from file://[TEMP_DIR]/)
+ sniffio==1.3.1
"###);

let test_script = context.temp_dir.child("main.py");
test_script.write_str(indoc! { r"
import iniconfig
"
})?;

uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 8 packages in [TIME]
Audited 4 packages in [TIME]
Traceback (most recent call last):
File "[TEMP_DIR]/main.py", line 1, in <module>
import iniconfig
ModuleNotFoundError: No module named 'iniconfig'
"###);

uv_snapshot!(context.filters(), context.run().arg("--package").arg("child1").arg("main.py"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 8 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ child1==0.1.0 (from file://[TEMP_DIR]/child1)
+ iniconfig==2.0.0
"###);

let test_script = context.temp_dir.child("main.py");
test_script.write_str(indoc! { r"
import typing_extensions
"
})?;

uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 8 packages in [TIME]
Audited 4 packages in [TIME]
Traceback (most recent call last):
File "[TEMP_DIR]/main.py", line 1, in <module>
import typing_extensions
ModuleNotFoundError: No module named 'typing_extensions'
"###);

uv_snapshot!(context.filters(), context.run().arg("--all").arg("main.py"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 8 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ child2==0.1.0 (from file://[TEMP_DIR]/child2)
+ typing-extensions==4.10.0
"###);

Ok(())
}

#[test]
fn run_with_editable() -> Result<()> {
let context = TestContext::new("3.12");
Expand Down
8 changes: 7 additions & 1 deletion docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@ uv run [OPTIONS] [COMMAND]

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt><code>--all-extras</code></dt><dd><p>Include all optional dependencies.</p>
<dl class="cli-reference"><dt><code>--all</code></dt><dd><p>Run the command with all workspace members installed.</p>

<p>The workspace&#8217;s environment (<code>.venv</code>) is updated to include all workspace members.</p>

<p>Any extras or groups specified via <code>--extra</code>, <code>--group</code>, or related options will be applied to all workspace members.</p>

</dd><dt><code>--all-extras</code></dt><dd><p>Include all optional dependencies.</p>

<p>Optional dependencies are defined via <code>project.optional-dependencies</code> in a <code>pyproject.toml</code>.</p>

Expand Down

0 comments on commit 0a6d802

Please sign in to comment.