diff --git a/crates/uv-distribution-types/src/lib.rs b/crates/uv-distribution-types/src/lib.rs index 5b4178f05..c0a6cd2bd 100644 --- a/crates/uv-distribution-types/src/lib.rs +++ b/crates/uv-distribution-types/src/lib.rs @@ -159,9 +159,9 @@ pub enum InstalledVersion<'a> { Url(&'a DisplaySafeUrl, &'a Version), } -impl InstalledVersion<'_> { +impl<'a> InstalledVersion<'a> { /// If it is a URL, return its value. - pub fn url(&self) -> Option<&DisplaySafeUrl> { + pub fn url(&self) -> Option<&'a DisplaySafeUrl> { match self { Self::Version(_) => None, Self::Url(url, _) => Some(url), @@ -169,7 +169,7 @@ impl InstalledVersion<'_> { } /// If it is a version, return its value. - pub fn version(&self) -> &Version { + pub fn version(&self) -> &'a Version { match self { Self::Version(version) => version, Self::Url(_, version) => version, diff --git a/crates/uv/src/commands/mod.rs b/crates/uv/src/commands/mod.rs index 72e9daa3e..b2a755bc0 100644 --- a/crates/uv/src/commands/mod.rs +++ b/crates/uv/src/commands/mod.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::io::stdout; use std::path::{Path, PathBuf}; use std::time::Duration; -use std::{fmt::Display, fmt::Write, process::ExitCode}; +use std::{fmt::Write, process::ExitCode}; use anstream::AutoStream; use anyhow::Context; @@ -62,10 +62,8 @@ pub(crate) use tool::upgrade::upgrade as tool_upgrade; use uv_cache::Cache; use uv_configuration::Concurrency; pub(crate) use uv_console::human_readable_bytes; -use uv_distribution_types::InstalledMetadata; use uv_fs::{CWD, Simplified}; use uv_installer::compile_tree; -use uv_normalize::PackageName; use uv_python::PythonEnvironment; use uv_scripts::Pep723Script; pub(crate) use venv::venv; @@ -73,6 +71,7 @@ pub(crate) use workspace::dir::dir; pub(crate) use workspace::list::list; pub(crate) use workspace::metadata::metadata; +use crate::commands::pip::operations::ChangedDist; use crate::printer::Printer; mod auth; @@ -148,15 +147,8 @@ pub(super) enum ChangeEventKind { } #[derive(Debug)] -pub(super) struct ChangeEvent<'a, T: InstalledMetadata> { - dist: &'a T, - kind: ChangeEventKind, -} - -#[derive(Debug)] -pub(super) struct DryRunEvent { - name: PackageName, - version: T, +pub(super) struct ChangeEvent<'a> { + dist: &'a ChangedDist, kind: ChangeEventKind, } diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index dc1bcda6a..55fccdae0 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -1,5 +1,4 @@ use std::collections::BTreeSet; -use std::fmt::Write; use anyhow::Context; use itertools::Itertools; @@ -349,10 +348,7 @@ pub(crate) async fn pip_install( debug!("Requirement satisfied: {requirement}"); } } - DefaultInstallLogger.on_audit(requirements.len(), start, printer)?; - if dry_run.enabled() { - writeln!(printer.stderr(), "Would make no changes")?; - } + DefaultInstallLogger.on_audit(requirements.len(), start, printer, dry_run)?; return Ok(ExitStatus::Success); } diff --git a/crates/uv/src/commands/pip/loggers.rs b/crates/uv/src/commands/pip/loggers.rs index 4752fd0d0..d23a9bc7f 100644 --- a/crates/uv/src/commands/pip/loggers.rs +++ b/crates/uv/src/commands/pip/loggers.rs @@ -6,18 +6,24 @@ use itertools::Itertools; use owo_colors::OwoColorize; use rustc_hash::{FxBuildHasher, FxHashMap}; -use uv_distribution_types::{InstalledMetadata, Name}; +use uv_configuration::DryRun; +use uv_distribution_types::Name; use uv_normalize::PackageName; -use uv_pep440::Version; -use crate::commands::pip::operations::Changelog; +use crate::commands::pip::operations::{Changelog, ShortSpecifier}; use crate::commands::{ChangeEvent, ChangeEventKind, elapsed}; use crate::printer::Printer; /// A trait to handle logging during install operations. pub(crate) trait InstallLogger { /// Log the completion of the audit phase. - fn on_audit(&self, count: usize, start: std::time::Instant, printer: Printer) -> fmt::Result; + fn on_audit( + &self, + count: usize, + start: std::time::Instant, + printer: Printer, + dry_run: DryRun, + ) -> fmt::Result; /// Log the completion of the preparation phase. fn on_prepare( @@ -26,6 +32,7 @@ pub(crate) trait InstallLogger { suffix: Option<&str>, start: std::time::Instant, printer: Printer, + dry_run: DryRun, ) -> fmt::Result; /// Log the completion of the uninstallation phase. @@ -34,13 +41,20 @@ pub(crate) trait InstallLogger { count: usize, start: std::time::Instant, printer: Printer, + dry_run: DryRun, ) -> fmt::Result; /// Log the completion of the installation phase. - fn on_install(&self, count: usize, start: std::time::Instant, printer: Printer) -> fmt::Result; + fn on_install( + &self, + count: usize, + start: std::time::Instant, + printer: Printer, + dry_run: DryRun, + ) -> fmt::Result; /// Log the completion of the operation. - fn on_complete(&self, changelog: &Changelog, printer: Printer) -> fmt::Result; + fn on_complete(&self, changelog: &Changelog, printer: Printer, dry_run: DryRun) -> fmt::Result; } /// The default logger for install operations. @@ -48,13 +62,19 @@ pub(crate) trait InstallLogger { pub(crate) struct DefaultInstallLogger; impl InstallLogger for DefaultInstallLogger { - fn on_audit(&self, count: usize, start: std::time::Instant, printer: Printer) -> fmt::Result { + fn on_audit( + &self, + count: usize, + start: std::time::Instant, + printer: Printer, + dry_run: DryRun, + ) -> fmt::Result { if count == 0 { writeln!( printer.stderr(), "{}", format!("Audited in {}", elapsed(start.elapsed())).dimmed() - ) + )?; } else { let s = if count == 1 { "" } else { "s" }; writeln!( @@ -66,8 +86,12 @@ impl InstallLogger for DefaultInstallLogger { format!("in {}", elapsed(start.elapsed())).dimmed() ) .dimmed() - ) + )?; } + if dry_run.enabled() { + writeln!(printer.stderr(), "Would make no changes")?; + } + Ok(()) } fn on_prepare( @@ -76,21 +100,26 @@ impl InstallLogger for DefaultInstallLogger { suffix: Option<&str>, start: std::time::Instant, printer: Printer, + dry_run: DryRun, ) -> fmt::Result { let s = if count == 1 { "" } else { "s" }; + let what = if let Some(suffix) = suffix { + format!("{count} package{s} {suffix}") + } else { + format!("{count} package{s}") + }; + let what = what.bold(); writeln!( printer.stderr(), "{}", - format!( - "Prepared {} {}", - if let Some(suffix) = suffix { - format!("{count} package{s} {suffix}") - } else { - format!("{count} package{s}") - } - .bold(), - format!("in {}", elapsed(start.elapsed())).dimmed() - ) + if dry_run.enabled() { + format!("Would download {what}") + } else { + format!( + "Prepared {what} {}", + format!("in {}", elapsed(start.elapsed())).dimmed() + ) + } .dimmed() ) } @@ -100,35 +129,57 @@ impl InstallLogger for DefaultInstallLogger { count: usize, start: std::time::Instant, printer: Printer, + dry_run: DryRun, ) -> fmt::Result { let s = if count == 1 { "" } else { "s" }; + let what = format!("{count} package{s}"); + let what = what.bold(); writeln!( printer.stderr(), "{}", - format!( - "Uninstalled {} {}", - format!("{count} package{s}").bold(), - format!("in {}", elapsed(start.elapsed())).dimmed() - ) + if dry_run.enabled() { + format!("Would uninstall {what}") + } else { + format!( + "Uninstalled {what} {}", + format!("in {}", elapsed(start.elapsed())).dimmed() + ) + } .dimmed() ) } - fn on_install(&self, count: usize, start: std::time::Instant, printer: Printer) -> fmt::Result { + fn on_install( + &self, + count: usize, + start: std::time::Instant, + printer: Printer, + dry_run: DryRun, + ) -> fmt::Result { let s = if count == 1 { "" } else { "s" }; + let what = format!("{count} package{s}"); + let what = what.bold(); writeln!( printer.stderr(), "{}", - format!( - "Installed {} {}", - format!("{count} package{s}").bold(), - format!("in {}", elapsed(start.elapsed())).dimmed() - ) + if dry_run.enabled() { + format!("Would install {what}") + } else { + format!( + "Installed {what} {}", + format!("in {}", elapsed(start.elapsed())).dimmed() + ) + } .dimmed() ) } - fn on_complete(&self, changelog: &Changelog, printer: Printer) -> fmt::Result { + fn on_complete( + &self, + changelog: &Changelog, + printer: Printer, + _dry_run: DryRun, + ) -> fmt::Result { for event in changelog .uninstalled .iter() @@ -154,7 +205,7 @@ impl InstallLogger for DefaultInstallLogger { .name() .cmp(b.dist.name()) .then_with(|| a.kind.cmp(&b.kind)) - .then_with(|| a.dist.installed_version().cmp(&b.dist.installed_version())) + .then_with(|| a.dist.long_specifier().cmp(&b.dist.long_specifier())) }) { match event.kind { @@ -164,7 +215,7 @@ impl InstallLogger for DefaultInstallLogger { " {} {}{}", "+".green(), event.dist.name().bold(), - event.dist.installed_version().dimmed() + event.dist.long_specifier().dimmed() )?; } ChangeEventKind::Removed => { @@ -173,7 +224,7 @@ impl InstallLogger for DefaultInstallLogger { " {} {}{}", "-".red(), event.dist.name().bold(), - event.dist.installed_version().dimmed() + event.dist.long_specifier().dimmed() )?; } ChangeEventKind::Reinstalled => { @@ -182,7 +233,7 @@ impl InstallLogger for DefaultInstallLogger { " {} {}{}", "~".yellow(), event.dist.name().bold(), - event.dist.installed_version().dimmed() + event.dist.long_specifier().dimmed() )?; } } @@ -202,6 +253,7 @@ impl InstallLogger for SummaryInstallLogger { _count: usize, _start: std::time::Instant, _printer: Printer, + _dry_run: DryRun, ) -> fmt::Result { Ok(()) } @@ -212,6 +264,7 @@ impl InstallLogger for SummaryInstallLogger { _suffix: Option<&str>, _start: std::time::Instant, _printer: Printer, + _dry_run: DryRun, ) -> fmt::Result { Ok(()) } @@ -221,35 +274,27 @@ impl InstallLogger for SummaryInstallLogger { count: usize, start: std::time::Instant, printer: Printer, + dry_run: DryRun, ) -> fmt::Result { - let s = if count == 1 { "" } else { "s" }; - writeln!( - printer.stderr(), - "{}", - format!( - "Uninstalled {} {}", - format!("{count} package{s}").bold(), - format!("in {}", elapsed(start.elapsed())).dimmed() - ) - .dimmed() - ) + DefaultInstallLogger.on_uninstall(count, start, printer, dry_run) } - fn on_install(&self, count: usize, start: std::time::Instant, printer: Printer) -> fmt::Result { - let s = if count == 1 { "" } else { "s" }; - writeln!( - printer.stderr(), - "{}", - format!( - "Installed {} {}", - format!("{count} package{s}").bold(), - format!("in {}", elapsed(start.elapsed())).dimmed() - ) - .dimmed() - ) + fn on_install( + &self, + count: usize, + start: std::time::Instant, + printer: Printer, + dry_run: DryRun, + ) -> fmt::Result { + DefaultInstallLogger.on_install(count, start, printer, dry_run) } - fn on_complete(&self, _changelog: &Changelog, _printer: Printer) -> fmt::Result { + fn on_complete( + &self, + _changelog: &Changelog, + _printer: Printer, + _dry_run: DryRun, + ) -> fmt::Result { Ok(()) } } @@ -273,6 +318,7 @@ impl InstallLogger for UpgradeInstallLogger { _count: usize, _start: std::time::Instant, _printer: Printer, + _dry_run: DryRun, ) -> fmt::Result { Ok(()) } @@ -283,6 +329,7 @@ impl InstallLogger for UpgradeInstallLogger { _suffix: Option<&str>, _start: std::time::Instant, _printer: Printer, + _dry_run: DryRun, ) -> fmt::Result { Ok(()) } @@ -292,6 +339,7 @@ impl InstallLogger for UpgradeInstallLogger { _count: usize, _start: std::time::Instant, _printer: Printer, + _dry_run: DryRun, ) -> fmt::Result { Ok(()) } @@ -301,31 +349,38 @@ impl InstallLogger for UpgradeInstallLogger { _count: usize, _start: std::time::Instant, _printer: Printer, + _dry_run: DryRun, ) -> fmt::Result { Ok(()) } - fn on_complete(&self, changelog: &Changelog, printer: Printer) -> fmt::Result { + fn on_complete( + &self, + changelog: &Changelog, + printer: Printer, + // TODO(tk): Adjust format for dry_run + _dry_run: DryRun, + ) -> fmt::Result { // Index the removals by package name. - let removals: FxHashMap<&PackageName, BTreeSet> = + let removals: FxHashMap<&PackageName, BTreeSet> = changelog.uninstalled.iter().fold( FxHashMap::with_capacity_and_hasher(changelog.uninstalled.len(), FxBuildHasher), |mut acc, distribution| { acc.entry(distribution.name()) .or_default() - .insert(distribution.installed_version().version().clone()); + .insert(distribution.short_specifier()); acc }, ); // Index the additions by package name. - let additions: FxHashMap<&PackageName, BTreeSet> = + let additions: FxHashMap<&PackageName, BTreeSet> = changelog.installed.iter().fold( FxHashMap::with_capacity_and_hasher(changelog.installed.len(), FxBuildHasher), |mut acc, distribution| { acc.entry(distribution.name()) .or_default() - .insert(distribution.installed_version().version().clone()); + .insert(distribution.short_specifier()); acc }, ); @@ -407,7 +462,7 @@ impl InstallLogger for UpgradeInstallLogger { } // Follow-up with a detailed summary of all changes. - DefaultInstallLogger.on_complete(changelog, printer)?; + DefaultInstallLogger.on_complete(changelog, printer, _dry_run)?; Ok(()) } diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 01db57db0..1886dec3c 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -19,15 +19,17 @@ use uv_configuration::{ use uv_dispatch::BuildDispatch; use uv_distribution::{DistributionDatabase, SourcedDependencyGroups}; use uv_distribution_types::{ - CachedDist, Diagnostic, InstalledDist, LocalDist, NameRequirementSpecification, Requirement, - ResolutionDiagnostic, UnresolvedRequirement, UnresolvedRequirementSpecification, + CachedDist, Diagnostic, Dist, InstalledDist, InstalledVersion, LocalDist, + NameRequirementSpecification, Requirement, ResolutionDiagnostic, UnresolvedRequirement, + UnresolvedRequirementSpecification, VersionOrUrlRef, }; use uv_distribution_types::{DistributionMetadata, InstalledMetadata, Name, Resolution}; use uv_fs::Simplified; use uv_install_wheel::LinkMode; use uv_installer::{InstallationStrategy, Plan, Planner, Preparer, SitePackages}; use uv_normalize::PackageName; -use uv_pep508::{MarkerEnvironment, RequirementOrigin}; +use uv_pep440::Version; +use uv_pep508::{MarkerEnvironment, RequirementOrigin, VerbatimUrl}; use uv_platform_tags::Tags; use uv_preview::Preview; use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment}; @@ -44,9 +46,9 @@ 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::compile_bytecode; +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; /// Consolidate the requirements for an installation. @@ -381,30 +383,104 @@ pub(crate) enum Modifications { Exact, } +/// A distribution which was or would be modified +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[allow(clippy::large_enum_variant)] +pub(crate) enum ChangedDist { + Local(LocalDist), + Remote(Arc), +} + +impl Name for ChangedDist { + fn name(&self) -> &PackageName { + match self { + Self::Local(dist) => dist.name(), + Self::Remote(dist) => dist.name(), + } + } +} + +/// The [`Version`] or [`VerbatimUrl`] for a changed dist. +#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub(crate) enum ShortSpecifier<'a> { + Version(&'a Version), + Url(&'a VerbatimUrl), +} + +impl std::fmt::Display for ShortSpecifier<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Version(version) => version.fmt(f), + Self::Url(url) => write!(f, " @ {url}"), + } + } +} + +/// The [`InstalledVersion`] or [`VerbatimUrl`] for a changed dist. +#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub(crate) enum LongSpecifier<'a> { + InstalledVersion(InstalledVersion<'a>), + Url(&'a VerbatimUrl), +} + +impl std::fmt::Display for LongSpecifier<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InstalledVersion(version) => version.fmt(f), + Self::Url(url) => write!(f, " @ {url}"), + } + } +} + +impl ChangedDist { + pub(crate) fn short_specifier(&self) -> ShortSpecifier<'_> { + match self { + Self::Local(dist) => ShortSpecifier::Version(dist.installed_version().version()), + Self::Remote(dist) => match dist.version_or_url() { + VersionOrUrlRef::Version(version) => ShortSpecifier::Version(version), + VersionOrUrlRef::Url(url) => ShortSpecifier::Url(url), + }, + } + } + + pub(crate) fn long_specifier(&self) -> LongSpecifier<'_> { + match self { + Self::Local(dist) => LongSpecifier::InstalledVersion(dist.installed_version()), + Self::Remote(dist) => match dist.version_or_url() { + VersionOrUrlRef::Version(version) => { + LongSpecifier::InstalledVersion(InstalledVersion::Version(version)) + } + VersionOrUrlRef::Url(url) => LongSpecifier::Url(url), + }, + } + } +} + /// A summary of the changes made to the environment during an installation. #[derive(Debug, Clone, Default)] pub(crate) struct Changelog { /// The distributions that were installed. - pub(crate) installed: HashSet, + pub(crate) installed: HashSet, /// The distributions that were uninstalled. - pub(crate) uninstalled: HashSet, + pub(crate) uninstalled: HashSet, /// The distributions that were reinstalled. - pub(crate) reinstalled: HashSet, + pub(crate) reinstalled: HashSet, } impl Changelog { - /// Create a [`Changelog`] from a list of installed and uninstalled distributions. - pub(crate) fn new(installed: Vec, uninstalled: Vec) -> Self { + /// Create a [`Changelog`] from two iterators of [`ChangedDist`]s. + pub(crate) fn new(installed: I, uninstalled: U) -> Self + where + I: IntoIterator, + U: IntoIterator, + { // SAFETY: This is allowed because `LocalDist` implements `Hash` and `Eq` based solely on // the inner `kind`, and omits the types that rely on internal mutability. #[allow(clippy::mutable_key_type)] - let mut uninstalled: HashSet<_> = uninstalled.into_iter().map(LocalDist::from).collect(); - + let mut uninstalled: HashSet<_> = uninstalled.into_iter().collect(); let (reinstalled, installed): (HashSet<_>, HashSet<_>) = installed .into_iter() - .map(LocalDist::from) .partition(|dist| uninstalled.contains(dist)); - uninstalled.retain(|dist| !reinstalled.contains(dist)); Self { @@ -414,13 +490,21 @@ impl Changelog { } } + /// Create a [`Changelog`] from a list of local distributions. + pub(crate) fn from_local(installed: Vec, uninstalled: Vec) -> Self { + Self::new( + installed + .into_iter() + .map(|dist| ChangedDist::Local(dist.into())), + uninstalled + .into_iter() + .map(|dist| ChangedDist::Local(dist.into())), + ) + } + /// Create a [`Changelog`] from a list of installed distributions. pub(crate) fn from_installed(installed: Vec) -> Self { - Self { - installed: installed.into_iter().map(LocalDist::from).collect(), - uninstalled: HashSet::default(), - reinstalled: HashSet::default(), - } + Self::from_local(installed, Vec::new()) } /// Returns `true` if the changelog includes a distribution with the given name, either via @@ -485,8 +569,15 @@ 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()); + return report_dry_run( + dry_run, + resolution, + plan, + modifications, + start, + logger.as_ref(), + printer, + ); } let Plan { @@ -509,7 +600,7 @@ pub(crate) async fn install( && extraneous.is_empty() && !compile { - logger.on_audit(resolution.len(), start, printer)?; + logger.on_audit(resolution.len(), start, printer, dry_run)?; return Ok(Changelog::default()); } @@ -590,10 +681,10 @@ pub(crate) async fn install( } // Construct a summary of the changes made to the environment. - let changelog = Changelog::new(installs, uninstalls); + let changelog = Changelog::from_local(installs, uninstalls); // Notify the user of any environment modifications. - logger.on_complete(&changelog, printer)?; + logger.on_complete(&changelog, printer, dry_run)?; Ok(changelog) } @@ -660,7 +751,13 @@ async fn execute_plan( .prepare(remote.clone(), in_flight, resolution) .await?; - logger.on_prepare(wheels.len(), phase.map(InstallPhase::label), start, printer)?; + logger.on_prepare( + wheels.len(), + phase.map(InstallPhase::label), + start, + printer, + DryRun::Disabled, + )?; wheels }; @@ -702,7 +799,7 @@ async fn execute_plan( } } - logger.on_uninstall(uninstalls.len(), start, printer)?; + logger.on_uninstall(uninstalls.len(), start, printer, DryRun::Disabled)?; } // Install the resolved distributions. @@ -721,7 +818,7 @@ async fn execute_plan( // task. .install_blocking(installs)?; - logger.on_install(installs.len(), start, printer)?; + logger.on_install(installs.len(), start, printer, DryRun::Disabled)?; } Ok((installs, uninstalls)) @@ -840,8 +937,9 @@ fn report_dry_run( plan: Plan, modifications: Modifications, start: std::time::Instant, + logger: &dyn InstallLogger, printer: Printer, -) -> Result<(), Error> { +) -> Result { let Plan { cached, remote, @@ -857,25 +955,15 @@ fn report_dry_run( // 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(()); + logger.on_audit(resolution.len(), start, printer, dry_run)?; + return Ok(Changelog::default()); } // Download, build, and unzip any missing distributions. let wheels = if remote.is_empty() { vec![] } else { - let s = if remote.len() == 1 { "" } else { "s" }; - writeln!( - printer.stderr(), - "{}", - format!( - "Would download {}", - format!("{} package{}", remote.len(), s).bold(), - ) - .dimmed() - )?; + logger.on_prepare(remote.len(), None, start, printer, dry_run)?; remote.clone() }; @@ -883,87 +971,35 @@ fn report_dry_run( let uninstalls = extraneous.len() + reinstalls.len(); if uninstalls > 0 { - let s = if uninstalls == 1 { "" } else { "s" }; - writeln!( - printer.stderr(), - "{}", - format!( - "Would uninstall {}", - format!("{uninstalls} package{s}").bold(), - ) - .dimmed() - )?; + logger.on_uninstall(uninstalls, start, printer, dry_run)?; } // Install the resolved distributions. let installs = wheels.len() + cached.len(); if installs > 0 { - let s = if installs == 1 { "" } else { "s" }; - writeln!( - printer.stderr(), - "{}", - format!("Would install {}", format!("{installs} package{s}").bold()).dimmed() - )?; + logger.on_install(installs, start, printer, dry_run)?; } - // TODO(charlie): DRY this up with `report_modifications`. The types don't quite line up. - for event in reinstalls + let uninstalled = reinstalls .into_iter() - .chain(extraneous.into_iter()) - .map(|distribution| DryRunEvent { - name: distribution.name().clone(), - version: distribution.installed_version().to_string(), - kind: ChangeEventKind::Removed, - }) - .chain(wheels.into_iter().map(|distribution| DryRunEvent { - name: distribution.name().clone(), - version: distribution.version_or_url().to_string(), - kind: ChangeEventKind::Added, - })) - .chain(cached.into_iter().map(|distribution| DryRunEvent { - name: distribution.name().clone(), - version: distribution.installed_version().to_string(), - kind: ChangeEventKind::Added, - })) - .sorted_unstable_by(|a, b| a.name.cmp(&b.name).then_with(|| a.kind.cmp(&b.kind))) - { - match event.kind { - ChangeEventKind::Added => { - writeln!( - printer.stderr(), - " {} {}{}", - "+".green(), - event.name.bold(), - event.version.dimmed() - )?; - } - ChangeEventKind::Removed => { - writeln!( - printer.stderr(), - " {} {}{}", - "-".red(), - event.name.bold(), - event.version.dimmed() - )?; - } - ChangeEventKind::Reinstalled => { - writeln!( - printer.stderr(), - " {} {}{}", - "~".yellow(), - event.name.bold(), - event.version.dimmed() - )?; - } - } - } + .chain(extraneous) + .map(|dist| ChangedDist::Local(dist.into())); + let installed = wheels.into_iter().map(ChangedDist::Remote).chain( + cached + .into_iter() + .map(|dist| ChangedDist::Local(dist.into())), + ); + + let changelog = Changelog::new(installed, uninstalled); + + logger.on_complete(&changelog, printer, dry_run)?; if matches!(dry_run, DryRun::Check) { return Err(Error::OutdatedEnvironment); } - Ok(()) + Ok(changelog) } /// Report any diagnostics on resolved distributions. diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 13fe8fb51..eb7526520 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -10,8 +10,8 @@ use thiserror::Error; use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ - BuildOptions, Concurrency, Constraints, DependencyGroups, IndexStrategy, KeyringProviderType, - NoBinary, NoBuild, SourceStrategy, + BuildOptions, Concurrency, Constraints, DependencyGroups, DryRun, IndexStrategy, + KeyringProviderType, NoBinary, NoBuild, SourceStrategy, }; use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution_types::{ @@ -310,7 +310,7 @@ pub(crate) async fn venv( .map_err(|err| VenvError::Seed(err.into()))?; let changelog = Changelog::from_installed(installed); - DefaultInstallLogger.on_complete(&changelog, printer)?; + DefaultInstallLogger.on_complete(&changelog, printer, DryRun::Disabled)?; } // Determine the appropriate activation command.