@@ -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