Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/api/rules_python/python/config_settings/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,27 @@ Values:
:::
::::

::::{bzl:flag} py_pbs_distribution
Set which python-build-standalone archive the hermetic Python toolchains use.

Values:
* `auto`: Let `rules_python` choose; currently the same as `install_only`,
default.
* `install_only`: Use the runtime with debug symbols.
* `install_only_stripped`: Use the runtime without debug symbols, which is
substantially smaller.
* `full`: Use the runtime that also includes build artifacts.

If a Python version or platform has no archive of the selected kind, the
`install_only` archive is used instead.

See {ref}`Selecting the runtime archive <selecting-the-runtime-archive>` for
details.

:::{versionadded} VERSION_NEXT_FEATURE
:::
::::

::::{bzl:flag} pip_env_marker_config
The target that provides the values for pip env marker evaluation.

Expand Down
31 changes: 31 additions & 0 deletions docs/toolchains.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,35 @@ locations. These will be helpful if you need to set environment variables of bin
while using [`--nolegacy_external_runfiles`](https://bazel.build/reference/command-line-reference#flag--legacy_external_runfiles).
The original make variables still work in exec contexts such as genrules.

(selecting-the-runtime-archive)=
### Selecting the runtime archive

python-build-standalone publishes each runtime as several kinds of archive. The
{obj}`--@rules_python//python/config_settings:py_pbs_distribution`
flag selects which one the hermetic toolchains use:

* `auto` (default): let `rules_python` choose; currently `install_only`.
* `install_only`: the runtime with debug symbols.
* `install_only_stripped`: the runtime without debug symbols, which is
substantially smaller. This reduces the size of runtimes and
{obj}`py_zipapp_binary` outputs.
* `full`: the runtime plus build artifacts.

Because this is a build flag, a single workspace can build with different
archives, for example keeping debug symbols in development builds and using
stripped runtimes in production builds:

```
# File: .bazelrc
build:prod --@rules_python//python/config_settings:py_pbs_distribution=install_only_stripped
```

A toolchain is registered for each archive kind, and only the archive a build
selects is downloaded. If a Python version or platform has no archive of the
selected kind, the `install_only` archive is used instead. For example,
stripped archives are only available for runtimes released from `20240726`
onward.

### Overriding toolchain defaults and adding more versions

One can perform various overrides for the registered toolchains from the root
Expand Down Expand Up @@ -875,6 +904,8 @@ Currently the following flags are used to influence toolchain selection:
* {obj}`--@rules_python//python/config_settings:py_linux_libc` for selecting the Linux libc variant.
* {obj}`--@rules_python//python/config_settings:py_freethreaded` for selecting
the freethreaded experimental Python builds available from `3.13.0` onwards.
* {obj}`--@rules_python//python/config_settings:py_pbs_distribution` for selecting
which python-build-standalone archive to use, e.g. stripped runtimes.

## Running the underlying interpreter

Expand Down
6 changes: 6 additions & 0 deletions news/4163.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(toolchains) Added the
{obj}`--@rules_python//python/config_settings:py_pbs_distribution`
flag to select which python-build-standalone archive the hermetic toolchains
use. Setting it to `install_only_stripped` uses runtimes without debug symbols,
which reduces runtime and zipapp size.
([#4163](https://github.com/bazel-contrib/rules_python/issues/4163))
9 changes: 9 additions & 0 deletions python/config_settings/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ load(
"ExecToolsToolchainFlag",
"FreeThreadedFlag",
"LibcFlag",
"PbsDistributionFlag",
"PrecompileFlag",
"PrecompileSourceRetentionFlag",
"ValidateTestMainFlag",
"VenvsSitePackages",
"VenvsUseDeclareSymlinkFlag",
"pbs_distribution_flag",
rp_string_flag = "string_flag",
)
load("//python/private:visibility.bzl", "NOT_ACTUALLY_PUBLIC") # buildifier: disable=bzl-visibility
Expand All @@ -38,6 +40,7 @@ construct_config_settings(
":pip_whl_osx_version",
":py_freethreaded",
":py_linux_libc",
":py_pbs_distribution",
],
minor_mapping = MINOR_MAPPING,
versions = PYTHON_VERSIONS,
Expand Down Expand Up @@ -137,6 +140,12 @@ string_flag(
visibility = ["//visibility:public"],
)

pbs_distribution_flag(
name = "py_pbs_distribution",
build_setting_default = PbsDistributionFlag.AUTO,
visibility = ["//visibility:public"],
)

alias(
name = "is_py_freethreaded",
actual = ":_is_py_freethreaded_yes",
Expand Down
2 changes: 2 additions & 0 deletions python/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,7 @@ bzl_library(
deps = [
":coverage_deps",
":full_version",
":pbs_manifest",
":python_repository",
":toolchains_repo",
"//python:versions",
Expand Down Expand Up @@ -874,6 +875,7 @@ bzl_library(
name = "toolchains_repo",
srcs = ["toolchains_repo.bzl"],
deps = [
":pbs_manifest",
":repo_utils",
":text_util",
"//python:versions",
Expand Down
1 change: 1 addition & 0 deletions python/private/common_labels.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ labels = struct(
PYTHON_VERSION_MAJOR_MINOR = str(Label("//python/config_settings:python_version_major_minor")),
PY_FREETHREADED = str(Label("//python/config_settings:py_freethreaded")),
PY_LINUX_LIBC = str(Label("//python/config_settings:py_linux_libc")),
PY_PBS_DISTRIBUTION = str(Label("//python/config_settings:py_pbs_distribution")),
REPL_DEP = str(Label("//python/bin:repl_dep")),
VALIDATE_TEST_MAIN = str(Label("//python/config_settings:validate_test_main")),
VENV = str(Label("//python/config_settings:venv")),
Expand Down
10 changes: 10 additions & 0 deletions python/private/config_settings.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,16 @@ def construct_config_settings(
visibility = NOT_ACTUALLY_PUBLIC,
)

# NOTE: The py_pbs_distribution flag reports `auto` as `install_only`, so
# the install_only setting also matches the default.
pbs_distribution = Label("//python/config_settings:py_pbs_distribution")
for distribution in ["install_only", "install_only_stripped", "full"]:
native.config_setting(
name = "_is_py_pbs_distribution_" + distribution,
flag_values = {pbs_distribution: distribution},
visibility = NOT_ACTUALLY_PUBLIC,
)

def _python_version_flag_impl(ctx):
value = ctx.build_setting_value
return [
Expand Down
40 changes: 40 additions & 0 deletions python/private/flags.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,43 @@ LibcFlag = FlagEnum(
MUSL = "musl",
get_value = _libc_flag_get_value,
)

# Determines which python-build-standalone distribution (archive) is used for
# the hermetic runtimes.
#
# buildifier: disable=name-conventions
PbsDistributionFlag = FlagEnum(
# Let rules_python choose; currently the same as install_only.
AUTO = "auto",
# The install_only archive, which includes debug symbols.
INSTALL_ONLY = "install_only",
# The install_only_stripped archive, which omits debug symbols.
INSTALL_ONLY_STRIPPED = "install_only_stripped",
# The full archive, which includes build artifacts.
FULL = "full",
)

def _pbs_distribution_flag_impl(ctx):
value = ctx.build_setting_value
if value not in PbsDistributionFlag.flag_values():
fail("Invalid value for {name}: got {value}, must be one of {allowed}".format(
name = ctx.label,
value = value,
allowed = PbsDistributionFlag.flag_values(),
))

# config_setting matches against the FeatureFlagInfo value, so `auto` is
# resolved here and toolchains only need to match concrete distributions.
effective = value
if value == PbsDistributionFlag.AUTO:
effective = PbsDistributionFlag.INSTALL_ONLY
return [
BuildSettingInfo(value = value),
config_common.FeatureFlagInfo(value = effective),
]

pbs_distribution_flag = rule(
implementation = _pbs_distribution_flag_impl,
build_setting = config.string(flag = True),
doc = "The python-build-standalone distribution to use for hermetic runtimes.",
)
59 changes: 59 additions & 0 deletions python/private/pbs_manifest.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,65 @@ _ASTRAL_RELEASE_URL_PREFIXES = [
"https://releases.astral.sh/github/python-build-standalone/releases/download/",
]

ARCHIVE_FLAVORS = ["install_only", "install_only_stripped", "full"]

# Platform key suffixes for the non-default archive flavors. Runtimes for the
# default flavor use the plain platform key (e.g. `x86_64-unknown-linux-gnu`).
PBS_DISTRIBUTION_SUFFIXES = {
"full": "-full",
"install_only_stripped": "-install_only_stripped",
}

def split_pbs_distribution(platform):
"""Splits a platform key into its base platform and distribution suffix.

Args:
platform: A platform key, e.g. `x86_64-unknown-linux-gnu-full`.

Returns:
A tuple of `(base_platform, suffix)`, where `suffix` is empty for the
plain platform key.
"""
for suffix in PBS_DISTRIBUTION_SUFFIXES.values():
if platform.endswith(suffix):
return platform.removesuffix(suffix), suffix
return platform, ""

def manifest_entry_platform_keys(entry, platform):
"""Returns the platform keys a manifest entry can provide a runtime for.

Args:
entry: A parsed manifest entry struct.
platform: The plain platform key for the entry.

Returns:
A list of platform keys: the plain key, plus the key for the entry's
archive flavor if it has one.
"""
keys = [platform]
if entry.archive_flavor in PBS_DISTRIBUTION_SUFFIXES:
keys.append(platform + PBS_DISTRIBUTION_SUFFIXES[entry.archive_flavor])
return keys

def manifest_entry_sort_key(entry):
"""Sort key that ranks manifest entries by archive preference.

Args:
entry: A parsed manifest entry struct.

Returns:
A sortable tuple where lower values are preferred.
"""
flavor_rank = {"full": 3, "install_only": 1, "install_only_stripped": 2}.get(entry.archive_flavor, 4)
microarch = entry.microarch
if not microarch:
microarch_rank = 0
elif microarch.startswith("v") and microarch[1:].isdigit():
microarch_rank = int(microarch[1:])
else:
microarch_rank = 999
return (flavor_rank, microarch_rank)

def parse_filename(filename):
"""Parses a python-build-standalone filename (or URL) into its components.

Expand Down
Loading
Loading