Skip to content

Commit 2f277fd

Browse files
committed
feat(build): make graph executor production-safe
1 parent 0690789 commit 2f277fd

3 files changed

Lines changed: 167 additions & 23 deletions

File tree

include/vix/cli/build/BuildGraphExecutor.hpp

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
*
1111
* Vix.cpp
1212
*
13-
* Target-aware build graph executor
13+
* Production-safe target-aware build graph executor
1414
*
1515
*/
1616

1717
#ifndef VIX_CLI_BUILD_BUILD_GRAPH_EXECUTOR_HPP
1818
#define VIX_CLI_BUILD_BUILD_GRAPH_EXECUTOR_HPP
1919

20+
#include <cstddef>
2021
#include <filesystem>
2122
#include <string>
2223
#include <vector>
@@ -28,6 +29,46 @@ namespace vix::cli::build
2829
{
2930
namespace fs = std::filesystem;
3031

32+
enum class BuildGraphExecutorStatus
33+
{
34+
Success,
35+
UpToDate,
36+
DelegatedToNinja,
37+
InvalidRequest,
38+
InvalidGraph,
39+
UnsupportedTarget,
40+
CompileFailed,
41+
NinjaFailed,
42+
CacheFailed
43+
};
44+
45+
inline const char *to_string(BuildGraphExecutorStatus status)
46+
{
47+
switch (status)
48+
{
49+
case BuildGraphExecutorStatus::Success:
50+
return "success";
51+
case BuildGraphExecutorStatus::UpToDate:
52+
return "up-to-date";
53+
case BuildGraphExecutorStatus::DelegatedToNinja:
54+
return "delegated-to-ninja";
55+
case BuildGraphExecutorStatus::InvalidRequest:
56+
return "invalid-request";
57+
case BuildGraphExecutorStatus::InvalidGraph:
58+
return "invalid-graph";
59+
case BuildGraphExecutorStatus::UnsupportedTarget:
60+
return "unsupported-target";
61+
case BuildGraphExecutorStatus::CompileFailed:
62+
return "compile-failed";
63+
case BuildGraphExecutorStatus::NinjaFailed:
64+
return "ninja-failed";
65+
case BuildGraphExecutorStatus::CacheFailed:
66+
return "cache-failed";
67+
default:
68+
return "unknown";
69+
}
70+
}
71+
3172
struct BuildGraphExecutorOptions
3273
{
3374
fs::path buildDir;
@@ -36,13 +77,36 @@ namespace vix::cli::build
3677
int jobs{0};
3778
bool quiet{false};
3879
bool verbose{false};
80+
81+
/*
82+
* Production rule:
83+
* The graph executor is an optimization layer.
84+
* Ninja remains the source of truth when the graph is incomplete,
85+
* ambiguous, unsupported or too risky.
86+
*/
87+
bool allowNinjaFallback{true};
88+
89+
/*
90+
* 0 means no artificial limit.
91+
* Production builds should not fail just because many files are dirty.
92+
* Large dirty sets can be delegated to Ninja instead.
93+
*/
94+
std::size_t maxGraphDirtyCompileTasks{0};
3995
};
4096

4197
struct BuildGraphExecutorResult
4298
{
4399
bool ok{false};
44100

101+
BuildGraphExecutorStatus status{BuildGraphExecutorStatus::InvalidRequest};
102+
45103
std::string target;
104+
std::string reason;
105+
106+
bool usedGraph{false};
107+
bool usedNinja{false};
108+
bool usedFallback{false};
109+
46110
std::size_t selectedTasks{0};
47111
std::size_t selectedCompileTasks{0};
48112
std::size_t dirtyCompileTasks{0};
@@ -51,6 +115,11 @@ namespace vix::cli::build
51115

52116
int exitCode{0};
53117
std::string output;
118+
119+
bool success() const
120+
{
121+
return ok && exitCode == 0;
122+
}
54123
};
55124

56125
class BuildGraphExecutor

src/build/BuildGraph.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ namespace vix::cli::build
3030
{
3131
static constexpr std::uint64_t FNV_OFFSET = 1469598103934665603ull;
3232
static constexpr std::uint64_t FNV_PRIME = 1099511628211ull;
33+
static constexpr const char *BUILD_GRAPH_MAGIC = "vix-build-graph";
3334

3435
static std::uint64_t fnv_mix(
3536
std::uint64_t h,
@@ -1269,7 +1270,7 @@ namespace vix::cli::build
12691270
bool BuildGraph::save(const fs::path &path) const
12701271
{
12711272
std::ostringstream out;
1272-
out << "vix-build-graph-v1\n";
1273+
out << BUILD_GRAPH_MAGIC << "\n";
12731274
out << "project=" << escape_field(config_.projectDir.string()) << "\n";
12741275
out << "build=" << escape_field(config_.buildDir.string()) << "\n";
12751276
out << "object=" << escape_field(config_.objectDir.string()) << "\n";
@@ -1326,7 +1327,7 @@ namespace vix::cli::build
13261327
if (!std::getline(in, magic))
13271328
return std::nullopt;
13281329

1329-
if (magic != "vix-build-graph-v1")
1330+
if (magic != BUILD_GRAPH_MAGIC)
13301331
return std::nullopt;
13311332

13321333
BuildGraph graph;

src/build/BuildGraphExecutor.cpp

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,25 @@ namespace vix::cli::build
6767
return false;
6868
}
6969

70+
static BuildGraphExecutorResult make_failed_result(
71+
const BuildGraphExecutorOptions &options,
72+
BuildGraphExecutorStatus status,
73+
int exitCode,
74+
const std::string &reason)
75+
{
76+
BuildGraphExecutorResult result;
77+
result.ok = false;
78+
result.status = status;
79+
result.target = options.target;
80+
result.reason = reason;
81+
result.exitCode = exitCode;
82+
83+
if (!reason.empty())
84+
result.output = reason + "\n";
85+
86+
return result;
87+
}
88+
7089
static std::unordered_map<std::string, std::string>
7190
make_output_to_task_map(const BuildGraph &graph)
7291
{
@@ -107,9 +126,10 @@ namespace vix::cli::build
107126
const BuildTask &task = kv.second;
108127

109128
/*
110-
* Safe V1:
111-
* Link/Archive -> Graph Executor can handle target closure.
112-
* Copy/Install/Generate/Utility/phony -> fallback CMake/Ninja.
129+
* Production policy:
130+
* Only real link/archive targets are executed through the graph path.
131+
* Generated, phony, copy and install targets are delegated to Ninja because
132+
* Ninja remains the authoritative executor for complex build semantics.
113133
*/
114134
if (!is_real_graph_target_task(task))
115135
continue;
@@ -374,6 +394,64 @@ namespace vix::cli::build
374394
return task;
375395
}
376396

397+
static BuildGraphExecutorResult run_ninja_target_fallback(
398+
const BuildGraphExecutorOptions &options,
399+
BuildGraphExecutorStatus status,
400+
const std::string &reason)
401+
{
402+
if (!options.allowNinjaFallback)
403+
{
404+
return make_failed_result(
405+
options,
406+
status,
407+
2,
408+
reason);
409+
}
410+
411+
BuildGraphExecutorResult result;
412+
result.target = options.target;
413+
result.status = BuildGraphExecutorStatus::DelegatedToNinja;
414+
result.reason = reason;
415+
result.usedNinja = true;
416+
result.usedFallback = true;
417+
418+
BuildTask ninjaTargetTask =
419+
make_ninja_target_task(
420+
options.buildDir,
421+
options.target);
422+
423+
const process::ExecResult ninjaResult =
424+
run_process_live_to_log(
425+
ninjaTargetTask.command,
426+
{},
427+
options.buildDir / "build.log",
428+
options.quiet,
429+
/*cmakeVerbose=*/false,
430+
/*progressOnly=*/false);
431+
432+
result.exitCode = ninjaResult.exitCode;
433+
434+
if (!reason.empty())
435+
result.output = reason + "\n";
436+
437+
if (ninjaResult.producedOutput && !ninjaResult.capturedFirstLine.empty())
438+
result.output += ninjaResult.capturedFirstLine + "\n";
439+
440+
if (ninjaResult.exitCode == 0)
441+
{
442+
result.ok = true;
443+
return result;
444+
}
445+
446+
result.ok = false;
447+
result.status = BuildGraphExecutorStatus::NinjaFailed;
448+
449+
if (!ninjaResult.displayCommand.empty())
450+
result.output += ninjaResult.displayCommand + "\n";
451+
452+
return result;
453+
}
454+
377455
static bool graph_debug_logs_enabled()
378456
{
379457
const char *level = std::getenv("VIX_LOG_LEVEL");
@@ -441,13 +519,10 @@ namespace vix::cli::build
441519

442520
if (!targetTask)
443521
{
444-
result.ok = false;
445-
result.exitCode = 2;
446-
result.output =
447-
"Unable to resolve a unique real graph output target: " +
448-
options_.target +
449-
"\n";
450-
return result;
522+
return run_ninja_target_fallback(
523+
options_,
524+
BuildGraphExecutorStatus::UnsupportedTarget,
525+
"Unable to resolve a unique graph output target: " + options_.target);
451526
}
452527

453528
graph_log(
@@ -503,18 +578,17 @@ namespace vix::cli::build
503578
"graph: dirty compile tasks: " +
504579
std::to_string(result.dirtyCompileTasks));
505580

506-
if (result.dirtyCompileTasks > 128)
581+
if (options_.maxGraphDirtyCompileTasks > 0 &&
582+
result.dirtyCompileTasks > options_.maxGraphDirtyCompileTasks)
507583
{
508-
result.ok = false;
509-
result.exitCode = 2;
510-
result.output =
584+
return run_ninja_target_fallback(
585+
options_,
586+
BuildGraphExecutorStatus::DelegatedToNinja,
511587
"Graph target has too many dirty compile tasks: " +
512-
std::to_string(result.dirtyCompileTasks) +
513-
" dirty tasks from " +
514-
std::to_string(result.selectedCompileTasks) +
515-
" selected compile tasks. Falling back to Ninja.\n";
516-
517-
return result;
588+
std::to_string(result.dirtyCompileTasks) +
589+
" dirty tasks from " +
590+
std::to_string(result.selectedCompileTasks) +
591+
" selected compile tasks. Delegating to Ninja.");
518592
}
519593

520594
if (!dirtyCompileTasks.empty())

0 commit comments

Comments
 (0)