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)] #[arg(long)]
pub no_install_project: bool, 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. /// 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -863,3 +863,70 @@ fn no_install_project() -> Result<()> {
Ok(()) 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(())
}

View File

@ -191,7 +191,7 @@ If not mounting the cache, image size can be reduced with `--no-cache` flag.
### Intermediate layers ### Intermediate layers
If you're using uv to manage your project, you can improve build times by moving your transitive If you're using uv to manage your project, you can improve build times by moving your transitive
dependency installation into its own layer via `uv sync --no-install-project`. dependency installation into its own layer via the `--no-install` options.
`uv sync --no-install-project` will install the dependencies of the project but not the project `uv sync --no-install-project` will install the dependencies of the project but not the project
itself. Since the project changes frequently, but its dependencies are generally static, this can be itself. Since the project changes frequently, but its dependencies are generally static, this can be
@ -216,3 +216,8 @@ WORKDIR /app
# Sync the project # Sync the project
RUN uv sync --frozen RUN uv sync --frozen
``` ```
!!! tip
If you're using a [workspace](../../concepts/workspaces.md), then consider the
`--no-install-workspace` flag which excludes the project _and_ any workspace members.

View File

@ -1174,6 +1174,10 @@ uv sync [OPTIONS]
<p>By default, the current project is installed into the environment with all of its dependencies. The <code>--no-install-project</code> option allows the project to be excluded, but all of its dependencies are still installed. This is particularly useful in situations like building Docker images where installing the project separately from its dependencies allows optimal layer caching.</p> <p>By default, the current project is installed into the environment with all of its dependencies. The <code>--no-install-project</code> option allows the project to be excluded, but all of its dependencies are still installed. This is particularly useful in situations like building Docker images where installing the project separately from its dependencies allows optimal layer caching.</p>
</dd><dt><code>--no-install-workspace</code></dt><dd><p>Do not install any workspace members, including the root project.</p>
<p>By default, all of the workspace members and their dependencies are installed into the environment. The <code>--no-install-workspace</code> 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.</p>
</dd><dt><code>--no-progress</code></dt><dd><p>Hide all progress outputs.</p> </dd><dt><code>--no-progress</code></dt><dd><p>Hide all progress outputs.</p>
<p>For example, spinners or progress bars.</p> <p>For example, spinners or progress bars.</p>