Add uv sync --no-install-workspace to skip installation of all workspace members (#6539)

Extends #6538
See #4028

Another version of https://github.com/astral-sh/uv/pull/6398
This commit is contained in:
Zanie Blue 2024-08-23 15:39:33 -05:00 committed by GitHub
parent 4e82db093a
commit ca50243174
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 117 additions and 1 deletions

View file

@ -2282,6 +2282,16 @@ pub struct SyncArgs {
#[arg(long)]
pub no_install_project: bool,
/// Do not install any workspace members, including the root project.
///
/// By default, all of the workspace members and their dependencies are installed into the
/// environment. The `--no-install-workspace` option allows exclusion of all the workspace
/// members while retaining their dependencies. This is particularly useful in situations like
/// building Docker images where installing the workspace separately from its dependencies
/// allows optimal layer caching.
#[arg(long)]
pub no_install_workspace: bool,
/// Assert that the `uv.lock` will remain unchanged.
///
/// Requires that the lockfile is up-to-date. If the lockfile is missing or

View file

@ -604,6 +604,7 @@ pub(crate) async fn add(
&extras,
dev,
false,
false,
Modifications::Sufficient,
settings.as_ref().into(),
&state,

View file

@ -191,6 +191,7 @@ pub(crate) async fn remove(
let extras = ExtrasSpecification::All;
let dev = true;
let no_install_project = false;
let no_install_workspace = false;
// Initialize any shared state.
let state = SharedState::default();
@ -202,6 +203,7 @@ pub(crate) async fn remove(
&extras,
dev,
no_install_project,
no_install_workspace,
Modifications::Exact,
settings.as_ref().into(),
&state,

View file

@ -418,6 +418,7 @@ pub(crate) async fn run(
&extras,
dev,
false,
false,
Modifications::Sufficient,
settings.as_ref().into(),
&state,

View file

@ -33,6 +33,7 @@ pub(crate) async fn sync(
extras: ExtrasSpecification,
dev: bool,
no_install_project: bool,
no_install_workspace: bool,
modifications: Modifications,
python: Option<String>,
python_preference: PythonPreference,
@ -106,6 +107,7 @@ pub(crate) async fn sync(
&extras,
dev,
no_install_project,
no_install_workspace,
modifications,
settings.as_ref().into(),
&state,
@ -122,6 +124,7 @@ pub(crate) async fn sync(
}
/// Sync a lockfile with an environment.
#[allow(clippy::fn_params_excessive_bools)]
pub(super) async fn do_sync(
project: &VirtualProject,
venv: &PythonEnvironment,
@ -129,6 +132,7 @@ pub(super) async fn do_sync(
extras: &ExtrasSpecification,
dev: bool,
no_install_project: bool,
no_install_workspace: bool,
modifications: Modifications,
settings: InstallerSettingsRef<'_>,
state: &SharedState,
@ -195,6 +199,9 @@ pub(super) async fn do_sync(
// If `--no-install-project` is set, remove the project itself.
let resolution = apply_no_install_project(no_install_project, resolution, project);
// If `--no-install-workspace` is set, remove the project and any workspace members.
let resolution = apply_no_install_workspace(no_install_workspace, resolution, project);
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
store_credentials_from_url(url);
@ -299,3 +306,18 @@ fn apply_no_install_project(
resolution.filter(|dist| dist.name() != project_name)
}
fn apply_no_install_workspace(
no_install_workspace: bool,
resolution: distribution_types::Resolution,
project: &VirtualProject,
) -> distribution_types::Resolution {
if !no_install_workspace {
return resolution;
}
let workspace_packages = project.workspace().packages();
resolution.filter(|dist| {
!workspace_packages.contains_key(dist.name()) && Some(dist.name()) != project.project_name()
})
}

View file

@ -1103,6 +1103,7 @@ async fn run_project(
args.extras,
args.dev,
args.no_install_project,
args.no_install_workspace,
args.modifications,
args.python,
globals.python_preference,

View file

@ -618,6 +618,7 @@ pub(crate) struct SyncSettings {
pub(crate) extras: ExtrasSpecification,
pub(crate) dev: bool,
pub(crate) no_install_project: bool,
pub(crate) no_install_workspace: bool,
pub(crate) modifications: Modifications,
pub(crate) package: Option<PackageName>,
pub(crate) python: Option<String>,
@ -638,6 +639,7 @@ impl SyncSettings {
inexact,
exact,
no_install_project,
no_install_workspace,
locked,
frozen,
installer,
@ -671,6 +673,7 @@ impl SyncSettings {
),
dev: flag(dev, no_dev).unwrap_or(true),
no_install_project,
no_install_workspace,
modifications,
package,
python,

View file

@ -863,3 +863,70 @@ fn no_install_project() -> Result<()> {
Ok(())
}
/// Avoid syncing local dependencies for workspace dependencies when `--no-install-project` is provided, but
/// include the workspace dependency's dependencies.
#[test]
fn no_install_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.7.0", "child"]
[tool.uv.workspace]
members = ["child"]
[tool.uv.sources]
child = { workspace = true }
"#,
)?;
// Add a workspace member.
let child = context.temp_dir.child("child");
child.child("pyproject.toml").write_str(
r#"
[project]
name = "child"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig>1"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
"#,
)?;
child
.child("src")
.child("child")
.child("__init__.py")
.touch()?;
// Generate a lockfile.
context.lock().assert().success();
// Running with `--no-install-workspace` should install `anyio` and `iniconfig`, but not
// `project` or `child`.
uv_snapshot!(context.filters(), context.sync().arg("--no-install-workspace"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Prepared 4 packages in [TIME]
Installed 4 packages in [TIME]
+ anyio==3.7.0
+ idna==3.6
+ iniconfig==2.0.0
+ sniffio==1.3.1
"###);
Ok(())
}