mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Add a command abstraction to uv run
(#4657)
## Summary Small refactor broken out from #4656.
This commit is contained in:
parent
7cc4565b5b
commit
b4c53fd15f
1 changed files with 99 additions and 29 deletions
|
@ -3,7 +3,6 @@ use std::ffi::OsString;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use itertools::Itertools;
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
@ -48,6 +47,9 @@ pub(crate) async fn run(
|
||||||
warn_user_once!("`uv run` is experimental and may change without warning.");
|
warn_user_once!("`uv run` is experimental and may change without warning.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the input command.
|
||||||
|
let command = RunCommand::from(command);
|
||||||
|
|
||||||
// Discover and sync the base environment.
|
// Discover and sync the base environment.
|
||||||
let base_interpreter = if isolated {
|
let base_interpreter = if isolated {
|
||||||
// package is `None`, isolated and package are marked as conflicting in clap.
|
// package is `None`, isolated and package are marked as conflicting in clap.
|
||||||
|
@ -215,25 +217,8 @@ pub(crate) async fn run(
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (target, args) = command.split();
|
debug!("Running `{command}`");
|
||||||
let (command, prefix_args) = if let Some(target) = target {
|
let mut process = Command::from(&command);
|
||||||
let target_path = PathBuf::from(&target);
|
|
||||||
if target_path
|
|
||||||
.extension()
|
|
||||||
.map_or(false, |ext| ext.eq_ignore_ascii_case("py"))
|
|
||||||
&& target_path.exists()
|
|
||||||
{
|
|
||||||
(OsString::from("python"), vec![target_path])
|
|
||||||
} else {
|
|
||||||
(target.clone(), vec![])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(OsString::from("python"), vec![])
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut process = Command::new(&command);
|
|
||||||
process.args(prefix_args);
|
|
||||||
process.args(args);
|
|
||||||
|
|
||||||
// Construct the `PATH` environment variable.
|
// Construct the `PATH` environment variable.
|
||||||
let new_path = std::env::join_paths(
|
let new_path = std::env::join_paths(
|
||||||
|
@ -285,15 +270,12 @@ pub(crate) async fn run(
|
||||||
// Spawn and wait for completion
|
// Spawn and wait for completion
|
||||||
// Standard input, output, and error streams are all inherited
|
// Standard input, output, and error streams are all inherited
|
||||||
// TODO(zanieb): Throw a nicer error message if the command is not found
|
// TODO(zanieb): Throw a nicer error message if the command is not found
|
||||||
let space = if args.is_empty() { "" } else { " " };
|
let mut handle = process.spawn().with_context(|| {
|
||||||
debug!(
|
format!(
|
||||||
"Running `{}{space}{}`",
|
"Failed to spawn: `{}`",
|
||||||
command.to_string_lossy(),
|
command.executable().to_string_lossy()
|
||||||
args.iter().map(|arg| arg.to_string_lossy()).join(" ")
|
)
|
||||||
);
|
})?;
|
||||||
let mut handle = process
|
|
||||||
.spawn()
|
|
||||||
.with_context(|| format!("Failed to spawn: `{}`", command.to_string_lossy()))?;
|
|
||||||
let status = handle.wait().await.context("Child process disappeared")?;
|
let status = handle.wait().await.context("Child process disappeared")?;
|
||||||
|
|
||||||
// Exit based on the result of the command
|
// Exit based on the result of the command
|
||||||
|
@ -304,3 +286,91 @@ pub(crate) async fn run(
|
||||||
Ok(ExitStatus::Failure)
|
Ok(ExitStatus::Failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum RunCommand {
|
||||||
|
/// Execute a `python` script.
|
||||||
|
Python(PathBuf, Vec<OsString>),
|
||||||
|
/// Execute an external command.
|
||||||
|
External(OsString, Vec<OsString>),
|
||||||
|
/// Execute an empty command (in practice, `python` with no arguments).
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunCommand {
|
||||||
|
/// Return the name of the target executable.
|
||||||
|
fn executable(&self) -> Cow<'_, OsString> {
|
||||||
|
match self {
|
||||||
|
Self::Python(_, _) | Self::Empty => Cow::Owned(OsString::from("python")),
|
||||||
|
Self::External(executable, _) => Cow::Borrowed(executable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for RunCommand {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Python(target, args) => {
|
||||||
|
write!(f, "python {}", target.display())?;
|
||||||
|
for arg in args {
|
||||||
|
write!(f, " {}", arg.to_string_lossy())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Self::External(command, args) => {
|
||||||
|
write!(f, "{}", command.to_string_lossy())?;
|
||||||
|
for arg in args {
|
||||||
|
write!(f, " {}", arg.to_string_lossy())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Self::Empty => {
|
||||||
|
write!(f, "python")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ExternalCommand> for RunCommand {
|
||||||
|
fn from(command: ExternalCommand) -> Self {
|
||||||
|
let (target, args) = command.split();
|
||||||
|
|
||||||
|
let Some(target) = target else {
|
||||||
|
return Self::Empty;
|
||||||
|
};
|
||||||
|
|
||||||
|
let target_path = PathBuf::from(&target);
|
||||||
|
if target_path
|
||||||
|
.extension()
|
||||||
|
.map_or(false, |ext| ext.eq_ignore_ascii_case("py"))
|
||||||
|
&& target_path.exists()
|
||||||
|
{
|
||||||
|
Self::Python(target_path, args.to_vec())
|
||||||
|
} else {
|
||||||
|
Self::External(
|
||||||
|
target.clone(),
|
||||||
|
args.iter().map(std::clone::Clone::clone).collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&RunCommand> for Command {
|
||||||
|
fn from(command: &RunCommand) -> Self {
|
||||||
|
match command {
|
||||||
|
RunCommand::Python(target, args) => {
|
||||||
|
let mut process = Command::new("python");
|
||||||
|
process.arg(target);
|
||||||
|
process.args(args);
|
||||||
|
process
|
||||||
|
}
|
||||||
|
RunCommand::External(executable, args) => {
|
||||||
|
let mut process = Command::new(executable);
|
||||||
|
process.args(args);
|
||||||
|
process
|
||||||
|
}
|
||||||
|
RunCommand::Empty => Command::new("python"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue