Start with a checkout of the repository you want to change. Run every command from that repository's root, where MODULE.bazel selects the published Toolchain and SDK versions used by the build.

Build tools

Install Git, Python 3 and Bazelisk as the bazel command. Bazelisk selects the Bazel version recorded in the checkout's .bazelversion. Toolchain acquires the matching Clang, platform SDKs, formatter and Web runtime; do not configure a second compiler inside the repository.

From your checkout, check that the tools are available:

git --version
python3 --version
bazel --version

The first command may download substantial compiler and SDK archives. Keep the dependency versions selected by the checkout. Another copy of TTX, Perimortem or Toolchain beside the repository does not replace its declared dependency.

The shell examples here use Linux. Target platform settings are separate from the host running Bazel. Use the configurations provided by the repository when cross-compiling, and run its tests on a host that can execute the resulting binaries.

Toolchain project setup

Toolchain is consumed as a versioned Bazel module. A new project needs .bazelversion, .bazelrc, MODULE.bazel, a root BUILD, and a source/BUILD that describes the library. Toolchain 0.4.1 uses Bazel 9.2.0.

Toolchain release and targets

Pin the released source archive and register the target environments in MODULE.bazel:

module(
    name = "example",
    version = "0.1.0",
)

bazel_dep(name = "rules_cc", version = "0.2.17")
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "rules_pkg", version = "1.3.0")
bazel_dep(name = "tetro_toolchain", version = "0.4.1")
archive_override(
    module_name = "tetro_toolchain",
    sha256 = "9b501a36786fdb96f0708135cff7a62a1178fef66d245a4acae22dd2dd657ae6",
    urls = ["https://github.com/tetrodotoxin-dev/Toolchain/releases/download/v0.4.1/tetro_toolchain-0.4.1-source.tar.gz"],
)

register_toolchains(
    "@tetro_toolchain//:cc_toolchain_for_linux_x86_64",
    "@tetro_toolchain//:cc_toolchain_for_wasm32",
    "@tetro_toolchain//:cc_toolchain_for_wasm32_runner",
    "@tetro_toolchain//:cc_toolchain_for_windows_x64",
)

Set .bazelversion to 9.2.0. A minimal .bazelrc keeps outputs inside the checkout and names the supported target configurations:

common --symlink_prefix=.bin/
run --run_in_cwd
build --compilation_mode=dbg
build:debug --compilation_mode=dbg
build:release --compilation_mode=opt
build:release --strip=always
build:linux --platforms=@tetro_toolchain//:linux_x86_64
build:windows --platforms=@tetro_toolchain//:windows_x64
build:web --platforms=@tetro_toolchain//:wasm32
build:web_tests --platforms=@tetro_toolchain//:wasm32_runner
try-import %workspace%/.bazelrc.local

Project layout

Toolchain's library and validation macros give the repository one predictable shape. Authored library code lives under source/, while executable correctness and performance checks live under validation/. A small project named example can begin with this tree:

example/
├── .bazelrc
├── .bazelversion
├── BUILD
├── LICENSE
├── MODULE.bazel
├── source/
│   ├── BUILD
│   └── example/
│       ├── example.cpp
│       └── example.hpp
└── validation/
    ├── benchmarks/
    │   └── example.cpp
    ├── data/
    └── tests/
        └── example.cpp

The example directory matches the name in MODULE.bazel. That lets the default library, package and validation declarations agree on one project identity. Projects can add subdirectories freely beneath it. Public component boundaries become useful only when the SDK needs independently consumable header groups; they are not required to bootstrap one library.

Single-library SDK

Load library() from source/BUILD and give it the implementation and public headers. The first version can glob the complete source tree:

# source/BUILD
load("@tetro_toolchain//:defs.bzl", "library")

library(
    srcs = glob(["example/**/*.cpp"]),
    hdrs = glob([
        "example/**/*.h",
        "example/**/*.hpp",
    ]),
    includes = ["."],
)

With no explicit name or module, library() uses the Bazel module name. This creates the static //source:example target, publishes headers as example/example.hpp, and supplies the EXAMPLE export identity used by shared and static builds. A project can later declare a shared variant or component map without changing its authored include paths.

Project operations

The root BUILD turns that compiled library into the repository's public and development surface:

load(
    "@tetro_toolchain//:defs.bzl",
    "format",
    "package",
    "snapshot",
    "validation",
    "vscode",
)

package()
format()
snapshot()
validation()
vscode()

package() publishes //:example and builds the versioned :sdk archives. validation() recursively compiles the C and C++ files under validation/tests/ and validation/benchmarks/ into standalone :tests and :benchmarks programs; both link the project library automatically. Files under validation/data/ are available to those programs using the same relative paths under Bazel and direct execution.

format(), snapshot() and vscode() add the repository-owned quality check, clean-commit source archive and generated editor environment. Add the project license at LICENSE, then ignore .bin/, .vscode/ and Bazel's generated workspace links. Projects with public components or external SDK dependencies extend these declarations rather than replacing the root workflow.

VS Code environment

Repositories on Toolchain 0.4 declare a root :vscode target. Run it before opening the checkout:

bazel run :vscode
code .

The target creates the complete .vscode/ environment for the selected Toolchain release:

  • C23 and C++26 source paths for the repository and Bazel outputs;
  • the pinned Clang formatter, generated include ordering and format-on-save;
  • Debug and Release build, test, benchmark and format tasks;
  • CodeLLDB launch configurations for the standalone test and benchmark programs;
  • extension recommendations for C/C++ editing and native debugging.

Rerunning bazel run :vscode replaces those shared files, including the formatter path and style, after a Toolchain upgrade. The generated directory is ignored by repositories using this workflow. Avoid hand-maintaining parallel tasks or IntelliSense include lists that can drift from the selected build.

Install the recommended extensions when VS Code prompts. For a workspace with several repositories, add each repository as its own workspace folder and run :vscode from each root; every folder retains its own dependency pins and Bazel root.

Project targets

Toolchain 0.4 projects expose one root workflow:

bazel run :format -- --check
bazel build //source/...
bazel run :tests
bazel run :benchmarks --config=release
bazel build :sdk --config=release

format checks canonical formatting, source structure and comment quality. tests and benchmarks are standalone executables assembled from validation/tests/ and validation/benchmarks/. sdk builds the public headers, libraries, runtime dependencies and metadata for every configured target.

Projects that support hosted Web validation can run the same test program through the pinned Emscripten and Node environment:

bazel run :tests --config=web_tests

Web and Windows cross-builds establish compilation and packaging only. Run the produced program on its target before claiming runtime behavior there.

GitHub release

Update the module version and dependency pins before validating the release. Commit that exact state, then run the applicable native and hosted checks from a clean worktree:

bazel run :format -- --check
bazel run :tests --config=release
bazel run :benchmarks --config=release
bazel run :tests --config=web_tests
bazel build :sdk --config=release
bazel run :snapshot

The SDK build writes versioned header, platform and checksum archives under .bin/bin/. snapshot refuses a dirty worktree and writes the committed source archive, its SHA-256 file and exact commit under .bin/release/. Review the generated asset list before publication; a project that omits a linkage variant or target should not advertise that archive.

Create and push an annotated version tag, then upload those exact artifacts with the GitHub CLI:

git tag -a v0.1.0 -m "Example 0.1.0"
git push origin main v0.1.0

gh release create v0.1.0 --verify-tag \
  .bin/bin/example-0.1.0-*.zip \
  .bin/bin/example-0.1.0-sha256.txt \
  .bin/release/example-0.1.0-source.tar.gz \
  .bin/release/example-0.1.0-source.tar.gz.sha256 \
  .bin/release/example-0.1.0-source.tar.gz.commit \
  --title "Example 0.1.0" \
  --notes-file /path/to/release-notes.md

The uploaded snapshot, not GitHub's automatically generated source archive, is the source product consumed by downstream archive_override declarations. After publication, download the public assets by URL and prove an independent consumer against them before updating dependent repositories.

Build outputs and source

Toolchain configures Bazel's output prefix as .bin/. Generated libraries, standalone validation programs and imported SDK include trees are visible under .bin/bin/; they remain Bazel outputs rather than authored source.

The common layout is:

  • source/ owns the implementation and public headers;
  • validation/tests/ owns correctness cases;
  • validation/benchmarks/ owns measured workloads;
  • validation/data/ contains runtime fixtures;
  • MODULE.bazel selects released dependencies;
  • the root BUILD declares formatting, validation, packaging, snapshots and editor generation.

Read the root BUILD before assuming every repository enables every optional target. Godot and Compute also carry integration-specific targets; continue with the Godot setup guide or Compute build instructions for those workflows.

Tetrodotoxin 0.4 migration

Tetrodotoxin is moving its Source and Puffer work onto the Toolchain 0.4 frontend for its first release. Until that migration lands, its checked-in .vscode/ configuration and repository-specific //tests:all targets remain authoritative. Do not replace them with the generated library-project setup or infer target names from Perimortem and TTX.

The repository's current BUILD.bazel, DEVELOPMENT.md and .vscode/README.md record which Source, module and Puffer checks are active at that checkpoint.

Once the environment is ready, continue with development and review for ownership, interface comments and checks across a foreign consumer boundary.