Because Bazel manages the complete dependency tree, it becomes incredibly easy to integrate Bazel with CI systems. For the most part, the entire Bazel CI build/testing system can be reduced down to a couple of Bazel commands.

bazel test //src/...
bazel build //src/...

That’s it! Those two commands will run all tests and build all targets under src, which is pretty much the primary goal of any CI system.

We can do better, however… by actually running these commands on a CI system. I chose to work with Travis-CI, as it is free for open source projects. I was inspired by a previous Bazel-Travis CI solution that needed some updating from Bazel 0.3 to 0.11, but I was having issues with that solution due to Debian package download server uptimes. I decided to package Bazel within a container that could then be used as the builder.

I’ve augmented my bazel_examples project over at https://github.com/curtismuntz/bazel_examples that has all the necessary components:

  • the Bazel Docker image
  • travis.yaml definition
  • the Bazel ci script

Docker CI Solution Within Travis

I’ve decided to maintain a Bazel Docker file within this repository due to the uptime issues that I mentioned previously. Running within a container also gives me the flexibility to lock down which version of Bazel to run (up to and including master), and gives me the ability to add system dependencies should I need to.

The image is heavily inspired by insready’s Bazel container but also installs my system dependencies (currently just python). It can be found on Dockerhub here.

Travis.yaml

I headed over to https://travis-ci.org/ in order to set up a public repository for Travis. Once set up, I added the following .travis.yaml file to the workspace root, and Travis started an automated build running the CI script under tools/ci/run_ci_tests.sh.

sudo: required
language: cpp
os: linux
dist: xenial
services:
  - docker
install: skip
script:
  - tools/ci/run_ci_tests.sh

Note that sudo is needed in order to run Docker commands on a Travis worker instance.

The CI Script

My script simply spins up a Docker image and execs the Bazel commands to test and compile for both x86 and the raspberry pi toolchains. The CI container is given a specific bazelrc file that enables sandboxed compilation and testing, adds in verbose failures, and sets the Bazel server to start up in batch mode.

By using set -e, bash exits if any subcommand has a failure. This is necessary to stop the whole build in the event of a failure with one of the Docker execs.

Finally, I placed an exit trap so that this script stops the running Bazel container in the event of a failure.

#!/bin/bash
set -e

function stop {
  docker stop travis_build
}
trap stop EXIT

TARGETS="//src/...
         //deploy/...
        "
CONFIG="--bazelrc=tools/ci/bazelrc_travis"

OPTS="-c opt"

docker run -it --rm -d \
  --name travis_build \
  -v "$PWD":/opt/src \
  murtis/bazel \
  /bin/bash

docker exec travis_build bazel $CONFIG build $OPTS $TARGETS
docker exec travis_build bazel $CONFIG build $OPTS $TARGETS --crosstool_top=//compilers/arm_compiler:toolchain --cpu=rpi

Limitations

There is a pretty glaring circular dependency that I introduced into this repository. Anytime I update any of my Dockerfiles, both Dockerhub and Travis will spawn builds. The Dockerhub maintained image will not be available to Travis immediately, so technically it will be running on an out of date container image.

I could move these files out of this repository to help mitigate this container image issue but I felt that the benefits of the monorepo concept outweigh this issue, especially for an example Bazel project.