Skip to content

Commit

Permalink
wip: fix pyc_collection bug
Browse files Browse the repository at this point in the history
  • Loading branch information
rickeylev committed Oct 9, 2024
1 parent 83eb5e8 commit ac954e6
Show file tree
Hide file tree
Showing 17 changed files with 742 additions and 344 deletions.
21 changes: 19 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ A brief description of the categories of changes:
[x.x.x]: https://github.com/bazelbuild/rules_python/releases/tag/x.x.x

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

Expand All @@ -47,6 +55,9 @@ A brief description of the categories of changes:
* (rules) `compile_pip_requirements` passes `env` to the `X.update` target (and
not only to the `X_test` target, a bug introduced in
[#1067](https://github.com/bazelbuild/rules_python/pull/1067)).
* (precompiling) The {obj}`pyc_collection` attribute now correctly
enables (or disables) using pyc files from targets transitively
>>>>>>> 607a9c3c (wip: fix pyc_collection bug)
### Added
* (py_wheel) Now supports `compress = (True|False)` to allow disabling
Expand All @@ -63,12 +74,18 @@ A brief description of the categories of changes:
* `3.10 -> 3.10.15`
* `3.11 -> 3.11.10`
* `3.12 -> 3.12.7`
[20241008]: https://github.com/indygreg/python-build-standalone/releases/tag/20241008
* (coverage) Add support for python 3.13 and bump `coverage.py` to 7.6.1.
* (api) PyInfo fields: {obj}`PyInfo.transitive_implicit_pyc_files`,
{obj}`PyInfo.transitive_implicit_pyc_source_files`.

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

### Removed
* Nothing yet
* (precompiling) {obj}`--precompile_add_to_runfiles` has been removed.
* (precompiling) {obj}`--pyc_collection` has been removed. The `pyc_collection`
attribute now bases its default on {obj}`--precompile`.
* (precompiling) The {obj}`precompile=if_generated_source` value has been removed.
* (precompiling) The {obj}`precompile_source_retention=omit_if_generated_source` value has been removed.

## [0.36.0] - 2024-09-24

Expand Down
60 changes: 22 additions & 38 deletions docs/api/rules_python/python/config_settings/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@

# //python/config_settings

:::{bzl:flag} add_srcs_to_runfiles
Determines if the `srcs` of targets are added to their runfiles.

More specifically, the sources added to runfiles are the `.py` files in `srcs`.
If precompiling is performed, it is the `.py` files that are kept according
to {obj}`precompile_source_retention`.

Values:
* `auto`: (default) Automatically decide the effective value; the current
behavior is `disabled`.
* `disabled`: Don't add `srcs` to a target's runfiles.
* `enabled`: Add `srcs` to a target's runfiles.
::::{versionadded} 0.37.0
::::
:::

:::{bzl:flag} python_version
Determines the default hermetic Python toolchain version. This can be set to
one of the values that `rules_python` maintains.
Expand Down Expand Up @@ -42,12 +58,8 @@ Values:

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

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

:::{versionadded} 0.33.0
:::
:::{versionadded} 0.36.0
The `auto` value
:::
::::

::::{bzl:flag} precompile_add_to_runfiles
Determines if a target adds its compiled files to its runfiles.

When a target compiles its files, but doesn't add them to its own runfiles, it
relies on a downstream target to retrieve them from
{bzl:obj}`PyInfo.transitive_pyc_files`

Values:
* `always`: Always include the compiled files in the target's runfiles.
* `decided_elsewhere`: Don't include the compiled files in the target's
runfiles; they are still added to {bzl:obj}`PyInfo.transitive_pyc_files`. See
also: {bzl:obj}`py_binary.pyc_collection` attribute. This is useful for allowing
incrementally enabling precompilation on a per-binary basis.
:::{versionadded} 0.33.0
:::
::::

::::{bzl:flag} pyc_collection
Determine if `py_binary` collects transitive pyc files.

:::{note}
This flag is overridden by the target level `pyc_collection` attribute.
:::

Values:
* `include_pyc`: Include `PyInfo.transitive_pyc_files` as part of the binary.
* `disabled`: Don't include `PyInfo.transitive_pyc_files` as part of the binary.
:::{versionadded} 0.33.0
:::
:::{versionchanged} 0.37.0
The `omit_if_generated_source` value was removed
::::

::::{bzl:flag} py_linux_libc
Expand Down
36 changes: 21 additions & 15 deletions docs/precompiling.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,24 @@ While precompiling helps runtime performance, it has two main costs:

## Binary-level opt-in

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

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

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

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

## Advanced precompiler customization

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

* The exec tools toolchain allows customizing the precompiler binary used with
the `precompiler` attribute. Arbitrary binaries are supported.
the {bzl:attr}`precompiler` attribute. Arbitrary binaries are supported.
* The execution requirements can be customized using
`--@rules_python//tools/precompiler:execution_requirements`. This is a list
flag that can be repeated. Each entry is a key=value that is added to the
Expand Down Expand Up @@ -92,3 +92,9 @@ Note that any execution requirements values can be specified in the flag.
`foo.cpython-39.opt-2.pyc`). This works fine (it's all byte code), but also
means the interpreter `-O` argument can't be used -- doing so will cause the
interpreter to look for the non-existent `opt-N` named files.
* Targets with the same source files and different exec properites will result
in action conflicts. This most commonly occurs when a `py_binary` and
`py_library` have the same source files. To fix, modify both targets so
they have the same exec properties. If this is difficult because unsupported
exec groups end up being passed to the Python rules, please file an issue
to have those exec groups added to the Python rules.
27 changes: 9 additions & 18 deletions python/config_settings/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
load("@pythons_hub//:versions.bzl", "DEFAULT_PYTHON_VERSION", "MINOR_MAPPING", "PYTHON_VERSIONS")
load(
"//python/private:flags.bzl",
"AddSrcsToRunfilesFlag",
"BootstrapImplFlag",
"ExecToolsToolchainFlag",
"PrecompileAddToRunfilesFlag",
"PrecompileFlag",
"PrecompileSourceRetentionFlag",
"PycCollectionFlag",
)
load(
"//python/private/pypi:flags.bzl",
Expand All @@ -33,6 +32,14 @@ construct_config_settings(
versions = PYTHON_VERSIONS,
)

string_flag(
name = "add_srcs_to_runfiles",
build_setting_default = AddSrcsToRunfilesFlag.AUTO,
values = AddSrcsToRunfilesFlag.flag_values(),
# NOTE: Only public because it is dependency of public rules.
visibility = ["//visibility:public"],
)

string_flag(
name = "exec_tools_toolchain",
build_setting_default = ExecToolsToolchainFlag.DISABLED,
Expand Down Expand Up @@ -68,22 +75,6 @@ string_flag(
visibility = ["//visibility:public"],
)

string_flag(
name = "precompile_add_to_runfiles",
build_setting_default = PrecompileAddToRunfilesFlag.ALWAYS,
values = sorted(PrecompileAddToRunfilesFlag.__members__.values()),
# NOTE: Only public because it's an implicit dependency
visibility = ["//visibility:public"],
)

string_flag(
name = "pyc_collection",
build_setting_default = PycCollectionFlag.DISABLED,
values = sorted(PycCollectionFlag.__members__.values()),
# NOTE: Only public because it's an implicit dependency
visibility = ["//visibility:public"],
)

string_flag(
name = "bootstrap_impl",
build_setting_default = BootstrapImplFlag.SYSTEM_PYTHON,
Expand Down
4 changes: 4 additions & 0 deletions python/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ bzl_library(
name = "py_package_bzl",
srcs = ["py_package.bzl"],
visibility = ["//:__subpackages__"],
deps = [
":builders_bzl",
":py_info_bzl",
],
)

bzl_library(
Expand Down
48 changes: 27 additions & 21 deletions python/private/common/attributes.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ load(

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

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

_STAMP_VALUES = [-1, 0, 1]

def _precompile_attr_get_effective_value(ctx):
Expand All @@ -50,7 +69,6 @@ def _precompile_attr_get_effective_value(ctx):
if precompile not in (
PrecompileAttr.ENABLED,
PrecompileAttr.DISABLED,
PrecompileAttr.IF_GENERATED_SOURCE,
):
fail("Unexpected final precompile value: {}".format(repr(precompile)))

Expand All @@ -60,14 +78,10 @@ def _precompile_attr_get_effective_value(ctx):
PrecompileAttr = enum(
# Determine the effective value from --precompile
INHERIT = "inherit",
# Compile Python source files at build time. Note that
# --precompile_add_to_runfiles affects how the compiled files are included
# into a downstream binary.
# Compile Python source files at build time.
ENABLED = "enabled",
# Don't compile Python source files at build time.
DISABLED = "disabled",
# Compile Python source files, but only if they're a generated file.
IF_GENERATED_SOURCE = "if_generated_source",
get_effective_value = _precompile_attr_get_effective_value,
)

Expand All @@ -90,7 +104,6 @@ def _precompile_source_retention_get_effective_value(ctx):
if attr_value not in (
PrecompileSourceRetentionAttr.KEEP_SOURCE,
PrecompileSourceRetentionAttr.OMIT_SOURCE,
PrecompileSourceRetentionAttr.OMIT_IF_GENERATED_SOURCE,
):
fail("Unexpected final precompile_source_retention value: {}".format(repr(attr_value)))
return attr_value
Expand All @@ -100,14 +113,17 @@ PrecompileSourceRetentionAttr = enum(
INHERIT = "inherit",
KEEP_SOURCE = "keep_source",
OMIT_SOURCE = "omit_source",
OMIT_IF_GENERATED_SOURCE = "omit_if_generated_source",
get_effective_value = _precompile_source_retention_get_effective_value,
)

def _pyc_collection_attr_is_pyc_collection_enabled(ctx):
pyc_collection = ctx.attr.pyc_collection
if pyc_collection == PycCollectionAttr.INHERIT:
pyc_collection = ctx.attr._pyc_collection_flag[BuildSettingInfo].value
precompile_flag = PrecompileFlag.get_effective_value(ctx)
if precompile_flag in (PrecompileFlag.ENABLED, PrecompileFlag.FORCE_ENABLED):
pyc_collection = PycCollectionAttr.INCLUDE_PYC
else:
pyc_collection = PycCollectionAttr.DISABLED

if pyc_collection not in (PycCollectionAttr.INCLUDE_PYC, PycCollectionAttr.DISABLED):
fail("Unexpected final pyc_collection value: {}".format(repr(pyc_collection)))
Expand Down Expand Up @@ -283,13 +299,9 @@ Whether py source files **for this target** should be precompiled.
Values:
* `inherit`: Determine the value from the {flag}`--precompile` flag.
* `enabled`: Compile Python source files at build time. Note that
--precompile_add_to_runfiles affects how the compiled files are included into
a downstream binary.
* `inherit`: Allow the downstream binary decide if precompiled files are used.
* `enabled`: Compile Python source files at build time.
* `disabled`: Don't compile Python source files at build time.
* `if_generated_source`: Compile Python source files, but only if they're a
generated file.
:::{seealso}
Expand Down Expand Up @@ -344,8 +356,6 @@ in the resulting output or not. Valid values are:
* `inherit`: Inherit the value from the {flag}`--precompile_source_retention` flag.
* `keep_source`: Include the original Python source.
* `omit_source`: Don't include the original py source.
* `omit_if_generated_source`: Keep the original source if it's a regular source
file, but omit it if it's a generated file.
""",
),
# Required attribute, but details vary by rule.
Expand All @@ -357,10 +367,6 @@ in the resulting output or not. Valid values are:
# Required attribute, but the details vary by rule.
# Use create_srcs_version_attr to create one.
"srcs_version": None,
"_precompile_add_to_runfiles_flag": attr.label(
default = "//python/config_settings:precompile_add_to_runfiles",
providers = [BuildSettingInfo],
),
"_precompile_flag": attr.label(
default = "//python/config_settings:precompile",
providers = [BuildSettingInfo],
Expand Down
Loading

0 comments on commit ac954e6

Please sign in to comment.