Farid Zakaria

7 min read

A C++ toolchain from 357 bytes, in Bazel


I have been fascinated and amazed by stage0 for a while now ever since I learnt about it via Guix using it to provide twenty two thousand packages source-bootstrapped from the 357-byte seed.

What is stage0?

It is a chain of compilers and assemblers that can be built from source, starting from a 357-byte program that can eventually build a recent GCC.11Once you can reach a recent-enough GCC, you can build any C/C++ program and beyond easily. 

Since then, NixOS and other distributions have also adopted the same approach to minimize their binary seed which makes it possible to onboard new architectures and platforms much simpler.

What’s always frustrated me as a Bazel (& Buck) user is the reliance on prebuilt toolchains even for things that should be built from source easily like protoc.

Bazel has given up trying to provide a hermetic C++ toolchain and the upstream rules_cc ruleset just points you elsewhere:

Configuring a hermetic toolchain makes your build more deterministic. rules_cc itself does not yet offer a hermetic toolchain distribution

I had attempted to provide a stage0 hermetic C++ toolchain in October 2024 via https://github.com/fzakaria/stage0-bazel. I made substantial process through the bootstrap process but I did not make it far enought to be usabale.

To be honest, I was also a little disheartened that no one else in the community thought it was the greatest thing since slice bread. Everyone seems to be content with using prebuilt toolchains as they go deeper into MODULE.bzl madness.

I had put it aside for a while, but I have been thinking about it again recently. The steps are mechanical and the process imitates existing distributions, so this became a perfect project for me to throw at an LLM to finish.22Consider this the disclosure that I used an LLM to help me write the remainder of the toolchain. 

You can now leverage the toolchain to build cc_binary in Bazel and have it compiled by a toolchain whose entire ancestry is in the repository from that same 357-byte seed. 🎆

How complete is this toolchain?

I pointed the toolchain at Abseil and GoogleTest straight from the Bazel Central Registry without any patches.

bazel_dep(name = "stage0-bazel", version = "0.1.0")
bazel_dep(name = "abseil-cpp", version = "20260107.1")
bazel_dep(name = "googletest", version = "1.17.0.bcr.2")


register_toolchains(
    "@stage0-bazel//toolchain:clang",
    "@stage0-bazel//toolchain:cc",
)

We then can build and run their testsuite to provide a sanity check that the toolchain is working correctly.

$ bazel test --target_pattern_file=absl-tests.txt
Executed 236 out of 236 tests: 236 tests pass

We use a --target_pattern_file to filter tests that require google_benchmark. Abseil marks google_benchmark as a dev_dependency, and Bzlmod drops dev dependencies of non-root modules.

That is us building Abseil and GoogleTest, from the registry, unpatched, compiled by a toolchain that began as 357 bytes of hex.

bootstrap seed hex0   357 bytes stage0 hex1 → hex2 → M0 → cc_x86 M2-Planet, kaem, M1 seed->stage0 mes GNU Mes   mescc stage0->mes tccmes tcc-mes mes->tccmes tcc tinycc   self-hosted tccmes->tcc musl musl tcc->musl bin binutils musl->bin gcc46 GCC 4.6.4 bin->gcc46 gcc10 GCC 10.4.0   C++17 gcc46->gcc10 extras tar, findutils Linux UAPI headers gcc10->extras llvm clang 22.1.8  +  lld 22.1.8 gcc10->llvm extras->llvm absl Abseil + GoogleTest 236 tests pass llvm->absl

How can I be so sure this is a hermetic toolchain?

The toolchain includes an audit report that uses Bazel’s aspects to inspect every action in the build graph and verify that it only executes programs built by the toolchain itself. The report is generated by running bazel build //:trust-report and will fail if any action executes a program outside of the Bazel output tree.33We also set BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 to disable Bazel’s built-in C++ host toolchain detection. 

The report is two lines long:

Bootstrap trust report

Every action in the checked graph runs a program built by this
repository, except for these audited seed binaries:

external/+_repo_rules+hex0-seeds/POSIX/x86/hex0-seed
/nix/store/…-bash-interactive-5.3p3/bin/bash

Unfortunately, since genrule runs a shell it takes as an absolute system path that is also listed as a seed binary. sh_toolchain’s path attribute is a string, and the shell is not a declared input of the action, so no artifact this repository built can provide it.

Building toolchains from bootstrap seeds was never a priority for companies like Google where they control the entire build environment. However we seemed to have adopted the same approach as Bazel and similar build systems have become more popular in the open-source community. We should strive to make our builds more reproducible and hermetic, and this is a step in that direction.