How to use Bazel platform transitions in a Go Gazelle project

737 Views Asked by At

I have a (pure) Go project set up using Gazelle. It has a main binary, its cmd/main/BUILD generated by Gazelle looks like this:

load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")

go_library(
    name = "main_lib",
    srcs = ["main.go"],
    importpath = "github.com/me/myrepo/....",
    visibility = ["//visibility:private"],
    deps = [
        "//pkg/api",
        "//pkg/archive",
        ...
    ],
)

go_binary(
    name = "main",
    embed = [":main_lib"],
    visibility = ["//visibility:public"],
)

I can build the binary for my host system, or for our deploy target by passing --platforms=@io_bazel_rules_go//go/toolchain:linux_arm64 on the command line. But now, I want to be able to create a tar containing builds for both platforms without calling Bazel with different CLI arguments.

As recommended here ("You can equivalently depend on a go_binary or go_test rule through a Bazel configuration transition on //command_line_option:platforms"), I am trying to set this up using transitions.

My transitions.bzl file:

# build opts for development machine
def _host_transition_impl(settings, attr):
    _ignore = (settings, attr)
    return {
        "//command_line_option:platforms": "@io_bazel_rules_go//go/toolchain:linux_amd64",
        "//command_line_option:compilation_mode": "fastbuild",
    }

# build opts for deployment target
def _target_transition_impl(settings, attr):
    _ignore = (settings, attr)
    return {
        "//command_line_option:platforms": "@io_bazel_rules_go//go/toolchain:linux_arm64",
        "//command_line_option:compilation_mode": "opt",
    }

host_transition = transition(
    implementation = _host_transition_impl,
    inputs = [],
    outputs = ["//command_line_option:platforms", "//command_line_option:compilation_mode"],
)

target_transition = transition(
    implementation = _target_transition_impl,
    inputs = [],
    outputs = ["//command_line_option:platforms", "//command_line_option:compilation_mode"],
)

def _impl(ctx):
    return []

host_transitioning_rule = rule(
    implementation = _impl,
    cfg = host_transition,
    attrs = {
        "_allowlist_function_transition": attr.label(
            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
        ),
    },
)

target_transitioning_rule = rule(
    implementation = _impl,
    cfg = target_transition,
    attrs = {
        "_allowlist_function_transition": attr.label(
            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
        ),
    },
)

But now, I don't know how to attach the transitions to the go_binary rule that Gazelle generated. My root BUILD file:

load("@bazel_gazelle//:def.bzl", "gazelle")

load(":transitions.bzl", "host_transitioning_rule", "target_transitioning_rule")

load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar")

# gazelle:prefix github.com/me/mypath...
gazelle(name = "gazelle")

gazelle(
    name = "gazelle-update-repos",
    args = [
        "-from_file=go.mod",
        "-to_macro=deps.bzl%go_dependencies",
        "-prune",
        "-build_file_proto_mode=disable_global",
    ],
    command = "update-repos",
)

host_transitioning_rule(
    name = "mainHost",
    # TODO: how to attach this to //cmd/main:main
)

target_transitioning_rule(
    name = "mainTarget",
    # TODO: how to attach this to //cmd/main:main
)

pkg_tar(
    name = "release",
    srcs = [
        ":mainHost",
        ":mainTarget",
    ],
    package_dir = "lib",
)

1

There are 1 best solutions below

4
On

Just forward the relevant providers from some other rule. FilesToRunProvider and DefaultInfo are usually the main ones which matter. You're effectively recreating alias with your transition attached on the outgoing edge. Something like this:

def _impl(ctx):
    providers = []
    if DefaultInfo in ctx.attr.dep:
        providers.append(ctx.attr.dep[DefaultInfo])
    if FilesToRunProvider in ctx.attr.dep:
        providers.append(ctx.attr.dep[FilesToRunProvider])
    return providers

host_transitioning_rule = rule(
    implementation = _impl,
    attrs = {
        "_allowlist_function_transition": attr.label(
            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
        ),
        "dep": attr.label(cfg = host_transition),
    },
)

and then use it like:

host_transitioning_rule(
    name = "mainHost",
    dep = "//cmd/main:main",
)

You tried attaching the transition on the incoming edge. That changes which configuration the rule itself is. Doesn't really make a difference in this case, because the rule only has the single dependency, but if you wanted to combine one dependency in the host configuration and one in the target (for example) then you would need to use outgoing edge transitions on separate attrs.

Instead of looking for specific providers, you could iterate over all the providers like examples/blob/main/rules/attributes/printer.bzl and forward them all. That can get a bit tricky with some providers though, so I would avoid it. For example, if you just blindly forward GoLibrary when cross compiling you'll end up trying to link libraries compiled for different architectures, which won't work.