Object Cache
The object cache is the source-file cache layer in vix build.
Use this guide when you want to understand how Vix can avoid recompiling C++ files that were already compiled with the same inputs.
The problem
C++ compilation is expensive because each source file can pull in many headers.
A small file like:
src/main.cppcan indirectly depend on:
include/app/App.hpp
include/app/Routes.hpp
include/app/Config.hpp
include/vix.hpp
many standard library headers
many third-party headers2
3
4
5
6
Without caching, the compiler may repeat work that has already been done before. The goal of the object cache is simple:
same compile identity
same source inputs
same object output
reuse safely2
3
4
What the object cache does
The object cache stores and restores compile outputs.
A compile task usually produces:
source file -> object file
source file -> dependency file2
For example:
src/server.cpp -> server.cpp.o
src/server.cpp -> server.cpp.d2
The object cache can reuse those outputs when the compile identity has not changed.
Object cache vs artifact cache
The object cache works at the source-file level. The artifact cache works at the target level.
ObjectCache
reuses .o and .d files
ArtifactCache
reuses final binaries, libraries, or package outputs2
3
4
5
They are complementary.
A strong Vix build can use both:
ArtifactCache hit
restore final target
skip compile and link
ArtifactCache miss
check ObjectCache
restore known compile outputs
compile only missing or dirty files
link target2
3
4
5
6
7
8
9
Why object caching matters
Object caching helps when:
a clean build directory was removed
a few source files changed
a target shares compile outputs with another build
a previous compile result is still valid
CI restored the cache
a dependency did not really change2
3
4
5
6
It is especially useful for large C++ projects where many files compile independently.
The safety rule
The safety rule is:
reuse an object file only when the full compile identity matchesA cached object file is not valid just because the source file name is the same. The compile identity must include enough data to avoid false reuse. A false cache miss is acceptable. A false cache hit is dangerous.
Compile identity
An object cache key should include the inputs that affect compilation.
Examples:
source file content
included header content
compiler path
compiler version
compiler flags
include directories
compile definitions
C++ standard
target triple
build type
precompiled configuration
dependency file state2
3
4
5
6
7
8
9
10
11
12
If any important input changes, the object cache must miss.
Source file fingerprint
The source file content is part of the cache identity.
If this file changes:
src/BuildCommand.cppthen the cached object for that exact old content is not valid anymore. Vix should rebuild that source file or restore a matching object from the cache if one exists.
Header dependency fingerprint
Headers are part of the compile identity.
If a source file includes:
#include <vix/cli/commands/BuildCommand.hpp>and that header changes, the object that depends on it may no longer be valid. That is why dependency files matter.
A compiler-generated dependency file can describe what headers were used by a compile task:
BuildCommand.cpp.o: BuildCommand.cpp BuildCommand.hpp BuildOptions.hppVix can use this information to avoid guessing.
Dependency files
The object cache should preserve dependency files together with object files. For a compile output, Vix may store:
object file
dependency file
metadata
compile fingerprint2
3
4
Example:
BuildCommand.cpp.o
BuildCommand.cpp.d
metadata.json2
3
The .d file helps Vix understand which headers were part of the compile task.
What gets restored
On an object cache hit, Vix can restore:
.o file
.d file2
This means the build directory can regain the compile output without running the compiler again. After that, the linker may still need to run if the final target is missing or dirty.
Basic model
The object cache flow is:
1. Build compile task identity
2. Compute fingerprint
3. Check object cache
4. If hit, restore .o and .d
5. If miss, run compiler
6. Store .o and .d after successful compile2
3
4
5
6
This is conservative and explainable.
Relationship with BuildGraph
The BuildGraph tells Vix what compile tasks exist and which target needs them. The object cache tells Vix whether each compile task can be reused.
Together:
BuildGraph
selects compile tasks for the target
ObjectCache
decides which selected compile tasks can be restored2
3
4
5
This helps target-aware builds.
If you run:
vix build --build-target vixVix should focus on the compile tasks needed by the vix target. It should not assume the whole project must rebuild.
Relationship with BuildState
BuildState is faster than object cache.
BuildState answers:
Is this target already valid?ObjectCache answers:
Can this compile output be reused?So the order is:
BuildState hit
return early
BuildState miss
check ArtifactCache
ArtifactCache miss
use BuildGraph and ObjectCache2
3
4
5
6
7
8
Relationship with ArtifactCache
ArtifactCache is above ObjectCache.
If the final binary is already cached, restoring the full artifact is better than restoring individual objects.
Example:
ArtifactCache hit
restore build-ninja/vix
done
ArtifactCache miss
restore object files where possible
compile remaining files
link2
3
4
5
6
7
8
Object cache is still valuable when the final artifact cannot be reused.
Relationship with CMake and Ninja
Vix can use object caching while still preserving CMake and Ninja compatibility. For CMake projects, Vix should be conservative.
It can read:
compile_commands.json
build.ninja
dependency files
CMakeCache.txt2
3
4
Then it can build cache keys from real generated build data instead of guessing.
If Vix is unsure, it should delegate to Ninja.
Cache key examples
A cache key can be built from data like:
compiler: /usr/bin/g++
compiler_version: GCC 13.3.0
standard: c++20
source_hash: ...
header_hashes: ...
compile_flags_hash: ...
include_dirs_hash: ...
defines_hash: ...
target_triple: x86_64-linux-gnu
build_type: Debug2
3
4
5
6
7
8
9
10
The exact implementation can evolve.
The important rule stays stable:
same compile identity -> safe reuse
different compile identity -> cache miss2
Example workflow
Build a project:
vix buildClean the build directory:
rm -rf build-ninjaBuild again:
vix buildIf the object cache is warm, Vix can restore compile outputs instead of recompiling every file. The final link may still run if the target output is missing.
Example output shape
A verbose build can show object cache behavior:
Object cache
selected: 42
hits: 39
compiled: 3
stored: 32
3
4
5
This means:
42 compile tasks were considered
39 were restored from cache
3 were compiled
3 new outputs were stored2
3
4
Why object cache hits can still relink
Restoring object files does not always mean the final binary exists.
For example:
rm -f build-ninja/vix
vix build --build-target vix2
Even if all object files are restored, Vix may still need to link:
Relinking vix
reason: target output missing2
That is correct.
Object cache avoids recompilation. Artifact cache can avoid relinking.
When the object cache should hit
The object cache can hit when:
source file content is unchanged
headers used by the source are unchanged
compiler identity is unchanged
compile flags are unchanged
include directories are unchanged
definitions are unchanged
build type is unchanged
target platform is unchanged2
3
4
5
6
7
8
When the object cache should miss
The object cache should miss when:
source file changed
included header changed
compiler changed
C++ standard changed
compile flags changed
include directories changed
compile definitions changed
target architecture changed
build type changed
dependency data is missing or invalid
cache entry is incomplete2
3
4
5
6
7
8
9
10
11
Missing dependency data
If dependency data is missing, Vix should be careful. For example, if Vix cannot know which headers affected a compile task, it should not pretend that the object is safe.
The safe behavior is:
dependency information missing
cache miss or delegate2
This avoids stale object reuse.
Header changes
Header changes are one of the hardest parts of C++ caching. A header can affect many source files.
Example:
include/vix/cli/BuildOptions.hppIf this header changes, every source file that includes it directly or indirectly may need recompilation. The object cache should rely on dependency data and fingerprints, not only timestamps.
Timestamp is not enough
Timestamps are useful but not sufficient. A file can have a different timestamp with the same content. A file can also be restored in a way that changes timestamps. A safer cache uses content fingerprints.
The principle is:
content identity beats timestamp identityCache metadata
Each cached object entry should have metadata.
Example shape:
{
"source": "src/BuildCommand.cpp",
"object": "BuildCommand.cpp.o",
"deps": "BuildCommand.cpp.d",
"compiler": "g++",
"compiler_version": "13.3.0",
"standard": "c++20",
"build_type": "Debug",
"target_triple": "x86_64-linux-gnu",
"fingerprint": "..."
}2
3
4
5
6
7
8
9
10
11
The exact format can change. The goal is to make cache behavior inspectable and explainable.
Store location
Object cache data can live under the Vix cache area.
A possible layout:
~/.vix/cache/build/objects/
<compiler>/
<target-triple>/
<build-type>/
<fingerprint>/
object.o
deps.d
metadata.json2
3
4
5
6
7
8
The exact layout can evolve.
The important part is stable identity.
Local and global reuse
Object cache can help within one project. It can also help across projects when the compile identity is identical. Cross-project reuse must be conservative because paths, flags, includes, and dependencies can differ. If path-sensitive compiler flags are used, they must be included in the key.
CI usage
In CI, object cache can be useful if the cache directory is restored before build.
A CI workflow can restore:
~/.vix/cache/build/Then run:
vix build --preset releaseBut the cache key must include:
OS
compiler
compiler version
target architecture
build type
dependency versions2
3
4
5
6
Otherwise the cache can become unsafe.
Explainable object cache
Vix should make cache decisions visible.
Useful messages include:
object cache hit: BuildCommand.cpp
object cache miss: source file changed
object cache miss: header changed
object cache miss: compiler flags changed
object cache miss: compiler changed
object cache restored: 39 object(s)2
3
4
5
6
This helps developers trust the build system.
--explain
Use --explain to understand rebuild decisions:
vix build --explainFor a source change:
Rebuilding BuildCommand.cpp
reason: source file changed2
For a header change:
Rebuilding BuildCommand.cpp
reason: included header changed2
If Vix cannot map the change precisely, it should say so:
Project input changed
reason: dependency changed, delegating to Ninja2
That is better than claiming false precision.
Object cache and vix.app
vix.app makes object caching easier because the project inputs are structured.
It can explicitly describe:
sources
include_dirs
defines
links
compile_options
packages
resources
output_dir2
3
4
5
6
7
8
This gives Vix a cleaner path to compute compile identities. For generated CMake projects, Vix can still use the generated CMake and Ninja files as the compatibility layer.
Object cache and CMake projects
For arbitrary CMake projects, Vix should avoid guessing. CMake can run custom logic. So Vix should use generated build outputs:
compile_commands.json
build.ninja
CMakeCache.txt
dependency files2
3
4
This keeps caching compatible with existing C++ projects.
Good first implementation
A practical object cache should start with:
compile command fingerprinting
source content hash
dependency file preservation
object file preservation
compiler identity
build type
target triple
safe restore
clear hit and miss logs2
3
4
5
6
7
8
9
This is enough to provide real value without pretending to solve every C++ edge case on day one.
Later improvements
Later improvements can include:
more precise header graph tracking
better explain output
remote object cache
cache eviction
content-addressed storage
cross-project reuse
toolchain-aware cache namespaces
debug info path normalization2
3
4
5
6
7
8
The system should grow in layers.
Common workflows
# Normal build
vix build
# Build a specific target
vix build --build-target vix
# Explain rebuild decisions
vix build --explain --build-target vix
# Force clean build when needed
vix build --clean
# Inspect Vix cache paths
vix info2
3
4
5
6
7
8
9
10
11
12
13
14
Common mistakes
Thinking object cache avoids linking
Object cache avoids recompiling source files. It does not always avoid linking. Artifact cache is the layer that can restore final target outputs.
Reusing objects without compiler identity
Object files depend on compiler and ABI details. Compiler identity must be part of the cache key.
Ignoring headers
In C++, headers are part of compilation. A valid object cache must account for included headers.
Trusting timestamps only
Timestamps are not strong enough for safe reuse. Use content fingerprints and dependency information.
Treating cache misses as failures
A cache miss is normal. It only means Vix must compile normally.
Related commands
| Command | Purpose |
|---|---|
vix build | Build the project |
vix build --explain | Explain rebuild decisions |
vix build --clean | Force a clean rebuild |
vix info | Show cache and store paths |
vix clean | Remove project-local generated state |
vix reset | Clean and reinstall dependencies |
What you should remember
The object cache is the compile-output reuse layer.
It stores and restores:
.o files
.d files
compile metadata2
3
Its rule is:
reuse only when the full compile identity matchesIt does not replace CMake or Ninja. It works with the build graph, artifact cache, build state, and fallback path to make repeated C++ builds faster without becoming fragile.