Add uv run command (#3074)

Adds `uv run` which executes a command in your current virtual
environment.

This is a simple first milestone, lots of remaining work and behavior.
The command is hidden.
This commit is contained in:
Zanie Blue 2024-04-17 11:20:43 -05:00 committed by GitHub
parent 2cee7525c7
commit f7b83e9e83
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 79 additions and 0 deletions

View file

@ -111,6 +111,8 @@ pub(crate) enum Commands {
/// Clear the cache, removing all entries or those linked to specific packages.
#[command(hide = true)]
Clean(CleanArgs),
#[clap(hide = true)]
Run(RunArgs),
/// Display uv's version
Version {
#[arg(long, value_enum, default_value = "text")]
@ -1307,6 +1309,17 @@ pub(crate) struct VenvArgs {
pub(crate) compat_args: compat::VenvCompatArgs,
}
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub(crate) struct RunArgs {
/// Command
pub(crate) command: String,
/// Arguments
#[clap(allow_hyphen_values = true)]
pub(crate) args: Vec<String>,
}
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
struct AddArgs {

View file

@ -16,6 +16,7 @@ pub(crate) use pip_list::pip_list;
pub(crate) use pip_show::pip_show;
pub(crate) use pip_sync::pip_sync;
pub(crate) use pip_uninstall::pip_uninstall;
pub(crate) use run::run;
#[cfg(feature = "self-update")]
pub(crate) use self_update::self_update;
use uv_cache::Cache;
@ -40,6 +41,7 @@ mod pip_show;
mod pip_sync;
mod pip_uninstall;
mod reporters;
mod run;
#[cfg(feature = "self-update")]
mod self_update;
mod venv;

View file

@ -0,0 +1,63 @@
use std::ffi::OsString;
use std::{env, iter};
use anyhow::Result;
use owo_colors::OwoColorize;
use tracing::debug;
use uv_fs::Simplified;
use uv_interpreter::PythonEnvironment;
use crate::commands::ExitStatus;
use tokio::process::Command;
use uv_cache::Cache;
/// Run a command.
#[allow(clippy::unnecessary_wraps, clippy::too_many_arguments)]
pub(crate) async fn run(command: String, args: Vec<String>, cache: &Cache) -> Result<ExitStatus> {
debug!("Running `{command} {}`", args.join(" "));
// Detect the current Python interpreter.
// TODO(zanieb): Create ephemeral environments
// TODO(zanieb): Accept `--python`
let python_env = match PythonEnvironment::from_virtualenv(cache) {
Ok(env) => Some(env),
Err(uv_interpreter::Error::VenvNotFound) => None,
Err(err) => return Err(err.into()),
};
// Construct the command
let mut process = Command::new(command);
process.args(args);
// Set up the PATH
if let Some(python_env) = python_env {
debug!(
"Using Python {} environment at {}",
python_env.interpreter().python_version(),
python_env.python_executable().user_display().cyan()
);
let new_path = if let Some(path) = std::env::var_os("PATH") {
let python_env_path =
iter::once(python_env.scripts().to_path_buf()).chain(env::split_paths(&path));
env::join_paths(python_env_path)?
} else {
OsString::from(python_env.scripts())
};
process.env("PATH", new_path);
};
// Spawn and wait for completion
// Standard input, output, and error streams are all inherited
// TODO(zanieb): Throw a nicer error message if the command is not found
let mut handle = process.spawn()?;
let status = handle.wait().await?;
// Exit based on the result of the command
// TODO(zanieb): Do we want to exit with the code of the child process? Probably.
if status.success() {
Ok(ExitStatus::Success)
} else {
Ok(ExitStatus::Failure)
}
}

View file

@ -539,6 +539,7 @@ async fn run() -> Result<ExitStatus> {
)
.await
}
Commands::Run(args) => commands::run(args.command, args.args, &cache).await,
#[cfg(feature = "self-update")]
Commands::Self_(SelfNamespace {
command: SelfCommand::Update,