rbs builds Go with a hermetic, downloaded toolchain — no Go installation on the
host is required. Compilation always runs offline: third-party modules are fetched once
into a shared workspace cache (verified against your committed go.sum) or read from a
committed vendor/ tree, and the compiler itself can never reach the network.
The Go rules live in two modules:
Declaring the Go toolchain
Declare the toolchain once in your workspace’sWORKSPACE.rbs. rbs downloads the
matching Go SDK for your platform on first build and reuses it from a shared store
afterwards.
1
Declare the toolchain in WORKSPACE.rbs
2
Build a Go target
go_toolchain accepts:
If no Go toolchain is declared, the rules fall back to whatever
go is on your
PATH. That works for quick experiments but is not hermetic — declare the
toolchain for anything you share or ship.Rules
go_binary
Builds a Go main package into a runnable binary. Sources (plusgo.mod/go.sum for
module builds) are staged into an isolated build workspace; the build runs with the
network disabled.
A typical service target uses a standard Go module layout — a thin
main under
cmd/, everything else under internal/:
go_library
Packages Go sources under a name so other targets can depend on them. It does not compile a separate archive — the consuminggo_binary or go_test compiles all
sources together.
Within a single Go module, the simplest pattern is to
glob the module’s sources
directly into each go_binary/go_test target (as in the go_binary example
above) — the Go compiler resolves package imports through the module path.go_test
Runsgo test hermetically over your module’s packages, with the same offline
module resolution as go_binary. It is also the coverage entry point for
rbs coverage.
Stage every file your tests read at runtime. If tests load fixtures such as SQL
migrations, add them to
srcs (e.g. + glob(["migrations/*.sql"])) so they exist
in the isolated test workspace.go_deps_consistency
An opt-in gate for workspaces with multiple Go modules (onego.mod per
service). It fails the build when two modules require the same dependency at
different versions, naming every module, version, and file. Skip it if your
workspace uses a single shared go.mod — drift is then impossible by construction.
go get <module>@<version> && go mod tidy in each service) or list the module in
exempt with a reason.
go_proto_library
Generates Go protobuf/gRPC code from.proto sources using the hermetic protoc
and pinned protoc-gen-go / protoc-gen-go-grpc plugins. Generated .go files are
written into the source tree (stamped DO NOT EDIT) so they can be committed
next to the code that imports them — go_binary/go_test build from committed
sources, so generated code must live beside them. Requires proto_toolchain() in
WORKSPACE.rbs.
rbs build //proto/orders:go) after editing the .proto to
refresh the committed stubs; catch drift in CI by regenerating and diffing.
Third-party dependencies
Go dependencies are declared exactly as Go expects — in your module’sgo.mod and
go.sum — and rbs resolves them hermetically. Compilation always runs with module
fetching disabled, so dependencies must be on disk before the compiler starts. Which
mode you are in follows from what you commit:
- go.sum (default)
- vendor/
- No dependencies
Commit
go.mod + go.sum (and list both in srcs), with no vendor/ tree.
In a single network-enabled staging step, rbs downloads modules from the Go
module proxy into a shared per-workspace cache, verifying every download
against your committed go.sum. The build and all future builds then run
offline against that cache.If go.sum is missing entries, the build fails and tells you to run
go mod tidy and commit the result — incomplete checksums are never silently
self-healed.Testing and coverage
Run tests withrbs test:
rbs coverage:
rbs coverage runs the tests with coverage instrumentation across all staged
packages — untested packages count as 0% instead of silently dropping out — converts
the Go cover profile to lcov, and fails the target if line coverage falls below
min_line_coverage.
Go coverage measures statements, so
go_test exposes a line coverage threshold
only — there are no branch or function thresholds that could never be measured.coverage_exclude sparingly and only for machine-generated files (for example
"*.pb.go" from go_proto_library) — excluded files disappear from both sides of
the ratio, and the coverage log names every active exclusion so the narrowed
denominator is never silent.
Cross-compilation
go_binary cross-compiles per target via the goos and goarch attributes — the
standard workflow for packaging Linux images from a macOS workspace:
- Two targets that differ only in
goos/goarchare cached and built independently — they can never reuse each other’s output. cgo = Truecannot be combined withgoos/goarch: cross-compiling CGO would need a target C toolchain, whichrbsdoes not provide.- A cross-compiled binary is for packaging and deployment, not for running locally.
Invoking it on a mismatched host prints a clear explanation instead of the
kernel’s cryptic
exec format error. - Container images stamp their os/arch from the packaged binary itself, so packaging a host-platform binary into a Linux-stamped image fails loudly rather than producing a lying image.
Caching
Go builds share one module cache and one build cache per workspace, so each dependency version downloads once and packages compile incrementally across all targets; completed build actions are additionally cached in the sharedrbs store,
so unchanged targets are not rebuilt at all.