Skip to content

Commit 34259fb

Browse files
committed
feat(build): improve explain safety and graph fallback
2 parents 73020b5 + 0690789 commit 34259fb

8 files changed

Lines changed: 1406 additions & 194 deletions

File tree

include/vix/cli/cache/ArtifactCache.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ namespace vix::cli::cache
281281
const std::string &signature,
282282
const std::string &projectFingerprint,
283283
const std::string &buildTarget,
284+
const std::string &preset,
285+
const std::string &buildType,
286+
const std::string &target,
287+
const std::string &compiler,
284288
const std::vector<ProjectInput> &currentInputs);
285289

286290
/**

include/vix/cli/process/Process.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ namespace vix::cli::process
102102
*/
103103
bool verbose = false;
104104

105+
/**
106+
* @brief Explains why Vix rebuilds files or targets.
107+
*/
108+
bool explain = false;
109+
105110
/**
106111
* @brief Optional project directory passed with `--dir`.
107112
*/

src/ErrorHandler.cpp

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#include <unordered_set>
3131
#include <vector>
3232
#include <fstream>
33+
#include <cstdlib>
34+
#include <cctype>
3335

3436
#include <vix/cli/Style.hpp>
3537
#include <vix/cli/build/BuildStyle.hpp>
@@ -102,11 +104,47 @@ namespace
102104
{
103105
const std::string &message = err.message;
104106

107+
if (message.find("No such file or directory") != std::string::npos ||
108+
message.find("file not found") != std::string::npos)
109+
{
110+
return "Check that the header exists and that its directory is listed in include_dirs, target_include_directories, or your compiler include paths.";
111+
}
112+
113+
if (message.find("fatal error:") != std::string::npos &&
114+
message.find("#include") != std::string::npos)
115+
{
116+
return "A header could not be found. Check the include path or the header name.";
117+
}
118+
105119
if (message.find("use of undeclared identifier 'std'") != std::string::npos)
120+
{
106121
return "Include the required standard header before using std names.";
122+
}
123+
124+
if (message.find("use of undeclared identifier") != std::string::npos)
125+
{
126+
return "Declare the symbol before use, include the right header, or check the namespace.";
127+
}
128+
129+
if (message.find("was not declared in this scope") != std::string::npos)
130+
{
131+
return "Declare the symbol before use, include the right header, or move the function definition above the call.";
132+
}
133+
134+
if (message.find("does not name a type") != std::string::npos)
135+
{
136+
return "Check that the type is declared before use and that the correct header is included.";
137+
}
107138

108139
if (message.find("expected ';'") != std::string::npos)
140+
{
109141
return "Add the missing semicolon, often on the previous line.";
142+
}
143+
144+
if (message.find("expected primary-expression") != std::string::npos)
145+
{
146+
return "Check the expression syntax near this location, especially missing operators, parentheses, or variables.";
147+
}
110148

111149
if (message.find("no matching function for call to") != std::string::npos)
112150
{
@@ -120,11 +158,15 @@ namespace
120158
return "Check argument types, overloads, const qualifiers, and references.";
121159
}
122160

123-
if (message.find("was not declared in this scope") != std::string::npos)
124-
return "Declare the symbol before use, include the right header, or move the function definition above the call.";
161+
if (message.find("undefined reference to") != std::string::npos)
162+
{
163+
return "A symbol is declared but not linked. Check missing .cpp files, libraries, or target_link_libraries.";
164+
}
125165

126166
if (message.find("defined but not used") != std::string::npos)
167+
{
127168
return "Remove the unused function or mark it intentionally unused.";
169+
}
128170

129171
return {};
130172
}
@@ -302,7 +344,22 @@ namespace vix::cli
302344

303345
print_hint("run with --verbose to inspect the full build output");
304346

305-
if (!cleanedLog.empty())
347+
const bool debugOutput = []()
348+
{
349+
const char *level = std::getenv("VIX_LOG_LEVEL");
350+
351+
if (!level || !*level)
352+
return false;
353+
354+
std::string value(level);
355+
356+
for (char &c : value)
357+
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
358+
359+
return value == "debug" || value == "trace";
360+
}();
361+
362+
if (debugOutput && !cleanedLog.empty())
306363
{
307364
std::cerr << "\n"
308365
<< GRAY
@@ -315,6 +372,10 @@ namespace vix::cli
315372
if (cleanedLog.back() != '\n')
316373
std::cerr << "\n";
317374
}
375+
else
376+
{
377+
print_hint("run with VIX_LOG_LEVEL=debug to inspect the full compiler output");
378+
}
318379

319380
return false;
320381
}

src/build/BuildGraph.cpp

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,16 @@ namespace vix::cli::build
507507
return BuildNodeKind::Config;
508508
}
509509

510+
/*
511+
* Utility/phony/generated outputs are intentionally imported as Config.
512+
*
513+
* They are useful as graph dependencies, but they are not safe direct
514+
* Graph Executor targets yet. BuildGraphExecutor will reject them and
515+
* fallback to CMake/Ninja.
516+
*/
517+
if (edge.kind == NinjaEdgeKind::Utility)
518+
return BuildNodeKind::Config;
519+
510520
return BuildNodeKind::Unknown;
511521
}
512522

@@ -592,12 +602,23 @@ namespace vix::cli::build
592602
if (!edge.valid())
593603
return false;
594604

605+
/*
606+
* Compile commands are imported from compile_commands.json because that
607+
* gives Vix the exact compiler argv, working directory and object output.
608+
*/
595609
if (edge.kind == NinjaEdgeKind::Compile)
596610
return false;
597611

598612
if (edge.kind == NinjaEdgeKind::Unknown)
599613
return false;
600614

615+
/*
616+
* Import Link/Archive/Copy/Install/Utility edges.
617+
*
618+
* The executor will decide later if a target is safe to execute through
619+
* Graph Executor. Importing the DAG is useful even when execution falls
620+
* back to CMake/Ninja.
621+
*/
601622
return true;
602623
}
603624
} // namespace
@@ -863,6 +884,16 @@ namespace vix::cli::build
863884

864885
std::size_t imported = 0;
865886

887+
std::unordered_map<std::string, std::string> outputToTask;
888+
889+
for (const auto &kv : tasks_)
890+
{
891+
const BuildTask &task = kv.second;
892+
893+
for (const std::string &outputId : task.outputs)
894+
outputToTask[outputId] = task.id;
895+
}
896+
866897
for (const NinjaEdge &edge : ninjaBuild->edges)
867898
{
868899
if (!should_import_ninja_edge(edge))
@@ -881,11 +912,10 @@ namespace vix::cli::build
881912
task.workingDirectory = ninjaBuild->directory;
882913

883914
/*
884-
* Do not expand Ninja rule commands here.
915+
* We intentionally delegate non-compile Ninja edges back to Ninja.
885916
*
886-
* build.ninja is already a complete execution graph. For now we import
887-
* the DAG structure only. Execution remains delegated to Ninja/CMake until
888-
* Vix has a full Ninja variable expander and target-aware executor.
917+
* Vix imports the DAG and target metadata here, but does not yet expand
918+
* Ninja variables or reimplement every CMake-generated build rule.
889919
*/
890920
task.command = {
891921
"ninja",
@@ -895,44 +925,39 @@ namespace vix::cli::build
895925

896926
task.commandHash = command_hash_for_argv(task.command);
897927

898-
for (const fs::path &input : edge.explicitInputs)
928+
std::vector<std::string> inputNodeIds;
929+
930+
auto import_input = [&](const fs::path &input)
899931
{
900932
const BuildNodeKind inputKind = node_kind_from_ninja_input(input);
901933

902934
BuildNode inputNode = make_file_build_node(inputKind, input);
903935

904936
/*
905937
* Keep Ninja import cheap.
906-
* scan_project() and load_dependency_files() already hash real project
907-
* inputs where needed. Ninja edges may reference many generated files.
938+
* Real source/header hashes are provided by scan_project() and .d files.
939+
* Ninja can reference many generated/internal files.
908940
*/
909941
inputNode.hash.clear();
910942

911943
add_node(inputNode);
944+
912945
task.add_input(inputNode.id);
913-
}
946+
inputNodeIds.push_back(inputNode.id);
914947

915-
for (const fs::path &input : edge.implicitInputs)
916-
{
917-
const BuildNodeKind inputKind = node_kind_from_ninja_input(input);
948+
const auto producerIt = outputToTask.find(inputNode.id);
949+
if (producerIt != outputToTask.end())
950+
task.add_dependency(producerIt->second);
951+
};
918952

919-
BuildNode inputNode = make_file_build_node(inputKind, input);
920-
inputNode.hash.clear();
953+
for (const fs::path &input : edge.explicitInputs)
954+
import_input(input);
921955

922-
add_node(inputNode);
923-
task.add_input(inputNode.id);
924-
}
956+
for (const fs::path &input : edge.implicitInputs)
957+
import_input(input);
925958

926959
for (const fs::path &input : edge.orderOnlyInputs)
927-
{
928-
const BuildNodeKind inputKind = node_kind_from_ninja_input(input);
929-
930-
BuildNode inputNode = make_file_build_node(inputKind, input);
931-
inputNode.hash.clear();
932-
933-
add_node(inputNode);
934-
task.add_input(inputNode.id);
935-
}
960+
import_input(input);
936961

937962
for (const fs::path &output : edge.outputs)
938963
{
@@ -945,7 +970,7 @@ namespace vix::cli::build
945970
BuildNode outputNode = make_file_build_node(outputKind, output);
946971
outputNode.hash.clear();
947972

948-
for (const auto &inputId : task.inputs)
973+
for (const auto &inputId : inputNodeIds)
949974
outputNode.add_dependency(inputId);
950975

951976
add_node(outputNode);
@@ -956,6 +981,10 @@ namespace vix::cli::build
956981
continue;
957982

958983
add_task(task);
984+
985+
for (const std::string &outputId : task.outputs)
986+
outputToTask[outputId] = task.id;
987+
959988
++imported;
960989
}
961990

0 commit comments

Comments
 (0)