aboutsummaryrefslogtreecommitdiff
path: root/python/config_settings/transition.bzl
diff options
context:
space:
mode:
Diffstat (limited to 'python/config_settings/transition.bzl')
-rw-r--r--python/config_settings/transition.bzl227
1 files changed, 227 insertions, 0 deletions
diff --git a/python/config_settings/transition.bzl b/python/config_settings/transition.bzl
new file mode 100644
index 0000000..20e03dc
--- /dev/null
+++ b/python/config_settings/transition.bzl
@@ -0,0 +1,227 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""The transition module contains the rule definitions to wrap py_binary and py_test and transition
+them to the desired target platform.
+"""
+
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
+load("//python:defs.bzl", _py_binary = "py_binary", _py_test = "py_test")
+load("//python/config_settings/private:py_args.bzl", "py_args")
+
+def _transition_python_version_impl(_, attr):
+ return {"//python/config_settings:python_version": str(attr.python_version)}
+
+_transition_python_version = transition(
+ implementation = _transition_python_version_impl,
+ inputs = [],
+ outputs = ["//python/config_settings:python_version"],
+)
+
+def _transition_py_impl(ctx):
+ target = ctx.attr.target
+ windows_constraint = ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]
+ target_is_windows = ctx.target_platform_has_constraint(windows_constraint)
+ executable = ctx.actions.declare_file(ctx.attr.name + (".exe" if target_is_windows else ""))
+ ctx.actions.symlink(
+ is_executable = True,
+ output = executable,
+ target_file = target[DefaultInfo].files_to_run.executable,
+ )
+ zipfile_symlink = None
+ if target_is_windows:
+ # Under Windows, the expected "<name>.zip" does not exist, so we have to
+ # create the symlink ourselves to achieve the same behaviour as in macOS
+ # and Linux.
+ zipfile = None
+ expected_target_path = target[DefaultInfo].files_to_run.executable.short_path[:-4] + ".zip"
+ for file in target[DefaultInfo].default_runfiles.files.to_list():
+ if file.short_path == expected_target_path:
+ zipfile = file
+ zipfile_symlink = ctx.actions.declare_file(ctx.attr.name + ".zip")
+ ctx.actions.symlink(
+ is_executable = True,
+ output = zipfile_symlink,
+ target_file = zipfile,
+ )
+ env = {}
+ for k, v in ctx.attr.env.items():
+ env[k] = ctx.expand_location(v)
+
+ providers = [
+ DefaultInfo(
+ executable = executable,
+ files = depset([zipfile_symlink] if zipfile_symlink else [], transitive = [target[DefaultInfo].files]),
+ runfiles = ctx.runfiles([zipfile_symlink] if zipfile_symlink else []).merge(target[DefaultInfo].default_runfiles),
+ ),
+ target[PyInfo],
+ target[PyRuntimeInfo],
+ # Ensure that the binary we're wrapping is included in code coverage.
+ coverage_common.instrumented_files_info(
+ ctx,
+ dependency_attributes = ["target"],
+ ),
+ target[OutputGroupInfo],
+ # TODO(f0rmiga): testing.TestEnvironment is deprecated in favour of RunEnvironmentInfo but
+ # RunEnvironmentInfo is not exposed in Bazel < 5.3.
+ # https://github.com/bazelbuild/rules_python/issues/901
+ # https://github.com/bazelbuild/bazel/commit/dbdfa07e92f99497be9c14265611ad2920161483
+ testing.TestEnvironment(env),
+ ]
+ return providers
+
+_COMMON_ATTRS = {
+ "deps": attr.label_list(
+ mandatory = False,
+ ),
+ "env": attr.string_dict(
+ mandatory = False,
+ ),
+ "python_version": attr.string(
+ mandatory = True,
+ ),
+ "srcs": attr.label_list(
+ allow_files = True,
+ mandatory = False,
+ ),
+ "target": attr.label(
+ executable = True,
+ cfg = "target",
+ mandatory = True,
+ providers = [PyInfo],
+ ),
+ # "tools" is a hack here. It should be "data" but "data" is not included by default in the
+ # location expansion in the same way it is in the native Python rules. The difference on how
+ # the Bazel deals with those special attributes differ on the LocationExpander, e.g.:
+ # https://github.com/bazelbuild/bazel/blob/ce611646/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java#L415-L429
+ #
+ # Since the default LocationExpander used by ctx.expand_location is not the same as the native
+ # rules (it doesn't set "allowDataAttributeEntriesInLabel"), we use "tools" temporarily while a
+ # proper fix in Bazel happens.
+ #
+ # A fix for this was proposed in https://github.com/bazelbuild/bazel/pull/16381.
+ "tools": attr.label_list(
+ allow_files = True,
+ mandatory = False,
+ ),
+ # Required to Opt-in to the transitions feature.
+ "_allowlist_function_transition": attr.label(
+ default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
+ ),
+ "_windows_constraint": attr.label(
+ default = "@platforms//os:windows",
+ ),
+}
+
+_transition_py_binary = rule(
+ _transition_py_impl,
+ attrs = _COMMON_ATTRS,
+ cfg = _transition_python_version,
+ executable = True,
+)
+
+_transition_py_test = rule(
+ _transition_py_impl,
+ attrs = _COMMON_ATTRS,
+ cfg = _transition_python_version,
+ test = True,
+)
+
+def _py_rule(rule_impl, transition_rule, name, python_version, **kwargs):
+ pyargs = py_args(name, kwargs)
+ args = pyargs["args"]
+ data = pyargs["data"]
+ env = pyargs["env"]
+ srcs = pyargs["srcs"]
+ deps = pyargs["deps"]
+ main = pyargs["main"]
+
+ # Attributes common to all build rules.
+ # https://bazel.build/reference/be/common-definitions#common-attributes
+ compatible_with = kwargs.pop("compatible_with", None)
+ deprecation = kwargs.pop("deprecation", None)
+ distribs = kwargs.pop("distribs", None)
+ exec_compatible_with = kwargs.pop("exec_compatible_with", None)
+ exec_properties = kwargs.pop("exec_properties", None)
+ features = kwargs.pop("features", None)
+ restricted_to = kwargs.pop("restricted_to", None)
+ tags = kwargs.pop("tags", None)
+ target_compatible_with = kwargs.pop("target_compatible_with", None)
+ testonly = kwargs.pop("testonly", None)
+ toolchains = kwargs.pop("toolchains", None)
+ visibility = kwargs.pop("visibility", None)
+
+ common_attrs = {
+ "compatible_with": compatible_with,
+ "deprecation": deprecation,
+ "distribs": distribs,
+ "exec_compatible_with": exec_compatible_with,
+ "exec_properties": exec_properties,
+ "features": features,
+ "restricted_to": restricted_to,
+ "target_compatible_with": target_compatible_with,
+ "testonly": testonly,
+ "toolchains": toolchains,
+ }
+
+ # Test-specific extra attributes.
+ if "env_inherit" in kwargs:
+ common_attrs["env_inherit"] = kwargs.pop("env_inherit")
+ if "size" in kwargs:
+ common_attrs["size"] = kwargs.pop("size")
+ if "timeout" in kwargs:
+ common_attrs["timeout"] = kwargs.pop("timeout")
+ if "flaky" in kwargs:
+ common_attrs["flaky"] = kwargs.pop("flaky")
+ if "shard_count" in kwargs:
+ common_attrs["shard_count"] = kwargs.pop("shard_count")
+ if "local" in kwargs:
+ common_attrs["local"] = kwargs.pop("local")
+
+ # Binary-specific extra attributes.
+ if "output_licenses" in kwargs:
+ common_attrs["output_licenses"] = kwargs.pop("output_licenses")
+
+ rule_impl(
+ name = "_" + name,
+ args = args,
+ data = data,
+ deps = deps,
+ env = env,
+ srcs = srcs,
+ main = main,
+ tags = ["manual"] + (tags if tags else []),
+ visibility = ["//visibility:private"],
+ **dicts.add(common_attrs, kwargs)
+ )
+
+ return transition_rule(
+ name = name,
+ args = args,
+ deps = deps,
+ env = env,
+ python_version = python_version,
+ srcs = srcs,
+ tags = tags,
+ target = ":_" + name,
+ tools = data,
+ visibility = visibility,
+ **common_attrs
+ )
+
+def py_binary(name, python_version, **kwargs):
+ return _py_rule(_py_binary, _transition_py_binary, name, python_version, **kwargs)
+
+def py_test(name, python_version, **kwargs):
+ return _py_rule(_py_test, _transition_py_test, name, python_version, **kwargs)