Skip to content

feat(toolchain): package standard library into zip file for hermetic runtimes - #4146

Draft
rickeylev wants to merge 17 commits into
bazel-contrib:mainfrom
rickeylev:zip_stdlib_runtime_rule
Draft

feat(toolchain): package standard library into zip file for hermetic runtimes#4146
rickeylev wants to merge 17 commits into
bazel-contrib:mainfrom
rickeylev:zip_stdlib_runtime_rule

Conversation

@rickeylev

Copy link
Copy Markdown
Collaborator

Hermetic Python runtimes include thousands of individual standard
library files. Staging each file in runfiles trees consumes inodes,
increases manifest overhead, and degrades test invocation latency.

Python natively supports importing modules from a zip archive via
zipimport. Packaging pure Python standard library modules into a single
compressed archive substantially shrinks runfiles trees and accelerates
runfiles creation.

Update hermetic toolchain runtimes to package standard library modules
into a zip archive and omit loose files from runfiles trees. Add a
//python/config_settings:zip_stdlib string flag ('yes'/'no', defaulting
to 'yes') so users can opt out and retain on-disk files when necessary.

Work towards #1653.

…runtimes

Package the Python standard library into a platform-appropriate compressed
zip archive and wire it into hermetic toolchain py_runtime definitions so
standard library modules can be imported directly from the zip.

Add a private zip_stdlib rule wrapping @bazel_tools//tools/zip:zipper
to construct the standard library archive with prefix stripping and a
mandatory output location. Update hermetic_runtime_repo_setup with a
_define_zip_stdlib helper to create zip_stdlib targets targeting
platform-specific standard library zip paths (lib/pythonXY[t].zip on POSIX,
pythonXY[t].zip on Windows) and include the resulting zip target in
py3_runtime's files attribute.
Add a pytest_test target in tests/zip_stdlib to assert pure Python
standard library modules are loaded via zipimport from the hermetic
runtime zip.
…in test

Exclude on-disk standard library files from the hermetic runtime files
filegroup so only the zipped archive is provided at runtime. Add test
assertions in zip_stdlib_test to verify unzipped standard library files
are not present on disk in runfiles.
…ry zipping

Allow users to configure whether the standard library is packaged into a
zip file using the zip_stdlib string flag (values: 'yes', 'no', default: 'yes').

- Define ZipStdlibFlag in python/private/flags.bzl and declare string_flag
  zip_stdlib in python/config_settings/BUILD.bazel.
- Define _is_zip_stdlib_yes and _is_zip_stdlib_no config settings in
  construct_config_settings using labels.ZIP_STDLIB.
- Use _IS_ZIP_STDLIB_YES and _IS_ZIP_STDLIB_NO in hermetic runtime repo
  setup to conditionally package and include the stdlib zip or on-disk files.
- Add analysis tests covering flag disabled behavior.
Document the zip_stdlib flag in the configuration settings API documentation so users understand the option to package or retain standard library files on disk.
…e wrapping

Add manual tags to intermediate filegroups and shorten test
implementation function name to comply with line length limits.
… tag handling

Wildcard builds failed because helper targets in hermetic_runtime_repo_setup
were missing the manual tag, and zip_stdlib referenced ctx.attr.strip_prefix
instead of the local variable.

Mark helper targets as manual, fix strip_prefix reference, and move
list_add_unique to util.bzl to merge tags cleanly.
…reters

When standard library zipping is enabled, CPython requires landmark files
(like os.py) to resolve its prefix, Windows venvs need pythonXY.zip in
Scripts/, and exec actions running bare interpreters need the zip in
PYTHONPATH.

Expose zip_stdlib in PyRuntimeInfo and py_runtime, retain landmark files
in files_base, include the zip in Windows venv_bin_files, and add it to
action_env in actions_run.
…ackages

CPython on Windows requires DLLs/ in sys.path and registered with
os.add_dll_directory to load C-extensions such as _socket and _ctypes.
Symlink DLLs/ when creating Windows venvs and add it in stage 2 bootstrap.

Additionally, python-build-standalone bundles _distutils_hack in
site-packages which caused ModuleNotFoundError when the bare interpreter
invoked distutils-precedence.pth. Exclude bundled site-packages from
files_base while preserving site-packages landmark files.
On Windows, UNC prefixes injected into target_root corrupt sys.path,
and sys.path.insert for DLLs alters sys.path ordering; use abspath and
os.add_dll_directory instead. Symlink python*.zip into Windows venvs.

Actions running bare interpreters like py_console_script_gen need
access to the stdlib zip, so execute via py_interpreter_program and
actions_run.

Finally, keep encodings/*.py in landmark files on disk so
FrozenImporter and early site-packages initialization can resolve
utf-8-sig on Python 3.15 before _bazel_site_init runs.
On Windows, runtime virtual environments execute python from the venv
Scripts directory, where CPython's getpath expects the stdlib zip to
reside. When missing, early startup fails to locate the stdlib zip.
Additionally, native C extensions located in DLLs/ were omitted from
sys.path, and premature sys.base_prefix entries violated sys.path
ordering invariants.

Symlink the standard library zip into venv bin and recreate encodings
in venv Lib. Add runtime DLLs to sys.path after stdlib archives and omit
bare prefix roots from early sys.path positions.
Python on Windows looks for the stdlib zip in the virtual environment
root (PREFIX) and BASE_PREFIX during startup. Placing it only in the
venv Scripts directory prevents CPython (notably 3.15) from finding
standard library encodings when importing site.

Symlink stdlib zip files into both the venv root and venv bin
directory at build time in py_executable.bzl and at runtime in the
bootstrap template. Also scan the actual interpreter directory in the
runfiles tree at bootstrap runtime.
In Python 3.15, site.py decodes .pth files using utf-8-sig. When running
in a venv on Windows, getpath sets sys._stdlib_dir to the venv Lib dir
because site-packages exists within it. FrozenImporter resolves
submodules of encodings strictly via encodings.__path__
(sys._stdlib_dir/encodings). If loose codec files are missing from
that directory, imports fail with LookupError.

Extract standard library encodings into the venv Lib/encodings directory
at bootstrap on Windows if not already present. Also include all
encodings files in files_landmark.
…aths

Subprocess output on Windows emits carriage returns (\r\n) which leaves
trailing \r on resolved sys.executable and sys.base_prefix paths when
split on newline. This causes subsequent path lookups, directory listing,
and pyvenv.cfg generation to point to non-existent paths.

Use splitlines() and strip() when parsing subprocess output, interpreter
arguments, and runtime venv symlinks. Ensure standard library encodings
are extracted to both python_home and venv Lib directories on Windows
when required by Python 3.15.
…n 3.15 stdlib zip

In Python 3.15, site.py decodes .pth files using utf-8-sig. When frozen
modules are enabled (the default), encodings is loaded as a frozen
package whose submodules are only searched within sys._stdlib_dir on
disk. With stdlib zip packaging, loose codec files are not present on
disk, causing LookupError: unknown encoding: utf-8-sig.

Pass -Xfrozen_modules=off when probing and executing Python subprocesses
on Windows so encodings and its submodules are imported via zipimport
directly from the stdlib zip archive. Also restrict extraction targets
to the writable venv directory to avoid PermissionError on read-only
runfiles trees.
Wrap lines to 80 columns across Starlark and runtime bootstrap
templates, normalize tag arguments in toolchain macros, and
canonicalize the Windows platform label reference.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant