From 1a39ffe39106c89f16f6267a8942ee6d926be8bf Mon Sep 17 00:00:00 2001 From: Kemal Akkoyun Date: Tue, 8 Oct 2024 21:34:50 +0200 Subject: [PATCH] uv run: List available scripts when a script is not specified (#7687) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kemal Akkoyun ## Summary This PR adds the ability to list available scripts in the environment when `uv run` is invoked without any arguments. It somewhat mimics the behavior of `rye run` command (See https://rye.astral.sh/guide/commands/run). This is an attempt to fix #4024. ## Test Plan I added test cases. The CI pipeline should pass. ### Manuel Tests ```shell ❯ uv run Provide a command or script to invoke with `uv run ` or `uv run script.py`. The following scripts are available: normalizer python python3 python3.12 See `uv run --help` for more information. ``` --------- Signed-off-by: Kemal Akkoyun Co-authored-by: Zanie Blue --- Cargo.lock | 4 +- crates/uv-cli/src/lib.rs | 2 +- crates/uv-fs/Cargo.toml | 6 ++ crates/uv-fs/src/lib.rs | 1 + crates/{uv-python => uv-fs}/src/which.rs | 2 +- crates/uv-python/Cargo.toml | 4 -- crates/uv-python/src/discovery.rs | 2 +- crates/uv-python/src/lib.rs | 1 - crates/uv/src/commands/project/run.rs | 71 +++++++++++++++++++++++- crates/uv/src/lib.rs | 5 +- crates/uv/tests/run.rs | 68 +++++++++++++++++++++++ docs/reference/cli.md | 2 +- 12 files changed, 152 insertions(+), 16 deletions(-) rename crates/{uv-python => uv-fs}/src/which.rs (95%) diff --git a/Cargo.lock b/Cargo.lock index ec23dcef7..c78618ef5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4679,12 +4679,14 @@ dependencies = [ "fs2", "junction", "path-slash", + "rustix", "schemars", "serde", "tempfile", "tokio", "tracing", "urlencoding", + "winsafe 0.0.22", ] [[package]] @@ -4982,7 +4984,6 @@ dependencies = [ "reqwest", "reqwest-middleware", "rmp-serde", - "rustix", "same-file", "schemars", "serde", @@ -5014,7 +5015,6 @@ dependencies = [ "windows-registry", "windows-result 0.2.0", "windows-sys 0.59.0", - "winsafe 0.0.22", ] [[package]] diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 1ea7b55b1..4835ea3e5 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2543,7 +2543,7 @@ pub struct RunArgs { /// If the path to a Python script (i.e., ending in `.py`), it will be /// executed with the Python interpreter. #[command(subcommand)] - pub command: ExternalCommand, + pub command: Option, /// Run with the given packages installed. /// diff --git a/crates/uv-fs/Cargo.toml b/crates/uv-fs/Cargo.toml index c7aa1cb1a..88f53659b 100644 --- a/crates/uv-fs/Cargo.toml +++ b/crates/uv-fs/Cargo.toml @@ -29,6 +29,12 @@ tempfile = { workspace = true } tracing = { workspace = true } urlencoding = { workspace = true } +[target.'cfg(target_os = "windows")'.dependencies] +winsafe = { workspace = true } + +[target.'cfg(any(unix, target_os = "wasi", target_os = "redox"))'.dependencies] +rustix = { workspace = true } + [target.'cfg(windows)'.dependencies] junction = { workspace = true } diff --git a/crates/uv-fs/src/lib.rs b/crates/uv-fs/src/lib.rs index f339e37c5..a3a582986 100644 --- a/crates/uv-fs/src/lib.rs +++ b/crates/uv-fs/src/lib.rs @@ -8,6 +8,7 @@ pub use crate::path::*; pub mod cachedir; mod path; +pub mod which; /// Reads data from the path and requires that it be valid UTF-8 or UTF-16. /// diff --git a/crates/uv-python/src/which.rs b/crates/uv-fs/src/which.rs similarity index 95% rename from crates/uv-python/src/which.rs rename to crates/uv-fs/src/which.rs index 352bc14f3..26a68203d 100644 --- a/crates/uv-python/src/which.rs +++ b/crates/uv-fs/src/which.rs @@ -3,7 +3,7 @@ use std::path::Path; /// Check whether a path in PATH is a valid executable. /// /// Derived from `which`'s `Checker`. -pub(crate) fn is_executable(path: &Path) -> bool { +pub fn is_executable(path: &Path) -> bool { #[cfg(any(unix, target_os = "wasi", target_os = "redox"))] { if rustix::fs::access(path, rustix::fs::Access::EXEC_OK).is_err() { diff --git a/crates/uv-python/Cargo.toml b/crates/uv-python/Cargo.toml index 190d8d322..c41c21775 100644 --- a/crates/uv-python/Cargo.toml +++ b/crates/uv-python/Cargo.toml @@ -53,12 +53,8 @@ tracing = { workspace = true } url = { workspace = true } which = { workspace = true } -[target.'cfg(any(unix, target_os = "wasi", target_os = "redox"))'.dependencies] -rustix = { workspace = true } - [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { workspace = true } -winsafe = { workspace = true } windows-registry = { workspace = true } windows-result = { workspace = true } diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 8a1cda526..cc2c6b498 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -10,6 +10,7 @@ use tracing::{debug, instrument, trace}; use which::{which, which_all}; use uv_cache::Cache; +use uv_fs::which::is_executable; use uv_fs::Simplified; use uv_pep440::{Prerelease, Version, VersionSpecifier, VersionSpecifiers}; use uv_warnings::warn_user_once; @@ -27,7 +28,6 @@ use crate::virtualenv::{ conda_prefix_from_env, virtualenv_from_env, virtualenv_from_working_dir, virtualenv_python_executable, }; -use crate::which::is_executable; use crate::{Interpreter, PythonVersion}; /// A request to find a Python installation. diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index fc691ddf7..55063f9fb 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -37,7 +37,6 @@ mod python_version; mod target; mod version_files; mod virtualenv; -mod which; #[cfg(not(test))] pub(crate) fn current_dir() -> Result { diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 5d7a839ad..635a0137a 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -18,9 +18,11 @@ use uv_configuration::{ Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions, SourceStrategy, }; use uv_distribution::LoweredRequirement; +use uv_fs::which::is_executable; use uv_fs::{PythonExt, Simplified}; use uv_installer::{SatisfiesResult, SitePackages}; use uv_normalize::PackageName; + use uv_python::{ EnvironmentPreference, Interpreter, PythonDownloads, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, PythonVariant, PythonVersionFile, VersionRequest, @@ -51,7 +53,7 @@ use crate::settings::ResolverInstallerSettings; pub(crate) async fn run( project_dir: &Path, script: Option, - command: RunCommand, + command: Option, requirements: Vec, show_resolution: bool, locked: bool, @@ -751,6 +753,73 @@ pub(crate) async fn run( .as_ref() .map_or_else(|| &base_interpreter, |env| env.interpreter()); + // Check if any run command is given. + // If not, print the available scripts for the current interpreter. + let Some(command) = command else { + writeln!( + printer.stdout(), + "Provide a command or script to invoke with `uv run ` or `uv run