Skip to main content
Every rbs workspace is a directory tree with a WORKSPACE.rbs file at its root. Inside it, any directory that contains a build file is a package, and each package declares one or more targets — the things you build, test, and run. Targets are addressed by labels like //services/api:server.

The workspace

The workspace root is marked by a WORKSPACE.rbs file. Every rbs command finds the root by walking up from your current directory, so you can run commands from anywhere inside the workspace. WORKSPACE.rbs is also where you declare the hermetic toolchains your workspace uses — rbs downloads and manages them itself, so nothing needs to be installed on the machine:
rbs keeps per-workspace build state in a .rbs/ directory next to WORKSPACE.rbs (build outputs, launchers, test logs). It is disposable — safe to delete, never commit it. Cached action results live in the shared content-addressed cache outside the workspace, so deleting .rbs/ does not throw away cached work.
You can point a command at a specific workspace with --workspace-root or the RBS_WORKSPACE_ROOT environment variable instead of relying on auto-discovery.

Packages and build files

A package is any directory containing a file named BUILD.rbs (lowercase build.rbs is also accepted). The package’s path relative to the workspace root is its name: the build file at services/api/BUILD.rbs defines the package services/api. A build file at the workspace root defines the root package. Build files are written in the RBS language — a small, deterministic, Python-like language. A build file loads the rules it needs and calls them to declare targets:
glob() is available in every build file without a load. It matches files relative to the package directory:
Directories starting with . are never treated as packages, so a build file inside a hidden directory is ignored.

Labels

A label names one target. The full form is //package/path:target_name: Inside a build file, use :name for targets in the same package and //package:name for targets in other packages.

Target patterns

The build, test, run, query, and coverage commands all accept the same pattern syntax for addressing sets of targets:
Most commands also accept a natural syntax: a package path followed by one or more target names, which is convenient for building several targets in one package:

Depending on other targets

The deps attribute wires targets together. rbs resolves the whole dependency closure, builds it in dependency order, and runs independent targets in parallel:
Third-party packages from a language ecosystem are addressed with @external:// labels, pinned to a version:
rbs resolves external dependencies itself and shares them across workspaces through the shared cache — there is no npm install or pip install step.

load() semantics

load() imports named symbols from another .rbs module. Four path forms exist:
You can rename a symbol as you load it:

Gotchas worth knowing

load() bindings are file-local — they are not re-exported. If module a.rbs loads y from x.rbs, a file that loads a.rbs does not see y. An aggregator module must re-export explicitly:
Without the assignment, the aggregator silently exports nothing.
No implicit string concatenation. Adjacent string literals are a parse error in the RBS language. Use explicit + or join:
Other language properties to keep in mind:
  • Build files are declarative: evaluating a build file only registers targets — no compilation or command runs until you invoke rbs build.
  • glob() is the way to enumerate source files; there is no general file I/O in build files.
  • Loaded modules are cached, so loading the same module from many files is cheap.