diff --git a/Cargo.lock b/Cargo.lock index ec23dcef73a9..c78618ef5986 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 8f77936adf88..477bea54596f 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 c7aa1cb1a20f..88f53659bfa0 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 f339e37c5ce6..a3a582986eae 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 352bc14f3b93..26a68203dbf2 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 190d8d322a1c..c41c217751d7 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 8a1cda5260f0..cc2c6b498ec8 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 fc691ddf751e..55063f9fbff4 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 5d7a839ad28f..635a0137ae6e 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