Go With Bazel

How to build Go projects.

Intro

Working on different projects, I’ve been using various ways for building apps, like a go build or make something and other magic commands. The more significant project, the more magic behind the build process. But, the issue is, there should not be any magic. Any build command or tool should be clear and straightforward, at least for developers who use it. While in most cases, I see damn “multi-step-build-process” instructions with a tiny possibility it’s built for the first time from scratch correctly.

That’s not good. The build process is overcomplicated. Imagine how actual source code looks like. Okay, it should be fixed. How? Let’s see what we can do here.

Some considerations

As a developer, I want to be able:

  • To build the project, I’ll be working on it for the next several months daily.
  • To re-rebuild the project within seconds.
  • To build the project without Docker. (hey, it’s just a bunch of source code, isn’t it?).
  • To understand the build process:
    • see build command
    • see list of dependencies
    • see all of the build steps
  • To modify build steps along with development requirements.
  • To have a unified interface for building code in different languages.

Isn’t “go build” enough?

Well, it depends. For relatively small projects without external non-Go dependencies and code generation, it’s okay. If you have one, stop reading here, you don’t need Bazel … yet.

Medium-sized and larger projects consist of Go code and gRPC/Protobuf code, Python scripts, Java modules, and so on. Even pure Golang projects often include some code generation, like go generate calls, which call external binaries, bash scripts, or something else.

Let me describe such a build process with the following steps:

  • Install tools: A, B, and C
  • Then run tools B, C, A.
  • And run go build.

Easy enough, unless versions of those tools produce similar output and you strictly following these steps. Otherwise, we can bravely multiply the number of different results by the number of various order of tools runs and get the tens of possible versions of generated code.

So, we have some multistep build processes with code generation and some other steps. Now what?

What is Bazel?

Bazel is a build tool for many languages and many platforms. It builds code inside the isolated environment, caches intermediate build step results, and handles dependencies. Builds are reproducible and fast.

Bazel makes an internal dependency graph, which allows rebuilding only changed code without touching other application parts.

Bazel can be used with projects of any size, but it’s most useful for big projects.

Bazel uses Starlark language for defining build targets. It’s a limited subset of Python, though pretty powerful for describing build targets.

How can Bazel help us?

Let’s say we have a monorepo (if you don’t combine all of your projects into it), which contains 10-20-50 apps/microservices/tools, and so on. Within just one command

% bazel build //...

You can build every app inside your repo without installing anything except Bazel, which downloads all dependencies, generates all needed code and produces all binaries. Yes, you don’t need to have the tool pre-installed. Bazel will care about it.

With next command

% bazel test //...

you can run all of the tests.

Conclusion

Bazel is a great tool that simplifies development workflow and allows reproducible, hermetic, fast, and correct builds.

To be continued…