Add uv sync --no-install-project to skip installation of the project (#6538)

See #4028

A smaller version of https://github.com/astral-sh/uv/pull/6398

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
Zanie Blue 2024-08-23 15:19:47 -05:00 committed by GitHub
parent e0abab8259
commit be1599ebf6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 140 additions and 4 deletions

View file

@ -70,6 +70,29 @@ impl Resolution {
pub fn diagnostics(&self) -> &[ResolutionDiagnostic] {
&self.diagnostics
}
/// Filter the resolution to only include packages that match the given predicate.
#[must_use]
pub fn filter(self, predicate: impl Fn(&ResolvedDist) -> bool) -> Self {
let packages = self
.packages
.iter()
.filter(|(_, dist)| predicate(dist))
.map(|(name, dist)| (name.clone(), dist.clone()))
.collect::<BTreeMap<_, _>>();
let hashes = self
.hashes
.iter()
.filter(|(name, _)| packages.contains_key(name))
.map(|(name, hashes)| (name.clone(), hashes.clone()))
.collect();
let diagnostics = self.diagnostics.clone();
Self {
packages,
hashes,
diagnostics,
}
}
}
#[derive(Debug, Clone, Hash)]

View file

@ -2272,6 +2272,16 @@ pub struct SyncArgs {
#[arg(long, overrides_with("inexact"), hide = true)]
pub exact: bool,
/// Do not install the current project.
///
/// By default, the current project is installed into the environment with all of its
/// dependencies. The `--no-install-project` 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.
#[arg(long)]
pub no_install_project: 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

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

View file

@ -190,6 +190,7 @@ pub(crate) async fn remove(
// TODO(ibraheem): Should we accept CLI overrides for this? Should we even sync here?
let extras = ExtrasSpecification::All;
let dev = true;
let no_install_project = false;
// Initialize any shared state.
let state = SharedState::default();
@ -200,6 +201,7 @@ pub(crate) async fn remove(
&lock,
&extras,
dev,
no_install_project,
Modifications::Exact,
settings.as_ref().into(),
&state,

View file

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

View file

@ -1,6 +1,8 @@
use anyhow::{Context, Result};
use distribution_types::Name;
use itertools::Itertools;
use pep508_rs::MarkerTree;
use tracing::debug;
use uv_auth::store_credentials_from_url;
use uv_cache::Cache;
use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder};
@ -30,6 +32,7 @@ pub(crate) async fn sync(
package: Option<PackageName>,
extras: ExtrasSpecification,
dev: bool,
no_install_project: bool,
modifications: Modifications,
python: Option<String>,
python_preference: PythonPreference,
@ -102,6 +105,7 @@ pub(crate) async fn sync(
&lock,
&extras,
dev,
no_install_project,
modifications,
settings.as_ref().into(),
&state,
@ -124,6 +128,7 @@ pub(super) async fn do_sync(
lock: &Lock,
extras: &ExtrasSpecification,
dev: bool,
no_install_project: bool,
modifications: Modifications,
settings: InstallerSettingsRef<'_>,
state: &SharedState,
@ -187,6 +192,9 @@ pub(super) async fn do_sync(
// Read the lockfile.
let resolution = lock.to_resolution(project, markers, tags, extras, &dev)?;
// If `--no-install-project` is set, remove the project itself.
let resolution = apply_no_install_project(no_install_project, resolution, project);
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
store_credentials_from_url(url);
@ -274,3 +282,20 @@ pub(super) async fn do_sync(
Ok(())
}
fn apply_no_install_project(
no_install_project: bool,
resolution: distribution_types::Resolution,
project: &VirtualProject,
) -> distribution_types::Resolution {
if !no_install_project {
return resolution;
}
let Some(project_name) = project.project_name() else {
debug!("Ignoring `--no-install-project` for virtual workspace");
return resolution;
};
resolution.filter(|dist| dist.name() != project_name)
}

View file

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

View file

@ -617,6 +617,7 @@ pub(crate) struct SyncSettings {
pub(crate) frozen: bool,
pub(crate) extras: ExtrasSpecification,
pub(crate) dev: bool,
pub(crate) no_install_project: bool,
pub(crate) modifications: Modifications,
pub(crate) package: Option<PackageName>,
pub(crate) python: Option<String>,
@ -629,8 +630,6 @@ impl SyncSettings {
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn resolve(args: SyncArgs, filesystem: Option<FilesystemOptions>) -> Self {
let SyncArgs {
locked,
frozen,
extra,
all_extras,
no_all_extras,
@ -638,6 +637,9 @@ impl SyncSettings {
no_dev,
inexact,
exact,
no_install_project,
locked,
frozen,
installer,
build,
refresh,
@ -668,6 +670,7 @@ impl SyncSettings {
extra.unwrap_or_default(),
),
dev: flag(dev, no_dev).unwrap_or(true),
no_install_project,
modifications,
package,
python,

View file

@ -826,3 +826,40 @@ fn read_metadata_statically_over_the_cache() -> Result<()> {
Ok(())
}
/// Avoid syncing the project package when `--no-install-project` is provided.
#[test]
fn no_install_project() -> 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"]
"#,
)?;
// Generate a lockfile.
context.lock().assert().success();
// Running with `--no-install-project` should install `anyio`, but not `project`.
uv_snapshot!(context.filters(), context.sync().arg("--no-install-project"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==3.7.0
+ idna==3.6
+ sniffio==1.3.1
"###);
Ok(())
}