blob: 2aca2f18682062df48f1ff220f0513094b0d0521 [file] [log] [blame] [edit]
"""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(
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(
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",
),
},
)