xref: /aosp_15_r20/external/bazelbuild-rules_python/python/private/common/attributes.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1# Copyright 2022 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Attributes for Python rules."""
15
16load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
17load("@rules_cc//cc:defs.bzl", "CcInfo")
18load("//python/private:enum.bzl", "enum")
19load("//python/private:flags.bzl", "PrecompileFlag", "PrecompileSourceRetentionFlag")
20load("//python/private:reexports.bzl", "BuiltinPyInfo")
21load(":common.bzl", "union_attrs")
22load(":providers.bzl", "PyInfo")
23load(":py_internal.bzl", "py_internal")
24load(
25    ":semantics.bzl",
26    "DEPS_ATTR_ALLOW_RULES",
27    "SRCS_ATTR_ALLOW_FILES",
28)
29
30_PackageSpecificationInfo = getattr(py_internal, "PackageSpecificationInfo", None)
31
32_STAMP_VALUES = [-1, 0, 1]
33
34def _precompile_attr_get_effective_value(ctx):
35    precompile_flag = PrecompileFlag.get_effective_value(ctx)
36
37    if precompile_flag == PrecompileFlag.FORCE_ENABLED:
38        return PrecompileAttr.ENABLED
39    if precompile_flag == PrecompileFlag.FORCE_DISABLED:
40        return PrecompileAttr.DISABLED
41
42    precompile_attr = ctx.attr.precompile
43    if precompile_attr == PrecompileAttr.INHERIT:
44        precompile = precompile_flag
45    else:
46        precompile = precompile_attr
47
48    # Guard against bad final states because the two enums are similar with
49    # magic values.
50    if precompile not in (
51        PrecompileAttr.ENABLED,
52        PrecompileAttr.DISABLED,
53        PrecompileAttr.IF_GENERATED_SOURCE,
54    ):
55        fail("Unexpected final precompile value: {}".format(repr(precompile)))
56
57    return precompile
58
59# buildifier: disable=name-conventions
60PrecompileAttr = enum(
61    # Determine the effective value from --precompile
62    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.
66    ENABLED = "enabled",
67    # Don't compile Python source files at build time.
68    DISABLED = "disabled",
69    # Compile Python source files, but only if they're a generated file.
70    IF_GENERATED_SOURCE = "if_generated_source",
71    get_effective_value = _precompile_attr_get_effective_value,
72)
73
74# buildifier: disable=name-conventions
75PrecompileInvalidationModeAttr = enum(
76    # Automatically pick a value based on build settings.
77    AUTO = "auto",
78    # Use the pyc file if the hash of the originating source file matches the
79    # hash recorded in the pyc file.
80    CHECKED_HASH = "checked_hash",
81    # Always use the pyc file, even if the originating source has changed.
82    UNCHECKED_HASH = "unchecked_hash",
83)
84
85def _precompile_source_retention_get_effective_value(ctx):
86    attr_value = ctx.attr.precompile_source_retention
87    if attr_value == PrecompileSourceRetentionAttr.INHERIT:
88        attr_value = PrecompileSourceRetentionFlag.get_effective_value(ctx)
89
90    if attr_value not in (
91        PrecompileSourceRetentionAttr.KEEP_SOURCE,
92        PrecompileSourceRetentionAttr.OMIT_SOURCE,
93        PrecompileSourceRetentionAttr.OMIT_IF_GENERATED_SOURCE,
94    ):
95        fail("Unexpected final precompile_source_retention value: {}".format(repr(attr_value)))
96    return attr_value
97
98# buildifier: disable=name-conventions
99PrecompileSourceRetentionAttr = enum(
100    INHERIT = "inherit",
101    KEEP_SOURCE = "keep_source",
102    OMIT_SOURCE = "omit_source",
103    OMIT_IF_GENERATED_SOURCE = "omit_if_generated_source",
104    get_effective_value = _precompile_source_retention_get_effective_value,
105)
106
107def _pyc_collection_attr_is_pyc_collection_enabled(ctx):
108    pyc_collection = ctx.attr.pyc_collection
109    if pyc_collection == PycCollectionAttr.INHERIT:
110        pyc_collection = ctx.attr._pyc_collection_flag[BuildSettingInfo].value
111
112    if pyc_collection not in (PycCollectionAttr.INCLUDE_PYC, PycCollectionAttr.DISABLED):
113        fail("Unexpected final pyc_collection value: {}".format(repr(pyc_collection)))
114
115    return pyc_collection == PycCollectionAttr.INCLUDE_PYC
116
117# buildifier: disable=name-conventions
118PycCollectionAttr = enum(
119    INHERIT = "inherit",
120    INCLUDE_PYC = "include_pyc",
121    DISABLED = "disabled",
122    is_pyc_collection_enabled = _pyc_collection_attr_is_pyc_collection_enabled,
123)
124
125def create_stamp_attr(**kwargs):
126    return {
127        "stamp": attr.int(
128            values = _STAMP_VALUES,
129            doc = """
130Whether to encode build information into the binary. Possible values:
131
132* `stamp = 1`: Always stamp the build information into the binary, even in
133  `--nostamp` builds. **This setting should be avoided**, since it potentially kills
134  remote caching for the binary and any downstream actions that depend on it.
135* `stamp = 0`: Always replace build information by constant values. This gives
136  good build result caching.
137* `stamp = -1`: Embedding of build information is controlled by the
138  `--[no]stamp` flag.
139
140Stamped binaries are not rebuilt unless their dependencies change.
141
142WARNING: Stamping can harm build performance by reducing cache hits and should
143be avoided if possible.
144""",
145            **kwargs
146        ),
147    }
148
149def create_srcs_attr(*, mandatory):
150    return {
151        "srcs": attr.label_list(
152            # Google builds change the set of allowed files.
153            allow_files = SRCS_ATTR_ALLOW_FILES,
154            mandatory = mandatory,
155            # Necessary for --compile_one_dependency to work.
156            flags = ["DIRECT_COMPILE_TIME_INPUT"],
157            doc = """
158The list of Python source files that are processed to create the target. This
159includes all your checked-in code and may include generated source files.  The
160`.py` files belong in `srcs` and library targets belong in `deps`. Other binary
161files that may be needed at run time belong in `data`.
162""",
163        ),
164    }
165
166SRCS_VERSION_ALL_VALUES = ["PY2", "PY2ONLY", "PY2AND3", "PY3", "PY3ONLY"]
167SRCS_VERSION_NON_CONVERSION_VALUES = ["PY2AND3", "PY2ONLY", "PY3ONLY"]
168
169def create_srcs_version_attr(values):
170    return {
171        "srcs_version": attr.string(
172            default = "PY2AND3",
173            values = values,
174            doc = "Defunct, unused, does nothing.",
175        ),
176    }
177
178def copy_common_binary_kwargs(kwargs):
179    return {
180        key: kwargs[key]
181        for key in BINARY_ATTR_NAMES
182        if key in kwargs
183    }
184
185def copy_common_test_kwargs(kwargs):
186    return {
187        key: kwargs[key]
188        for key in TEST_ATTR_NAMES
189        if key in kwargs
190    }
191
192CC_TOOLCHAIN = {
193    # NOTE: The `cc_helper.find_cpp_toolchain()` function expects the attribute
194    # name to be this name.
195    "_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"),
196}
197
198# The common "data" attribute definition.
199DATA_ATTRS = {
200    # NOTE: The "flags" attribute is deprecated, but there isn't an alternative
201    # way to specify that constraints should be ignored.
202    "data": attr.label_list(
203        allow_files = True,
204        flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
205        doc = """
206The list of files need by this library at runtime. See comments about
207the [`data` attribute typically defined by rules](https://bazel.build/reference/be/common-definitions#typical-attributes).
208
209There is no `py_embed_data` like there is `cc_embed_data` and `go_embed_data`.
210This is because Python has a concept of runtime resources.
211""",
212    ),
213}
214
215def _create_native_rules_allowlist_attrs():
216    if py_internal:
217        # The fragment and name are validated when configuration_field is called
218        default = configuration_field(
219            fragment = "py",
220            name = "native_rules_allowlist",
221        )
222
223        # A None provider isn't allowed
224        providers = [_PackageSpecificationInfo]
225    else:
226        default = None
227        providers = []
228
229    return {
230        "_native_rules_allowlist": attr.label(
231            default = default,
232            providers = providers,
233        ),
234    }
235
236NATIVE_RULES_ALLOWLIST_ATTRS = _create_native_rules_allowlist_attrs()
237
238# Attributes common to all rules.
239COMMON_ATTRS = union_attrs(
240    DATA_ATTRS,
241    NATIVE_RULES_ALLOWLIST_ATTRS,
242    # buildifier: disable=attr-licenses
243    {
244        # NOTE: This attribute is deprecated and slated for removal.
245        "distribs": attr.string_list(),
246        # TODO(b/148103851): This attribute is deprecated and slated for
247        # removal.
248        # NOTE: The license attribute is missing in some Java integration tests,
249        # so fallback to a regular string_list for that case.
250        # buildifier: disable=attr-license
251        "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
252    },
253    allow_none = True,
254)
255
256# Attributes common to rules accepting Python sources and deps.
257PY_SRCS_ATTRS = union_attrs(
258    {
259        "deps": attr.label_list(
260            providers = [
261                [PyInfo],
262                [CcInfo],
263                [BuiltinPyInfo],
264            ],
265            # TODO(b/228692666): Google-specific; remove these allowances once
266            # the depot is cleaned up.
267            allow_rules = DEPS_ATTR_ALLOW_RULES,
268            doc = """
269List of additional libraries to be linked in to the target.
270See comments about
271the [`deps` attribute typically defined by
272rules](https://bazel.build/reference/be/common-definitions#typical-attributes).
273These are typically `py_library` rules.
274
275Targets that only provide data files used at runtime belong in the `data`
276attribute.
277""",
278        ),
279        "precompile": attr.string(
280            doc = """
281Whether py source files **for this target** should be precompiled.
282
283Values:
284
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.
289* `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.
292
293:::{seealso}
294
295* The {flag}`--precompile` flag, which can override this attribute in some cases
296  and will affect all targets when building.
297* The {obj}`pyc_collection` attribute for transitively enabling precompiling on
298  a per-target basis.
299* The [Precompiling](precompiling) docs for a guide about using precompiling.
300:::
301""",
302            default = PrecompileAttr.INHERIT,
303            values = sorted(PrecompileAttr.__members__.values()),
304        ),
305        "precompile_invalidation_mode": attr.string(
306            doc = """
307How precompiled files should be verified to be up-to-date with their associated
308source files. Possible values are:
309* `auto`: The effective value will be automatically determined by other build
310  settings.
311* `checked_hash`: Use the pyc file if the hash of the source file matches the hash
312  recorded in the pyc file. This is most useful when working with code that
313  you may modify.
314* `unchecked_hash`: Always use the pyc file; don't check the pyc's hash against
315  the source file. This is most useful when the code won't be modified.
316
317For more information on pyc invalidation modes, see
318https://docs.python.org/3/library/py_compile.html#py_compile.PycInvalidationMode
319""",
320            default = PrecompileInvalidationModeAttr.AUTO,
321            values = sorted(PrecompileInvalidationModeAttr.__members__.values()),
322        ),
323        "precompile_optimize_level": attr.int(
324            doc = """
325The optimization level for precompiled files.
326
327For more information about optimization levels, see the `compile()` function's
328`optimize` arg docs at https://docs.python.org/3/library/functions.html#compile
329
330NOTE: The value `-1` means "current interpreter", which will be the interpreter
331used _at build time when pycs are generated_, not the interpreter used at
332runtime when the code actually runs.
333""",
334            default = 0,
335        ),
336        "precompile_source_retention": attr.string(
337            default = PrecompileSourceRetentionAttr.INHERIT,
338            values = sorted(PrecompileSourceRetentionAttr.__members__.values()),
339            doc = """
340Determines, when a source file is compiled, if the source file is kept
341in the resulting output or not. Valid values are:
342
343* `inherit`: Inherit the value from the {flag}`--precompile_source_retention` flag.
344* `keep_source`: Include the original Python source.
345* `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.
348""",
349        ),
350        # Required attribute, but details vary by rule.
351        # Use create_srcs_attr to create one.
352        "srcs": None,
353        # NOTE: In Google, this attribute is deprecated, and can only
354        # effectively be PY3 or PY3ONLY. Externally, with Bazel, this attribute
355        # has a separate story.
356        # Required attribute, but the details vary by rule.
357        # Use create_srcs_version_attr to create one.
358        "srcs_version": None,
359        "_precompile_add_to_runfiles_flag": attr.label(
360            default = "//python/config_settings:precompile_add_to_runfiles",
361            providers = [BuildSettingInfo],
362        ),
363        "_precompile_flag": attr.label(
364            default = "//python/config_settings:precompile",
365            providers = [BuildSettingInfo],
366        ),
367        "_precompile_source_retention_flag": attr.label(
368            default = "//python/config_settings:precompile_source_retention",
369            providers = [BuildSettingInfo],
370        ),
371        # Force enabling auto exec groups, see
372        # https://bazel.build/extending/auto-exec-groups#how-enable-particular-rule
373        "_use_auto_exec_groups": attr.bool(default = True),
374    },
375    allow_none = True,
376)
377
378# Attributes specific to Python executable-equivalent rules. Such rules may not
379# accept Python sources (e.g. some packaged-version of a py_test/py_binary), but
380# still accept Python source-agnostic settings.
381AGNOSTIC_EXECUTABLE_ATTRS = union_attrs(
382    DATA_ATTRS,
383    {
384        "env": attr.string_dict(
385            doc = """\
386Dictionary of strings; optional; values are subject to `$(location)` and "Make
387variable" substitution.
388
389Specifies additional environment variables to set when the target is executed by
390`test` or `run`.
391""",
392        ),
393        # The value is required, but varies by rule and/or rule type. Use
394        # create_stamp_attr to create one.
395        "stamp": None,
396    },
397    allow_none = True,
398)
399
400# Attributes specific to Python test-equivalent executable rules. Such rules may
401# not accept Python sources (e.g. some packaged-version of a py_test/py_binary),
402# but still accept Python source-agnostic settings.
403AGNOSTIC_TEST_ATTRS = union_attrs(
404    AGNOSTIC_EXECUTABLE_ATTRS,
405    # Tests have stamping disabled by default.
406    create_stamp_attr(default = 0),
407    {
408        "env_inherit": attr.string_list(
409            doc = """\
410List of strings; optional
411
412Specifies additional environment variables to inherit from the external
413environment when the test is executed by bazel test.
414""",
415        ),
416        # TODO(b/176993122): Remove when Bazel automatically knows to run on darwin.
417        "_apple_constraints": attr.label_list(
418            default = [
419                "@platforms//os:ios",
420                "@platforms//os:macos",
421                "@platforms//os:tvos",
422                "@platforms//os:visionos",
423                "@platforms//os:watchos",
424            ],
425        ),
426    },
427)
428
429# Attributes specific to Python binary-equivalent executable rules. Such rules may
430# not accept Python sources (e.g. some packaged-version of a py_test/py_binary),
431# but still accept Python source-agnostic settings.
432AGNOSTIC_BINARY_ATTRS = union_attrs(
433    AGNOSTIC_EXECUTABLE_ATTRS,
434    create_stamp_attr(default = -1),
435)
436
437# Attribute names common to all Python rules
438COMMON_ATTR_NAMES = [
439    "compatible_with",
440    "deprecation",
441    "distribs",  # NOTE: Currently common to all rules, but slated for removal
442    "exec_compatible_with",
443    "exec_properties",
444    "features",
445    "restricted_to",
446    "tags",
447    "target_compatible_with",
448    # NOTE: The testonly attribute requires careful handling: None/unset means
449    # to use the `package(default_testonly`) value, which isn't observable
450    # during the loading phase.
451    "testonly",
452    "toolchains",
453    "visibility",
454] + list(COMMON_ATTRS)  # Use list() instead .keys() so it's valid Python
455
456# Attribute names common to all test=True rules
457TEST_ATTR_NAMES = COMMON_ATTR_NAMES + [
458    "args",
459    "size",
460    "timeout",
461    "flaky",
462    "shard_count",
463    "local",
464] + list(AGNOSTIC_TEST_ATTRS)  # Use list() instead .keys() so it's valid Python
465
466# Attribute names common to all executable=True rules
467BINARY_ATTR_NAMES = COMMON_ATTR_NAMES + [
468    "args",
469    "output_licenses",  # NOTE: Common to all rules, but slated for removal
470] + list(AGNOSTIC_BINARY_ATTRS)  # Use list() instead .keys() so it's valid Python
471