Skip to content

Commit 15ec791

Browse files
committed
feat(cli): add incremental build graph
2 parents e553b12 + c7773e2 commit 15ec791

15 files changed

Lines changed: 5946 additions & 80 deletions
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
/**
2+
*
3+
* @file BuildGraph.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+
* Incremental build graph
14+
*
15+
*/
16+
17+
#ifndef VIX_CLI_BUILD_BUILD_GRAPH_HPP
18+
#define VIX_CLI_BUILD_BUILD_GRAPH_HPP
19+
20+
#include <cstdint>
21+
#include <filesystem>
22+
#include <optional>
23+
#include <string>
24+
#include <unordered_map>
25+
#include <vector>
26+
27+
#include <vix/cli/build/BuildNode.hpp>
28+
#include <vix/cli/build/BuildTask.hpp>
29+
#include <vix/cli/build/DependencyFile.hpp>
30+
31+
namespace vix::cli::build
32+
{
33+
namespace fs = std::filesystem;
34+
35+
/**
36+
* @brief Configuration used to scan and build a project graph.
37+
*
38+
* This structure tells Vix where the project lives, where generated build
39+
* files should be stored, which compiler command should be used, and which
40+
* global build fingerprint should be attached to object cache keys.
41+
*/
42+
struct BuildGraphConfig
43+
{
44+
fs::path projectDir; ///< Project root directory
45+
fs::path buildDir; ///< Build directory used by Vix
46+
fs::path objectDir; ///< Directory where object files are generated
47+
48+
std::string compiler{"c++"}; ///< Compiler executable
49+
std::string buildFingerprint; ///< Global build/config fingerprint
50+
51+
std::vector<std::string> includeDirs; ///< Include directories
52+
std::vector<std::string> defines; ///< Preprocessor defines
53+
std::vector<std::string> flags; ///< Extra compiler flags
54+
55+
/**
56+
* @brief Check whether the configuration has enough data.
57+
*
58+
* @return true if projectDir and buildDir are not empty
59+
*/
60+
bool valid() const;
61+
};
62+
63+
/**
64+
* @brief Summary returned after scanning a project graph.
65+
*/
66+
struct BuildGraphScanResult
67+
{
68+
std::size_t sources{0}; ///< Number of source files found
69+
std::size_t headers{0}; ///< Number of header files found
70+
std::size_t configs{0}; ///< Number of config files found
71+
std::size_t tasks{0}; ///< Number of generated tasks
72+
};
73+
74+
/**
75+
* @brief Incremental build graph for Vix projects.
76+
*
77+
* BuildGraph is the central model used by the new Vix build architecture.
78+
*
79+
* It stores:
80+
* - BuildNode entries for sources, headers, config files, objects and outputs
81+
* - BuildTask entries for compile/link/archive actions
82+
* - dependency links extracted from compiler .d files
83+
*
84+
* The graph is designed to support:
85+
* - dirty/clean analysis
86+
* - object cache reuse
87+
* - parallel task execution
88+
* - deterministic rebuild decisions
89+
*/
90+
class BuildGraph
91+
{
92+
public:
93+
/**
94+
* @brief Create an empty build graph.
95+
*/
96+
BuildGraph() = default;
97+
98+
/**
99+
* @brief Create a build graph with a configuration.
100+
*
101+
* @param config Build graph configuration
102+
*/
103+
explicit BuildGraph(BuildGraphConfig config);
104+
105+
/**
106+
* @brief Return the build graph configuration.
107+
*
108+
* @return Build graph configuration
109+
*/
110+
const BuildGraphConfig &config() const;
111+
112+
/**
113+
* @brief Set the build graph configuration.
114+
*
115+
* @param config Build graph configuration
116+
*/
117+
void set_config(BuildGraphConfig config);
118+
119+
/**
120+
* @brief Remove all nodes and tasks.
121+
*/
122+
void clear();
123+
124+
/**
125+
* @brief Check whether the graph is empty.
126+
*
127+
* @return true if there are no nodes and no tasks
128+
*/
129+
bool empty() const;
130+
131+
/**
132+
* @brief Add or replace a build node.
133+
*
134+
* @param node Build node
135+
* @return true if the node was valid and stored
136+
*/
137+
bool add_node(const BuildNode &node);
138+
139+
/**
140+
* @brief Add or replace a build task.
141+
*
142+
* @param task Build task
143+
* @return true if the task was valid and stored
144+
*/
145+
bool add_task(const BuildTask &task);
146+
147+
/**
148+
* @brief Find a node by id.
149+
*
150+
* @param id Node id
151+
* @return Node pointer, or nullptr if not found
152+
*/
153+
BuildNode *find_node(const std::string &id);
154+
155+
/**
156+
* @brief Find a node by id.
157+
*
158+
* @param id Node id
159+
* @return Node pointer, or nullptr if not found
160+
*/
161+
const BuildNode *find_node(const std::string &id) const;
162+
163+
/**
164+
* @brief Find a task by id.
165+
*
166+
* @param id Task id
167+
* @return Task pointer, or nullptr if not found
168+
*/
169+
BuildTask *find_task(const std::string &id);
170+
171+
/**
172+
* @brief Find a task by id.
173+
*
174+
* @param id Task id
175+
* @return Task pointer, or nullptr if not found
176+
*/
177+
const BuildTask *find_task(const std::string &id) const;
178+
179+
/**
180+
* @brief Return all nodes.
181+
*
182+
* @return Node map
183+
*/
184+
const std::unordered_map<std::string, BuildNode> &nodes() const;
185+
186+
/**
187+
* @brief Return all tasks.
188+
*
189+
* @return Task map
190+
*/
191+
const std::unordered_map<std::string, BuildTask> &tasks() const;
192+
193+
/**
194+
* @brief Return node ids in deterministic order.
195+
*
196+
* @return Sorted node ids
197+
*/
198+
std::vector<std::string> sorted_node_ids() const;
199+
200+
/**
201+
* @brief Return task ids in deterministic order.
202+
*
203+
* @return Sorted task ids
204+
*/
205+
std::vector<std::string> sorted_task_ids() const;
206+
207+
/**
208+
* @brief Scan the project directory and generate source/header/config nodes.
209+
*
210+
* This does not run the compiler. It only discovers files and creates
211+
* initial compile tasks.
212+
*
213+
* @return Scan summary
214+
*/
215+
BuildGraphScanResult scan_project();
216+
217+
/**
218+
* @brief Load dependency files from the object directory and connect nodes.
219+
*
220+
* This reads .d files generated by GCC/Clang and links source/header
221+
* dependencies into the graph.
222+
*/
223+
void load_dependency_files();
224+
225+
/**
226+
* @brief Mark all nodes dirty.
227+
*/
228+
void mark_all_dirty();
229+
230+
/**
231+
* @brief Mark nodes clean when their metadata matches a previous graph.
232+
*
233+
* Nodes are considered clean when kind, path, size, mtime and hash match.
234+
*
235+
* @param previous Previous graph
236+
*/
237+
void mark_clean_from_previous(const BuildGraph &previous);
238+
239+
/**
240+
* @brief Propagate dirty state through dependencies.
241+
*
242+
* If a dependency is dirty or missing, dependent nodes become dirty.
243+
*/
244+
void propagate_dirty();
245+
246+
/**
247+
* @brief Return all compile tasks.
248+
*
249+
* @return Compile tasks
250+
*/
251+
std::vector<BuildTask> compile_tasks() const;
252+
253+
/**
254+
* @brief Return all dirty compile tasks.
255+
*
256+
* A compile task is dirty if one of its input or output nodes is dirty
257+
* or missing.
258+
*
259+
* @return Dirty compile tasks
260+
*/
261+
std::vector<BuildTask> dirty_compile_tasks() const;
262+
263+
/**
264+
* @brief Check whether a task should run.
265+
*
266+
* @param task Task to inspect
267+
* @return true if one input/output is dirty or missing
268+
*/
269+
bool task_is_dirty(const BuildTask &task) const;
270+
271+
/**
272+
* @brief Compute a stable graph fingerprint.
273+
*
274+
* The fingerprint includes nodes, node hashes, dependencies, tasks and
275+
* task command hashes.
276+
*
277+
* @return Stable hexadecimal fingerprint
278+
*/
279+
std::string fingerprint() const;
280+
281+
/**
282+
* @brief Save graph state to disk.
283+
*
284+
* @param path Output path
285+
* @return true on success
286+
*/
287+
bool save(const fs::path &path) const;
288+
289+
/**
290+
* @brief Load graph state from disk.
291+
*
292+
* @param path Input path
293+
* @return Parsed graph, or std::nullopt if invalid
294+
*/
295+
static std::optional<BuildGraph> load(const fs::path &path);
296+
297+
/**
298+
* @brief Return the default graph state path for a build directory.
299+
*
300+
* @param buildDir Build directory
301+
* @return Graph state path
302+
*/
303+
static fs::path default_graph_path(const fs::path &buildDir);
304+
305+
private:
306+
BuildGraphConfig config_{};
307+
std::unordered_map<std::string, BuildNode> nodes_;
308+
std::unordered_map<std::string, BuildTask> tasks_;
309+
310+
fs::path object_path_for_source(const fs::path &source) const;
311+
fs::path dependency_path_for_source(const fs::path &source) const;
312+
313+
std::vector<std::string> compile_command_for(
314+
const fs::path &source,
315+
const fs::path &object,
316+
const fs::path &dependencyFile) const;
317+
};
318+
319+
} // namespace vix::cli::build
320+
321+
#endif

0 commit comments

Comments
 (0)