Skip to content

Commit a3cdab5

Browse files
authored
fix(precompiling)!: make binary-level precompile opt-in/opt-opt work (bazel-contrib#2243)
This makes binary-level opt-in/opt-out of precompiling work as intended. Previously, when `pyc_collection=include_pyc` was set on a binary, only transitive libraries that had explicitly enabled precompiling were being included (which was moot anyways -- libraries put their files in runfiles, so no matter what, their files were included). The intent was that, when a binary set `pyc_collection=include_pyc`, then precompiled files would be used for all its transitive dependencies (unless they had, at the target-level, disabled precompiling). Conversely, if `pyc_collection=disabled` was set, the precompiled files would not be used (unless a target had, at the target level, enabled precompiling). To make it work as desired, the basic fix is to make it so that libraries have a place to put the implicit pyc files (the ones automatically generated), and have the binaries include those when requested. The net effect is a library has 4 sets of files it produces: * required py files: py source files that should always go into the binary's runfiles * required pyc files: precompiled pyc files that should always go into the binary's runfiles (e.g., when a library sets `precompile=enabled` directly). * implicit pyc files: precompiled pyc files for a library that are always generated, but it's up to the binary if they go into the runfiles * implicit pyc source files: the source py file for an implicit pyc file. When a binary *doesn't* include the implicit pyc file, it must include the source py file (otherwise none of the library's code ends up included). Similarly, in order to allow a binary to decide what files are used, libraries must stop putting the py/pyc files into runfiles themselves. While this is potentially a breaking change, I found that, within Google, there was no reliance on this behavior, so should be safe enough. That said, I added `--add_srcs_to_runfiles` to restore the previous behavior to aid in transitioning. **BREAKING CHANGES** 1. `py_library` no longer puts its srcs into runfiles directly. 2. Removed `--precompile_add_to_runfiles` 3. Removed `--pyc_collection` 4. `precompile=if_generated_source` removed 5. `precompile_source_retention=omit_if_generated_source` removed Though 2 through 5 are technically breaking changes, I don't think precompiling was very usable anyways, so usages of those flags/values is rare. Fixes bazel-contrib#2212
1 parent f6361e4 commit a3cdab5

File tree

17 files changed

+746
-344
lines changed

17 files changed

+746
-344
lines changed

CHANGELOG.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ A brief description of the categories of changes:
2525
[x.x.x]: https://github.com/bazelbuild/rules_python/releases/tag/x.x.x
2626

2727
### Changed
28+
* **BREAKING** `py_library` no longer puts its source files or generated pyc
29+
files in runfiles; it's the responsibility of consumers (e.g. binaries) to
30+
populate runfiles with the necessary files. Adding source files to runfiles
31+
can be temporarily restored by setting {obj}`--add_srcs_to_runfiles=enabled`,
32+
but this flag will be removed in a subsequent releases.
33+
* {obj}`PyInfo.transitive_sources` is now added to runfiles. These files are
34+
`.py` files that are required to be added to runfiles by downstream binaries
35+
(or equivalent).
2836
* (toolchains) `py_runtime.implementation_name` now defaults to `cpython`
2937
(previously it defaulted to None).
3038

@@ -50,6 +58,8 @@ A brief description of the categories of changes:
5058
* (bzlmod) In hybrid bzlmod with WORKSPACE builds,
5159
`python_register_toolchains(register_toolchains=True)` is respected
5260
([#1675](https://github.com/bazelbuild/rules_python/issues/1675)).
61+
* (precompiling) The {obj}`pyc_collection` attribute now correctly
62+
enables (or disables) using pyc files from targets transitively
5363

5464
### Added
5565
* (py_wheel) Now supports `compress = (True|False)` to allow disabling
@@ -66,14 +76,20 @@ A brief description of the categories of changes:
6676
* `3.10 -> 3.10.15`
6777
* `3.11 -> 3.11.10`
6878
* `3.12 -> 3.12.7`
69-
[20241008]: https://github.com/indygreg/python-build-standalone/releases/tag/20241008
7079
* (coverage) Add support for python 3.13 and bump `coverage.py` to 7.6.1.
7180
* (bzlmod) Add support for `download_only` flag to disable usage of `sdists`
7281
when {bzl:attr}`pip.parse.experimental_index_url` is set.
82+
* (api) PyInfo fields: {obj}`PyInfo.transitive_implicit_pyc_files`,
83+
{obj}`PyInfo.transitive_implicit_pyc_source_files`.
7384

85+
[20241008]: https://github.com/indygreg/python-build-standalone/releases/tag/20241008
7486

7587
### Removed
76-
* Nothing yet
88+
* (precompiling) {obj}`--precompile_add_to_runfiles` has been removed.
89+
* (precompiling) {obj}`--pyc_collection` has been removed. The `pyc_collection`
90+
attribute now bases its default on {obj}`--precompile`.
91+
* (precompiling) The {obj}`precompile=if_generated_source` value has been removed.
92+
* (precompiling) The {obj}`precompile_source_retention=omit_if_generated_source` value has been removed.
7793

7894
## [0.36.0] - 2024-09-24
7995

docs/api/rules_python/python/config_settings/index.md

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@
55

66
# //python/config_settings
77

8+
:::{bzl:flag} add_srcs_to_runfiles
9+
Determines if the `srcs` of targets are added to their runfiles.
10+
11+
More specifically, the sources added to runfiles are the `.py` files in `srcs`.
12+
If precompiling is performed, it is the `.py` files that are kept according
13+
to {obj}`precompile_source_retention`.
14+
15+
Values:
16+
* `auto`: (default) Automatically decide the effective value; the current
17+
behavior is `disabled`.
18+
* `disabled`: Don't add `srcs` to a target's runfiles.
19+
* `enabled`: Add `srcs` to a target's runfiles.
20+
::::{versionadded} 0.37.0
21+
::::
22+
::::{deprecated} 0.37.0
23+
This is a transition flag and will be removed in a subsequent release.
24+
::::
25+
:::
26+
827
:::{bzl:flag} python_version
928
Determines the default hermetic Python toolchain version. This can be set to
1029
one of the values that `rules_python` maintains.
@@ -42,12 +61,8 @@ Values:
4261

4362
* `auto`: (default) Automatically decide the effective value based on environment,
4463
target platform, etc.
45-
* `enabled`: Compile Python source files at build time. Note that
46-
{bzl:obj}`--precompile_add_to_runfiles` affects how the compiled files are included into
47-
a downstream binary.
64+
* `enabled`: Compile Python source files at build time.
4865
* `disabled`: Don't compile Python source files at build time.
49-
* `if_generated_source`: Compile Python source files, but only if they're a
50-
generated file.
5166
* `force_enabled`: Like `enabled`, except overrides target-level setting. This
5267
is mostly useful for development, testing enabling precompilation more
5368
broadly, or as an escape hatch if build-time compiling is not available.
@@ -56,6 +71,9 @@ Values:
5671
broadly, or as an escape hatch if build-time compiling is not available.
5772
:::{versionadded} 0.33.0
5873
:::
74+
:::{versionchanged} 0.37.0
75+
The `if_generated_source` value was removed
76+
:::
5977
::::
6078

6179
::::{bzl:flag} precompile_source_retention
@@ -73,45 +91,14 @@ Values:
7391
target platform, etc.
7492
* `keep_source`: Include the original Python source.
7593
* `omit_source`: Don't include the orignal py source.
76-
* `omit_if_generated_source`: Keep the original source if it's a regular source
77-
file, but omit it if it's a generated file.
7894

7995
:::{versionadded} 0.33.0
8096
:::
8197
:::{versionadded} 0.36.0
8298
The `auto` value
8399
:::
84-
::::
85-
86-
::::{bzl:flag} precompile_add_to_runfiles
87-
Determines if a target adds its compiled files to its runfiles.
88-
89-
When a target compiles its files, but doesn't add them to its own runfiles, it
90-
relies on a downstream target to retrieve them from
91-
{bzl:obj}`PyInfo.transitive_pyc_files`
92-
93-
Values:
94-
* `always`: Always include the compiled files in the target's runfiles.
95-
* `decided_elsewhere`: Don't include the compiled files in the target's
96-
runfiles; they are still added to {bzl:obj}`PyInfo.transitive_pyc_files`. See
97-
also: {bzl:obj}`py_binary.pyc_collection` attribute. This is useful for allowing
98-
incrementally enabling precompilation on a per-binary basis.
99-
:::{versionadded} 0.33.0
100-
:::
101-
::::
102-
103-
::::{bzl:flag} pyc_collection
104-
Determine if `py_binary` collects transitive pyc files.
105-
106-
:::{note}
107-
This flag is overridden by the target level `pyc_collection` attribute.
108-
:::
109-
110-
Values:
111-
* `include_pyc`: Include `PyInfo.transitive_pyc_files` as part of the binary.
112-
* `disabled`: Don't include `PyInfo.transitive_pyc_files` as part of the binary.
113-
:::{versionadded} 0.33.0
114-
:::
100+
:::{versionchanged} 0.37.0
101+
The `omit_if_generated_source` value was removed
115102
::::
116103

117104
::::{bzl:flag} py_linux_libc

docs/precompiling.md

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,24 @@ While precompiling helps runtime performance, it has two main costs:
2020

2121
## Binary-level opt-in
2222

23-
Because of the costs of precompiling, it may not be feasible to globally enable it
24-
for your repo for everything. For example, some binaries may be
25-
particularly large, and doubling the number of runfiles isn't doable.
23+
Binary-level opt-in allows enabling precompiling on a per-target basic. This is
24+
useful for situations such as:
2625

27-
If this is the case, there's an alternative way to more selectively and
28-
incrementally control precompiling on a per-binry basis.
26+
* Globally enabling precompiling in your `.bazelrc` isn't feasible. This may
27+
be because some targets don't work with precompiling, e.g. because they're too
28+
big.
29+
* Enabling precompiling for build tools (exec config targets) separately from
30+
target-config programs.
2931

30-
To use this approach, the two basic steps are:
31-
1. Disable pyc files from being automatically added to runfiles:
32-
{bzl:obj}`--@rules_python//python/config_settings:precompile_add_to_runfiles=decided_elsewhere`,
33-
2. Set the `pyc_collection` attribute on the binaries/tests that should or should
34-
not use precompiling.
32+
To use this approach, set the {bzl:attr}`pyc_collection` attribute on the
33+
binaries/tests that should or should not use precompiling. Then change the
34+
{bzl:flag}`--precompile` default.
3535

36-
The default for the `pyc_collection` attribute is controlled by the flag
37-
{bzl:obj}`--@rules_python//python/config_settings:pyc_collection`, so you
36+
The default for the {bzl:attr}`pyc_collection` attribute is controlled by the flag
37+
{bzl:obj}`--@rules_python//python/config_settings:precompile`, so you
3838
can use an opt-in or opt-out approach by setting its value:
39-
* targets must opt-out: `--@rules_python//python/config_settings:pyc_collection=include_pyc`
40-
* targets must opt-in: `--@rules_python//python/config_settings:pyc_collection=disabled`
39+
* targets must opt-out: `--@rules_python//python/config_settings:precompile=enabled`
40+
* targets must opt-in: `--@rules_python//python/config_settings:precompile=disabled`
4141

4242
## Advanced precompiler customization
4343

@@ -48,7 +48,7 @@ not work as well for remote execution builds. To customize the precompiler, two
4848
mechanisms are available:
4949

5050
* The exec tools toolchain allows customizing the precompiler binary used with
51-
the `precompiler` attribute. Arbitrary binaries are supported.
51+
the {bzl:attr}`precompiler` attribute. Arbitrary binaries are supported.
5252
* The execution requirements can be customized using
5353
`--@rules_python//tools/precompiler:execution_requirements`. This is a list
5454
flag that can be repeated. Each entry is a key=value that is added to the
@@ -92,3 +92,9 @@ Note that any execution requirements values can be specified in the flag.
9292
`foo.cpython-39.opt-2.pyc`). This works fine (it's all byte code), but also
9393
means the interpreter `-O` argument can't be used -- doing so will cause the
9494
interpreter to look for the non-existent `opt-N` named files.
95+
* Targets with the same source files and different exec properites will result
96+
in action conflicts. This most commonly occurs when a `py_binary` and
97+
`py_library` have the same source files. To fix, modify both targets so
98+
they have the same exec properties. If this is difficult because unsupported
99+
exec groups end up being passed to the Python rules, please file an issue
100+
to have those exec groups added to the Python rules.

python/config_settings/BUILD.bazel

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
22
load("@pythons_hub//:versions.bzl", "DEFAULT_PYTHON_VERSION", "MINOR_MAPPING", "PYTHON_VERSIONS")
33
load(
44
"//python/private:flags.bzl",
5+
"AddSrcsToRunfilesFlag",
56
"BootstrapImplFlag",
67
"ExecToolsToolchainFlag",
7-
"PrecompileAddToRunfilesFlag",
88
"PrecompileFlag",
99
"PrecompileSourceRetentionFlag",
10-
"PycCollectionFlag",
1110
)
1211
load(
1312
"//python/private/pypi:flags.bzl",
@@ -33,6 +32,14 @@ construct_config_settings(
3332
versions = PYTHON_VERSIONS,
3433
)
3534

35+
string_flag(
36+
name = "add_srcs_to_runfiles",
37+
build_setting_default = AddSrcsToRunfilesFlag.AUTO,
38+
values = AddSrcsToRunfilesFlag.flag_values(),
39+
# NOTE: Only public because it is dependency of public rules.
40+
visibility = ["//visibility:public"],
41+
)
42+
3643
string_flag(
3744
name = "exec_tools_toolchain",
3845
build_setting_default = ExecToolsToolchainFlag.DISABLED,
@@ -68,22 +75,6 @@ string_flag(
6875
visibility = ["//visibility:public"],
6976
)
7077

71-
string_flag(
72-
name = "precompile_add_to_runfiles",
73-
build_setting_default = PrecompileAddToRunfilesFlag.ALWAYS,
74-
values = sorted(PrecompileAddToRunfilesFlag.__members__.values()),
75-
# NOTE: Only public because it's an implicit dependency
76-
visibility = ["//visibility:public"],
77-
)
78-
79-
string_flag(
80-
name = "pyc_collection",
81-
build_setting_default = PycCollectionFlag.DISABLED,
82-
values = sorted(PycCollectionFlag.__members__.values()),
83-
# NOTE: Only public because it's an implicit dependency
84-
visibility = ["//visibility:public"],
85-
)
86-
8778
string_flag(
8879
name = "bootstrap_impl",
8980
build_setting_default = BootstrapImplFlag.SYSTEM_PYTHON,

python/private/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ bzl_library(
297297
name = "py_package_bzl",
298298
srcs = ["py_package.bzl"],
299299
visibility = ["//:__subpackages__"],
300+
deps = [
301+
":builders_bzl",
302+
":py_info_bzl",
303+
],
300304
)
301305

302306
bzl_library(

python/private/common/attributes.bzl

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,26 @@ load(
2929

3030
_PackageSpecificationInfo = getattr(py_internal, "PackageSpecificationInfo", None)
3131

32+
# Due to how the common exec_properties attribute works, rules must add exec
33+
# groups even if they don't actually use them. This is due to two interactions:
34+
# 1. Rules give an error if users pass an unsupported exec group.
35+
# 2. exec_properties is configurable, so macro-code can't always filter out
36+
# exec group names that aren't supported by the rule.
37+
# The net effect is, if a user passes exec_properties to a macro, and the macro
38+
# invokes two rules, the macro can't always ensure each rule is only passed
39+
# valid exec groups, and is thus liable to cause an error.
40+
#
41+
# NOTE: These are no-op/empty exec groups. If a rule *does* support an exec
42+
# group and needs custom settings, it should merge this dict with one that
43+
# overrides the supported key.
44+
REQUIRED_EXEC_GROUPS = {
45+
# py_binary may invoke C++ linking, or py rules may be used in combination
46+
# with cc rules (e.g. within the same macro), so support that exec group.
47+
# This exec group is defined by rules_cc for the cc rules.
48+
"cpp_link": exec_group(),
49+
"py_precompile": exec_group(),
50+
}
51+
3252
_STAMP_VALUES = [-1, 0, 1]
3353

3454
def _precompile_attr_get_effective_value(ctx):
@@ -50,7 +70,6 @@ def _precompile_attr_get_effective_value(ctx):
5070
if precompile not in (
5171
PrecompileAttr.ENABLED,
5272
PrecompileAttr.DISABLED,
53-
PrecompileAttr.IF_GENERATED_SOURCE,
5473
):
5574
fail("Unexpected final precompile value: {}".format(repr(precompile)))
5675

@@ -60,14 +79,10 @@ def _precompile_attr_get_effective_value(ctx):
6079
PrecompileAttr = enum(
6180
# Determine the effective value from --precompile
6281
INHERIT = "inherit",
63-
# Compile Python source files at build time. Note that
64-
# --precompile_add_to_runfiles affects how the compiled files are included
65-
# into a downstream binary.
82+
# Compile Python source files at build time.
6683
ENABLED = "enabled",
6784
# Don't compile Python source files at build time.
6885
DISABLED = "disabled",
69-
# Compile Python source files, but only if they're a generated file.
70-
IF_GENERATED_SOURCE = "if_generated_source",
7186
get_effective_value = _precompile_attr_get_effective_value,
7287
)
7388

@@ -90,7 +105,6 @@ def _precompile_source_retention_get_effective_value(ctx):
90105
if attr_value not in (
91106
PrecompileSourceRetentionAttr.KEEP_SOURCE,
92107
PrecompileSourceRetentionAttr.OMIT_SOURCE,
93-
PrecompileSourceRetentionAttr.OMIT_IF_GENERATED_SOURCE,
94108
):
95109
fail("Unexpected final precompile_source_retention value: {}".format(repr(attr_value)))
96110
return attr_value
@@ -100,14 +114,17 @@ PrecompileSourceRetentionAttr = enum(
100114
INHERIT = "inherit",
101115
KEEP_SOURCE = "keep_source",
102116
OMIT_SOURCE = "omit_source",
103-
OMIT_IF_GENERATED_SOURCE = "omit_if_generated_source",
104117
get_effective_value = _precompile_source_retention_get_effective_value,
105118
)
106119

107120
def _pyc_collection_attr_is_pyc_collection_enabled(ctx):
108121
pyc_collection = ctx.attr.pyc_collection
109122
if pyc_collection == PycCollectionAttr.INHERIT:
110-
pyc_collection = ctx.attr._pyc_collection_flag[BuildSettingInfo].value
123+
precompile_flag = PrecompileFlag.get_effective_value(ctx)
124+
if precompile_flag in (PrecompileFlag.ENABLED, PrecompileFlag.FORCE_ENABLED):
125+
pyc_collection = PycCollectionAttr.INCLUDE_PYC
126+
else:
127+
pyc_collection = PycCollectionAttr.DISABLED
111128

112129
if pyc_collection not in (PycCollectionAttr.INCLUDE_PYC, PycCollectionAttr.DISABLED):
113130
fail("Unexpected final pyc_collection value: {}".format(repr(pyc_collection)))
@@ -283,13 +300,9 @@ Whether py source files **for this target** should be precompiled.
283300
284301
Values:
285302
286-
* `inherit`: Determine the value from the {flag}`--precompile` flag.
287-
* `enabled`: Compile Python source files at build time. Note that
288-
--precompile_add_to_runfiles affects how the compiled files are included into
289-
a downstream binary.
303+
* `inherit`: Allow the downstream binary decide if precompiled files are used.
304+
* `enabled`: Compile Python source files at build time.
290305
* `disabled`: Don't compile Python source files at build time.
291-
* `if_generated_source`: Compile Python source files, but only if they're a
292-
generated file.
293306
294307
:::{seealso}
295308
@@ -344,8 +357,6 @@ in the resulting output or not. Valid values are:
344357
* `inherit`: Inherit the value from the {flag}`--precompile_source_retention` flag.
345358
* `keep_source`: Include the original Python source.
346359
* `omit_source`: Don't include the original py source.
347-
* `omit_if_generated_source`: Keep the original source if it's a regular source
348-
file, but omit it if it's a generated file.
349360
""",
350361
),
351362
# Required attribute, but details vary by rule.
@@ -357,10 +368,6 @@ in the resulting output or not. Valid values are:
357368
# Required attribute, but the details vary by rule.
358369
# Use create_srcs_version_attr to create one.
359370
"srcs_version": None,
360-
"_precompile_add_to_runfiles_flag": attr.label(
361-
default = "//python/config_settings:precompile_add_to_runfiles",
362-
providers = [BuildSettingInfo],
363-
),
364371
"_precompile_flag": attr.label(
365372
default = "//python/config_settings:precompile",
366373
providers = [BuildSettingInfo],

0 commit comments

Comments
 (0)