diff --git a/crates/uv/src/cli.rs b/crates/uv/src/cli.rs index 913f613f1..d5b4b5df2 100644 --- a/crates/uv/src/cli.rs +++ b/crates/uv/src/cli.rs @@ -1706,6 +1706,20 @@ pub(crate) struct RunArgs { #[arg(long)] pub(crate) with: Vec, + /// The Python interpreter to use to build the run environment. + /// + /// By default, `uv` uses the virtual environment in the current working directory or any parent + /// directory, falling back to searching for a Python executable in `PATH`. The `--python` + /// option allows you to specify a different interpreter. + /// + /// Supported formats: + /// - `3.10` looks for an installed Python 3.10 using `py --list-paths` on Windows, or + /// `python3.10` on Linux and macOS. + /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. + /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. + #[arg(long, short, verbatim_doc_comment, group = "discovery")] + pub(crate) python: Option, + /// Run without the current workspace installed. #[arg(long)] pub(crate) no_workspace: bool, diff --git a/crates/uv/src/commands/run.rs b/crates/uv/src/commands/run.rs index 5dee4e4e2..245796928 100644 --- a/crates/uv/src/commands/run.rs +++ b/crates/uv/src/commands/run.rs @@ -45,6 +45,7 @@ pub(crate) async fn run( target: Option, mut args: Vec, mut requirements: Vec, + python: Option, isolated: bool, no_workspace: bool, preview: PreviewMode, @@ -86,7 +87,15 @@ pub(crate) async fn run( // Detect the current Python interpreter. // TODO(zanieb): Create ephemeral environments // TODO(zanieb): Accept `--python` - let run_env = environment_for_run(&requirements, &overrides, isolated, cache, printer).await?; + let run_env = environment_for_run( + &requirements, + &overrides, + python.as_deref(), + isolated, + cache, + printer, + ) + .await?; let python_env = run_env.python; // Construct the command @@ -164,6 +173,7 @@ fn find_workspace_requirements() -> Result>> { async fn environment_for_run( requirements: &[RequirementsSource], overrides: &[RequirementsSource], + python: Option<&str>, isolated: bool, cache: &Cache, printer: Printer, @@ -194,35 +204,42 @@ async fn environment_for_run( ) .await?; - // Check if the current environment satisfies the requirements - if let Some(venv) = current_venv { - // Determine the set of installed packages. - let site_packages = SitePackages::from_executable(&venv)?; - - // If the requirements are already satisfied, we're done. Ideally, the resolver would be fast - // enough to let us remove this check. But right now, for large environments, it's an order of - // magnitude faster to validate the environment than to resolve the requirements. - if spec.source_trees.is_empty() - && site_packages.satisfies(&spec.requirements, &spec.editables, &spec.constraints)? - { - debug!("Current environment satisfies requirements"); - return Ok(RunEnvironment { - python: venv, - _temp_dir_drop: None, - }); - } - } - // Otherwise, we need a new environment - - // Find an interpreter to use - // TODO(zanieb): Populate `python` from the user - let python = None; + // Determine an interpreter to use let python_env = if let Some(python) = python { PythonEnvironment::from_requested_python(python, cache)? } else { PythonEnvironment::from_default_python(cache)? }; + // Check if the current environment satisfies the requirements + if let Some(venv) = current_venv { + // Ensure it matches the selected interpreter + // TODO(zanieb): We should check if a version was requested and see if the environment meets that + // too but this can wait until we refactor interpreter discovery + if venv.root() == python_env.root() { + // Determine the set of installed packages. + let site_packages = SitePackages::from_executable(&venv)?; + + // If the requirements are already satisfied, we're done. Ideally, the resolver would be fast + // enough to let us remove this check. But right now, for large environments, it's an order of + // magnitude faster to validate the environment than to resolve the requirements. + if spec.source_trees.is_empty() + && site_packages.satisfies( + &spec.requirements, + &spec.editables, + &spec.constraints, + )? + { + debug!("Current environment satisfies requirements"); + return Ok(RunEnvironment { + python: venv, + _temp_dir_drop: None, + }); + } + } + } + // Otherwise, we need a new environment + // Create a virtual environment // TODO(zanieb): Move this path derivation elsewhere let uv_state_path = std::env::current_dir()?.join(".uv"); diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 962a9906c..945b28fd0 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -507,6 +507,7 @@ async fn run() -> Result { args.target, args.args, requirements, + args.python, args.isolated, args.no_workspace, globals.preview, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index ae1cd39dc..d458ff214 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -88,6 +88,7 @@ pub(crate) struct RunSettings { pub(crate) isolated: bool, pub(crate) with: Vec, pub(crate) no_workspace: bool, + pub(crate) python: Option, } impl RunSettings { @@ -100,6 +101,7 @@ impl RunSettings { isolated, with, no_workspace, + python, } = args; Self { @@ -109,6 +111,7 @@ impl RunSettings { isolated, with, no_workspace, + python, } } }