diff --git a/.bazelrc b/.bazelrc index de624e78..bcfe66ff 100644 --- a/.bazelrc +++ b/.bazelrc @@ -27,8 +27,12 @@ common:ci --toolchain_resolution_debug='@@bazel_tools//tools/cpp:toolchain_type' common:release --build_tag_filters=release common:release --stamp common:release --compilation_mode=opt -common:release --@rules_rust//rust/settings:lto=fat -common:release --@rules_rust//:extra_rustc_flags=-Cpanic=abort + +# These are reproducibility settings, but we set them globally to avoid having +# to rebuild rust libraries and C-extensions in many configurations. +common --@rules_rust//rust/settings:lto=fat +common --@rules_rust//:extra_rustc_flags=-Cstrip=debuginfo +common --stripopt=--strip-all # Speed up local development by using the non-hermetic CPP toolchain. common:nollvm --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=0 diff --git a/Cargo.lock b/Cargo.lock index 3a540ae7..f90838a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,9 +366,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -376,9 +376,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -388,9 +388,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -400,9 +400,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" @@ -3314,6 +3314,7 @@ dependencies = [ name = "venv_shim" version = "0.1.0" dependencies = [ + "clap", "miette", "runfiles", "which 8.0.0", diff --git a/bazel/rust/defs.bzl b/bazel/rust/defs.bzl index d5bc4697..55de39ce 100644 --- a/bazel/rust/defs.bzl +++ b/bazel/rust/defs.bzl @@ -2,26 +2,12 @@ load("@aspect_bazel_lib//lib:expand_template.bzl", _expand_template = "expand_template") load("@rules_rust//rust:defs.bzl", _rust_binary = "rust_binary", _rust_library = "rust_library", _rust_proc_macro = "rust_proc_macro", _rust_test = "rust_test") -load("@with_cfg.bzl", "with_cfg") _default_platform = select({ # Non-Linux binaries should just build with their default platforms "//conditions:default": None, }) -rust_opt_binary, _rust_opt_binary_internal = with_cfg(_rust_binary).set( - "compilation_mode", - "opt", -).set( - Label("@rules_rust//:extra_rustc_flags"), - [ - "-Cstrip=symbols", - "-Ccodegen-units=1", - "-Cpanic=abort", - ], - # Avoid rules_rust trying to instrument this binary -).set("collect_code_coverage", "false").build() - def rust_binary(name, rustc_env_files = [], version_key = "", crate_features = [], platform = _default_platform, **kwargs): """ Macro for rust_binary defaults. @@ -50,7 +36,7 @@ def rust_binary(name, rustc_env_files = [], version_key = "", crate_features = [ # Note that we use symbol stripping to # try and make these artifacts reproducibly sized for the # container_structure tests. - rust_opt_binary( + _rust_binary( name = name, rustc_env_files = rustc_env_files, crate_features = crate_features + ["bazel"], diff --git a/e2e/cases/uv-deps-650/crossbuild/BUILD.bazel b/e2e/cases/uv-deps-650/crossbuild/BUILD.bazel index 03cab11a..34206357 100644 --- a/e2e/cases/uv-deps-650/crossbuild/BUILD.bazel +++ b/e2e/cases/uv-deps-650/crossbuild/BUILD.bazel @@ -13,9 +13,6 @@ platform( flags = [ "--@aspect_rules_py//uv/private/constraints/platform:platform_libc=glibc", "--@aspect_rules_py//uv/private/constraints/platform:platform_version=2.39", - "--@rules_rust//:extra_rustc_flag=-Cstrip=debuginfo", - "--@rules_rust//rust/settings:lto=fat", - "--stripopt=--strip-all", ], ) @@ -28,9 +25,6 @@ platform( flags = [ "--@aspect_rules_py//uv/private/constraints/platform:platform_libc=glibc", "--@aspect_rules_py//uv/private/constraints/platform:platform_version=2.39", - "--@rules_rust//:extra_rustc_flag=-Cstrip=debuginfo", - "--@rules_rust//rust/settings:lto=fat", - "--stripopt=--strip-all", ], ) diff --git a/py/private/py_venv/entrypoint.tmpl.sh b/py/private/py_venv/entrypoint.tmpl.sh index 69e47ea3..89363410 100644 --- a/py/private/py_venv/entrypoint.tmpl.sh +++ b/py/private/py_venv/entrypoint.tmpl.sh @@ -12,4 +12,4 @@ set -o errexit -o nounset -o pipefail source "$(rlocation "{{VENV}}")"/bin/activate -exec "$(rlocation "{{VENV}}")"/bin/python {{INTERPRETER_FLAGS}} "$@" +exec "$(rlocation "{{VENV}}")"/bin/python3 {{INTERPRETER_FLAGS}} "$@" diff --git a/py/tests/py-venv-standalone-interpreter/test.sh b/py/tests/py-venv-standalone-interpreter/test.sh index fb0d923e..d3775b73 100755 --- a/py/tests/py-venv-standalone-interpreter/test.sh +++ b/py/tests/py-venv-standalone-interpreter/test.sh @@ -4,7 +4,7 @@ set -ex ROOT="$(dirname $0)" -"$ROOT"/.ex/bin/python --help >/dev/null 2>&1 +"$ROOT"/.ex/bin/python --help if [ "Hello, world!" != "$($ROOT/.ex/bin/python -c 'from ex import hello; print(hello())')" ]; then exit 1 diff --git a/py/tests/py_venv_image_layer/BUILD.bazel b/py/tests/py_venv_image_layer/BUILD.bazel index d4db5eac..eec1692d 100644 --- a/py/tests/py_venv_image_layer/BUILD.bazel +++ b/py/tests/py_venv_image_layer/BUILD.bazel @@ -14,9 +14,6 @@ platform( flags = [ "--//uv/private/constraints/platform:platform_libc=glibc", "--//uv/private/constraints/platform:platform_version=2.39", - "--@rules_rust//:extra_rustc_flag=-Cstrip=debuginfo", - "--@rules_rust//rust/settings:lto=fat", - "--stripopt=--strip-all", ], ) @@ -29,9 +26,6 @@ platform( flags = [ "--//uv/private/constraints/platform:platform_libc=glibc", "--//uv/private/constraints/platform:platform_version=2.39", - "--@rules_rust//:extra_rustc_flag=-Cstrip=debuginfo", - "--@rules_rust//rust/settings:lto=fat", - "--stripopt=--strip-all", ], ) diff --git a/py/tests/py_venv_image_layer/my_app_amd64_layers_listing.yaml b/py/tests/py_venv_image_layer/my_app_amd64_layers_listing.yaml index 469cf47e..8bf9d3a9 100644 --- a/py/tests/py_venv_image_layer/my_app_amd64_layers_listing.yaml +++ b/py/tests/py_venv_image_layer/my_app_amd64_layers_listing.yaml @@ -2501,7 +2501,7 @@ files: - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/ - - -rwxr-xr-x 0 0 0 1245 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin + - -rwxr-xr-x 0 0 0 1246 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/+uv+whl_install__pypi__default__colorama/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/+uv+whl_install__pypi__default__colorama/install/ @@ -2521,14 +2521,14 @@ files: - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/ - -rwxr-xr-x 0 0 0 2503 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/activate - - -rwxr-xr-x 0 0 0 799896 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python - - -rwxr-xr-x 0 0 0 799896 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python3 - - -rwxr-xr-x 0 0 0 799896 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python3.9 + - -rwxr-xr-x 0 0 0 4342824 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python + - -rwxr-xr-x 0 0 0 4342824 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python3 + - -rwxr-xr-x 0 0 0 4342824 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python3.9 - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/lib/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/lib/python3.9/ - -rwxr-xr-x 0 0 0 348 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/pyvenv.cfg - -rwxr-xr-x 0 0 0 390 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/__main__.py - - -rwxr-xr-x 0 0 0 1245 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/my_app_bin + - -rwxr-xr-x 0 0 0 1246 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/my_app_bin - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/bazel_tools/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/bazel_tools/tools/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/bazel_tools/tools/bash/ diff --git a/py/tests/py_venv_image_layer/my_app_arm64_layers_listing.yaml b/py/tests/py_venv_image_layer/my_app_arm64_layers_listing.yaml index 90e9c70d..12b32df5 100644 --- a/py/tests/py_venv_image_layer/my_app_arm64_layers_listing.yaml +++ b/py/tests/py_venv_image_layer/my_app_arm64_layers_listing.yaml @@ -2482,7 +2482,7 @@ files: - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/ - - -rwxr-xr-x 0 0 0 1245 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin + - -rwxr-xr-x 0 0 0 1246 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/+uv+whl_install__pypi__default__colorama/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/+uv+whl_install__pypi__default__colorama/install/ @@ -2502,14 +2502,14 @@ files: - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/ - -rwxr-xr-x 0 0 0 2503 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/activate - - -rwxr-xr-x 0 0 0 871208 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python - - -rwxr-xr-x 0 0 0 871208 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python3 - - -rwxr-xr-x 0 0 0 871208 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python3.9 + - -rwxr-xr-x 0 0 0 4531120 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python + - -rwxr-xr-x 0 0 0 4531120 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python3 + - -rwxr-xr-x 0 0 0 4531120 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/bin/python3.9 - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/lib/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/lib/python3.9/ - -rwxr-xr-x 0 0 0 349 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/.my_app_bin/pyvenv.cfg - -rwxr-xr-x 0 0 0 390 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/__main__.py - - -rwxr-xr-x 0 0 0 1245 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/my_app_bin + - -rwxr-xr-x 0 0 0 1246 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/_main/py/tests/py_venv_image_layer/my_app_bin - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/bazel_tools/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/bazel_tools/tools/ - drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/bazel_tools/tools/bash/ diff --git a/py/tools/venv_shim/BUILD.bazel b/py/tools/venv_shim/BUILD.bazel index 260200ec..91368894 100644 --- a/py/tools/venv_shim/BUILD.bazel +++ b/py/tools/venv_shim/BUILD.bazel @@ -1,3 +1,4 @@ +load("@rules_rust//rust:defs.bzl", "rust_test") load("//bazel/release:release.bzl", "release") load("//bazel/rust:defs.bzl", "rust_binary") load("//bazel/rust:multi_platform_rust_binaries.bzl", "multi_platform_rust_binaries") @@ -6,14 +7,21 @@ rust_binary( name = "shim", srcs = [ "src/main.rs", + "src/pyargs.rs", ], deps = [ "//py/tools/runfiles", + "@crates//:clap", "@crates//:miette", "@crates//:which", ], ) +rust_test( + name = "shim_tests", + crate = ":shim", +) + multi_platform_rust_binaries( name = "bins", target = ":shim", diff --git a/py/tools/venv_shim/Cargo.toml b/py/tools/venv_shim/Cargo.toml index f695dbaf..cd1ee47a 100644 --- a/py/tools/venv_shim/Cargo.toml +++ b/py/tools/venv_shim/Cargo.toml @@ -20,5 +20,6 @@ path = "src/main.rs" [dependencies] miette = { workspace = true } +clap = { workspace = true } runfiles = { path = "../runfiles" } which = "8.0.0" diff --git a/py/tools/venv_shim/src/main.rs b/py/tools/venv_shim/src/main.rs index 4ac0782c..8f060847 100644 --- a/py/tools/venv_shim/src/main.rs +++ b/py/tools/venv_shim/src/main.rs @@ -1,4 +1,6 @@ -use miette::{miette, Context, IntoDiagnostic}; +mod pyargs; + +use miette::{miette, Context, IntoDiagnostic, Result}; use runfiles::Runfiles; use which::which; // Depended on out of rules_rust @@ -12,7 +14,7 @@ use std::process::Command; const PYVENV: &str = "pyvenv.cfg"; -fn find_venv_root(exec_name: impl AsRef) -> miette::Result<(PathBuf, PathBuf)> { +fn find_venv_root(exec_name: impl AsRef) -> Result<(PathBuf, PathBuf)> { let exec_name = exec_name.as_ref().to_owned(); if let Some(parent) = exec_name.parent().and_then(|it| it.parent()) { let cfg = parent.join(PYVENV); @@ -40,7 +42,7 @@ struct PyCfg { user_site: bool, } -fn parse_venv_cfg(venv_root: &Path, cfg_path: &Path) -> miette::Result { +fn parse_venv_cfg(venv_root: &Path, cfg_path: &Path) -> Result { let mut version: Option = None; let mut bazel_interpreter: Option = None; let mut bazel_repo: Option = None; @@ -74,10 +76,7 @@ fn parse_venv_cfg(venv_root: &Path, cfg_path: &Path) -> miette::Result { root: venv_root.to_path_buf(), cfg: cfg_path.to_path_buf(), version_info: version, - interpreter: InterpreterConfig::Runfiles { - rpath: rloc, - repo: repo, - }, + interpreter: InterpreterConfig::Runfiles { rpath: rloc, repo }, user_site: user_site.expect("User site flag not set!"), }), (Some(version), None, None) => Ok(PyCfg { @@ -137,7 +136,7 @@ fn find_python_executables(version_from_cfg: &str, exclude_dir: &Path) -> Option } } -fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> miette::Result { +fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> Result { match &cfg.interpreter { InterpreterConfig::External { version } => { // NOTE (reid@aspect.build): @@ -164,7 +163,7 @@ fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> miette: // That logic has optimistically been discarded. If this causes // problems we'd put it back here. - let Some(python_executables) = find_python_executables(&version, &cfg.root.join("bin")) + let Some(python_executables) = find_python_executables(version, &cfg.root.join("bin")) else { miette::bail!( "No suitable Python interpreter found in PATH matching version '{version}'." @@ -181,7 +180,7 @@ fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> miette: } } - let Some(actual_interpreter_path) = python_executables.get(0) else { + let Some(actual_interpreter_path) = python_executables.first() else { miette::bail!("Unable to find another interpreter!"); }; @@ -189,8 +188,8 @@ fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> miette: } InterpreterConfig::Runfiles { rpath, repo } => { if let Ok(r) = Runfiles::create(&executable) { - if let Some(interpreter) = r.rlocation_from(rpath.as_str(), &repo) { - Ok(PathBuf::from(interpreter)) + if let Some(interpreter) = r.rlocation_from(rpath.as_str(), repo) { + Ok(interpreter) } else { miette::bail!(format!( "Unable to identify an interpreter for venv {:?}", @@ -205,12 +204,12 @@ fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> miette: PathBuf::from(".") }; for candidate in [ - action_root.join("external").join(&rpath), + action_root.join("external").join(rpath), action_root .join("bazel-out/k8-fastbuild/bin/external") - .join(&rpath), - action_root.join(&rpath), - action_root.join("bazel-out/k8-fastbuild/bin").join(&rpath), + .join(rpath), + action_root.join(rpath), + action_root.join("bazel-out/k8-fastbuild/bin").join(rpath), ] { if candidate.exists() { return Ok(candidate); @@ -224,9 +223,9 @@ fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> miette: } } -fn main() -> miette::Result<()> { +fn main() -> Result<()> { let all_args: Vec<_> = env::args().collect(); - let Some((exec_name, exec_args)) = all_args.split_first() else { + let Some((exec_name, _exec_args)) = all_args.split_first() else { miette::bail!("could not discover an execution command-line"); }; @@ -259,12 +258,14 @@ fn main() -> miette::Result<()> { executable = candidate; #[cfg(feature = "debug")] eprintln!(" {:?}", executable); - } else if let Ok(exe) = which(&exec_name) { + } else if let Ok(exe) = which(exec_name) { executable = exe; #[cfg(feature = "debug")] eprintln!(" {:?}", executable); } else { - return Err(miette!("Unable to identify the real interpreter path!")); + executable = env::current_exe().unwrap(); + #[cfg(feature = "debug")] + eprintln!("Falling back to the libc abspath {:?}", executable); } } @@ -276,7 +277,7 @@ fn main() -> miette::Result<()> { && !executable.components().any(|it| { it.as_os_str() .to_str() - .expect(&format!("Failed to normalize {:?} as a str", it)) + .unwrap_or_else(|| panic!("Failed to normalize {:?} as a str", it)) .ends_with(".runfiles") }) { @@ -294,7 +295,7 @@ fn main() -> miette::Result<()> { // Resolve the link we identified let parent = parent .parent() - .expect(&format!("Failed to take the parent of {:?}", parent)) + .unwrap_or_else(|| panic!("Failed to take the parent of {:?}", parent)) .join(parent.read_link().into_diagnostic()?); // And join the tail to the resolved head executable = parent.join(suffix); @@ -325,7 +326,7 @@ fn main() -> miette::Result<()> { eprintln!("[aspect] {:?}", venv_config); // The logical path of the interpreter - let venv_interpreter = venv_root.join("bin/python3"); + let venv_interpreter = venv_root.join("bin").join(executable.file_name().unwrap()); #[cfg(feature = "debug")] eprintln!("[aspect] {:?}", venv_interpreter); @@ -339,17 +340,23 @@ fn main() -> miette::Result<()> { &actual_interpreter, &venv_interpreter, exec_args, ); + let mut reexec_args = pyargs::reparse_args(&all_args.iter().map(|it| it.as_ref()).collect())?; + // Lie about the value of argv0 to hoodwink the interpreter as to its + // location on Linux-based platforms. + reexec_args.remove(0); + + #[cfg(feature = "debug")] + eprintln!("Reexec args {:?}", reexec_args); + let mut cmd = Command::new(&actual_interpreter); let cmd = cmd - // Pass along our args - .args(exec_args) - // Lie about the value of argv0 to hoodwink the interpreter as to its - // location on Linux-based platforms. .arg0(&venv_interpreter) + // Pass along our args + .args(reexec_args) // Pseudo-`activate` .env("VIRTUAL_ENV", &venv_root); - let venv_bin = (&venv_root).join("bin"); + let venv_bin = venv_root.join("bin"); // TODO(arrdem|myrrlyn): PATHSEP is : on Unix and ; on Windows if let Ok(path) = env::var("PATH") { let mut path_segments = path @@ -357,10 +364,7 @@ fn main() -> miette::Result<()> { .filter(|&p| !p.is_empty()) // skip over `::`, which is possible .map(ToOwned::to_owned) // we're dropping the big string, so own the fragments .collect::>(); // and save them. - let need_venv_in_path = path_segments - .iter() - .find(|&p| OsStr::new(p) == &venv_bin) - .is_none(); + let need_venv_in_path = !path_segments.iter().any(|p| OsStr::new(p) == venv_bin); if need_venv_in_path { // append to back path_segments.push(venv_bin.to_string_lossy().into_owned()); @@ -377,12 +381,6 @@ fn main() -> miette::Result<()> { // Clobber VIRTUAL_ENV which may have been set by activate to an unresolved path cmd.env("VIRTUAL_ENV", &venv_root); - // Similar to `-s` but this avoids us having to muck with the argv in ways - // that could be visible to the called program. - if !venv_config.user_site { - cmd.env("PYTHONNOUSERSITE", "1"); - } - // Set the interpreter home to the resolved install base. This works around // the home = property in the pyvenv.cfg being wrong because we don't // (currently) have a good way to map the interpreter rlocation to a @@ -400,26 +398,15 @@ fn main() -> miette::Result<()> { eprintln!("Setting PYTHONHOME to {home:?} for {actual_interpreter:?}"); cmd.env("PYTHONHOME", home); + // For the future, we could read, validate and reuse the env state. let mut hasher = DefaultHasher::new(); venv_interpreter.to_str().unwrap().hash(&mut hasher); + venv_root.to_str().unwrap().hash(&mut hasher); home.to_str().unwrap().hash(&mut hasher); - cmd.env("ASPECT_PY_VALIDITY", format!("{}", hasher.finish())); - // For the future, we could read, validate and reuse the env state. - // - // if let Ok(home) = env::var("PYTHONHOME") { - // if let Ok(executable) = env::var("PYTHONEXECUTABLE") { - // if let Ok(checksum) = env::var("ASPECT_PY_VALIDITY") { - // let mut hasher = DefaultHasher::new(); - // executable.hash(&mut hasher); - // home.hash(&mut hasher); - // if checksum == format!("{}", hasher.finish()) { - // return Ok(PathBuf::from(home).join("bin/python3")); - // } - // } - // } - // } + #[cfg(feature = "debug")] + eprintln!("Punting to {:?}", cmd); // And punt let err = cmd.exec(); diff --git a/py/tools/venv_shim/src/pyargs.rs b/py/tools/venv_shim/src/pyargs.rs new file mode 100644 index 00000000..e9313824 --- /dev/null +++ b/py/tools/venv_shim/src/pyargs.rs @@ -0,0 +1,231 @@ +use clap::{arg, Arg, ArgAction, ArgMatches, Command}; +use miette::{IntoDiagnostic, Result}; + +fn build_parser() -> Command { + Command::new("python_like_parser") + .disable_version_flag(true) + .disable_help_flag(true) + .dont_delimit_trailing_values(true) + .allow_hyphen_values(true) + .arg(Arg::new("ignore_env").short('E').action(ArgAction::SetTrue)) + .arg(Arg::new("isolate").short('I').action(ArgAction::SetTrue)) + .arg( + Arg::new("no_user_site") + .short('s') + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("no_import_site") + .short('S') + .action(ArgAction::SetTrue), + ) + .arg( + arg!( ...) + .trailing_var_arg(true) + .required(false) + .allow_hyphen_values(true), + ) +} + +pub struct ArgState { + pub ignore_env: bool, + pub isolate: bool, + pub no_import_site: bool, + pub no_user_site: bool, + pub remaining_args: Vec, +} + +fn extract_state(matches: &ArgMatches) -> ArgState { + ArgState { + // E and I are crucial for transformation + ignore_env: *matches.get_one::("ignore_env").unwrap_or(&false), + isolate: *matches.get_one::("isolate").unwrap_or(&false), + + // s is crucial for transformation + no_user_site: *matches.get_one::("no_user_site").unwrap_or(&false), + + no_import_site: *matches.get_one::("no_import_site").unwrap_or(&false), + + remaining_args: matches + .get_many::("args") + .unwrap_or_default() + .map(|it| it.to_string()) + .collect::>(), + } +} + +pub fn reparse_args(original_argv: &Vec<&str>) -> Result> { + let parser = build_parser(); + let matches = parser + .try_get_matches_from(original_argv) + .into_diagnostic()?; + let parsed_args = extract_state(&matches); + + let mut argv: Vec = Vec::new(); + let push_flag = |argv: &mut Vec, flag: char, is_set: bool| { + if is_set { + argv.push(format!("-{}", flag)); + } + }; + + // Retain the original argv binary + argv.push(original_argv[0].to_string()); + + // -I replacement logic: -I is never pushed, its effects (-E and -s) are handled separately. + // -E removal: -E is never pushd + // -s inclusion logic: we ALWAYS push -s + push_flag( + &mut argv, + 's', + parsed_args.no_user_site | parsed_args.isolate, + ); + + push_flag(&mut argv, 'S', parsed_args.no_import_site); + + argv.extend(parsed_args.remaining_args.iter().cloned()); + + Ok(argv) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn basic_args_preserved1() { + let orig = vec!["python", "-B", "-s", "script.py", "arg1"]; + let reparsed = reparse_args(&orig); + assert!(reparsed.is_ok(), "Args failed to parse {:?}", reparsed); + let reparsed = reparsed.unwrap(); + assert!( + orig == reparsed, + "Args shouldn't have changed, got {:?}", + reparsed + ); + } + + #[test] + fn basic_args_preserved2() { + let orig = vec!["python", "-s", "-c", "exit(0)", "arg1"]; + let reparsed = reparse_args(&orig); + assert!(&reparsed.is_ok(), "Args failed to parse {:?}", reparsed); + let reparsed = reparsed.unwrap(); + assert!( + orig == reparsed, + "Args shouldn't have changed, got {:?}", + reparsed + ); + } + + #[test] + fn basic_binary_preserved() { + let orig = vec![ + "/some/arbitrary/path/python", + "-B", + "-s", + "script.py", + "arg1", + ]; + let reparsed = reparse_args(&orig); + assert!(reparsed.is_ok(), "Args failed to parse {:?}", reparsed); + let reparsed = reparsed.unwrap(); + assert!( + orig == reparsed, + "Args shouldn't have changed, got {:?}", + reparsed + ); + } + + #[test] + fn basic_s_remains() { + // We expect to not modify the -s flag + let orig = vec!["python", "-s", "-c", "exit(0)", "arg1"]; + let reparsed = reparse_args(&orig); + assert!(reparsed.is_ok(), "Args failed to parse {:?}", reparsed); + assert!(orig == reparsed.unwrap(), "Args shouldn't have changed"); + } + + #[test] + fn basic_e_gets_unset() { + // We expect to REMOVE the -E flag + let orig = vec!["python", "-E", "-s", "-c", "exit(0)", "arg1"]; + let expected = vec!["python", "-s", "-c", "exit(0)", "arg1"]; + let reparsed = reparse_args(&orig); + assert!(reparsed.is_ok(), "Args failed to parse {:?}", reparsed); + assert!(expected == reparsed.unwrap(), "-E wasn't unset"); + } + + #[test] + fn basic_i_becomes_s() { + // We expect to CONVERT the -I flag to -E (which we ignore) and -s (which we keep) + let orig = vec!["python", "-I", "-c", "exit(0)", "arg1"]; + let expected = vec!["python", "-s", "-c", "exit(0)", "arg1"]; + let reparsed = reparse_args(&orig); + assert!(reparsed.is_ok(), "Args failed to parse {:?}", reparsed); + assert!(expected == reparsed.unwrap(), "Didn't translate -I to -s"); + } + + #[test] + fn basic_m_preserved() { + // We expect to ADD the -s flag + let orig = vec!["python", "-m", "build", "--unknown", "arg1"]; + let expected = vec!["python", "-m", "build", "--unknown", "arg1"]; + let reparsed = reparse_args(&orig); + assert!(reparsed.is_ok(), "Args failed to parse {:?}", reparsed); + assert!(expected == reparsed.unwrap(), "Didn't add -s"); + } + + #[test] + fn basic_trailing_args_preserved() { + let orig = vec![ + "python3", + "uv/private/sdist_build/build_helper.py", + "bazel-out/darwin_arm64-fastbuild/bin/external/+uv+sbuild__pypi__default__bravado_core/src", + "bazel-out/darwin_arm64-fastbuild/bin/external/+uv+sbuild__pypi__default__bravado_core/build" + ]; + let expected = vec![ + "python3", + "uv/private/sdist_build/build_helper.py", + "bazel-out/darwin_arm64-fastbuild/bin/external/+uv+sbuild__pypi__default__bravado_core/src", + "bazel-out/darwin_arm64-fastbuild/bin/external/+uv+sbuild__pypi__default__bravado_core/build" + ]; + let reparsed = reparse_args(&orig); + assert!(reparsed.is_ok(), "Args failed to parse {:?}", reparsed); + let reparsed = reparsed.unwrap(); + assert!( + expected == reparsed, + "Something happened to the args, got {:?}", + reparsed + ); + } + + #[test] + fn m_preserved_s_added_varargs_preserved() { + let orig = vec![ + "python3", + "-m", + "build", + "--no-isolation", + "--out-dir", + "bazel-out/darwin_arm64-fastbuild/bin/external/+uv+sbuild__pypi__default__bravado_core/build", + "bazel-out/darwin_arm64-fastbuild/bin/external/+uv+sbuild__pypi__default__bravado_core/src", + ]; + let expected = vec![ + "python3", + "-m", + "build", + "--no-isolation", + "--out-dir", + "bazel-out/darwin_arm64-fastbuild/bin/external/+uv+sbuild__pypi__default__bravado_core/build", + "bazel-out/darwin_arm64-fastbuild/bin/external/+uv+sbuild__pypi__default__bravado_core/src", + ]; + let reparsed = reparse_args(&orig); + assert!(reparsed.is_ok(), "Args failed to parse {:?}", reparsed); + let reparsed = reparsed.unwrap(); + assert!( + expected == reparsed, + "Something happened to the args, got {:?}", + reparsed + ); + } +} diff --git a/uv/private/sdist_build/build_helper.py b/uv/private/sdist_build/build_helper.py index d92c0cae..c73a6ae0 100644 --- a/uv/private/sdist_build/build_helper.py +++ b/uv/private/sdist_build/build_helper.py @@ -3,8 +3,8 @@ from argparse import ArgumentParser import shutil import sys -from os import getenv, listdir, path -from subprocess import call +from os import getenv +from build.__main__ import main as build # Under Bazel, the source dir of a sdist to build is immutable. `build` and # other tools however are constitutionally incapable of not writing to the @@ -15,6 +15,8 @@ # - It punts to `build` targeting the tempdir print(sys.executable, file=sys.stderr) +for e in sys.path: + print(" -", e, file=sys.stderr) PARSER = ArgumentParser() PARSER.add_argument("srcdir") @@ -27,14 +29,11 @@ shutil.copystat = lambda x, y, **k: None shutil.copytree(opts.srcdir, t, dirs_exist_ok=True) -outdir = path.abspath(opts.outdir) +# 1 -call([ - sys.executable, - "-m", "build", +build([ "--wheel", "--no-isolation", - "--outdir", outdir, -], cwd=t) - -print(listdir(outdir), file=sys.stderr) + "--outdir", opts.outdir, + t, +])