From 44223859f94056dd000af0e31f6ff796fc2db90a Mon Sep 17 00:00:00 2001 From: "Reid D. McKenzie" Date: Fri, 7 Nov 2025 12:44:01 -0700 Subject: [PATCH 1/7] hack(venv_shim): Unset -E and translate -I to -s Python has an `-E` flag, which causes it to ignore PYTHONHOME and PYTHONEXECUTABLE both of which are key to how we're making the shim work. Add a flag parser to the shim so we process the argv and strip out -E if it's set, and if -I is set we translate it to its equivalent -E and -s, and drop the -E. Add Rust tests covering some basic cases for the parser so we aren't integration testing that. Update Clap. Go back to unwind rather than abort as our error handling strategy as it's required for writing tests and clap. --- .bazelrc | 1 - Cargo.lock | 17 +- bazel/rust/defs.bzl | 1 - py/tools/venv_shim/BUILD.bazel | 8 + py/tools/venv_shim/Cargo.toml | 1 + py/tools/venv_shim/src/main.rs | 16 +- py/tools/venv_shim/src/pyargs.rs | 289 +++++++++++++++++++++++++++++++ 7 files changed, 317 insertions(+), 16 deletions(-) create mode 100644 py/tools/venv_shim/src/pyargs.rs diff --git a/.bazelrc b/.bazelrc index de624e78..372cec55 100644 --- a/.bazelrc +++ b/.bazelrc @@ -28,7 +28,6 @@ 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 # 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..5f4e93ef 100644 --- a/bazel/rust/defs.bzl +++ b/bazel/rust/defs.bzl @@ -17,7 +17,6 @@ rust_opt_binary, _rust_opt_binary_internal = with_cfg(_rust_binary).set( [ "-Cstrip=symbols", "-Ccodegen-units=1", - "-Cpanic=abort", ], # Avoid rules_rust trying to instrument this binary ).set("collect_code_coverage", "false").build() diff --git a/py/tools/venv_shim/BUILD.bazel b/py/tools/venv_shim/BUILD.bazel index 260200ec..ff501cd8 100644 --- a/py/tools/venv_shim/BUILD.bazel +++ b/py/tools/venv_shim/BUILD.bazel @@ -1,19 +1,27 @@ load("//bazel/release:release.bzl", "release") load("//bazel/rust:defs.bzl", "rust_binary") +load("@rules_rust//rust:defs.bzl", "rust_test") load("//bazel/rust:multi_platform_rust_binaries.bzl", "multi_platform_rust_binaries") rust_binary( name = "shim", srcs = [ + "src/pyargs.rs", "src/main.rs", ], deps = [ "//py/tools/runfiles", "@crates//:miette", "@crates//:which", + "@crates//:clap", ], ) +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..fe36b13e 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; @@ -137,7 +139,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): @@ -224,7 +226,7 @@ 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 { miette::bail!("could not discover an execution command-line"); @@ -342,7 +344,9 @@ fn main() -> miette::Result<()> { let mut cmd = Command::new(&actual_interpreter); let cmd = cmd // Pass along our args - .args(exec_args) + .args(pyargs::reparse_args( + &exec_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. .arg0(&venv_interpreter) diff --git a/py/tools/venv_shim/src/pyargs.rs b/py/tools/venv_shim/src/pyargs.rs new file mode 100644 index 00000000..b7694a63 --- /dev/null +++ b/py/tools/venv_shim/src/pyargs.rs @@ -0,0 +1,289 @@ +use clap::{arg, Arg, ArgAction, ArgMatches, Command, Parser}; +use miette::{miette, Context, 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("bytes_warning_level") + .short('b') + .action(ArgAction::Count), + ) + .arg( + Arg::new("dont_write_bytecode") + .short('B') + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("command") + .short('c') + .value_name("cmd") + .conflicts_with("module"), + ) + .arg( + Arg::new("debug_parser") + .short('d') + .action(ArgAction::SetTrue), + ) + .arg(Arg::new("ignore_env").short('E').action(ArgAction::SetTrue)) + .arg( + Arg::new("help") + .short('h') + .long("help") + .action(ArgAction::Help), + ) + .arg(Arg::new("inspect").short('i').action(ArgAction::SetTrue)) + .arg(Arg::new("isolate").short('I').action(ArgAction::SetTrue)) + .arg( + Arg::new("module") + .short('m') + .value_name("mod") + .conflicts_with("command"), + ) + .arg( + Arg::new("optimize_level") + .short('O') + .action(ArgAction::Count), + ) + .arg(Arg::new("quiet").short('q').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::new("unbuffered").short('u').action(ArgAction::SetTrue)) + .arg(Arg::new("verbosity").short('v').action(ArgAction::Count)) + .arg( + Arg::new("version") + .short('V') + .long("version") + .action(ArgAction::Count), + ) + .arg( + Arg::new("warnings") + .short('W') + .value_name("arg") + .action(ArgAction::Append), + ) + .arg( + Arg::new("skip_first_line") + .short('x') + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("extended_options") + .short('X') + .value_name("opt") + .action(ArgAction::Append), + ) + .arg( + arg!( ...) + .trailing_var_arg(true) + .required(false) + .allow_hyphen_values(true), + ) +} + +pub struct ArgState { + pub bytes_warning_level: u8, + pub dont_write_bytecode: bool, + pub command: Option, + pub debug_parser: bool, + pub ignore_env: bool, + pub help: bool, + pub inspect: bool, + pub isolate: bool, + pub module: Option, + pub optimize_level: u8, + pub quiet: bool, + pub no_user_site: bool, + pub no_import_site: bool, + pub unbuffered: bool, + pub verbosity: u8, + pub version: u8, + pub warnings: Vec, + pub skip_first_line: bool, + pub extended_options: Vec, + pub remaining_args: Vec, +} + +fn extract_state(matches: &ArgMatches) -> ArgState { + ArgState { + bytes_warning_level: *matches.get_one::("bytes_warning_level").unwrap_or(&0), + dont_write_bytecode: *matches + .get_one::("dont_write_bytecode") + .unwrap_or(&false), + command: matches.get_one::("command").cloned(), + debug_parser: *matches.get_one::("debug_parser").unwrap_or(&false), + // 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), + + help: *matches.get_one::("help").unwrap_or(&false), + inspect: *matches.get_one::("inspect").unwrap_or(&false), + module: matches.get_one::("module").cloned(), + optimize_level: *matches.get_one::("optimize_level").unwrap_or(&0), + quiet: *matches.get_one::("quiet").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), + unbuffered: *matches.get_one::("unbuffered").unwrap_or(&false), + verbosity: *matches.get_one::("verbosity").unwrap_or(&0), + version: *matches.get_one::("version").unwrap_or(&0), + skip_first_line: *matches.get_one::("skip_first_line").unwrap_or(&false), + + // For multiple values, clone the Vec + warnings: matches + .get_many::("warnings") + .unwrap_or_default() + .cloned() + .collect(), + extended_options: matches + .get_many::("extended_options") + .unwrap_or_default() + .cloned() + .collect(), + + 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)); + } + }; + + let add_s = parsed_args.isolate || parsed_args.no_user_site; + + argv.push(String::from("python")); + + if parsed_args.bytes_warning_level == 1 { + argv.push(String::from("-b")); + } else if parsed_args.bytes_warning_level >= 2 { + argv.push(String::from("-bb")); + } + + push_flag(&mut argv, 'B', parsed_args.dont_write_bytecode); + push_flag(&mut argv, 'd', parsed_args.debug_parser); + push_flag(&mut argv, 'h', parsed_args.help); + push_flag(&mut argv, 'i', parsed_args.inspect); + + // -I replacement logic: -I is never pushed, its effects (-E and -s) are handled separately. + // -E removal: -E is never pushd + // -s inclusion logic: push -s if it was set directly OR implied by -I + push_flag(&mut argv, 's', add_s); + + push_flag(&mut argv, 'S', parsed_args.no_import_site); + push_flag(&mut argv, 'u', parsed_args.unbuffered); + push_flag(&mut argv, 'q', parsed_args.quiet); + push_flag(&mut argv, 'x', parsed_args.skip_first_line); + + if let Some(cmd) = &parsed_args.command { + argv.push(String::from("-c")); + argv.push(cmd.clone()); + } + if let Some(module) = &parsed_args.module { + argv.push(String::from("-m")); + argv.push(module.clone()); + } + if parsed_args.optimize_level == 1 { + argv.push(String::from("-O")); + } else if parsed_args.optimize_level >= 2 { + argv.push(String::from("-OO")); + } + + for _ in 0..parsed_args.verbosity { + argv.push(String::from("-v")); + } + if parsed_args.version == 1 { + argv.push(String::from("-V")); + } else if parsed_args.version >= 2 { + argv.push(String::from("-VV")); + } + + for warning in &parsed_args.warnings { + argv.push(String::from("-W")); + argv.push(warning.clone()); + } + for opt in &parsed_args.extended_options { + argv.push(String::from("-X")); + argv.push(opt.clone()); + } + + 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", "script.py", "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_args_preserved2() { + let orig = vec!["python", "-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_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", "-c", "exit(0)", "arg1"]; + let expected = vec!["python", "-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"); + } +} From 4c548bd55c23709edafb87741d4ea74dfcd816a6 Mon Sep 17 00:00:00 2001 From: "Reid D. McKenzie" Date: Fri, 7 Nov 2025 12:51:45 -0700 Subject: [PATCH 2/7] [NO TESTS] WIP --- py/tools/venv_shim/src/pyargs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/tools/venv_shim/src/pyargs.rs b/py/tools/venv_shim/src/pyargs.rs index b7694a63..a953464a 100644 --- a/py/tools/venv_shim/src/pyargs.rs +++ b/py/tools/venv_shim/src/pyargs.rs @@ -1,5 +1,5 @@ -use clap::{arg, Arg, ArgAction, ArgMatches, Command, Parser}; -use miette::{miette, Context, IntoDiagnostic, Result}; +use clap::{arg, Arg, ArgAction, ArgMatches, Command}; +use miette::{IntoDiagnostic, Result}; fn build_parser() -> Command { Command::new("python_like_parser") From 477a059e314c8c0ba88d1a1303d4783301c7807f Mon Sep 17 00:00:00 2001 From: Reid D McKenzie Date: Fri, 7 Nov 2025 12:53:10 -0700 Subject: [PATCH 3/7] Clippy --- py/tools/venv_shim/src/main.rs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/py/tools/venv_shim/src/main.rs b/py/tools/venv_shim/src/main.rs index fe36b13e..ccae0526 100644 --- a/py/tools/venv_shim/src/main.rs +++ b/py/tools/venv_shim/src/main.rs @@ -78,7 +78,7 @@ fn parse_venv_cfg(venv_root: &Path, cfg_path: &Path) -> Result { version_info: version, interpreter: InterpreterConfig::Runfiles { rpath: rloc, - repo: repo, + repo, }, user_site: user_site.expect("User site flag not set!"), }), @@ -166,7 +166,7 @@ fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> Result< // 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}'." @@ -183,7 +183,7 @@ fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> Result< } } - 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!"); }; @@ -191,8 +191,8 @@ fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> Result< } 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 {:?}", @@ -207,12 +207,12 @@ fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> Result< 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); @@ -261,7 +261,7 @@ fn main() -> 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); @@ -278,7 +278,7 @@ fn main() -> 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") }) { @@ -296,7 +296,7 @@ fn main() -> 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); @@ -353,7 +353,7 @@ fn main() -> Result<()> { // 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 @@ -361,10 +361,8 @@ fn main() -> 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()); From be434ddb1cc0527451060c15b63c18dde6a17114 Mon Sep 17 00:00:00 2001 From: "Reid D. McKenzie" Date: Sat, 8 Nov 2025 10:30:11 -0700 Subject: [PATCH 4/7] Greener tests --- .bazelrc | 7 +- bazel/rust/defs.bzl | 14 +-- e2e/cases/uv-deps-650/crossbuild/BUILD.bazel | 6 - py/tests/py_venv_image_layer/BUILD.bazel | 6 - py/tools/venv_shim/src/main.rs | 48 +++---- py/tools/venv_shim/src/pyargs.rs | 126 +++++++++++++++++-- uv/private/sdist_build/build_helper.py | 13 +- 7 files changed, 146 insertions(+), 74 deletions(-) diff --git a/.bazelrc b/.bazelrc index 372cec55..bcfe66ff 100644 --- a/.bazelrc +++ b/.bazelrc @@ -27,7 +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 + +# 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/bazel/rust/defs.bzl b/bazel/rust/defs.bzl index 5f4e93ef..9636ced8 100644 --- a/bazel/rust/defs.bzl +++ b/bazel/rust/defs.bzl @@ -9,18 +9,6 @@ _default_platform = select({ "//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", - ], - # 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. @@ -49,7 +37,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/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/tools/venv_shim/src/main.rs b/py/tools/venv_shim/src/main.rs index ccae0526..2382df17 100644 --- a/py/tools/venv_shim/src/main.rs +++ b/py/tools/venv_shim/src/main.rs @@ -76,10 +76,7 @@ fn parse_venv_cfg(venv_root: &Path, cfg_path: &Path) -> Result { root: venv_root.to_path_buf(), cfg: cfg_path.to_path_buf(), version_info: version, - interpreter: InterpreterConfig::Runfiles { - rpath: rloc, - repo, - }, + interpreter: InterpreterConfig::Runfiles { rpath: rloc, repo }, user_site: user_site.expect("User site flag not set!"), }), (Some(version), None, None) => Ok(PyCfg { @@ -341,15 +338,17 @@ fn main() -> 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); + eprintln!("Reexec args {:?}", reexec_args); + let mut cmd = Command::new(&actual_interpreter); let cmd = cmd - // Pass along our args - .args(pyargs::reparse_args( - &exec_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. .arg0(&venv_interpreter) + // Pass along our args + .args(reexec_args) // Pseudo-`activate` .env("VIRTUAL_ENV", &venv_root); @@ -361,8 +360,7 @@ fn main() -> 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().any(|p| OsStr::new(p) == venv_bin); + 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()); @@ -379,12 +377,6 @@ fn main() -> 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 @@ -402,26 +394,16 @@ fn main() -> 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")); - // } - // } - // } - // } + // 1 + + 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 index a953464a..9774ff68 100644 --- a/py/tools/venv_shim/src/pyargs.rs +++ b/py/tools/venv_shim/src/pyargs.rs @@ -176,9 +176,8 @@ pub fn reparse_args(original_argv: &Vec<&str>) -> Result> { } }; - let add_s = parsed_args.isolate || parsed_args.no_user_site; - - argv.push(String::from("python")); + // Retain the original argv binary + argv.push(original_argv[0].to_string()); if parsed_args.bytes_warning_level == 1 { argv.push(String::from("-b")); @@ -193,8 +192,8 @@ pub fn reparse_args(original_argv: &Vec<&str>) -> Result> { // -I replacement logic: -I is never pushed, its effects (-E and -s) are handled separately. // -E removal: -E is never pushd - // -s inclusion logic: push -s if it was set directly OR implied by -I - push_flag(&mut argv, 's', add_s); + // -s inclusion logic: we ALWAYS push -s + push_flag(&mut argv, 's', true); push_flag(&mut argv, 'S', parsed_args.no_import_site); push_flag(&mut argv, 'u', parsed_args.unbuffered); @@ -244,18 +243,47 @@ mod test { #[test] fn basic_args_preserved1() { - let orig = vec!["python", "-B", "script.py", "arg1"]; + let orig = vec!["python", "-B", "-s", "script.py", "arg1"]; let reparsed = reparse_args(&orig); assert!(reparsed.is_ok(), "Args failed to parse {:?}", reparsed); - assert!(orig == reparsed.unwrap(), "Args shouldn't have changed"); + let reparsed = reparsed.unwrap(); + assert!( + orig == reparsed, + "Args shouldn't have changed, got {:?}", + reparsed + ); } #[test] fn basic_args_preserved2() { - let orig = vec!["python", "-c", "exit(0)", "arg1"]; + 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); - assert!(orig == reparsed.unwrap(), "Args shouldn't have changed"); + let reparsed = reparsed.unwrap(); + assert!( + orig == reparsed, + "Args shouldn't have changed, got {:?}", + reparsed + ); } #[test] @@ -270,8 +298,8 @@ mod test { #[test] fn basic_e_gets_unset() { // We expect to REMOVE the -E flag - let orig = vec!["python", "-E", "-c", "exit(0)", "arg1"]; - let expected = vec!["python", "-c", "exit(0)", "arg1"]; + 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"); @@ -286,4 +314,80 @@ mod test { assert!(reparsed.is_ok(), "Args failed to parse {:?}", reparsed); assert!(expected == reparsed.unwrap(), "Didn't translate -I to -s"); } + + #[test] + fn basic_add_s() { + // We expect to ADD the -s flag + let orig = vec!["python", "-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 add -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", "-s", "-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", + "-s", + "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", + "-s", + "-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..3a6dbf19 100644 --- a/uv/private/sdist_build/build_helper.py +++ b/uv/private/sdist_build/build_helper.py @@ -4,7 +4,7 @@ import shutil import sys from os import getenv, listdir, path -from subprocess import call +from subprocess import check_call, CalledProcessError # 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 @@ -28,13 +28,18 @@ shutil.copytree(opts.srcdir, t, dirs_exist_ok=True) outdir = path.abspath(opts.outdir) - -call([ +args = [ sys.executable, "-m", "build", "--wheel", "--no-isolation", "--outdir", outdir, -], cwd=t) +] +print(args, file=sys.stderr) + +try: + check_call(args, cwd=t) +except CalledProcessError: + exit(1) print(listdir(outdir), file=sys.stderr) From 605af2dcf3944a9741f62a54312652ce07645493 Mon Sep 17 00:00:00 2001 From: "Reid D. McKenzie" Date: Sat, 8 Nov 2025 14:23:50 -0700 Subject: [PATCH 5/7] [NO TESTS] WIP --- py/private/py_venv/entrypoint.tmpl.sh | 2 +- .../py-venv-standalone-interpreter/test.sh | 2 +- py/tools/venv_shim/src/main.rs | 9 +- py/tools/venv_shim/src/pyargs.rs | 176 +----------------- uv/private/sdist_build/build_helper.py | 21 +-- 5 files changed, 21 insertions(+), 189 deletions(-) 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/tools/venv_shim/src/main.rs b/py/tools/venv_shim/src/main.rs index 2382df17..0b91a3e0 100644 --- a/py/tools/venv_shim/src/main.rs +++ b/py/tools/venv_shim/src/main.rs @@ -225,7 +225,7 @@ fn find_actual_interpreter(executable: impl AsRef, cfg: &PyCfg) -> 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"); }; @@ -324,7 +324,7 @@ fn main() -> 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); @@ -342,6 +342,8 @@ fn main() -> Result<()> { // 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); @@ -401,8 +403,7 @@ fn main() -> Result<()> { home.to_str().unwrap().hash(&mut hasher); cmd.env("ASPECT_PY_VALIDITY", format!("{}", hasher.finish())); - // 1 - + #[cfg(feature = "debug")] eprintln!("Punting to {:?}", cmd); // And punt diff --git a/py/tools/venv_shim/src/pyargs.rs b/py/tools/venv_shim/src/pyargs.rs index 9774ff68..e9313824 100644 --- a/py/tools/venv_shim/src/pyargs.rs +++ b/py/tools/venv_shim/src/pyargs.rs @@ -7,48 +7,8 @@ fn build_parser() -> Command { .disable_help_flag(true) .dont_delimit_trailing_values(true) .allow_hyphen_values(true) - .arg( - Arg::new("bytes_warning_level") - .short('b') - .action(ArgAction::Count), - ) - .arg( - Arg::new("dont_write_bytecode") - .short('B') - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("command") - .short('c') - .value_name("cmd") - .conflicts_with("module"), - ) - .arg( - Arg::new("debug_parser") - .short('d') - .action(ArgAction::SetTrue), - ) .arg(Arg::new("ignore_env").short('E').action(ArgAction::SetTrue)) - .arg( - Arg::new("help") - .short('h') - .long("help") - .action(ArgAction::Help), - ) - .arg(Arg::new("inspect").short('i').action(ArgAction::SetTrue)) .arg(Arg::new("isolate").short('I').action(ArgAction::SetTrue)) - .arg( - Arg::new("module") - .short('m') - .value_name("mod") - .conflicts_with("command"), - ) - .arg( - Arg::new("optimize_level") - .short('O') - .action(ArgAction::Count), - ) - .arg(Arg::new("quiet").short('q').action(ArgAction::SetTrue)) .arg( Arg::new("no_user_site") .short('s') @@ -59,31 +19,6 @@ fn build_parser() -> Command { .short('S') .action(ArgAction::SetTrue), ) - .arg(Arg::new("unbuffered").short('u').action(ArgAction::SetTrue)) - .arg(Arg::new("verbosity").short('v').action(ArgAction::Count)) - .arg( - Arg::new("version") - .short('V') - .long("version") - .action(ArgAction::Count), - ) - .arg( - Arg::new("warnings") - .short('W') - .value_name("arg") - .action(ArgAction::Append), - ) - .arg( - Arg::new("skip_first_line") - .short('x') - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("extended_options") - .short('X') - .value_name("opt") - .action(ArgAction::Append), - ) .arg( arg!( ...) .trailing_var_arg(true) @@ -93,66 +28,23 @@ fn build_parser() -> Command { } pub struct ArgState { - pub bytes_warning_level: u8, - pub dont_write_bytecode: bool, - pub command: Option, - pub debug_parser: bool, pub ignore_env: bool, - pub help: bool, - pub inspect: bool, pub isolate: bool, - pub module: Option, - pub optimize_level: u8, - pub quiet: bool, - pub no_user_site: bool, pub no_import_site: bool, - pub unbuffered: bool, - pub verbosity: u8, - pub version: u8, - pub warnings: Vec, - pub skip_first_line: bool, - pub extended_options: Vec, + pub no_user_site: bool, pub remaining_args: Vec, } fn extract_state(matches: &ArgMatches) -> ArgState { ArgState { - bytes_warning_level: *matches.get_one::("bytes_warning_level").unwrap_or(&0), - dont_write_bytecode: *matches - .get_one::("dont_write_bytecode") - .unwrap_or(&false), - command: matches.get_one::("command").cloned(), - debug_parser: *matches.get_one::("debug_parser").unwrap_or(&false), // 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), - help: *matches.get_one::("help").unwrap_or(&false), - inspect: *matches.get_one::("inspect").unwrap_or(&false), - module: matches.get_one::("module").cloned(), - optimize_level: *matches.get_one::("optimize_level").unwrap_or(&0), - quiet: *matches.get_one::("quiet").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), - unbuffered: *matches.get_one::("unbuffered").unwrap_or(&false), - verbosity: *matches.get_one::("verbosity").unwrap_or(&0), - version: *matches.get_one::("version").unwrap_or(&0), - skip_first_line: *matches.get_one::("skip_first_line").unwrap_or(&false), - - // For multiple values, clone the Vec - warnings: matches - .get_many::("warnings") - .unwrap_or_default() - .cloned() - .collect(), - extended_options: matches - .get_many::("extended_options") - .unwrap_or_default() - .cloned() - .collect(), remaining_args: matches .get_many::("args") @@ -179,58 +71,16 @@ pub fn reparse_args(original_argv: &Vec<&str>) -> Result> { // Retain the original argv binary argv.push(original_argv[0].to_string()); - if parsed_args.bytes_warning_level == 1 { - argv.push(String::from("-b")); - } else if parsed_args.bytes_warning_level >= 2 { - argv.push(String::from("-bb")); - } - - push_flag(&mut argv, 'B', parsed_args.dont_write_bytecode); - push_flag(&mut argv, 'd', parsed_args.debug_parser); - push_flag(&mut argv, 'h', parsed_args.help); - push_flag(&mut argv, 'i', parsed_args.inspect); - // -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', true); + push_flag( + &mut argv, + 's', + parsed_args.no_user_site | parsed_args.isolate, + ); push_flag(&mut argv, 'S', parsed_args.no_import_site); - push_flag(&mut argv, 'u', parsed_args.unbuffered); - push_flag(&mut argv, 'q', parsed_args.quiet); - push_flag(&mut argv, 'x', parsed_args.skip_first_line); - - if let Some(cmd) = &parsed_args.command { - argv.push(String::from("-c")); - argv.push(cmd.clone()); - } - if let Some(module) = &parsed_args.module { - argv.push(String::from("-m")); - argv.push(module.clone()); - } - if parsed_args.optimize_level == 1 { - argv.push(String::from("-O")); - } else if parsed_args.optimize_level >= 2 { - argv.push(String::from("-OO")); - } - - for _ in 0..parsed_args.verbosity { - argv.push(String::from("-v")); - } - if parsed_args.version == 1 { - argv.push(String::from("-V")); - } else if parsed_args.version >= 2 { - argv.push(String::from("-VV")); - } - - for warning in &parsed_args.warnings { - argv.push(String::from("-W")); - argv.push(warning.clone()); - } - for opt in &parsed_args.extended_options { - argv.push(String::from("-X")); - argv.push(opt.clone()); - } argv.extend(parsed_args.remaining_args.iter().cloned()); @@ -315,21 +165,11 @@ mod test { assert!(expected == reparsed.unwrap(), "Didn't translate -I to -s"); } - #[test] - fn basic_add_s() { - // We expect to ADD the -s flag - let orig = vec!["python", "-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 add -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", "-s", "-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"); @@ -345,7 +185,6 @@ mod test { ]; let expected = vec![ "python3", - "-s", "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" @@ -373,7 +212,6 @@ mod test { ]; let expected = vec![ "python3", - "-s", "-m", "build", "--no-isolation", diff --git a/uv/private/sdist_build/build_helper.py b/uv/private/sdist_build/build_helper.py index 3a6dbf19..22331941 100644 --- a/uv/private/sdist_build/build_helper.py +++ b/uv/private/sdist_build/build_helper.py @@ -3,7 +3,7 @@ from argparse import ArgumentParser import shutil import sys -from os import getenv, listdir, path +from os import getenv, listdir, path, execv from subprocess import check_call, CalledProcessError # Under Bazel, the source dir of a sdist to build is immutable. `build` and @@ -27,19 +27,12 @@ shutil.copystat = lambda x, y, **k: None shutil.copytree(opts.srcdir, t, dirs_exist_ok=True) -outdir = path.abspath(opts.outdir) -args = [ - sys.executable, +for e in sys.path: + print(" -", e, file=sys.stderr) + +execv(sys.executable, [ "-m", "build", "--wheel", "--no-isolation", - "--outdir", outdir, -] -print(args, file=sys.stderr) - -try: - check_call(args, cwd=t) -except CalledProcessError: - exit(1) - -print(listdir(outdir), file=sys.stderr) + "--outdir", opts.outdir, +]) From 7f72e6f79c41d2ac6e7160f417db78457e7d2136 Mon Sep 17 00:00:00 2001 From: "Reid D. McKenzie" Date: Sat, 8 Nov 2025 15:07:40 -0700 Subject: [PATCH 6/7] [NO TESTS] WIP --- .../my_app_amd64_layers_listing.yaml | 10 +++++----- .../my_app_arm64_layers_listing.yaml | 10 +++++----- py/tools/venv_shim/src/main.rs | 4 +++- uv/private/sdist_build/build_helper.py | 15 ++++++++------- 4 files changed, 21 insertions(+), 18 deletions(-) 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..8775aa5c 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 4463808 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 4463808 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 4463808 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/src/main.rs b/py/tools/venv_shim/src/main.rs index 0b91a3e0..8f060847 100644 --- a/py/tools/venv_shim/src/main.rs +++ b/py/tools/venv_shim/src/main.rs @@ -263,7 +263,9 @@ fn main() -> Result<()> { #[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); } } diff --git a/uv/private/sdist_build/build_helper.py b/uv/private/sdist_build/build_helper.py index 22331941..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, execv -from subprocess import check_call, CalledProcessError +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,12 +29,11 @@ shutil.copystat = lambda x, y, **k: None shutil.copytree(opts.srcdir, t, dirs_exist_ok=True) -for e in sys.path: - print(" -", e, file=sys.stderr) - -execv(sys.executable, [ - "-m", "build", +# 1 + +build([ "--wheel", "--no-isolation", "--outdir", opts.outdir, + t, ]) From 2bd921c31b9ffd1c8d5b201d782bb7e5f038eb12 Mon Sep 17 00:00:00 2001 From: "aspect-marvin[bot]" Date: Sat, 8 Nov 2025 23:19:58 +0000 Subject: [PATCH 7/7] Pre-commit changes --- bazel/rust/defs.bzl | 1 - .../py_venv_image_layer/my_app_amd64_layers_listing.yaml | 6 +++--- py/tools/venv_shim/BUILD.bazel | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bazel/rust/defs.bzl b/bazel/rust/defs.bzl index 9636ced8..55de39ce 100644 --- a/bazel/rust/defs.bzl +++ b/bazel/rust/defs.bzl @@ -2,7 +2,6 @@ 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 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 8775aa5c..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 @@ -2521,9 +2521,9 @@ 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 4463808 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 4463808 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 4463808 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 diff --git a/py/tools/venv_shim/BUILD.bazel b/py/tools/venv_shim/BUILD.bazel index ff501cd8..91368894 100644 --- a/py/tools/venv_shim/BUILD.bazel +++ b/py/tools/venv_shim/BUILD.bazel @@ -1,19 +1,19 @@ +load("@rules_rust//rust:defs.bzl", "rust_test") load("//bazel/release:release.bzl", "release") load("//bazel/rust:defs.bzl", "rust_binary") -load("@rules_rust//rust:defs.bzl", "rust_test") load("//bazel/rust:multi_platform_rust_binaries.bzl", "multi_platform_rust_binaries") rust_binary( name = "shim", srcs = [ - "src/pyargs.rs", "src/main.rs", + "src/pyargs.rs", ], deps = [ "//py/tools/runfiles", + "@crates//:clap", "@crates//:miette", "@crates//:which", - "@crates//:clap", ], )