diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index dc1bcda6a3c8d..5a430d81dd294 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -639,14 +639,35 @@ pub(crate) async fn pip_install( preview, ); - // Sync the environment. - match operations::install( + let start = std::time::Instant::now(); + + // Make a plan + let plan = match operations::plan( &resolution, site_packages, InstallationStrategy::Permissive, modifications, &reinstall, &build_options, + &hasher, + &tags, + &build_dispatch, + &cache, + &environment, + ) { + Ok(plan) => plan, + Err(err) => { + return diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls()) + .report(err) + .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); + } + }; + + // Sync the environment. + match operations::install( + &resolution, + plan, + &build_options, link_mode, compile, &hasher, @@ -662,6 +683,7 @@ pub(crate) async fn pip_install( dry_run, printer, preview, + start, ) .await { diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 01db57db0cbc0..8db3ca6386954 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -44,7 +44,7 @@ use uv_tool::InstalledTools; use uv_types::{BuildContext, HashStrategy, InFlight, InstalledPackagesProvider}; use uv_warnings::warn_user; -use crate::commands::pip::loggers::{DefaultInstallLogger, InstallLogger, ResolveLogger}; +use crate::commands::pip::loggers::{InstallLogger, ResolveLogger}; use crate::commands::reporters::{InstallReporter, PrepareReporter, ResolverReporter}; use crate::commands::{ChangeEventKind, DryRunEvent, compile_bytecode}; use crate::printer::Printer; @@ -436,34 +436,21 @@ impl Changelog { } } -/// Install a set of requirements into the current environment. -/// -/// Returns a [`Changelog`] summarizing the changes made to the environment. -pub(crate) async fn install( +/// Produce a [`Plan`] for how to approach installing a set of packages +#[allow(clippy::result_large_err)] +pub(crate) fn plan( resolution: &Resolution, site_packages: SitePackages, installation: InstallationStrategy, modifications: Modifications, reinstall: &Reinstall, build_options: &BuildOptions, - link_mode: LinkMode, - compile: bool, hasher: &HashStrategy, tags: &Tags, - client: &RegistryClient, - in_flight: &InFlight, - concurrency: Concurrency, build_dispatch: &BuildDispatch<'_>, cache: &Cache, venv: &PythonEnvironment, - logger: Box, - installer_metadata: bool, - dry_run: DryRun, - printer: Printer, - preview: Preview, -) -> Result { - let start = std::time::Instant::now(); - +) -> Result { // Partition into those that should be linked from the cache (`local`), those that need to be // downloaded (`remote`), and those that should be removed (`extraneous`). let plan = Planner::new(resolution) @@ -484,11 +471,6 @@ pub(crate) async fn install( ) .context("Failed to determine installation plan")?; - if dry_run.enabled() { - report_dry_run(dry_run, resolution, plan, modifications, start, printer)?; - return Ok(Changelog::default()); - } - let Plan { cached, remote, @@ -502,27 +484,62 @@ pub(crate) async fn install( Modifications::Exact => extraneous, }; + Ok(Plan { + cached, + remote, + reinstalls, + extraneous, + }) +} + +/// Install a set of requirements into the current environment. +/// +/// Returns a [`Changelog`] summarizing the changes made to the environment. +pub(crate) async fn install( + resolution: &Resolution, + plan: Plan, + build_options: &BuildOptions, + link_mode: LinkMode, + compile: bool, + hasher: &HashStrategy, + tags: &Tags, + client: &RegistryClient, + in_flight: &InFlight, + concurrency: Concurrency, + build_dispatch: &BuildDispatch<'_>, + cache: &Cache, + venv: &PythonEnvironment, + logger: Box, + installer_metadata: bool, + dry_run: DryRun, + printer: Printer, + preview: Preview, + start: std::time::Instant, +) -> Result { // Nothing to do. - if remote.is_empty() - && cached.is_empty() - && reinstalls.is_empty() - && extraneous.is_empty() + if plan.remote.is_empty() + && plan.cached.is_empty() + && plan.reinstalls.is_empty() + && plan.extraneous.is_empty() && !compile { logger.on_audit(resolution.len(), start, printer)?; + if dry_run.enabled() { + writeln!(printer.stderr(), "Would make no changes")?; + } + return Ok(Changelog::default()); + } + + if dry_run.enabled() { + report_dry_run(dry_run, plan, printer)?; return Ok(Changelog::default()); } // Partition into two sets: those that require build isolation, and those that disable it. This // is effectively a heuristic to make `--no-build-isolation` work "more often" by way of giving // `--no-build-isolation` packages "access" to the rest of the environment. - let (isolated_phase, shared_phase) = Plan { - cached, - remote, - reinstalls, - extraneous, - } - .partition(|name| build_dispatch.build_isolation().is_isolated(Some(name))); + let (isolated_phase, shared_phase) = + plan.partition(|name| build_dispatch.build_isolation().is_isolated(Some(name))); let has_isolated_phase = !isolated_phase.is_empty(); let has_shared_phase = !shared_phase.is_empty(); @@ -834,14 +851,7 @@ pub(crate) fn report_target_environment( /// Report on the results of a dry-run installation. #[allow(clippy::result_large_err)] -fn report_dry_run( - dry_run: DryRun, - resolution: &Resolution, - plan: Plan, - modifications: Modifications, - start: std::time::Instant, - printer: Printer, -) -> Result<(), Error> { +fn report_dry_run(dry_run: DryRun, plan: Plan, printer: Printer) -> Result<(), Error> { let Plan { cached, remote, @@ -849,19 +859,6 @@ fn report_dry_run( extraneous, } = plan; - // If we're in `install` mode, ignore any extraneous distributions. - let extraneous = match modifications { - Modifications::Sufficient => vec![], - Modifications::Exact => extraneous, - }; - - // Nothing to do. - if remote.is_empty() && cached.is_empty() && reinstalls.is_empty() && extraneous.is_empty() { - DefaultInstallLogger.on_audit(resolution.len(), start, printer)?; - writeln!(printer.stderr(), "Would make no changes")?; - return Ok(()); - } - // Download, build, and unzip any missing distributions. let wheels = if remote.is_empty() { vec![] diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 39c569288b4c9..cf8b830929b3b 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -544,14 +544,35 @@ pub(crate) async fn pip_sync( preview, ); - // Sync the environment. - match operations::install( + let start = std::time::Instant::now(); + + // Make a plan + let plan = match operations::plan( &resolution, site_packages, InstallationStrategy::Permissive, Modifications::Exact, &reinstall, &build_options, + &hasher, + &tags, + &build_dispatch, + &cache, + &environment, + ) { + Ok(plan) => plan, + Err(err) => { + return diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls()) + .report(err) + .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); + } + }; + + // Sync the environment. + match operations::install( + &resolution, + plan, + &build_options, link_mode, compile, &hasher, @@ -567,6 +588,7 @@ pub(crate) async fn pip_sync( dry_run, printer, preview, + start, ) .await { diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 8b18e247c9648..73712c5316ec1 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -2140,14 +2140,27 @@ pub(crate) async fn sync_environment( preview, ); - // Sync the environment. - pip::operations::install( + let start = std::time::Instant::now(); + + let plan = pip::operations::plan( resolution, site_packages, InstallationStrategy::Permissive, modifications, reinstall, build_options, + &hasher, + tags, + &build_dispatch, + cache, + &venv, + )?; + + // Sync the environment. + pip::operations::install( + resolution, + plan, + build_options, link_mode, compile_bytecode, &hasher, @@ -2163,6 +2176,7 @@ pub(crate) async fn sync_environment( dry_run, printer, preview, + start, ) .await?; @@ -2408,14 +2422,28 @@ pub(crate) async fn update_environment( Err(err) => return Err(err.into()), }; - // Sync the environment. - let changelog = pip::operations::install( + let start = std::time::Instant::now(); + + // Make a plan + let plan = pip::operations::plan( &resolution, site_packages, InstallationStrategy::Permissive, modifications, reinstall, build_options, + &hasher, + &tags, + &build_dispatch, + cache, + &venv, + )?; + + // Sync the environment. + let changelog = pip::operations::install( + &resolution, + plan, + build_options, *link_mode, *compile_bytecode, &hasher, @@ -2431,6 +2459,7 @@ pub(crate) async fn update_environment( dry_run, printer, preview, + start, ) .await?; diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 2542965e8cd53..09db7716b025a 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -824,14 +824,27 @@ pub(super) async fn do_sync( let site_packages = SitePackages::from_environment(venv)?; - // Sync the environment. - operations::install( + let start = std::time::Instant::now(); + + let plan = operations::plan( &resolution, site_packages, InstallationStrategy::Strict, modifications, reinstall, build_options, + &hasher, + &tags, + &build_dispatch, + cache, + venv, + )?; + + // Sync the environment. + operations::install( + &resolution, + plan, + build_options, link_mode, compile_bytecode, &hasher, @@ -847,6 +860,7 @@ pub(super) async fn do_sync( dry_run, printer, preview, + start, ) .await?;