Skip to content

Commit b653d4a

Browse files
committed
Add uv run command
1 parent dd09de2 commit b653d4a

File tree

4 files changed

+80
-0
lines changed

4 files changed

+80
-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

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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(command: String, args: Vec<String>, cache: &Cache) -> Result<ExitStatus> {
18+
debug!("Running `{command} {}`", args.join(" "));
19+
20+
// Detect the current Python interpreter.
21+
// TODO(zanieb): Create ephemeral environments
22+
// TODO(zanieb): Accept `--python`
23+
let python_env = match PythonEnvironment::from_virtualenv(cache) {
24+
Ok(env) => Some(env),
25+
Err(uv_interpreter::Error::VenvNotFound) => None,
26+
Err(err) => return Err(err.into()),
27+
};
28+
29+
// Construct the command
30+
let mut process = Command::new(command);
31+
process.args(args);
32+
33+
// Set up the PATH
34+
if let Some(python_env) = python_env {
35+
debug!(
36+
"Using Python {} environment at {}",
37+
python_env.interpreter().python_version(),
38+
python_env.python_executable().user_display().cyan()
39+
);
40+
let new_path = if let Some(path) = std::env::var_os("PATH") {
41+
let python_env_path =
42+
iter::once(python_env.scripts().to_path_buf()).chain(env::split_paths(&path));
43+
env::join_paths(python_env_path)?
44+
} else {
45+
OsString::from(python_env.scripts())
46+
};
47+
48+
process.env("PATH", new_path);
49+
};
50+
51+
// Spawn and wait for completion
52+
// Standard input, output, and error streams are all inherited
53+
// TODO(zanieb): Throw a nicer error message if the command is not found
54+
let mut handle = process.spawn()?;
55+
let status = handle.wait().await?;
56+
57+
// Exit based on the result of the command
58+
// TODO(zanieb): Do we want to exit with the code of the child process? Probably.
59+
if status.success() {
60+
Ok(ExitStatus::Success)
61+
} else {
62+
Ok(ExitStatus::Failure)
63+
}
64+
}

crates/uv/src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ async fn run() -> Result<ExitStatus> {
542542
)
543543
.await
544544
}
545+
Commands::Run(args) => commands::run(args.command, args.args, &cache).await,
545546
#[cfg(feature = "self-update")]
546547
Commands::Self_(SelfNamespace {
547548
command: SelfCommand::Update,

0 commit comments

Comments
 (0)