Bazel Golang Hello World

Warning: only use if you have a severe need for speed and want to be more productive.

Bazel is seriously amazing. The primary kubernetes/kubernetes repository has had bazel enabled for months, but I had run into problems with building with bazel on my MacBook Pro. The learning curve to use bazel is a bit steep, but once @justinsb and I got it running in kops: Holy Cow Batman! kops tests and builds are so much faster. When cached build times are sub-second which took 6 min. Those numbers are no joke.

Bazel Logo

See this link for information on their logo.

What is Bazel

On February 25th, 2015, a Googler pushed the first commit to the bazel project, and Google officially Open Sourced the project. Blaze is Google’s closed source build tool and is the predecessor to bazel.

Bazel is a build tool that replaces using Makefiles or other tools for building go. Under the hood, it uses go build. But it is not your average build tool, just as make has many different options, bazel provides many exciting features including dependency management, and templating with external tools, and the capability to build containers without docker.

Why Bazel

  1. Speed, speed and more speed, Ferrari like performance. Because of caching, compilation and unit test speed is ridiculous.
  2. One tool to rule them all. Bazel supports Go, Java, C++, Android, iOS, on OSX, Linux, and Windows.
  3. Extensibility. Add plugins and call external tools.
  4. The tool scales. It handles codebases of any size and integrates into CI. The kubernetes/kubernetes repository is a huge repo with multiple containers and binaries.
  5. Build vendoring for go. We where not able to fully use it in kops, because of some challenges, but for other projects it works great. Watch out dep, cause you have a serious competitor.

Using Bazel with Go

Pre-flight Checks

  1. Install bazel - Instructions are here.
  2. It is my understanding that you do not even need to install golang, but it is helpful otherwise. Here are install instructions for golang.

Helper Script

Create your project with your source control tool of choice. Inside your project run the bash script provided here.

Provide the go path for the project. For example: create-bazel-workspace github.com/myuser/myproject

Here is the script:

#!/bin/bash

if [ -z "$1" ]; then 
  echo >&2 "please provide your projects base go path as an argument."
  ehco >&2 "For example: github.com/chrislovecnm/go-bazel-hello-world"
  exit 1 
fi

PREFIX=$1

cat > WORKSPACE <<- EOM
git_repository(
    name = "io_bazel_rules_go",
    remote = "https://github.com/bazelbuild/rules_go.git",
    tag = "0.6.0",
)

load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains", "go_repository")
go_rules_dependencies()
go_register_toolchains()

go_repository(
    name = "com_github_golang_glog",
    commit = "23def4e6c14b4da8ac2ed8007337bc5eb5007998",
    importpath = "github.com/golang/glog",
)

go_repository(
    name = "com_github_spf13_cobra",
    commit = "7b1b6e8dc027253d45fc029bc269d1c019f83a34",
    importpath = "github.com/spf13/cobra",
)

go_repository(
    name = "com_github_spf13_pflag",
    commit = "f1d95a35e132e8a1868023a08932b14f0b8b8fcb",
    importpath = "github.com/spf13/pflag",
)
EOM

cat > BUILD <<- EOMB
load("@io_bazel_rules_go//go:def.bzl", "go_prefix", "gazelle")

go_prefix("${PREFIX}")

# bazel rule definition
gazelle(
  prefix = "${PREFIX}",
  name = "gazelle",
  command = "fix",
)
EOMB

echo "project WORKSPACE and BUILD files created, run gazelle to create required BUILD.bazel files"

The above script will seed a project to use cobra. I have provided an example project located here on Github.

The Files

The above script creates two files. The “WORKSPACE” file is mandatory for every bazel project. This file typically contains references to external build rules and project dependencies.

From my example project:

git_repository(
    name = "io_bazel_rules_go",
    remote = "https://github.com/bazelbuild/rules_go.git",
    tag = "0.6.0",
)

load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains", "go_repository")
go_rules_dependencies()
go_register_toolchains()

go_repository(
    name = "com_github_golang_glog",
    commit = "23def4e6c14b4da8ac2ed8007337bc5eb5007998",
    importpath = "github.com/golang/glog",
)

go_repository(
    name = "com_github_spf13_cobra",
    commit = "7b1b6e8dc027253d45fc029bc269d1c019f83a34",
    importpath = "github.com/spf13/cobra",
)

go_repository(
    name = "com_github_spf13_pflag",
    commit = "f1d95a35e132e8a1868023a08932b14f0b8b8fcb",
    importpath = "github.com/spf13/pflag",
)

The syntax quite similar to groovy, and you can see the go dependencies being added to the Workspace. Continually adding go_repository rules is one of the areas that needs some automation, and hopefully be addressed with work on this issue.

The next set of files are called BUILD or BUILD.bazel.

From bazel documentation:

By definition, every package contains a BUILD file, which is a short program written in the Build Language. Most BUILD files appear to be little more than a series of declarations of build rules; indeed, the declarative style is strongly encouraged when writing BUILD files.

Here is the BUILD file in the root directory of the example project.

load("@io_bazel_rules_go//go:def.bzl", "gazelle", "go_binary", "go_library", "go_prefix")

go_prefix("github.com/chrislovecnm/go-bazel-hello-world")

# bazel rule definition
gazelle(
    name = "gazelle",
    command = "fix",
    prefix = "github.com/chrislovecnm/go-bazel-hello-world",
)

The BUILD and BUILD.bazel files are the continual work in bazel. Add a new import in go, and you need to update the BUILD file. Add a new go file or test, and yep update BUILD.bazel. Thankfully the bazel authors have added gazelle.

Gazelle Build File Generator

Once you have the project initialized, you can execute bazel run //:gazelle. This command will execute gazelle because the gazelle rule is defined in the above BUILD file. Running gazelle will create various BUILD.bazel files in your project.

More documentation about Gazelle.

Defining Your Binary File(s)

One thing that gazelle did not do initially was define a rule to build a binary. I added the go_binary and go_library rules by hand, after gazelle, generated the files.

load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")

go_binary(
    name = "go-bazel-hello-world",
    importpath = "github.com/chrislovecnm/go-bazel-hello-world/cmd",
    library = ":go_default_library",
    visibility = ["//visibility:public"],
)

go_library(
    name = "go_default_library",
    srcs = ["main.go"],
    importpath = "github.com/chrislovecnm/go-bazel-hello-world/cmd",
    visibility = ["//visibility:public"],
    deps = ["//pkg/cmd:go_default_library"],
)

go_binary(
    name = "cmd",
    importpath = "github.com/chrislovecnm/go-bazel-hello-world/cmd",
    library = ":go_default_library",
    visibility = ["//visibility:public"],
)

Common Bazel Commands

The example Makefile contains the usual suspects for bazel commands. The usual workflows for development: build, test, and gazelle for bazel.

all:
	bazel build //...

test:
	bazel test //...

gofmt:
	gofmt -w -s pkg/ cmd/

gazelle:
	bazel run //:gazelle

Next Steps

Bazel is powerful. You can do cool things like running external targets, and building containers without docker, a heck of a lot faster than docker does it!

Cross-compiling with bazel is complicated, and the support for making Linux binaries on OSX is limited. One solution is to use a container such as planter.

I am planning on further posts about using go-bindata and containers with bazel once we have those fixed in kops.

We need to integrate bazel into kops testing with Travis. One note, use caching inside your CI tool. Without caching you loose ALL of bazel’s performance. Frankly you may as well use go build.

TLDR;

  1. Install bazel
  2. Run the script or create the Workspace and BUILD files
  3. Execute bazel run //:gazelle
  4. Define your binaries with bazel rules
  5. Enjoy the serious performance

Thanks

First off thanks to the bazel team, for such an amazing tool. Thanks to @justinsb for his kopeio/build project. I was able to work through issues for my example, using the project as a base.

@ixby, @bentheelder, and others help on the #bazel Kubernetes slack channel have been invaluable.

Thanks to @jroberts235 for recommending including a basic overview of bazel.

For Help with KubernetesAdoption, kops, or all things Kubernetes. Have me contact you today!