| """Rules to create python distribution files and properly name them""" | |
| load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") | |
| load("@system_python//:version.bzl", "SYSTEM_PYTHON_VERSION") | |
| def _get_os_name(ctx): | |
| for name, label in ctx.attr._os_constraints.items(): | |
| if ctx.target_platform_has_constraint(label[platform_common.ConstraintValueInfo]): | |
| return name | |
| fail("Protobuf: Unknown OS for target platform") | |
| def _get_cpu_name(ctx): | |
| for name, label in ctx.attr._cpu_constraints.items(): | |
| if ctx.target_platform_has_constraint(label[platform_common.ConstraintValueInfo]): | |
| return name | |
| fail("Protobuf: Unknown CPU for target platform") | |
| def _get_suffix(ctx, limited_api, python_version): | |
| """Computes an ABI version tag for an extension module per PEP 3149.""" | |
| os = _get_os_name(ctx) | |
| cpu = _get_cpu_name(ctx) | |
| if os == "windows": | |
| if limited_api: | |
| return ".pyd" | |
| if cpu == "x86_32": | |
| abi = "win32" | |
| elif cpu == "x86_64": | |
| abi = "win_amd64" | |
| else: | |
| fail("Unsupported CPU: " + cpu) | |
| return ".cp{}-{}.{}".format(python_version, abi, "pyd") | |
| if python_version == "system": | |
| python_version = SYSTEM_PYTHON_VERSION | |
| if int(python_version) < 38: | |
| python_version += "m" | |
| if os == "osx": | |
| abi = "darwin" | |
| elif os == "linux": | |
| if cpu == "x86_64": | |
| abi = "x86_64-linux-gnu" | |
| elif cpu == "aarch64": | |
| abi = "aarch64-linux-gnu" | |
| elif cpu == "s390x": | |
| abi = "s390x-linux-gnu" | |
| else: | |
| fail("Unsupported CPU: " + cpu) | |
| else: | |
| fail("Unsupported OS: " + os) | |
| return ".cpython-{}-{}.{}".format( | |
| python_version, | |
| abi, | |
| "so" if limited_api else "abi3.so", | |
| ) | |
| elif limited_api: | |
| return ".abi3.so" | |
| fail("Unsupported combination of flags") | |
| def _declare_module_file(ctx, module_name, python_version, limited_api): | |
| """Declares an output file for a Python module with this name, version, and limited api.""" | |
| base_filename = module_name.replace(".", "/") | |
| suffix = _get_suffix( | |
| ctx = ctx, | |
| python_version = python_version, | |
| limited_api = limited_api, | |
| ) | |
| filename = base_filename + suffix | |
| return ctx.actions.declare_file(filename) | |
| # -------------------------------------------------------------------------------------------------- | |
| # py_dist_module() | |
| # | |
| # Creates a Python binary extension module that is ready for distribution. | |
| # | |
| # py_dist_module( | |
| # name = "message_mod", | |
| # extension = "//python:_message_binary", | |
| # module_name = "google._upb._message", | |
| # ) | |
| # | |
| # In the simple case, this simply involves copying the input file to the proper filename for | |
| # our current configuration (module_name, cpu, python_version, limited_abi). | |
| # | |
| # For multiarch platforms (osx-universal2), we must combine binaries for multiple architectures | |
| # into a single output binary using the "llvm-lipo" tool. A config transition depends on multiple | |
| # architectures to get us the input files we need. | |
| def _py_multiarch_transition_impl(settings, attr): | |
| if len(settings["//command_line_option:platforms"]) == 1 and settings["//command_line_option:platforms"][0] == Label("//build_defs:osx-universal2"): | |
| ret = [ | |
| {"//command_line_option:platforms": platform} | |
| for platform in [ | |
| "//build_defs:osx-aarch_64", | |
| "//build_defs:osx-x86_64", | |
| ] | |
| ] | |
| return ret | |
| else: | |
| return settings | |
| _py_multiarch_transition = transition( | |
| implementation = _py_multiarch_transition_impl, | |
| inputs = ["//command_line_option:platforms"], | |
| outputs = ["//command_line_option:platforms"], | |
| ) | |
| def _py_dist_module_impl(ctx): | |
| output_file = _declare_module_file( | |
| ctx = ctx, | |
| module_name = ctx.attr.module_name, | |
| python_version = ctx.attr._python_version[BuildSettingInfo].value, | |
| limited_api = ctx.attr._limited_api[BuildSettingInfo].value, | |
| ) | |
| if len(ctx.attr.extension) == 1: | |
| src = ctx.attr.extension[0][DefaultInfo].files.to_list()[0] | |
| ctx.actions.run( | |
| mnemonic = "PyDistModule", | |
| executable = "cp", | |
| arguments = [src.path, output_file.path], | |
| inputs = [src], | |
| outputs = [output_file], | |
| ) | |
| return [ | |
| DefaultInfo(files = depset([output_file])), | |
| ] | |
| else: | |
| srcs = [mod[DefaultInfo].files.to_list()[0] for mod in ctx.attr.extension] | |
| ctx.actions.run( | |
| mnemonic = "PyDistModule", | |
| executable = "/usr/local/bin/llvm-lipo", | |
| arguments = ["-create", "-output", output_file.path] + [src.path for src in srcs], | |
| inputs = srcs, | |
| outputs = [output_file], | |
| ) | |
| return [ | |
| DefaultInfo(files = depset([output_file])), | |
| ] | |
| py_dist_module = rule( | |
| implementation = _py_dist_module_impl, | |
| attrs = { | |
| "module_name": attr.string(mandatory = True), | |
| "extension": attr.label( | |
| mandatory = True, | |
| cfg = _py_multiarch_transition, | |
| ), | |
| "_limited_api": attr.label(default = "//python:limited_api"), | |
| "_python_version": attr.label(default = "//python:python_version"), | |
| "_allowlist_function_transition": attr.label( | |
| default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | |
| ), | |
| "_os_constraints": attr.string_keyed_label_dict( | |
| default = { | |
| "osx": "@platforms//os:osx", | |
| "windows": "@platforms//os:windows", | |
| "linux": "@platforms//os:linux", | |
| }, | |
| ), | |
| "_cpu_constraints": attr.string_keyed_label_dict( | |
| default = { | |
| "aarch64": "@platforms//cpu:aarch64", | |
| "x86_64": "@platforms//cpu:x86_64", | |
| "x86_32": "@platforms//cpu:x86_32", | |
| "s390x": "@platforms//cpu:s390x", | |
| "ppc64le": "@platforms//cpu:ppc64le", | |
| }, | |
| ), | |
| }, | |
| ) | |
| # -------------------------------------------------------------------------------------------------- | |
| # py_dist() | |
| # | |
| # A rule that builds a collection of binary wheels, using transitions to depend on many different | |
| # python versions and cpus. | |
| def _py_dist_transition_impl(settings, attr): | |
| _ignore = (settings) # @unused | |
| transitions = [] | |
| for platform, version in attr.limited_api_platforms.items(): | |
| transitions.append({ | |
| "//command_line_option:platforms": platform, | |
| "//python:python_version": version, | |
| "//python:limited_api": True, | |
| }) | |
| for version in attr.full_api_versions: | |
| for platform in attr.full_api_platforms: | |
| transitions.append({ | |
| "//command_line_option:platforms": platform, | |
| "//python:python_version": version, | |
| "//python:limited_api": False, | |
| }) | |
| return transitions | |
| _py_dist_transition = transition( | |
| implementation = _py_dist_transition_impl, | |
| inputs = [], | |
| outputs = [ | |
| "//command_line_option:platforms", | |
| "//python:python_version", | |
| "//python:limited_api", | |
| ], | |
| ) | |
| def _py_dist_impl(ctx): | |
| binary_files = [dep[DefaultInfo].files for dep in ctx.attr.binary_wheel] | |
| pure_python_files = [ctx.attr.pure_python_wheel[DefaultInfo].files] | |
| return [ | |
| DefaultInfo(files = depset( | |
| transitive = binary_files + pure_python_files, | |
| )), | |
| ] | |
| py_dist = rule( | |
| implementation = _py_dist_impl, | |
| attrs = { | |
| "binary_wheel": attr.label( | |
| mandatory = True, | |
| cfg = _py_dist_transition, | |
| ), | |
| "pure_python_wheel": attr.label(mandatory = True), | |
| "limited_api_platforms": attr.string_dict(), | |
| "full_api_versions": attr.string_list(), | |
| "full_api_platforms": attr.string_list(), | |
| "_allowlist_function_transition": attr.label( | |
| default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | |
| ), | |
| }, | |
| ) |