Skip to content

Commit

Permalink
Add --exact flag to uv run (#10198)
Browse files Browse the repository at this point in the history
## Summary

`uv run --exact` will remove any unnecessary packages prior to running
the given command. (By default, `uv run` uses "inexact" semantics.)

Closes #7838.
  • Loading branch information
charliermarsh authored Dec 27, 2024
1 parent 0bc33e8 commit 49a2b6f
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 2 deletions.
11 changes: 11 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2718,6 +2718,17 @@ pub struct RunArgs {
#[arg(long)]
pub no_editable: bool,

/// Do not remove extraneous packages present in the environment.
#[arg(long, overrides_with("exact"), alias = "no-exact", hide = true)]
pub inexact: bool,

/// Perform an exact sync, removing extraneous packages.
///
/// When enabled, uv will remove any extraneous packages from the environment.
/// By default, `uv run` will make the minimum necessary changes to satisfy the requirements.
#[arg(long, overrides_with("inexact"))]
pub exact: bool,

/// Load environment variables from a `.env` file.
///
/// Can be provided multiple times, with subsequent files overriding values defined in
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub(crate) async fn run(
extras: ExtrasSpecification,
dev: DevGroupsSpecification,
editable: EditableMode,
modifications: Modifications,
python: Option<String>,
install_mirrors: PythonInstallMirrors,
settings: ResolverInstallerSettings,
Expand Down Expand Up @@ -705,7 +706,7 @@ pub(crate) async fn run(
&dev.with_defaults(defaults),
editable,
install_options,
Modifications::Sufficient,
modifications,
settings.as_ref().into(),
if show_resolution {
Box::new(DefaultInstallLogger)
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 @@ -1414,6 +1414,7 @@ async fn run_project(
args.extras,
args.dev,
args.editable,
args.modifications,
args.python,
args.install_mirrors,
args.settings,
Expand Down
8 changes: 8 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ pub(crate) struct RunSettings {
pub(crate) extras: ExtrasSpecification,
pub(crate) dev: DevGroupsSpecification,
pub(crate) editable: EditableMode,
pub(crate) modifications: Modifications,
pub(crate) with: Vec<String>,
pub(crate) with_editable: Vec<String>,
pub(crate) with_requirements: Vec<PathBuf>,
Expand Down Expand Up @@ -297,6 +298,8 @@ impl RunSettings {
module: _,
only_dev,
no_editable,
inexact,
exact,
script: _,
gui_script: _,
command: _,
Expand Down Expand Up @@ -336,6 +339,11 @@ impl RunSettings {
dev, no_dev, only_dev, group, no_group, only_group, all_groups,
),
editable: EditableMode::from_args(no_editable),
modifications: if flag(exact, inexact).unwrap_or(false) {
Modifications::Exact
} else {
Modifications::Sufficient
},
with: with
.into_iter()
.flat_map(CommaSeparatedRequirements::into_iter)
Expand Down
72 changes: 71 additions & 1 deletion crates/uv/tests/it/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ fn run_args() -> Result<()> {
Ok(())
}

/// Run without specifying any argunments.
/// Run without specifying any arguments.
///
/// This should list the available scripts.
#[test]
fn run_no_args() -> Result<()> {
Expand Down Expand Up @@ -807,6 +808,75 @@ fn run_managed_false() -> Result<()> {
Ok(())
}

#[test]
fn run_exact() -> Result<()> {
let context = TestContext::new("3.12");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["iniconfig"]
"#
})?;

uv_snapshot!(context.filters(), context.run().arg("python").arg("-c").arg("import iniconfig"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
"###);

// Remove `iniconfig`.
pyproject_toml.write_str(indoc! { r#"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio"]
"#
})?;

// By default, `uv run` uses inexact semantics, so both `iniconfig` and `anyio` should still be available.
uv_snapshot!(context.filters(), context.run().arg("python").arg("-c").arg("import iniconfig; import anyio"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==4.3.0
+ idna==3.6
+ sniffio==1.3.1
"###);

// But under `--exact`, `iniconfig` should not be available.
uv_snapshot!(context.filters(), context.run().arg("--exact").arg("python").arg("-c").arg("import iniconfig"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Uninstalled 1 package in [TIME]
- iniconfig==2.0.0
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'iniconfig'
"###);

Ok(())
}

#[test]
fn run_with() -> Result<()> {
let context = TestContext::new("3.12");
Expand Down
4 changes: 4 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ uv run [OPTIONS] [COMMAND]
<p>Can be provided multiple times, with subsequent files overriding values defined in previous files.</p>

<p>May also be set with the <code>UV_ENV_FILE</code> environment variable.</p>
</dd><dt><code>--exact</code></dt><dd><p>Perform an exact sync, removing extraneous packages.</p>

<p>When enabled, uv will remove any extraneous packages from the environment. By default, <code>uv run</code> will make the minimum necessary changes to satisfy the requirements.</p>

</dd><dt><code>--exclude-newer</code> <i>exclude-newer</i></dt><dd><p>Limit candidate packages to those that were uploaded prior to the given date.</p>

<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>
Expand Down

0 comments on commit 49a2b6f

Please sign in to comment.