mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
feat: add tool version to list command (#4674)
<!-- Thank you for contributing to uv! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> Closes #4653 ## Summary Adds the tool version to the list command right beside the tool name ``` $ uv tool list black v24.2.0 ``` Following the proposed format discussed in #4653 ## Test Plan `cargo test tool_list` <!-- How was it tested? --> --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
parent
d24b075b2d
commit
c17761904e
5 changed files with 100 additions and 8 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5094,6 +5094,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"uv-cache",
|
||||
"uv-fs",
|
||||
"uv-installer",
|
||||
"uv-python",
|
||||
"uv-state",
|
||||
"uv-virtualenv",
|
||||
|
|
|
@ -23,6 +23,7 @@ uv-state = { workspace = true }
|
|||
uv-python = { workspace = true }
|
||||
uv-virtualenv = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
uv-installer = { workspace = true }
|
||||
|
||||
dirs-sys = { workspace = true }
|
||||
fs-err = { workspace = true }
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
use core::fmt;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use fs_err as fs;
|
||||
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::{InvalidNameError, PackageName};
|
||||
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use fs_err::File;
|
||||
use thiserror::Error;
|
||||
use tracing::debug;
|
||||
|
||||
use install_wheel_rs::read_record_file;
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::PackageName;
|
||||
|
||||
pub use receipt::ToolReceipt;
|
||||
pub use tool::{Tool, ToolEntrypoint};
|
||||
use uv_cache::Cache;
|
||||
use uv_fs::{LockedFile, Simplified};
|
||||
use uv_installer::SitePackages;
|
||||
use uv_python::{Interpreter, PythonEnvironment};
|
||||
use uv_state::{StateBucket, StateStore};
|
||||
use uv_warnings::warn_user_once;
|
||||
|
@ -38,9 +44,15 @@ pub enum Error {
|
|||
#[error("Failed to find a directory for executables")]
|
||||
NoExecutableDirectory,
|
||||
#[error(transparent)]
|
||||
ToolName(#[from] InvalidNameError),
|
||||
#[error(transparent)]
|
||||
EnvironmentError(#[from] uv_python::Error),
|
||||
#[error("Failed to find a receipt for tool `{0}` at {1}")]
|
||||
MissingToolReceipt(String, PathBuf),
|
||||
#[error("Failed to read tool environment packages at `{0}`: {1}")]
|
||||
EnvironmentRead(PathBuf, String),
|
||||
#[error("Failed find tool package `{0}` at `{1}`")]
|
||||
MissingToolPackage(PackageName, PathBuf),
|
||||
}
|
||||
|
||||
/// A collection of uv-managed tools installed on the current system.
|
||||
|
@ -230,6 +242,19 @@ impl InstalledTools {
|
|||
))
|
||||
}
|
||||
|
||||
pub fn version(&self, name: &str, cache: &Cache) -> Result<Version, Error> {
|
||||
let environment_path = self.root.join(name);
|
||||
let package_name = PackageName::from_str(name)?;
|
||||
let environment = PythonEnvironment::from_root(&environment_path, cache)?;
|
||||
let site_packages = SitePackages::from_environment(&environment)
|
||||
.map_err(|err| Error::EnvironmentRead(environment_path.clone(), err.to_string()))?;
|
||||
let packages = site_packages.get_packages(&package_name);
|
||||
let package = packages
|
||||
.first()
|
||||
.ok_or_else(|| Error::MissingToolPackage(package_name, environment_path))?;
|
||||
Ok(package.version().clone())
|
||||
}
|
||||
|
||||
/// Initialize the tools directory.
|
||||
///
|
||||
/// Ensures the directory is created.
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::fmt::Write;
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use uv_cache::Cache;
|
||||
use uv_configuration::PreviewMode;
|
||||
use uv_tool::InstalledTools;
|
||||
use uv_warnings::warn_user_once;
|
||||
|
@ -26,8 +27,17 @@ pub(crate) async fn list(preview: PreviewMode, printer: Printer) -> Result<ExitS
|
|||
}
|
||||
|
||||
for (name, tool) in tools {
|
||||
// Output tool name
|
||||
writeln!(printer.stdout(), "{name}")?;
|
||||
// Output tool name and version
|
||||
let version =
|
||||
match installed_tools.version(&name, &Cache::from_path(installed_tools.root())) {
|
||||
Ok(version) => version,
|
||||
Err(e) => {
|
||||
writeln!(printer.stderr(), "{e}")?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
writeln!(printer.stdout(), "{name} v{version}")?;
|
||||
|
||||
// Output tool entrypoints
|
||||
for entrypoint in tool.entrypoints() {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#![cfg(all(feature = "python", feature = "pypi"))]
|
||||
|
||||
use fs_err as fs;
|
||||
|
||||
use anyhow::Result;
|
||||
use assert_cmd::assert::OutputAssertExt;
|
||||
use assert_fs::fixture::PathChild;
|
||||
use common::{uv_snapshot, TestContext};
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
|
@ -27,7 +29,7 @@ fn tool_list() {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
black
|
||||
black v24.2.0
|
||||
black
|
||||
blackd
|
||||
|
||||
|
@ -85,3 +87,56 @@ fn tool_list_missing_receipt() {
|
|||
No tools installed
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_list_bad_environment() -> Result<()> {
|
||||
let context = TestContext::new("3.12").with_filtered_exe_suffix();
|
||||
let tool_dir = context.temp_dir.child("tools");
|
||||
let bin_dir = context.temp_dir.child("bin");
|
||||
|
||||
// Install `black`
|
||||
context
|
||||
.tool_install()
|
||||
.arg("black==24.2.0")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// Install `ruff`
|
||||
context
|
||||
.tool_install()
|
||||
.arg("ruff==0.3.4")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let venv_path = common::venv_bin_path(tool_dir.path().join("black"));
|
||||
// Remove the python interpreter for black
|
||||
fs::remove_dir_all(venv_path.clone())?;
|
||||
|
||||
let mut filters = context.filters().clone();
|
||||
filters.push((r"/black/.*", "/black/[VENV_PATH]`"));
|
||||
|
||||
uv_snapshot!(
|
||||
filters,
|
||||
context
|
||||
.tool_list()
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str()),
|
||||
@r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
ruff v0.3.4
|
||||
ruff
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv tool list` is experimental and may change without warning.
|
||||
Python interpreter not found at `[TEMP_DIR]/tools/black/[VENV_PATH]`
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue