Skip to content

Commit 83704c6

Browse files
committed
Add uv run command
1 parent dd09de2 commit 83704c6

File tree

4 files changed

+88
-0
lines changed

4 files changed

+88
-0
lines changed

crates/uv/src/cli.rs

+13
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ pub(crate) enum Commands {
111111
/// Clear the cache, removing all entries or those linked to specific packages.
112112
#[clap(hide = true)]
113113
Clean(CleanArgs),
114+
#[clap(hide = true)]
115+
Run(RunArgs),
114116
/// Display uv's version
115117
Version {
116118
#[arg(long, value_enum, default_value = "text")]
@@ -1307,6 +1309,17 @@ pub(crate) struct VenvArgs {
13071309
pub(crate) compat_args: compat::VenvCompatArgs,
13081310
}
13091311

1312+
#[derive(Args)]
1313+
#[allow(clippy::struct_excessive_bools)]
1314+
pub(crate) struct RunArgs {
1315+
/// Command
1316+
pub(crate) command: String,
1317+
1318+
/// Arguments
1319+
#[clap(allow_hyphen_values = true)]
1320+
pub(crate) args: Vec<String>,
1321+
}
1322+
13101323
#[derive(Args)]
13111324
#[allow(clippy::struct_excessive_bools)]
13121325
struct AddArgs {

crates/uv/src/commands/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub(crate) use pip_list::pip_list;
1616
pub(crate) use pip_show::pip_show;
1717
pub(crate) use pip_sync::pip_sync;
1818
pub(crate) use pip_uninstall::pip_uninstall;
19+
pub(crate) use run::run;
1920
#[cfg(feature = "self-update")]
2021
pub(crate) use self_update::self_update;
2122
use uv_cache::Cache;
@@ -40,6 +41,7 @@ mod pip_show;
4041
mod pip_sync;
4142
mod pip_uninstall;
4243
mod reporters;
44+
mod run;
4345
#[cfg(feature = "self-update")]
4446
mod self_update;
4547
mod venv;

crates/uv/src/commands/run.rs

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use std::ffi::OsString;
2+
use std::{env, iter};
3+
4+
use anyhow::Result;
5+
use owo_colors::OwoColorize;
6+
use tracing::debug;
7+
use uv_fs::Simplified;
8+
use uv_interpreter::PythonEnvironment;
9+
10+
use crate::commands::ExitStatus;
11+
use crate::printer::Printer;
12+
use tokio::process::Command;
13+
use uv_cache::Cache;
14+
15+
/// Run a command.
16+
#[allow(clippy::unnecessary_wraps, clippy::too_many_arguments)]
17+
pub(crate) async fn run(
18+
command: String,
19+
args: Vec<String>,
20+
_native_tls: bool,
21+
cache: &Cache,
22+
_printer: Printer,
23+
) -> Result<ExitStatus> {
24+
debug!("Running `{command} {}`", args.join(" "));
25+
26+
// Detect the current Python interpreter.
27+
// TODO(zanieb): Create ephemeral environments
28+
// TODO(zanieb): Accept `--python`
29+
let python_env = match PythonEnvironment::from_virtualenv(cache) {
30+
Ok(env) => Some(env),
31+
Err(uv_interpreter::Error::VenvNotFound) => None,
32+
Err(err) => return Err(err.into()),
33+
};
34+
35+
// Construct the command
36+
let mut process = Command::new(command);
37+
process.args(args);
38+
39+
// Set up the PATH
40+
if let Some(python_env) = python_env {
41+
debug!(
42+
"Using Python {} environment at {}",
43+
python_env.interpreter().python_version(),
44+
python_env.python_executable().user_display().cyan()
45+
);
46+
let new_path = if let Some(path) = std::env::var_os("PATH") {
47+
let python_env_path =
48+
iter::once(python_env.scripts().to_path_buf()).chain(env::split_paths(&path));
49+
env::join_paths(python_env_path)?
50+
} else {
51+
OsString::from(python_env.scripts())
52+
};
53+
54+
process.env("PATH", new_path);
55+
};
56+
57+
// Spawn and wait for completion
58+
// Standard input, output, and error streams are all inherited
59+
// TODO(zanieb): Throw a nicer error message if the command is not found
60+
let mut handle = process.spawn()?;
61+
let status = handle.wait().await?;
62+
63+
// Exit based on the result of the command
64+
// TODO(zanieb): Do we want to exit with the code of the child process? Probably.
65+
if status.success() {
66+
Ok(ExitStatus::Success)
67+
} else {
68+
Ok(ExitStatus::Failure)
69+
}
70+
}

crates/uv/src/main.rs

+3
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,9 @@ async fn run() -> Result<ExitStatus> {
542542
)
543543
.await
544544
}
545+
Commands::Run(args) => {
546+
commands::run(args.command, args.args, globals.native_tls, &cache, printer).await
547+
}
545548
#[cfg(feature = "self-update")]
546549
Commands::Self_(SelfNamespace {
547550
command: SelfCommand::Update,

0 commit comments

Comments
 (0)