mirror of https://github.com/astral-sh/uv
Tweak `uv pip check` output for consistency with other interfaces (#2480)
This commit is contained in:
parent
2b01d9f70b
commit
b7ecd4faa1
|
|
@ -3,7 +3,7 @@ pub use downloader::{Downloader, Reporter as DownloadReporter};
|
|||
pub use editable::{is_dynamic, BuiltEditable, ResolvedEditable};
|
||||
pub use installer::{Installer, Reporter as InstallReporter};
|
||||
pub use plan::{Plan, Planner, Reinstall};
|
||||
pub use site_packages::SitePackages;
|
||||
pub use site_packages::{Diagnostic, SitePackages};
|
||||
pub use uninstall::uninstall;
|
||||
pub use uv_traits::NoBinary;
|
||||
|
||||
|
|
|
|||
|
|
@ -67,13 +67,16 @@ impl From<ExitStatus> for ExitCode {
|
|||
/// Format a duration as a human-readable string, Cargo-style.
|
||||
pub(super) fn elapsed(duration: Duration) -> String {
|
||||
let secs = duration.as_secs();
|
||||
let ms = duration.subsec_millis();
|
||||
|
||||
if secs >= 60 {
|
||||
format!("{}m {:02}s", secs / 60, secs % 60)
|
||||
} else if secs > 0 {
|
||||
format!("{}.{:02}s", secs, duration.subsec_nanos() / 10_000_000)
|
||||
} else if ms > 0 {
|
||||
format!("{ms}ms")
|
||||
} else {
|
||||
format!("{}ms", duration.subsec_millis())
|
||||
format!("0.{:02}ms", duration.subsec_nanos() / 10_000)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,28 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use distribution_types::InstalledDist;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::time::Instant;
|
||||
use tracing::debug;
|
||||
|
||||
use uv_cache::Cache;
|
||||
use uv_fs::Simplified;
|
||||
use uv_installer::SitePackages;
|
||||
use uv_installer::{Diagnostic, SitePackages};
|
||||
use uv_interpreter::PythonEnvironment;
|
||||
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::commands::{elapsed, ExitStatus};
|
||||
use crate::printer::Printer;
|
||||
|
||||
/// Show information about one or more installed packages.
|
||||
/// Check for incompatibilties in installed packages.
|
||||
pub(crate) fn pip_check(
|
||||
python: Option<&str>,
|
||||
system: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
let start = Instant::now();
|
||||
|
||||
// Detect the current Python interpreter.
|
||||
let venv = if let Some(python) = python {
|
||||
PythonEnvironment::from_requested_python(python, cache)?
|
||||
|
|
@ -42,18 +46,50 @@ pub(crate) fn pip_check(
|
|||
|
||||
// Build the installed index.
|
||||
let site_packages = SitePackages::from_executable(&venv)?;
|
||||
let packages: Vec<&InstalledDist> = site_packages.iter().collect();
|
||||
|
||||
let mut is_compatible = true;
|
||||
// This loop is entered if and only if there is at least one conflict.
|
||||
for diagnostic in site_packages.diagnostics()? {
|
||||
is_compatible = false;
|
||||
writeln!(printer.stdout(), "{}", diagnostic.message())?;
|
||||
let s = if packages.len() == 1 { "" } else { "s" };
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}",
|
||||
format!(
|
||||
"Checked {} in {}",
|
||||
format!("{} package{}", packages.len(), s).bold(),
|
||||
elapsed(start.elapsed())
|
||||
)
|
||||
.dimmed()
|
||||
)?;
|
||||
|
||||
let diagnostics: Vec<Diagnostic> = site_packages.diagnostics()?.into_iter().collect();
|
||||
|
||||
if diagnostics.is_empty() {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}",
|
||||
"All installed packages are compatible".to_string().dimmed()
|
||||
)?;
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
} else {
|
||||
let incompats = if diagnostics.len() == 1 {
|
||||
"incompatibility"
|
||||
} else {
|
||||
"incompatibilities"
|
||||
};
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}",
|
||||
format!(
|
||||
"Found {}",
|
||||
format!("{} {}", diagnostics.len(), incompats).bold()
|
||||
)
|
||||
.dimmed()
|
||||
)?;
|
||||
|
||||
for diagnostic in &diagnostics {
|
||||
writeln!(printer.stderr(), "{}", diagnostic.message().bold())?;
|
||||
}
|
||||
|
||||
Ok(ExitStatus::Failure)
|
||||
}
|
||||
|
||||
if !is_compatible {
|
||||
return Ok(ExitStatus::Failure);
|
||||
}
|
||||
|
||||
writeln!(printer.stdout(), "Installed packages pass the check.").unwrap();
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,20 @@ fn install_command(context: &TestContext) -> Command {
|
|||
command
|
||||
}
|
||||
|
||||
/// Create a `pip check` command with options shared across scenarios.
|
||||
fn check_command(context: &TestContext) -> Command {
|
||||
let mut command = Command::new(get_bin());
|
||||
command
|
||||
.arg("pip")
|
||||
.arg("check")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
.current_dir(&context.temp_dir);
|
||||
|
||||
command
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_compatible_packages() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
|
@ -60,20 +74,14 @@ fn check_compatible_packages() -> Result<()> {
|
|||
"###
|
||||
);
|
||||
|
||||
// Guards against the package names being sorted.
|
||||
uv_snapshot!([], Command::new(get_bin())
|
||||
.arg("pip")
|
||||
.arg("check")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
.current_dir(&context.temp_dir), @r###"
|
||||
uv_snapshot!(check_command(&context), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Installed packages pass the check.
|
||||
|
||||
----- stderr -----
|
||||
Checked 5 packages in [TIME]
|
||||
All installed packages are compatible
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
@ -132,20 +140,15 @@ fn check_incompatible_packages() -> Result<()> {
|
|||
"###
|
||||
);
|
||||
|
||||
// Guards against the package names being sorted.
|
||||
uv_snapshot!([], Command::new(get_bin())
|
||||
.arg("pip")
|
||||
.arg("check")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
.current_dir(&context.temp_dir), @r###"
|
||||
uv_snapshot!(check_command(&context), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
The package `requests` requires `idna <4, >=2.5`, but `2.4` is installed.
|
||||
|
||||
----- stderr -----
|
||||
Checked 5 packages in [TIME]
|
||||
Found 1 incompatibility
|
||||
The package `requests` requires `idna <4, >=2.5`, but `2.4` is installed.
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
@ -208,21 +211,16 @@ fn check_multiple_incompatible_packages() -> Result<()> {
|
|||
"###
|
||||
);
|
||||
|
||||
// Guards against the package names being sorted.
|
||||
uv_snapshot!([], Command::new(get_bin())
|
||||
.arg("pip")
|
||||
.arg("check")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
.current_dir(&context.temp_dir), @r###"
|
||||
uv_snapshot!(check_command(&context), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
The package `requests` requires `idna <4, >=2.5`, but `2.4` is installed.
|
||||
The package `requests` requires `urllib3 <3, >=1.21.1`, but `1.20` is installed.
|
||||
|
||||
----- stderr -----
|
||||
Checked 5 packages in [TIME]
|
||||
Found 2 incompatibilities
|
||||
The package `requests` requires `idna <4, >=2.5`, but `2.4` is installed.
|
||||
The package `requests` requires `urllib3 <3, >=1.21.1`, but `1.20` is installed.
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue