Support dry-run in InstallLoggers

This commit is contained in:
Tomasz (Tom) Kramkowski 2025-12-08 15:04:06 +00:00
parent f3cdf29a6f
commit a0a0a8d0e5
4 changed files with 129 additions and 75 deletions

View File

@ -1,5 +1,4 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fmt::Write;
use anyhow::Context; use anyhow::Context;
use itertools::Itertools; use itertools::Itertools;
@ -349,10 +348,7 @@ pub(crate) async fn pip_install(
debug!("Requirement satisfied: {requirement}"); debug!("Requirement satisfied: {requirement}");
} }
} }
DefaultInstallLogger.on_audit(requirements.len(), start, printer)?; DefaultInstallLogger.on_audit(requirements.len(), start, printer, dry_run)?;
if dry_run.enabled() {
writeln!(printer.stderr(), "Would make no changes")?;
}
return Ok(ExitStatus::Success); return Ok(ExitStatus::Success);
} }

View File

@ -6,6 +6,7 @@ use itertools::Itertools;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use rustc_hash::{FxBuildHasher, FxHashMap}; use rustc_hash::{FxBuildHasher, FxHashMap};
use uv_configuration::DryRun;
use uv_distribution_types::{InstalledMetadata, Name}; use uv_distribution_types::{InstalledMetadata, Name};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::Version; use uv_pep440::Version;
@ -17,7 +18,13 @@ use crate::printer::Printer;
/// A trait to handle logging during install operations. /// A trait to handle logging during install operations.
pub(crate) trait InstallLogger { pub(crate) trait InstallLogger {
/// Log the completion of the audit phase. /// 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. /// Log the completion of the preparation phase.
fn on_prepare( fn on_prepare(
@ -26,6 +33,7 @@ pub(crate) trait InstallLogger {
suffix: Option<&str>, suffix: Option<&str>,
start: std::time::Instant, start: std::time::Instant,
printer: Printer, printer: Printer,
dry_run: DryRun,
) -> fmt::Result; ) -> fmt::Result;
/// Log the completion of the uninstallation phase. /// Log the completion of the uninstallation phase.
@ -34,13 +42,20 @@ pub(crate) trait InstallLogger {
count: usize, count: usize,
start: std::time::Instant, start: std::time::Instant,
printer: Printer, printer: Printer,
dry_run: DryRun,
) -> fmt::Result; ) -> fmt::Result;
/// Log the completion of the installation phase. /// 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. /// 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. /// The default logger for install operations.
@ -48,13 +63,19 @@ pub(crate) trait InstallLogger {
pub(crate) struct DefaultInstallLogger; pub(crate) struct DefaultInstallLogger;
impl InstallLogger for 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 { if count == 0 {
writeln!( writeln!(
printer.stderr(), printer.stderr(),
"{}", "{}",
format!("Audited in {}", elapsed(start.elapsed())).dimmed() format!("Audited in {}", elapsed(start.elapsed())).dimmed()
) )?;
} else { } else {
let s = if count == 1 { "" } else { "s" }; let s = if count == 1 { "" } else { "s" };
writeln!( writeln!(
@ -66,8 +87,12 @@ impl InstallLogger for DefaultInstallLogger {
format!("in {}", elapsed(start.elapsed())).dimmed() format!("in {}", elapsed(start.elapsed())).dimmed()
) )
.dimmed() .dimmed()
) )?;
} }
if dry_run.enabled() {
writeln!(printer.stderr(), "Would make no changes")?;
}
Ok(())
} }
fn on_prepare( fn on_prepare(
@ -76,21 +101,26 @@ impl InstallLogger for DefaultInstallLogger {
suffix: Option<&str>, suffix: Option<&str>,
start: std::time::Instant, start: std::time::Instant,
printer: Printer, printer: Printer,
dry_run: DryRun,
) -> fmt::Result { ) -> fmt::Result {
let s = if count == 1 { "" } else { "s" }; 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!( writeln!(
printer.stderr(), printer.stderr(),
"{}", "{}",
format!( if dry_run.enabled() {
"Prepared {} {}", format!("Would download {what}")
if let Some(suffix) = suffix { } else {
format!("{count} package{s} {suffix}") format!(
} else { "Prepared {what} {}",
format!("{count} package{s}") format!("in {}", elapsed(start.elapsed())).dimmed()
} )
.bold(), }
format!("in {}", elapsed(start.elapsed())).dimmed()
)
.dimmed() .dimmed()
) )
} }
@ -100,35 +130,57 @@ impl InstallLogger for DefaultInstallLogger {
count: usize, count: usize,
start: std::time::Instant, start: std::time::Instant,
printer: Printer, printer: Printer,
dry_run: DryRun,
) -> fmt::Result { ) -> fmt::Result {
let s = if count == 1 { "" } else { "s" }; let s = if count == 1 { "" } else { "s" };
let what = format!("{count} package{s}");
let what = what.bold();
writeln!( writeln!(
printer.stderr(), printer.stderr(),
"{}", "{}",
format!( if dry_run.enabled() {
"Uninstalled {} {}", format!("Would uninstall {what}")
format!("{count} package{s}").bold(), } else {
format!("in {}", elapsed(start.elapsed())).dimmed() format!(
) "Uninstalled {what} {}",
format!("in {}", elapsed(start.elapsed())).dimmed()
)
}
.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 s = if count == 1 { "" } else { "s" };
let what = format!("{count} package{s}");
let what = what.bold();
writeln!( writeln!(
printer.stderr(), printer.stderr(),
"{}", "{}",
format!( if dry_run.enabled() {
"Installed {} {}", format!("Would install {what}")
format!("{count} package{s}").bold(), } else {
format!("in {}", elapsed(start.elapsed())).dimmed() format!(
) "Installed {what} {}",
format!("in {}", elapsed(start.elapsed())).dimmed()
)
}
.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 for event in changelog
.uninstalled .uninstalled
.iter() .iter()
@ -202,6 +254,7 @@ impl InstallLogger for SummaryInstallLogger {
_count: usize, _count: usize,
_start: std::time::Instant, _start: std::time::Instant,
_printer: Printer, _printer: Printer,
_dry_run: DryRun,
) -> fmt::Result { ) -> fmt::Result {
Ok(()) Ok(())
} }
@ -212,6 +265,7 @@ impl InstallLogger for SummaryInstallLogger {
_suffix: Option<&str>, _suffix: Option<&str>,
_start: std::time::Instant, _start: std::time::Instant,
_printer: Printer, _printer: Printer,
_dry_run: DryRun,
) -> fmt::Result { ) -> fmt::Result {
Ok(()) Ok(())
} }
@ -221,15 +275,27 @@ impl InstallLogger for SummaryInstallLogger {
count: usize, count: usize,
start: std::time::Instant, start: std::time::Instant,
printer: Printer, printer: Printer,
dry_run: DryRun,
) -> fmt::Result { ) -> fmt::Result {
DefaultInstallLogger.on_uninstall(count, start, printer) DefaultInstallLogger.on_uninstall(count, start, printer, dry_run)
} }
fn on_install(&self, count: usize, start: std::time::Instant, printer: Printer) -> fmt::Result { fn on_install(
DefaultInstallLogger.on_install(count, start, printer) &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(()) Ok(())
} }
} }
@ -253,6 +319,7 @@ impl InstallLogger for UpgradeInstallLogger {
_count: usize, _count: usize,
_start: std::time::Instant, _start: std::time::Instant,
_printer: Printer, _printer: Printer,
_dry_run: DryRun,
) -> fmt::Result { ) -> fmt::Result {
Ok(()) Ok(())
} }
@ -263,6 +330,7 @@ impl InstallLogger for UpgradeInstallLogger {
_suffix: Option<&str>, _suffix: Option<&str>,
_start: std::time::Instant, _start: std::time::Instant,
_printer: Printer, _printer: Printer,
_dry_run: DryRun,
) -> fmt::Result { ) -> fmt::Result {
Ok(()) Ok(())
} }
@ -272,6 +340,7 @@ impl InstallLogger for UpgradeInstallLogger {
_count: usize, _count: usize,
_start: std::time::Instant, _start: std::time::Instant,
_printer: Printer, _printer: Printer,
_dry_run: DryRun,
) -> fmt::Result { ) -> fmt::Result {
Ok(()) Ok(())
} }
@ -281,11 +350,18 @@ impl InstallLogger for UpgradeInstallLogger {
_count: usize, _count: usize,
_start: std::time::Instant, _start: std::time::Instant,
_printer: Printer, _printer: Printer,
_dry_run: DryRun,
) -> fmt::Result { ) -> fmt::Result {
Ok(()) 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. // Index the removals by package name.
let removals: FxHashMap<&PackageName, BTreeSet<Version>> = let removals: FxHashMap<&PackageName, BTreeSet<Version>> =
changelog.uninstalled.iter().fold( changelog.uninstalled.iter().fold(
@ -387,7 +463,7 @@ impl InstallLogger for UpgradeInstallLogger {
} }
// Follow-up with a detailed summary of all changes. // Follow-up with a detailed summary of all changes.
DefaultInstallLogger.on_complete(changelog, printer)?; DefaultInstallLogger.on_complete(changelog, printer, _dry_run)?;
Ok(()) Ok(())
} }

View File

@ -509,7 +509,7 @@ pub(crate) async fn install(
&& extraneous.is_empty() && extraneous.is_empty()
&& !compile && !compile
{ {
logger.on_audit(resolution.len(), start, printer)?; logger.on_audit(resolution.len(), start, printer, dry_run)?;
return Ok(Changelog::default()); return Ok(Changelog::default());
} }
@ -593,7 +593,7 @@ pub(crate) async fn install(
let changelog = Changelog::new(installs, uninstalls); let changelog = Changelog::new(installs, uninstalls);
// Notify the user of any environment modifications. // Notify the user of any environment modifications.
logger.on_complete(&changelog, printer)?; logger.on_complete(&changelog, printer, dry_run)?;
Ok(changelog) Ok(changelog)
} }
@ -660,7 +660,13 @@ async fn execute_plan(
.prepare(remote.clone(), in_flight, resolution) .prepare(remote.clone(), in_flight, resolution)
.await?; .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 wheels
}; };
@ -702,7 +708,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. // Install the resolved distributions.
@ -721,7 +727,7 @@ async fn execute_plan(
// task. // task.
.install_blocking(installs)?; .install_blocking(installs)?;
logger.on_install(installs.len(), start, printer)?; logger.on_install(installs.len(), start, printer, DryRun::Disabled)?;
} }
Ok((installs, uninstalls)) Ok((installs, uninstalls))
@ -857,8 +863,7 @@ fn report_dry_run(
// Nothing to do. // Nothing to do.
if remote.is_empty() && cached.is_empty() && reinstalls.is_empty() && extraneous.is_empty() { if remote.is_empty() && cached.is_empty() && reinstalls.is_empty() && extraneous.is_empty() {
DefaultInstallLogger.on_audit(resolution.len(), start, printer)?; DefaultInstallLogger.on_audit(resolution.len(), start, printer, dry_run)?;
writeln!(printer.stderr(), "Would make no changes")?;
return Ok(()); return Ok(());
} }
@ -866,16 +871,7 @@ fn report_dry_run(
let wheels = if remote.is_empty() { let wheels = if remote.is_empty() {
vec![] vec![]
} else { } else {
let s = if remote.len() == 1 { "" } else { "s" }; DefaultInstallLogger.on_prepare(remote.len(), None, start, printer, dry_run)?;
writeln!(
printer.stderr(),
"{}",
format!(
"Would download {}",
format!("{} package{}", remote.len(), s).bold(),
)
.dimmed()
)?;
remote.clone() remote.clone()
}; };
@ -883,28 +879,14 @@ fn report_dry_run(
let uninstalls = extraneous.len() + reinstalls.len(); let uninstalls = extraneous.len() + reinstalls.len();
if uninstalls > 0 { if uninstalls > 0 {
let s = if uninstalls == 1 { "" } else { "s" }; DefaultInstallLogger.on_uninstall(uninstalls, start, printer, dry_run)?;
writeln!(
printer.stderr(),
"{}",
format!(
"Would uninstall {}",
format!("{uninstalls} package{s}").bold(),
)
.dimmed()
)?;
} }
// Install the resolved distributions. // Install the resolved distributions.
let installs = wheels.len() + cached.len(); let installs = wheels.len() + cached.len();
if installs > 0 { if installs > 0 {
let s = if installs == 1 { "" } else { "s" }; DefaultInstallLogger.on_install(installs, start, printer, dry_run)?;
writeln!(
printer.stderr(),
"{}",
format!("Would install {}", format!("{installs} package{s}").bold()).dimmed()
)?;
} }
// TODO(charlie): DRY this up with `report_modifications`. The types don't quite line up. // TODO(charlie): DRY this up with `report_modifications`. The types don't quite line up.

View File

@ -10,8 +10,8 @@ use thiserror::Error;
use uv_cache::Cache; use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{ use uv_configuration::{
BuildOptions, Concurrency, Constraints, DependencyGroups, IndexStrategy, KeyringProviderType, BuildOptions, Concurrency, Constraints, DependencyGroups, DryRun, IndexStrategy,
NoBinary, NoBuild, SourceStrategy, KeyringProviderType, NoBinary, NoBuild, SourceStrategy,
}; };
use uv_dispatch::{BuildDispatch, SharedState}; use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution_types::{ use uv_distribution_types::{
@ -310,7 +310,7 @@ pub(crate) async fn venv(
.map_err(|err| VenvError::Seed(err.into()))?; .map_err(|err| VenvError::Seed(err.into()))?;
let changelog = Changelog::from_installed(installed); let changelog = Changelog::from_installed(installed);
DefaultInstallLogger.on_complete(&changelog, printer)?; DefaultInstallLogger.on_complete(&changelog, printer, DryRun::Disabled)?;
} }
// Determine the appropriate activation command. // Determine the appropriate activation command.