uv icon indicating copy to clipboard operation
uv copied to clipboard

feat: add tool version to list command

Open caiquejjx opened this issue 1 year ago • 9 comments

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

caiquejjx avatar Jul 01 '24 00:07 caiquejjx

Thanks for the pull request!

I'm not sure we should store the version in the receipt like this — can we instead read it from the site packages of the tool's virtual environment? I'm not entirely sure what the trade-offs would be here, but it'd be nice to avoid storing things in the receipt that are available elsewhere.

Let me know if you have any questions.

zanieb avatar Jul 01 '24 20:07 zanieb

sure thing @zanieb makes sense, I was taking a look and if I understood correctly to do the way you mentioned I would need to instance an Interpreter to get an PythonEnvironment and from that get the SitePackages, is that right? Similar to what is done in the install step

caiquejjx avatar Jul 01 '24 21:07 caiquejjx

@zanieb I have added a draft implementation of what I was able to come up with (quite verbose xD), can you take a look?

caiquejjx avatar Jul 01 '24 22:07 caiquejjx

You can use PythonEnvironment::from_root as we do at https://github.com/astral-sh/uv/blob/324e9fe5cf67d98495427ceb9a390c854e2f9f18/crates/uv-tool/src/lib.rs#L179 then yeah you can grab the package information from site-packages as we do during install https://github.com/astral-sh/uv/blob/305868cdcc65aa7bc50378eb2cf65200a5a7a359/crates/uv/src/commands/tool/install.rs#L158-L162

zanieb avatar Jul 01 '24 22:07 zanieb

Ah yeah that looks pretty reasonable but yeah kind of verbose let me see...

zanieb avatar Jul 01 '24 22:07 zanieb

Yeah I took a swing at this and ended up with a pretty verbose implementation too

diff --git a/Cargo.lock b/Cargo.lock
index d846cc93..4df5f5d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5048,6 +5048,7 @@ dependencies = [
  "tracing",
  "uv-cache",
  "uv-fs",
+ "uv-installer",
  "uv-state",
  "uv-toolchain",
  "uv-virtualenv",
diff --git a/crates/uv-tool/Cargo.toml b/crates/uv-tool/Cargo.toml
index 33426cab..3747e1e2 100644
--- a/crates/uv-tool/Cargo.toml
+++ b/crates/uv-tool/Cargo.toml
@@ -19,6 +19,7 @@ pep508_rs = { workspace = true }
 pypi-types = { workspace = true }
 uv-cache = { workspace = true }
 uv-fs = { workspace = true }
+uv-installer = { workspace = true }
 uv-state = { workspace = true }
 uv-toolchain = { workspace = true }
 uv-virtualenv = { workspace = true }
diff --git a/crates/uv-tool/src/lib.rs b/crates/uv-tool/src/lib.rs
index ecab0edf..da3db376 100644
--- a/crates/uv-tool/src/lib.rs
+++ b/crates/uv-tool/src/lib.rs
@@ -1,6 +1,7 @@
 use core::fmt;
 use std::io::{self, Write};
 use std::path::{Path, PathBuf};
+use std::str::FromStr;
 
 use fs_err as fs;
 use fs_err::File;
@@ -9,11 +10,12 @@ use tracing::debug;
 
 use install_wheel_rs::read_record_file;
 use pep440_rs::Version;
-use pep508_rs::PackageName;
+use pep508_rs::{InvalidNameError, 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_state::{StateBucket, StateStore};
 use uv_toolchain::{Interpreter, PythonEnvironment};
 use uv_warnings::warn_user_once;
@@ -31,6 +33,10 @@ pub enum Error {
     ReceiptRead(PathBuf, #[source] Box<toml::de::Error>),
     #[error(transparent)]
     VirtualEnvError(#[from] uv_virtualenv::Error),
+    #[error("Failed to read tool environment packages at `{0}`: {1}")]
+    EnvironmentRead(PathBuf, String),
+    #[error("Failed find tool package `{0}` at `{1}`")]
+    MissingToolPackage(PackageName, PathBuf),
     #[error("Failed to read package entry points {0}")]
     EntrypointRead(#[from] install_wheel_rs::Error),
     #[error("Failed to find dist-info directory `{0}` in environment at {1}")]
@@ -38,6 +44,8 @@ pub enum Error {
     #[error("Failed to find a directory for executables")]
     NoExecutableDirectory,
     #[error(transparent)]
+    ToolName(#[from] InvalidNameError),
+    #[error(transparent)]
     EnvironmentError(#[from] uv_toolchain::Error),
     #[error("Failed to find a receipt for tool `{0}` at {1}")]
     MissingToolReceipt(String, PathBuf),
@@ -203,6 +211,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.

I just moved it into the InstalledTools type and added some error variants. I'm not sure we can do much better here?

zanieb avatar Jul 01 '24 22:07 zanieb

As an aside, it probably makes sense for uv tool list to emit warnings when the environment for one of the tools is broken rather than a hard failure.

zanieb avatar Jul 01 '24 22:07 zanieb

yeah I think there's not much to do to improve it besides what u already did, I think I'll start from where you stopped and handle the warnings/avoid hard failures, wdyt?

caiquejjx avatar Jul 01 '24 22:07 caiquejjx

That sounds good to me!

zanieb avatar Jul 02 '24 01:07 zanieb

@zanieb can you help me understand why my test fails for windows?

caiquejjx avatar Jul 02 '24 22:07 caiquejjx

@caiquejjx I think on Windows it's a Scripts directory not bin. You can use virtualenv_python_executable to find the path to the executable you want to remove in a cross-platform fashion.

zanieb avatar Jul 02 '24 22:07 zanieb

I broke it! I'll explore my filter change separately.

zanieb avatar Jul 03 '24 18:07 zanieb