Use upgrade-specific output for tool upgrade (#5997)

## Summary

Closes https://github.com/astral-sh/uv/issues/5949.
This commit is contained in:
Charlie Marsh 2024-08-10 20:24:49 -04:00 committed by GitHub
parent f5110f7b5e
commit ec8248ff93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 222 additions and 76 deletions

View file

@ -124,6 +124,24 @@ pub enum InstalledVersion<'a> {
Url(&'a Url, &'a Version),
}
impl<'a> InstalledVersion<'a> {
/// If it is a URL, return its value.
pub fn url(&self) -> Option<&Url> {
match self {
InstalledVersion::Version(_) => None,
InstalledVersion::Url(url, _) => Some(url),
}
}
/// If it is a version, return its value.
pub fn version(&self) -> &Version {
match self {
InstalledVersion::Version(version) => version,
InstalledVersion::Url(_, version) => version,
}
}
}
impl std::fmt::Display for InstalledVersion<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {

View file

@ -1,13 +1,15 @@
use std::collections::BTreeSet;
use std::fmt;
use std::fmt::Write;
use itertools::Itertools;
use owo_colors::OwoColorize;
use distribution_types::{CachedDist, InstalledDist, InstalledMetadata, LocalDist, Name};
use crate::commands::{elapsed, ChangeEvent, ChangeEventKind};
use crate::printer::Printer;
use distribution_types::{CachedDist, InstalledDist, InstalledMetadata, LocalDist, Name};
use itertools::Itertools;
use owo_colors::OwoColorize;
use pep440_rs::Version;
use rustc_hash::{FxBuildHasher, FxHashMap};
use uv_normalize::PackageName;
/// A trait to handle logging during install operations.
pub(crate) trait InstallLogger {
@ -223,6 +225,172 @@ impl InstallLogger for SummaryInstallLogger {
}
}
/// A logger that shows special output for the modification of the given target.
#[derive(Debug, Clone)]
pub(crate) struct UpgradeInstallLogger {
target: PackageName,
}
impl UpgradeInstallLogger {
/// Create a new logger for the given target.
pub(crate) fn new(target: PackageName) -> Self {
Self { target }
}
}
impl InstallLogger for UpgradeInstallLogger {
fn on_audit(
&self,
_count: usize,
_start: std::time::Instant,
_printer: Printer,
) -> fmt::Result {
Ok(())
}
fn on_prepare(
&self,
_count: usize,
_start: std::time::Instant,
_printer: Printer,
) -> fmt::Result {
Ok(())
}
fn on_uninstall(
&self,
_count: usize,
_start: std::time::Instant,
_printer: Printer,
) -> fmt::Result {
Ok(())
}
fn on_install(
&self,
_count: usize,
_start: std::time::Instant,
_printer: Printer,
) -> fmt::Result {
Ok(())
}
fn on_complete(
&self,
installed: Vec<CachedDist>,
reinstalled: Vec<InstalledDist>,
uninstalled: Vec<InstalledDist>,
printer: Printer,
) -> fmt::Result {
// Index the removals by package name.
let removals: FxHashMap<&PackageName, BTreeSet<Version>> =
reinstalled.iter().chain(uninstalled.iter()).fold(
FxHashMap::with_capacity_and_hasher(
reinstalled.len() + uninstalled.len(),
FxBuildHasher,
),
|mut acc, distribution| {
acc.entry(distribution.name())
.or_default()
.insert(distribution.installed_version().version().clone());
acc
},
);
// Index the additions by package name.
let additions: FxHashMap<&PackageName, BTreeSet<Version>> = installed.iter().fold(
FxHashMap::with_capacity_and_hasher(installed.len(), FxBuildHasher),
|mut acc, distribution| {
acc.entry(distribution.name())
.or_default()
.insert(distribution.installed_version().version().clone());
acc
},
);
// Summarize the change for the target.
match (removals.get(&self.target), additions.get(&self.target)) {
(Some(removals), Some(additions)) => {
if removals == additions {
let reinstalls = additions
.iter()
.map(|version| format!("v{version}"))
.collect::<Vec<_>>()
.join(", ");
writeln!(
printer.stderr(),
"{} {} {}",
"Reinstalled".yellow().bold(),
&self.target,
reinstalls
)?;
} else {
let removals = removals
.iter()
.map(|version| format!("v{version}"))
.collect::<Vec<_>>()
.join(", ");
let additions = additions
.iter()
.map(|version| format!("v{version}"))
.collect::<Vec<_>>()
.join(", ");
writeln!(
printer.stderr(),
"{} {} {} -> {}",
"Updated".green().bold(),
&self.target,
removals,
additions
)?;
}
}
(Some(removals), None) => {
let removals = removals
.iter()
.map(|version| format!("v{version}"))
.collect::<Vec<_>>()
.join(", ");
writeln!(
printer.stderr(),
"{} {} {}",
"Removed".red().bold(),
&self.target,
removals
)?;
}
(None, Some(additions)) => {
let additions = additions
.iter()
.map(|version| format!("v{version}"))
.collect::<Vec<_>>()
.join(", ");
writeln!(
printer.stderr(),
"{} {} {}",
"Added".green().bold(),
&self.target,
additions
)?;
}
(None, None) => {
writeln!(
printer.stderr(),
"{} {} {}",
"Modified".dimmed(),
&self.target.dimmed().bold(),
"environment".dimmed()
)?;
}
}
// Follow-up with a detailed summary of all changes.
DefaultInstallLogger.on_complete(installed, reinstalled, uninstalled, printer)?;
Ok(())
}
}
/// A trait to handle logging during resolve operations.
pub(crate) trait ResolveLogger {
/// Log the completion of the operation.

View file

@ -61,13 +61,6 @@ pub(crate) fn remove_entrypoints(tool: &Tool) {
}
}
/// Represents the action to be performed on executables: update or install.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum InstallAction {
Update,
Install,
}
/// Installs tool executables for a given package and handles any conflicts.
pub(crate) fn install_executables(
environment: &PythonEnvironment,
@ -77,7 +70,6 @@ pub(crate) fn install_executables(
force: bool,
python: Option<String>,
requirements: Vec<Requirement>,
action: InstallAction,
printer: Printer,
) -> anyhow::Result<ExitStatus> {
let site_packages = SitePackages::from_environment(environment)?;
@ -102,8 +94,7 @@ pub(crate) fn install_executables(
installed_dist.version(),
)?;
// Determine the entry points targets
// Use a sorted collection for deterministic output
// Determine the entry points targets. Use a sorted collection for deterministic output.
let target_entry_points = entry_points
.into_iter()
.map(|(name, source_path)| {
@ -180,13 +171,9 @@ pub(crate) fn install_executables(
} else {
"s"
};
let install_message = match action {
InstallAction::Install => "Installed",
InstallAction::Update => "Updated",
};
writeln!(
printer.stderr(),
"{install_message} {} executable{s}: {}",
"Installed {} executable{s}: {}",
target_entry_points.len(),
target_entry_points
.iter()
@ -194,7 +181,7 @@ pub(crate) fn install_executables(
.join(", ")
)?;
debug!("Adding receipt for tool `{}`", name);
debug!("Adding receipt for tool `{name}`");
let tool = Tool::new(
requirements.into_iter().collect(),
python,

View file

@ -20,11 +20,10 @@ use uv_warnings::{warn_user, warn_user_once};
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger};
use crate::commands::tool::common::remove_entrypoints;
use crate::commands::{
project::{resolve_environment, resolve_names, sync_environment, update_environment},
tool::common::InstallAction,
use crate::commands::project::{
resolve_environment, resolve_names, sync_environment, update_environment,
};
use crate::commands::tool::common::remove_entrypoints;
use crate::commands::{reporters::PythonDownloadReporter, tool::common::install_executables};
use crate::commands::{ExitStatus, SharedState};
use crate::printer::Printer;
@ -352,7 +351,6 @@ pub(crate) async fn install(
force || invalid_tool_receipt,
python,
requirements,
InstallAction::Install,
printer,
)
}

View file

@ -4,12 +4,6 @@ use anyhow::Result;
use owo_colors::OwoColorize;
use tracing::debug;
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger};
use crate::commands::project::update_environment;
use crate::commands::tool::common::{remove_entrypoints, InstallAction};
use crate::commands::{tool::common::install_executables, ExitStatus, SharedState};
use crate::printer::Printer;
use crate::settings::ResolverInstallerSettings;
use uv_cache::Cache;
use uv_client::Connectivity;
use uv_configuration::{Concurrency, PreviewMode};
@ -19,6 +13,13 @@ use uv_settings::{Combine, ResolverInstallerOptions, ToolOptions};
use uv_tool::InstalledTools;
use uv_warnings::warn_user_once;
use crate::commands::pip::loggers::{SummaryResolveLogger, UpgradeInstallLogger};
use crate::commands::project::update_environment;
use crate::commands::tool::common::remove_entrypoints;
use crate::commands::{tool::common::install_executables, ExitStatus, SharedState};
use crate::printer::Printer;
use crate::settings::ResolverInstallerSettings;
/// Upgrade a tool.
pub(crate) async fn upgrade(
name: Option<PackageName>,
@ -127,8 +128,8 @@ pub(crate) async fn upgrade(
spec,
&settings,
&state,
Box::new(DefaultResolveLogger),
Box::new(DefaultInstallLogger),
Box::new(SummaryResolveLogger),
Box::new(UpgradeInstallLogger::new(name.clone())),
preview,
connectivity,
concurrency,
@ -150,7 +151,6 @@ pub(crate) async fn upgrade(
true,
existing_tool_receipt.python().to_owned(),
requirements.to_vec(),
InstallAction::Update,
printer,
)?;
}

View file

@ -50,14 +50,11 @@ fn test_tool_upgrade_name() {
----- stderr -----
warning: `uv tool upgrade` is experimental and may change without warning
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Uninstalled [N] packages in [TIME]
Installed [N] packages in [TIME]
Updated babel v2.6.0 -> v2.14.0
- babel==2.6.0
+ babel==2.14.0
- pytz==2018.5
Updated 1 executable: pybabel
Installed 1 executable: pybabel
"###);
}
@ -126,21 +123,15 @@ fn test_tool_upgrade_all() {
----- stderr -----
warning: `uv tool upgrade` is experimental and may change without warning
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Uninstalled [N] packages in [TIME]
Installed [N] packages in [TIME]
Updated babel v2.6.0 -> v2.14.0
- babel==2.6.0
+ babel==2.14.0
- pytz==2018.5
Updated 1 executable: pybabel
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Uninstalled [N] packages in [TIME]
Installed [N] packages in [TIME]
Installed 1 executable: pybabel
Updated python-dotenv v0.10.2.post2 -> v1.0.1
- python-dotenv==0.10.2.post2
+ python-dotenv==1.0.1
Updated 1 executable: dotenv
Installed 1 executable: dotenv
"###);
}
@ -228,9 +219,7 @@ fn test_tool_upgrade_settings() {
----- stderr -----
warning: `uv tool upgrade` is experimental and may change without warning
Resolved [N] packages in [TIME]
Audited [N] packages in [TIME]
Updated 2 executables: black, blackd
Installed 2 executables: black, blackd
"###);
// Upgrade `black`, but override the resolution.
@ -246,13 +235,10 @@ fn test_tool_upgrade_settings() {
----- stderr -----
warning: `uv tool upgrade` is experimental and may change without warning
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Uninstalled [N] packages in [TIME]
Installed [N] packages in [TIME]
Updated black v23.1.0 -> v24.3.0
- black==23.1.0
+ black==24.3.0
Updated 2 executables: black, blackd
Installed 2 executables: black, blackd
"###);
}
@ -300,15 +286,12 @@ fn test_tool_upgrade_respect_constraints() {
----- stderr -----
warning: `uv tool upgrade` is experimental and may change without warning
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Uninstalled [N] packages in [TIME]
Installed [N] packages in [TIME]
Updated babel v2.6.0 -> v2.9.1
- babel==2.6.0
+ babel==2.9.1
- pytz==2018.5
+ pytz==2024.1
Updated 1 executable: pybabel
Installed 1 executable: pybabel
"###);
}
@ -358,15 +341,12 @@ fn test_tool_upgrade_constraint() {
----- stderr -----
warning: `uv tool upgrade` is experimental and may change without warning
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Uninstalled [N] packages in [TIME]
Installed [N] packages in [TIME]
Updated babel v2.6.0 -> v2.13.1
- babel==2.6.0
+ babel==2.13.1
- pytz==2018.5
+ setuptools==69.2.0
Updated 1 executable: pybabel
Installed 1 executable: pybabel
"###);
// Upgrade `babel` without a constraint.
@ -383,14 +363,11 @@ fn test_tool_upgrade_constraint() {
----- stderr -----
warning: `uv tool upgrade` is experimental and may change without warning
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Uninstalled [N] packages in [TIME]
Installed [N] packages in [TIME]
Updated babel v2.13.1 -> v2.14.0
- babel==2.13.1
+ babel==2.14.0
- setuptools==69.2.0
Updated 1 executable: pybabel
Installed 1 executable: pybabel
"###);
// Passing `--upgrade` explicitly should warn.
@ -409,8 +386,6 @@ fn test_tool_upgrade_constraint() {
----- stderr -----
warning: `--upgrade` is enabled by default on `uv tool upgrade`
warning: `uv tool upgrade` is experimental and may change without warning
Resolved [N] packages in [TIME]
Audited [N] packages in [TIME]
Updated 1 executable: pybabel
Installed 1 executable: pybabel
"###);
}