mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Create ephemeral virtual environments for uv run
(#3075)
If a virtual environment does not exist, we will create one for the duration of the invocation. Adds an `--isolated` flag to force this behavior (ignoring an existing virtual environment).
This commit is contained in:
parent
f7b83e9e83
commit
dcc2c6865c
4 changed files with 96 additions and 29 deletions
|
@ -126,6 +126,11 @@ impl PythonEnvironment {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Interpreter`] for this virtual environment.
|
||||
pub fn into_interpreter(self) -> Interpreter {
|
||||
self.interpreter
|
||||
}
|
||||
}
|
||||
|
||||
/// Locate the current virtual environment.
|
||||
|
|
|
@ -1312,12 +1312,16 @@ pub(crate) struct VenvArgs {
|
|||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub(crate) struct RunArgs {
|
||||
/// Command
|
||||
/// The command to run.
|
||||
pub(crate) command: String,
|
||||
|
||||
/// Arguments
|
||||
#[clap(allow_hyphen_values = true)]
|
||||
/// The arguments to the command.
|
||||
#[arg(allow_hyphen_values = true)]
|
||||
pub(crate) args: Vec<String>,
|
||||
|
||||
/// Always use a new virtual environment.
|
||||
#[arg(long)]
|
||||
pub(crate) isolated: bool,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::{env, iter};
|
|||
|
||||
use anyhow::Result;
|
||||
use owo_colors::OwoColorize;
|
||||
use tempfile::{tempdir_in, TempDir};
|
||||
use tracing::debug;
|
||||
use uv_fs::Simplified;
|
||||
use uv_interpreter::PythonEnvironment;
|
||||
|
@ -13,43 +14,42 @@ 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(" "));
|
||||
|
||||
pub(crate) async fn run(
|
||||
command: String,
|
||||
args: Vec<String>,
|
||||
isolated: bool,
|
||||
cache: &Cache,
|
||||
) -> Result<ExitStatus> {
|
||||
// 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()),
|
||||
};
|
||||
let run_env = environment_for_run(isolated, cache)?;
|
||||
let python_env = run_env.python;
|
||||
|
||||
// Construct the command
|
||||
let mut process = Command::new(command);
|
||||
process.args(args);
|
||||
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);
|
||||
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
|
||||
debug!("Running `{command} {}`", args.join(" "));
|
||||
let mut handle = process.spawn()?;
|
||||
let status = handle.wait().await?;
|
||||
|
||||
|
@ -61,3 +61,61 @@ pub(crate) async fn run(command: String, args: Vec<String>, cache: &Cache) -> Re
|
|||
Ok(ExitStatus::Failure)
|
||||
}
|
||||
}
|
||||
|
||||
struct RunEnvironment {
|
||||
/// The Python environment to execute the run in.
|
||||
python: PythonEnvironment,
|
||||
/// A temporary directory, if a new virtual environment was created.
|
||||
///
|
||||
/// Included to ensure that the temporary directory exists for the length of the operation, but
|
||||
/// is dropped at the end as appropriate.
|
||||
_temp_dir_drop: Option<TempDir>,
|
||||
}
|
||||
|
||||
/// Returns an environment for a `run` invocation.
|
||||
///
|
||||
/// Will use the current virtual environment (if any) unless `isolated` is true.
|
||||
/// Will create virtual environments in a temporary directory (if necessary).
|
||||
fn environment_for_run(isolated: bool, cache: &Cache) -> Result<RunEnvironment> {
|
||||
if !isolated {
|
||||
// Return the active environment if it exists
|
||||
match PythonEnvironment::from_virtualenv(cache) {
|
||||
Ok(env) => {
|
||||
return Ok(RunEnvironment {
|
||||
python: env,
|
||||
_temp_dir_drop: None,
|
||||
})
|
||||
}
|
||||
Err(uv_interpreter::Error::VenvNotFound) => {}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
}
|
||||
|
||||
// Find an interpreter to use
|
||||
// TODO(zanieb): Populate `python` from the user
|
||||
let python = None;
|
||||
let python_env = if let Some(python) = python {
|
||||
PythonEnvironment::from_requested_python(python, cache)?
|
||||
} else {
|
||||
PythonEnvironment::from_default_python(cache)?
|
||||
};
|
||||
|
||||
// Create a virtual environment directory
|
||||
// TODO(zanieb): Move this path derivation elsewhere
|
||||
let uv_state_path = std::env::current_dir()?.join(".uv");
|
||||
fs_err::create_dir_all(&uv_state_path)?;
|
||||
let tmpdir = tempdir_in(uv_state_path)?;
|
||||
|
||||
// Create the environment
|
||||
// TODO(zanieb): Add dependencies to the env
|
||||
Ok(RunEnvironment {
|
||||
python: uv_virtualenv::create_venv(
|
||||
tmpdir.path(),
|
||||
python_env.into_interpreter(),
|
||||
uv_virtualenv::Prompt::None,
|
||||
false,
|
||||
Vec::new(),
|
||||
)?,
|
||||
_temp_dir_drop: Some(tmpdir),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -539,7 +539,7 @@ async fn run() -> Result<ExitStatus> {
|
|||
)
|
||||
.await
|
||||
}
|
||||
Commands::Run(args) => commands::run(args.command, args.args, &cache).await,
|
||||
Commands::Run(args) => commands::run(args.command, args.args, args.isolated, &cache).await,
|
||||
#[cfg(feature = "self-update")]
|
||||
Commands::Self_(SelfNamespace {
|
||||
command: SelfCommand::Update,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue