Vix Build Roadmap Execution
vix build is becoming more than a CMake/Ninja frontend.
The goal is not to replace CMake and Ninja immediately. The goal is to build a smarter layer above them:
fast when safe
correct by default
fallback when neededThe latest work focused on turning the build roadmap into real implementation steps.
The main areas were:
- build routing
- clean output
- safe fast path
- security tests
- no-op optimization
- why rebuild
- safer Ninja import
- ObjectCache
- ArtifactCache
- native vix.app builds
- diagnostics
- CI and benchmarks
This article summarizes what changed and why it matters.
1. Stabilizing the build routing
The first step was to make the build path predictable. The rules are now clearer:
vix build --build-target all
-> CMake/Ninja
vix build --build-target <real-target>
-> Graph Target Executor
-> fallback CMake/Ninja
vix build --fast --build-target <real-target>
-> build-state fast path
-> fallback Graph Target Executor
-> fallback CMake/Ninja
VIX_GRAPH_EXECUTOR=0 vix build --build-target <target>
-> CMake/NinjaThis matters because build systems must never guess dangerously.
The all target stays on the CMake/Ninja path because it can represent many things:
- executables
- libraries
- tests
- examples
- generated targets
- install targets
- utility targets
A real executable or library target can use the graph executor. A global or ambiguous target falls back to CMake/Ninja.
The principle is simple:
real target -> Vix graph path
global target -> CMake/Ninja path
ambiguous target -> CMake/Ninja path2. Cleaner user output
The second step was to make the output more stable and less noisy. A no-op fast path should look like this:
Checking vix (dev)
✔ Up to date in 0.30sA graph target build should look like this:
Building vix (dev)
✔ Graph target: vix
✔ Up to date
✔ DoneThe output rules are now:
vix build
-> minimal user output
vix build -v
-> detailed but still readable output
VIX_LOG_LEVEL=debug vix build -v
-> internal graph/cache/build logsInternal details should not pollute normal builds. Users should see what matters. Developers debugging Vix internals can still access the deeper logs.
3. Safer --fast
The --fast path is only useful if it is safe.
It should never say Up to date unless the target is really up to date.
The build state validation was strengthened to check:
- project fingerprint
- build signature
- build target
- preset
- build type
- compiler identity
- target identity
- project inputs
- last binary path
- last binary exists
- last binary is executable
- artifact root exists
Some values such as launcher, linker and CMake variables are already part of the build signature. The important rule is:
state hit is not enough
the final binary must still existSo if the last binary was deleted, Vix must fallback to the normal build path.
Example:
vix build --build-target vix
rm -f build-ninja/vix
vix build --fast --build-target vixThe last command must not return a false Up to date.
4. Build safety tests
The next step was adding a dedicated safety test script. The script covers the most fragile paths:
- build target all
- build target real
- graph executor enabled
- graph executor disabled
--faststate hit--fastfallback- missing last binary
- changed source file
- changed header file
- changed CMakeLists.txt
- changed compiler flags
- changed build target
The goal is regression protection.
Every optimization must prove that it does not break correctness.
The test script creates a small temporary CMake project, runs vix build in different modes, and checks the output behavior.
This gives Vix a safety net before adding more aggressive caching or native build execution.
5. Faster no-op builds without --fast
Before this step, the normal build path could still pay for:
- scan project
- load compile_commands.json
- load build.ninja
- load dependency files
- propagate dirty state
even when nothing changed.
The improvement was to allow a valid build state hit to return early even without --fast.
That means:
vix build --build-target vixcan also become fast when the build state proves that nothing changed. The ideal no-op path becomes:
read build state
snapshot project inputs
compare signatures
verify last binary
return up to dateThis turns the build state from a --fast-only feature into a general no-op optimization.
6. Explaining why Vix rebuilds
Speed is not the only goal.
A build tool should also explain its decisions.
The new --explain path is designed for this:
vix build --explain --build-target vixExpected output:
Rebuilding BuildCommand.cpp
reason: source file changed
Rebuilding CLI.cpp
reason: CLI.hpp changed
Relinking vix
reason: object file changedThe first version uses the current graph and the previous graph to compare:
- task existence
- command hash
- missing outputs
- source changes
- header changes
- config changes
- node state changes
This starts the foundation for a bigger feature:
Vix should not only rebuild. Vix should explain why it rebuilt. That is very important for large C++ projects. When a single header causes many files to rebuild, users should be able to see the reason.
7. Safer Ninja import
Vix imports build.ninja so it can understand the generated build graph.
The goal is not to blindly reimplement Ninja. The goal is to use Ninja metadata safely. The improved rules are:
--build-target all -> CMake/Ninja
phony complex target -> CMake/Ninja
real output target -> Graph Executor
ambiguous target -> CMake/NinjaThis means Vix can import more Ninja edges, but still avoid unsafe execution. The safer import path improves:
- link edges
- archive edges
- copy edges
- install edges
- utility edges
- target dependencies
- real output detection
But the executor only handles clear real outputs.
For now:
Link/Archive target -> safe candidate
Copy/Install/Utility/phony -> fallback
multiple matches -> fallback
zero matches -> fallbackThe correction rule is more important than speed: if unsure, use CMake/Ninja.
8. Stronger ObjectCache
ObjectCache is responsible for avoiding unnecessary recompilation. The cache key must be strong enough to prevent wrong reuse. A compile cache key now depends on:
- source content hash
- dependency/header content hash
- command hash
- compiler identity
- target triple
- build fingerprint
- build type
- defines
- include dirs
- compile flags
The expected flow is:
for each compile task:
compute object cache key
if cache hit:
restore .o
restore .d
skip compiler
else:
compile
store .o
store .dA key improvement is making the object cache survive build directory deletion. Instead of only living under the build directory, the object cache can live under:
~/.vix/cache/objectsThat enables this workflow:
vix build --build-target vix
rm -rf build-ninja
vix build --build-target vixWith a warm cache, Vix should restore object files instead of recompiling everything.
9. ArtifactCache for complete targets
ObjectCache avoids recompiling .o files.
ArtifactCache goes one level higher.
It can restore the final target itself:
ArtifactCache -> restore final binary/library
ObjectCache -> restore .o/.d
Compiler -> compile only missesThe ideal flow is:
check artifact cache
if artifact hit:
restore final binary/lib
done
else:
check object cache
compile only misses
link
store artifactThis matters for:
- CI
- clean builds
- developer machines
- packages
- global dependencies
- release builds
For a target like vix, the cache can store the final binary under the artifact root:
~/.vix/cache/build/.../bin/vixThen a later build can restore it directly.
This is the fastest path after build-state validation.
10. Native vix.app build path
vix.app already exists.
Today, the compatibility path is:
vix.app -> generated CMake -> CMake/NinjaThat is good because it supports more features safely. The new direction is to add a native path for simple cases:
vix.app simple executable
-> Native BuildGraph
-> ObjectCache
-> Scheduler
-> LinkBut the important rule stays:
simple features -> native Vix build path
complex features -> generated CMake fallbackVix already has:
- AppManifest
- AppManifest parser
- AppProjectResolver
- AppCMakeGenerator
So the native path should reuse the existing AppManifest. It should not create a second parser. The safe V1 supports simple executable projects. Complex features still fallback to generated CMake:
- packages
- resources
- links
- compile features
- static libraries
- shared libraries
This keeps compatibility while letting Vix start owning the native build path.
11. Better diagnostics
A faster build is not enough. When a build fails, the output should help the developer fix it quickly. The diagnostic improvements focus on:
- compiler errors with code frame
- warnings grouped
- file + line + column
- raw command hidden by default
- raw command visible in debug
- hint for missing headers
- hint for linker errors
- hint for unresolved targets
Expected style:
Build failed
src/main.cpp:12:10
error: 'App' was not declared
Hint:
Declare the symbol before use, include the right header,
or move the function definition above the call.For missing headers:
Build failed
src/main.cpp:2:10
error: fatal error: app.hpp: No such file or directory
Hint:
Check that the header exists and that its directory is listed
in include_dirs, target_include_directories, or your compiler include paths.Warnings should be grouped:
warning 3 compiler warnings
• src/main.cpp:10:9: warning: unused variable 'x'
• src/app.cpp:4:12: warning: unused function 'foo'
• src/db.cpp:8:5: warning: control reaches end of non-void functionRaw commands should stay hidden by default. Debug mode can show them:
VIX_LOG_LEVEL=debug vix build -vThis keeps normal output clean and makes internal debugging possible.
12. CI and official benchmarks
The last step was adding a reproducible way to prove the gains. The standard benchmark is:
hyperfine --warmup 5 --runs 20 \
'vix build --fast --build-target vix' \
'vix build --build-target vix' \
'VIX_GRAPH_EXECUTOR=0 vix build --build-target vix'The official scenarios are:
- no-op build
- incremental one source changed
- incremental one header changed
- clean build cold cache
- clean build warm object cache
- target build vs all build
- fast path hit
- fast path fallback
Every benchmark report should include:
- CPU, RAM, OS
- compiler, linker, launcher
- Vix version
- project size
- number of source files, headers, compile tasks
- cache status
This matters because build performance depends heavily on the machine and environment. A benchmark without environment details is not very useful.
Current architecture after this work
The build model now has multiple layers:
Build state -> fastest no-op validation
ArtifactCache -> restore complete target
BuildGraph -> target-aware analysis
ObjectCache -> restore .o/.d files
Scheduler -> execute selected compile tasks
CMake/Ninja -> compatibility fallbackThe routing model is:
if build state proves clean:
return up to date
else if complete artifact exists:
restore target
else if real target is graph-safe:
use Graph Executor
else:
use CMake/NinjaThis creates a progressive build system. Each layer tries to solve the build earlier. If a layer cannot prove correctness, Vix falls back to the next safer layer.
Why this matters
C++ build tooling often forces users to choose between power and simplicity.
Vix is taking a different path:
- keep CMake/Ninja compatibility
- add fast paths where safe
- add graph intelligence gradually
- improve diagnostics
- make common workflows simpler
This is not a rewrite of the C++ ecosystem. It is a practical build layer that improves the developer workflow step by step.
The principle
Vix should be fast when it can prove correctness. Vix should fallback when another tool is safer.
That principle applies to every part:
all target -> fallback
ambiguous target -> fallback
complex vix.app -> fallback
missing binary -> fallback
changed inputs -> rebuild
valid no-op state -> return fastThis is how Vix can become faster without becoming fragile.
Conclusion
The latest vix build work turns the roadmap into a stronger build architecture.
Vix now has a clearer path toward:
- fast no-op builds
- safer target routing
- better cache reuse
- native vix.app execution
- clearer diagnostics
- reproducible benchmarks
The long-term direction is now visible:
CMake/Ninja -> for compatibility
BuildGraph -> for intelligence
ObjectCache -> for compile reuse
ArtifactCache -> for target reuse
vix.app -> for native simple builds
diagnostics -> for developer experience
CI/benchmarks -> for proofThis is the foundation of a modern C++ build workflow inside Vix.