refactor: extract GetCompileCommand and StdPcmDir out of Build

The clang invocation was assembled inline across four regions of Build,
interleaved with the dependency-graph walk, so nothing else could ask "what
flags does this Configuration compile with". The linter's AST layer needs
exactly that, and it cannot approximate it: a precompiled module is rejected
outright by a translation unit whose target features differ from the one that
wrote it. Dropping just -march=native produces hundreds of "compiled with the
target feature '+avx512bw' but the current translation unit is not" errors and
no usable parse, so reconstructed flags fail hard rather than degrade.

GetCompileCommand is the config-pure part: target, arch, standard,
configuration defines, module search paths, includes, user compileFlags,
optimisation and LTO. Build appends only what depends on work having happened
— dependency public flags and external dependency flags. The sub-strings it
also needs on their own (includes, defines, user flags, LTO) come back as
struct members, so the .c compile path is unchanged.

Verified by probing `command` at the equivalent point before and after and
diffing: byte-identical across all 23 configurations exercised by a full build
plus the test suite.

Two incidental simplifications fell out. pcmDir was recomputing what
Configuration::PcmDir() already returns, and cmakeBuildType is now a
one-liner. GetCompileCommand is also most of what a compile_commands.json
would need, which this repo lacks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-07-30 23:15:20 +02:00
commit 7bb0a19ed0
2 changed files with 193 additions and 141 deletions

View file

@ -418,6 +418,40 @@ export namespace Crafter {
std::vector<std::string> requires_;
};
// Directory holding the std module PCM for this Configuration. Keyed on
// target+march because a BMI is only loadable by a TU with matching target
// features, and suffixed with the wasm variant flags for the same reason.
CRAFTER_API fs::path StdPcmDir(const Configuration& config);
// The clang invocation every C++ translation unit in this Configuration is
// compiled with, before anything that depends on work having happened
// (dependency public flags, external dependency flags — Build appends
// those itself). A pure function of the Configuration.
//
// Anything that needs to PARSE this project's sources rather than build
// them — the linter's AST layer, a future compile_commands.json — must go
// through this instead of assembling its own flags. A precompiled module
// is rejected outright by a TU whose target features differ from the one
// that wrote it, so approximately-right flags fail hard rather than
// degrade: omitting -march=native alone produces hundreds of "compiled
// with the target feature '+avx512bw' but the current translation unit is
// not" errors and no usable parse.
struct CompileCommand {
std::string command; // full C++ compile prefix, shell-ready
// Sub-sets Build also needs on its own: the .c compile path takes the
// includes, defines and user flags but not the module-only bits.
std::string includeFlags;
std::string defineFlags;
std::string userFlags;
std::string ltoCompileFlags;
std::string ltoLinkFlags;
// ThinLTO is on: objects hold bitcode, so archiving needs llvm-ar.
bool useLto = false;
fs::path stdPcmDir;
fs::path pcmDir;
};
CRAFTER_API CompileCommand GetCompileCommand(const Configuration& config);
CRAFTER_API BuildResult Build(Configuration& config, std::unordered_map<fs::path, std::shared_future<BuildResult>>& depResults, std::mutex& depMutex);
CRAFTER_API int Run(int argc, char** argv);