Add --exact flag to uv run (#10198)

## Summary

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

Closes https://github.com/astral-sh/uv/issues/7838.
This commit is contained in:
Charlie Marsh
2024-12-27 11:43:30 -05:00
committed by GitHub
parent 0bc33e87b8
commit 49a2b6f85c
6 changed files with 97 additions and 2 deletions
+11
View File
@@ -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
+2 -1
View File
@@ -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,
@@ -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)
+1
View File
@@ -1414,6 +1414,7 @@ async fn run_project(
args.extras,
args.dev,
args.editable,
args.modifications,
args.python,
args.install_mirrors,
args.settings,
+8
View File
@@ -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>,
@@ -297,6 +298,8 @@ impl RunSettings {
module: _,
only_dev,
no_editable,
inexact,
exact,
script: _,
gui_script: _,
command: _,
@@ -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)
+71 -1
View File
@@ -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<()> {
@@ -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");
+4
View File
@@ -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>