diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index a257a4dea..6a41d9e53 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -158,16 +158,14 @@ pub(crate) async fn run( None }; - let temp_dir; - // Discover and sync the base environment. + let temp_dir; let base_interpreter = if let Some(script_interpreter) = script_interpreter { Some(script_interpreter) - } else if no_project { - // package is `None` (`--no-project` and `--package` are marked as conflicting in Clap). - None } else { - let project = if let Some(package) = package { + let project = if no_project { + None + } else if let Some(package) = package { // We need a workspace, but we don't need to have a current package, we can be e.g. in // the root of a virtual workspace and then switch into the selected package. Some(VirtualProject::Project( @@ -199,6 +197,8 @@ pub(crate) async fn run( } let venv = if isolated { + debug!("Creating isolated virtual environment"); + // If we're isolating the environment, use an ephemeral virtual environment as the // base environment for the project. let interpreter = { @@ -301,23 +301,43 @@ pub(crate) async fn run( } else { debug!("No project found; searching for Python interpreter"); - let client_builder = BaseClientBuilder::new() - .connectivity(connectivity) - .native_tls(native_tls); + let interpreter = { + let client_builder = BaseClientBuilder::new() + .connectivity(connectivity) + .native_tls(native_tls); - let python = PythonInstallation::find_or_fetch( - python.as_deref().map(PythonRequest::parse), - // No opt-in is required for system environments, since we are not mutating it. - EnvironmentPreference::Any, - python_preference, - python_fetch, - &client_builder, - cache, - Some(&reporter), - ) - .await?; + let python = PythonInstallation::find_or_fetch( + python.as_deref().map(PythonRequest::parse), + // No opt-in is required for system environments, since we are not mutating it. + EnvironmentPreference::Any, + python_preference, + python_fetch, + &client_builder, + cache, + Some(&reporter), + ) + .await?; - python.into_interpreter() + python.into_interpreter() + }; + + if isolated { + debug!("Creating isolated virtual environment"); + + // If we're isolating the environment, use an ephemeral virtual environment. + temp_dir = cache.environment()?; + let venv = uv_virtualenv::create_venv( + temp_dir.path(), + interpreter, + uv_virtualenv::Prompt::None, + false, + false, + false, + )?; + venv.into_interpreter() + } else { + interpreter + } }; Some(interpreter) diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index 5395622fa..b3907fd4e 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -1014,3 +1014,75 @@ fn run_isolated_python_version() -> Result<()> { Ok(()) } + +/// Ignore the existing project when executing with `--no-project`. +#[test] +fn run_no_project() -> Result<()> { + let context = TestContext::new("3.12") + .with_filtered_python_names() + .with_filtered_virtualenv_bin() + .with_filtered_exe_suffix(); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! { r#" + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + + [project] + name = "foo" + version = "1.0.0" + requires-python = ">=3.8" + dependencies = ["anyio"] + "# + })?; + + let src = context.temp_dir.child("src").child("foo"); + src.create_dir_all()?; + + let init = src.child("__init__.py"); + init.touch()?; + + // `run` should run in the context of the project. + uv_snapshot!(context.filters(), context.run().arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + [VENV]/[BIN]/python + + ----- stderr ----- + warning: `uv run` is experimental and may change without warning + Resolved 6 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + anyio==4.3.0 + + foo==1.0.0 (from file://[TEMP_DIR]/) + + idna==3.6 + + sniffio==1.3.1 + "###); + + // `run --no-project` should not (but it should still run in the same environment, as it would + // if there were no project at all). + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + [VENV]/[BIN]/python + + ----- stderr ----- + warning: `uv run` is experimental and may change without warning + "###); + + // `run --no-project --isolated` should run in an entirely isolated environment. + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("--isolated").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + [CACHE_DIR]/builds-v0/[TMP]/python + + ----- stderr ----- + warning: `uv run` is experimental and may change without warning + "###); + + Ok(()) +}