mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-02 15:01:16 +00:00
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:
parent
2cee7525c7
commit
f7b83e9e83
4 changed files with 79 additions and 0 deletions
|
@ -111,6 +111,8 @@ pub(crate) enum Commands {
|
||||||
/// Clear the cache, removing all entries or those linked to specific packages.
|
/// Clear the cache, removing all entries or those linked to specific packages.
|
||||||
#[command(hide = true)]
|
#[command(hide = true)]
|
||||||
Clean(CleanArgs),
|
Clean(CleanArgs),
|
||||||
|
#[clap(hide = true)]
|
||||||
|
Run(RunArgs),
|
||||||
/// Display uv's version
|
/// Display uv's version
|
||||||
Version {
|
Version {
|
||||||
#[arg(long, value_enum, default_value = "text")]
|
#[arg(long, value_enum, default_value = "text")]
|
||||||
|
@ -1307,6 +1309,17 @@ pub(crate) struct VenvArgs {
|
||||||
pub(crate) compat_args: compat::VenvCompatArgs,
|
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)]
|
#[derive(Args)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
struct AddArgs {
|
struct AddArgs {
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub(crate) use pip_list::pip_list;
|
||||||
pub(crate) use pip_show::pip_show;
|
pub(crate) use pip_show::pip_show;
|
||||||
pub(crate) use pip_sync::pip_sync;
|
pub(crate) use pip_sync::pip_sync;
|
||||||
pub(crate) use pip_uninstall::pip_uninstall;
|
pub(crate) use pip_uninstall::pip_uninstall;
|
||||||
|
pub(crate) use run::run;
|
||||||
#[cfg(feature = "self-update")]
|
#[cfg(feature = "self-update")]
|
||||||
pub(crate) use self_update::self_update;
|
pub(crate) use self_update::self_update;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
|
@ -40,6 +41,7 @@ mod pip_show;
|
||||||
mod pip_sync;
|
mod pip_sync;
|
||||||
mod pip_uninstall;
|
mod pip_uninstall;
|
||||||
mod reporters;
|
mod reporters;
|
||||||
|
mod run;
|
||||||
#[cfg(feature = "self-update")]
|
#[cfg(feature = "self-update")]
|
||||||
mod self_update;
|
mod self_update;
|
||||||
mod venv;
|
mod venv;
|
||||||
|
|
63
crates/uv/src/commands/run.rs
Normal file
63
crates/uv/src/commands/run.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -539,6 +539,7 @@ async fn run() -> Result<ExitStatus> {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
Commands::Run(args) => commands::run(args.command, args.args, &cache).await,
|
||||||
#[cfg(feature = "self-update")]
|
#[cfg(feature = "self-update")]
|
||||||
Commands::Self_(SelfNamespace {
|
Commands::Self_(SelfNamespace {
|
||||||
command: SelfCommand::Update,
|
command: SelfCommand::Update,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue