Posted on February 19, 2018

5 minute guide to Bazel, Part 2: Command lines and tools

The aim of this guide is to get you up and running with Bazel as fast as possible. The steps will assume you have Bazel installed.

This part will show how to run a command line using genrule. This rule is the generic way to specify sources, a tool (like a shell script), a command line, and the outputs. You can think of it as a way to define a function in your BUILD file with the following signature:

genrule :: (name, sources, tool, command) -> output

In this example, we want to create a C source file, copy it using cp, and run sed on it with a shell script, and build an executable from the result.

Let’s get started in an empty directory called dir.

  1. Create an empty WORKSPACE file.
dir $ touch WORKSPACE
  1. Create a file called main.c and write some C in it.
// dir/main.c

#include <stdio.h>

int main(int argc, char **argv) {
  printf("Hello Blaze.\n");
  return 0;
}
  1. Write a BUILD file with the genrule to copy main.c.
# dir/BUILD

genrule(
  name = "copy_of_main",
  srcs = ["main.c"],
  outs = ["copy_of_main.c"],
  cmd = "cp $< $@",
)

$< expands to the location of main.c, and $@ expands to the location of copy_of_main.c. See the full list of supported variables here.

  1. Let’s build this target, //:copy_of_main.
dir $ bazel build //:copy_of_main
....................
INFO: Analysed target //:copy_of_main (7 packages loaded).
INFO: Found 1 target...
Target //:copy_of_main up-to-date:
  bazel-genfiles/copy_of_main.c
INFO: Elapsed time: 10.323s, Critical Path: 0.08s
INFO: Build completed successfully, 2 total actions

dir $ cat bazel-genfiles/copy_of_main.c
#include <stdio.h>

int main(int argc, char **argv) {
  printf("Hello Blaze.\n");
  return 0;
}

The file is copied successfully!

  1. Use the tool attribute to specify a separate tool to run in the cmd string.

We want to substitute the word “Blaze” with “Bazel” in the source code, because that’s the name the build system was open sourced with. Let’s write the genrule for that:

# dir/BUILD

# ...

genrule(
  name = "renamed_main",
  srcs = ["copy_of_main.c"],
  outs = ["renamed_main.c"],
  tools = ["substitute.sh"],
  cmd = "$(location substitute.sh) 'Blaze' 'Bazel' $< $@",
)

location is Bazel’s helper function to resolve the location of the tool when this command is executed.

Then, create a file substitute.sh that calls out to sed:

#!/bin/bash

sed "s/$1/$2/" $3 > $4

Don’t forget to make it executable with chmod u+x substitute.sh.

  1. Build the target //:renamed_main.
dir $ bazel build :renamed_main
INFO: Analysed target //:renamed_main (0 packages loaded).
INFO: Found 1 target...
Target //:renamed_main up-to-date:
  bazel-genfiles/renamed_main.c
INFO: Elapsed time: 0.261s, Critical Path: 0.07s
INFO: Build completed successfully, 2 total actions

dir $ cat bazel-genfiles/renamed_main.c
#include <stdio.h>

int main(int argc, char **argv) {
  printf("Hello Bazel.\n");
  return 0;
}

We are now using the correct name.

  1. To wrap it all up, let’s use cc_binary from Part 1.
# dir/BUILD

cc_binary(
  name = "hello_bazel",
  srcs = [":renamed_main"],
)
dir $ bazel run //:hello_bazel
INFO: Analysed target //:hello_bazel (3 packages loaded).
INFO: Found 1 target...
Target //:hello_bazel up-to-date:
  bazel-bin/hello_bazel
INFO: Elapsed time: 5.817s, Critical Path: 0.52s
INFO: Build completed successfully, 5 total actions

INFO: Running command line: bazel-bin/hello_bazel
Hello Bazel.

This is how we can use genrule to preprocess files before passing them in to other rules. It’s a simple and flexible way to create pipelines using Bazel.