diff --git a/news/zipapp-absolute-source-symlinks.fixed.md b/news/zipapp-absolute-source-symlinks.fixed.md new file mode 100644 index 0000000000..2cc2b2fe0e --- /dev/null +++ b/news/zipapp-absolute-source-symlinks.fixed.md @@ -0,0 +1,4 @@ +(zipapp) Shared libraries that a {obj}`py_zipapp_binary` reaches through +Bazel's `_solib` symlinks are stored as files again, instead of as symlinks to +absolute paths in the build machine's output base that dangle wherever the +zipapp runs. diff --git a/tests/tools/zipapp/zipper_test.py b/tests/tools/zipapp/zipper_test.py index e0ff1557d5..4c66c7fc5f 100644 --- a/tests/tools/zipapp/zipper_test.py +++ b/tests/tools/zipapp/zipper_test.py @@ -174,6 +174,36 @@ def test_create_zip_with_sandboxed_source_symlink(tmp_path): assert_zip_file_content(zf, "runfiles/my_ws/bin/python3.14", content="python") +def test_create_zip_with_sandboxed_absolute_source_symlink(tmp_path): + # Bazel's _solib entries are absolute symlinks into the output base. Their + # target only exists on the build machine, so the file must be stored. + manifest_path = tmp_path / "manifest.txt" + output_zip = tmp_path / "output.zip" + + library_dir = tmp_path / "external" + library_dir.mkdir() + library = library_dir / "libfoo.so" + library.write_text("library") + + solib_dir = tmp_path / "solib" + solib_dir.mkdir() + solib_link = solib_dir / "libfoo.so" + solib_link.symlink_to(library) + + sandbox_dir = tmp_path / "sandbox" + sandbox_dir.mkdir() + sandbox_link = sandbox_dir / "libfoo.so" + sandbox_link.symlink_to(solib_link) + manifest_path.write_text(f"rf-file|0|_solib/libfoo.so|{sandbox_link}") + + create_zip(manifest_path, output_zip) + + with zipfile.ZipFile(output_zip, "r") as zf: + assert_zip_file_content( + zf, "runfiles/my_ws/_solib/libfoo.so", content="library" + ) + + def test_pathsep_normalization(tmp_path): manifest_path = tmp_path / "manifest.txt" output_zip = tmp_path / "output.zip" diff --git a/tools/zipapp/zipper.py b/tools/zipapp/zipper.py index c0e547284a..aa96160f11 100644 --- a/tools/zipapp/zipper.py +++ b/tools/zipapp/zipper.py @@ -130,8 +130,13 @@ def _source_symlink_target(content_path, is_symlink_str): # Bazel sandboxes expose regular inputs as absolute symlinks. Look through # that indirection to detect whether the original source is also a symlink. + # Only a relative link can be kept: an absolute one points into this + # machine's Bazel output base (e.g. the _solib links Bazel creates for shared + # libraries) and would dangle wherever the zipapp runs, so store the file. if os.path.islink(target): - return os.readlink(target) + source_target = os.readlink(target) + if not os.path.isabs(source_target): + return source_target return None