Refactor the Changelog for use in `report_dry_run` (#17039)

## Summary

Remove duplication in `report_dry_run` by making `Changelog` support
both local and remote dists. This is in support of #16653 and will form
a new basis for #16981.

This also involved refactoring `InstallLogger` and its implementations
to support dry run logging.

Additionally includes some minor refactoring in `SummaryInstallLogger`
and a fix to `InstalledVersion`.

See https://github.com/astral-sh/uv/compare/tk/dry-run-refactor for an
alternative approach (although obviously comes with some caveats).

## Test Plan

There are already quite a few tests which cover the output and they
pass. Manual testing was used to ensure styling stayed consistent.
This commit is contained in:
Tomasz Kramkowski 2025-12-12 10:37:30 +00:00 committed by GitHub
parent 38ae414682
commit 6ad80c5150
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 275 additions and 196 deletions

View File

@ -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,

View File

@ -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<T: Display> {
name: PackageName,
version: T,
pub(super) struct ChangeEvent<'a> {
dist: &'a ChangedDist,
kind: ChangeEventKind,
}

View File

@ -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);
}

View File

@ -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<Version>> =
let removals: FxHashMap<&PackageName, BTreeSet<ShortSpecifier>> =
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<Version>> =
let additions: FxHashMap<&PackageName, BTreeSet<ShortSpecifier>> =
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(())
}

View File

@ -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<Dist>),
}
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<LocalDist>,
pub(crate) installed: HashSet<ChangedDist>,
/// The distributions that were uninstalled.
pub(crate) uninstalled: HashSet<LocalDist>,
pub(crate) uninstalled: HashSet<ChangedDist>,
/// The distributions that were reinstalled.
pub(crate) reinstalled: HashSet<LocalDist>,
pub(crate) reinstalled: HashSet<ChangedDist>,
}
impl Changelog {
/// Create a [`Changelog`] from a list of installed and uninstalled distributions.
pub(crate) fn new(installed: Vec<CachedDist>, uninstalled: Vec<InstalledDist>) -> Self {
/// Create a [`Changelog`] from two iterators of [`ChangedDist`]s.
pub(crate) fn new<I, U>(installed: I, uninstalled: U) -> Self
where
I: IntoIterator<Item = ChangedDist>,
U: IntoIterator<Item = ChangedDist>,
{
// 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<CachedDist>, uninstalled: Vec<InstalledDist>) -> 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<CachedDist>) -> 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<Changelog, Error> {
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.

View File

@ -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.