mirror of https://github.com/astral-sh/uv
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:
parent
38ae414682
commit
6ad80c5150
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue