Skip to content

Commit 458ae82

Browse files
committed
feat(build): add global artifact cache and deterministic config fingerprint
2 parents 2965353 + 40af4b2 commit 458ae82

File tree

7 files changed

+936
-35
lines changed

7 files changed

+936
-35
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/**
2+
*
3+
* @file ArtifactCache.hpp
4+
* @author Gaspard Kirira
5+
*
6+
* Copyright 2026, Gaspard Kirira. All rights reserved.
7+
* https://github.com/vixcpp/vix
8+
* Use of this source code is governed by a MIT license
9+
* that can be found in the License file.
10+
*
11+
* Vix.cpp
12+
*
13+
* Global compiled artifact cache
14+
*
15+
* This module provides a reusable cache layer for compiled packages.
16+
* Inspired by Zig/Mojo/Nix strategies, it allows:
17+
*
18+
* - Reuse of compiled dependencies across projects
19+
* - Avoid recompilation of unchanged packages
20+
* - Deterministic build reuse based on fingerprint
21+
*
22+
* Cache layout:
23+
*
24+
* ~/.vix/cache/build/
25+
* <target>/
26+
* <compiler>/
27+
* <build-type>/
28+
* <package>@<version>/
29+
* <fingerprint>/
30+
* include/
31+
* lib/
32+
* share/
33+
* manifest.json
34+
*
35+
*/
36+
37+
#ifndef VIX_CLI_ARTIFACT_CACHE_HPP
38+
#define VIX_CLI_ARTIFACT_CACHE_HPP
39+
40+
#include <filesystem>
41+
#include <optional>
42+
#include <string>
43+
44+
namespace vix::cli::cache
45+
{
46+
namespace fs = std::filesystem;
47+
48+
/**
49+
* @brief Description of a compiled artifact
50+
*
51+
* This structure identifies one reusable compiled artifact stored
52+
* in the global Vix cache.
53+
*/
54+
struct Artifact
55+
{
56+
std::string package; ///< Package name (ex: softadastra-core)
57+
std::string version; ///< Version (ex: 1.3.0)
58+
std::string target; ///< Target triple (ex: x86_64-linux-gnu)
59+
std::string compiler; ///< Compiler identity/version
60+
std::string buildType; ///< Build type (Debug / Release)
61+
std::string fingerprint; ///< Unique fingerprint of the build configuration
62+
63+
fs::path root; ///< Artifact root directory
64+
fs::path include; ///< Include directory
65+
fs::path lib; ///< Library directory
66+
};
67+
68+
/**
69+
* @brief Global artifact cache manager
70+
*
71+
* This class is responsible for:
72+
* - locating the global cache root
73+
* - computing deterministic artifact paths
74+
* - checking cache existence
75+
* - preparing artifact layout
76+
* - writing and reading manifest metadata
77+
*/
78+
class ArtifactCache
79+
{
80+
public:
81+
/**
82+
* @brief Return the global artifact cache root directory
83+
*
84+
* Usually:
85+
* ~/.vix/cache/build
86+
*
87+
* @return Cache root path
88+
*/
89+
static fs::path cache_root();
90+
91+
/**
92+
* @brief Compute the on-disk path of an artifact
93+
*
94+
* The path is derived from:
95+
* target / compiler / build-type / package@version / fingerprint
96+
*
97+
* @param a Artifact descriptor
98+
* @return Artifact root path
99+
*/
100+
static fs::path artifact_path(const Artifact &a);
101+
102+
/**
103+
* @brief Check whether an artifact exists in the cache
104+
*
105+
* A valid artifact is expected to contain:
106+
* - include/
107+
* - lib/
108+
* - manifest.json
109+
*
110+
* @param a Artifact descriptor
111+
* @return true if the artifact is available
112+
*/
113+
static bool exists(const Artifact &a);
114+
115+
/**
116+
* @brief Resolve an artifact from cache
117+
*
118+
* If the artifact exists, this returns a copy with resolved
119+
* root/include/lib paths filled in.
120+
*
121+
* @param a Artifact descriptor
122+
* @return Resolved artifact, or std::nullopt if missing
123+
*/
124+
static std::optional<Artifact> resolve(const Artifact &a);
125+
126+
/**
127+
* @brief Ensure the directory layout for an artifact exists
128+
*
129+
* The layout includes:
130+
* - root/
131+
* - include/
132+
* - lib/
133+
* - share/
134+
*
135+
* @param a Artifact descriptor
136+
* @return true on success
137+
*/
138+
static bool ensure_layout(const Artifact &a);
139+
140+
/**
141+
* @brief Write the manifest metadata for an artifact
142+
*
143+
* This also ensures the layout exists before writing.
144+
*
145+
* @param a Artifact descriptor
146+
* @return true on success
147+
*/
148+
static bool write_manifest(const Artifact &a);
149+
150+
/**
151+
* @brief Read an artifact manifest from disk
152+
*
153+
* @param artifactRoot Root directory of the artifact
154+
* @return Parsed artifact metadata, or std::nullopt if invalid
155+
*/
156+
static std::optional<Artifact> read_manifest(const fs::path &artifactRoot);
157+
};
158+
159+
} // namespace vix::cli::cache
160+
161+
#endif

include/vix/cli/cmake/GlobalPackages.hpp

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,19 @@
99
* that can be found in the License file.
1010
*
1111
* Vix.cpp
12+
*
13+
* Global packages integration for vix build.
14+
*
15+
* This module is responsible for:
16+
*
17+
* - Loading globally installed Vix packages
18+
* - Generating the CMake integration file used by `vix build`
19+
* - Injecting header-only packages
20+
* - Preparing compiled package reuse through cached artifact prefixes
21+
* - Falling back to source-based integration when needed
22+
*
1223
*/
24+
1325
#ifndef VIX_CLI_CMAKE_GLOBALPACKAGES_HPP
1426
#define VIX_CLI_CMAKE_GLOBALPACKAGES_HPP
1527

@@ -21,17 +33,49 @@ namespace vix::cli::build
2133
{
2234
namespace fs = std::filesystem;
2335

36+
/**
37+
* @brief Description of a globally installed package
38+
*
39+
* This structure represents one package entry loaded from:
40+
*
41+
* ~/.vix/global/installed.json
42+
*
43+
* A package can be:
44+
* - header-only
45+
* - source-based / compiled
46+
*
47+
* For compiled packages, Vix may also look for reusable cached
48+
* artifact prefixes under:
49+
*
50+
* ~/.vix/cache/build/
51+
*/
2452
struct GlobalPackage
2553
{
26-
std::string id;
27-
std::string pkgDir;
28-
std::string includeDir{"include"};
29-
std::string type{"header-only"};
30-
fs::path installedPath;
54+
std::string id; ///< Full package id (ex: @softadastra/core@1.3.0)
55+
std::string pkgDir; ///< Normalized package directory key
56+
std::string includeDir{"include"}; ///< Include directory relative to installedPath
57+
std::string type{"header-only"}; ///< Package type: header-only or compiled/source
58+
fs::path installedPath; ///< Installed source/package root path
3159
};
3260

61+
/**
62+
* @brief Load globally installed packages from the global manifest
63+
*
64+
* @return Vector of discovered global packages
65+
*/
3366
std::vector<GlobalPackage> load_global_packages();
3467

68+
/**
69+
* @brief Generate the CMake integration file for global packages
70+
*
71+
* The generated CMake content can:
72+
* - add include directories for header-only packages
73+
* - inject artifact cache prefixes into CMAKE_PREFIX_PATH
74+
* - fallback to add_subdirectory(...) for source packages
75+
*
76+
* @param packages List of global packages
77+
* @return Generated CMake script content
78+
*/
3579
std::string make_global_packages_cmake(
3680
const std::vector<GlobalPackage> &packages);
3781

include/vix/cli/util/Hash.hpp

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,18 @@
99
* that can be found in the License file.
1010
*
1111
* Vix.cpp
12+
*
13+
* Hash and fingerprint utilities for the CLI build system.
14+
*
15+
* This module provides:
16+
*
17+
* - FNV-1a 64-bit hashing helpers
18+
* - SHA-256 helpers for files and directories
19+
* - CMake configuration fingerprint generation
20+
* - Signature helpers for build/config cache validation
21+
*
1222
*/
23+
1324
#ifndef VIX_CLI_HASH_HPP
1425
#define VIX_CLI_HASH_HPP
1526

@@ -24,18 +35,94 @@ namespace vix::cli::util
2435
{
2536
namespace fs = std::filesystem;
2637

27-
// Hashing primitives (FNV-1a 64-bit)
38+
/**
39+
* @brief Compute a FNV-1a 64-bit hash from raw bytes
40+
*
41+
* @param data Input byte buffer
42+
* @param n Number of bytes
43+
* @param seed Initial hash seed
44+
* @return 64-bit hash value
45+
*/
2846
std::uint64_t fnv1a64_bytes(const void *data, std::size_t n, std::uint64_t seed);
47+
48+
/**
49+
* @brief Compute a FNV-1a 64-bit hash from a string
50+
*
51+
* @param s Input string
52+
* @param seed Initial hash seed
53+
* @return 64-bit hash value
54+
*/
2955
std::uint64_t fnv1a64_str(const std::string &s, std::uint64_t seed);
56+
57+
/**
58+
* @brief Convert a 64-bit integer hash to lowercase hexadecimal
59+
*
60+
* @param v Hash value
61+
* @return 16-character hexadecimal string
62+
*/
3063
std::string hex64(std::uint64_t v);
64+
65+
/**
66+
* @brief Read and hash a file using FNV-1a 64-bit
67+
*
68+
* @param p File path
69+
* @return Hexadecimal hash string if successful
70+
*/
3171
std::optional<std::string> read_file_hash_hex(const fs::path &p);
3272

33-
// SHA256 hashing
73+
/**
74+
* @brief Compute the SHA-256 hash of a file
75+
*
76+
* @param p File path
77+
* @return SHA-256 hex string if successful
78+
*/
3479
std::optional<std::string> sha256_file(const fs::path &p);
80+
81+
/**
82+
* @brief Compute a deterministic SHA-256 hash of a directory
83+
*
84+
* The directory hash is based on:
85+
* - relative file paths
86+
* - individual file SHA-256 hashes
87+
*
88+
* @param dir Directory path
89+
* @return SHA-256 hex string if successful
90+
*/
3591
std::optional<std::string> sha256_directory(const fs::path &dir);
3692

93+
/**
94+
* @brief Compute the fingerprint of the CMake-related project configuration
95+
*
96+
* This fingerprint is intended for configure-cache validation and should
97+
* only depend on files that affect CMake configuration, such as:
98+
* - CMakeLists.txt
99+
* - .cmake files
100+
* - CMakePresets.json / CMakeUserPresets.json
101+
* - vix.json / vix.lock when relevant to configuration
102+
*
103+
* @param projectDir Project root directory
104+
* @return Deterministic configuration fingerprint
105+
*/
37106
std::string compute_cmake_config_fingerprint(const fs::path &projectDir);
107+
108+
/**
109+
* @brief Check whether a saved signature file matches a provided signature
110+
*
111+
* @param sigFile Signature file path
112+
* @param sig Expected signature content
113+
* @return true if the stored signature exactly matches
114+
*/
38115
bool signature_matches(const fs::path &sigFile, const std::string &sig);
116+
117+
/**
118+
* @brief Join key/value pairs into a deterministic signature block
119+
*
120+
* Each pair is serialized as:
121+
* key=value
122+
*
123+
* @param kvs Key/value pairs
124+
* @return Joined signature text
125+
*/
39126
std::string signature_join(const std::vector<std::pair<std::string, std::string>> &kvs);
40127

41128
} // namespace vix::cli::util

0 commit comments

Comments
 (0)