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