|
| 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 | +} |
0 commit comments