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:
- How configuration is loaded, layered, and consumed
- How BUILD files are discovered, loaded, and registered
- Built-in functions and the
blade.*DSL - How target visibility is implemented and enforced
- Dependency analysis and ninja file generation
gen_rule: custom build steps and how other targets see them- How custom rules (
define_rule) work - How C/C++ programs are built
- How the C/C++ header dependency check (hdrs check) works
- How the static undefined-symbol check (
check_undefined) works - How
export_map(symbol-export control) works - How vcpkg support works
- How
proto_libraryhandles multi-language codegen - How Java and Scala programs are built
- How Python targets are built and packaged
- The console: the build progress panel and the ninja status pipeline
- How
blade testruns user tests - How blade-build itself is tested
Testing¶
Unit tests (src/tests/unit/)¶
Fast, offline tests that exercise individual modules. No toolchain or system dependencies required.
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¶
Adding a New Build Rule¶
- Create a target class in a new or existing
*_targets.pymodule. Inherit fromTarget(or a suitable subclass) and implementgenerate(). - Define the rule-entry function (e.g.,
windows_resources()) — this function normalizes BUILD-file-friendly types (StrOrListOpt) intolist[str]viavar_to_list/var_to_list_or_none, creates the target instance, and registers it viabuild_manager.instance.register_target(). - Expose it in the DSL by adding the function to
blade/__init__.pyand dsl_api.py. - Add integration test data under
src/test/testdata/<rule_name>/with aBUILDfile and source fixtures, add a test class insrc/test/<rule_name>_test.py, and register that class in theTEST_CASESlist insrc/test/blade_main_test.py(CI runs only the classes listed there;src/tests/unit/integration_suite_coverage_test.pyguards against omissions). - Update documentation in build_rules/cc.md (or a new file for a new category).
Key design patterns¶
StrOrList/StrOrListOpt— Rule-entry functions acceptstr | list[str]unions for BUILD-file ergonomics. Always normalize tolist[str]viavar_to_list()(orvar_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 theToolChainbase class. Rule implementations query the toolchain rather than branching onos.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:
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:
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):