Sunday, March 13, 2016

Bazel and Golang

As a Google software engineer, I use the internal Blaze build system every day. As far as build systems go, it’s really good. So I figured I’d give Bazel a try, since it’s basically the same thing.

Basically.

The part I guess I forgot about was that it isn’t supported and developed by teams of paid engineers all the time. That said, it’s actually not bad. It just feels incomplete after coming from Blaze.

I’m partway through setting it up to build an existing project in Go, which is just barely supported. The documentation is minimal and unclear, but I think I can help out.

Here’s how to take a $GOPATH-type workspace and make it work with Bazel without mucking up all of your project paths, and without symlinking parts of one workspace into another. Because that’s actually what the documentation tells you to do, and it feels wrong to follow it.

First, install Bazel. I’ll wait.

All done? Oh, you don’t like this writing style? Neither do I. I’ll stop now...

Next:
  1. Go to your $GOPATH (the parent of bin/ pkg/ and src/) and clone the Bazel repository:
    git clone github.com/bazelbuild/bazel
  2. Now we need to make some changes under src/:
    cd src/
    ln -s ../bazel/tools .
  3. Write a WORKSPACE file:
    cat >WORKSPACE <
    load("@bazel_tools//tools/build_rules/go:def.bzl", "go_repositories")
    go_repositories()
    EOF
  4. Write a BUILD file:
    cat >BUILD <
    load("@bazel_tools//tools/build_rules/go:def.bzl", "go_prefix")
    go_prefix("")
    EOF

I’m not sure why the tools symlink is necessary; it may be a bug in the Go rules. They can’t seem to find their automatically-added dependency without it.

And that’s it, besides for writing BUILD files for each of your packages. I do have one more tip for that part, though: Write your BUILD files in the parent directories of your packages. If you do this:
src/github.com/me/mypackage/BUILD:
load("@bazel_tools//tools/build_rules/go:def.bzl", "go_library")
go_library(
name = “mypackage”,
srcs = [“mypackage.go”],
visibility = [“//visibility:public”],
)

Then when you use that package from Go, you’ll need to double the ‘mypackage’ part:
src/github.com/me/pkguser/BUILD:
load("@bazel_tools//tools/build_rules/go:def.bzl", “go_binary”)
go_binary(
name = “pkguser”,
srcs = [“pkguser/pkguser.go”],
deps = [“//github.com/me/mypackage”],
)

src/github.com/me/pkguser/pkguser.go:
package pkguser
import (
“github.com/me/mypackage/mypackage” // Redundant, eh?
)

But if you write the BUILD file one level up, the names work out as you would expect:
src/github.com/me/BUILD:
load("@bazel_tools//tools/build_rules/go:def.bzl", "go_library", “go_binary”)
go_library(
name = “mypackage”,
srcs = [“mypackage/mypackage.go”],
)
go_binary(
name = “pkguser”,
srcs = [“pkguser/pkguser.go”],
deps = [“:mypackage”],
)

src/github.com/me/pkguser/pkguser.go:
package pkguser
import (
“github.com/me/mypackage”
)

On the other hand, your BUILD files will be longer mix together multiple unrelated packages if you do it this way, so you’ll need to decide what is best for your own project.

2 comments:

Rodrigo said...

And how do you handle BUILD files for external packages?, such as github.com/golang/glog. Internally, you would use glaze to automatically generate BUILD files for go packages.

Jonathan said...

That, and the lack of support for protocol buffer compilation, were the reasons I put down my project with Bazel. I haven't picked it up since March; it might be worth checking whether things have improved.