Skip to content

Development

Code Structure

src/
├── blade/              # Main source package
│   ├── main.py         # CLI entry point
│   ├── command_line.py # Argument parsing
│   ├── config.py       # Configuration loading (blade.conf, BLADE_ROOT, etc.)
│   ├── workspace.py    # Workspace discovery and management
│   ├── load_build_files.py  # BUILD file loading and DSL sandbox
│   ├── dependency_analyzer.py  # Topological sort and dependency resolution
│   ├── backend.py      # Backend build system generation (Ninja)
│   ├── build_manager.py     # Build orchestration
│   ├── ninja_runner.py      # Ninja invocation
│   ├── binary_runner.py     # Executing built binaries
│   ├── test_runner.py       # Test sandbox and execution
│   ├── test_scheduler.py    # Parallel test scheduling
│   ├── toolchain.py    # Compiler toolchain abstraction (GCC, MSVC, Clang)
│   ├── *_targets.py    # Build rule implementations (cc, java, py, go, etc.)
│   ├── target.py       # Base Target class
│   ├── build_rules.py  # Rule registration infrastructure
│   ├── dsl_api.py      # Safe `blade.*` DSL module exposed to BUILD files
│   ├── blade_types.py  # Shared type aliases (StrOrList, StrOrListOpt)
│   ├── util.py         # General-purpose helpers
│   ├── console.py      # Logging and diagnostic output
│   ├── config.py       # Configuration schema
│   └── inclusion_check.py  # Header dependency checking for C/C++
├── tests/
│   ├── unit/           # Unit tests (pytest, fast, offline)
│   └── (integration)   # See src/test/
└── test/               # Integration / end-to-end tests (runall.sh / blade_main_test.py, require toolchain)

Rule-target modules (*_targets.py) each define one or more build rule types:

Module Rule(s)
cc_targets.py cc_library, cc_binary, cc_test, cc_plugin, prebuilt_cc_library, foreign_cc_library
java_targets.py java_library, java_binary, java_test
py_targets.py py_library, py_binary, py_test
proto_library_target.py proto_library
go_targets.py go_library, go_binary, go_test
scala_targets.py scala_library, scala_test
cu_targets.py cu_library, cu_binary, cu_test
gen_rule_target.py gen_rule
lex_yacc_target.py lex_yacc_library
resource_library_target.py resource_library
windows_resources_target.py windows_resources
package_target.py package
sh_test_target.py sh_test

How It Works

Loading Configuration

After Blade starts, it loads configuration files from multiple paths via execfile. These are Python source files that call predefined configuration functions, updating the configuration dict in blade.config.

Command-line options matching global_config keys are then applied with the highest priority.

Loading BUILD Files

Blade expands from the command-line targets, executing BUILD files one by one via a restricted execfile sandbox. When BUILD code runs, it calls rule functions (e.g., cc_library(...)) which register targets into Blade's internal data structures.

BUILD files are loaded recursively for all transitive dependencies.

Dependency Analysis

Blade performs a topological sort from the command-line target roots, producing an ordered build target list.

Generating Backend Build Files

Each target generates backend actions (Ninja rules) which are written to a backend build file (e.g. build.ninja).

Executing the Backend Build

Blade invokes the backend build tool (Ninja) to perform the actual build. The generated build file (e.g. build.ninja) is kept in the build directory for incremental builds and debugging; only blade clean removes it.

Running Tests

Test targets are built, then executed in parallel within sandbox environments. Results are collected and reported.

Implementation Topics

In-depth documents on how individual subsystems are implemented, in roughly the order they appear in a blade build invocation:

Testing

Unit tests (src/tests/unit/)

Fast, offline tests that exercise individual modules. No toolchain or system dependencies required.

pip install -r requirements-dev.txt
PYTHONPATH=src python -m pytest src/tests/unit/ -v

Integration tests (src/test/)

End-to-end tests that drive real build/test cycles against fixture data in src/test/testdata/. Requires a working C/C++ toolchain (GCC, MSVC, or Clang).

src/test/runall.sh          # Run all integration tests
src/test/run.sh <test_name> # Run a single test (e.g. cc_library_test)

Type checking

pip install -r requirements-dev.txt
pyright

Adding a New Build Rule

  1. Create a target class in a new or existing *_targets.py module. Inherit from Target (or a suitable subclass) and implement generate().
  2. Define the rule-entry function (e.g., windows_resources()) — this function normalizes BUILD-file-friendly types (StrOrListOpt) into list[str] via var_to_list / var_to_list_or_none, creates the target instance, and registers it via build_manager.instance.register_target().
  3. Expose it in the DSL by adding the function to blade/__init__.py and dsl_api.py.
  4. Add integration test data under src/test/testdata/<rule_name>/ with a BUILD file and source fixtures, add a test class in src/test/<rule_name>_test.py, and register that class in the TEST_CASES list in src/test/blade_main_test.py (CI runs only the classes listed there; src/tests/unit/integration_suite_coverage_test.py guards against omissions).
  5. Update documentation in build_rules/cc.md (or a new file for a new category).

Key design patterns

  • StrOrList / StrOrListOpt — Rule-entry functions accept str | list[str] unions for BUILD-file ergonomics. Always normalize to list[str] via var_to_list() (or var_to_list_or_none() for optional params) before passing to parent constructors.
  • Toolchain abstraction (toolchain.py) — Platform-specific build details (compiler, linker, file suffixes) are encapsulated behind the ToolChain base class. Rule implementations query the toolchain rather than branching on os.name.
  • blade.cc_toolchain — A read-only proxy exposed to BUILD files via the DSL for platform-aware decisions (file naming, capability queries).

Debugging and Diagnostics

Most subcommands support --stop-after with options {load, analyze, generate, build} to stop after a specific phase:

blade build --stop-after generate

This stops after generating the backend build file (e.g. build.ninja), letting you inspect the generated output.

--profiling outputs a performance analysis report after execution. Combine with --stop-after to profile specific phases.

Passing options to the backend builder (ninja)

--backend-builder-options forwards arbitrary options straight to ninja (see ninja_runner.py). The most useful for debugging:

# Why is a target (re)built — especially one that rebuilds on EVERY run?
# Ask ninja to explain each decision.
blade build //foo/... --backend-builder-options="-d explain"

# Show the exact commands ninja runs (-v), and/or dry-run without building (-n).
blade build //foo/... --backend-builder-options="-v"

-d explain prints, for every edge it would run, why it is considered dirty — a newer input, an output older than an input, a changed command line, or a recorded dependency (deps/depfile) that no longer exists. It is the first tool to reach for when a build isn't incremental.

You can also run ninja directly against the generated build file (handy after blade build --stop-after generate, which stops once the file is written, before ninja runs). Blade runs ninja from the workspace root, so use the build-dir-relative path; add -n for a dry run that explains without building:

ninja -f build_release/build.ninja -d explain -n

Tip: ninja only strips its msvc_deps_prefix (Note: including file:) lines from console output as a side effect of deps = msvc; all other tool chatter is echoed verbatim. On MSVC, Blade's cc_wrapper.py / link_wrapper.py (written into the build dir) filter compiler/linker noise that ninja won't.

CI

GitHub Actions workflows run on every PR and push to master:

Workflow Purpose
Python package Unit tests + integration tests + E2E smoke + pyright on Ubuntu (Python 3.10–3.14)
macOS CI Platform unit subset + in-process smoke + E2E smoke on macOS
Windows CI Platform unit subset + in-process smoke + E2E smoke on Windows
CodeQL Security analysis
Check Markdown links Link validation for documentation

Distribution

dist_blade in the repository root packages the source into a zip for deployment. Place it alongside the blade bootstrap script and blade.conf.

Other Resources

Community analyses of Blade's internals (based on earlier versions, still informative):