Appearance
vix build Graph Design
vix build is not only a command that runs CMake.
The deeper direction is to understand the build as a graph.
A build graph lets Vix reason about:
txt
files
objects
libraries
executables
compile tasks
link tasks
copy tasks
dependencies
dirty state
cache hits
target selectionThis matters because a fast build system should not ask only:
txt
How do I build everything?It should ask:
txt
What exactly changed?
What depends on it?
Which tasks are dirty?
Which outputs can be restored from cache?
Which target did the user ask for?
What can be skipped safely?That is the purpose of the Vix build graph.
Why a build graph exists
A C++ build is naturally a graph.
A source file produces an object file.
An object file contributes to a library or executable.
A header can affect many object files.
A generated file can affect another build step.
A resource copy can depend on a runtime asset.
Conceptually:
txt
src/main.cpp
-> main.o
-> app
include/app.hpp
-> main.o
-> app
assets/config.json
-> copy resourceIf Vix can model this, it can make better decisions.
The simple model
A build graph has two main concepts:
txt
nodes
tasksNodes represent things.
Tasks represent work.
Example:
txt
Node: src/main.cpp
Node: build/main.o
Node: build/myapp
Task: compile src/main.cpp -> build/main.o
Task: link build/main.o -> build/myappThe graph connects inputs, tasks, and outputs.
Build nodes
A build node represents an input or output in the build system.
Examples:
txt
source file
header file
config file
package
object file
static library
shared library
executableA node can have a state:
txt
clean
dirty
missingThis state helps Vix decide whether a task needs to run.
Source nodes
A source node represents a file that is compiled.
Example:
txt
src/main.cpp
src/app.cpp
src/server.cppThese files usually become object files.
If a source file changes, the compile task that depends on it becomes dirty.
Header nodes
A header node represents an included header.
Example:
txt
include/myapp/app.hpp
include/myapp/config.hppHeaders are important because they can affect many source files.
If include/myapp/config.hpp changes, several object files may need to rebuild.
That is why dependency files matter.
Object nodes
An object node represents a compiled output.
Example:
txt
build/CMakeFiles/myapp.dir/src/main.cpp.oObject files are the main unit of incremental C++ builds.
If an object file is still valid, Vix should not recompile it.
If it is missing or stale, Vix should rebuild or restore it from cache.
Library nodes
A library node represents an archive or shared object.
Examples:
txt
libmathlib.a
libplugin.so
mathlib.lib
plugin.dllLibraries depend on object files.
If the object files do not change, the library may not need to be rebuilt.
Executable nodes
An executable node represents the final runnable binary.
Examples:
txt
myapp
server
hello.exeAn executable depends on object files and linked libraries.
If none of those inputs changed, relinking can be skipped.
Build tasks
A build task represents an action.
Common task kinds:
txt
compile
link
archive
copy
generateEach task has:
txt
inputs
outputs
command
dependencies
stateThe command matters because changing compiler flags can invalidate outputs even if the source file did not change.
Compile tasks
A compile task turns a source file into an object file.
Conceptually:
txt
compile src/main.cpp -> main.oThe task depends on:
txt
source file
headers
compiler
compiler flags
include dirs
defines
C++ standard
target tripleIf any of these change, the object is dirty.
Link tasks
A link task turns object files and libraries into an executable or shared library.
Conceptually:
txt
link main.o app.o -> myappThe task depends on:
txt
object files
libraries
linker flags
linker identity
target tripleIf no link inputs changed, Vix should avoid relinking.
Archive tasks
An archive task creates a static library.
Conceptually:
txt
archive add.o mul.o -> libmathlib.aThis is common for type = static projects.
Copy tasks
A copy task moves files or directories.
For vix.app, resources become copy tasks.
Example:
ini
resources = [
assets,
"data/config.json=config/config.json",
]Conceptually:
txt
copy assets -> $<TARGET_FILE_DIR>/assets
copy data/config.json -> $<TARGET_FILE_DIR>/config/config.jsonCopy tasks should run only when needed.
Generate tasks
A generate task creates files used later in the build.
Examples:
txt
generated source files
generated headers
generated config files
generated CMake filesFor vix.app, generating:
txt
.vix/generated/app/CMakeLists.txtis a generation step.
In the future, Vix may use generate tasks for its own native build graph.
Importing the graph from CMake
Today, CMake remains the compatibility path.
That means Vix can learn the build graph from files generated by CMake and Ninja.
Important files:
txt
compile_commands.json
build.ninja
*.d dependency filesThese files give Vix real build information.
Vix should not guess compiler commands if CMake already generated them.
compile_commands.json
compile_commands.json contains exact compile commands.
A typical entry describes:
txt
working directory
source file
compiler command
compiler arguments
object outputThis is useful because the object cache needs exact compiler inputs.
A cache key based on guesses would be unsafe.
A cache key based on actual compile commands is stronger.
build.ninja
build.ninja contains the generated Ninja build rules.
Vix can parse it to find build edges.
Edges can be classified as:
txt
Compile
Archive
Link
Copy
Install
Utility
UnknownThis gives Vix a lower-level view of what the build system will do.
Dependency files
C++ compilers can emit dependency files.
Example:
txt
main.o: src/main.cpp include/app.hpp include/config.hppThis tells Vix which headers affect an object file.
Without dependency files, Vix would not know that changing a header should rebuild specific object files.
Dependency files are essential for correct incremental builds.
Dirty state
Dirty state tells Vix whether a node or task must be rebuilt.
A node may be dirty when:
txt
the file changed
the file is missing
a dependency changed
the command changed
the build fingerprint changed
the compiler changed
the target triple changedA task is dirty when any of its important inputs are dirty or invalid.
Clean state
A node is clean when its output is valid.
Example:
txt
main.o is cleanmeans:
txt
source is unchanged
headers are unchanged
compiler command is unchanged
output exists
cache metadata is validClean nodes can be skipped.
Missing state
A node is missing when the expected output does not exist.
Example:
txt
build/main.odoes not exist.
Then the compile task must run or restore the object from cache.
Command hash
The command hash is part of correctness.
If this command changes:
bash
g++ -std=c++20 -Iinclude -c src/main.cpp -o main.oto this:
bash
g++ -std=c++23 -Iinclude -DMYAPP_DEBUG=1 -c src/main.cpp -o main.othe output object is no longer equivalent.
Even if the source file is unchanged, the object should be rebuilt.
So tasks need command fingerprints.
File metadata vs content hashing
A build system can use:
txt
file timestamps
file sizes
content hashesTimestamps are fast but can be less reliable.
Content hashes are more reliable but more expensive.
A practical system can combine both:
txt
use metadata for quick checks
use hashes for cache keys and correctness-critical decisionsObject cache and artifact cache should be conservative.
Correctness is more important than a false cache hit.
Target-aware execution
A graph allows target-aware execution.
If the user asks for:
bash
vix build --build-target serverVix should select only the tasks required for:
txt
serverThe graph can compute the dependency closure:
txt
server
depends on server.o
depends on router.o
depends on libcore.aThen unrelated tasks can be ignored.
Dependency closure
The dependency closure of a target is the set of tasks and nodes needed to build it.
Example:
txt
myapp
main.o
app.o
config.oIf a project has other targets:
txt
tests
benchmarks
examplesthey do not need to be built unless the selected target depends on them.
This is important for large projects.
Scheduler
Once Vix knows which tasks are needed, it can schedule them.
A scheduler runs tasks when their dependencies are ready.
Example:
txt
compile main.cpp
compile app.cpp
compile server.cpp
link myappThe compile tasks can run in parallel.
The link task must wait for the object files.
Parallelism
Parallel builds matter in C++ because each source file can often compile independently.
If there are eight independent compile tasks, Vix can run several at once.
The job count controls this:
bash
vix build -j 8A good scheduler should respect dependencies and job limits.
Object cache integration
The build graph makes object cache decisions more precise.
For each compile task, Vix can ask:
txt
Is there a cached object for this exact input set?The input set includes:
txt
source content
header dependencies
compiler command
compiler identity
target triple
build typeIf the cache has a valid object, Vix can restore it instead of compiling.
Artifact cache integration
The artifact cache works at a larger level.
It can reuse:
txt
compiled libraries
executables
package artifacts
whole target outputsA graph helps decide whether a target artifact is still valid.
If all inputs and build settings match, Vix may restore or skip the entire target.
No-op builds
The graph is especially useful for no-op builds.
A no-op build means:
txt
nothing changed
nothing needs to runThe ideal result is:
txt
skip configure
skip compile
skip link
finish almost immediatelyThis is one of the biggest opportunities for Vix.
Incremental builds
An incremental build happens when only part of the project changed.
Example:
txt
src/app.cpp changedThen Vix should rebuild:
txt
app.oand relink:
txt
myappBut it should not rebuild:
txt
main.o
server.o
unrelated librariesA graph makes that possible.
Header changes
Header changes are harder.
If this changes:
txt
include/config.hppVix must know which source files include it.
That information comes from dependency files.
If three object files depend on the header, those three become dirty.
The rest can stay clean.
Resource changes
Resources should also be modeled.
If this changes:
txt
assets/logo.pngVix should not recompile C++.
It should only rerun the resource copy task.
This is another reason resources should be separate from sources.
Generated CMake and graph import
For vix.app, the current path is:
txt
vix.app
-> generated CMake
-> CMake/Ninja files
-> imported BuildGraphThis gives Vix a bridge.
The manifest is simple.
CMake provides compatibility.
The graph layer lets Vix analyze and optimize.
Native vix.app graph
The future path is more direct:
txt
vix.app
-> native BuildGraph
-> ObjectCache
-> Scheduler
-> LinkIn that model, Vix does not need to import a graph from CMake for simple projects.
It can create the graph directly from:
txt
sources
include_dirs
defines
compile_options
link_options
links
resources
output_dirThis is why vix.app is strategically important.
Why arbitrary CMake is harder
CMake is a full language.
It can do many things conditionally:
txt
if platform is Linux
if compiler is Clang
if package exists
generate files
add targets dynamically
call functions
use generator expressionsVix cannot safely understand all arbitrary CMake logic without letting CMake run.
That is why the CMake path imports generated build files instead of trying to interpret CMake itself.
Why vix.app is easier
vix.app is narrow and declarative.
It says:
txt
these are the sources
these are the includes
these are the links
these are the resourcesThere is no arbitrary control flow.
That makes it possible for Vix to build a native graph directly.
Graph storage
A build graph can be stored between builds.
Stored graph data can include:
txt
nodes
tasks
command hashes
dependency state
output paths
dirty state
cache metadataOn the next build, Vix can reload this state and compare it with the current project state.
That helps reduce repeated work.
Correctness first
A build graph must be correct before it is fast.
A wrong cache hit is worse than a slow build.
If Vix is unsure whether an output is valid, it should rebuild.
Good build systems are conservative.
The rule should be:
txt
skip only when correctness is provenDebugging the graph
A useful build graph should be inspectable.
Engineers may need to know:
txt
why this file rebuilt
why this object was dirty
why this target linked again
why this cache entry was missed
which header caused the rebuildOver time, Vix can expose commands or verbose output to explain graph decisions.
This is important for trust.
Example graph
For this project:
txt
myapp/
vix.app
include/
myapp/
app.hpp
src/
main.cpp
app.cppManifest:
ini
name = myapp
type = executable
standard = c++20
sources = [
src/main.cpp,
src/app.cpp,
]
include_dirs = [
include,
]Conceptual graph:
txt
Node: src/main.cpp
Node: src/app.cpp
Node: include/myapp/app.hpp
Node: build/main.o
Node: build/app.o
Node: build/myapp
Task: compile main.cpp -> main.o
Task: compile app.cpp -> app.o
Task: link main.o app.o -> myappIf src/app.cpp changes:
txt
app.o dirty
myapp dirty
main.o cleanThat is the behavior Vix wants.
Example with resources
Manifest:
ini
resources = [
assets,
]Graph:
txt
Node: assets/
Node: build/bin/assets/
Task: copy assets -> build/bin/assetsIf assets/logo.png changes, Vix should rerun the copy task.
It should not recompile unrelated C++ files.
Example with package links
Manifest:
ini
packages = [
Threads:REQUIRED,
]
links = [
Threads::Threads,
]Graph-level model:
txt
Package node: Threads
Link input: Threads::Threads
Task: link myappIf package configuration changes, linking may become dirty.
Relationship with Ninja
Ninja already has a build graph.
So why does Vix need one?
Because Vix needs its own higher-level decisions:
txt
artifact cache
object cache
diagnostics
target-aware UX
future native builds
cross-project reuse
explainable rebuildsNinja executes a graph very well.
Vix needs to understand and augment the graph.
Relationship with CMake
CMake generates the build graph.
Vix can use CMake as the source of truth for complex projects.
But for vix.app, Vix can eventually become the source of truth.
So the relationship is:
txt
CMake project:
CMake generates graph
Vix imports and augments it
vix.app project:
Vix generates CMake today
Vix can generate native graph laterThe long-term architecture
The long-term architecture can look like this:
txt
ProjectResolver
-> BuildPlan
-> BuildGraph
-> ObjectCache
-> ArtifactCache
-> Scheduler
-> LinkFor CMake projects:
txt
CMakeLists.txt
-> CMake configure
-> import compile_commands.json
-> import build.ninja
-> graph analysisFor vix.app projects:
txt
vix.app
-> native graph generation
-> graph executionwith CMake still available as fallback.
The engineering goal
The goal is not only faster builds.
The goal is understandable builds.
Vix should be able to explain:
txt
what it is building
why it is building it
what changed
what was cached
what was skipped
what failedA build graph makes that possible.
Conclusion
The Vix build graph is the foundation for a smarter C++ build pipeline.
It models files as nodes.
It models work as tasks.
It tracks dependencies and dirty state.
It enables target-aware builds, object caching, artifact caching, and future native execution.
Today, Vix can import graph information from CMake and Ninja.
Tomorrow, vix.app can feed the graph directly.
That is the direction:
txt
CMake compatibility for complex projects
native BuildGraph for simple vix.app projects
cache and scheduling across both