From bd9fe3ddff3642ae4d29ce17be553c49834e66c7 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 26 Sep 2024 15:26:26 -0500 Subject: [PATCH] Add a `uv-python` shim executable --- .github/workflows/build-binaries.yml | 4 + .github/workflows/build-docker.yml | 2 +- Dockerfile | 7 +- crates/uv-cli/src/lib.rs | 8 ++ crates/uv-python/src/discovery.rs | 1 + crates/uv-python/src/interpreter.rs | 1 + crates/uv-python/src/lib.rs | 2 +- crates/uv/src/bin/uv-python.rs | 168 +++++++++++++++++++++++ crates/uv/src/commands/python/install.rs | 64 ++++++++- crates/uv/src/lib.rs | 8 ++ crates/uv/src/settings.rs | 14 +- crates/uv/tests/help.rs | 8 ++ docs/reference/cli.md | 4 + 13 files changed, 279 insertions(+), 12 deletions(-) create mode 100644 crates/uv/src/bin/uv-python.rs diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 74809604419a..291f2b7d8e03 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -56,6 +56,7 @@ jobs: ${{ env.MODULE_NAME }} --help python -m ${{ env.MODULE_NAME }} --help uvx --help + uv-python +3.11 --help - name: "Upload sdist" uses: actions/upload-artifact@v4 with: @@ -128,6 +129,7 @@ jobs: ${{ env.MODULE_NAME }} --help python -m ${{ env.MODULE_NAME }} --help uvx --help + uv-python +3.11 --help - name: "Upload wheels" uses: actions/upload-artifact@v4 with: @@ -185,6 +187,7 @@ jobs: ${{ env.MODULE_NAME }} --help python -m ${{ env.MODULE_NAME }} --help uvx --help + uv-python +3.11 --help - name: "Upload wheels" uses: actions/upload-artifact@v4 with: @@ -251,6 +254,7 @@ jobs: ${{ env.MODULE_NAME }} --help python -m ${{ env.MODULE_NAME }} --help uvx --help + uv-python +3.11 --help - name: "Upload wheels" uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index c4ce6a9cdf27..d73fda0e7cc4 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -196,7 +196,7 @@ jobs: # Generate Dockerfile content cat < Dockerfile FROM ${BASE_IMAGE} - COPY --from=${{ env.UV_BASE_IMG }}:latest /uv /uvx /usr/local/bin/ + COPY --from=${{ env.UV_BASE_IMG }}:latest /uv /uvx /uv-python /usr/local/bin/ ENTRYPOINT [] CMD ["/usr/local/bin/uv"] EOF diff --git a/Dockerfile b/Dockerfile index d6aa983854e1..3b091d484d87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,13 +34,14 @@ RUN rustup target add $(cat rust_target.txt) COPY crates crates COPY ./Cargo.toml Cargo.toml COPY ./Cargo.lock Cargo.lock -RUN cargo zigbuild --bin uv --bin uvx --target $(cat rust_target.txt) --release +RUN cargo zigbuild --bin uv --bin uvx --bin uv-python --target $(cat rust_target.txt) --release RUN cp target/$(cat rust_target.txt)/release/uv /uv \ - && cp target/$(cat rust_target.txt)/release/uvx /uvx + && cp target/$(cat rust_target.txt)/release/uvx /uvx \ + && cp target/$(cat rust_target.txt)/release/uv-python /uv-python # TODO(konsti): Optimize binary size, with a version that also works when cross compiling # RUN strip --strip-all /uv FROM scratch -COPY --from=build /uv /uvx / +COPY --from=build /uv /uvx /uv-python / WORKDIR /io ENTRYPOINT ["/uv"] diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 68b4dba9aada..514d1cdbcb6a 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3546,6 +3546,14 @@ pub struct PythonInstallArgs { /// installed. #[arg(long, short, alias = "force")] pub reinstall: bool, + + /// Install a `python` shim. + #[arg(long, overrides_with("no_shim"))] + pub shim: bool, + + /// Do not install a `python` shim. + #[arg(long, overrides_with("shim"))] + pub no_shim: bool, } #[derive(Args)] diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index a0ff76405ccd..e1f6dba155df 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -1219,6 +1219,7 @@ impl PythonRequest { if let Ok(version) = VersionRequest::from_str(value) { return Self::Version(version); } + // e.g. `python3.12.1` if let Some(remainder) = value.strip_prefix("python") { if let Ok(version) = VersionRequest::from_str(remainder) { diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index 4457f14cc6ad..a1349a6d1c2b 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -620,6 +620,7 @@ impl InterpreterInfo { .arg("-B") // Don't write bytecode. .arg("-c") .arg(script) + .env("UV_INTERNAL__PYTHON_QUERY", "1") .output() .map_err(|err| Error::SpawnFailed { path: interpreter.to_path_buf(), diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index cb8e99f67f62..895430648dd1 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -6,7 +6,7 @@ pub use crate::discovery::{ PythonNotFound, PythonPreference, PythonRequest, PythonSource, VersionRequest, }; pub use crate::environment::{InvalidEnvironment, InvalidEnvironmentKind, PythonEnvironment}; -pub use crate::implementation::ImplementationName; +pub use crate::implementation::{ImplementationName, LenientImplementationName}; pub use crate::installation::{PythonInstallation, PythonInstallationKey}; pub use crate::interpreter::{Error as InterpreterError, Interpreter}; pub use crate::pointer_size::PointerSize; diff --git a/crates/uv/src/bin/uv-python.rs b/crates/uv/src/bin/uv-python.rs new file mode 100644 index 000000000000..21b814a78f50 --- /dev/null +++ b/crates/uv/src/bin/uv-python.rs @@ -0,0 +1,168 @@ +use std::convert::Infallible; +use std::io::Write; +use std::{ + ffi::OsString, + process::{Command, ExitCode, ExitStatus}, +}; + +/// Spawns a command exec style. +fn exec_spawn(cmd: &mut Command) -> std::io::Result { + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + let err = cmd.exec(); + Err(err) + } + #[cfg(windows)] + { + cmd.stdin(std::process::Stdio::inherit()); + let status = cmd.status()?; + + #[allow(clippy::exit)] + std::process::exit(status.code().unwrap()) + } +} + +#[derive(Debug)] +enum Error { + Io(std::io::Error), + Which(which::Error), + NoInterpreter(String), + RecursiveQuery, +} + +#[derive(Debug, Default)] +struct Options { + request: Option, + system: bool, + managed: bool, + verbose: bool, +} + +impl Options { + fn as_args(&self) -> Vec<&str> { + let mut args = Vec::new(); + if let Some(request) = &self.request { + args.push(request.as_str()); + } else { + // By default, we should never select an alternative implementation with the shim + args.push("cpython"); + } + if self.system { + args.push("--system"); + } + if self.verbose { + args.push("--verbose"); + } + if self.managed { + args.push("--python-preference"); + args.push("only-managed"); + } + args + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Io(err) => write!(f, "{err}"), + Self::Which(err) => write!(f, "Failed to find uv binary: {err}"), + Self::NoInterpreter(inner) => write!(f, "{inner}"), + Self::RecursiveQuery => write!(f, "Ignoring recursive query from uv"), + } + } +} + +/// Parse `+