From 6a27135a6570c0ea43887b1f6bbafac9f5e15be8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 4 Jul 2024 13:23:13 -0400 Subject: [PATCH] Use cached environments in PEP 723 execution (#4789) ## Summary This seems like another good candidate for environment caching. If you run a script repeatedly, we can just use the existing cached environment. --- crates/uv/src/commands/project/environment.rs | 5 +++ crates/uv/src/commands/project/run.rs | 31 ++++++------------- crates/uv/tests/run.rs | 11 +++++++ 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index d83f6d3e5522..097392efee0c 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -126,4 +126,9 @@ impl CachedEnvironment { Ok(Self(venv)) } + + /// Convert the [`EphemeralEnvironment`] into an [`Interpreter`]. + pub(crate) fn into_interpreter(self) -> Interpreter { + self.0.into_interpreter() + } } diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 0b2a12e6649e..52abd1c7f4f0 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -21,6 +21,7 @@ use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_warnings::warn_user_once; use crate::commands::pip::operations::Modifications; +use crate::commands::project::environment::CachedEnvironment; use crate::commands::{project, ExitStatus, SharedState}; use crate::printer::Printer; use crate::settings::ResolverInstallerSettings; @@ -55,19 +56,10 @@ pub(crate) async fn run( let state = SharedState::default(); // Determine whether the command to execute is a PEP 723 script. - let temp_dir; let script_interpreter = if let RunCommand::Python(target, _) = &command { if let Some(metadata) = uv_scripts::read_pep723_metadata(&target).await? { debug!("Found PEP 723 script at: {}", target.display()); - let spec = RequirementsSpecification::from_requirements( - metadata - .dependencies - .into_iter() - .map(Requirement::from) - .collect(), - ); - // (1) Explicit request from user let python_request = if let Some(request) = python.as_deref() { Some(PythonRequest::parse(request)) @@ -96,20 +88,15 @@ pub(crate) async fn run( .await? .into_interpreter(); - // Create a virtual environment - temp_dir = cache.environment()?; - let venv = uv_virtualenv::create_venv( - temp_dir.path(), - interpreter, - uv_virtualenv::Prompt::None, - false, - false, - )?; - // Install the script requirements. - let environment = project::update_environment( - venv, - spec, + let requirements = metadata + .dependencies + .into_iter() + .map(Requirement::from) + .collect(); + let environment = CachedEnvironment::get_or_create( + requirements, + interpreter, &settings, &state, preview, diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index fb321a107e2d..4ca7583e3a3c 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -223,6 +223,7 @@ fn run_script() -> Result<()> { "# })?; + // Running the script should install the requirements. uv_snapshot!(context.filters(), context.run().arg("--preview").arg("main.py"), @r###" success: true exit_code: 0 @@ -235,6 +236,16 @@ fn run_script() -> Result<()> { + iniconfig==2.0.0 "###); + // Running again should use the existing environment. + uv_snapshot!(context.filters(), context.run().arg("--preview").arg("main.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + "###); + // Otherwise, the script requirements should _not_ be available, but the project requirements // should. let test_non_script = context.temp_dir.child("main.py");