blob: 32a84b3c91d1d6975e3845f24fd71a92a9f290e5 [file]
"""Macro and rule to build the files for a release and list where they are."""
load("//python/private:common_labels.bzl", "labels") # buildifier: disable=bzl-visibility
_FEATURES = "//command_line_option:features"
_RUSTC_FLAGS = "@rules_rust//rust/settings:extra_rustc_flags"
def _release_transition_impl(settings, attr):
features = []
rustc_flags = []
if attr.target_os == "linux":
# Rust links glibc dynamically, so the files would require at least
# the build machine's glibc version. Link it statically so they run on
# older distros too. Rust links musl statically for most CPUs, but not
# all, e.g. riscv64.
rustc_flags = ["-Ctarget-feature=+crt-static"]
if attr.target_libc != "musl":
# The gold linker can't link static glibc, so use bfd.
rustc_flags.append("-Clink-arg=-fuse-ld=bfd")
elif attr.target_os == "windows":
# Statically link the C runtime so that the VC++ redistributable isn't
# required.
features = ["static_link_msvcrt"]
rustc_flags = ["-Ctarget-feature=+crt-static"]
return {
"//command_line_option:compilation_mode": "opt",
# Only affects macOS. Without it, the minimum OS version is the build
# machine's SDK version.
"//command_line_option:macos_minimum_os": "11.0",
_FEATURES: settings[_FEATURES] + features,
_RUSTC_FLAGS: settings[_RUSTC_FLAGS] + rustc_flags,
}
_release_transition = transition(
implementation = _release_transition_impl,
inputs = [_FEATURES, _RUSTC_FLAGS],
outputs = [
"//command_line_option:compilation_mode",
"//command_line_option:macos_minimum_os",
_FEATURES,
_RUSTC_FLAGS,
],
)
def _triple(ctx):
cpu = ctx.attr.target_cpu
os = ctx.attr.target_os
if os == "macos":
return "{}-apple-darwin".format(cpu)
if os == "windows":
return "{}-pc-windows-msvc".format(cpu)
if os == "linux":
if cpu == "armv7":
return "{}-unknown-linux-musleabihf".format(cpu)
if ctx.attr.target_libc == "musl":
return "{}-unknown-linux-musl".format(cpu)
return "{}-unknown-linux-gnu".format(cpu)
fail("Unsupported target_os: {}".format(os))
def _release_files_impl(ctx):
triple = _triple(ctx)
files = []
for src in ctx.files.srcs:
if src.extension:
stem = src.basename[:-len(src.extension) - 1]
ext = "." + src.extension
else:
stem = src.basename
ext = ""
out = ctx.actions.declare_file("{}-{}{}".format(stem, triple, ext))
ctx.actions.symlink(
output = out,
target_file = src,
is_executable = True,
)
files.append(out)
manifest = ctx.actions.declare_file(ctx.label.name + ".txt")
ctx.actions.write(
output = manifest,
# End every line with "\n", including the last: `while read` loops skip
# an unterminated last line, and build.sh adds a line after the paths.
content = "".join([file.path + "\n" for file in files]),
)
return [DefaultInfo(files = depset([manifest] + files))]
_release_files = rule(
implementation = _release_files_impl,
attrs = {
"srcs": attr.label_list(
allow_files = True,
cfg = _release_transition,
doc = "The files to build and list.",
),
"target_cpu": attr.string(
doc = "The CPU that the files are built for. Set by the macro.",
mandatory = True,
),
"target_libc": attr.string(
doc = "The libc that the files are built for. Set by the macro.",
),
"target_os": attr.string(
doc = "The OS that the files are built for. Set by the macro.",
mandatory = True,
),
},
)
def release_files(name, srcs, **kwargs):
"""Builds files for a release and writes their paths to `<name>.txt`.
The files are named `<stem>-<triple>[.<ext>]` and built for the target
platform (`--platforms`), with release settings for its OS and libc:
optimized, and linked so that they run on older OS versions than the build
machine's, without extra runtime libraries.
The paths, one per line, are relative to the execroot, e.g.
`bazel-out/k8-opt/bin/dev/release_artifacts/foo-x86_64-unknown-linux-gnu`.
Paths of generated files also resolve from the workspace root through the
`bazel-out` convenience symlink. This lets scripts find the files without
running `bazel cquery`, which re-analyzes the build.
Args:
name: The target name.
srcs: The files to build and list.
**kwargs: Additional attributes for the rule.
"""
_release_files(
name = name,
srcs = srcs,
target_cpu = select({
Label("@platforms//cpu:aarch64"): "aarch64",
Label("@platforms//cpu:armv7"): "armv7",
Label("@platforms//cpu:ppc64le"): "powerpc64le",
Label("@platforms//cpu:riscv64"): "riscv64gc",
Label("@platforms//cpu:x86_32"): "i686",
Label("@platforms//cpu:x86_64"): "x86_64",
}),
target_libc = select({
"//dev/platforms:musl": "musl",
"//conditions:default": "",
}),
target_os = select({
Label("@platforms//os:linux"): "linux",
labels.PLATFORMS_OS_MACOS: "macos",
labels.PLATFORMS_OS_WINDOWS: "windows",
}),
**kwargs
)
def _platforms_transition_impl(_settings, attr):
# A split transition: one configuration per platform, keyed by its label.
return {
str(platform): {"//command_line_option:platforms": str(platform)}
for platform in attr.platforms
}
_platforms_transition = transition(
implementation = _platforms_transition_impl,
inputs = [],
outputs = ["//command_line_option:platforms"],
)
def _cross_compiled_files_impl(ctx):
# Without platforms, the transition leaves src in this target's
# configuration, so skip it.
if not ctx.attr.platforms:
return [DefaultInfo()]
# With a transition, ctx.attr.src is a list: one target per platform.
files = [src[DefaultInfo].files for src in ctx.attr.src]
return [DefaultInfo(files = depset(transitive = files))]
cross_compiled_files = rule(
implementation = _cross_compiled_files_impl,
doc = """
Builds `src` for each platform in `platforms`, in one build.
`--platforms` selects only one target platform per build. With no platforms,
this builds nothing.
""",
attrs = {
"platforms": attr.label_list(
doc = "The platforms to build `src` for.",
providers = [platform_common.PlatformInfo],
),
"src": attr.label(
cfg = _platforms_transition,
doc = "The target to build.",
mandatory = True,
),
},
)