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