Separate compile and linking actions with Bazel

Viewed 637

I would like to know if there is a way to execute compiling and linking actions with Bazel in separate steps. If i execute the command

bazel build -s //main:hello-world

all the bazel SUBCOMMANDS are printed. I am aware that is possible to execute these subcommands from command line, but I would like to know how to use Bazel to execute for instance only the compile action, and only the linking action in separate steps. Thank you in advance for any help!

4 Answers

Not sure what exactly you are after, but essentially, you can write a custom rule you pass cc_* target as a source into and try to pick its actions apart only getting outputs of the ones you are interest in, e.g.:

def _impl(ctx):
    outs = []
    for src in ctx.attr.srcs:
        if hasattr(src, "actions"):
            for action in src.actions:
                if action.mnemonic == "CppCompile":
                    outs.append(action.outputs)
    return DefaultInfo(files = depset(transitive = outs))

just_compile = rule(
    _impl,
    attrs = {
        "srcs": attr.label_list(mandatory = True),
    },

)      

If I then have the following BUILD file:

load("//:just_compile.bzl", "just_compile")

just_compile(
    name = "compile_hello",
    srcs = [":hello"],
)

cc_binary(
    name = "hello",
    srcs = ["hello.cpp"],
)

And you run (you can add -s to watch):

bazel build compile_hello

You will get just the compilation step executed (because we did not actually request hello target and there is nothing else needing linking in the tree as requested) and results it produced:

bazel-bin/
bazel-bin/_objs
bazel-bin/_objs/hello
bazel-bin/_objs/hello/hello.pic.d
bazel-bin/_objs/hello/hello.pic.o

Linking mnemonic is CppLink.

So, yes, you can attach yourself to specific actions. And for the sake of debate, this would be at least one option I am aware to do that. But again, I am not entirely sure what exactly are you actually after, what problem are you trying to solve.

You can do the compilation pretty easily with a custom rule. You'll need to use the cc_common APIs to do the normal things that cc_library does, but returning all the files in the DefaultInfo.files provider.

For linking, I'd just use cc_binary. You can also do something similar with a custom rule to split out the pieces if you want.

Something like this:

load("@rules_cc//cc:action_names.bzl", "C_COMPILE_ACTION_NAME")
load("@rules_cc//cc:toolchain_utils.bzl", "find_cpp_toolchain")

def _my_c_compile_impl(ctx):
    cc_toolchain = find_cpp_toolchain(ctx)
    source_file = ctx.file.src
    output_file = ctx.actions.declare_file(ctx.label.name + ".o")
    feature_configuration = cc_common.configure_features(
        ctx = ctx,
        cc_toolchain = cc_toolchain,
        requested_features = ctx.features,
        unsupported_features = DISABLED_FEATURES + ctx.disabled_features,
    )
    _, outputs = cc_common.compile(
        actions = ctx.actions,
        cc_toolchain = cc_toolchain,
        srcs = [source_file],
        name = ctx.label.name,
    )
    return [
        DefaultInfo(files = depset(direct = outputs.objects)),
    ]

my_c_compile = rule(
    implementation = _my_c_compile_impl,
    attrs = {
        "src": attr.label(mandatory = True, allow_single_file = True),
        "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
    },
    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
    incompatible_use_toolchain_transition = True,
    fragments = ["cpp"],
)

The rules_cc my_c_archive example is a good starting point if you want to customize things more than cc_common.compile allows.

If you want to make this a general debugging tool, you can package similar logic as an aspect to apply it to arbitrary rules.

For quick debugging, --save_temps is another technique.

I think it is not possible to do compilation and linking in two separate steps with Bazel with the default rules_cc.

The closest what you can get is.

There is a --nobuild option thta causes the build to stop before executing the build actions (e.g. compiling, linking, etc.)

You can use aquery to query for actions in your build graph.

This will give you similar information as -s does, but without actually calling the commands.

Another approach could be to come up with own rules, e.g.

cc_binary_compilation(
    name = "...",
    srcs = [...]
)

cc_binary_linking(...)

You can specify to only receive a certain output group. So to look at what output groups there are use:

bazel cquery //my_target --output=starlark --starlark:expr="[str(og) for og in target.output_groups]"

Then to build only that output group this one is for c++ compilation only (no link):

bazel build //my_target --output_groups=compilation_outputs

The trick is to not use + or - in the output_groups this modifies the default output group.

Related