I want the output binary to know the details of the build process: time of the build and build target. How can I add an additional step to bazel build process to generate a file with this info and attach this file to build output?
Bazel calls this "build stamping". See https://bazel.build/docs/user-manual#workspace-status.
The build timestamp is part of the "volatile status". As an example:
main.c
:
#include <stdio.h>
#include "build_info.h"
int main() {
printf("build timestamp is %d\n", BUILD_TIMESTAMP);
return 0;
}
BUILD
:
cc_binary(
name = "main",
srcs = [
"main.c",
":build_info.h",
],
)
genrule(
name = "gen_build_info",
outs = ["build_info.h"],
stamp = True,
cmd = """
echo stable status:
cat bazel-out/stable-status.txt
echo volatile status:
cat bazel-out/volatile-status.txt
BUILD_TIMESTAMP="$$(cat bazel-out/volatile-status.txt | grep BUILD_TIMESTAMP)"
echo "#define $$BUILD_TIMESTAMP" > $@
""",
)
usage:
$ bazel run main
Starting local Bazel server and connecting to it...
INFO: Analyzed target //:main (87 packages loaded, 404 targets configured).
INFO: From Executing genrule //:gen_build_info:
stable status:
BUILD_EMBED_LABEL
BUILD_HOST ...
BUILD_USER ...
volatile status:
BUILD_TIMESTAMP 1727191789
FORMATTED_DATE 2024 Sep 24 15 29 49 Tue
INFO: Found 1 target...
Target //:main up-to-date:
bazel-bin/main
INFO: Elapsed time: 5.287s, Critical Path: 0.25s
INFO: 8 processes: 5 internal, 3 linux-sandbox.
INFO: Build completed successfully, 8 total actions
INFO: Running command line: bazel-bin/main
build timestamp is 1727191789
Embedding the label is trickier.
As a special case, the deploy jar of java_binary
embeds the label in a build-data.properties
file at the root of the jar:
Main.java
:
package main;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.nio.file.Files;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader buf = new BufferedReader(
new InputStreamReader(Main.class.getResourceAsStream("/build-data.properties")));
buf.lines().forEach(System.out::println);
}
}
BUILD
:
java_binary(
name = "java_main",
srcs = ["Main.java"],
main_class = "main.Main",
)
usage:
$ bazel run java_main_deploy.jar
INFO: Analyzed target //:java_main_deploy.jar (9 packages loaded, 1158 targets configured).
INFO: From Building java_main.jar (1 source file):
INFO: Found 1 target...
Target //:java_main_deploy.jar up-to-date:
bazel-bin/java_main_deploy.jar
INFO: Elapsed time: 7.111s, Critical Path: 3.65s
INFO: 6 processes: 2 internal, 3 linux-sandbox, 1 worker.
INFO: Build completed successfully, 6 total actions
build.target=@@//:java_main
main.class=main.Main
build.time=Thu Jan 01 00\:00\:00 1970 (0)
build.timestamp=Thu Jan 01 00\:00\:00 1970 (0)
build.timestamp.as.int=0
Note specifically that the target being built is java_main_deploy.jar
(an additional output of java_binary
), the regular java_main
target is not built as a deploy jar (rolling up itself and all its dependencies in one jar) and won't have the build-data.properties
file.
For other rulesets, other approaches might be necessary. There's --embed_label
bazel flag which will put the text of that flag as the value of BUILD_EMBED_LABEL
in the stable status file, which could be read from a build data genrule as above or other rule. You could pass the target on the command line that way, but you would need to be sure that the correct value is passed to the flag and to bazel as the target to build (handling cases like relative labels).
Another approach I could imagine is a macro that creates the top-level target along with the build data generating rule, in order to get the name of the top-level target. Something like:
def cc_binary_with_build_data(name, srcs, **args):
build_info_header_name = name + "_build_info.h"
native.cc_binary(
name = name,
srcs = srcs + [build_info_header_name],
**args,
)
native.genrule(
name = name + "_gen_build_info",
outs = [build_info_header_name],
stamp = True,
cmd = """
echo stable status:
cat bazel-out/stable-status.txt
echo volatile status:
cat bazel-out/volatile-status.txt
BUILD_TIMESTAMP="$$(cat bazel-out/volatile-status.txt | grep BUILD_TIMESTAMP)"
cat > $@ <<EOF
#define $$BUILD_TIMESTAMP
#define BUILD_LABEL "%s"
EOF
""" % ("//" + native.package_name() + ":" + name),
)
Finally, note also that depending on the situation and use case, it's not enough to have just the target / label to reproduce a binary. There's the repo commit it was built at (the link above has an example for git), and there's the build flags passed to bazel, any environment variables that rulesets might depend on (generally discouraged, but it exists), etc.