Skip to content

Commit 7abcca7

Browse files
committed
wip: fix pyc_collection bug
1 parent b580138 commit 7abcca7

File tree

13 files changed

+614
-337
lines changed

13 files changed

+614
-337
lines changed

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

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ Values:
3838

3939
* `auto`: (default) Automatically decide the effective value based on environment,
4040
target platform, etc.
41-
* `enabled`: Compile Python source files at build time. Note that
42-
{bzl:obj}`--precompile_add_to_runfiles` affects how the compiled files are included into
43-
a downstream binary.
41+
* `enabled`: Compile Python source files at build time.
4442
* `disabled`: Don't compile Python source files at build time.
4543
* `if_generated_source`: Compile Python source files, but only if they're a
4644
generated file.
@@ -79,37 +77,6 @@ The `auto` value
7977
:::
8078
::::
8179

82-
::::{bzl:flag} precompile_add_to_runfiles
83-
Determines if a target adds its compiled files to its runfiles.
84-
85-
When a target compiles its files, but doesn't add them to its own runfiles, it
86-
relies on a downstream target to retrieve them from
87-
{bzl:obj}`PyInfo.transitive_pyc_files`
88-
89-
Values:
90-
* `always`: Always include the compiled files in the target's runfiles.
91-
* `decided_elsewhere`: Don't include the compiled files in the target's
92-
runfiles; they are still added to {bzl:obj}`PyInfo.transitive_pyc_files`. See
93-
also: {bzl:obj}`py_binary.pyc_collection` attribute. This is useful for allowing
94-
incrementally enabling precompilation on a per-binary basis.
95-
:::{versionadded} 0.33.0
96-
:::
97-
::::
98-
99-
::::{bzl:flag} pyc_collection
100-
Determine if `py_binary` collects transitive pyc files.
101-
102-
:::{note}
103-
This flag is overridden by the target level `pyc_collection` attribute.
104-
:::
105-
106-
Values:
107-
* `include_pyc`: Include `PyInfo.transitive_pyc_files` as part of the binary.
108-
* `disabled`: Don't include `PyInfo.transitive_pyc_files` as part of the binary.
109-
:::{versionadded} 0.33.0
110-
:::
111-
::::
112-
11380
::::{bzl:flag} py_linux_libc
11481
Set what libc is used for the target platform. This will affect which whl binaries will be pulled and what toolchain will be auto-detected. Currently `rules_python` only supplies toolchains compatible with `glibc`.
11582

docs/precompiling.md

Lines changed: 15 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

python/config_settings/BUILD.bazel

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ load(
44
"//python/private:flags.bzl",
55
"BootstrapImplFlag",
66
"ExecToolsToolchainFlag",
7-
"PrecompileAddToRunfilesFlag",
87
"PrecompileFlag",
98
"PrecompileSourceRetentionFlag",
10-
"PycCollectionFlag",
119
)
1210
load(
1311
"//python/private/pypi:flags.bzl",
@@ -67,22 +65,6 @@ string_flag(
6765
visibility = ["//visibility:public"],
6866
)
6967

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

python/private/common/attributes.bzl

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@ 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+
"cpp_link": exec_group(),
48+
"py_precompile": exec_group(),
49+
}
50+
3251
_STAMP_VALUES = [-1, 0, 1]
3352

3453
def _precompile_attr_get_effective_value(ctx):
@@ -50,7 +69,6 @@ def _precompile_attr_get_effective_value(ctx):
5069
if precompile not in (
5170
PrecompileAttr.ENABLED,
5271
PrecompileAttr.DISABLED,
53-
PrecompileAttr.IF_GENERATED_SOURCE,
5472
):
5573
fail("Unexpected final precompile value: {}".format(repr(precompile)))
5674

@@ -60,14 +78,10 @@ def _precompile_attr_get_effective_value(ctx):
6078
PrecompileAttr = enum(
6179
# Determine the effective value from --precompile
6280
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.
81+
# Compile Python source files at build time.
6682
ENABLED = "enabled",
6783
# Don't compile Python source files at build time.
6884
DISABLED = "disabled",
69-
# Compile Python source files, but only if they're a generated file.
70-
IF_GENERATED_SOURCE = "if_generated_source",
7185
get_effective_value = _precompile_attr_get_effective_value,
7286
)
7387

@@ -90,7 +104,6 @@ def _precompile_source_retention_get_effective_value(ctx):
90104
if attr_value not in (
91105
PrecompileSourceRetentionAttr.KEEP_SOURCE,
92106
PrecompileSourceRetentionAttr.OMIT_SOURCE,
93-
PrecompileSourceRetentionAttr.OMIT_IF_GENERATED_SOURCE,
94107
):
95108
fail("Unexpected final precompile_source_retention value: {}".format(repr(attr_value)))
96109
return attr_value
@@ -100,14 +113,17 @@ PrecompileSourceRetentionAttr = enum(
100113
INHERIT = "inherit",
101114
KEEP_SOURCE = "keep_source",
102115
OMIT_SOURCE = "omit_source",
103-
OMIT_IF_GENERATED_SOURCE = "omit_if_generated_source",
104116
get_effective_value = _precompile_source_retention_get_effective_value,
105117
)
106118

107119
def _pyc_collection_attr_is_pyc_collection_enabled(ctx):
108120
pyc_collection = ctx.attr.pyc_collection
109121
if pyc_collection == PycCollectionAttr.INHERIT:
110-
pyc_collection = ctx.attr._pyc_collection_flag[BuildSettingInfo].value
122+
precompile_flag = PrecompileFlag.get_effective_value(ctx)
123+
if precompile_flag in (PrecompileFlag.ENABLED, PrecompileFlag.FORCE_ENABLED):
124+
pyc_collection = PycCollectionAttr.INCLUDE_PYC
125+
else:
126+
pyc_collection = PycCollectionAttr.DISABLED
111127

112128
if pyc_collection not in (PycCollectionAttr.INCLUDE_PYC, PycCollectionAttr.DISABLED):
113129
fail("Unexpected final pyc_collection value: {}".format(repr(pyc_collection)))
@@ -282,13 +298,9 @@ Whether py source files **for this target** should be precompiled.
282298
283299
Values:
284300
285-
* `inherit`: Determine the value from the {flag}`--precompile` flag.
286-
* `enabled`: Compile Python source files at build time. Note that
287-
--precompile_add_to_runfiles affects how the compiled files are included into
288-
a downstream binary.
301+
* `inherit`: Allow the downstream binary decide if precompiled files are used.
302+
* `enabled`: Compile Python source files at build time.
289303
* `disabled`: Don't compile Python source files at build time.
290-
* `if_generated_source`: Compile Python source files, but only if they're a
291-
generated file.
292304
293305
:::{seealso}
294306
@@ -343,8 +355,6 @@ in the resulting output or not. Valid values are:
343355
* `inherit`: Inherit the value from the {flag}`--precompile_source_retention` flag.
344356
* `keep_source`: Include the original Python source.
345357
* `omit_source`: Don't include the original py source.
346-
* `omit_if_generated_source`: Keep the original source if it's a regular source
347-
file, but omit it if it's a generated file.
348358
""",
349359
),
350360
# Required attribute, but details vary by rule.
@@ -356,10 +366,6 @@ in the resulting output or not. Valid values are:
356366
# Required attribute, but the details vary by rule.
357367
# Use create_srcs_version_attr to create one.
358368
"srcs_version": None,
359-
"_precompile_add_to_runfiles_flag": attr.label(
360-
default = "//python/config_settings:precompile_add_to_runfiles",
361-
providers = [BuildSettingInfo],
362-
),
363369
"_precompile_flag": attr.label(
364370
default = "//python/config_settings:precompile",
365371
providers = [BuildSettingInfo],

python/private/common/common.bzl

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -348,15 +348,29 @@ def collect_runfiles(ctx, files = depset()):
348348
collect_default = True,
349349
)
350350

351-
def create_py_info(ctx, *, direct_sources, direct_pyc_files, imports):
351+
def create_py_info(
352+
ctx,
353+
*,
354+
required_py_files,
355+
required_pyc_files,
356+
implicit_pyc_files,
357+
implicit_pyc_source_files,
358+
imports):
352359
"""Create PyInfo provider.
353360
354361
Args:
355362
ctx: rule ctx.
356-
direct_sources: depset of Files; the direct, raw `.py` sources for the
357-
target. This should only be Python source files. It should not
358-
include pyc files.
359-
direct_pyc_files: depset of Files; the direct `.pyc` sources for the target.
363+
required_py_files: `depset[File]`; the direct, `.py` sources for the
364+
target that **must** be included by downstream targets. This should
365+
only be Python source files. It should not include pyc files.
366+
required_pyc_files: `depset[File]`; the direct `.pyc` files this target
367+
produces.
368+
implicit_pyc_files: `depset[File]` pyc files that are only used if pyc
369+
collection is enabled.
370+
implicit_pyc_source_files: `depset[File]` source files for implicit pyc
371+
files that are used when the implicit pyc files are not.
372+
implicit_pyc_files: {type}`depset[File]` Implicitly generated pyc files
373+
that a binary can choose to include.
360374
imports: depset of strings; the import path values to propagate.
361375
362376
Returns:
@@ -366,8 +380,10 @@ def create_py_info(ctx, *, direct_sources, direct_pyc_files, imports):
366380
"""
367381

368382
py_info = PyInfoBuilder()
369-
py_info.direct_pyc_files.add(direct_pyc_files)
370-
py_info.transitive_pyc_files.add(direct_pyc_files)
383+
py_info.direct_pyc_files.add(required_pyc_files)
384+
py_info.transitive_pyc_files.add(required_pyc_files)
385+
py_info.transitive_implicit_pyc_files.add(implicit_pyc_files)
386+
py_info.transitive_implicit_pyc_source_files.add(implicit_pyc_source_files)
371387
py_info.imports.add(imports)
372388
py_info.merge_has_py2_only_sources(ctx.attr.srcs_version in ("PY2", "PY2ONLY"))
373389
py_info.merge_has_py3_only_sources(ctx.attr.srcs_version in ("PY3", "PY3ONLY"))
@@ -386,7 +402,7 @@ def create_py_info(ctx, *, direct_sources, direct_pyc_files, imports):
386402
py_info.merge_uses_shared_libraries(cc_helper.is_valid_shared_library_artifact(f))
387403

388404
deps_transitive_sources = py_info.transitive_sources.build()
389-
py_info.transitive_sources.add(direct_sources)
405+
py_info.transitive_sources.add(required_py_files)
390406

391407
# We only look at data to calculate uses_shared_libraries, if it's already
392408
# true, then we don't need to waste time looping over it.

python/private/common/common_bazel.bzl

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
"""Common functions that are specific to Bazel rule implementation"""
1515

1616
load("@bazel_skylib//lib:paths.bzl", "paths")
17+
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
1718
load("@rules_cc//cc:defs.bzl", "CcInfo", "cc_common")
19+
load("//python/private:flags.bzl", "PrecompileFlag")
1820
load("//python/private:py_interpreter_program.bzl", "PyInterpreterProgramInfo")
1921
load("//python/private:toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "TARGET_TOOLCHAIN_TYPE")
2022
load(":attributes.bzl", "PrecompileAttr", "PrecompileInvalidationModeAttr", "PrecompileSourceRetentionAttr")
@@ -60,7 +62,7 @@ def maybe_precompile(ctx, srcs):
6062
Returns:
6163
Struct of precompiling results with fields:
6264
* `keep_srcs`: list of File; the input sources that should be included
63-
as default outputs and runfiles.
65+
as default outputs.
6466
* `pyc_files`: list of File; the precompiled files.
6567
* `py_to_pyc_map`: dict of src File input to pyc File output. If a source
6668
file wasn't precompiled, it won't be in the dict.
@@ -72,36 +74,45 @@ def maybe_precompile(ctx, srcs):
7274
if exec_tools_toolchain == None or exec_tools_toolchain.exec_tools.precompiler == None:
7375
precompile = PrecompileAttr.DISABLED
7476
else:
75-
precompile = PrecompileAttr.get_effective_value(ctx)
77+
precompile_flag = ctx.attr._precompile_flag[BuildSettingInfo].value
78+
79+
if precompile_flag == PrecompileFlag.FORCE_ENABLED:
80+
precompile = PrecompileAttr.ENABLED
81+
elif precompile_flag == PrecompileFlag.FORCE_DISABLED:
82+
precompile = PrecompileAttr.DISABLED
83+
else:
84+
precompile = ctx.attr.precompile
85+
86+
# Unless explicitly disabled, we always generate a pyc. This allows
87+
# binaries to decide whether to include them or not later.
88+
if precompile != PrecompileAttr.DISABLED:
89+
should_precompile = True
90+
else:
91+
should_precompile = False
7692

7793
source_retention = PrecompileSourceRetentionAttr.get_effective_value(ctx)
94+
keep_source = (
95+
not should_precompile or
96+
source_retention == PrecompileSourceRetentionAttr.KEEP_SOURCE
97+
)
7898

7999
result = struct(
80100
keep_srcs = [],
81101
pyc_files = [],
82102
py_to_pyc_map = {},
83103
)
84104
for src in srcs:
85-
# The logic below is a bit convoluted. The gist is:
86-
# * If precompiling isn't done, add the py source to default outputs.
87-
# Otherwise, the source retention flag decides.
88-
# * In order to determine `use_pycache`, we have to know if the source
89-
# is being added to the default outputs.
90-
is_generated_source = not src.is_source
91-
should_precompile = (
92-
precompile == PrecompileAttr.ENABLED or
93-
(precompile == PrecompileAttr.IF_GENERATED_SOURCE and is_generated_source)
94-
)
95-
keep_source = (
96-
not should_precompile or
97-
source_retention == PrecompileSourceRetentionAttr.KEEP_SOURCE or
98-
(source_retention == PrecompileSourceRetentionAttr.OMIT_IF_GENERATED_SOURCE and not is_generated_source)
99-
)
100105
if should_precompile:
106+
# NOTE: _precompile() may return None
101107
pyc = _precompile(ctx, src, use_pycache = keep_source)
108+
else:
109+
pyc = None
110+
111+
if pyc:
102112
result.pyc_files.append(pyc)
103113
result.py_to_pyc_map[src] = pyc
104-
if keep_source:
114+
115+
if keep_source or not pyc:
105116
result.keep_srcs.append(src)
106117

107118
return result

0 commit comments

Comments
 (0)