mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-03 05:03:46 +00:00
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:
parent
e0abab8259
commit
be1599ebf6
11 changed files with 140 additions and 4 deletions
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -603,6 +603,7 @@ pub(crate) async fn add(
|
|||
&lock,
|
||||
&extras,
|
||||
dev,
|
||||
false,
|
||||
Modifications::Sufficient,
|
||||
settings.as_ref().into(),
|
||||
&state,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -417,6 +417,7 @@ pub(crate) async fn run(
|
|||
result.lock(),
|
||||
&extras,
|
||||
dev,
|
||||
false,
|
||||
Modifications::Sufficient,
|
||||
settings.as_ref().into(),
|
||||
&state,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue