Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3435,6 +3435,14 @@ pub struct SyncArgs {
#[arg(long)]
pub no_install_workspace: bool,

/// Do not install local path dependencies
///
/// Skips the current project, workspace members, and any other local (path or editable)
/// packages. Only remote/indexed dependencies are installed. Useful in Docker builds to cache
/// heavy third-party dependencies first and layer local packages separately.
#[arg(long)]
pub no_install_local: bool,

/// Do not install the given package(s).
///
/// By default, all of the project's dependencies are installed into the environment. The
Expand Down Expand Up @@ -3503,6 +3511,7 @@ pub struct SyncArgs {
conflicts_with = "package",
conflicts_with = "no_install_project",
conflicts_with = "no_install_workspace",
conflicts_with = "no_install_local",
conflicts_with = "extra",
conflicts_with = "all_extras",
conflicts_with = "no_extra",
Expand Down Expand Up @@ -3847,6 +3856,14 @@ pub struct AddArgs {
/// allows optimal layer caching.
#[arg(long, conflicts_with = "frozen", conflicts_with = "no_sync")]
pub no_install_workspace: bool,

/// Do not install local path dependencies
///
/// Skips the current project, workspace members, and any other local (path or editable)
/// packages. Only remote/indexed dependencies are installed. Useful in Docker builds to cache
/// heavy third-party dependencies first and layer local packages separately.
#[arg(long, conflicts_with = "frozen", conflicts_with = "no_sync")]
pub no_install_local: bool,
}

#[derive(Args)]
Expand Down Expand Up @@ -4239,6 +4256,14 @@ pub struct ExportArgs {
#[arg(long, alias = "no-install-workspace")]
pub no_emit_workspace: bool,

/// Do not include local path dependencies in the exported requirements.
///
/// Omits the current project, workspace members, and any other local (path or editable)
/// packages from the export. Only remote/indexed dependencies are written. Useful for Docker
/// and CI flows that want to export and cache third-party dependencies first.
#[arg(long, alias = "no-install-local")]
pub no_emit_local: bool,

/// Do not emit the given package(s).
///
/// By default, all of the project's dependencies are included in the exported requirements
Expand Down
42 changes: 33 additions & 9 deletions crates/uv-configuration/src/install_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,23 @@ use tracing::debug;

use uv_normalize::PackageName;

/// Minimal view of a package used to apply install filters.
#[derive(Debug, Clone, Copy)]
pub struct InstallTarget<'a> {
/// The package name.
pub name: &'a PackageName,
/// Whether the package refers to a local source (path, directory, editable, etc.).
pub is_local: bool,
}

#[derive(Debug, Clone, Default)]
pub struct InstallOptions {
/// Omit the project itself from the resolution.
pub no_install_project: bool,
/// Omit all workspace members (including the project itself) from the resolution.
pub no_install_workspace: bool,
/// Omit all local packages from the resolution.
pub no_install_local: bool,
/// Omit the specified packages from the resolution.
pub no_install_package: Vec<PackageName>,
}
Expand All @@ -18,27 +29,32 @@ impl InstallOptions {
pub fn new(
no_install_project: bool,
no_install_workspace: bool,
no_install_local: bool,
no_install_package: Vec<PackageName>,
) -> Self {
Self {
no_install_project,
no_install_workspace,
no_install_local,
no_install_package,
}
}

/// Returns `true` if a package passes the install filters.
pub fn include_package(
&self,
package: &PackageName,
target: InstallTarget<'_>,
project_name: Option<&PackageName>,
members: &BTreeSet<PackageName>,
) -> bool {
let package_name = target.name;
// If `--no-install-project` is set, remove the project itself.
if self.no_install_project {
if let Some(project_name) = project_name {
if package == project_name {
debug!("Omitting `{package}` from resolution due to `--no-install-project`");
if package_name == project_name {
debug!(
"Omitting `{package_name}` from resolution due to `--no-install-project`"
);
return false;
}
}
Expand All @@ -51,24 +67,32 @@ impl InstallOptions {
// is set.)
if !self.no_install_project {
if let Some(project_name) = project_name {
if package == project_name {
if package_name == project_name {
debug!(
"Omitting `{package}` from resolution due to `--no-install-workspace`"
"Omitting `{package_name}` from resolution due to `--no-install-workspace`"
);
return false;
}
}
}

if members.contains(package) {
debug!("Omitting `{package}` from resolution due to `--no-install-workspace`");
if members.contains(package_name) {
debug!("Omitting `{package_name}` from resolution due to `--no-install-workspace`");
return false;
}
}

// If `--no-install-local` is set, remove local packages.
if self.no_install_local {
if target.is_local {
debug!("Omitting `{package_name}` from resolution due to `--no-install-local`");
return false;
}
}

// If `--no-install-package` is provided, remove the requested packages.
if self.no_install_package.contains(package) {
debug!("Omitting `{package}` from resolution due to `--no-install-package`");
if self.no_install_package.contains(package_name) {
debug!("Omitting `{package_name}` from resolution due to `--no-install-package`");
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/uv-resolver/src/lock/export/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ impl<'lock> ExportableRequirements<'lock> {
})
.filter(|(_index, package)| {
install_options.include_package(
&package.id.name,
package.as_install_target(),
target.project_name(),
target.lock().members(),
)
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-resolver/src/lock/installable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ pub trait Installable<'lock> {
install_options: &InstallOptions,
) -> Result<Node, LockError> {
if install_options.include_package(
package.name(),
package.as_install_target(),
self.project_name(),
self.lock().members(),
) {
Expand Down
18 changes: 17 additions & 1 deletion crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use tracing::debug;
use url::Url;

use uv_cache_key::RepositoryUrl;
use uv_configuration::{BuildOptions, Constraints};
use uv_configuration::{BuildOptions, Constraints, InstallTarget};
use uv_distribution::{DistributionDatabase, FlatRequiresDist};
use uv_distribution_filename::{
BuildTag, DistExtension, ExtensionError, SourceDistExtension, WheelFilename,
Expand Down Expand Up @@ -3123,6 +3123,14 @@ impl Package {
pub fn resolved_dependency_groups(&self) -> &BTreeMap<GroupName, Vec<Dependency>> {
&self.dependency_groups
}

/// Returns an [`InstallTarget`] view for filtering decisions.
pub fn as_install_target(&self) -> InstallTarget<'_> {
InstallTarget {
name: self.name(),
is_local: self.id.source.is_local(),
}
}
}

/// Attempts to construct a `VerbatimUrl` from the given normalized `Path`.
Expand Down Expand Up @@ -3688,6 +3696,14 @@ impl Source {
}
}
}

/// Check if a package is local by examining its source.
pub(crate) fn is_local(&self) -> bool {
matches!(
self,
Self::Path(_) | Self::Directory(_) | Self::Editable(_) | Self::Virtual(_)
)
}
}

#[derive(Clone, Debug, serde::Deserialize)]
Expand Down
10 changes: 9 additions & 1 deletion crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub(crate) async fn add(
no_sync: bool,
no_install_project: bool,
no_install_workspace: bool,
no_install_local: bool,
requirements: Vec<RequirementsSource>,
constraints: Vec<RequirementsSource>,
marker: Option<MarkerTree>,
Expand Down Expand Up @@ -738,6 +739,7 @@ pub(crate) async fn add(
locked,
no_install_project,
no_install_workspace,
no_install_local,
&defaulted_extras,
&defaulted_groups,
raw,
Expand Down Expand Up @@ -968,6 +970,7 @@ async fn lock_and_sync(
locked: bool,
no_install_project: bool,
no_install_workspace: bool,
no_install_local: bool,
extras: &ExtrasSpecificationWithDefaults,
groups: &DependencyGroupsWithDefaults,
raw: bool,
Expand Down Expand Up @@ -1154,7 +1157,12 @@ async fn lock_and_sync(
extras,
groups,
EditableMode::Editable,
InstallOptions::new(no_install_project, no_install_workspace, vec![]),
InstallOptions::new(
no_install_project,
no_install_workspace,
no_install_local,
vec![],
),
Modifications::Sufficient,
None,
settings.into(),
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1978,6 +1978,7 @@ async fn run_project(
args.no_sync,
args.no_install_project,
args.no_install_workspace,
args.no_install_local,
requirements,
constraints,
args.marker,
Expand Down
7 changes: 7 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,7 @@ impl SyncSettings {
exact,
no_install_project,
no_install_workspace,
no_install_local,
no_install_package,
locked,
frozen,
Expand Down Expand Up @@ -1286,6 +1287,7 @@ impl SyncSettings {
install_options: InstallOptions::new(
no_install_project,
no_install_workspace,
no_install_local,
no_install_package,
),
modifications: if flag(exact, inexact, "inexact").unwrap_or(true) {
Expand Down Expand Up @@ -1377,6 +1379,7 @@ pub(crate) struct AddSettings {
pub(crate) workspace: Option<bool>,
pub(crate) no_install_project: bool,
pub(crate) no_install_workspace: bool,
pub(crate) no_install_local: bool,
pub(crate) install_mirrors: PythonInstallMirrors,
pub(crate) refresh: Refresh,
pub(crate) indexes: Vec<Index>,
Expand Down Expand Up @@ -1418,6 +1421,7 @@ impl AddSettings {
no_workspace,
no_install_project,
no_install_workspace,
no_install_local,
} = args;

let dependency_type = if let Some(extra) = optional {
Expand Down Expand Up @@ -1521,6 +1525,7 @@ impl AddSettings {
workspace: flag(workspace, no_workspace, "workspace"),
no_install_project,
no_install_workspace,
no_install_local,
editable: flag(editable, no_editable, "editable"),
extras: extra.unwrap_or_default(),
refresh: Refresh::from(refresh),
Expand Down Expand Up @@ -1820,6 +1825,7 @@ impl ExportSettings {
no_emit_project,
no_emit_workspace,
no_emit_package,
no_emit_local,
locked,
frozen,
resolver,
Expand Down Expand Up @@ -1862,6 +1868,7 @@ impl ExportSettings {
install_options: InstallOptions::new(
no_emit_project,
no_emit_workspace,
no_emit_local,
no_emit_package,
),
output_file,
Expand Down
Loading
Loading