1*60517a1eSAndroid Build Coastguard Worker# Copyright 2023 The Bazel Authors. All rights reserved. 2*60517a1eSAndroid Build Coastguard Worker# 3*60517a1eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*60517a1eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*60517a1eSAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*60517a1eSAndroid Build Coastguard Worker# 7*60517a1eSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*60517a1eSAndroid Build Coastguard Worker# 9*60517a1eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*60517a1eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*60517a1eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*60517a1eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*60517a1eSAndroid Build Coastguard Worker# limitations under the License. 14*60517a1eSAndroid Build Coastguard Worker 15*60517a1eSAndroid Build Coastguard Worker"""The transition module contains the rule definitions to wrap py_binary and py_test and transition 16*60517a1eSAndroid Build Coastguard Workerthem to the desired target platform. 17*60517a1eSAndroid Build Coastguard Worker""" 18*60517a1eSAndroid Build Coastguard Worker 19*60517a1eSAndroid Build Coastguard Workerload("@bazel_skylib//lib:dicts.bzl", "dicts") 20*60517a1eSAndroid Build Coastguard Workerload("//python:py_binary.bzl", _py_binary = "py_binary") 21*60517a1eSAndroid Build Coastguard Workerload("//python:py_info.bzl", "PyInfo") 22*60517a1eSAndroid Build Coastguard Workerload("//python:py_runtime_info.bzl", "PyRuntimeInfo") 23*60517a1eSAndroid Build Coastguard Workerload("//python:py_test.bzl", _py_test = "py_test") 24*60517a1eSAndroid Build Coastguard Workerload("//python/config_settings/private:py_args.bzl", "py_args") 25*60517a1eSAndroid Build Coastguard Workerload("//python/private:reexports.bzl", "BuiltinPyInfo", "BuiltinPyRuntimeInfo") 26*60517a1eSAndroid Build Coastguard Worker 27*60517a1eSAndroid Build Coastguard Workerdef _transition_python_version_impl(_, attr): 28*60517a1eSAndroid Build Coastguard Worker return {"//python/config_settings:python_version": str(attr.python_version)} 29*60517a1eSAndroid Build Coastguard Worker 30*60517a1eSAndroid Build Coastguard Worker_transition_python_version = transition( 31*60517a1eSAndroid Build Coastguard Worker implementation = _transition_python_version_impl, 32*60517a1eSAndroid Build Coastguard Worker inputs = [], 33*60517a1eSAndroid Build Coastguard Worker outputs = ["//python/config_settings:python_version"], 34*60517a1eSAndroid Build Coastguard Worker) 35*60517a1eSAndroid Build Coastguard Worker 36*60517a1eSAndroid Build Coastguard Workerdef _transition_py_impl(ctx): 37*60517a1eSAndroid Build Coastguard Worker target = ctx.attr.target 38*60517a1eSAndroid Build Coastguard Worker windows_constraint = ctx.attr._windows_constraint[platform_common.ConstraintValueInfo] 39*60517a1eSAndroid Build Coastguard Worker target_is_windows = ctx.target_platform_has_constraint(windows_constraint) 40*60517a1eSAndroid Build Coastguard Worker executable = ctx.actions.declare_file(ctx.attr.name + (".exe" if target_is_windows else "")) 41*60517a1eSAndroid Build Coastguard Worker ctx.actions.symlink( 42*60517a1eSAndroid Build Coastguard Worker is_executable = True, 43*60517a1eSAndroid Build Coastguard Worker output = executable, 44*60517a1eSAndroid Build Coastguard Worker target_file = target[DefaultInfo].files_to_run.executable, 45*60517a1eSAndroid Build Coastguard Worker ) 46*60517a1eSAndroid Build Coastguard Worker default_outputs = [] 47*60517a1eSAndroid Build Coastguard Worker if target_is_windows: 48*60517a1eSAndroid Build Coastguard Worker # NOTE: Bazel 6 + host=linux + target=windows results in the .exe extension missing 49*60517a1eSAndroid Build Coastguard Worker inner_bootstrap_path = _strip_suffix(target[DefaultInfo].files_to_run.executable.short_path, ".exe") 50*60517a1eSAndroid Build Coastguard Worker inner_bootstrap = None 51*60517a1eSAndroid Build Coastguard Worker inner_zip_file_path = inner_bootstrap_path + ".zip" 52*60517a1eSAndroid Build Coastguard Worker inner_zip_file = None 53*60517a1eSAndroid Build Coastguard Worker for file in target[DefaultInfo].files.to_list(): 54*60517a1eSAndroid Build Coastguard Worker if file.short_path == inner_bootstrap_path: 55*60517a1eSAndroid Build Coastguard Worker inner_bootstrap = file 56*60517a1eSAndroid Build Coastguard Worker elif file.short_path == inner_zip_file_path: 57*60517a1eSAndroid Build Coastguard Worker inner_zip_file = file 58*60517a1eSAndroid Build Coastguard Worker 59*60517a1eSAndroid Build Coastguard Worker # TODO: Use `fragments.py.build_python_zip` once Bazel 6 support is dropped. 60*60517a1eSAndroid Build Coastguard Worker # Which file the Windows .exe looks for depends on the --build_python_zip file. 61*60517a1eSAndroid Build Coastguard Worker # Bazel 7+ has APIs to know the effective value of that flag, but not Bazel 6. 62*60517a1eSAndroid Build Coastguard Worker # To work around this, we treat the existence of a .zip in the default outputs 63*60517a1eSAndroid Build Coastguard Worker # to mean --build_python_zip=true. 64*60517a1eSAndroid Build Coastguard Worker if inner_zip_file: 65*60517a1eSAndroid Build Coastguard Worker suffix = ".zip" 66*60517a1eSAndroid Build Coastguard Worker underlying_launched_file = inner_zip_file 67*60517a1eSAndroid Build Coastguard Worker else: 68*60517a1eSAndroid Build Coastguard Worker suffix = "" 69*60517a1eSAndroid Build Coastguard Worker underlying_launched_file = inner_bootstrap 70*60517a1eSAndroid Build Coastguard Worker 71*60517a1eSAndroid Build Coastguard Worker if underlying_launched_file: 72*60517a1eSAndroid Build Coastguard Worker launched_file_symlink = ctx.actions.declare_file(ctx.attr.name + suffix) 73*60517a1eSAndroid Build Coastguard Worker ctx.actions.symlink( 74*60517a1eSAndroid Build Coastguard Worker is_executable = True, 75*60517a1eSAndroid Build Coastguard Worker output = launched_file_symlink, 76*60517a1eSAndroid Build Coastguard Worker target_file = underlying_launched_file, 77*60517a1eSAndroid Build Coastguard Worker ) 78*60517a1eSAndroid Build Coastguard Worker default_outputs.append(launched_file_symlink) 79*60517a1eSAndroid Build Coastguard Worker 80*60517a1eSAndroid Build Coastguard Worker env = {} 81*60517a1eSAndroid Build Coastguard Worker for k, v in ctx.attr.env.items(): 82*60517a1eSAndroid Build Coastguard Worker env[k] = ctx.expand_location(v) 83*60517a1eSAndroid Build Coastguard Worker 84*60517a1eSAndroid Build Coastguard Worker providers = [ 85*60517a1eSAndroid Build Coastguard Worker DefaultInfo( 86*60517a1eSAndroid Build Coastguard Worker executable = executable, 87*60517a1eSAndroid Build Coastguard Worker files = depset(default_outputs, transitive = [target[DefaultInfo].files]), 88*60517a1eSAndroid Build Coastguard Worker runfiles = ctx.runfiles(default_outputs).merge(target[DefaultInfo].default_runfiles), 89*60517a1eSAndroid Build Coastguard Worker ), 90*60517a1eSAndroid Build Coastguard Worker # Ensure that the binary we're wrapping is included in code coverage. 91*60517a1eSAndroid Build Coastguard Worker coverage_common.instrumented_files_info( 92*60517a1eSAndroid Build Coastguard Worker ctx, 93*60517a1eSAndroid Build Coastguard Worker dependency_attributes = ["target"], 94*60517a1eSAndroid Build Coastguard Worker ), 95*60517a1eSAndroid Build Coastguard Worker target[OutputGroupInfo], 96*60517a1eSAndroid Build Coastguard Worker # TODO(f0rmiga): testing.TestEnvironment is deprecated in favour of RunEnvironmentInfo but 97*60517a1eSAndroid Build Coastguard Worker # RunEnvironmentInfo is not exposed in Bazel < 5.3. 98*60517a1eSAndroid Build Coastguard Worker # https://github.com/bazelbuild/rules_python/issues/901 99*60517a1eSAndroid Build Coastguard Worker # https://github.com/bazelbuild/bazel/commit/dbdfa07e92f99497be9c14265611ad2920161483 100*60517a1eSAndroid Build Coastguard Worker testing.TestEnvironment(env), 101*60517a1eSAndroid Build Coastguard Worker ] 102*60517a1eSAndroid Build Coastguard Worker if PyInfo in target: 103*60517a1eSAndroid Build Coastguard Worker providers.append(target[PyInfo]) 104*60517a1eSAndroid Build Coastguard Worker if BuiltinPyInfo in target and PyInfo != BuiltinPyInfo: 105*60517a1eSAndroid Build Coastguard Worker providers.append(target[BuiltinPyInfo]) 106*60517a1eSAndroid Build Coastguard Worker 107*60517a1eSAndroid Build Coastguard Worker if PyRuntimeInfo in target: 108*60517a1eSAndroid Build Coastguard Worker providers.append(target[PyRuntimeInfo]) 109*60517a1eSAndroid Build Coastguard Worker if BuiltinPyRuntimeInfo in target and PyRuntimeInfo != BuiltinPyRuntimeInfo: 110*60517a1eSAndroid Build Coastguard Worker providers.append(target[BuiltinPyRuntimeInfo]) 111*60517a1eSAndroid Build Coastguard Worker return providers 112*60517a1eSAndroid Build Coastguard Worker 113*60517a1eSAndroid Build Coastguard Worker_COMMON_ATTRS = { 114*60517a1eSAndroid Build Coastguard Worker "deps": attr.label_list( 115*60517a1eSAndroid Build Coastguard Worker mandatory = False, 116*60517a1eSAndroid Build Coastguard Worker ), 117*60517a1eSAndroid Build Coastguard Worker "env": attr.string_dict( 118*60517a1eSAndroid Build Coastguard Worker mandatory = False, 119*60517a1eSAndroid Build Coastguard Worker ), 120*60517a1eSAndroid Build Coastguard Worker "python_version": attr.string( 121*60517a1eSAndroid Build Coastguard Worker mandatory = True, 122*60517a1eSAndroid Build Coastguard Worker ), 123*60517a1eSAndroid Build Coastguard Worker "srcs": attr.label_list( 124*60517a1eSAndroid Build Coastguard Worker allow_files = True, 125*60517a1eSAndroid Build Coastguard Worker mandatory = False, 126*60517a1eSAndroid Build Coastguard Worker ), 127*60517a1eSAndroid Build Coastguard Worker "target": attr.label( 128*60517a1eSAndroid Build Coastguard Worker executable = True, 129*60517a1eSAndroid Build Coastguard Worker cfg = "target", 130*60517a1eSAndroid Build Coastguard Worker mandatory = True, 131*60517a1eSAndroid Build Coastguard Worker providers = [PyInfo], 132*60517a1eSAndroid Build Coastguard Worker ), 133*60517a1eSAndroid Build Coastguard Worker # "tools" is a hack here. It should be "data" but "data" is not included by default in the 134*60517a1eSAndroid Build Coastguard Worker # location expansion in the same way it is in the native Python rules. The difference on how 135*60517a1eSAndroid Build Coastguard Worker # the Bazel deals with those special attributes differ on the LocationExpander, e.g.: 136*60517a1eSAndroid Build Coastguard Worker # https://github.com/bazelbuild/bazel/blob/ce611646/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java#L415-L429 137*60517a1eSAndroid Build Coastguard Worker # 138*60517a1eSAndroid Build Coastguard Worker # Since the default LocationExpander used by ctx.expand_location is not the same as the native 139*60517a1eSAndroid Build Coastguard Worker # rules (it doesn't set "allowDataAttributeEntriesInLabel"), we use "tools" temporarily while a 140*60517a1eSAndroid Build Coastguard Worker # proper fix in Bazel happens. 141*60517a1eSAndroid Build Coastguard Worker # 142*60517a1eSAndroid Build Coastguard Worker # A fix for this was proposed in https://github.com/bazelbuild/bazel/pull/16381. 143*60517a1eSAndroid Build Coastguard Worker "tools": attr.label_list( 144*60517a1eSAndroid Build Coastguard Worker allow_files = True, 145*60517a1eSAndroid Build Coastguard Worker mandatory = False, 146*60517a1eSAndroid Build Coastguard Worker ), 147*60517a1eSAndroid Build Coastguard Worker # Required to Opt-in to the transitions feature. 148*60517a1eSAndroid Build Coastguard Worker "_allowlist_function_transition": attr.label( 149*60517a1eSAndroid Build Coastguard Worker default = "@bazel_tools//tools/allowlists/function_transition_allowlist", 150*60517a1eSAndroid Build Coastguard Worker ), 151*60517a1eSAndroid Build Coastguard Worker "_windows_constraint": attr.label( 152*60517a1eSAndroid Build Coastguard Worker default = "@platforms//os:windows", 153*60517a1eSAndroid Build Coastguard Worker ), 154*60517a1eSAndroid Build Coastguard Worker} 155*60517a1eSAndroid Build Coastguard Worker 156*60517a1eSAndroid Build Coastguard Worker_PY_TEST_ATTRS = { 157*60517a1eSAndroid Build Coastguard Worker # Magic attribute to help C++ coverage work. There's no 158*60517a1eSAndroid Build Coastguard Worker # docs about this; see TestActionBuilder.java 159*60517a1eSAndroid Build Coastguard Worker "_collect_cc_coverage": attr.label( 160*60517a1eSAndroid Build Coastguard Worker default = "@bazel_tools//tools/test:collect_cc_coverage", 161*60517a1eSAndroid Build Coastguard Worker executable = True, 162*60517a1eSAndroid Build Coastguard Worker cfg = "exec", 163*60517a1eSAndroid Build Coastguard Worker ), 164*60517a1eSAndroid Build Coastguard Worker # Magic attribute to make coverage work. There's no 165*60517a1eSAndroid Build Coastguard Worker # docs about this; see TestActionBuilder.java 166*60517a1eSAndroid Build Coastguard Worker "_lcov_merger": attr.label( 167*60517a1eSAndroid Build Coastguard Worker default = configuration_field(fragment = "coverage", name = "output_generator"), 168*60517a1eSAndroid Build Coastguard Worker executable = True, 169*60517a1eSAndroid Build Coastguard Worker cfg = "exec", 170*60517a1eSAndroid Build Coastguard Worker ), 171*60517a1eSAndroid Build Coastguard Worker} 172*60517a1eSAndroid Build Coastguard Worker 173*60517a1eSAndroid Build Coastguard Worker_transition_py_binary = rule( 174*60517a1eSAndroid Build Coastguard Worker _transition_py_impl, 175*60517a1eSAndroid Build Coastguard Worker attrs = _COMMON_ATTRS | _PY_TEST_ATTRS, 176*60517a1eSAndroid Build Coastguard Worker cfg = _transition_python_version, 177*60517a1eSAndroid Build Coastguard Worker executable = True, 178*60517a1eSAndroid Build Coastguard Worker fragments = ["py"], 179*60517a1eSAndroid Build Coastguard Worker) 180*60517a1eSAndroid Build Coastguard Worker 181*60517a1eSAndroid Build Coastguard Worker_transition_py_test = rule( 182*60517a1eSAndroid Build Coastguard Worker _transition_py_impl, 183*60517a1eSAndroid Build Coastguard Worker attrs = _COMMON_ATTRS | _PY_TEST_ATTRS, 184*60517a1eSAndroid Build Coastguard Worker cfg = _transition_python_version, 185*60517a1eSAndroid Build Coastguard Worker test = True, 186*60517a1eSAndroid Build Coastguard Worker fragments = ["py"], 187*60517a1eSAndroid Build Coastguard Worker) 188*60517a1eSAndroid Build Coastguard Worker 189*60517a1eSAndroid Build Coastguard Workerdef _py_rule(rule_impl, transition_rule, name, python_version, **kwargs): 190*60517a1eSAndroid Build Coastguard Worker pyargs = py_args(name, kwargs) 191*60517a1eSAndroid Build Coastguard Worker args = pyargs["args"] 192*60517a1eSAndroid Build Coastguard Worker data = pyargs["data"] 193*60517a1eSAndroid Build Coastguard Worker env = pyargs["env"] 194*60517a1eSAndroid Build Coastguard Worker srcs = pyargs["srcs"] 195*60517a1eSAndroid Build Coastguard Worker deps = pyargs["deps"] 196*60517a1eSAndroid Build Coastguard Worker main = pyargs["main"] 197*60517a1eSAndroid Build Coastguard Worker 198*60517a1eSAndroid Build Coastguard Worker # Attributes common to all build rules. 199*60517a1eSAndroid Build Coastguard Worker # https://bazel.build/reference/be/common-definitions#common-attributes 200*60517a1eSAndroid Build Coastguard Worker compatible_with = kwargs.pop("compatible_with", None) 201*60517a1eSAndroid Build Coastguard Worker deprecation = kwargs.pop("deprecation", None) 202*60517a1eSAndroid Build Coastguard Worker exec_compatible_with = kwargs.pop("exec_compatible_with", None) 203*60517a1eSAndroid Build Coastguard Worker exec_properties = kwargs.pop("exec_properties", None) 204*60517a1eSAndroid Build Coastguard Worker features = kwargs.pop("features", None) 205*60517a1eSAndroid Build Coastguard Worker restricted_to = kwargs.pop("restricted_to", None) 206*60517a1eSAndroid Build Coastguard Worker tags = kwargs.pop("tags", None) 207*60517a1eSAndroid Build Coastguard Worker target_compatible_with = kwargs.pop("target_compatible_with", None) 208*60517a1eSAndroid Build Coastguard Worker testonly = kwargs.pop("testonly", None) 209*60517a1eSAndroid Build Coastguard Worker toolchains = kwargs.pop("toolchains", None) 210*60517a1eSAndroid Build Coastguard Worker visibility = kwargs.pop("visibility", None) 211*60517a1eSAndroid Build Coastguard Worker 212*60517a1eSAndroid Build Coastguard Worker common_attrs = { 213*60517a1eSAndroid Build Coastguard Worker "compatible_with": compatible_with, 214*60517a1eSAndroid Build Coastguard Worker "deprecation": deprecation, 215*60517a1eSAndroid Build Coastguard Worker "exec_compatible_with": exec_compatible_with, 216*60517a1eSAndroid Build Coastguard Worker "exec_properties": exec_properties, 217*60517a1eSAndroid Build Coastguard Worker "features": features, 218*60517a1eSAndroid Build Coastguard Worker "restricted_to": restricted_to, 219*60517a1eSAndroid Build Coastguard Worker "target_compatible_with": target_compatible_with, 220*60517a1eSAndroid Build Coastguard Worker "testonly": testonly, 221*60517a1eSAndroid Build Coastguard Worker "toolchains": toolchains, 222*60517a1eSAndroid Build Coastguard Worker } 223*60517a1eSAndroid Build Coastguard Worker 224*60517a1eSAndroid Build Coastguard Worker # Test-specific extra attributes. 225*60517a1eSAndroid Build Coastguard Worker if "env_inherit" in kwargs: 226*60517a1eSAndroid Build Coastguard Worker common_attrs["env_inherit"] = kwargs.pop("env_inherit") 227*60517a1eSAndroid Build Coastguard Worker if "size" in kwargs: 228*60517a1eSAndroid Build Coastguard Worker common_attrs["size"] = kwargs.pop("size") 229*60517a1eSAndroid Build Coastguard Worker if "timeout" in kwargs: 230*60517a1eSAndroid Build Coastguard Worker common_attrs["timeout"] = kwargs.pop("timeout") 231*60517a1eSAndroid Build Coastguard Worker if "flaky" in kwargs: 232*60517a1eSAndroid Build Coastguard Worker common_attrs["flaky"] = kwargs.pop("flaky") 233*60517a1eSAndroid Build Coastguard Worker if "shard_count" in kwargs: 234*60517a1eSAndroid Build Coastguard Worker common_attrs["shard_count"] = kwargs.pop("shard_count") 235*60517a1eSAndroid Build Coastguard Worker if "local" in kwargs: 236*60517a1eSAndroid Build Coastguard Worker common_attrs["local"] = kwargs.pop("local") 237*60517a1eSAndroid Build Coastguard Worker 238*60517a1eSAndroid Build Coastguard Worker # Binary-specific extra attributes. 239*60517a1eSAndroid Build Coastguard Worker if "output_licenses" in kwargs: 240*60517a1eSAndroid Build Coastguard Worker common_attrs["output_licenses"] = kwargs.pop("output_licenses") 241*60517a1eSAndroid Build Coastguard Worker 242*60517a1eSAndroid Build Coastguard Worker rule_impl( 243*60517a1eSAndroid Build Coastguard Worker name = "_" + name, 244*60517a1eSAndroid Build Coastguard Worker args = args, 245*60517a1eSAndroid Build Coastguard Worker data = data, 246*60517a1eSAndroid Build Coastguard Worker deps = deps, 247*60517a1eSAndroid Build Coastguard Worker env = env, 248*60517a1eSAndroid Build Coastguard Worker srcs = srcs, 249*60517a1eSAndroid Build Coastguard Worker main = main, 250*60517a1eSAndroid Build Coastguard Worker tags = ["manual"] + (tags if tags else []), 251*60517a1eSAndroid Build Coastguard Worker visibility = ["//visibility:private"], 252*60517a1eSAndroid Build Coastguard Worker **dicts.add(common_attrs, kwargs) 253*60517a1eSAndroid Build Coastguard Worker ) 254*60517a1eSAndroid Build Coastguard Worker 255*60517a1eSAndroid Build Coastguard Worker return transition_rule( 256*60517a1eSAndroid Build Coastguard Worker name = name, 257*60517a1eSAndroid Build Coastguard Worker args = args, 258*60517a1eSAndroid Build Coastguard Worker deps = deps, 259*60517a1eSAndroid Build Coastguard Worker env = env, 260*60517a1eSAndroid Build Coastguard Worker python_version = python_version, 261*60517a1eSAndroid Build Coastguard Worker srcs = srcs, 262*60517a1eSAndroid Build Coastguard Worker tags = tags, 263*60517a1eSAndroid Build Coastguard Worker target = ":_" + name, 264*60517a1eSAndroid Build Coastguard Worker tools = data, 265*60517a1eSAndroid Build Coastguard Worker visibility = visibility, 266*60517a1eSAndroid Build Coastguard Worker **common_attrs 267*60517a1eSAndroid Build Coastguard Worker ) 268*60517a1eSAndroid Build Coastguard Worker 269*60517a1eSAndroid Build Coastguard Workerdef py_binary(name, python_version, **kwargs): 270*60517a1eSAndroid Build Coastguard Worker return _py_rule(_py_binary, _transition_py_binary, name, python_version, **kwargs) 271*60517a1eSAndroid Build Coastguard Worker 272*60517a1eSAndroid Build Coastguard Workerdef py_test(name, python_version, **kwargs): 273*60517a1eSAndroid Build Coastguard Worker return _py_rule(_py_test, _transition_py_test, name, python_version, **kwargs) 274*60517a1eSAndroid Build Coastguard Worker 275*60517a1eSAndroid Build Coastguard Workerdef _strip_suffix(s, suffix): 276*60517a1eSAndroid Build Coastguard Worker if s.endswith(suffix): 277*60517a1eSAndroid Build Coastguard Worker return s[:-len(suffix)] 278*60517a1eSAndroid Build Coastguard Worker else: 279*60517a1eSAndroid Build Coastguard Worker return s 280