From 9ddd6176bfcb8aaa18de0d649c95b4243fcd4fea Mon Sep 17 00:00:00 2001 From: Peter Lobsinger Date: Sat, 28 Jun 2025 12:11:21 -0400 Subject: [PATCH 1/2] Work in terms of bytes when patching shebang lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As [Uv's implementation](https://github.com/astral-sh/uv/blob/db14cc3005d2cd53802cb04c2f1e177a22c934ac/crates/uv-install-wheel/src/wheel.rs#L425) notes: > scripts might be binaries, so we read an exact number of bytes instead of the first line as string Indeed, one wheel that contains a binary "script" is [`uv` itself](https://pypi.org/project/uv/). Constructing a venv that happens to include `uv` was previously failing with: ``` ERROR: /Users/peter/tecton/sdk/pypi/BUILD.bazel:97:8: Action sdk/pypi/.venv failed: (Exit 1): sandbox-exec failed: error executing Action command (cd /private/var/tmp/_bazel_peter/dfecb8ec3f6f433d8509be7ebe017232/sandbox/darwin-sandbox/589/execroot/_main && \ exec env - \ TMPDIR=/var/folders/9_/p2d_shr10b91_464_3jfl5t80000gn/T/ \ /usr/bin/sandbox-exec -f /private/var/tmp/_bazel_peter/dfecb8ec3f6f433d8509be7ebe017232/sandbox/darwin-sandbox/589/sandbox.sb /var/tmp/_bazel_peter/install/96e26d97222159f904e14600d7490eb0/process-wrapper '--timeout=0' '--kill_delay=15' '--stats=/private/var/tmp/_bazel_peter/dfecb8ec3f6f433d8509be7ebe017232/sandbox/darwin-sandbox/589/stats.out' bazel-out/darwin_arm64-opt-exec-ST-2adb5a2e0ae2/bin/external/aspect_rules_py~/py/tools/venv_bin/venv_macos_aarch64_build '--location=bazel-out/darwin_arm64-fastbuild/bin/sdk/pypi/.venv' '--venv-shim=bazel-out/darwin_arm64-fastbuild-ST-2adb5a2e0ae2/bin/external/aspect_rules_py~/py/tools/venv_shim/shim_macos_aarch64_build' '--python=python_3.8_macos_aarch64_runtime/python/install/bin/python3.8' '--pth-file=bazel-out/darwin_arm64-fastbuild/bin/sdk/pypi/venv.pth' '--env-file=bazel-out/darwin_arm64-fastbuild/bin/sdk/pypi/venv.env' '--bin-dir=bazel-out/darwin_arm64-fastbuild/bin' '--collision-strategy=error' '--venv-name=.venv' '--mode=static-copy' '--version=3.8') Error: × Unable to run command: ╰─▶ stream did not contain valid UTF-8 ``` --- py/tools/py/src/venv.rs | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/py/tools/py/src/venv.rs b/py/tools/py/src/venv.rs index 4368e9e7..88864b42 100644 --- a/py/tools/py/src/venv.rs +++ b/py/tools/py/src/venv.rs @@ -8,7 +8,7 @@ use sha256::try_digest; use std::{ env::current_dir, fs::{self, File}, - io::{BufRead, BufReader, BufWriter, Write}, + io::{self, BufRead, BufReader, BufWriter, Read, Write, Seek}, os::unix::fs::{MetadataExt, PermissionsExt}, path::{Path, PathBuf}, }; @@ -172,7 +172,34 @@ fn copy(original: &PathBuf, link: &PathBuf) -> miette::Result<()> { )); } -const RELOCATABLE_SHEBANG: &str = "\ +fn copy_and_patch_shebang(original: &PathBuf, link: &PathBuf) -> miette::Result<()> { + let mut src = File::open(original.as_path()).into_diagnostic()?; + + let mut buf = [0u8; PLACEHOLDER_SHEBANG.len()]; + let found_shebang = match src.read_exact(&mut buf) { + Ok(()) => buf == PLACEHOLDER_SHEBANG, + Err(error) => match error.kind() { + io::ErrorKind::UnexpectedEof => false, // File too short to contain shebang. + _ => Err(error).into_diagnostic()?, + }, + }; + + if found_shebang { + let mut dst = File::create(link.as_path()).into_diagnostic()?; + dst.write_all(RELOCATABLE_SHEBANG).into_diagnostic()?; + src.rewind().into_diagnostic()?; + io::copy(&mut src, &mut dst).into_diagnostic()?; + } + else { + copy(original, link)?; + } + + Ok(()) +} + +const PLACEHOLDER_SHEBANG: &[u8] = b"#!/dev/null"; + +const RELOCATABLE_SHEBANG: &[u8] = b"\ #!/bin/sh '''exec' \"$(dirname -- \"$(realpath -- \"$0\")\")\"/'python3' \"$0\" \"$@\" ' ''' @@ -619,11 +646,7 @@ pub fn create_tree( // In the case of copying bin entries, we need to patch them. Yay. if link_dir.file_name() == Some(OsStr::new("bin")) { - let mut content = fs::read_to_string(original_entry).into_diagnostic()?; - if content.starts_with("#!/dev/null") { - content.replace_range(..0, &RELOCATABLE_SHEBANG); - } - fs::write(&link_entry, content).into_diagnostic()?; + copy_and_patch_shebang(&original_entry, &link_entry)?; } // Normal case of needing to link a file :smile: else { From 4ecf725c8dd06530f5d16dfc0adcc680b3883fa1 Mon Sep 17 00:00:00 2001 From: Peter Lobsinger Date: Tue, 1 Jul 2025 09:38:04 -0400 Subject: [PATCH 2/2] Document why placeholder matcher is so odd --- py/tools/py/src/venv.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py/tools/py/src/venv.rs b/py/tools/py/src/venv.rs index 88864b42..9443f2ce 100644 --- a/py/tools/py/src/venv.rs +++ b/py/tools/py/src/venv.rs @@ -197,6 +197,8 @@ fn copy_and_patch_shebang(original: &PathBuf, link: &PathBuf) -> miette::Result< Ok(()) } +// Matches entrypoints that have had their interpreter "fixed" by rules_python. +// https://github.com/bazel-contrib/rules_python/blob/cd6948a0f706e75fa0f3ebd35e485aeec3e299fc/python/private/pypi/whl_installer/wheel.py#L319C13-L319C24 const PLACEHOLDER_SHEBANG: &[u8] = b"#!/dev/null"; const RELOCATABLE_SHEBANG: &[u8] = b"\