Add uv run --no-sync (#7192)

## Summary

When `--no-sync` is provided, we won't lock or sync, but we will run the
command in the project environment.

Closes https://github.com/astral-sh/uv/issues/7165.
This commit is contained in:
Charlie Marsh 2024-09-10 17:29:43 -04:00 committed by GitHub
parent 4b7fed84eb
commit 3f011f3b7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 142 additions and 56 deletions

View file

@ -2434,6 +2434,13 @@ pub struct RunArgs {
#[arg(long)] #[arg(long)]
pub isolated: bool, pub isolated: bool,
/// Avoid syncing the virtual environment.
///
/// Implies `--frozen`, as the project dependencies will be ignored (i.e., the lockfile will not
/// be updated, since the environment will not be synced regardless).
#[arg(long, conflicts_with = "frozen")]
pub no_sync: bool,
/// Assert that the `uv.lock` will remain unchanged. /// Assert that the `uv.lock` will remain unchanged.
/// ///
/// Requires that the lockfile is up-to-date. If the lockfile is missing or /// Requires that the lockfile is up-to-date. If the lockfile is missing or
@ -2735,7 +2742,7 @@ pub struct AddArgs {
#[arg(long)] #[arg(long)]
pub extra: Option<Vec<ExtraName>>, pub extra: Option<Vec<ExtraName>>,
/// Avoid syncing the virtual environment after re-locking the project. /// Avoid syncing the virtual environment.
#[arg(long, conflicts_with = "frozen")] #[arg(long, conflicts_with = "frozen")]
pub no_sync: bool, pub no_sync: bool,

View file

@ -49,6 +49,7 @@ pub(crate) async fn run(
show_resolution: bool, show_resolution: bool,
locked: bool, locked: bool,
frozen: bool, frozen: bool,
no_sync: bool,
isolated: bool, isolated: bool,
package: Option<PackageName>, package: Option<PackageName>,
no_project: bool, no_project: bool,
@ -258,6 +259,11 @@ pub(crate) async fn run(
"`--frozen` is a no-op for Python scripts with inline metadata, which always run in isolation" "`--frozen` is a no-op for Python scripts with inline metadata, which always run in isolation"
); );
} }
if no_sync {
warn_user!(
"`--no-sync` is a no-op for Python scripts with inline metadata, which always run in isolation"
);
}
if isolated { if isolated {
warn_user!( warn_user!(
"`--isolated` is a no-op for Python scripts with inline metadata, which always run in isolation" "`--isolated` is a no-op for Python scripts with inline metadata, which always run in isolation"
@ -318,6 +324,9 @@ pub(crate) async fn run(
if frozen { if frozen {
warn_user!("`--frozen` has no effect when used alongside `--no-project`"); warn_user!("`--frozen` has no effect when used alongside `--no-project`");
} }
if no_sync {
warn_user!("`--no-sync` has no effect when used alongside `--no-project`");
}
} else if project.is_none() { } else if project.is_none() {
// If we can't find a project and the user provided a project-only setting, warn. // If we can't find a project and the user provided a project-only setting, warn.
if !extras.is_empty() { if !extras.is_empty() {
@ -329,8 +338,8 @@ pub(crate) async fn run(
if locked { if locked {
warn_user!("`--locked` has no effect when used outside of a project"); warn_user!("`--locked` has no effect when used outside of a project");
} }
if frozen { if no_sync {
warn_user!("`--frozen` has no effect when used outside of a project"); warn_user!("`--no-sync` has no effect when used outside of a project");
} }
} }
@ -414,60 +423,64 @@ pub(crate) async fn run(
.await? .await?
}; };
let result = match project::lock::do_safe_lock( if no_sync {
locked, debug!("Skipping environment synchronization due to `--no-sync`");
frozen, } else {
project.workspace(), let result = match project::lock::do_safe_lock(
venv.interpreter(), locked,
settings.as_ref().into(), frozen,
if show_resolution { project.workspace(),
Box::new(DefaultResolveLogger) venv.interpreter(),
} else { settings.as_ref().into(),
Box::new(SummaryResolveLogger) if show_resolution {
}, Box::new(DefaultResolveLogger)
connectivity, } else {
concurrency, Box::new(SummaryResolveLogger)
native_tls, },
cache, connectivity,
printer, concurrency,
) native_tls,
.await cache,
{ printer,
Ok(result) => result, )
Err(ProjectError::Operation(operations::Error::Resolve( .await
uv_resolver::ResolveError::NoSolution(err), {
))) => { Ok(result) => result,
let report = miette::Report::msg(format!("{err}")).context(err.header()); Err(ProjectError::Operation(operations::Error::Resolve(
eprint!("{report:?}"); uv_resolver::ResolveError::NoSolution(err),
return Ok(ExitStatus::Failure); ))) => {
} let report = miette::Report::msg(format!("{err}")).context(err.header());
Err(err) => return Err(err.into()), eprint!("{report:?}");
}; return Ok(ExitStatus::Failure);
}
Err(err) => return Err(err.into()),
};
let install_options = InstallOptions::default(); let install_options = InstallOptions::default();
project::sync::do_sync( project::sync::do_sync(
InstallTarget::from(&project), InstallTarget::from(&project),
&venv, &venv,
result.lock(), result.lock(),
&extras, &extras,
dev, dev,
install_options, install_options,
Modifications::Sufficient, Modifications::Sufficient,
settings.as_ref().into(), settings.as_ref().into(),
&state, &state,
if show_resolution { if show_resolution {
Box::new(DefaultInstallLogger) Box::new(DefaultInstallLogger)
} else { } else {
Box::new(SummaryInstallLogger) Box::new(SummaryInstallLogger)
}, },
connectivity, connectivity,
concurrency, concurrency,
native_tls, native_tls,
cache, cache,
printer, printer,
) )
.await?; .await?;
}
venv.into_interpreter() venv.into_interpreter()
} else { } else {

View file

@ -1123,6 +1123,7 @@ async fn run_project(
args.show_resolution || globals.verbose > 0, args.show_resolution || globals.verbose > 0,
args.locked, args.locked,
args.frozen, args.frozen,
args.no_sync,
args.isolated, args.isolated,
args.package, args.package,
args.no_project, args.no_project,

View file

@ -217,6 +217,7 @@ pub(crate) struct RunSettings {
pub(crate) show_resolution: bool, pub(crate) show_resolution: bool,
pub(crate) package: Option<PackageName>, pub(crate) package: Option<PackageName>,
pub(crate) no_project: bool, pub(crate) no_project: bool,
pub(crate) no_sync: bool,
pub(crate) python: Option<String>, pub(crate) python: Option<String>,
pub(crate) refresh: Refresh, pub(crate) refresh: Refresh,
pub(crate) settings: ResolverInstallerSettings, pub(crate) settings: ResolverInstallerSettings,
@ -237,6 +238,7 @@ impl RunSettings {
with_editable, with_editable,
with_requirements, with_requirements,
isolated, isolated,
no_sync,
locked, locked,
frozen, frozen,
installer, installer,
@ -266,6 +268,7 @@ impl RunSettings {
show_resolution, show_resolution,
package, package,
no_project, no_project,
no_sync,
python, python,
refresh: Refresh::from(refresh), refresh: Refresh::from(refresh),
settings: ResolverInstallerSettings::combine( settings: ResolverInstallerSettings::combine(

View file

@ -976,6 +976,62 @@ fn run_frozen() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn run_no_sync() -> 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.7.0"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
// Running with `--no-sync` should succeed error, even if the lockfile isn't present.
uv_snapshot!(context.filters(), context.run().arg("--no-sync").arg("--").arg("python").arg("--version"), @r###"
success: true
exit_code: 0
----- stdout -----
Python 3.12.[X]
----- stderr -----
"###);
context.lock().assert().success();
// Running with `--no-sync` should not install any requirements.
uv_snapshot!(context.filters(), context.run().arg("--no-sync").arg("--").arg("python").arg("--version"), @r###"
success: true
exit_code: 0
----- stdout -----
Python 3.12.[X]
----- stderr -----
"###);
context.sync().assert().success();
// But it should have access to the installed packages.
uv_snapshot!(context.filters(), context.run().arg("--no-sync").arg("--").arg("python").arg("-c").arg("import anyio; print(anyio.__name__)"), @r###"
success: true
exit_code: 0
----- stdout -----
anyio
----- stderr -----
"###);
Ok(())
}
#[test] #[test]
fn run_empty_requirements_txt() -> Result<()> { fn run_empty_requirements_txt() -> Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.12");

View file

@ -358,6 +358,8 @@ and not usable by other tools.
To avoid updating the lockfile during `uv sync` and `uv run` invocations, use the `--frozen` flag. To avoid updating the lockfile during `uv sync` and `uv run` invocations, use the `--frozen` flag.
To avoid updating the environment during `uv run` invocations, use the `--no-sync` flag.
To assert the lockfile matches the project metadata, use the `--locked` flag. If the lockfile is not To assert the lockfile matches the project metadata, use the `--locked` flag. If the lockfile is not
up-to-date, an error will be raised instead of updating the lockfile. up-to-date, an error will be raised instead of updating the lockfile.

View file

@ -265,6 +265,10 @@ uv run [OPTIONS] <COMMAND>
</dd><dt><code>--no-sources</code></dt><dd><p>Ignore the <code>tool.uv.sources</code> table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources</p> </dd><dt><code>--no-sources</code></dt><dd><p>Ignore the <code>tool.uv.sources</code> table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources</p>
</dd><dt><code>--no-sync</code></dt><dd><p>Avoid syncing the virtual environment.</p>
<p>Implies <code>--frozen</code>, as the project dependencies will be ignored (i.e., the lockfile will not be updated, since the environment will not be synced regardless).</p>
</dd><dt><code>--offline</code></dt><dd><p>Disable network access.</p> </dd><dt><code>--offline</code></dt><dd><p>Disable network access.</p>
<p>When disabled, uv will only use locally cached data and locally available files.</p> <p>When disabled, uv will only use locally cached data and locally available files.</p>
@ -721,7 +725,7 @@ uv add [OPTIONS] <PACKAGES|--requirements <REQUIREMENTS>>
</dd><dt><code>--no-sources</code></dt><dd><p>Ignore the <code>tool.uv.sources</code> table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources</p> </dd><dt><code>--no-sources</code></dt><dd><p>Ignore the <code>tool.uv.sources</code> table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources</p>
</dd><dt><code>--no-sync</code></dt><dd><p>Avoid syncing the virtual environment after re-locking the project</p> </dd><dt><code>--no-sync</code></dt><dd><p>Avoid syncing the virtual environment</p>
</dd><dt><code>--offline</code></dt><dd><p>Disable network access.</p> </dd><dt><code>--offline</code></dt><dd><p>Disable network access.</p>