Merge topic 'curl-tls-verify'
[kiteware-cmake.git] / Source / cmGlobalNinjaGenerator.cxx
blob17e9c440a800bf3d083a7299a6301308a6663de4
1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmGlobalNinjaGenerator.h"
5 #include <algorithm>
6 #include <cassert>
7 #include <cctype>
8 #include <cstdio>
9 #include <functional>
10 #include <sstream>
11 #include <type_traits>
12 #include <utility>
14 #include <cm/iterator>
15 #include <cm/memory>
16 #include <cm/optional>
17 #include <cm/string_view>
18 #include <cmext/algorithm>
19 #include <cmext/memory>
20 #include <cmext/string_view>
22 #include <cm3p/json/reader.h>
23 #include <cm3p/json/value.h>
24 #include <cm3p/json/writer.h>
26 #include "cmsys/FStream.hxx"
28 #include "cmCustomCommand.h"
29 #include "cmCxxModuleMapper.h"
30 #include "cmDyndepCollation.h"
31 #include "cmFortranParser.h"
32 #include "cmGeneratedFileStream.h"
33 #include "cmGeneratorExpressionEvaluationFile.h"
34 #include "cmGeneratorTarget.h"
35 #include "cmGlobalGenerator.h"
36 #include "cmLinkLineComputer.h"
37 #include "cmList.h"
38 #include "cmListFileCache.h"
39 #include "cmLocalGenerator.h"
40 #include "cmLocalNinjaGenerator.h"
41 #include "cmMakefile.h"
42 #include "cmMessageType.h"
43 #include "cmNinjaLinkLineComputer.h"
44 #include "cmOutputConverter.h"
45 #include "cmRange.h"
46 #include "cmScanDepFormat.h"
47 #include "cmSourceFile.h"
48 #include "cmState.h"
49 #include "cmStateDirectory.h"
50 #include "cmStateSnapshot.h"
51 #include "cmStateTypes.h"
52 #include "cmStringAlgorithms.h"
53 #include "cmSystemTools.h"
54 #include "cmTarget.h"
55 #include "cmTargetDepend.h"
56 #include "cmValue.h"
57 #include "cmVersion.h"
58 #include "cmake.h"
60 const char* cmGlobalNinjaGenerator::NINJA_BUILD_FILE = "build.ninja";
61 const char* cmGlobalNinjaGenerator::NINJA_RULES_FILE =
62 "CMakeFiles/rules.ninja";
63 const char* cmGlobalNinjaGenerator::INDENT = " ";
64 #ifdef _WIN32
65 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = "cd .";
66 #else
67 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = ":";
68 #endif
70 namespace {
71 #ifdef _WIN32
72 bool DetectGCCOnWindows(cm::string_view compilerId, cm::string_view simulateId,
73 cm::string_view compilerFrontendVariant)
75 return ((compilerId == "Clang"_s && compilerFrontendVariant == "GNU"_s) ||
76 (simulateId != "MSVC"_s &&
77 (compilerId == "GNU"_s || compilerId == "QCC"_s ||
78 cmHasLiteralSuffix(compilerId, "Clang"))));
80 #endif
83 bool operator==(
84 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
85 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
87 return lhs.Target == rhs.Target && lhs.Config == rhs.Config &&
88 lhs.GenexOutput == rhs.GenexOutput;
91 bool operator!=(
92 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
93 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
95 return !(lhs == rhs);
98 bool operator<(
99 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
100 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
102 return lhs.Target < rhs.Target ||
103 (lhs.Target == rhs.Target &&
104 (lhs.Config < rhs.Config ||
105 (lhs.Config == rhs.Config && lhs.GenexOutput < rhs.GenexOutput)));
108 bool operator>(
109 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
110 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
112 return rhs < lhs;
115 bool operator<=(
116 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
117 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
119 return !(lhs > rhs);
122 bool operator>=(
123 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
124 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
126 return rhs <= lhs;
129 void cmGlobalNinjaGenerator::Indent(std::ostream& os, int count)
131 for (int i = 0; i < count; ++i) {
132 os << cmGlobalNinjaGenerator::INDENT;
136 void cmGlobalNinjaGenerator::WriteDivider(std::ostream& os)
138 os << "# ======================================"
139 "=======================================\n";
142 void cmGlobalNinjaGenerator::WriteComment(std::ostream& os,
143 const std::string& comment)
145 if (comment.empty()) {
146 return;
149 std::string::size_type lpos = 0;
150 std::string::size_type rpos;
151 os << "\n#############################################\n";
152 while ((rpos = comment.find('\n', lpos)) != std::string::npos) {
153 os << "# " << comment.substr(lpos, rpos - lpos) << "\n";
154 lpos = rpos + 1;
156 os << "# " << comment.substr(lpos) << "\n\n";
159 std::unique_ptr<cmLinkLineComputer>
160 cmGlobalNinjaGenerator::CreateLinkLineComputer(
161 cmOutputConverter* outputConverter,
162 cmStateDirectory const& /* stateDir */) const
164 return std::unique_ptr<cmLinkLineComputer>(
165 cm::make_unique<cmNinjaLinkLineComputer>(
166 outputConverter,
167 this->LocalGenerators[0]->GetStateSnapshot().GetDirectory(), this));
170 std::string cmGlobalNinjaGenerator::EncodeRuleName(std::string const& name)
172 // Ninja rule names must match "[a-zA-Z0-9_.-]+". Use ".xx" to encode
173 // "." and all invalid characters as hexadecimal.
174 std::string encoded;
175 for (char i : name) {
176 if (isalnum(i) || i == '_' || i == '-') {
177 encoded += i;
178 } else {
179 char buf[16];
180 snprintf(buf, sizeof(buf), ".%02x", static_cast<unsigned int>(i));
181 encoded += buf;
184 return encoded;
187 std::string cmGlobalNinjaGenerator::GetEncodedLiteral(const std::string& lit)
189 std::string result = lit;
190 return this->EncodeLiteral(result);
193 std::string& cmGlobalNinjaGenerator::EncodeLiteral(std::string& lit)
195 cmSystemTools::ReplaceString(lit, "$", "$$");
196 cmSystemTools::ReplaceString(lit, "\n", "$\n");
197 if (this->IsMultiConfig()) {
198 cmSystemTools::ReplaceString(lit, cmStrCat('$', this->GetCMakeCFGIntDir()),
199 this->GetCMakeCFGIntDir());
201 return lit;
204 std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path)
206 std::string result = path;
207 #ifdef _WIN32
208 if (this->IsGCCOnWindows())
209 std::replace(result.begin(), result.end(), '\\', '/');
210 else
211 std::replace(result.begin(), result.end(), '/', '\\');
212 #endif
213 this->EncodeLiteral(result);
214 cmSystemTools::ReplaceString(result, " ", "$ ");
215 cmSystemTools::ReplaceString(result, ":", "$:");
216 return result;
219 void cmGlobalNinjaGenerator::WriteBuild(std::ostream& os,
220 cmNinjaBuild const& build,
221 int cmdLineLimit,
222 bool* usedResponseFile)
224 // Make sure there is a rule.
225 if (build.Rule.empty()) {
226 cmSystemTools::Error(cmStrCat(
227 "No rule for WriteBuild! called with comment: ", build.Comment));
228 return;
231 // Make sure there is at least one output file.
232 if (build.Outputs.empty()) {
233 cmSystemTools::Error(cmStrCat(
234 "No output files for WriteBuild! called with comment: ", build.Comment));
235 return;
238 cmGlobalNinjaGenerator::WriteComment(os, build.Comment);
240 // Write output files.
241 std::string buildStr("build");
243 // Write explicit outputs
244 for (std::string const& output : build.Outputs) {
245 buildStr = cmStrCat(buildStr, ' ', this->EncodePath(output));
246 if (this->ComputingUnknownDependencies) {
247 this->CombinedBuildOutputs.insert(output);
250 // Write implicit outputs
251 if (!build.ImplicitOuts.empty()) {
252 // Assume Ninja is new enough to support implicit outputs.
253 // Callers should not populate this field otherwise.
254 buildStr = cmStrCat(buildStr, " |");
255 for (std::string const& implicitOut : build.ImplicitOuts) {
256 buildStr = cmStrCat(buildStr, ' ', this->EncodePath(implicitOut));
257 if (this->ComputingUnknownDependencies) {
258 this->CombinedBuildOutputs.insert(implicitOut);
263 // Repeat some outputs, but expressed as absolute paths.
264 // This helps Ninja handle absolute paths found in a depfile.
265 // FIXME: Unfortunately this causes Ninja to stat the file twice.
266 // We could avoid this if Ninja Issue 1251 were fixed.
267 if (!build.WorkDirOuts.empty()) {
268 if (this->SupportsImplicitOuts() && build.ImplicitOuts.empty()) {
269 // Make them implicit outputs if supported by this version of Ninja.
270 buildStr = cmStrCat(buildStr, " |");
272 for (std::string const& workdirOut : build.WorkDirOuts) {
273 buildStr = cmStrCat(buildStr, " ${cmake_ninja_workdir}",
274 this->EncodePath(workdirOut));
278 // Write the rule.
279 buildStr = cmStrCat(buildStr, ": ", build.Rule);
282 std::string arguments;
284 // TODO: Better formatting for when there are multiple input/output files.
286 // Write explicit dependencies.
287 for (std::string const& explicitDep : build.ExplicitDeps) {
288 arguments += cmStrCat(' ', this->EncodePath(explicitDep));
291 // Write implicit dependencies.
292 if (!build.ImplicitDeps.empty()) {
293 arguments += " |";
294 for (std::string const& implicitDep : build.ImplicitDeps) {
295 arguments += cmStrCat(' ', this->EncodePath(implicitDep));
299 // Write order-only dependencies.
300 if (!build.OrderOnlyDeps.empty()) {
301 arguments += " ||";
302 for (std::string const& orderOnlyDep : build.OrderOnlyDeps) {
303 arguments += cmStrCat(' ', this->EncodePath(orderOnlyDep));
307 arguments += '\n';
310 // Write the variables bound to this build statement.
311 std::string assignments;
313 std::ostringstream variable_assignments;
314 for (auto const& variable : build.Variables) {
315 cmGlobalNinjaGenerator::WriteVariable(
316 variable_assignments, variable.first, variable.second, "", 1);
319 // check if a response file rule should be used
320 assignments = variable_assignments.str();
321 bool useResponseFile = false;
322 if (cmdLineLimit < 0 ||
323 (cmdLineLimit > 0 &&
324 (arguments.size() + buildStr.size() + assignments.size() + 1000) >
325 static_cast<size_t>(cmdLineLimit))) {
326 variable_assignments.str(std::string());
327 cmGlobalNinjaGenerator::WriteVariable(variable_assignments, "RSP_FILE",
328 build.RspFile, "", 1);
329 assignments += variable_assignments.str();
330 useResponseFile = true;
332 if (usedResponseFile) {
333 *usedResponseFile = useResponseFile;
337 os << buildStr << arguments << assignments << "\n";
340 void cmGlobalNinjaGenerator::AddCustomCommandRule()
342 cmNinjaRule rule("CUSTOM_COMMAND");
343 rule.Command = "$COMMAND";
344 rule.Description = "$DESC";
345 rule.Comment = "Rule for running custom commands.";
346 this->AddRule(rule);
349 void cmGlobalNinjaGenerator::CCOutputs::Add(
350 std::vector<std::string> const& paths)
352 for (std::string const& path : paths) {
353 std::string out = this->GG->ConvertToNinjaPath(path);
354 if (!cmSystemTools::FileIsFullPath(out)) {
355 // This output is expressed as a relative path. Repeat it,
356 // but expressed as an absolute path for Ninja Issue 1251.
357 this->WorkDirOuts.emplace_back(out);
358 this->GG->SeenCustomCommandOutput(this->GG->ConvertToNinjaAbsPath(path));
360 this->GG->SeenCustomCommandOutput(out);
361 this->ExplicitOuts.emplace_back(std::move(out));
365 void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
366 std::string const& command, std::string const& description,
367 std::string const& comment, std::string const& depfile,
368 std::string const& job_pool, bool uses_terminal, bool restat,
369 std::string const& config, CCOutputs outputs, cmNinjaDeps explicitDeps,
370 cmNinjaDeps orderOnlyDeps)
372 this->AddCustomCommandRule();
374 if (this->ComputingUnknownDependencies) {
375 // we need to track every dependency that comes in, since we are trying
376 // to find dependencies that are side effects of build commands
377 for (std::string const& dep : explicitDeps) {
378 this->CombinedCustomCommandExplicitDependencies.insert(dep);
383 std::string ninjaDepfilePath;
384 bool depfileIsOutput = false;
385 if (!depfile.empty()) {
386 ninjaDepfilePath = this->ConvertToNinjaPath(depfile);
387 depfileIsOutput =
388 std::find(outputs.ExplicitOuts.begin(), outputs.ExplicitOuts.end(),
389 ninjaDepfilePath) != outputs.ExplicitOuts.end();
392 cmNinjaBuild build("CUSTOM_COMMAND");
393 build.Comment = comment;
394 build.Outputs = std::move(outputs.ExplicitOuts);
395 build.WorkDirOuts = std::move(outputs.WorkDirOuts);
396 build.ExplicitDeps = std::move(explicitDeps);
397 build.OrderOnlyDeps = std::move(orderOnlyDeps);
399 cmNinjaVars& vars = build.Variables;
401 std::string cmd = command; // NOLINT(*)
402 #ifdef _WIN32
403 if (cmd.empty())
404 // TODO Shouldn't an empty command be handled by ninja?
405 cmd = "cmd.exe /c";
406 #endif
407 vars["COMMAND"] = std::move(cmd);
409 vars["DESC"] = this->GetEncodedLiteral(description);
410 if (restat) {
411 vars["restat"] = "1";
413 if (uses_terminal && this->SupportsDirectConsole()) {
414 vars["pool"] = "console";
415 } else if (!job_pool.empty()) {
416 vars["pool"] = job_pool;
418 if (!depfile.empty()) {
419 vars["depfile"] = ninjaDepfilePath;
420 // Add the depfile to the `.ninja_deps` database. Since this (generally)
421 // removes the file, it cannot be declared as an output or byproduct of
422 // the command.
423 if (!depfileIsOutput) {
424 vars["deps"] = "gcc";
427 if (config.empty()) {
428 this->WriteBuild(*this->GetCommonFileStream(), build);
429 } else {
430 this->WriteBuild(*this->GetImplFileStream(config), build);
435 void cmGlobalNinjaGenerator::AddMacOSXContentRule()
437 cmNinjaRule rule("COPY_OSX_CONTENT");
438 rule.Command = cmStrCat(this->CMakeCmd(), " -E copy $in $out");
439 rule.Description = "Copying OS X Content $out";
440 rule.Comment = "Rule for copying OS X bundle content file.";
441 this->AddRule(rule);
444 void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(std::string input,
445 std::string output,
446 const std::string& config)
448 this->AddMacOSXContentRule();
450 cmNinjaBuild build("COPY_OSX_CONTENT");
451 build.Outputs.push_back(std::move(output));
452 build.ExplicitDeps.push_back(std::move(input));
453 this->WriteBuild(*this->GetImplFileStream(config), build);
457 void cmGlobalNinjaGenerator::WriteRule(std::ostream& os,
458 cmNinjaRule const& rule)
460 // -- Parameter checks
461 // Make sure the rule has a name.
462 if (rule.Name.empty()) {
463 cmSystemTools::Error(cmStrCat(
464 "No name given for WriteRule! called with comment: ", rule.Comment));
465 return;
468 // Make sure a command is given.
469 if (rule.Command.empty()) {
470 cmSystemTools::Error(cmStrCat(
471 "No command given for WriteRule! called with comment: ", rule.Comment));
472 return;
475 // Make sure response file content is given
476 if (!rule.RspFile.empty() && rule.RspContent.empty()) {
477 cmSystemTools::Error(
478 cmStrCat("rspfile but no rspfile_content given for WriteRule! "
479 "called with comment: ",
480 rule.Comment));
481 return;
484 // -- Write rule
485 // Write rule intro
486 cmGlobalNinjaGenerator::WriteComment(os, rule.Comment);
487 os << "rule " << rule.Name << '\n';
489 // Write rule key/value pairs
490 auto writeKV = [&os](const char* key, std::string const& value) {
491 if (!value.empty()) {
492 cmGlobalNinjaGenerator::Indent(os, 1);
493 os << key << " = " << value << '\n';
497 writeKV("depfile", rule.DepFile);
498 writeKV("deps", rule.DepType);
499 writeKV("command", rule.Command);
500 writeKV("description", rule.Description);
501 if (!rule.RspFile.empty()) {
502 writeKV("rspfile", rule.RspFile);
503 writeKV("rspfile_content", rule.RspContent);
505 writeKV("restat", rule.Restat);
506 if (rule.Generator) {
507 writeKV("generator", "1");
510 // Finish rule
511 os << '\n';
514 void cmGlobalNinjaGenerator::WriteVariable(std::ostream& os,
515 const std::string& name,
516 const std::string& value,
517 const std::string& comment,
518 int indent)
520 // Make sure we have a name.
521 if (name.empty()) {
522 cmSystemTools::Error(cmStrCat("No name given for WriteVariable! called "
523 "with comment: ",
524 comment));
525 return;
528 std::string val;
529 static std::unordered_set<std::string> const variablesShouldNotBeTrimmed = {
530 "CODE_CHECK", "LAUNCHER"
532 if (variablesShouldNotBeTrimmed.find(name) ==
533 variablesShouldNotBeTrimmed.end()) {
534 val = cmTrimWhitespace(value);
535 } else {
536 val = value;
539 // Do not add a variable if the value is empty.
540 if (val.empty()) {
541 return;
544 cmGlobalNinjaGenerator::WriteComment(os, comment);
545 cmGlobalNinjaGenerator::Indent(os, indent);
546 os << name << " = " << val << "\n";
549 void cmGlobalNinjaGenerator::WriteInclude(std::ostream& os,
550 const std::string& filename,
551 const std::string& comment)
553 cmGlobalNinjaGenerator::WriteComment(os, comment);
554 os << "include " << filename << "\n";
557 void cmGlobalNinjaGenerator::WriteDefault(std::ostream& os,
558 const cmNinjaDeps& targets,
559 const std::string& comment)
561 cmGlobalNinjaGenerator::WriteComment(os, comment);
562 os << "default";
563 for (std::string const& target : targets) {
564 os << " " << target;
566 os << "\n";
569 cmGlobalNinjaGenerator::cmGlobalNinjaGenerator(cmake* cm)
570 : cmGlobalCommonGenerator(cm)
572 #ifdef _WIN32
573 cm->GetState()->SetWindowsShell(true);
575 // Attempt to use full path to COMSPEC, default "cmd.exe"
576 this->Comspec = cmSystemTools::GetComspec();
577 #endif
578 cm->GetState()->SetNinja(true);
579 this->FindMakeProgramFile = "CMakeNinjaFindMake.cmake";
582 // Virtual public methods.
584 std::unique_ptr<cmLocalGenerator> cmGlobalNinjaGenerator::CreateLocalGenerator(
585 cmMakefile* mf)
587 return std::unique_ptr<cmLocalGenerator>(
588 cm::make_unique<cmLocalNinjaGenerator>(this, mf));
591 codecvt_Encoding cmGlobalNinjaGenerator::GetMakefileEncoding() const
593 return this->NinjaExpectedEncoding;
596 cmDocumentationEntry cmGlobalNinjaGenerator::GetDocumentation()
598 return { cmGlobalNinjaGenerator::GetActualName(),
599 "Generates build.ninja files." };
602 std::vector<std::string> const& cmGlobalNinjaGenerator::GetConfigNames() const
604 return static_cast<cmLocalNinjaGenerator const*>(
605 this->LocalGenerators.front().get())
606 ->GetConfigNames();
609 // Implemented in all cmGlobaleGenerator sub-classes.
610 // Used in:
611 // Source/cmLocalGenerator.cxx
612 // Source/cmake.cxx
613 void cmGlobalNinjaGenerator::Generate()
615 // Check minimum Ninja version.
616 if (cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
617 RequiredNinjaVersion())) {
618 std::ostringstream msg;
619 msg << "The detected version of Ninja (" << this->NinjaVersion;
620 msg << ") is less than the version of Ninja required by CMake (";
621 msg << cmGlobalNinjaGenerator::RequiredNinjaVersion() << ").";
622 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
623 msg.str());
624 return;
626 this->InitOutputPathPrefix();
627 if (!this->OpenBuildFileStreams()) {
628 return;
630 if (!this->OpenRulesFileStream()) {
631 return;
634 for (auto& it : this->Configs) {
635 it.second.TargetDependsClosures.clear();
638 this->TargetAll = this->NinjaOutputPath("all");
639 this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt");
640 this->DiagnosedCxxModuleNinjaSupport = false;
641 this->ClangTidyExportFixesDirs.clear();
642 this->ClangTidyExportFixesFiles.clear();
644 this->PolicyCMP0058 =
645 this->LocalGenerators[0]->GetMakefile()->GetPolicyStatus(
646 cmPolicies::CMP0058);
647 this->ComputingUnknownDependencies =
648 (this->PolicyCMP0058 == cmPolicies::OLD ||
649 this->PolicyCMP0058 == cmPolicies::WARN);
651 this->cmGlobalGenerator::Generate();
653 this->WriteAssumedSourceDependencies();
654 this->WriteTargetAliases(*this->GetCommonFileStream());
655 this->WriteFolderTargets(*this->GetCommonFileStream());
656 this->WriteUnknownExplicitDependencies(*this->GetCommonFileStream());
657 this->WriteBuiltinTargets(*this->GetCommonFileStream());
659 if (cmSystemTools::GetErrorOccurredFlag()) {
660 this->RulesFileStream->setstate(std::ios::failbit);
661 for (std::string const& config : this->GetConfigNames()) {
662 this->GetImplFileStream(config)->setstate(std::ios::failbit);
663 this->GetConfigFileStream(config)->setstate(std::ios::failbit);
665 this->GetCommonFileStream()->setstate(std::ios::failbit);
668 this->CloseCompileCommandsStream();
669 this->CloseRulesFileStream();
670 this->CloseBuildFileStreams();
672 #ifdef _WIN32
673 // Older ninja tools will not be able to update metadata on Windows
674 // when we are re-generating inside an existing 'ninja' invocation
675 // because the outer tool has the files open for write.
676 if (this->NinjaSupportsMetadataOnRegeneration ||
677 !this->GetCMakeInstance()->GetRegenerateDuringBuild())
678 #endif
680 this->CleanMetaData();
683 this->RemoveUnknownClangTidyExportFixesFiles();
686 void cmGlobalNinjaGenerator::CleanMetaData()
688 auto run_ninja_tool = [this](std::vector<char const*> const& args) {
689 std::vector<std::string> command;
690 command.push_back(this->NinjaCommand);
691 command.emplace_back("-C");
692 command.emplace_back(this->GetCMakeInstance()->GetHomeOutputDirectory());
693 command.emplace_back("-t");
694 for (auto const& arg : args) {
695 command.emplace_back(arg);
697 std::string error;
698 if (!cmSystemTools::RunSingleCommand(command, nullptr, &error, nullptr,
699 nullptr,
700 cmSystemTools::OUTPUT_NONE)) {
701 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
702 cmStrCat("Running\n '",
703 cmJoin(command, "' '"),
704 "'\n"
705 "failed with:\n ",
706 error));
707 cmSystemTools::SetFatalErrorOccurred();
711 // Can the tools below expect 'build.ninja' to be loadable?
712 bool const expectBuildManifest =
713 !this->IsMultiConfig() && this->OutputPathPrefix.empty();
715 // Skip some ninja tools if they need 'build.ninja' but it is missing.
716 bool const missingBuildManifest = expectBuildManifest &&
717 this->NinjaSupportsUnconditionalRecompactTool &&
718 !cmSystemTools::FileExists("build.ninja");
720 // The `recompact` tool loads the manifest. As above, we don't have a single
721 // `build.ninja` to load for this in Ninja-Multi. This may be relaxed in the
722 // future pending further investigation into how Ninja works upstream
723 // (ninja#1721).
724 if (this->NinjaSupportsUnconditionalRecompactTool &&
725 !this->GetCMakeInstance()->GetRegenerateDuringBuild() &&
726 expectBuildManifest && !missingBuildManifest) {
727 run_ninja_tool({ "recompact" });
729 if (this->NinjaSupportsRestatTool && this->OutputPathPrefix.empty()) {
730 // XXX(ninja): We only list `build.ninja` entry files here because CMake
731 // *always* rewrites these files on a reconfigure. If CMake ever gets
732 // smarter about this, all CMake-time created/edited files listed as
733 // outputs for the reconfigure build statement will need to be listed here.
734 cmNinjaDeps outputs;
735 this->AddRebuildManifestOutputs(outputs);
736 std::vector<const char*> args;
737 args.reserve(outputs.size() + 1);
738 args.push_back("restat");
739 for (auto const& output : outputs) {
740 args.push_back(output.c_str());
742 run_ninja_tool(args);
746 bool cmGlobalNinjaGenerator::FindMakeProgram(cmMakefile* mf)
748 if (!this->cmGlobalGenerator::FindMakeProgram(mf)) {
749 return false;
751 if (cmValue ninjaCommand = mf->GetDefinition("CMAKE_MAKE_PROGRAM")) {
752 this->NinjaCommand = *ninjaCommand;
753 std::vector<std::string> command;
754 command.push_back(this->NinjaCommand);
755 command.emplace_back("--version");
756 std::string version;
757 std::string error;
758 if (!cmSystemTools::RunSingleCommand(command, &version, &error, nullptr,
759 nullptr,
760 cmSystemTools::OUTPUT_NONE)) {
761 mf->IssueMessage(MessageType::FATAL_ERROR,
762 cmStrCat("Running\n '", cmJoin(command, "' '"),
763 "'\n"
764 "failed with:\n ",
765 error));
766 cmSystemTools::SetFatalErrorOccurred();
767 return false;
769 this->NinjaVersion = cmTrimWhitespace(version);
770 this->CheckNinjaFeatures();
772 return true;
775 void cmGlobalNinjaGenerator::CheckNinjaFeatures()
777 this->NinjaSupportsConsolePool =
778 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
779 RequiredNinjaVersionForConsolePool());
780 this->NinjaSupportsImplicitOuts = !cmSystemTools::VersionCompare(
781 cmSystemTools::OP_LESS, this->NinjaVersion,
782 cmGlobalNinjaGenerator::RequiredNinjaVersionForImplicitOuts());
783 this->NinjaSupportsManifestRestat =
784 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
785 RequiredNinjaVersionForManifestRestat());
786 this->NinjaSupportsMultilineDepfile =
787 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
788 RequiredNinjaVersionForMultilineDepfile());
789 this->NinjaSupportsDyndepsCxx =
790 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
791 RequiredNinjaVersionForDyndepsCxx());
792 this->NinjaSupportsDyndepsFortran =
793 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
794 RequiredNinjaVersionForDyndepsFortran());
795 if (!this->NinjaSupportsDyndepsFortran) {
796 // The ninja version number is not new enough to have upstream support.
797 // Our ninja branch adds ".dyndep-#" to its version number,
798 // where '#' is a feature-specific version number. Extract it.
799 static std::string const k_DYNDEP_ = ".dyndep-";
800 std::string::size_type pos = this->NinjaVersion.find(k_DYNDEP_);
801 if (pos != std::string::npos) {
802 const char* fv = &this->NinjaVersion[pos + k_DYNDEP_.size()];
803 unsigned long dyndep = 0;
804 cmStrToULong(fv, &dyndep);
805 if (dyndep == 1) {
806 this->NinjaSupportsDyndepsFortran = true;
810 this->NinjaSupportsUnconditionalRecompactTool =
811 !cmSystemTools::VersionCompare(
812 cmSystemTools::OP_LESS, this->NinjaVersion,
813 RequiredNinjaVersionForUnconditionalRecompactTool());
814 this->NinjaSupportsRestatTool =
815 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
816 RequiredNinjaVersionForRestatTool());
817 this->NinjaSupportsMultipleOutputs =
818 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
819 RequiredNinjaVersionForMultipleOutputs());
820 this->NinjaSupportsMetadataOnRegeneration = !cmSystemTools::VersionCompare(
821 cmSystemTools::OP_LESS, this->NinjaVersion,
822 RequiredNinjaVersionForMetadataOnRegeneration());
823 #ifdef _WIN32
824 this->NinjaSupportsCodePage =
825 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
826 RequiredNinjaVersionForCodePage());
827 if (this->NinjaSupportsCodePage) {
828 this->CheckNinjaCodePage();
829 } else {
830 this->NinjaExpectedEncoding = codecvt_Encoding::ANSI;
832 #endif
833 this->NinjaSupportsCWDDepend =
834 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
835 RequiredNinjaVersionForCWDDepend());
838 void cmGlobalNinjaGenerator::CheckNinjaCodePage()
840 std::vector<std::string> command{ this->NinjaCommand, "-t", "wincodepage" };
841 std::string output;
842 std::string error;
843 int result;
844 if (!cmSystemTools::RunSingleCommand(command, &output, &error, &result,
845 nullptr, cmSystemTools::OUTPUT_NONE)) {
846 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
847 cmStrCat("Running\n '",
848 cmJoin(command, "' '"),
849 "'\n"
850 "failed with:\n ",
851 error));
852 cmSystemTools::SetFatalErrorOccurred();
853 } else if (result == 0) {
854 std::istringstream outputStream(output);
855 std::string line;
856 bool found = false;
857 while (cmSystemTools::GetLineFromStream(outputStream, line)) {
858 if (cmHasLiteralPrefix(line, "Build file encoding: ")) {
859 cm::string_view lineView(line);
860 cm::string_view encoding =
861 lineView.substr(cmStrLen("Build file encoding: "));
862 if (encoding == "UTF-8") {
863 // Ninja expects UTF-8. We use that internally. No conversion needed.
864 this->NinjaExpectedEncoding = codecvt_Encoding::None;
865 } else {
866 this->NinjaExpectedEncoding = codecvt_Encoding::ANSI;
868 found = true;
869 break;
872 if (!found) {
873 this->GetCMakeInstance()->IssueMessage(
874 MessageType::WARNING,
875 "Could not determine Ninja's code page, defaulting to UTF-8");
876 this->NinjaExpectedEncoding = codecvt_Encoding::None;
878 } else {
879 this->NinjaExpectedEncoding = codecvt_Encoding::ANSI;
883 bool cmGlobalNinjaGenerator::CheckLanguages(
884 std::vector<std::string> const& languages, cmMakefile* mf) const
886 if (cm::contains(languages, "Fortran")) {
887 return this->CheckFortran(mf);
889 if (cm::contains(languages, "ISPC")) {
890 return this->CheckISPC(mf);
892 if (cm::contains(languages, "Swift")) {
893 const std::string architectures =
894 mf->GetSafeDefinition("CMAKE_OSX_ARCHITECTURES");
895 if (architectures.find_first_of(';') != std::string::npos) {
896 mf->IssueMessage(MessageType::FATAL_ERROR,
897 "multiple values for CMAKE_OSX_ARCHITECTURES not "
898 "supported with Swift");
899 cmSystemTools::SetFatalErrorOccurred();
900 return false;
903 return true;
906 bool cmGlobalNinjaGenerator::CheckCxxModuleSupport(CxxModuleSupportQuery query)
908 if (this->NinjaSupportsDyndepsCxx) {
909 return true;
911 bool const diagnose = !this->DiagnosedCxxModuleNinjaSupport &&
912 !this->CMakeInstance->GetIsInTryCompile() &&
913 query == CxxModuleSupportQuery::Expected;
914 if (diagnose) {
915 std::ostringstream e;
916 /* clang-format off */
917 e <<
918 "The Ninja generator does not support C++20 modules "
919 "using Ninja version \n"
920 " " << this->NinjaVersion << "\n"
921 "due to lack of required features. "
922 "Ninja " << RequiredNinjaVersionForDyndepsCxx() <<
923 " or higher is required."
925 /* clang-format on */
926 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str());
927 cmSystemTools::SetFatalErrorOccurred();
929 return false;
932 bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const
934 if (this->NinjaSupportsDyndepsFortran) {
935 return true;
938 std::ostringstream e;
939 /* clang-format off */
940 e <<
941 "The Ninja generator does not support Fortran using Ninja version\n"
942 " " << this->NinjaVersion << "\n"
943 "due to lack of required features. "
944 "Ninja " << RequiredNinjaVersionForDyndepsFortran() <<
945 " or higher is required."
947 /* clang-format on */
948 mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
949 cmSystemTools::SetFatalErrorOccurred();
950 return false;
953 bool cmGlobalNinjaGenerator::CheckISPC(cmMakefile* mf) const
955 if (this->NinjaSupportsMultipleOutputs) {
956 return true;
959 std::ostringstream e;
960 /* clang-format off */
961 e <<
962 "The Ninja generator does not support ISPC using Ninja version\n"
963 " " << this->NinjaVersion << "\n"
964 "due to lack of required features. "
965 "Ninja " << RequiredNinjaVersionForMultipleOutputs() <<
966 " or higher is required."
968 /* clang-format on */
969 mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
970 cmSystemTools::SetFatalErrorOccurred();
971 return false;
974 void cmGlobalNinjaGenerator::EnableLanguage(
975 std::vector<std::string> const& langs, cmMakefile* mf, bool optional)
977 if (this->IsMultiConfig()) {
978 mf->InitCMAKE_CONFIGURATION_TYPES("Debug;Release;RelWithDebInfo");
981 this->cmGlobalGenerator::EnableLanguage(langs, mf, optional);
982 for (std::string const& l : langs) {
983 if (l == "NONE") {
984 continue;
986 this->ResolveLanguageCompiler(l, mf, optional);
987 #ifdef _WIN32
988 std::string const& compilerId =
989 mf->GetSafeDefinition(cmStrCat("CMAKE_", l, "_COMPILER_ID"));
990 std::string const& simulateId =
991 mf->GetSafeDefinition(cmStrCat("CMAKE_", l, "_SIMULATE_ID"));
992 std::string const& compilerFrontendVariant = mf->GetSafeDefinition(
993 cmStrCat("CMAKE_", l, "_COMPILER_FRONTEND_VARIANT"));
994 if (DetectGCCOnWindows(compilerId, simulateId, compilerFrontendVariant)) {
995 this->MarkAsGCCOnWindows();
997 #endif
1001 // Implemented by:
1002 // cmGlobalUnixMakefileGenerator3
1003 // cmGlobalGhsMultiGenerator
1004 // cmGlobalVisualStudio10Generator
1005 // cmGlobalVisualStudio7Generator
1006 // cmGlobalXCodeGenerator
1007 // Called by:
1008 // cmGlobalGenerator::Build()
1009 std::vector<cmGlobalGenerator::GeneratedMakeCommand>
1010 cmGlobalNinjaGenerator::GenerateBuildCommand(
1011 const std::string& makeProgram, const std::string& /*projectName*/,
1012 const std::string& /*projectDir*/,
1013 std::vector<std::string> const& targetNames, const std::string& config,
1014 int jobs, bool verbose, const cmBuildOptions& /*buildOptions*/,
1015 std::vector<std::string> const& makeOptions)
1017 GeneratedMakeCommand makeCommand;
1018 makeCommand.Add(this->SelectMakeProgram(makeProgram));
1020 if (verbose) {
1021 makeCommand.Add("-v");
1024 if ((jobs != cmake::NO_BUILD_PARALLEL_LEVEL) &&
1025 (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL)) {
1026 makeCommand.Add("-j", std::to_string(jobs));
1029 this->AppendNinjaFileArgument(makeCommand, config);
1031 makeCommand.Add(makeOptions.begin(), makeOptions.end());
1032 for (const auto& tname : targetNames) {
1033 if (!tname.empty()) {
1034 makeCommand.Add(tname);
1037 return { std::move(makeCommand) };
1040 // Non-virtual public methods.
1042 void cmGlobalNinjaGenerator::AddRule(cmNinjaRule const& rule)
1044 // Do not add the same rule twice.
1045 if (!this->Rules.insert(rule.Name).second) {
1046 return;
1048 // Store command length
1049 this->RuleCmdLength[rule.Name] = static_cast<int>(rule.Command.size());
1050 // Write rule
1051 cmGlobalNinjaGenerator::WriteRule(*this->RulesFileStream, rule);
1054 bool cmGlobalNinjaGenerator::HasRule(const std::string& name)
1056 return (this->Rules.find(name) != this->Rules.end());
1059 // Private virtual overrides
1061 void cmGlobalNinjaGenerator::ComputeTargetObjectDirectory(
1062 cmGeneratorTarget* gt) const
1064 // Compute full path to object file directory for this target.
1065 std::string dir = cmStrCat(gt->LocalGenerator->GetCurrentBinaryDirectory(),
1066 '/', gt->LocalGenerator->GetTargetDirectory(gt),
1067 '/', this->GetCMakeCFGIntDir(), '/');
1068 gt->ObjectDirectory = dir;
1071 // Private methods
1073 bool cmGlobalNinjaGenerator::OpenBuildFileStreams()
1075 if (!this->OpenFileStream(this->BuildFileStream,
1076 cmGlobalNinjaGenerator::NINJA_BUILD_FILE)) {
1077 return false;
1080 // Write a comment about this file.
1081 *this->BuildFileStream
1082 << "# This file contains all the build statements describing the\n"
1083 << "# compilation DAG.\n\n";
1085 return true;
1088 bool cmGlobalNinjaGenerator::OpenFileStream(
1089 std::unique_ptr<cmGeneratedFileStream>& stream, const std::string& name)
1091 // Get a stream where to generate things.
1092 if (!stream) {
1093 // Compute Ninja's build file path.
1094 std::string path =
1095 cmStrCat(this->GetCMakeInstance()->GetHomeOutputDirectory(), '/', name);
1096 stream = cm::make_unique<cmGeneratedFileStream>(
1097 path, false, this->GetMakefileEncoding());
1098 if (!(*stream)) {
1099 // An error message is generated by the constructor if it cannot
1100 // open the file.
1101 return false;
1104 // Write the do not edit header.
1105 this->WriteDisclaimer(*stream);
1108 return true;
1111 cm::optional<std::set<std::string>> cmGlobalNinjaGenerator::ListSubsetWithAll(
1112 const std::set<std::string>& all, const std::set<std::string>& defaults,
1113 const std::vector<std::string>& items)
1115 std::set<std::string> result;
1117 for (auto const& item : items) {
1118 if (item == "all") {
1119 if (items.size() == 1) {
1120 result = defaults;
1121 } else {
1122 return cm::nullopt;
1124 } else if (all.count(item)) {
1125 result.insert(item);
1126 } else {
1127 return cm::nullopt;
1131 return cm::make_optional(result);
1134 void cmGlobalNinjaGenerator::CloseBuildFileStreams()
1136 if (this->BuildFileStream) {
1137 this->BuildFileStream.reset();
1138 } else {
1139 cmSystemTools::Error("Build file stream was not open.");
1143 bool cmGlobalNinjaGenerator::OpenRulesFileStream()
1145 if (!this->OpenFileStream(this->RulesFileStream,
1146 cmGlobalNinjaGenerator::NINJA_RULES_FILE)) {
1147 return false;
1150 // Write comment about this file.
1151 /* clang-format off */
1152 *this->RulesFileStream
1153 << "# This file contains all the rules used to get the outputs files\n"
1154 << "# built from the input files.\n"
1155 << "# It is included in the main '" << NINJA_BUILD_FILE << "'.\n\n"
1157 /* clang-format on */
1158 return true;
1161 void cmGlobalNinjaGenerator::CloseRulesFileStream()
1163 if (this->RulesFileStream) {
1164 this->RulesFileStream.reset();
1165 } else {
1166 cmSystemTools::Error("Rules file stream was not open.");
1170 static void EnsureTrailingSlash(std::string& path)
1172 if (path.empty()) {
1173 return;
1175 std::string::value_type last = path.back();
1176 #ifdef _WIN32
1177 if (last != '\\') {
1178 path += '\\';
1180 #else
1181 if (last != '/') {
1182 path += '/';
1184 #endif
1187 std::string const& cmGlobalNinjaGenerator::ConvertToNinjaPath(
1188 const std::string& path) const
1190 auto const f = this->ConvertToNinjaPathCache.find(path);
1191 if (f != this->ConvertToNinjaPathCache.end()) {
1192 return f->second;
1195 std::string convPath =
1196 this->LocalGenerators[0]->MaybeRelativeToTopBinDir(path);
1197 convPath = this->NinjaOutputPath(convPath);
1198 #ifdef _WIN32
1199 std::replace(convPath.begin(), convPath.end(), '/', '\\');
1200 #endif
1201 return this->ConvertToNinjaPathCache.emplace(path, std::move(convPath))
1202 .first->second;
1205 std::string cmGlobalNinjaGenerator::ConvertToNinjaAbsPath(
1206 std::string path) const
1208 #ifdef _WIN32
1209 std::replace(path.begin(), path.end(), '/', '\\');
1210 #endif
1211 return path;
1214 void cmGlobalNinjaGenerator::AddAdditionalCleanFile(std::string fileName,
1215 const std::string& config)
1217 this->Configs[config].AdditionalCleanFiles.emplace(std::move(fileName));
1220 void cmGlobalNinjaGenerator::AddCXXCompileCommand(
1221 const std::string& commandLine, const std::string& sourceFile,
1222 const std::string& objPath)
1224 // Compute Ninja's build file path.
1225 std::string buildFileDir =
1226 this->GetCMakeInstance()->GetHomeOutputDirectory();
1227 if (!this->CompileCommandsStream) {
1228 std::string buildFilePath =
1229 cmStrCat(buildFileDir, "/compile_commands.json");
1230 if (this->ComputingUnknownDependencies) {
1231 this->CombinedBuildOutputs.insert(
1232 this->NinjaOutputPath("compile_commands.json"));
1235 // Get a stream where to generate things.
1236 this->CompileCommandsStream =
1237 cm::make_unique<cmGeneratedFileStream>(buildFilePath);
1238 *this->CompileCommandsStream << "[\n";
1239 } else {
1240 *this->CompileCommandsStream << ",\n";
1243 std::string sourceFileName = sourceFile;
1244 if (!cmSystemTools::FileIsFullPath(sourceFileName)) {
1245 sourceFileName = cmSystemTools::CollapseFullPath(
1246 sourceFileName, this->GetCMakeInstance()->GetHomeOutputDirectory());
1249 /* clang-format off */
1250 *this->CompileCommandsStream << "{\n"
1251 << R"( "directory": ")"
1252 << cmGlobalGenerator::EscapeJSON(buildFileDir) << "\",\n"
1253 << R"( "command": ")"
1254 << cmGlobalGenerator::EscapeJSON(commandLine) << "\",\n"
1255 << R"( "file": ")"
1256 << cmGlobalGenerator::EscapeJSON(sourceFileName) << "\",\n"
1257 << R"( "output": ")"
1258 << cmGlobalGenerator::EscapeJSON(objPath) << "\"\n"
1259 << "}";
1260 /* clang-format on */
1263 void cmGlobalNinjaGenerator::CloseCompileCommandsStream()
1265 if (this->CompileCommandsStream) {
1266 *this->CompileCommandsStream << "\n]";
1267 this->CompileCommandsStream.reset();
1271 void cmGlobalNinjaGenerator::WriteDisclaimer(std::ostream& os) const
1273 os << "# CMAKE generated file: DO NOT EDIT!\n"
1274 << "# Generated by \"" << this->GetName() << "\""
1275 << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "."
1276 << cmVersion::GetMinorVersion() << "\n\n";
1279 void cmGlobalNinjaGenerator::WriteAssumedSourceDependencies()
1281 for (auto const& asd : this->AssumedSourceDependencies) {
1282 CCOutputs outputs(this);
1283 outputs.ExplicitOuts.emplace_back(asd.first);
1284 cmNinjaDeps orderOnlyDeps;
1285 std::copy(asd.second.begin(), asd.second.end(),
1286 std::back_inserter(orderOnlyDeps));
1287 this->WriteCustomCommandBuild(
1288 /*command=*/"", /*description=*/"",
1289 "Assume dependencies for generated source file.",
1290 /*depfile*/ "", /*job_pool*/ "",
1291 /*uses_terminal*/ false,
1292 /*restat*/ true, std::string(), outputs, cmNinjaDeps(),
1293 std::move(orderOnlyDeps));
1297 std::string cmGlobalNinjaGenerator::OrderDependsTargetForTarget(
1298 cmGeneratorTarget const* target, const std::string& /*config*/) const
1300 return cmStrCat("cmake_object_order_depends_target_", target->GetName());
1303 std::string cmGlobalNinjaGenerator::OrderDependsTargetForTargetPrivate(
1304 cmGeneratorTarget const* target, const std::string& config) const
1306 return cmStrCat(this->OrderDependsTargetForTarget(target, config),
1307 "_private");
1310 void cmGlobalNinjaGenerator::AppendTargetOutputs(
1311 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1312 const std::string& config, cmNinjaTargetDepends depends) const
1314 // for frameworks, we want the real name, not sample name
1315 // frameworks always appear versioned, and the build.ninja
1316 // will always attempt to manage symbolic links instead
1317 // of letting cmOSXBundleGenerator do it.
1318 bool realname = target->IsFrameworkOnApple();
1320 switch (target->GetType()) {
1321 case cmStateEnums::SHARED_LIBRARY:
1322 case cmStateEnums::STATIC_LIBRARY:
1323 case cmStateEnums::MODULE_LIBRARY: {
1324 if (depends == DependOnTargetOrdering) {
1325 outputs.push_back(this->OrderDependsTargetForTarget(target, config));
1326 break;
1329 CM_FALLTHROUGH;
1330 case cmStateEnums::EXECUTABLE: {
1331 if (target->IsApple() && target->HasImportLibrary(config)) {
1332 outputs.push_back(this->ConvertToNinjaPath(target->GetFullPath(
1333 config, cmStateEnums::ImportLibraryArtifact, realname)));
1335 outputs.push_back(this->ConvertToNinjaPath(target->GetFullPath(
1336 config, cmStateEnums::RuntimeBinaryArtifact, realname)));
1337 break;
1339 case cmStateEnums::OBJECT_LIBRARY: {
1340 if (depends == DependOnTargetOrdering) {
1341 outputs.push_back(this->OrderDependsTargetForTarget(target, config));
1342 break;
1345 CM_FALLTHROUGH;
1346 case cmStateEnums::GLOBAL_TARGET:
1347 case cmStateEnums::INTERFACE_LIBRARY:
1348 case cmStateEnums::UTILITY: {
1349 std::string path =
1350 cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
1351 target->GetName());
1352 std::string output = this->ConvertToNinjaPath(path);
1353 if (target->Target->IsPerConfig()) {
1354 output = this->BuildAlias(output, config);
1356 outputs.push_back(output);
1357 break;
1360 case cmStateEnums::UNKNOWN_LIBRARY:
1361 break;
1365 void cmGlobalNinjaGenerator::AppendTargetDepends(
1366 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1367 const std::string& config, const std::string& fileConfig,
1368 cmNinjaTargetDepends depends)
1370 if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
1371 // These depend only on other CMake-provided targets, e.g. "all".
1372 for (BT<std::pair<std::string, bool>> const& util :
1373 target->GetUtilities()) {
1374 std::string d =
1375 cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
1376 util.Value.first);
1377 outputs.push_back(this->BuildAlias(this->ConvertToNinjaPath(d), config));
1379 } else {
1380 cmNinjaDeps outs;
1382 auto computeISPCOutputs = [](cmGlobalNinjaGenerator* gg,
1383 cmGeneratorTarget const* depTarget,
1384 cmNinjaDeps& outputDeps,
1385 const std::string& targetConfig) {
1386 if (depTarget->CanCompileSources()) {
1387 auto headers = depTarget->GetGeneratedISPCHeaders(targetConfig);
1388 if (!headers.empty()) {
1389 std::transform(headers.begin(), headers.end(), headers.begin(),
1390 gg->MapToNinjaPath());
1391 outputDeps.insert(outputDeps.end(), headers.begin(), headers.end());
1393 auto objs = depTarget->GetGeneratedISPCObjects(targetConfig);
1394 if (!objs.empty()) {
1395 std::transform(objs.begin(), objs.end(), objs.begin(),
1396 gg->MapToNinjaPath());
1397 outputDeps.insert(outputDeps.end(), objs.begin(), objs.end());
1402 for (cmTargetDepend const& targetDep :
1403 this->GetTargetDirectDepends(target)) {
1404 if (!targetDep->IsInBuildSystem()) {
1405 continue;
1407 if (targetDep.IsCross()) {
1408 this->AppendTargetOutputs(targetDep, outs, fileConfig, depends);
1409 computeISPCOutputs(this, targetDep, outs, fileConfig);
1410 } else {
1411 this->AppendTargetOutputs(targetDep, outs, config, depends);
1412 computeISPCOutputs(this, targetDep, outs, config);
1415 std::sort(outs.begin(), outs.end());
1416 cm::append(outputs, outs);
1420 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
1421 cmGeneratorTarget const* target, std::unordered_set<std::string>& outputs,
1422 const std::string& config, const std::string& fileConfig, bool genexOutput,
1423 bool omit_self)
1426 // try to locate the target in the cache
1427 ByConfig::TargetDependsClosureKey key{
1428 target,
1429 config,
1430 genexOutput,
1432 auto find = this->Configs[fileConfig].TargetDependsClosures.lower_bound(key);
1434 if (find == this->Configs[fileConfig].TargetDependsClosures.end() ||
1435 find->first != key) {
1436 // We now calculate the closure outputs by inspecting the dependent
1437 // targets recursively.
1438 // For that we have to distinguish between a local result set that is only
1439 // relevant for filling the cache entries properly isolated and a global
1440 // result set that is relevant for the result of the top level call to
1441 // AppendTargetDependsClosure.
1442 std::unordered_set<std::string>
1443 this_outs; // this will be the new cache entry
1445 for (auto const& dep_target : this->GetTargetDirectDepends(target)) {
1446 if (!dep_target->IsInBuildSystem()) {
1447 continue;
1450 if (!this->IsSingleConfigUtility(target) &&
1451 !this->IsSingleConfigUtility(dep_target) &&
1452 this->EnableCrossConfigBuild() && !dep_target.IsCross() &&
1453 !genexOutput) {
1454 continue;
1457 if (dep_target.IsCross()) {
1458 this->AppendTargetDependsClosure(dep_target, this_outs, fileConfig,
1459 fileConfig, genexOutput, false);
1460 } else {
1461 this->AppendTargetDependsClosure(dep_target, this_outs, config,
1462 fileConfig, genexOutput, false);
1465 find = this->Configs[fileConfig].TargetDependsClosures.emplace_hint(
1466 find, key, std::move(this_outs));
1469 // now fill the outputs of the final result from the newly generated cache
1470 // entry
1471 outputs.insert(find->second.begin(), find->second.end());
1473 // finally generate the outputs of the target itself, if applicable
1474 cmNinjaDeps outs;
1475 if (!omit_self) {
1476 this->AppendTargetOutputs(target, outs, config, DependOnTargetArtifact);
1478 outputs.insert(outs.begin(), outs.end());
1481 void cmGlobalNinjaGenerator::AddTargetAlias(const std::string& alias,
1482 cmGeneratorTarget* target,
1483 const std::string& config)
1485 std::string outputPath = this->NinjaOutputPath(alias);
1486 std::string buildAlias = this->BuildAlias(outputPath, config);
1487 cmNinjaDeps outputs;
1488 if (config != "all") {
1489 this->AppendTargetOutputs(target, outputs, config, DependOnTargetArtifact);
1491 // Mark the target's outputs as ambiguous to ensure that no other target
1492 // uses the output as an alias.
1493 for (std::string const& output : outputs) {
1494 this->TargetAliases[output].GeneratorTarget = nullptr;
1495 this->DefaultTargetAliases[output].GeneratorTarget = nullptr;
1496 for (std::string const& config2 : this->GetConfigNames()) {
1497 this->Configs[config2].TargetAliases[output].GeneratorTarget = nullptr;
1501 // Insert the alias into the map. If the alias was already present in the
1502 // map and referred to another target, mark it as ambiguous.
1503 TargetAlias ta;
1504 ta.GeneratorTarget = target;
1505 ta.Config = config;
1507 auto newAliasGlobal =
1508 this->TargetAliases.insert(std::make_pair(buildAlias, ta));
1509 if (newAliasGlobal.second &&
1510 newAliasGlobal.first->second.GeneratorTarget != target) {
1511 newAliasGlobal.first->second.GeneratorTarget = nullptr;
1514 auto newAliasConfig =
1515 this->Configs[config].TargetAliases.insert(std::make_pair(outputPath, ta));
1516 if (newAliasConfig.second &&
1517 newAliasConfig.first->second.GeneratorTarget != target) {
1518 newAliasConfig.first->second.GeneratorTarget = nullptr;
1520 if (this->DefaultConfigs.count(config)) {
1521 auto newAliasDefaultGlobal =
1522 this->DefaultTargetAliases.insert(std::make_pair(outputPath, ta));
1523 if (newAliasDefaultGlobal.second &&
1524 newAliasDefaultGlobal.first->second.GeneratorTarget != target) {
1525 newAliasDefaultGlobal.first->second.GeneratorTarget = nullptr;
1530 void cmGlobalNinjaGenerator::WriteTargetAliases(std::ostream& os)
1532 cmGlobalNinjaGenerator::WriteDivider(os);
1533 os << "# Target aliases.\n\n";
1535 cmNinjaBuild build("phony");
1536 build.Outputs.emplace_back();
1537 for (auto const& ta : this->TargetAliases) {
1538 // Don't write ambiguous aliases.
1539 if (!ta.second.GeneratorTarget) {
1540 continue;
1543 // Don't write alias if there is a already a custom command with
1544 // matching output
1545 if (this->HasCustomCommandOutput(ta.first)) {
1546 continue;
1549 build.Outputs.front() = ta.first;
1550 build.ExplicitDeps.clear();
1551 if (ta.second.Config == "all") {
1552 for (auto const& config : this->CrossConfigs) {
1553 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1554 build.ExplicitDeps, config,
1555 DependOnTargetArtifact);
1557 } else {
1558 this->AppendTargetOutputs(ta.second.GeneratorTarget, build.ExplicitDeps,
1559 ta.second.Config, DependOnTargetArtifact);
1561 this->WriteBuild(this->EnableCrossConfigBuild() &&
1562 (ta.second.Config == "all" ||
1563 this->CrossConfigs.count(ta.second.Config))
1564 ? os
1565 : *this->GetImplFileStream(ta.second.Config),
1566 build);
1569 if (this->IsMultiConfig()) {
1570 for (std::string const& config : this->GetConfigNames()) {
1571 for (auto const& ta : this->Configs[config].TargetAliases) {
1572 // Don't write ambiguous aliases.
1573 if (!ta.second.GeneratorTarget) {
1574 continue;
1577 // Don't write alias if there is a already a custom command with
1578 // matching output
1579 if (this->HasCustomCommandOutput(ta.first)) {
1580 continue;
1583 build.Outputs.front() = ta.first;
1584 build.ExplicitDeps.clear();
1585 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1586 build.ExplicitDeps, config,
1587 DependOnTargetArtifact);
1588 this->WriteBuild(*this->GetConfigFileStream(config), build);
1592 if (!this->DefaultConfigs.empty()) {
1593 for (auto const& ta : this->DefaultTargetAliases) {
1594 // Don't write ambiguous aliases.
1595 if (!ta.second.GeneratorTarget) {
1596 continue;
1599 // Don't write alias if there is a already a custom command with
1600 // matching output
1601 if (this->HasCustomCommandOutput(ta.first)) {
1602 continue;
1605 build.Outputs.front() = ta.first;
1606 build.ExplicitDeps.clear();
1607 for (auto const& config : this->DefaultConfigs) {
1608 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1609 build.ExplicitDeps, config,
1610 DependOnTargetArtifact);
1612 this->WriteBuild(*this->GetDefaultFileStream(), build);
1618 void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os)
1620 cmGlobalNinjaGenerator::WriteDivider(os);
1621 os << "# Folder targets.\n\n";
1623 std::map<std::string, DirectoryTarget> dirTargets =
1624 this->ComputeDirectoryTargets();
1626 // Codegen target
1627 if (this->CheckCMP0171()) {
1628 for (auto const& it : dirTargets) {
1629 cmNinjaBuild build("phony");
1630 cmGlobalNinjaGenerator::WriteDivider(os);
1631 std::string const& currentBinaryDir = it.first;
1632 DirectoryTarget const& dt = it.second;
1633 std::vector<std::string> configs =
1634 dt.LG->GetMakefile()->GetGeneratorConfigs(
1635 cmMakefile::IncludeEmptyConfig);
1637 // Setup target
1638 cmNinjaDeps configDeps;
1639 build.Comment = cmStrCat("Folder: ", currentBinaryDir);
1640 build.Outputs.emplace_back();
1641 std::string const buildDirAllTarget =
1642 this->ConvertToNinjaPath(cmStrCat(currentBinaryDir, "/codegen"));
1644 cmNinjaDeps& explicitDeps = build.ExplicitDeps;
1646 for (auto const& config : configs) {
1647 explicitDeps.clear();
1649 for (DirectoryTarget::Target const& t : dt.Targets) {
1650 if (this->IsExcludedFromAllInConfig(t, config)) {
1651 continue;
1654 std::vector<cmSourceFile const*> customCommandSources;
1655 t.GT->GetCustomCommands(customCommandSources, config);
1656 for (cmSourceFile const* sf : customCommandSources) {
1657 cmCustomCommand const* cc = sf->GetCustomCommand();
1658 if (cc->GetCodegen()) {
1659 auto const& outputs = cc->GetOutputs();
1661 std::transform(outputs.begin(), outputs.end(),
1662 std::back_inserter(explicitDeps),
1663 this->MapToNinjaPath());
1668 build.Outputs.front() = this->BuildAlias(buildDirAllTarget, config);
1669 // Write target
1670 this->WriteBuild(this->EnableCrossConfigBuild() &&
1671 this->CrossConfigs.count(config)
1672 ? os
1673 : *this->GetImplFileStream(config),
1674 build);
1677 // Add shortcut target
1678 if (this->IsMultiConfig()) {
1679 for (auto const& config : configs) {
1680 build.ExplicitDeps = { this->BuildAlias(buildDirAllTarget, config) };
1681 build.Outputs.front() = buildDirAllTarget;
1682 this->WriteBuild(*this->GetConfigFileStream(config), build);
1685 if (!this->DefaultFileConfig.empty()) {
1686 build.ExplicitDeps.clear();
1687 for (auto const& config : this->DefaultConfigs) {
1688 build.ExplicitDeps.push_back(
1689 this->BuildAlias(buildDirAllTarget, config));
1691 build.Outputs.front() = buildDirAllTarget;
1692 this->WriteBuild(*this->GetDefaultFileStream(), build);
1696 // Add target for all configs
1697 if (this->EnableCrossConfigBuild()) {
1698 build.ExplicitDeps.clear();
1699 for (auto const& config : this->CrossConfigs) {
1700 build.ExplicitDeps.push_back(
1701 this->BuildAlias(buildDirAllTarget, config));
1703 build.Outputs.front() = this->BuildAlias(buildDirAllTarget, "codegen");
1704 this->WriteBuild(os, build);
1709 // All target
1710 for (auto const& it : dirTargets) {
1711 cmNinjaBuild build("phony");
1712 cmGlobalNinjaGenerator::WriteDivider(os);
1713 std::string const& currentBinaryDir = it.first;
1714 DirectoryTarget const& dt = it.second;
1715 std::vector<std::string> configs =
1716 static_cast<cmLocalNinjaGenerator const*>(dt.LG)->GetConfigNames();
1718 // Setup target
1719 cmNinjaDeps configDeps;
1720 build.Comment = cmStrCat("Folder: ", currentBinaryDir);
1721 build.Outputs.emplace_back();
1722 std::string const buildDirAllTarget =
1723 this->ConvertToNinjaPath(cmStrCat(currentBinaryDir, "/all"));
1724 for (auto const& config : configs) {
1725 build.ExplicitDeps.clear();
1726 build.Outputs.front() = this->BuildAlias(buildDirAllTarget, config);
1727 configDeps.emplace_back(build.Outputs.front());
1728 for (DirectoryTarget::Target const& t : dt.Targets) {
1729 if (!this->IsExcludedFromAllInConfig(t, config)) {
1730 this->AppendTargetOutputs(t.GT, build.ExplicitDeps, config,
1731 DependOnTargetArtifact);
1734 for (DirectoryTarget::Dir const& d : dt.Children) {
1735 if (!d.ExcludeFromAll) {
1736 build.ExplicitDeps.emplace_back(this->BuildAlias(
1737 this->ConvertToNinjaPath(cmStrCat(d.Path, "/all")), config));
1740 // Write target
1741 this->WriteBuild(this->EnableCrossConfigBuild() &&
1742 this->CrossConfigs.count(config)
1743 ? os
1744 : *this->GetImplFileStream(config),
1745 build);
1748 // Add shortcut target
1749 if (this->IsMultiConfig()) {
1750 for (auto const& config : configs) {
1751 build.ExplicitDeps = { this->BuildAlias(buildDirAllTarget, config) };
1752 build.Outputs.front() = buildDirAllTarget;
1753 this->WriteBuild(*this->GetConfigFileStream(config), build);
1756 if (!this->DefaultFileConfig.empty()) {
1757 build.ExplicitDeps.clear();
1758 for (auto const& config : this->DefaultConfigs) {
1759 build.ExplicitDeps.push_back(
1760 this->BuildAlias(buildDirAllTarget, config));
1762 build.Outputs.front() = buildDirAllTarget;
1763 this->WriteBuild(*this->GetDefaultFileStream(), build);
1767 // Add target for all configs
1768 if (this->EnableCrossConfigBuild()) {
1769 build.ExplicitDeps.clear();
1770 for (auto const& config : this->CrossConfigs) {
1771 build.ExplicitDeps.push_back(
1772 this->BuildAlias(buildDirAllTarget, config));
1774 build.Outputs.front() = this->BuildAlias(buildDirAllTarget, "all");
1775 this->WriteBuild(os, build);
1780 void cmGlobalNinjaGenerator::WriteUnknownExplicitDependencies(std::ostream& os)
1782 if (!this->ComputingUnknownDependencies) {
1783 return;
1786 // We need to collect the set of known build outputs.
1787 // Start with those generated by WriteBuild calls.
1788 // No other method needs this so we can take ownership
1789 // of the set locally and throw it out when we are done.
1790 std::set<std::string> knownDependencies;
1791 knownDependencies.swap(this->CombinedBuildOutputs);
1793 // now write out the unknown explicit dependencies.
1795 // union the configured files, evaluations files and the
1796 // CombinedBuildOutputs,
1797 // and then difference with CombinedExplicitDependencies to find the explicit
1798 // dependencies that we have no rule for
1800 cmGlobalNinjaGenerator::WriteDivider(os);
1801 /* clang-format off */
1802 os << "# Unknown Build Time Dependencies.\n"
1803 << "# Tell Ninja that they may appear as side effects of build rules\n"
1804 << "# otherwise ordered by order-only dependencies.\n\n";
1805 /* clang-format on */
1807 // get the list of files that cmake itself has generated as a
1808 // product of configuration.
1810 for (const auto& lg : this->LocalGenerators) {
1811 // get the vector of files created by this makefile and convert them
1812 // to ninja paths, which are all relative in respect to the build directory
1813 for (std::string const& file : lg->GetMakefile()->GetOutputFiles()) {
1814 knownDependencies.insert(this->ConvertToNinjaPath(file));
1816 if (!this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
1817 // get list files which are implicit dependencies as well and will be
1818 // phony for rebuild manifest
1819 for (std::string const& j : lg->GetMakefile()->GetListFiles()) {
1820 knownDependencies.insert(this->ConvertToNinjaPath(j));
1823 for (const auto& li : lg->GetMakefile()->GetEvaluationFiles()) {
1824 // get all the files created by generator expressions and convert them
1825 // to ninja paths
1826 for (std::string const& evaluationFile : li->GetFiles()) {
1827 knownDependencies.insert(this->ConvertToNinjaPath(evaluationFile));
1831 knownDependencies.insert(this->CMakeCacheFile);
1833 for (auto const& ta : this->TargetAliases) {
1834 knownDependencies.insert(this->ConvertToNinjaPath(ta.first));
1837 // remove all source files we know will exist.
1838 for (auto const& i : this->AssumedSourceDependencies) {
1839 knownDependencies.insert(this->ConvertToNinjaPath(i.first));
1842 // now we difference with CombinedCustomCommandExplicitDependencies to find
1843 // the list of items we know nothing about.
1844 // We have encoded all the paths in CombinedCustomCommandExplicitDependencies
1845 // and knownDependencies so no matter if unix or windows paths they
1846 // should all match now.
1848 std::vector<std::string> unknownExplicitDepends;
1849 this->CombinedCustomCommandExplicitDependencies.erase(this->TargetAll);
1851 std::set_difference(this->CombinedCustomCommandExplicitDependencies.begin(),
1852 this->CombinedCustomCommandExplicitDependencies.end(),
1853 knownDependencies.begin(), knownDependencies.end(),
1854 std::back_inserter(unknownExplicitDepends));
1856 std::vector<std::string> warnExplicitDepends;
1857 if (!unknownExplicitDepends.empty()) {
1858 cmake* cmk = this->GetCMakeInstance();
1859 std::string const& buildRoot = cmk->GetHomeOutputDirectory();
1860 bool const inSource = (buildRoot == cmk->GetHomeDirectory());
1861 bool const warn = (!inSource && (this->PolicyCMP0058 == cmPolicies::WARN));
1862 cmNinjaBuild build("phony");
1863 build.Outputs.emplace_back("");
1864 for (std::string const& ued : unknownExplicitDepends) {
1865 // verify the file is in the build directory
1866 std::string const absDepPath =
1867 cmSystemTools::CollapseFullPath(ued, buildRoot);
1868 if (cmSystemTools::IsSubDirectory(absDepPath, buildRoot)) {
1869 // Generate phony build statement
1870 build.Outputs[0] = ued;
1871 this->WriteBuild(os, build);
1872 // Add to warning on demand
1873 if (warn && warnExplicitDepends.size() < 10) {
1874 warnExplicitDepends.push_back(ued);
1880 if (!warnExplicitDepends.empty()) {
1881 std::ostringstream w;
1882 /* clang-format off */
1883 w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0058) << "\n"
1884 "This project specifies custom command DEPENDS on files "
1885 "in the build tree that are not specified as the OUTPUT or "
1886 "BYPRODUCTS of any add_custom_command or add_custom_target:\n"
1887 " " << cmJoin(warnExplicitDepends, "\n ") <<
1888 "\n"
1889 "For compatibility with versions of CMake that did not have "
1890 "the BYPRODUCTS option, CMake is generating phony rules for "
1891 "such files to convince 'ninja' to build."
1892 "\n"
1893 "Project authors should add the missing BYPRODUCTS or OUTPUT "
1894 "options to the custom commands that produce these files."
1896 /* clang-format on */
1897 this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
1898 w.str());
1902 void cmGlobalNinjaGenerator::WriteBuiltinTargets(std::ostream& os)
1904 // Write headers.
1905 cmGlobalNinjaGenerator::WriteDivider(os);
1906 os << "# Built-in targets\n\n";
1908 this->WriteTargetRebuildManifest(os);
1909 this->WriteTargetClean(os);
1910 this->WriteTargetHelp(os);
1912 for (std::string const& config : this->GetConfigNames()) {
1913 this->WriteTargetDefault(*this->GetConfigFileStream(config));
1916 if (!this->DefaultFileConfig.empty()) {
1917 this->WriteTargetDefault(*this->GetDefaultFileStream());
1920 if (this->InstallTargetEnabled &&
1921 this->GetCMakeInstance()->GetState()->GetGlobalPropertyAsBool(
1922 "INSTALL_PARALLEL") &&
1923 !this->Makefiles[0]->IsOn("CMAKE_SKIP_INSTALL_RULES")) {
1924 cmNinjaBuild build("phony");
1925 build.Comment = "Install every subdirectory in parallel";
1926 build.Outputs.emplace_back(this->GetInstallParallelTargetName());
1927 for (auto const& mf : this->Makefiles) {
1928 build.ExplicitDeps.emplace_back(
1929 this->ConvertToNinjaPath(cmStrCat(mf->GetCurrentBinaryDirectory(), "/",
1930 this->GetInstallLocalTargetName())));
1932 WriteBuild(os, build);
1936 void cmGlobalNinjaGenerator::WriteTargetDefault(std::ostream& os)
1938 if (!this->HasOutputPathPrefix()) {
1939 cmNinjaDeps all;
1940 all.push_back(this->TargetAll);
1941 cmGlobalNinjaGenerator::WriteDefault(os, all,
1942 "Make the all target the default.");
1946 void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
1948 if (this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
1949 return;
1952 cmake* cm = this->GetCMakeInstance();
1953 const auto& lg = this->LocalGenerators[0];
1956 cmNinjaRule rule("RERUN_CMAKE");
1957 rule.Command = cmStrCat(
1958 this->CMakeCmd(), " --regenerate-during-build",
1959 cm->GetIgnoreWarningAsError() ? " --compile-no-warning-as-error" : "",
1960 " -S",
1961 lg->ConvertToOutputFormat(lg->GetSourceDirectory(),
1962 cmOutputConverter::SHELL),
1963 " -B",
1964 lg->ConvertToOutputFormat(lg->GetBinaryDirectory(),
1965 cmOutputConverter::SHELL));
1966 rule.Description = "Re-running CMake...";
1967 rule.Comment = "Rule for re-running cmake.";
1968 rule.Generator = true;
1969 WriteRule(*this->RulesFileStream, rule);
1972 cmNinjaBuild reBuild("RERUN_CMAKE");
1973 reBuild.Comment = "Re-run CMake if any of its inputs changed.";
1974 this->AddRebuildManifestOutputs(reBuild.Outputs);
1976 for (const auto& localGen : this->LocalGenerators) {
1977 for (std::string const& fi : localGen->GetMakefile()->GetListFiles()) {
1978 reBuild.ImplicitDeps.push_back(this->ConvertToNinjaPath(fi));
1981 reBuild.ImplicitDeps.push_back(this->CMakeCacheFile);
1983 // Use 'console' pool to get non buffered output of the CMake re-run call
1984 // Available since Ninja 1.5
1985 if (this->SupportsDirectConsole()) {
1986 reBuild.Variables["pool"] = "console";
1989 if (this->SupportsManifestRestat() && cm->DoWriteGlobVerifyTarget()) {
1991 cmNinjaRule rule("VERIFY_GLOBS");
1992 rule.Command =
1993 cmStrCat(this->CMakeCmd(), " -P ",
1994 lg->ConvertToOutputFormat(cm->GetGlobVerifyScript(),
1995 cmOutputConverter::SHELL));
1996 rule.Description = "Re-checking globbed directories...";
1997 rule.Comment = "Rule for re-checking globbed directories.";
1998 rule.Generator = true;
1999 this->WriteRule(*this->RulesFileStream, rule);
2002 cmNinjaBuild phonyBuild("phony");
2003 phonyBuild.Comment = "Phony target to force glob verification run.";
2004 phonyBuild.Outputs.push_back(
2005 cmStrCat(cm->GetGlobVerifyScript(), "_force"));
2006 this->WriteBuild(os, phonyBuild);
2008 reBuild.Variables["restat"] = "1";
2009 std::string const verifyScriptFile =
2010 this->NinjaOutputPath(cm->GetGlobVerifyScript());
2011 std::string const verifyStampFile =
2012 this->NinjaOutputPath(cm->GetGlobVerifyStamp());
2014 cmNinjaBuild vgBuild("VERIFY_GLOBS");
2015 vgBuild.Comment =
2016 "Re-run CMake to check if globbed directories changed.";
2017 vgBuild.Outputs.push_back(verifyStampFile);
2018 vgBuild.ImplicitDeps = phonyBuild.Outputs;
2019 vgBuild.Variables = reBuild.Variables;
2020 this->WriteBuild(os, vgBuild);
2022 reBuild.Variables.erase("restat");
2023 reBuild.ImplicitDeps.push_back(verifyScriptFile);
2024 reBuild.ExplicitDeps.push_back(verifyStampFile);
2025 } else if (!this->SupportsManifestRestat() &&
2026 cm->DoWriteGlobVerifyTarget()) {
2027 std::ostringstream msg;
2028 msg << "The detected version of Ninja:\n"
2029 << " " << this->NinjaVersion << "\n"
2030 << "is less than the version of Ninja required by CMake for adding "
2031 "restat dependencies to the build.ninja manifest regeneration "
2032 "target:\n"
2033 << " "
2034 << cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat()
2035 << "\n";
2036 msg << "Any pre-check scripts, such as those generated for file(GLOB "
2037 "CONFIGURE_DEPENDS), will not be run by Ninja.";
2038 this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
2039 msg.str());
2042 std::sort(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end());
2043 reBuild.ImplicitDeps.erase(
2044 std::unique(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end()),
2045 reBuild.ImplicitDeps.end());
2047 this->WriteBuild(os, reBuild);
2050 cmNinjaBuild build("phony");
2051 build.Comment = "A missing CMake input file is not an error.";
2052 std::set_difference(std::make_move_iterator(reBuild.ImplicitDeps.begin()),
2053 std::make_move_iterator(reBuild.ImplicitDeps.end()),
2054 this->CustomCommandOutputs.begin(),
2055 this->CustomCommandOutputs.end(),
2056 std::back_inserter(build.Outputs));
2057 this->WriteBuild(os, build);
2061 std::string cmGlobalNinjaGenerator::CMakeCmd() const
2063 const auto& lgen = this->LocalGenerators.at(0);
2064 return lgen->ConvertToOutputFormat(cmSystemTools::GetCMakeCommand(),
2065 cmOutputConverter::SHELL);
2068 std::string cmGlobalNinjaGenerator::NinjaCmd() const
2070 const auto& lgen = this->LocalGenerators[0];
2071 if (lgen) {
2072 return lgen->ConvertToOutputFormat(this->NinjaCommand,
2073 cmOutputConverter::SHELL);
2075 return "ninja";
2078 bool cmGlobalNinjaGenerator::SupportsDirectConsole() const
2080 return this->NinjaSupportsConsolePool;
2083 bool cmGlobalNinjaGenerator::SupportsImplicitOuts() const
2085 return this->NinjaSupportsImplicitOuts;
2088 bool cmGlobalNinjaGenerator::SupportsManifestRestat() const
2090 return this->NinjaSupportsManifestRestat;
2093 bool cmGlobalNinjaGenerator::SupportsMultilineDepfile() const
2095 return this->NinjaSupportsMultilineDepfile;
2098 bool cmGlobalNinjaGenerator::SupportsCWDDepend() const
2100 return this->NinjaSupportsCWDDepend;
2103 bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
2105 const auto& lgr = this->LocalGenerators.at(0);
2106 std::string cleanScriptRel = "CMakeFiles/clean_additional.cmake";
2107 std::string cleanScriptAbs =
2108 cmStrCat(lgr->GetBinaryDirectory(), '/', cleanScriptRel);
2109 std::vector<std::string> const& configs = this->GetConfigNames();
2111 // Check if there are additional files to clean
2112 bool empty = true;
2113 for (auto const& config : configs) {
2114 auto const it = this->Configs.find(config);
2115 if (it != this->Configs.end() &&
2116 !it->second.AdditionalCleanFiles.empty()) {
2117 empty = false;
2118 break;
2121 if (empty) {
2122 // Remove cmake clean script file if it exists
2123 cmSystemTools::RemoveFile(cleanScriptAbs);
2124 return false;
2127 // Write cmake clean script file
2129 cmGeneratedFileStream fout(cleanScriptAbs);
2130 if (!fout) {
2131 return false;
2133 fout << "# Additional clean files\ncmake_minimum_required(VERSION 3.16)\n";
2134 for (auto const& config : configs) {
2135 auto const it = this->Configs.find(config);
2136 if (it != this->Configs.end() &&
2137 !it->second.AdditionalCleanFiles.empty()) {
2138 fout << "\nif(\"${CONFIG}\" STREQUAL \"\" OR \"${CONFIG}\" STREQUAL \""
2139 << config << "\")\n";
2140 fout << " file(REMOVE_RECURSE\n";
2141 for (std::string const& acf : it->second.AdditionalCleanFiles) {
2142 fout << " "
2143 << cmOutputConverter::EscapeForCMake(
2144 this->ConvertToNinjaPath(acf))
2145 << '\n';
2147 fout << " )\n";
2148 fout << "endif()\n";
2152 // Register clean script file
2153 lgr->GetMakefile()->AddCMakeOutputFile(cleanScriptAbs);
2155 // Write rule
2157 cmNinjaRule rule("CLEAN_ADDITIONAL");
2158 rule.Command = cmStrCat(
2159 this->CMakeCmd(), " -DCONFIG=$CONFIG -P ",
2160 lgr->ConvertToOutputFormat(this->NinjaOutputPath(cleanScriptRel),
2161 cmOutputConverter::SHELL));
2162 rule.Description = "Cleaning additional files...";
2163 rule.Comment = "Rule for cleaning additional files.";
2164 WriteRule(*this->RulesFileStream, rule);
2167 // Write build
2169 cmNinjaBuild build("CLEAN_ADDITIONAL");
2170 build.Comment = "Clean additional files.";
2171 build.Outputs.emplace_back();
2172 for (auto const& config : configs) {
2173 build.Outputs.front() = this->BuildAlias(
2174 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()), config);
2175 build.Variables["CONFIG"] = config;
2176 this->WriteBuild(os, build);
2178 if (this->IsMultiConfig()) {
2179 build.Outputs.front() =
2180 this->NinjaOutputPath(this->GetAdditionalCleanTargetName());
2181 build.Variables["CONFIG"] = "";
2182 this->WriteBuild(os, build);
2185 // Return success
2186 return true;
2189 void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os)
2191 // -- Additional clean target
2192 bool additionalFiles = this->WriteTargetCleanAdditional(os);
2194 // -- Default clean target
2195 // Write rule
2197 cmNinjaRule rule("CLEAN");
2198 rule.Command = cmStrCat(this->NinjaCmd(), " $FILE_ARG -t clean $TARGETS");
2199 rule.Description = "Cleaning all built files...";
2200 rule.Comment = "Rule for cleaning all built files.";
2201 WriteRule(*this->RulesFileStream, rule);
2204 // Write build
2206 cmNinjaBuild build("CLEAN");
2207 build.Comment = "Clean all the built files.";
2208 build.Outputs.emplace_back();
2210 for (std::string const& config : this->GetConfigNames()) {
2211 build.Outputs.front() = this->BuildAlias(
2212 this->NinjaOutputPath(this->GetCleanTargetName()), config);
2213 if (this->IsMultiConfig()) {
2214 build.Variables["TARGETS"] = cmStrCat(
2215 this->BuildAlias(
2216 this->NinjaOutputPath(GetByproductsForCleanTargetName()), config),
2217 " ", this->NinjaOutputPath(GetByproductsForCleanTargetName()));
2219 build.ExplicitDeps.clear();
2220 if (additionalFiles) {
2221 build.ExplicitDeps.push_back(this->BuildAlias(
2222 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
2223 config));
2225 for (std::string const& fileConfig : this->GetConfigNames()) {
2226 if (fileConfig != config && !this->EnableCrossConfigBuild()) {
2227 continue;
2229 if (this->IsMultiConfig()) {
2230 build.Variables["FILE_ARG"] = cmStrCat(
2231 "-f ",
2232 this->NinjaOutputPath(
2233 cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig)));
2235 this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
2239 if (this->EnableCrossConfigBuild()) {
2240 build.Outputs.front() = this->BuildAlias(
2241 this->NinjaOutputPath(this->GetCleanTargetName()), "all");
2242 build.ExplicitDeps.clear();
2244 if (additionalFiles) {
2245 for (auto const& config : this->CrossConfigs) {
2246 build.ExplicitDeps.push_back(this->BuildAlias(
2247 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
2248 config));
2252 std::vector<std::string> byproducts;
2253 byproducts.reserve(this->CrossConfigs.size());
2254 for (auto const& config : this->CrossConfigs) {
2255 byproducts.push_back(this->BuildAlias(
2256 this->NinjaOutputPath(GetByproductsForCleanTargetName()), config));
2258 byproducts.emplace_back(GetByproductsForCleanTargetName());
2259 build.Variables["TARGETS"] = cmJoin(byproducts, " ");
2261 for (std::string const& fileConfig : this->GetConfigNames()) {
2262 build.Variables["FILE_ARG"] = cmStrCat(
2263 "-f ",
2264 this->NinjaOutputPath(
2265 cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig)));
2266 this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
2271 if (this->IsMultiConfig()) {
2272 cmNinjaBuild build("phony");
2273 build.Outputs.emplace_back(
2274 this->NinjaOutputPath(this->GetCleanTargetName()));
2275 build.ExplicitDeps.emplace_back();
2277 for (std::string const& config : this->GetConfigNames()) {
2278 build.ExplicitDeps.front() = this->BuildAlias(
2279 this->NinjaOutputPath(this->GetCleanTargetName()), config);
2280 this->WriteBuild(*this->GetConfigFileStream(config), build);
2283 if (!this->DefaultConfigs.empty()) {
2284 build.ExplicitDeps.clear();
2285 for (auto const& config : this->DefaultConfigs) {
2286 build.ExplicitDeps.push_back(this->BuildAlias(
2287 this->NinjaOutputPath(this->GetCleanTargetName()), config));
2289 this->WriteBuild(*this->GetDefaultFileStream(), build);
2293 // Write byproducts
2294 if (this->IsMultiConfig()) {
2295 cmNinjaBuild build("phony");
2296 build.Comment = "Clean byproducts.";
2297 build.Outputs.emplace_back(
2298 this->ConvertToNinjaPath(GetByproductsForCleanTargetName()));
2299 build.ExplicitDeps = this->ByproductsForCleanTarget;
2300 this->WriteBuild(os, build);
2302 for (std::string const& config : this->GetConfigNames()) {
2303 build.Outputs.front() = this->BuildAlias(
2304 this->ConvertToNinjaPath(GetByproductsForCleanTargetName()), config);
2305 build.ExplicitDeps = this->Configs[config].ByproductsForCleanTarget;
2306 this->WriteBuild(os, build);
2311 void cmGlobalNinjaGenerator::WriteTargetHelp(std::ostream& os)
2314 cmNinjaRule rule("HELP");
2315 rule.Command = cmStrCat(this->NinjaCmd(), " -t targets");
2316 rule.Description = "All primary targets available:";
2317 rule.Comment = "Rule for printing all primary targets available.";
2318 WriteRule(*this->RulesFileStream, rule);
2321 cmNinjaBuild build("HELP");
2322 build.Comment = "Print all primary targets available.";
2323 build.Outputs.push_back(this->NinjaOutputPath("help"));
2324 this->WriteBuild(os, build);
2328 void cmGlobalNinjaGenerator::InitOutputPathPrefix()
2330 this->OutputPathPrefix =
2331 this->LocalGenerators[0]->GetMakefile()->GetSafeDefinition(
2332 "CMAKE_NINJA_OUTPUT_PATH_PREFIX");
2333 EnsureTrailingSlash(this->OutputPathPrefix);
2336 std::string cmGlobalNinjaGenerator::NinjaOutputPath(
2337 std::string const& path) const
2339 if (!this->HasOutputPathPrefix() || cmSystemTools::FileIsFullPath(path)) {
2340 return path;
2342 return cmStrCat(this->OutputPathPrefix, path);
2345 void cmGlobalNinjaGenerator::StripNinjaOutputPathPrefixAsSuffix(
2346 std::string& path)
2348 if (path.empty()) {
2349 return;
2351 EnsureTrailingSlash(path);
2352 cmStripSuffixIfExists(path, this->OutputPathPrefix);
2355 #if !defined(CMAKE_BOOTSTRAP)
2359 We use the following approach to support Fortran. Each target already
2360 has a <target>.dir/ directory used to hold intermediate files for CMake.
2361 For each target, a FortranDependInfo.json file is generated by CMake with
2362 information about include directories, module directories, and the locations
2363 the per-target directories for target dependencies.
2365 Compilation of source files within a target is split into the following steps:
2367 1. Preprocess all sources, scan preprocessed output for module dependencies.
2368 This step is done with independent build statements for each source,
2369 and can therefore be done in parallel.
2371 rule Fortran_PREPROCESS
2372 depfile = $DEP_FILE
2373 command = gfortran -cpp $DEFINES $INCLUDES $FLAGS -E $in -o $out &&
2374 cmake -E cmake_ninja_depends \
2375 --tdi=FortranDependInfo.json --lang=Fortran \
2376 --src=$out --out=$out --dep=$DEP_FILE --obj=$OBJ_FILE \
2377 --ddi=$DYNDEP_INTERMEDIATE_FILE
2379 build src.f90-pp.f90 | src.f90.o.ddi: Fortran_PREPROCESS src.f90
2380 OBJ_FILE = src.f90.o
2381 DEP_FILE = src.f90.o.d
2382 DYNDEP_INTERMEDIATE_FILE = src.f90.o.ddi
2384 The ``cmake -E cmake_ninja_depends`` tool reads the preprocessed output
2385 and generates the ninja depfile for preprocessor dependencies. It also
2386 generates a "ddi" file (in a format private to CMake) that lists the
2387 object file that compilation will produce along with the module names
2388 it provides and/or requires. The "ddi" file is an implicit output
2389 because it should not appear in "$out" but is generated by the rule.
2391 2. Consolidate the per-source module dependencies saved in the "ddi"
2392 files from all sources to produce a ninja "dyndep" file, ``Fortran.dd``.
2394 rule Fortran_DYNDEP
2395 command = cmake -E cmake_ninja_dyndep \
2396 --tdi=FortranDependInfo.json --lang=Fortran --dd=$out $in
2398 build Fortran.dd: Fortran_DYNDEP src1.f90.o.ddi src2.f90.o.ddi
2400 The ``cmake -E cmake_ninja_dyndep`` tool reads the "ddi" files from all
2401 sources in the target and the ``FortranModules.json`` files from targets
2402 on which the target depends. It computes dependency edges on compilations
2403 that require modules to those that provide the modules. This information
2404 is placed in the ``Fortran.dd`` file for ninja to load later. It also
2405 writes the expected location of modules provided by this target into
2406 ``FortranModules.json`` for use by dependent targets.
2408 3. Compile all sources after loading dynamically discovered dependencies
2409 of the compilation build statements from their ``dyndep`` bindings.
2411 rule Fortran_COMPILE
2412 command = gfortran $INCLUDES $FLAGS -c $in -o $out
2414 build src1.f90.o: Fortran_COMPILE src1.f90-pp.f90 || Fortran.dd
2415 dyndep = Fortran.dd
2417 The "dyndep" binding tells ninja to load dynamically discovered
2418 dependency information from ``Fortran.dd``. This adds information
2419 such as:
2421 build src1.f90.o | mod1.mod: dyndep
2422 restat = 1
2424 This tells ninja that ``mod1.mod`` is an implicit output of compiling
2425 the object file ``src1.f90.o``. The ``restat`` binding tells it that
2426 the timestamp of the output may not always change. Additionally:
2428 build src2.f90.o: dyndep | mod1.mod
2430 This tells ninja that ``mod1.mod`` is a dependency of compiling the
2431 object file ``src2.f90.o``. This ensures that ``src1.f90.o`` and
2432 ``mod1.mod`` will always be up to date before ``src2.f90.o`` is built
2433 (because the latter consumes the module).
2436 namespace {
2438 struct cmSourceInfo
2440 cmScanDepInfo ScanDep;
2441 std::vector<std::string> Includes;
2444 cm::optional<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
2445 std::string const& arg_tdi, std::string const& arg_src,
2446 std::string const& arg_src_orig);
2449 int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg,
2450 std::vector<std::string>::const_iterator argEnd)
2452 std::string arg_tdi;
2453 std::string arg_src;
2454 std::string arg_src_orig;
2455 std::string arg_out;
2456 std::string arg_dep;
2457 std::string arg_obj;
2458 std::string arg_ddi;
2459 std::string arg_lang;
2460 for (std::string const& arg : cmMakeRange(argBeg, argEnd)) {
2461 if (cmHasLiteralPrefix(arg, "--tdi=")) {
2462 arg_tdi = arg.substr(6);
2463 } else if (cmHasLiteralPrefix(arg, "--src=")) {
2464 arg_src = arg.substr(6);
2465 } else if (cmHasLiteralPrefix(arg, "--src-orig=")) {
2466 arg_src_orig = arg.substr(11);
2467 } else if (cmHasLiteralPrefix(arg, "--out=")) {
2468 arg_out = arg.substr(6);
2469 } else if (cmHasLiteralPrefix(arg, "--dep=")) {
2470 arg_dep = arg.substr(6);
2471 } else if (cmHasLiteralPrefix(arg, "--obj=")) {
2472 arg_obj = arg.substr(6);
2473 } else if (cmHasLiteralPrefix(arg, "--ddi=")) {
2474 arg_ddi = arg.substr(6);
2475 } else if (cmHasLiteralPrefix(arg, "--lang=")) {
2476 arg_lang = arg.substr(7);
2477 } else if (cmHasLiteralPrefix(arg, "--pp=")) {
2478 // CMake 3.26 and below used '--pp=' instead of '--src=' and '--out='.
2479 arg_src = arg.substr(5);
2480 arg_out = arg_src;
2481 } else {
2482 cmSystemTools::Error(
2483 cmStrCat("-E cmake_ninja_depends unknown argument: ", arg));
2484 return 1;
2487 if (arg_tdi.empty()) {
2488 cmSystemTools::Error("-E cmake_ninja_depends requires value for --tdi=");
2489 return 1;
2491 if (arg_src.empty()) {
2492 cmSystemTools::Error("-E cmake_ninja_depends requires value for --src=");
2493 return 1;
2495 if (arg_out.empty()) {
2496 cmSystemTools::Error("-E cmake_ninja_depends requires value for --out=");
2497 return 1;
2499 if (arg_dep.empty()) {
2500 cmSystemTools::Error("-E cmake_ninja_depends requires value for --dep=");
2501 return 1;
2503 if (arg_obj.empty()) {
2504 cmSystemTools::Error("-E cmake_ninja_depends requires value for --obj=");
2505 return 1;
2507 if (arg_ddi.empty()) {
2508 cmSystemTools::Error("-E cmake_ninja_depends requires value for --ddi=");
2509 return 1;
2511 if (arg_lang.empty()) {
2512 cmSystemTools::Error("-E cmake_ninja_depends requires value for --lang=");
2513 return 1;
2516 cm::optional<cmSourceInfo> info;
2517 if (arg_lang == "Fortran") {
2518 info = cmcmd_cmake_ninja_depends_fortran(arg_tdi, arg_src, arg_src_orig);
2519 } else {
2520 cmSystemTools::Error(
2521 cmStrCat("-E cmake_ninja_depends does not understand the ", arg_lang,
2522 " language"));
2523 return 1;
2526 if (!info) {
2527 // The error message is already expected to have been output.
2528 return 1;
2531 info->ScanDep.PrimaryOutput = arg_obj;
2534 cmGeneratedFileStream depfile(arg_dep);
2535 depfile << cmSystemTools::ConvertToUnixOutputPath(arg_out) << ":";
2536 for (std::string const& include : info->Includes) {
2537 depfile << " \\\n " << cmSystemTools::ConvertToUnixOutputPath(include);
2539 depfile << "\n";
2542 if (!cmScanDepFormat_P1689_Write(arg_ddi, info->ScanDep)) {
2543 cmSystemTools::Error(
2544 cmStrCat("-E cmake_ninja_depends failed to write ", arg_ddi));
2545 return 1;
2547 return 0;
2550 namespace {
2552 cm::optional<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
2553 std::string const& arg_tdi, std::string const& arg_src,
2554 std::string const& arg_src_orig)
2556 cm::optional<cmSourceInfo> info;
2557 cmFortranCompiler fc;
2558 std::vector<std::string> includes;
2559 std::string dir_top_bld;
2560 std::string module_dir;
2562 if (!arg_src_orig.empty()) {
2563 // Prepend the original source file's directory as an include directory
2564 // so Fortran INCLUDE statements can look for files in it.
2565 std::string src_orig_dir = cmSystemTools::GetParentDirectory(arg_src_orig);
2566 if (!src_orig_dir.empty()) {
2567 includes.push_back(src_orig_dir);
2572 Json::Value tdio;
2573 Json::Value const& tdi = tdio;
2575 cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary);
2576 Json::Reader reader;
2577 if (!reader.parse(tdif, tdio, false)) {
2578 cmSystemTools::Error(
2579 cmStrCat("-E cmake_ninja_depends failed to parse ", arg_tdi,
2580 reader.getFormattedErrorMessages()));
2581 return info;
2585 dir_top_bld = tdi["dir-top-bld"].asString();
2586 if (!dir_top_bld.empty() && !cmHasLiteralSuffix(dir_top_bld, "/")) {
2587 dir_top_bld += '/';
2590 Json::Value const& tdi_include_dirs = tdi["include-dirs"];
2591 if (tdi_include_dirs.isArray()) {
2592 for (auto const& tdi_include_dir : tdi_include_dirs) {
2593 includes.push_back(tdi_include_dir.asString());
2597 Json::Value const& tdi_module_dir = tdi["module-dir"];
2598 module_dir = tdi_module_dir.asString();
2599 if (!module_dir.empty() && !cmHasLiteralSuffix(module_dir, "/")) {
2600 module_dir += '/';
2603 Json::Value const& tdi_compiler_id = tdi["compiler-id"];
2604 fc.Id = tdi_compiler_id.asString();
2606 Json::Value const& tdi_submodule_sep = tdi["submodule-sep"];
2607 fc.SModSep = tdi_submodule_sep.asString();
2609 Json::Value const& tdi_submodule_ext = tdi["submodule-ext"];
2610 fc.SModExt = tdi_submodule_ext.asString();
2613 cmFortranSourceInfo finfo;
2614 std::set<std::string> defines;
2615 cmFortranParser parser(fc, includes, defines, finfo);
2616 if (!cmFortranParser_FilePush(&parser, arg_src.c_str())) {
2617 cmSystemTools::Error(
2618 cmStrCat("-E cmake_ninja_depends failed to open ", arg_src));
2619 return info;
2621 if (cmFortran_yyparse(parser.Scanner) != 0) {
2622 // Failed to parse the file.
2623 return info;
2626 info = cmSourceInfo();
2627 for (std::string const& provide : finfo.Provides) {
2628 cmSourceReqInfo src_info;
2629 src_info.LogicalName = provide;
2630 if (!module_dir.empty()) {
2631 std::string mod = cmStrCat(module_dir, provide);
2632 if (!dir_top_bld.empty() && cmHasPrefix(mod, dir_top_bld)) {
2633 mod = mod.substr(dir_top_bld.size());
2635 src_info.CompiledModulePath = std::move(mod);
2637 info->ScanDep.Provides.emplace_back(src_info);
2639 for (std::string const& require : finfo.Requires) {
2640 // Require modules not provided in the same source.
2641 if (finfo.Provides.count(require)) {
2642 continue;
2644 cmSourceReqInfo src_info;
2645 src_info.LogicalName = require;
2646 info->ScanDep.Requires.emplace_back(src_info);
2648 for (std::string const& include : finfo.Includes) {
2649 info->Includes.push_back(include);
2651 return info;
2655 bool cmGlobalNinjaGenerator::WriteDyndepFile(
2656 std::string const& dir_top_src, std::string const& dir_top_bld,
2657 std::string const& dir_cur_src, std::string const& dir_cur_bld,
2658 std::string const& arg_dd, std::vector<std::string> const& arg_ddis,
2659 std::string const& module_dir,
2660 std::vector<std::string> const& linked_target_dirs,
2661 std::vector<std::string> const& forward_modules_from_target_dirs,
2662 std::string const& arg_lang, std::string const& arg_modmapfmt,
2663 cmCxxModuleExportInfo const& export_info)
2665 // Setup path conversions.
2667 cmStateSnapshot snapshot = this->GetCMakeInstance()->GetCurrentSnapshot();
2668 snapshot.GetDirectory().SetCurrentSource(dir_cur_src);
2669 snapshot.GetDirectory().SetCurrentBinary(dir_cur_bld);
2670 auto mfd = cm::make_unique<cmMakefile>(this, snapshot);
2671 auto lgd = this->CreateLocalGenerator(mfd.get());
2672 lgd->SetRelativePathTop(dir_top_src, dir_top_bld);
2673 this->Makefiles.push_back(std::move(mfd));
2674 this->LocalGenerators.push_back(std::move(lgd));
2677 std::vector<cmScanDepInfo> objects;
2678 for (std::string const& arg_ddi : arg_ddis) {
2679 cmScanDepInfo info;
2680 if (!cmScanDepFormat_P1689_Parse(arg_ddi, &info)) {
2681 cmSystemTools::Error(
2682 cmStrCat("-E cmake_ninja_dyndep failed to parse ddi file ", arg_ddi));
2683 return false;
2685 objects.push_back(std::move(info));
2688 CxxModuleUsage usages;
2690 // Map from module name to module file path, if known.
2691 struct AvailableModuleInfo
2693 std::string BmiPath;
2694 bool IsPrivate;
2696 std::map<std::string, AvailableModuleInfo> mod_files;
2698 // Populate the module map with those provided by linked targets first.
2699 for (std::string const& linked_target_dir : linked_target_dirs) {
2700 std::string const ltmn =
2701 cmStrCat(linked_target_dir, '/', arg_lang, "Modules.json");
2702 Json::Value ltm;
2703 cmsys::ifstream ltmf(ltmn.c_str(), std::ios::in | std::ios::binary);
2704 if (!ltmf) {
2705 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to open ",
2706 ltmn, " for module information"));
2707 return false;
2709 Json::Reader reader;
2710 if (!reader.parse(ltmf, ltm, false)) {
2711 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
2712 linked_target_dir,
2713 reader.getFormattedErrorMessages()));
2714 return false;
2716 if (ltm.isObject()) {
2717 Json::Value const& target_modules = ltm["modules"];
2718 if (target_modules.isObject()) {
2719 for (auto i = target_modules.begin(); i != target_modules.end(); ++i) {
2720 Json::Value const& visible_module = *i;
2721 if (visible_module.isObject()) {
2722 Json::Value const& bmi_path = visible_module["bmi"];
2723 Json::Value const& is_private = visible_module["is-private"];
2724 mod_files[i.key().asString()] = AvailableModuleInfo{
2725 bmi_path.asString(),
2726 is_private.asBool(),
2731 Json::Value const& target_modules_references = ltm["references"];
2732 if (target_modules_references.isObject()) {
2733 for (auto i = target_modules_references.begin();
2734 i != target_modules_references.end(); ++i) {
2735 if (i->isObject()) {
2736 Json::Value const& reference_path = (*i)["path"];
2737 CxxModuleReference module_reference;
2738 if (reference_path.isString()) {
2739 module_reference.Path = reference_path.asString();
2741 Json::Value const& reference_method = (*i)["lookup-method"];
2742 if (reference_method.isString()) {
2743 std::string reference = reference_method.asString();
2744 if (reference == "by-name") {
2745 module_reference.Method = LookupMethod::ByName;
2746 } else if (reference == "include-angle") {
2747 module_reference.Method = LookupMethod::IncludeAngle;
2748 } else if (reference == "include-quote") {
2749 module_reference.Method = LookupMethod::IncludeQuote;
2752 usages.Reference[i.key().asString()] = module_reference;
2756 Json::Value const& target_modules_usage = ltm["usages"];
2757 if (target_modules_usage.isObject()) {
2758 for (auto i = target_modules_usage.begin();
2759 i != target_modules_usage.end(); ++i) {
2760 if (i->isArray()) {
2761 for (auto j = i->begin(); j != i->end(); ++j) {
2762 usages.Usage[i.key().asString()].insert(j->asString());
2770 cm::optional<CxxModuleMapFormat> modmap_fmt;
2771 if (arg_modmapfmt.empty()) {
2772 // nothing to do.
2773 } else if (arg_modmapfmt == "clang") {
2774 modmap_fmt = CxxModuleMapFormat::Clang;
2775 } else if (arg_modmapfmt == "gcc") {
2776 modmap_fmt = CxxModuleMapFormat::Gcc;
2777 } else if (arg_modmapfmt == "msvc") {
2778 modmap_fmt = CxxModuleMapFormat::Msvc;
2779 } else {
2780 cmSystemTools::Error(
2781 cmStrCat("-E cmake_ninja_dyndep does not understand the ", arg_modmapfmt,
2782 " module map format"));
2783 return false;
2786 auto module_ext = CxxModuleMapExtension(modmap_fmt);
2788 // Extend the module map with those provided by this target.
2789 // We do this after loading the modules provided by linked targets
2790 // in case we have one of the same name that must be preferred.
2791 Json::Value target_modules = Json::objectValue;
2792 for (cmScanDepInfo const& object : objects) {
2793 for (auto const& p : object.Provides) {
2794 std::string mod;
2795 if (cmDyndepCollation::IsBmiOnly(export_info, object.PrimaryOutput)) {
2796 mod = object.PrimaryOutput;
2797 } else if (!p.CompiledModulePath.empty()) {
2798 // The scanner provided the path to the module file.
2799 mod = p.CompiledModulePath;
2800 if (!cmSystemTools::FileIsFullPath(mod)) {
2801 // Treat relative to work directory (top of build tree).
2802 mod = cmSystemTools::CollapseFullPath(mod, dir_top_bld);
2804 } else {
2805 // Assume the module file path matches the logical module name.
2806 std::string safe_logical_name =
2807 p.LogicalName; // TODO: needs fixing for header units
2808 cmSystemTools::ReplaceString(safe_logical_name, ":", "-");
2809 mod = cmStrCat(module_dir, safe_logical_name, module_ext);
2811 mod_files[p.LogicalName] = AvailableModuleInfo{
2812 mod,
2813 false, // Always visible within our own target.
2815 Json::Value& module_info = target_modules[p.LogicalName] =
2816 Json::objectValue;
2817 module_info["bmi"] = mod;
2818 module_info["is-private"] =
2819 cmDyndepCollation::IsObjectPrivate(object.PrimaryOutput, export_info);
2823 cmGeneratedFileStream ddf(arg_dd);
2824 ddf << "ninja_dyndep_version = 1.0\n";
2827 CxxModuleLocations locs;
2828 locs.RootDirectory = ".";
2829 locs.PathForGenerator = [this](std::string path) -> std::string {
2830 path = this->ConvertToNinjaPath(path);
2831 # ifdef _WIN32
2832 if (this->IsGCCOnWindows()) {
2833 std::replace(path.begin(), path.end(), '\\', '/');
2835 # endif
2836 return path;
2838 locs.BmiLocationForModule =
2839 [&mod_files](std::string const& logical) -> CxxBmiLocation {
2840 auto m = mod_files.find(logical);
2841 if (m != mod_files.end()) {
2842 if (m->second.IsPrivate) {
2843 return CxxBmiLocation::Private();
2845 return CxxBmiLocation::Known(m->second.BmiPath);
2847 return CxxBmiLocation::Unknown();
2850 // Insert information about the current target's modules.
2851 if (modmap_fmt) {
2852 bool private_usage_found = false;
2853 auto cycle_modules =
2854 CxxModuleUsageSeed(locs, objects, usages, private_usage_found);
2855 if (!cycle_modules.empty()) {
2856 cmSystemTools::Error(
2857 cmStrCat("Circular dependency detected in the C++ module import "
2858 "graph. See modules named: \"",
2859 cmJoin(cycle_modules, R"(", ")"_s), '"'));
2860 return false;
2862 if (private_usage_found) {
2863 // Already errored in the function.
2864 return false;
2868 cmNinjaBuild build("dyndep");
2869 build.Outputs.emplace_back("");
2870 for (cmScanDepInfo const& object : objects) {
2871 build.Outputs[0] = this->ConvertToNinjaPath(object.PrimaryOutput);
2872 build.ImplicitOuts.clear();
2873 for (auto const& p : object.Provides) {
2874 auto const implicitOut =
2875 this->ConvertToNinjaPath(mod_files[p.LogicalName].BmiPath);
2876 // Ignore the `provides` when the BMI is the output.
2877 if (implicitOut != build.Outputs[0]) {
2878 build.ImplicitOuts.emplace_back(implicitOut);
2881 build.ImplicitDeps.clear();
2882 for (auto const& r : object.Requires) {
2883 auto mit = mod_files.find(r.LogicalName);
2884 if (mit != mod_files.end()) {
2885 build.ImplicitDeps.push_back(
2886 this->ConvertToNinjaPath(mit->second.BmiPath));
2889 build.Variables.clear();
2890 if (!object.Provides.empty()) {
2891 build.Variables.emplace("restat", "1");
2894 if (modmap_fmt) {
2895 auto mm = CxxModuleMapContent(*modmap_fmt, locs, object, usages);
2897 // XXX(modmap): If changing this path construction, change
2898 // `cmNinjaTargetGenerator::WriteObjectBuildStatements` and
2899 // `cmNinjaTargetGenerator::ExportObjectCompileCommand` to generate the
2900 // corresponding file path.
2901 cmGeneratedFileStream mmf;
2902 mmf.Open(cmStrCat(object.PrimaryOutput, ".modmap"), false,
2903 CxxModuleMapOpenMode(*modmap_fmt) ==
2904 CxxModuleMapMode::Binary);
2905 mmf.SetCopyIfDifferent(true);
2906 mmf << mm;
2909 this->WriteBuild(ddf, build);
2913 Json::Value target_module_info = Json::objectValue;
2914 target_module_info["modules"] = target_modules;
2916 auto& target_usages = target_module_info["usages"] = Json::objectValue;
2917 for (auto const& u : usages.Usage) {
2918 auto& mod_usage = target_usages[u.first] = Json::arrayValue;
2919 for (auto const& v : u.second) {
2920 mod_usage.append(v);
2924 auto name_for_method = [](LookupMethod method) -> cm::static_string_view {
2925 switch (method) {
2926 case LookupMethod::ByName:
2927 return "by-name"_s;
2928 case LookupMethod::IncludeAngle:
2929 return "include-angle"_s;
2930 case LookupMethod::IncludeQuote:
2931 return "include-quote"_s;
2933 assert(false && "unsupported lookup method");
2934 return ""_s;
2937 auto& target_references = target_module_info["references"] =
2938 Json::objectValue;
2939 for (auto const& r : usages.Reference) {
2940 auto& mod_ref = target_references[r.first] = Json::objectValue;
2941 mod_ref["path"] = r.second.Path;
2942 mod_ref["lookup-method"] = std::string(name_for_method(r.second.Method));
2945 // Store the map of modules provided by this target in a file for
2946 // use by dependents that reference this target in linked-target-dirs.
2947 std::string const target_mods_file = cmStrCat(
2948 cmSystemTools::GetFilenamePath(arg_dd), '/', arg_lang, "Modules.json");
2950 // Populate the module map with those provided by linked targets first.
2951 for (std::string const& forward_modules_from_target_dir :
2952 forward_modules_from_target_dirs) {
2953 std::string const fmftn =
2954 cmStrCat(forward_modules_from_target_dir, '/', arg_lang, "Modules.json");
2955 Json::Value fmft;
2956 cmsys::ifstream fmftf(fmftn.c_str(), std::ios::in | std::ios::binary);
2957 if (!fmftf) {
2958 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to open ",
2959 fmftn, " for module information"));
2960 return false;
2962 Json::Reader reader;
2963 if (!reader.parse(fmftf, fmft, false)) {
2964 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
2965 forward_modules_from_target_dir,
2966 reader.getFormattedErrorMessages()));
2967 return false;
2969 if (!fmft.isObject()) {
2970 continue;
2973 auto forward_info = [](Json::Value& target, Json::Value const& source) {
2974 if (!source.isObject()) {
2975 return;
2978 for (auto i = source.begin(); i != source.end(); ++i) {
2979 std::string const key = i.key().asString();
2980 if (target.isMember(key)) {
2981 continue;
2983 target[key] = *i;
2987 // Forward info from forwarding targets into our collation.
2988 Json::Value& tmi_target_modules = target_module_info["modules"];
2989 forward_info(tmi_target_modules, fmft["modules"]);
2990 forward_info(target_references, fmft["references"]);
2991 forward_info(target_usages, fmft["usages"]);
2994 cmGeneratedFileStream tmf(target_mods_file);
2995 tmf.SetCopyIfDifferent(true);
2996 tmf << target_module_info;
2998 cmDyndepMetadataCallbacks cb;
2999 cb.ModuleFile =
3000 [mod_files](std::string const& name) -> cm::optional<std::string> {
3001 auto m = mod_files.find(name);
3002 if (m != mod_files.end()) {
3003 return m->second.BmiPath;
3005 return {};
3008 return cmDyndepCollation::WriteDyndepMetadata(arg_lang, objects, export_info,
3009 cb);
3012 int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
3013 std::vector<std::string>::const_iterator argEnd)
3015 std::vector<std::string> arg_full =
3016 cmSystemTools::HandleResponseFile(argBeg, argEnd);
3018 std::string arg_dd;
3019 std::string arg_lang;
3020 std::string arg_tdi;
3021 std::string arg_modmapfmt;
3022 std::vector<std::string> arg_ddis;
3023 for (std::string const& arg : arg_full) {
3024 if (cmHasLiteralPrefix(arg, "--tdi=")) {
3025 arg_tdi = arg.substr(6);
3026 } else if (cmHasLiteralPrefix(arg, "--lang=")) {
3027 arg_lang = arg.substr(7);
3028 } else if (cmHasLiteralPrefix(arg, "--dd=")) {
3029 arg_dd = arg.substr(5);
3030 } else if (cmHasLiteralPrefix(arg, "--modmapfmt=")) {
3031 arg_modmapfmt = arg.substr(12);
3032 } else if (!cmHasLiteralPrefix(arg, "--") &&
3033 cmHasLiteralSuffix(arg, ".ddi")) {
3034 arg_ddis.push_back(arg);
3035 } else {
3036 cmSystemTools::Error(
3037 cmStrCat("-E cmake_ninja_dyndep unknown argument: ", arg));
3038 return 1;
3041 if (arg_tdi.empty()) {
3042 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --tdi=");
3043 return 1;
3045 if (arg_lang.empty()) {
3046 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --lang=");
3047 return 1;
3049 if (arg_dd.empty()) {
3050 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --dd=");
3051 return 1;
3054 Json::Value tdio;
3055 Json::Value const& tdi = tdio;
3057 cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary);
3058 Json::Reader reader;
3059 if (!reader.parse(tdif, tdio, false)) {
3060 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
3061 arg_tdi,
3062 reader.getFormattedErrorMessages()));
3063 return 1;
3067 std::string const dir_cur_bld = tdi["dir-cur-bld"].asString();
3068 std::string const dir_cur_src = tdi["dir-cur-src"].asString();
3069 std::string const dir_top_bld = tdi["dir-top-bld"].asString();
3070 std::string const dir_top_src = tdi["dir-top-src"].asString();
3071 std::string module_dir = tdi["module-dir"].asString();
3072 if (!module_dir.empty() && !cmHasLiteralSuffix(module_dir, "/")) {
3073 module_dir += '/';
3075 std::vector<std::string> linked_target_dirs;
3076 Json::Value const& tdi_linked_target_dirs = tdi["linked-target-dirs"];
3077 if (tdi_linked_target_dirs.isArray()) {
3078 for (auto const& tdi_linked_target_dir : tdi_linked_target_dirs) {
3079 linked_target_dirs.push_back(tdi_linked_target_dir.asString());
3082 std::vector<std::string> forward_modules_from_target_dirs;
3083 Json::Value const& tdi_forward_modules_from_target_dirs =
3084 tdi["forward-modules-from-target-dirs"];
3085 if (tdi_forward_modules_from_target_dirs.isArray()) {
3086 for (auto const& tdi_forward_modules_from_target_dir :
3087 tdi_forward_modules_from_target_dirs) {
3088 forward_modules_from_target_dirs.push_back(
3089 tdi_forward_modules_from_target_dir.asString());
3092 std::string const compilerId = tdi["compiler-id"].asString();
3093 std::string const simulateId = tdi["compiler-simulate-id"].asString();
3094 std::string const compilerFrontendVariant =
3095 tdi["compiler-frontend-variant"].asString();
3097 auto export_info = cmDyndepCollation::ParseExportInfo(tdi);
3099 cmake cm(cmake::RoleInternal, cmState::Unknown);
3100 cm.SetHomeDirectory(dir_top_src);
3101 cm.SetHomeOutputDirectory(dir_top_bld);
3102 auto ggd = cm.CreateGlobalGenerator("Ninja");
3103 if (!ggd) {
3104 return 1;
3106 cmGlobalNinjaGenerator& gg =
3107 cm::static_reference_cast<cmGlobalNinjaGenerator>(ggd);
3108 # ifdef _WIN32
3109 if (DetectGCCOnWindows(compilerId, simulateId, compilerFrontendVariant)) {
3110 gg.MarkAsGCCOnWindows();
3112 # endif
3113 return gg.WriteDyndepFile(dir_top_src, dir_top_bld, dir_cur_src, dir_cur_bld,
3114 arg_dd, arg_ddis, module_dir, linked_target_dirs,
3115 forward_modules_from_target_dirs, arg_lang,
3116 arg_modmapfmt, *export_info)
3118 : 1;
3121 #endif
3123 bool cmGlobalNinjaGenerator::EnableCrossConfigBuild() const
3125 return !this->CrossConfigs.empty();
3128 void cmGlobalNinjaGenerator::AppendDirectoryForConfig(
3129 const std::string& prefix, const std::string& config,
3130 const std::string& suffix, std::string& dir)
3132 if (!config.empty() && this->IsMultiConfig()) {
3133 dir += cmStrCat(prefix, config, suffix);
3137 std::set<std::string> cmGlobalNinjaGenerator::GetCrossConfigs(
3138 const std::string& fileConfig) const
3140 auto result = this->CrossConfigs;
3141 result.insert(fileConfig);
3142 return result;
3145 bool cmGlobalNinjaGenerator::IsSingleConfigUtility(
3146 cmGeneratorTarget const* target) const
3148 return target->GetType() == cmStateEnums::UTILITY &&
3149 !this->PerConfigUtilityTargets.count(target->GetName());
3152 std::string cmGlobalNinjaGenerator::ConvertToOutputPath(std::string path) const
3154 return this->ConvertToNinjaPath(path);
3157 const char* cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE =
3158 "CMakeFiles/common.ninja";
3159 const char* cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION = ".ninja";
3161 cmGlobalNinjaMultiGenerator::cmGlobalNinjaMultiGenerator(cmake* cm)
3162 : cmGlobalNinjaGenerator(cm)
3164 cm->GetState()->SetIsGeneratorMultiConfig(true);
3165 cm->GetState()->SetNinjaMulti(true);
3168 cmDocumentationEntry cmGlobalNinjaMultiGenerator::GetDocumentation()
3170 return { cmGlobalNinjaMultiGenerator::GetActualName(),
3171 "Generates build-<Config>.ninja files." };
3174 std::string cmGlobalNinjaMultiGenerator::ExpandCFGIntDir(
3175 const std::string& str, const std::string& config) const
3177 std::string result = str;
3178 cmSystemTools::ReplaceString(result, this->GetCMakeCFGIntDir(), config);
3179 return result;
3182 bool cmGlobalNinjaMultiGenerator::OpenBuildFileStreams()
3184 if (!this->OpenFileStream(this->CommonFileStream,
3185 cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE)) {
3186 return false;
3189 if (!this->OpenFileStream(this->DefaultFileStream, NINJA_BUILD_FILE)) {
3190 return false;
3192 *this->DefaultFileStream << "# Build using rules for '"
3193 << this->DefaultFileConfig << "'.\n\n"
3194 << "include "
3195 << this->NinjaOutputPath(
3196 GetNinjaImplFilename(this->DefaultFileConfig))
3197 << "\n\n";
3199 // Write a comment about this file.
3200 *this->CommonFileStream
3201 << "# This file contains build statements common to all "
3202 "configurations.\n\n";
3204 std::vector<std::string> const& configs = this->GetConfigNames();
3205 return std::all_of(
3206 configs.begin(), configs.end(), [this](std::string const& config) -> bool {
3207 // Open impl file.
3208 if (!this->OpenFileStream(this->ImplFileStreams[config],
3209 GetNinjaImplFilename(config))) {
3210 return false;
3213 // Write a comment about this file.
3214 *this->ImplFileStreams[config]
3215 << "# This file contains build statements specific to the \"" << config
3216 << "\"\n# configuration.\n\n";
3218 // Open config file.
3219 if (!this->OpenFileStream(this->ConfigFileStreams[config],
3220 GetNinjaConfigFilename(config))) {
3221 return false;
3224 // Write a comment about this file.
3225 *this->ConfigFileStreams[config]
3226 << "# This file contains aliases specific to the \"" << config
3227 << "\"\n# configuration.\n\n"
3228 << "include " << this->NinjaOutputPath(GetNinjaImplFilename(config))
3229 << "\n\n";
3231 return true;
3235 void cmGlobalNinjaMultiGenerator::CloseBuildFileStreams()
3237 if (this->CommonFileStream) {
3238 this->CommonFileStream.reset();
3239 } else {
3240 cmSystemTools::Error("Common file stream was not open.");
3243 if (this->DefaultFileStream) {
3244 this->DefaultFileStream.reset();
3245 } // No error if it wasn't open
3247 for (std::string const& config : this->GetConfigNames()) {
3248 if (this->ImplFileStreams[config]) {
3249 this->ImplFileStreams[config].reset();
3250 } else {
3251 cmSystemTools::Error(
3252 cmStrCat("Impl file stream for \"", config, "\" was not open."));
3254 if (this->ConfigFileStreams[config]) {
3255 this->ConfigFileStreams[config].reset();
3256 } else {
3257 cmSystemTools::Error(
3258 cmStrCat("Config file stream for \"", config, "\" was not open."));
3263 void cmGlobalNinjaMultiGenerator::AppendNinjaFileArgument(
3264 GeneratedMakeCommand& command, const std::string& config) const
3266 if (!config.empty()) {
3267 command.Add("-f");
3268 command.Add(GetNinjaConfigFilename(config));
3272 std::string cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(
3273 const std::string& config)
3275 return cmStrCat("CMakeFiles/impl-", config,
3276 cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
3279 std::string cmGlobalNinjaMultiGenerator::GetNinjaConfigFilename(
3280 const std::string& config)
3282 return cmStrCat("build-", config,
3283 cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
3286 void cmGlobalNinjaMultiGenerator::AddRebuildManifestOutputs(
3287 cmNinjaDeps& outputs) const
3289 for (std::string const& config : this->GetConfigNames()) {
3290 outputs.push_back(this->NinjaOutputPath(GetNinjaImplFilename(config)));
3291 outputs.push_back(this->NinjaOutputPath(GetNinjaConfigFilename(config)));
3293 if (!this->DefaultFileConfig.empty()) {
3294 outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
3298 void cmGlobalNinjaMultiGenerator::GetQtAutoGenConfigs(
3299 std::vector<std::string>& configs) const
3301 std::vector<std::string> const& allConfigs = this->GetConfigNames();
3302 configs.insert(configs.end(), cm::cbegin(allConfigs), cm::cend(allConfigs));
3305 bool cmGlobalNinjaMultiGenerator::InspectConfigTypeVariables()
3307 std::vector<std::string> configsList =
3308 this->Makefiles.front()->GetGeneratorConfigs(
3309 cmMakefile::IncludeEmptyConfig);
3310 std::set<std::string> configs(configsList.cbegin(), configsList.cend());
3312 this->DefaultFileConfig =
3313 this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_BUILD_TYPE");
3314 if (this->DefaultFileConfig.empty()) {
3315 this->DefaultFileConfig = configsList.front();
3317 if (!configs.count(this->DefaultFileConfig)) {
3318 std::ostringstream msg;
3319 msg << "The configuration specified by "
3320 << "CMAKE_DEFAULT_BUILD_TYPE (" << this->DefaultFileConfig
3321 << ") is not present in CMAKE_CONFIGURATION_TYPES";
3322 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
3323 msg.str());
3324 return false;
3327 cmList crossConfigsList{ this->Makefiles.front()->GetSafeDefinition(
3328 "CMAKE_CROSS_CONFIGS") };
3329 auto crossConfigs = ListSubsetWithAll(configs, configs, crossConfigsList);
3330 if (!crossConfigs) {
3331 std::ostringstream msg;
3332 msg << "CMAKE_CROSS_CONFIGS is not a subset of "
3333 << "CMAKE_CONFIGURATION_TYPES";
3334 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
3335 msg.str());
3336 return false;
3338 this->CrossConfigs = *crossConfigs;
3340 auto defaultConfigsString =
3341 this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_CONFIGS");
3342 if (defaultConfigsString.empty()) {
3343 defaultConfigsString = this->DefaultFileConfig;
3345 if (!defaultConfigsString.empty() &&
3346 defaultConfigsString != this->DefaultFileConfig &&
3347 (this->DefaultFileConfig.empty() || this->CrossConfigs.empty())) {
3348 std::ostringstream msg;
3349 msg << "CMAKE_DEFAULT_CONFIGS cannot be used without "
3350 << "CMAKE_DEFAULT_BUILD_TYPE or CMAKE_CROSS_CONFIGS";
3351 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
3352 msg.str());
3353 return false;
3356 cmList defaultConfigsList(defaultConfigsString);
3357 if (!this->DefaultFileConfig.empty()) {
3358 auto defaultConfigs =
3359 ListSubsetWithAll(this->GetCrossConfigs(this->DefaultFileConfig),
3360 this->CrossConfigs, defaultConfigsList);
3361 if (!defaultConfigs) {
3362 std::ostringstream msg;
3363 msg << "CMAKE_DEFAULT_CONFIGS is not a subset of CMAKE_CROSS_CONFIGS";
3364 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
3365 msg.str());
3366 return false;
3368 this->DefaultConfigs = *defaultConfigs;
3371 return true;
3374 std::string cmGlobalNinjaMultiGenerator::GetDefaultBuildConfig() const
3376 return "";
3379 std::string cmGlobalNinjaMultiGenerator::OrderDependsTargetForTarget(
3380 cmGeneratorTarget const* target, const std::string& config) const
3382 return cmStrCat("cmake_object_order_depends_target_", target->GetName(), '_',
3383 cmSystemTools::UpperCase(config));