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"
4 
5 #include <algorithm>
6 #include <cctype>
7 #include <cstdio>
8 #include <sstream>
9 #include <utility>
10 
11 #include <cm/iterator>
12 #include <cm/memory>
13 #include <cm/optional>
14 #include <cm/string_view>
15 #include <cmext/algorithm>
16 #include <cmext/memory>
17 
18 #include <cm3p/json/reader.h>
19 #include <cm3p/json/value.h>
20 #include <cm3p/json/writer.h>
21 
22 #include "cmsys/FStream.hxx"
23 
24 #include "cmDocumentationEntry.h"
25 #include "cmFortranParser.h"
26 #include "cmGeneratedFileStream.h"
27 #include "cmGeneratorExpressionEvaluationFile.h"
28 #include "cmGeneratorTarget.h"
29 #include "cmGlobalGenerator.h"
30 #include "cmLinkLineComputer.h"
31 #include "cmListFileCache.h"
32 #include "cmLocalGenerator.h"
33 #include "cmLocalNinjaGenerator.h"
34 #include "cmMakefile.h"
35 #include "cmMessageType.h"
36 #include "cmNinjaLinkLineComputer.h"
37 #include "cmOutputConverter.h"
38 #include "cmRange.h"
39 #include "cmScanDepFormat.h"
40 #include "cmState.h"
41 #include "cmStateDirectory.h"
42 #include "cmStateSnapshot.h"
43 #include "cmStateTypes.h"
44 #include "cmStringAlgorithms.h"
45 #include "cmSystemTools.h"
46 #include "cmTarget.h"
47 #include "cmTargetDepend.h"
48 #include "cmValue.h"
49 #include "cmVersion.h"
50 #include "cmake.h"
51 
52 const char* cmGlobalNinjaGenerator::NINJA_BUILD_FILE = "build.ninja";
53 const char* cmGlobalNinjaGenerator::NINJA_RULES_FILE =
54   "CMakeFiles/rules.ninja";
55 const char* cmGlobalNinjaGenerator::INDENT = "  ";
56 #ifdef _WIN32
57 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = "cd .";
58 #else
59 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = ":";
60 #endif
61 
operator ==(const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & lhs,const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & rhs)62 bool operator==(
63   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
64   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
65 {
66   return lhs.Target == rhs.Target && lhs.Config == rhs.Config &&
67     lhs.GenexOutput == rhs.GenexOutput;
68 }
69 
operator !=(const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & lhs,const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & rhs)70 bool operator!=(
71   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
72   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
73 {
74   return !(lhs == rhs);
75 }
76 
operator <(const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & lhs,const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & rhs)77 bool operator<(
78   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
79   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
80 {
81   return lhs.Target < rhs.Target ||
82     (lhs.Target == rhs.Target &&
83      (lhs.Config < rhs.Config ||
84       (lhs.Config == rhs.Config && lhs.GenexOutput < rhs.GenexOutput)));
85 }
86 
operator >(const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & lhs,const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & rhs)87 bool operator>(
88   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
89   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
90 {
91   return rhs < lhs;
92 }
93 
operator <=(const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & lhs,const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & rhs)94 bool operator<=(
95   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
96   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
97 {
98   return !(lhs > rhs);
99 }
100 
operator >=(const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & lhs,const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey & rhs)101 bool operator>=(
102   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
103   const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
104 {
105   return rhs <= lhs;
106 }
107 
Indent(std::ostream & os,int count)108 void cmGlobalNinjaGenerator::Indent(std::ostream& os, int count)
109 {
110   for (int i = 0; i < count; ++i) {
111     os << cmGlobalNinjaGenerator::INDENT;
112   }
113 }
114 
WriteDivider(std::ostream & os)115 void cmGlobalNinjaGenerator::WriteDivider(std::ostream& os)
116 {
117   os << "# ======================================"
118         "=======================================\n";
119 }
120 
WriteComment(std::ostream & os,const std::string & comment)121 void cmGlobalNinjaGenerator::WriteComment(std::ostream& os,
122                                           const std::string& comment)
123 {
124   if (comment.empty()) {
125     return;
126   }
127 
128   std::string::size_type lpos = 0;
129   std::string::size_type rpos;
130   os << "\n#############################################\n";
131   while ((rpos = comment.find('\n', lpos)) != std::string::npos) {
132     os << "# " << comment.substr(lpos, rpos - lpos) << "\n";
133     lpos = rpos + 1;
134   }
135   os << "# " << comment.substr(lpos) << "\n\n";
136 }
137 
138 std::unique_ptr<cmLinkLineComputer>
CreateLinkLineComputer(cmOutputConverter * outputConverter,cmStateDirectory const &) const139 cmGlobalNinjaGenerator::CreateLinkLineComputer(
140   cmOutputConverter* outputConverter,
141   cmStateDirectory const& /* stateDir */) const
142 {
143   return std::unique_ptr<cmLinkLineComputer>(
144     cm::make_unique<cmNinjaLinkLineComputer>(
145       outputConverter,
146       this->LocalGenerators[0]->GetStateSnapshot().GetDirectory(), this));
147 }
148 
EncodeRuleName(std::string const & name)149 std::string cmGlobalNinjaGenerator::EncodeRuleName(std::string const& name)
150 {
151   // Ninja rule names must match "[a-zA-Z0-9_.-]+".  Use ".xx" to encode
152   // "." and all invalid characters as hexadecimal.
153   std::string encoded;
154   for (char i : name) {
155     if (isalnum(i) || i == '_' || i == '-') {
156       encoded += i;
157     } else {
158       char buf[16];
159       sprintf(buf, ".%02x", static_cast<unsigned int>(i));
160       encoded += buf;
161     }
162   }
163   return encoded;
164 }
165 
EncodeLiteral(const std::string & lit)166 std::string cmGlobalNinjaGenerator::EncodeLiteral(const std::string& lit)
167 {
168   std::string result = lit;
169   cmSystemTools::ReplaceString(result, "$", "$$");
170   cmSystemTools::ReplaceString(result, "\n", "$\n");
171   if (this->IsMultiConfig()) {
172     cmSystemTools::ReplaceString(result,
173                                  cmStrCat('$', this->GetCMakeCFGIntDir()),
174                                  this->GetCMakeCFGIntDir());
175   }
176   return result;
177 }
178 
EncodePath(const std::string & path)179 std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path)
180 {
181   std::string result = path;
182 #ifdef _WIN32
183   if (this->IsGCCOnWindows())
184     std::replace(result.begin(), result.end(), '\\', '/');
185   else
186     std::replace(result.begin(), result.end(), '/', '\\');
187 #endif
188   result = this->EncodeLiteral(result);
189   cmSystemTools::ReplaceString(result, " ", "$ ");
190   cmSystemTools::ReplaceString(result, ":", "$:");
191   return result;
192 }
193 
WriteBuild(std::ostream & os,cmNinjaBuild const & build,int cmdLineLimit,bool * usedResponseFile)194 void cmGlobalNinjaGenerator::WriteBuild(std::ostream& os,
195                                         cmNinjaBuild const& build,
196                                         int cmdLineLimit,
197                                         bool* usedResponseFile)
198 {
199   // Make sure there is a rule.
200   if (build.Rule.empty()) {
201     cmSystemTools::Error(cmStrCat(
202       "No rule for WriteBuild! called with comment: ", build.Comment));
203     return;
204   }
205 
206   // Make sure there is at least one output file.
207   if (build.Outputs.empty()) {
208     cmSystemTools::Error(cmStrCat(
209       "No output files for WriteBuild! called with comment: ", build.Comment));
210     return;
211   }
212 
213   cmGlobalNinjaGenerator::WriteComment(os, build.Comment);
214 
215   // Write output files.
216   std::string buildStr("build");
217   {
218     // Write explicit outputs
219     for (std::string const& output : build.Outputs) {
220       buildStr = cmStrCat(buildStr, ' ', this->EncodePath(output));
221       if (this->ComputingUnknownDependencies) {
222         this->CombinedBuildOutputs.insert(output);
223       }
224     }
225     // Write implicit outputs
226     if (!build.ImplicitOuts.empty()) {
227       // Assume Ninja is new enough to support implicit outputs.
228       // Callers should not populate this field otherwise.
229       buildStr = cmStrCat(buildStr, " |");
230       for (std::string const& implicitOut : build.ImplicitOuts) {
231         buildStr = cmStrCat(buildStr, ' ', this->EncodePath(implicitOut));
232         if (this->ComputingUnknownDependencies) {
233           this->CombinedBuildOutputs.insert(implicitOut);
234         }
235       }
236     }
237 
238     // Repeat some outputs, but expressed as absolute paths.
239     // This helps Ninja handle absolute paths found in a depfile.
240     // FIXME: Unfortunately this causes Ninja to stat the file twice.
241     // We could avoid this if Ninja Issue 1251 were fixed.
242     if (!build.WorkDirOuts.empty()) {
243       if (this->SupportsImplicitOuts() && build.ImplicitOuts.empty()) {
244         // Make them implicit outputs if supported by this version of Ninja.
245         buildStr = cmStrCat(buildStr, " |");
246       }
247       for (std::string const& workdirOut : build.WorkDirOuts) {
248         buildStr = cmStrCat(buildStr, " ${cmake_ninja_workdir}",
249                             this->EncodePath(workdirOut));
250       }
251     }
252 
253     // Write the rule.
254     buildStr = cmStrCat(buildStr, ": ", build.Rule);
255   }
256 
257   std::string arguments;
258   {
259     // TODO: Better formatting for when there are multiple input/output files.
260 
261     // Write explicit dependencies.
262     for (std::string const& explicitDep : build.ExplicitDeps) {
263       arguments += cmStrCat(' ', this->EncodePath(explicitDep));
264     }
265 
266     // Write implicit dependencies.
267     if (!build.ImplicitDeps.empty()) {
268       arguments += " |";
269       for (std::string const& implicitDep : build.ImplicitDeps) {
270         arguments += cmStrCat(' ', this->EncodePath(implicitDep));
271       }
272     }
273 
274     // Write order-only dependencies.
275     if (!build.OrderOnlyDeps.empty()) {
276       arguments += " ||";
277       for (std::string const& orderOnlyDep : build.OrderOnlyDeps) {
278         arguments += cmStrCat(' ', this->EncodePath(orderOnlyDep));
279       }
280     }
281 
282     arguments += '\n';
283   }
284 
285   // Write the variables bound to this build statement.
286   std::string assignments;
287   {
288     std::ostringstream variable_assignments;
289     for (auto const& variable : build.Variables) {
290       cmGlobalNinjaGenerator::WriteVariable(
291         variable_assignments, variable.first, variable.second, "", 1);
292     }
293 
294     // check if a response file rule should be used
295     assignments = variable_assignments.str();
296     bool useResponseFile = false;
297     if (cmdLineLimit < 0 ||
298         (cmdLineLimit > 0 &&
299          (arguments.size() + buildStr.size() + assignments.size() + 1000) >
300            static_cast<size_t>(cmdLineLimit))) {
301       variable_assignments.str(std::string());
302       cmGlobalNinjaGenerator::WriteVariable(variable_assignments, "RSP_FILE",
303                                             build.RspFile, "", 1);
304       assignments += variable_assignments.str();
305       useResponseFile = true;
306     }
307     if (usedResponseFile) {
308       *usedResponseFile = useResponseFile;
309     }
310   }
311 
312   if (build.Variables.count("dyndep") > 0) {
313     // The ninja 'cleandead' operation does not account for outputs
314     // discovered by 'dyndep' bindings.  Avoid removing them.
315     this->DisableCleandead = true;
316   }
317 
318   os << buildStr << arguments << assignments << "\n";
319 }
320 
AddCustomCommandRule()321 void cmGlobalNinjaGenerator::AddCustomCommandRule()
322 {
323   cmNinjaRule rule("CUSTOM_COMMAND");
324   rule.Command = "$COMMAND";
325   rule.Description = "$DESC";
326   rule.Comment = "Rule for running custom commands.";
327   this->AddRule(rule);
328 }
329 
Add(std::vector<std::string> const & paths)330 void cmGlobalNinjaGenerator::CCOutputs::Add(
331   std::vector<std::string> const& paths)
332 {
333   for (std::string const& path : paths) {
334     std::string out = this->GG->ConvertToNinjaPath(path);
335     if (!cmSystemTools::FileIsFullPath(out)) {
336       // This output is expressed as a relative path.  Repeat it,
337       // but expressed as an absolute path for Ninja Issue 1251.
338       this->WorkDirOuts.emplace_back(out);
339       this->GG->SeenCustomCommandOutput(this->GG->ConvertToNinjaAbsPath(path));
340     }
341     this->GG->SeenCustomCommandOutput(out);
342     this->ExplicitOuts.emplace_back(std::move(out));
343   }
344 }
345 
WriteCustomCommandBuild(std::string const & command,std::string const & description,std::string const & comment,std::string const & depfile,std::string const & job_pool,bool uses_terminal,bool restat,std::string const & config,CCOutputs outputs,cmNinjaDeps explicitDeps,cmNinjaDeps orderOnlyDeps)346 void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
347   std::string const& command, std::string const& description,
348   std::string const& comment, std::string const& depfile,
349   std::string const& job_pool, bool uses_terminal, bool restat,
350   std::string const& config, CCOutputs outputs, cmNinjaDeps explicitDeps,
351   cmNinjaDeps orderOnlyDeps)
352 {
353   this->AddCustomCommandRule();
354 
355   if (this->ComputingUnknownDependencies) {
356     // we need to track every dependency that comes in, since we are trying
357     // to find dependencies that are side effects of build commands
358     for (std::string const& dep : explicitDeps) {
359       this->CombinedCustomCommandExplicitDependencies.insert(dep);
360     }
361   }
362 
363   {
364     cmNinjaBuild build("CUSTOM_COMMAND");
365     build.Comment = comment;
366     build.Outputs = std::move(outputs.ExplicitOuts);
367     build.WorkDirOuts = std::move(outputs.WorkDirOuts);
368     build.ExplicitDeps = std::move(explicitDeps);
369     build.OrderOnlyDeps = std::move(orderOnlyDeps);
370 
371     cmNinjaVars& vars = build.Variables;
372     {
373       std::string cmd = command; // NOLINT(*)
374 #ifdef _WIN32
375       if (cmd.empty())
376         // TODO Shouldn't an empty command be handled by ninja?
377         cmd = "cmd.exe /c";
378 #endif
379       vars["COMMAND"] = std::move(cmd);
380     }
381     vars["DESC"] = this->EncodeLiteral(description);
382     if (restat) {
383       vars["restat"] = "1";
384     }
385     if (uses_terminal && this->SupportsDirectConsole()) {
386       vars["pool"] = "console";
387     } else if (!job_pool.empty()) {
388       vars["pool"] = job_pool;
389     }
390     if (!depfile.empty()) {
391       vars["depfile"] = depfile;
392     }
393     if (config.empty()) {
394       this->WriteBuild(*this->GetCommonFileStream(), build);
395     } else {
396       this->WriteBuild(*this->GetImplFileStream(config), build);
397     }
398   }
399 }
400 
AddMacOSXContentRule()401 void cmGlobalNinjaGenerator::AddMacOSXContentRule()
402 {
403   cmNinjaRule rule("COPY_OSX_CONTENT");
404   rule.Command = cmStrCat(this->CMakeCmd(), " -E copy $in $out");
405   rule.Description = "Copying OS X Content $out";
406   rule.Comment = "Rule for copying OS X bundle content file.";
407   this->AddRule(rule);
408 }
409 
WriteMacOSXContentBuild(std::string input,std::string output,const std::string & config)410 void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(std::string input,
411                                                      std::string output,
412                                                      const std::string& config)
413 {
414   this->AddMacOSXContentRule();
415   {
416     cmNinjaBuild build("COPY_OSX_CONTENT");
417     build.Outputs.push_back(std::move(output));
418     build.ExplicitDeps.push_back(std::move(input));
419     this->WriteBuild(*this->GetImplFileStream(config), build);
420   }
421 }
422 
WriteRule(std::ostream & os,cmNinjaRule const & rule)423 void cmGlobalNinjaGenerator::WriteRule(std::ostream& os,
424                                        cmNinjaRule const& rule)
425 {
426   // -- Parameter checks
427   // Make sure the rule has a name.
428   if (rule.Name.empty()) {
429     cmSystemTools::Error(cmStrCat(
430       "No name given for WriteRule! called with comment: ", rule.Comment));
431     return;
432   }
433 
434   // Make sure a command is given.
435   if (rule.Command.empty()) {
436     cmSystemTools::Error(cmStrCat(
437       "No command given for WriteRule! called with comment: ", rule.Comment));
438     return;
439   }
440 
441   // Make sure response file content is given
442   if (!rule.RspFile.empty() && rule.RspContent.empty()) {
443     cmSystemTools::Error(
444       cmStrCat("rspfile but no rspfile_content given for WriteRule! "
445                "called with comment: ",
446                rule.Comment));
447     return;
448   }
449 
450   // -- Write rule
451   // Write rule intro
452   cmGlobalNinjaGenerator::WriteComment(os, rule.Comment);
453   os << "rule " << rule.Name << '\n';
454 
455   // Write rule key/value pairs
456   auto writeKV = [&os](const char* key, std::string const& value) {
457     if (!value.empty()) {
458       cmGlobalNinjaGenerator::Indent(os, 1);
459       os << key << " = " << value << '\n';
460     }
461   };
462 
463   writeKV("depfile", rule.DepFile);
464   writeKV("deps", rule.DepType);
465   writeKV("command", rule.Command);
466   writeKV("description", rule.Description);
467   if (!rule.RspFile.empty()) {
468     writeKV("rspfile", rule.RspFile);
469     writeKV("rspfile_content", rule.RspContent);
470   }
471   writeKV("restat", rule.Restat);
472   if (rule.Generator) {
473     writeKV("generator", "1");
474   }
475 
476   // Finish rule
477   os << '\n';
478 }
479 
WriteVariable(std::ostream & os,const std::string & name,const std::string & value,const std::string & comment,int indent)480 void cmGlobalNinjaGenerator::WriteVariable(std::ostream& os,
481                                            const std::string& name,
482                                            const std::string& value,
483                                            const std::string& comment,
484                                            int indent)
485 {
486   // Make sure we have a name.
487   if (name.empty()) {
488     cmSystemTools::Error(cmStrCat("No name given for WriteVariable! called "
489                                   "with comment: ",
490                                   comment));
491     return;
492   }
493 
494   // Do not add a variable if the value is empty.
495   std::string val = cmTrimWhitespace(value);
496   if (val.empty()) {
497     return;
498   }
499 
500   cmGlobalNinjaGenerator::WriteComment(os, comment);
501   cmGlobalNinjaGenerator::Indent(os, indent);
502   os << name << " = " << val << "\n";
503 }
504 
WriteInclude(std::ostream & os,const std::string & filename,const std::string & comment)505 void cmGlobalNinjaGenerator::WriteInclude(std::ostream& os,
506                                           const std::string& filename,
507                                           const std::string& comment)
508 {
509   cmGlobalNinjaGenerator::WriteComment(os, comment);
510   os << "include " << filename << "\n";
511 }
512 
WriteDefault(std::ostream & os,const cmNinjaDeps & targets,const std::string & comment)513 void cmGlobalNinjaGenerator::WriteDefault(std::ostream& os,
514                                           const cmNinjaDeps& targets,
515                                           const std::string& comment)
516 {
517   cmGlobalNinjaGenerator::WriteComment(os, comment);
518   os << "default";
519   for (std::string const& target : targets) {
520     os << " " << target;
521   }
522   os << "\n";
523 }
524 
cmGlobalNinjaGenerator(cmake * cm)525 cmGlobalNinjaGenerator::cmGlobalNinjaGenerator(cmake* cm)
526   : cmGlobalCommonGenerator(cm)
527 {
528 #ifdef _WIN32
529   cm->GetState()->SetWindowsShell(true);
530 #endif
531   this->FindMakeProgramFile = "CMakeNinjaFindMake.cmake";
532 }
533 
534 // Virtual public methods.
535 
CreateLocalGenerator(cmMakefile * mf)536 std::unique_ptr<cmLocalGenerator> cmGlobalNinjaGenerator::CreateLocalGenerator(
537   cmMakefile* mf)
538 {
539   return std::unique_ptr<cmLocalGenerator>(
540     cm::make_unique<cmLocalNinjaGenerator>(this, mf));
541 }
542 
GetMakefileEncoding() const543 codecvt::Encoding cmGlobalNinjaGenerator::GetMakefileEncoding() const
544 {
545   return this->NinjaExpectedEncoding;
546 }
547 
GetDocumentation(cmDocumentationEntry & entry)548 void cmGlobalNinjaGenerator::GetDocumentation(cmDocumentationEntry& entry)
549 {
550   entry.Name = cmGlobalNinjaGenerator::GetActualName();
551   entry.Brief = "Generates build.ninja files.";
552 }
553 
554 // Implemented in all cmGlobaleGenerator sub-classes.
555 // Used in:
556 //   Source/cmLocalGenerator.cxx
557 //   Source/cmake.cxx
Generate()558 void cmGlobalNinjaGenerator::Generate()
559 {
560   // Check minimum Ninja version.
561   if (cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
562                                     RequiredNinjaVersion())) {
563     std::ostringstream msg;
564     msg << "The detected version of Ninja (" << this->NinjaVersion;
565     msg << ") is less than the version of Ninja required by CMake (";
566     msg << cmGlobalNinjaGenerator::RequiredNinjaVersion() << ").";
567     this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
568                                            msg.str());
569     return;
570   }
571   if (!this->OpenBuildFileStreams()) {
572     return;
573   }
574   if (!this->OpenRulesFileStream()) {
575     return;
576   }
577 
578   for (auto& it : this->Configs) {
579     it.second.TargetDependsClosures.clear();
580   }
581 
582   this->InitOutputPathPrefix();
583   this->TargetAll = this->NinjaOutputPath("all");
584   this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt");
585   this->DisableCleandead = false;
586   this->DiagnosedCxxModuleSupport = false;
587 
588   this->PolicyCMP0058 =
589     this->LocalGenerators[0]->GetMakefile()->GetPolicyStatus(
590       cmPolicies::CMP0058);
591   this->ComputingUnknownDependencies =
592     (this->PolicyCMP0058 == cmPolicies::OLD ||
593      this->PolicyCMP0058 == cmPolicies::WARN);
594 
595   this->cmGlobalGenerator::Generate();
596 
597   this->WriteAssumedSourceDependencies();
598   this->WriteTargetAliases(*this->GetCommonFileStream());
599   this->WriteFolderTargets(*this->GetCommonFileStream());
600   this->WriteUnknownExplicitDependencies(*this->GetCommonFileStream());
601   this->WriteBuiltinTargets(*this->GetCommonFileStream());
602 
603   if (cmSystemTools::GetErrorOccuredFlag()) {
604     this->RulesFileStream->setstate(std::ios::failbit);
605     for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs(
606            cmMakefile::IncludeEmptyConfig)) {
607       this->GetImplFileStream(config)->setstate(std::ios::failbit);
608       this->GetConfigFileStream(config)->setstate(std::ios::failbit);
609     }
610     this->GetCommonFileStream()->setstate(std::ios::failbit);
611   }
612 
613   this->CloseCompileCommandsStream();
614   this->CloseRulesFileStream();
615   this->CloseBuildFileStreams();
616 
617 #ifdef _WIN32
618   // Older ninja tools will not be able to update metadata on Windows
619   // when we are re-generating inside an existing 'ninja' invocation
620   // because the outer tool has the files open for write.
621   if (this->NinjaSupportsMetadataOnRegeneration ||
622       !this->GetCMakeInstance()->GetRegenerateDuringBuild())
623 #endif
624   {
625     this->CleanMetaData();
626   }
627 }
628 
CleanMetaData()629 void cmGlobalNinjaGenerator::CleanMetaData()
630 {
631   auto run_ninja_tool = [this](std::vector<char const*> const& args) {
632     std::vector<std::string> command;
633     command.push_back(this->NinjaCommand);
634     command.emplace_back("-C");
635     command.emplace_back(this->GetCMakeInstance()->GetHomeOutputDirectory());
636     command.emplace_back("-t");
637     for (auto const& arg : args) {
638       command.emplace_back(arg);
639     }
640     std::string error;
641     if (!cmSystemTools::RunSingleCommand(command, nullptr, &error, nullptr,
642                                          nullptr,
643                                          cmSystemTools::OUTPUT_NONE)) {
644       this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
645                                              cmStrCat("Running\n '",
646                                                       cmJoin(command, "' '"),
647                                                       "'\n"
648                                                       "failed with:\n ",
649                                                       error));
650       cmSystemTools::SetFatalErrorOccured();
651     }
652   };
653 
654   // Can the tools below expect 'build.ninja' to be loadable?
655   bool const expectBuildManifest =
656     !this->IsMultiConfig() && this->OutputPathPrefix.empty();
657 
658   // Skip some ninja tools if they need 'build.ninja' but it is missing.
659   bool const missingBuildManifest = expectBuildManifest &&
660     this->NinjaSupportsUnconditionalRecompactTool &&
661     !cmSystemTools::FileExists("build.ninja");
662 
663   // The `recompact` tool loads the manifest. As above, we don't have a single
664   // `build.ninja` to load for this in Ninja-Multi. This may be relaxed in the
665   // future pending further investigation into how Ninja works upstream
666   // (ninja#1721).
667   if (this->NinjaSupportsUnconditionalRecompactTool &&
668       !this->GetCMakeInstance()->GetRegenerateDuringBuild() &&
669       expectBuildManifest && !missingBuildManifest) {
670     run_ninja_tool({ "recompact" });
671   }
672   if (this->NinjaSupportsRestatTool && this->OutputPathPrefix.empty()) {
673     // XXX(ninja): We only list `build.ninja` entry files here because CMake
674     // *always* rewrites these files on a reconfigure. If CMake ever gets
675     // smarter about this, all CMake-time created/edited files listed as
676     // outputs for the reconfigure build statement will need to be listed here.
677     cmNinjaDeps outputs;
678     this->AddRebuildManifestOutputs(outputs);
679     std::vector<const char*> args;
680     args.reserve(outputs.size() + 1);
681     args.push_back("restat");
682     for (auto const& output : outputs) {
683       args.push_back(output.c_str());
684     }
685     run_ninja_tool(args);
686   }
687 }
688 
FindMakeProgram(cmMakefile * mf)689 bool cmGlobalNinjaGenerator::FindMakeProgram(cmMakefile* mf)
690 {
691   if (!this->cmGlobalGenerator::FindMakeProgram(mf)) {
692     return false;
693   }
694   if (cmValue ninjaCommand = mf->GetDefinition("CMAKE_MAKE_PROGRAM")) {
695     this->NinjaCommand = *ninjaCommand;
696     std::vector<std::string> command;
697     command.push_back(this->NinjaCommand);
698     command.emplace_back("--version");
699     std::string version;
700     std::string error;
701     if (!cmSystemTools::RunSingleCommand(command, &version, &error, nullptr,
702                                          nullptr,
703                                          cmSystemTools::OUTPUT_NONE)) {
704       mf->IssueMessage(MessageType::FATAL_ERROR,
705                        cmStrCat("Running\n '", cmJoin(command, "' '"),
706                                 "'\n"
707                                 "failed with:\n ",
708                                 error));
709       cmSystemTools::SetFatalErrorOccured();
710       return false;
711     }
712     this->NinjaVersion = cmTrimWhitespace(version);
713     this->CheckNinjaFeatures();
714   }
715   return true;
716 }
717 
CheckNinjaFeatures()718 void cmGlobalNinjaGenerator::CheckNinjaFeatures()
719 {
720   this->NinjaSupportsConsolePool =
721     !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
722                                    RequiredNinjaVersionForConsolePool());
723   this->NinjaSupportsImplicitOuts = !cmSystemTools::VersionCompare(
724     cmSystemTools::OP_LESS, this->NinjaVersion,
725     cmGlobalNinjaGenerator::RequiredNinjaVersionForImplicitOuts());
726   this->NinjaSupportsManifestRestat =
727     !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
728                                    RequiredNinjaVersionForManifestRestat());
729   this->NinjaSupportsMultilineDepfile =
730     !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
731                                    RequiredNinjaVersionForMultilineDepfile());
732   this->NinjaSupportsDyndeps =
733     !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
734                                    RequiredNinjaVersionForDyndeps());
735   if (!this->NinjaSupportsDyndeps) {
736     // The ninja version number is not new enough to have upstream support.
737     // Our ninja branch adds ".dyndep-#" to its version number,
738     // where '#' is a feature-specific version number.  Extract it.
739     static std::string const k_DYNDEP_ = ".dyndep-";
740     std::string::size_type pos = this->NinjaVersion.find(k_DYNDEP_);
741     if (pos != std::string::npos) {
742       const char* fv = &this->NinjaVersion[pos + k_DYNDEP_.size()];
743       unsigned long dyndep = 0;
744       cmStrToULong(fv, &dyndep);
745       if (dyndep == 1) {
746         this->NinjaSupportsDyndeps = true;
747       }
748     }
749   }
750   this->NinjaSupportsUnconditionalRecompactTool =
751     !cmSystemTools::VersionCompare(
752       cmSystemTools::OP_LESS, this->NinjaVersion,
753       RequiredNinjaVersionForUnconditionalRecompactTool());
754   this->NinjaSupportsRestatTool =
755     !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
756                                    RequiredNinjaVersionForRestatTool());
757   this->NinjaSupportsMultipleOutputs =
758     !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
759                                    RequiredNinjaVersionForMultipleOutputs());
760   this->NinjaSupportsMetadataOnRegeneration = !cmSystemTools::VersionCompare(
761     cmSystemTools::OP_LESS, this->NinjaVersion,
762     RequiredNinjaVersionForMetadataOnRegeneration());
763 #ifdef _WIN32
764   this->NinjaSupportsCodePage =
765     !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
766                                    RequiredNinjaVersionForCodePage());
767   if (this->NinjaSupportsCodePage) {
768     this->CheckNinjaCodePage();
769   } else {
770     this->NinjaExpectedEncoding = codecvt::ANSI;
771   }
772 #endif
773 }
774 
CheckNinjaCodePage()775 void cmGlobalNinjaGenerator::CheckNinjaCodePage()
776 {
777   std::vector<std::string> command{ this->NinjaCommand, "-t", "wincodepage" };
778   std::string output;
779   std::string error;
780   int result;
781   if (!cmSystemTools::RunSingleCommand(command, &output, &error, &result,
782                                        nullptr, cmSystemTools::OUTPUT_NONE)) {
783     this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
784                                            cmStrCat("Running\n '",
785                                                     cmJoin(command, "' '"),
786                                                     "'\n"
787                                                     "failed with:\n ",
788                                                     error));
789     cmSystemTools::SetFatalErrorOccured();
790   } else if (result == 0) {
791     std::istringstream outputStream(output);
792     std::string line;
793     bool found = false;
794     while (cmSystemTools::GetLineFromStream(outputStream, line)) {
795       if (cmHasLiteralPrefix(line, "Build file encoding: ")) {
796         cm::string_view lineView(line);
797         cm::string_view encoding =
798           lineView.substr(cmStrLen("Build file encoding: "));
799         if (encoding == "UTF-8") {
800           // Ninja expects UTF-8. We use that internally. No conversion needed.
801           this->NinjaExpectedEncoding = codecvt::None;
802         } else {
803           this->NinjaExpectedEncoding = codecvt::ANSI;
804         }
805         found = true;
806         break;
807       }
808     }
809     if (!found) {
810       this->GetCMakeInstance()->IssueMessage(
811         MessageType::WARNING,
812         "Could not determine Ninja's code page, defaulting to UTF-8");
813       this->NinjaExpectedEncoding = codecvt::None;
814     }
815   } else {
816     this->NinjaExpectedEncoding = codecvt::ANSI;
817   }
818 }
819 
CheckLanguages(std::vector<std::string> const & languages,cmMakefile * mf) const820 bool cmGlobalNinjaGenerator::CheckLanguages(
821   std::vector<std::string> const& languages, cmMakefile* mf) const
822 {
823   if (cm::contains(languages, "Fortran")) {
824     return this->CheckFortran(mf);
825   }
826   if (cm::contains(languages, "ISPC")) {
827     return this->CheckISPC(mf);
828   }
829   if (cm::contains(languages, "Swift")) {
830     const std::string architectures =
831       mf->GetSafeDefinition("CMAKE_OSX_ARCHITECTURES");
832     if (architectures.find_first_of(';') != std::string::npos) {
833       mf->IssueMessage(MessageType::FATAL_ERROR,
834                        "multiple values for CMAKE_OSX_ARCHITECTURES not "
835                        "supported with Swift");
836       cmSystemTools::SetFatalErrorOccured();
837       return false;
838     }
839   }
840   return true;
841 }
842 
CheckCxxModuleSupport()843 bool cmGlobalNinjaGenerator::CheckCxxModuleSupport()
844 {
845   bool const diagnose = !this->DiagnosedCxxModuleSupport &&
846     !this->CMakeInstance->GetIsInTryCompile();
847   if (diagnose) {
848     this->DiagnosedCxxModuleSupport = true;
849     this->GetCMakeInstance()->IssueMessage(
850       MessageType::AUTHOR_WARNING,
851       "C++20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP "
852       "is experimental.  It is meant only for compiler developers to try.");
853   }
854   if (this->NinjaSupportsDyndeps) {
855     return true;
856   }
857   if (diagnose) {
858     std::ostringstream e;
859     /* clang-format off */
860     e <<
861       "The Ninja generator does not support C++20 modules "
862       "using Ninja version \n"
863       "  " << this->NinjaVersion << "\n"
864       "due to lack of required features.  "
865       "Ninja " << RequiredNinjaVersionForDyndeps() << " or higher is required."
866       ;
867     /* clang-format on */
868     this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str());
869     cmSystemTools::SetFatalErrorOccured();
870   }
871   return false;
872 }
873 
CheckFortran(cmMakefile * mf) const874 bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const
875 {
876   if (this->NinjaSupportsDyndeps) {
877     return true;
878   }
879 
880   std::ostringstream e;
881   /* clang-format off */
882   e <<
883     "The Ninja generator does not support Fortran using Ninja version\n"
884     "  " << this->NinjaVersion << "\n"
885     "due to lack of required features.  "
886     "Ninja " << RequiredNinjaVersionForDyndeps() << " or higher is required."
887     ;
888   /* clang-format on */
889   mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
890   cmSystemTools::SetFatalErrorOccured();
891   return false;
892 }
893 
CheckISPC(cmMakefile * mf) const894 bool cmGlobalNinjaGenerator::CheckISPC(cmMakefile* mf) const
895 {
896   if (this->NinjaSupportsMultipleOutputs) {
897     return true;
898   }
899 
900   std::ostringstream e;
901   /* clang-format off */
902   e <<
903     "The Ninja generator does not support ISPC using Ninja version\n"
904     "  " << this->NinjaVersion << "\n"
905     "due to lack of required features.  "
906     "Ninja " << RequiredNinjaVersionForMultipleOutputs() <<
907     " or higher is required."
908     ;
909   /* clang-format on */
910   mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
911   cmSystemTools::SetFatalErrorOccured();
912   return false;
913 }
914 
EnableLanguage(std::vector<std::string> const & langs,cmMakefile * mf,bool optional)915 void cmGlobalNinjaGenerator::EnableLanguage(
916   std::vector<std::string> const& langs, cmMakefile* mf, bool optional)
917 {
918   if (this->IsMultiConfig()) {
919     mf->InitCMAKE_CONFIGURATION_TYPES("Debug;Release;RelWithDebInfo");
920   }
921 
922   this->cmGlobalGenerator::EnableLanguage(langs, mf, optional);
923   for (std::string const& l : langs) {
924     if (l == "NONE") {
925       continue;
926     }
927     this->ResolveLanguageCompiler(l, mf, optional);
928 #ifdef _WIN32
929     std::string const& compilerId =
930       mf->GetSafeDefinition(cmStrCat("CMAKE_", l, "_COMPILER_ID"));
931     std::string const& simulateId =
932       mf->GetSafeDefinition(cmStrCat("CMAKE_", l, "_SIMULATE_ID"));
933     std::string const& compilerFrontendVariant = mf->GetSafeDefinition(
934       cmStrCat("CMAKE_", l, "_COMPILER_FRONTEND_VARIANT"));
935     if ((compilerId == "Clang" && compilerFrontendVariant == "GNU") ||
936         (simulateId != "MSVC" &&
937          (compilerId == "GNU" || compilerId == "QCC" ||
938           cmHasLiteralSuffix(compilerId, "Clang")))) {
939       this->UsingGCCOnWindows = true;
940     }
941 #endif
942   }
943 }
944 
945 // Implemented by:
946 //   cmGlobalUnixMakefileGenerator3
947 //   cmGlobalGhsMultiGenerator
948 //   cmGlobalVisualStudio10Generator
949 //   cmGlobalVisualStudio7Generator
950 //   cmGlobalXCodeGenerator
951 // Called by:
952 //   cmGlobalGenerator::Build()
953 std::vector<cmGlobalGenerator::GeneratedMakeCommand>
GenerateBuildCommand(const std::string & makeProgram,const std::string &,const std::string &,std::vector<std::string> const & targetNames,const std::string & config,bool,int jobs,bool verbose,std::vector<std::string> const & makeOptions)954 cmGlobalNinjaGenerator::GenerateBuildCommand(
955   const std::string& makeProgram, const std::string& /*projectName*/,
956   const std::string& /*projectDir*/,
957   std::vector<std::string> const& targetNames, const std::string& config,
958   bool /*fast*/, int jobs, bool verbose,
959   std::vector<std::string> const& makeOptions)
960 {
961   GeneratedMakeCommand makeCommand;
962   makeCommand.Add(this->SelectMakeProgram(makeProgram));
963 
964   if (verbose) {
965     makeCommand.Add("-v");
966   }
967 
968   if ((jobs != cmake::NO_BUILD_PARALLEL_LEVEL) &&
969       (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL)) {
970     makeCommand.Add("-j", std::to_string(jobs));
971   }
972 
973   this->AppendNinjaFileArgument(makeCommand, config);
974 
975   makeCommand.Add(makeOptions.begin(), makeOptions.end());
976   for (const auto& tname : targetNames) {
977     if (!tname.empty()) {
978       makeCommand.Add(tname);
979     }
980   }
981   return { std::move(makeCommand) };
982 }
983 
984 // Non-virtual public methods.
985 
AddRule(cmNinjaRule const & rule)986 void cmGlobalNinjaGenerator::AddRule(cmNinjaRule const& rule)
987 {
988   // Do not add the same rule twice.
989   if (!this->Rules.insert(rule.Name).second) {
990     return;
991   }
992   // Store command length
993   this->RuleCmdLength[rule.Name] = static_cast<int>(rule.Command.size());
994   // Write rule
995   cmGlobalNinjaGenerator::WriteRule(*this->RulesFileStream, rule);
996 }
997 
HasRule(const std::string & name)998 bool cmGlobalNinjaGenerator::HasRule(const std::string& name)
999 {
1000   return (this->Rules.find(name) != this->Rules.end());
1001 }
1002 
1003 // Private virtual overrides
1004 
ComputeTargetObjectDirectory(cmGeneratorTarget * gt) const1005 void cmGlobalNinjaGenerator::ComputeTargetObjectDirectory(
1006   cmGeneratorTarget* gt) const
1007 {
1008   // Compute full path to object file directory for this target.
1009   std::string dir = cmStrCat(gt->LocalGenerator->GetCurrentBinaryDirectory(),
1010                              '/', gt->LocalGenerator->GetTargetDirectory(gt),
1011                              '/', this->GetCMakeCFGIntDir(), '/');
1012   gt->ObjectDirectory = dir;
1013 }
1014 
1015 // Private methods
1016 
OpenBuildFileStreams()1017 bool cmGlobalNinjaGenerator::OpenBuildFileStreams()
1018 {
1019   if (!this->OpenFileStream(this->BuildFileStream,
1020                             cmGlobalNinjaGenerator::NINJA_BUILD_FILE)) {
1021     return false;
1022   }
1023 
1024   // Write a comment about this file.
1025   *this->BuildFileStream
1026     << "# This file contains all the build statements describing the\n"
1027     << "# compilation DAG.\n\n";
1028 
1029   return true;
1030 }
1031 
OpenFileStream(std::unique_ptr<cmGeneratedFileStream> & stream,const std::string & name)1032 bool cmGlobalNinjaGenerator::OpenFileStream(
1033   std::unique_ptr<cmGeneratedFileStream>& stream, const std::string& name)
1034 {
1035   // Get a stream where to generate things.
1036   if (!stream) {
1037     // Compute Ninja's build file path.
1038     std::string path =
1039       cmStrCat(this->GetCMakeInstance()->GetHomeOutputDirectory(), '/', name);
1040     stream = cm::make_unique<cmGeneratedFileStream>(
1041       path, false, this->GetMakefileEncoding());
1042     if (!(*stream)) {
1043       // An error message is generated by the constructor if it cannot
1044       // open the file.
1045       return false;
1046     }
1047 
1048     // Write the do not edit header.
1049     this->WriteDisclaimer(*stream);
1050   }
1051 
1052   return true;
1053 }
1054 
ListSubsetWithAll(const std::set<std::string> & all,const std::set<std::string> & defaults,const std::vector<std::string> & items)1055 cm::optional<std::set<std::string>> cmGlobalNinjaGenerator::ListSubsetWithAll(
1056   const std::set<std::string>& all, const std::set<std::string>& defaults,
1057   const std::vector<std::string>& items)
1058 {
1059   std::set<std::string> result;
1060 
1061   for (auto const& item : items) {
1062     if (item == "all") {
1063       if (items.size() == 1) {
1064         result = defaults;
1065       } else {
1066         return cm::nullopt;
1067       }
1068     } else if (all.count(item)) {
1069       result.insert(item);
1070     } else {
1071       return cm::nullopt;
1072     }
1073   }
1074 
1075   return cm::make_optional(result);
1076 }
1077 
CloseBuildFileStreams()1078 void cmGlobalNinjaGenerator::CloseBuildFileStreams()
1079 {
1080   if (this->BuildFileStream) {
1081     this->BuildFileStream.reset();
1082   } else {
1083     cmSystemTools::Error("Build file stream was not open.");
1084   }
1085 }
1086 
OpenRulesFileStream()1087 bool cmGlobalNinjaGenerator::OpenRulesFileStream()
1088 {
1089   if (!this->OpenFileStream(this->RulesFileStream,
1090                             cmGlobalNinjaGenerator::NINJA_RULES_FILE)) {
1091     return false;
1092   }
1093 
1094   // Write comment about this file.
1095   /* clang-format off */
1096   *this->RulesFileStream
1097     << "# This file contains all the rules used to get the outputs files\n"
1098     << "# built from the input files.\n"
1099     << "# It is included in the main '" << NINJA_BUILD_FILE << "'.\n\n"
1100     ;
1101   /* clang-format on */
1102   return true;
1103 }
1104 
CloseRulesFileStream()1105 void cmGlobalNinjaGenerator::CloseRulesFileStream()
1106 {
1107   if (this->RulesFileStream) {
1108     this->RulesFileStream.reset();
1109   } else {
1110     cmSystemTools::Error("Rules file stream was not open.");
1111   }
1112 }
1113 
EnsureTrailingSlash(std::string & path)1114 static void EnsureTrailingSlash(std::string& path)
1115 {
1116   if (path.empty()) {
1117     return;
1118   }
1119   std::string::value_type last = path.back();
1120 #ifdef _WIN32
1121   if (last != '\\') {
1122     path += '\\';
1123   }
1124 #else
1125   if (last != '/') {
1126     path += '/';
1127   }
1128 #endif
1129 }
1130 
ConvertToNinjaPath(const std::string & path) const1131 std::string const& cmGlobalNinjaGenerator::ConvertToNinjaPath(
1132   const std::string& path) const
1133 {
1134   auto const f = this->ConvertToNinjaPathCache.find(path);
1135   if (f != this->ConvertToNinjaPathCache.end()) {
1136     return f->second;
1137   }
1138 
1139   std::string convPath =
1140     this->LocalGenerators[0]->MaybeRelativeToTopBinDir(path);
1141   convPath = this->NinjaOutputPath(convPath);
1142 #ifdef _WIN32
1143   std::replace(convPath.begin(), convPath.end(), '/', '\\');
1144 #endif
1145   return this->ConvertToNinjaPathCache.emplace(path, std::move(convPath))
1146     .first->second;
1147 }
1148 
ConvertToNinjaAbsPath(std::string path) const1149 std::string cmGlobalNinjaGenerator::ConvertToNinjaAbsPath(
1150   std::string path) const
1151 {
1152 #ifdef _WIN32
1153   std::replace(path.begin(), path.end(), '/', '\\');
1154 #endif
1155   return path;
1156 }
1157 
AddAdditionalCleanFile(std::string fileName,const std::string & config)1158 void cmGlobalNinjaGenerator::AddAdditionalCleanFile(std::string fileName,
1159                                                     const std::string& config)
1160 {
1161   this->Configs[config].AdditionalCleanFiles.emplace(std::move(fileName));
1162 }
1163 
AddCXXCompileCommand(const std::string & commandLine,const std::string & sourceFile)1164 void cmGlobalNinjaGenerator::AddCXXCompileCommand(
1165   const std::string& commandLine, const std::string& sourceFile)
1166 {
1167   // Compute Ninja's build file path.
1168   std::string buildFileDir =
1169     this->GetCMakeInstance()->GetHomeOutputDirectory();
1170   if (!this->CompileCommandsStream) {
1171     std::string buildFilePath =
1172       cmStrCat(buildFileDir, "/compile_commands.json");
1173     if (this->ComputingUnknownDependencies) {
1174       this->CombinedBuildOutputs.insert(
1175         this->NinjaOutputPath("compile_commands.json"));
1176     }
1177 
1178     // Get a stream where to generate things.
1179     this->CompileCommandsStream =
1180       cm::make_unique<cmGeneratedFileStream>(buildFilePath);
1181     *this->CompileCommandsStream << "[\n";
1182   } else {
1183     *this->CompileCommandsStream << ",\n";
1184   }
1185 
1186   std::string sourceFileName = sourceFile;
1187   if (!cmSystemTools::FileIsFullPath(sourceFileName)) {
1188     sourceFileName = cmSystemTools::CollapseFullPath(
1189       sourceFileName, this->GetCMakeInstance()->GetHomeOutputDirectory());
1190   }
1191 
1192   /* clang-format off */
1193   *this->CompileCommandsStream << "{\n"
1194      << R"(  "directory": ")"
1195      << cmGlobalGenerator::EscapeJSON(buildFileDir) << "\",\n"
1196      << R"(  "command": ")"
1197      << cmGlobalGenerator::EscapeJSON(commandLine) << "\",\n"
1198      << R"(  "file": ")"
1199      << cmGlobalGenerator::EscapeJSON(sourceFileName) << "\"\n"
1200      << "}";
1201   /* clang-format on */
1202 }
1203 
CloseCompileCommandsStream()1204 void cmGlobalNinjaGenerator::CloseCompileCommandsStream()
1205 {
1206   if (this->CompileCommandsStream) {
1207     *this->CompileCommandsStream << "\n]";
1208     this->CompileCommandsStream.reset();
1209   }
1210 }
1211 
WriteDisclaimer(std::ostream & os) const1212 void cmGlobalNinjaGenerator::WriteDisclaimer(std::ostream& os) const
1213 {
1214   os << "# CMAKE generated file: DO NOT EDIT!\n"
1215      << "# Generated by \"" << this->GetName() << "\""
1216      << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "."
1217      << cmVersion::GetMinorVersion() << "\n\n";
1218 }
1219 
WriteAssumedSourceDependencies()1220 void cmGlobalNinjaGenerator::WriteAssumedSourceDependencies()
1221 {
1222   for (auto const& asd : this->AssumedSourceDependencies) {
1223     CCOutputs outputs(this);
1224     outputs.ExplicitOuts.emplace_back(asd.first);
1225     cmNinjaDeps orderOnlyDeps;
1226     std::copy(asd.second.begin(), asd.second.end(),
1227               std::back_inserter(orderOnlyDeps));
1228     this->WriteCustomCommandBuild(
1229       /*command=*/"", /*description=*/"",
1230       "Assume dependencies for generated source file.",
1231       /*depfile*/ "", /*job_pool*/ "",
1232       /*uses_terminal*/ false,
1233       /*restat*/ true, std::string(), outputs, cmNinjaDeps(),
1234       std::move(orderOnlyDeps));
1235   }
1236 }
1237 
OrderDependsTargetForTarget(cmGeneratorTarget const * target,const std::string &) const1238 std::string cmGlobalNinjaGenerator::OrderDependsTargetForTarget(
1239   cmGeneratorTarget const* target, const std::string& /*config*/) const
1240 {
1241   return cmStrCat("cmake_object_order_depends_target_", target->GetName());
1242 }
1243 
AppendTargetOutputs(cmGeneratorTarget const * target,cmNinjaDeps & outputs,const std::string & config,cmNinjaTargetDepends depends) const1244 void cmGlobalNinjaGenerator::AppendTargetOutputs(
1245   cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1246   const std::string& config, cmNinjaTargetDepends depends) const
1247 {
1248   // for frameworks, we want the real name, not smple name
1249   // frameworks always appear versioned, and the build.ninja
1250   // will always attempt to manage symbolic links instead
1251   // of letting cmOSXBundleGenerator do it.
1252   bool realname = target->IsFrameworkOnApple();
1253 
1254   switch (target->GetType()) {
1255     case cmStateEnums::SHARED_LIBRARY:
1256     case cmStateEnums::STATIC_LIBRARY:
1257     case cmStateEnums::MODULE_LIBRARY: {
1258       if (depends == DependOnTargetOrdering) {
1259         outputs.push_back(this->OrderDependsTargetForTarget(target, config));
1260         break;
1261       }
1262     }
1263       CM_FALLTHROUGH;
1264     case cmStateEnums::EXECUTABLE: {
1265       outputs.push_back(this->ConvertToNinjaPath(target->GetFullPath(
1266         config, cmStateEnums::RuntimeBinaryArtifact, realname)));
1267       break;
1268     }
1269     case cmStateEnums::OBJECT_LIBRARY: {
1270       if (depends == DependOnTargetOrdering) {
1271         outputs.push_back(this->OrderDependsTargetForTarget(target, config));
1272         break;
1273       }
1274     }
1275       CM_FALLTHROUGH;
1276     case cmStateEnums::GLOBAL_TARGET:
1277     case cmStateEnums::INTERFACE_LIBRARY:
1278     case cmStateEnums::UTILITY: {
1279       std::string path =
1280         cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
1281                  target->GetName());
1282       std::string output = this->ConvertToNinjaPath(path);
1283       if (target->Target->IsPerConfig()) {
1284         output = this->BuildAlias(output, config);
1285       }
1286       outputs.push_back(output);
1287       break;
1288     }
1289 
1290     case cmStateEnums::UNKNOWN_LIBRARY:
1291       break;
1292   }
1293 }
1294 
AppendTargetDepends(cmGeneratorTarget const * target,cmNinjaDeps & outputs,const std::string & config,const std::string & fileConfig,cmNinjaTargetDepends depends)1295 void cmGlobalNinjaGenerator::AppendTargetDepends(
1296   cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1297   const std::string& config, const std::string& fileConfig,
1298   cmNinjaTargetDepends depends)
1299 {
1300   if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
1301     // These depend only on other CMake-provided targets, e.g. "all".
1302     for (BT<std::pair<std::string, bool>> const& util :
1303          target->GetUtilities()) {
1304       std::string d =
1305         cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
1306                  util.Value.first);
1307       outputs.push_back(this->BuildAlias(this->ConvertToNinjaPath(d), config));
1308     }
1309   } else {
1310     cmNinjaDeps outs;
1311 
1312     auto computeISPCOuputs = [](cmGlobalNinjaGenerator* gg,
1313                                 cmGeneratorTarget const* depTarget,
1314                                 cmNinjaDeps& outputDeps,
1315                                 const std::string& targetConfig) {
1316       if (depTarget->CanCompileSources()) {
1317         auto headers = depTarget->GetGeneratedISPCHeaders(targetConfig);
1318         if (!headers.empty()) {
1319           std::transform(headers.begin(), headers.end(), headers.begin(),
1320                          gg->MapToNinjaPath());
1321           outputDeps.insert(outputDeps.end(), headers.begin(), headers.end());
1322         }
1323         auto objs = depTarget->GetGeneratedISPCObjects(targetConfig);
1324         if (!objs.empty()) {
1325           std::transform(objs.begin(), objs.end(), objs.begin(),
1326                          gg->MapToNinjaPath());
1327           outputDeps.insert(outputDeps.end(), objs.begin(), objs.end());
1328         }
1329       }
1330     };
1331 
1332     for (cmTargetDepend const& targetDep :
1333          this->GetTargetDirectDepends(target)) {
1334       if (!targetDep->IsInBuildSystem()) {
1335         continue;
1336       }
1337       if (targetDep.IsCross()) {
1338         this->AppendTargetOutputs(targetDep, outs, fileConfig, depends);
1339         computeISPCOuputs(this, targetDep, outs, fileConfig);
1340       } else {
1341         this->AppendTargetOutputs(targetDep, outs, config, depends);
1342         computeISPCOuputs(this, targetDep, outs, config);
1343       }
1344     }
1345     std::sort(outs.begin(), outs.end());
1346     cm::append(outputs, outs);
1347   }
1348 }
1349 
AppendTargetDependsClosure(cmGeneratorTarget const * target,cmNinjaDeps & outputs,const std::string & config,const std::string & fileConfig,bool genexOutput)1350 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
1351   cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1352   const std::string& config, const std::string& fileConfig, bool genexOutput)
1353 {
1354   cmNinjaOuts outs;
1355   this->AppendTargetDependsClosure(target, outs, config, fileConfig,
1356                                    genexOutput, true);
1357   cm::append(outputs, outs);
1358 }
1359 
AppendTargetDependsClosure(cmGeneratorTarget const * target,cmNinjaOuts & outputs,const std::string & config,const std::string & fileConfig,bool genexOutput,bool omit_self)1360 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
1361   cmGeneratorTarget const* target, cmNinjaOuts& outputs,
1362   const std::string& config, const std::string& fileConfig, bool genexOutput,
1363   bool omit_self)
1364 {
1365 
1366   // try to locate the target in the cache
1367   ByConfig::TargetDependsClosureKey key{
1368     target,
1369     config,
1370     genexOutput,
1371   };
1372   auto find = this->Configs[fileConfig].TargetDependsClosures.lower_bound(key);
1373 
1374   if (find == this->Configs[fileConfig].TargetDependsClosures.end() ||
1375       find->first != key) {
1376     // We now calculate the closure outputs by inspecting the dependent
1377     // targets recursively.
1378     // For that we have to distinguish between a local result set that is only
1379     // relevant for filling the cache entries properly isolated and a global
1380     // result set that is relevant for the result of the top level call to
1381     // AppendTargetDependsClosure.
1382     cmNinjaOuts this_outs; // this will be the new cache entry
1383 
1384     for (auto const& dep_target : this->GetTargetDirectDepends(target)) {
1385       if (!dep_target->IsInBuildSystem()) {
1386         continue;
1387       }
1388 
1389       if (!this->IsSingleConfigUtility(target) &&
1390           !this->IsSingleConfigUtility(dep_target) &&
1391           this->EnableCrossConfigBuild() && !dep_target.IsCross() &&
1392           !genexOutput) {
1393         continue;
1394       }
1395 
1396       if (dep_target.IsCross()) {
1397         this->AppendTargetDependsClosure(dep_target, this_outs, fileConfig,
1398                                          fileConfig, genexOutput, false);
1399       } else {
1400         this->AppendTargetDependsClosure(dep_target, this_outs, config,
1401                                          fileConfig, genexOutput, false);
1402       }
1403     }
1404     find = this->Configs[fileConfig].TargetDependsClosures.emplace_hint(
1405       find, key, std::move(this_outs));
1406   }
1407 
1408   // now fill the outputs of the final result from the newly generated cache
1409   // entry
1410   outputs.insert(find->second.begin(), find->second.end());
1411 
1412   // finally generate the outputs of the target itself, if applicable
1413   cmNinjaDeps outs;
1414   if (!omit_self) {
1415     this->AppendTargetOutputs(target, outs, config, DependOnTargetArtifact);
1416   }
1417   outputs.insert(outs.begin(), outs.end());
1418 }
1419 
AddTargetAlias(const std::string & alias,cmGeneratorTarget * target,const std::string & config)1420 void cmGlobalNinjaGenerator::AddTargetAlias(const std::string& alias,
1421                                             cmGeneratorTarget* target,
1422                                             const std::string& config)
1423 {
1424   std::string outputPath = this->NinjaOutputPath(alias);
1425   std::string buildAlias = this->BuildAlias(outputPath, config);
1426   cmNinjaDeps outputs;
1427   if (config != "all") {
1428     this->AppendTargetOutputs(target, outputs, config, DependOnTargetArtifact);
1429   }
1430   // Mark the target's outputs as ambiguous to ensure that no other target
1431   // uses the output as an alias.
1432   for (std::string const& output : outputs) {
1433     this->TargetAliases[output].GeneratorTarget = nullptr;
1434     this->DefaultTargetAliases[output].GeneratorTarget = nullptr;
1435     for (const std::string& config2 :
1436          this->Makefiles.front()->GetGeneratorConfigs(
1437            cmMakefile::IncludeEmptyConfig)) {
1438       this->Configs[config2].TargetAliases[output].GeneratorTarget = nullptr;
1439     }
1440   }
1441 
1442   // Insert the alias into the map.  If the alias was already present in the
1443   // map and referred to another target, mark it as ambiguous.
1444   TargetAlias ta;
1445   ta.GeneratorTarget = target;
1446   ta.Config = config;
1447 
1448   auto newAliasGlobal =
1449     this->TargetAliases.insert(std::make_pair(buildAlias, ta));
1450   if (newAliasGlobal.second &&
1451       newAliasGlobal.first->second.GeneratorTarget != target) {
1452     newAliasGlobal.first->second.GeneratorTarget = nullptr;
1453   }
1454 
1455   auto newAliasConfig =
1456     this->Configs[config].TargetAliases.insert(std::make_pair(outputPath, ta));
1457   if (newAliasConfig.second &&
1458       newAliasConfig.first->second.GeneratorTarget != target) {
1459     newAliasConfig.first->second.GeneratorTarget = nullptr;
1460   }
1461   if (this->DefaultConfigs.count(config)) {
1462     auto newAliasDefaultGlobal =
1463       this->DefaultTargetAliases.insert(std::make_pair(outputPath, ta));
1464     if (newAliasDefaultGlobal.second &&
1465         newAliasDefaultGlobal.first->second.GeneratorTarget != target) {
1466       newAliasDefaultGlobal.first->second.GeneratorTarget = nullptr;
1467     }
1468   }
1469 }
1470 
WriteTargetAliases(std::ostream & os)1471 void cmGlobalNinjaGenerator::WriteTargetAliases(std::ostream& os)
1472 {
1473   cmGlobalNinjaGenerator::WriteDivider(os);
1474   os << "# Target aliases.\n\n";
1475 
1476   cmNinjaBuild build("phony");
1477   build.Outputs.emplace_back();
1478   for (auto const& ta : this->TargetAliases) {
1479     // Don't write ambiguous aliases.
1480     if (!ta.second.GeneratorTarget) {
1481       continue;
1482     }
1483 
1484     // Don't write alias if there is a already a custom command with
1485     // matching output
1486     if (this->HasCustomCommandOutput(ta.first)) {
1487       continue;
1488     }
1489 
1490     build.Outputs.front() = ta.first;
1491     build.ExplicitDeps.clear();
1492     if (ta.second.Config == "all") {
1493       for (auto const& config : this->CrossConfigs) {
1494         this->AppendTargetOutputs(ta.second.GeneratorTarget,
1495                                   build.ExplicitDeps, config,
1496                                   DependOnTargetArtifact);
1497       }
1498     } else {
1499       this->AppendTargetOutputs(ta.second.GeneratorTarget, build.ExplicitDeps,
1500                                 ta.second.Config, DependOnTargetArtifact);
1501     }
1502     this->WriteBuild(this->EnableCrossConfigBuild() &&
1503                          (ta.second.Config == "all" ||
1504                           this->CrossConfigs.count(ta.second.Config))
1505                        ? os
1506                        : *this->GetImplFileStream(ta.second.Config),
1507                      build);
1508   }
1509 
1510   if (this->IsMultiConfig()) {
1511     for (auto const& config : this->Makefiles.front()->GetGeneratorConfigs(
1512            cmMakefile::IncludeEmptyConfig)) {
1513       for (auto const& ta : this->Configs[config].TargetAliases) {
1514         // Don't write ambiguous aliases.
1515         if (!ta.second.GeneratorTarget) {
1516           continue;
1517         }
1518 
1519         // Don't write alias if there is a already a custom command with
1520         // matching output
1521         if (this->HasCustomCommandOutput(ta.first)) {
1522           continue;
1523         }
1524 
1525         build.Outputs.front() = ta.first;
1526         build.ExplicitDeps.clear();
1527         this->AppendTargetOutputs(ta.second.GeneratorTarget,
1528                                   build.ExplicitDeps, config,
1529                                   DependOnTargetArtifact);
1530         this->WriteBuild(*this->GetConfigFileStream(config), build);
1531       }
1532     }
1533 
1534     if (!this->DefaultConfigs.empty()) {
1535       for (auto const& ta : this->DefaultTargetAliases) {
1536         // Don't write ambiguous aliases.
1537         if (!ta.second.GeneratorTarget) {
1538           continue;
1539         }
1540 
1541         // Don't write alias if there is a already a custom command with
1542         // matching output
1543         if (this->HasCustomCommandOutput(ta.first)) {
1544           continue;
1545         }
1546 
1547         build.Outputs.front() = ta.first;
1548         build.ExplicitDeps.clear();
1549         for (auto const& config : this->DefaultConfigs) {
1550           this->AppendTargetOutputs(ta.second.GeneratorTarget,
1551                                     build.ExplicitDeps, config,
1552                                     DependOnTargetArtifact);
1553         }
1554         this->WriteBuild(*this->GetDefaultFileStream(), build);
1555       }
1556     }
1557   }
1558 }
1559 
WriteFolderTargets(std::ostream & os)1560 void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os)
1561 {
1562   cmGlobalNinjaGenerator::WriteDivider(os);
1563   os << "# Folder targets.\n\n";
1564 
1565   std::map<std::string, DirectoryTarget> dirTargets =
1566     this->ComputeDirectoryTargets();
1567 
1568   for (auto const& it : dirTargets) {
1569     cmNinjaBuild build("phony");
1570     cmGlobalNinjaGenerator::WriteDivider(os);
1571     std::string const& currentBinaryDir = it.first;
1572     DirectoryTarget const& dt = it.second;
1573     std::vector<std::string> configs =
1574       dt.LG->GetMakefile()->GetGeneratorConfigs(
1575         cmMakefile::IncludeEmptyConfig);
1576 
1577     // Setup target
1578     cmNinjaDeps configDeps;
1579     build.Comment = cmStrCat("Folder: ", currentBinaryDir);
1580     build.Outputs.emplace_back();
1581     std::string const buildDirAllTarget =
1582       this->ConvertToNinjaPath(cmStrCat(currentBinaryDir, "/all"));
1583     for (auto const& config : configs) {
1584       build.ExplicitDeps.clear();
1585       build.Outputs.front() = this->BuildAlias(buildDirAllTarget, config);
1586       configDeps.emplace_back(build.Outputs.front());
1587       for (DirectoryTarget::Target const& t : dt.Targets) {
1588         if (!this->IsExcludedFromAllInConfig(t, config)) {
1589           this->AppendTargetOutputs(t.GT, build.ExplicitDeps, config,
1590                                     DependOnTargetArtifact);
1591         }
1592       }
1593       for (DirectoryTarget::Dir const& d : dt.Children) {
1594         if (!d.ExcludeFromAll) {
1595           build.ExplicitDeps.emplace_back(this->BuildAlias(
1596             this->ConvertToNinjaPath(cmStrCat(d.Path, "/all")), config));
1597         }
1598       }
1599       // Write target
1600       this->WriteBuild(this->EnableCrossConfigBuild() &&
1601                            this->CrossConfigs.count(config)
1602                          ? os
1603                          : *this->GetImplFileStream(config),
1604                        build);
1605     }
1606 
1607     // Add shortcut target
1608     if (this->IsMultiConfig()) {
1609       for (auto const& config : configs) {
1610         build.ExplicitDeps = { this->BuildAlias(buildDirAllTarget, config) };
1611         build.Outputs.front() = buildDirAllTarget;
1612         this->WriteBuild(*this->GetConfigFileStream(config), build);
1613       }
1614 
1615       if (!this->DefaultFileConfig.empty()) {
1616         build.ExplicitDeps.clear();
1617         for (auto const& config : this->DefaultConfigs) {
1618           build.ExplicitDeps.push_back(
1619             this->BuildAlias(buildDirAllTarget, config));
1620         }
1621         build.Outputs.front() = buildDirAllTarget;
1622         this->WriteBuild(*this->GetDefaultFileStream(), build);
1623       }
1624     }
1625 
1626     // Add target for all configs
1627     if (this->EnableCrossConfigBuild()) {
1628       build.ExplicitDeps.clear();
1629       for (auto const& config : this->CrossConfigs) {
1630         build.ExplicitDeps.push_back(
1631           this->BuildAlias(buildDirAllTarget, config));
1632       }
1633       build.Outputs.front() = this->BuildAlias(buildDirAllTarget, "all");
1634       this->WriteBuild(os, build);
1635     }
1636   }
1637 }
1638 
WriteUnknownExplicitDependencies(std::ostream & os)1639 void cmGlobalNinjaGenerator::WriteUnknownExplicitDependencies(std::ostream& os)
1640 {
1641   if (!this->ComputingUnknownDependencies) {
1642     return;
1643   }
1644 
1645   // We need to collect the set of known build outputs.
1646   // Start with those generated by WriteBuild calls.
1647   // No other method needs this so we can take ownership
1648   // of the set locally and throw it out when we are done.
1649   std::set<std::string> knownDependencies;
1650   knownDependencies.swap(this->CombinedBuildOutputs);
1651 
1652   // now write out the unknown explicit dependencies.
1653 
1654   // union the configured files, evaluations files and the
1655   // CombinedBuildOutputs,
1656   // and then difference with CombinedExplicitDependencies to find the explicit
1657   // dependencies that we have no rule for
1658 
1659   cmGlobalNinjaGenerator::WriteDivider(os);
1660   /* clang-format off */
1661   os << "# Unknown Build Time Dependencies.\n"
1662      << "# Tell Ninja that they may appear as side effects of build rules\n"
1663      << "# otherwise ordered by order-only dependencies.\n\n";
1664   /* clang-format on */
1665 
1666   // get the list of files that cmake itself has generated as a
1667   // product of configuration.
1668 
1669   for (const auto& lg : this->LocalGenerators) {
1670     // get the vector of files created by this makefile and convert them
1671     // to ninja paths, which are all relative in respect to the build directory
1672     for (std::string const& file : lg->GetMakefile()->GetOutputFiles()) {
1673       knownDependencies.insert(this->ConvertToNinjaPath(file));
1674     }
1675     if (!this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
1676       // get list files which are implicit dependencies as well and will be
1677       // phony for rebuild manifest
1678       for (std::string const& j : lg->GetMakefile()->GetListFiles()) {
1679         knownDependencies.insert(this->ConvertToNinjaPath(j));
1680       }
1681     }
1682     for (const auto& li : lg->GetMakefile()->GetEvaluationFiles()) {
1683       // get all the files created by generator expressions and convert them
1684       // to ninja paths
1685       for (std::string const& evaluationFile : li->GetFiles()) {
1686         knownDependencies.insert(this->ConvertToNinjaPath(evaluationFile));
1687       }
1688     }
1689   }
1690   knownDependencies.insert(this->CMakeCacheFile);
1691 
1692   for (auto const& ta : this->TargetAliases) {
1693     knownDependencies.insert(this->ConvertToNinjaPath(ta.first));
1694   }
1695 
1696   // remove all source files we know will exist.
1697   for (auto const& i : this->AssumedSourceDependencies) {
1698     knownDependencies.insert(this->ConvertToNinjaPath(i.first));
1699   }
1700 
1701   // now we difference with CombinedCustomCommandExplicitDependencies to find
1702   // the list of items we know nothing about.
1703   // We have encoded all the paths in CombinedCustomCommandExplicitDependencies
1704   // and knownDependencies so no matter if unix or windows paths they
1705   // should all match now.
1706 
1707   std::vector<std::string> unknownExplicitDepends;
1708   this->CombinedCustomCommandExplicitDependencies.erase(this->TargetAll);
1709 
1710   std::set_difference(this->CombinedCustomCommandExplicitDependencies.begin(),
1711                       this->CombinedCustomCommandExplicitDependencies.end(),
1712                       knownDependencies.begin(), knownDependencies.end(),
1713                       std::back_inserter(unknownExplicitDepends));
1714 
1715   std::vector<std::string> warnExplicitDepends;
1716   if (!unknownExplicitDepends.empty()) {
1717     cmake* cmk = this->GetCMakeInstance();
1718     std::string const& buildRoot = cmk->GetHomeOutputDirectory();
1719     bool const inSource = (buildRoot == cmk->GetHomeDirectory());
1720     bool const warn = (!inSource && (this->PolicyCMP0058 == cmPolicies::WARN));
1721     cmNinjaBuild build("phony");
1722     build.Outputs.emplace_back("");
1723     for (std::string const& ued : unknownExplicitDepends) {
1724       // verify the file is in the build directory
1725       std::string const absDepPath =
1726         cmSystemTools::CollapseFullPath(ued, buildRoot);
1727       if (cmSystemTools::IsSubDirectory(absDepPath, buildRoot)) {
1728         // Generate phony build statement
1729         build.Outputs[0] = ued;
1730         this->WriteBuild(os, build);
1731         // Add to warning on demand
1732         if (warn && warnExplicitDepends.size() < 10) {
1733           warnExplicitDepends.push_back(ued);
1734         }
1735       }
1736     }
1737   }
1738 
1739   if (!warnExplicitDepends.empty()) {
1740     std::ostringstream w;
1741     /* clang-format off */
1742     w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0058) << "\n"
1743       "This project specifies custom command DEPENDS on files "
1744       "in the build tree that are not specified as the OUTPUT or "
1745       "BYPRODUCTS of any add_custom_command or add_custom_target:\n"
1746       " " << cmJoin(warnExplicitDepends, "\n ") <<
1747       "\n"
1748       "For compatibility with versions of CMake that did not have "
1749       "the BYPRODUCTS option, CMake is generating phony rules for "
1750       "such files to convince 'ninja' to build."
1751       "\n"
1752       "Project authors should add the missing BYPRODUCTS or OUTPUT "
1753       "options to the custom commands that produce these files."
1754       ;
1755     /* clang-format on */
1756     this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
1757                                            w.str());
1758   }
1759 }
1760 
WriteBuiltinTargets(std::ostream & os)1761 void cmGlobalNinjaGenerator::WriteBuiltinTargets(std::ostream& os)
1762 {
1763   // Write headers.
1764   cmGlobalNinjaGenerator::WriteDivider(os);
1765   os << "# Built-in targets\n\n";
1766 
1767   this->WriteTargetRebuildManifest(os);
1768   this->WriteTargetClean(os);
1769   this->WriteTargetHelp(os);
1770 
1771   for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs(
1772          cmMakefile::IncludeEmptyConfig)) {
1773     this->WriteTargetDefault(*this->GetConfigFileStream(config));
1774   }
1775 
1776   if (!this->DefaultFileConfig.empty()) {
1777     this->WriteTargetDefault(*this->GetDefaultFileStream());
1778   }
1779 }
1780 
WriteTargetDefault(std::ostream & os)1781 void cmGlobalNinjaGenerator::WriteTargetDefault(std::ostream& os)
1782 {
1783   if (!this->HasOutputPathPrefix()) {
1784     cmNinjaDeps all;
1785     all.push_back(this->TargetAll);
1786     cmGlobalNinjaGenerator::WriteDefault(os, all,
1787                                          "Make the all target the default.");
1788   }
1789 }
1790 
WriteTargetRebuildManifest(std::ostream & os)1791 void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
1792 {
1793   if (this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
1794     return;
1795   }
1796   const auto& lg = this->LocalGenerators[0];
1797 
1798   {
1799     cmNinjaRule rule("RERUN_CMAKE");
1800     rule.Command =
1801       cmStrCat(this->CMakeCmd(), " --regenerate-during-build -S",
1802                lg->ConvertToOutputFormat(lg->GetSourceDirectory(),
1803                                          cmOutputConverter::SHELL),
1804                " -B",
1805                lg->ConvertToOutputFormat(lg->GetBinaryDirectory(),
1806                                          cmOutputConverter::SHELL));
1807     rule.Description = "Re-running CMake...";
1808     rule.Comment = "Rule for re-running cmake.";
1809     rule.Generator = true;
1810     WriteRule(*this->RulesFileStream, rule);
1811   }
1812 
1813   cmNinjaBuild reBuild("RERUN_CMAKE");
1814   reBuild.Comment = "Re-run CMake if any of its inputs changed.";
1815   this->AddRebuildManifestOutputs(reBuild.Outputs);
1816 
1817   for (const auto& localGen : this->LocalGenerators) {
1818     for (std::string const& fi : localGen->GetMakefile()->GetListFiles()) {
1819       reBuild.ImplicitDeps.push_back(this->ConvertToNinjaPath(fi));
1820     }
1821   }
1822   reBuild.ImplicitDeps.push_back(this->CMakeCacheFile);
1823 
1824   // Use 'console' pool to get non buffered output of the CMake re-run call
1825   // Available since Ninja 1.5
1826   if (this->SupportsDirectConsole()) {
1827     reBuild.Variables["pool"] = "console";
1828   }
1829 
1830   cmake* cm = this->GetCMakeInstance();
1831   if (this->SupportsManifestRestat() && cm->DoWriteGlobVerifyTarget()) {
1832     {
1833       cmNinjaRule rule("VERIFY_GLOBS");
1834       rule.Command =
1835         cmStrCat(this->CMakeCmd(), " -P ",
1836                  lg->ConvertToOutputFormat(cm->GetGlobVerifyScript(),
1837                                            cmOutputConverter::SHELL));
1838       rule.Description = "Re-checking globbed directories...";
1839       rule.Comment = "Rule for re-checking globbed directories.";
1840       rule.Generator = true;
1841       this->WriteRule(*this->RulesFileStream, rule);
1842     }
1843 
1844     cmNinjaBuild phonyBuild("phony");
1845     phonyBuild.Comment = "Phony target to force glob verification run.";
1846     phonyBuild.Outputs.push_back(
1847       cmStrCat(cm->GetGlobVerifyScript(), "_force"));
1848     this->WriteBuild(os, phonyBuild);
1849 
1850     reBuild.Variables["restat"] = "1";
1851     std::string const verifyScriptFile =
1852       this->NinjaOutputPath(cm->GetGlobVerifyScript());
1853     std::string const verifyStampFile =
1854       this->NinjaOutputPath(cm->GetGlobVerifyStamp());
1855     {
1856       cmNinjaBuild vgBuild("VERIFY_GLOBS");
1857       vgBuild.Comment =
1858         "Re-run CMake to check if globbed directories changed.";
1859       vgBuild.Outputs.push_back(verifyStampFile);
1860       vgBuild.ImplicitDeps = phonyBuild.Outputs;
1861       vgBuild.Variables = reBuild.Variables;
1862       this->WriteBuild(os, vgBuild);
1863     }
1864     reBuild.Variables.erase("restat");
1865     reBuild.ImplicitDeps.push_back(verifyScriptFile);
1866     reBuild.ExplicitDeps.push_back(verifyStampFile);
1867   } else if (!this->SupportsManifestRestat() &&
1868              cm->DoWriteGlobVerifyTarget()) {
1869     std::ostringstream msg;
1870     msg << "The detected version of Ninja:\n"
1871         << "  " << this->NinjaVersion << "\n"
1872         << "is less than the version of Ninja required by CMake for adding "
1873            "restat dependencies to the build.ninja manifest regeneration "
1874            "target:\n"
1875         << "  "
1876         << cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat()
1877         << "\n";
1878     msg << "Any pre-check scripts, such as those generated for file(GLOB "
1879            "CONFIGURE_DEPENDS), will not be run by Ninja.";
1880     this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
1881                                            msg.str());
1882   }
1883 
1884   std::sort(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end());
1885   reBuild.ImplicitDeps.erase(
1886     std::unique(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end()),
1887     reBuild.ImplicitDeps.end());
1888 
1889   this->WriteBuild(os, reBuild);
1890 
1891   {
1892     cmNinjaBuild build("phony");
1893     build.Comment = "A missing CMake input file is not an error.";
1894     std::set_difference(std::make_move_iterator(reBuild.ImplicitDeps.begin()),
1895                         std::make_move_iterator(reBuild.ImplicitDeps.end()),
1896                         this->CustomCommandOutputs.begin(),
1897                         this->CustomCommandOutputs.end(),
1898                         std::back_inserter(build.Outputs));
1899     this->WriteBuild(os, build);
1900   }
1901 }
1902 
CMakeCmd() const1903 std::string cmGlobalNinjaGenerator::CMakeCmd() const
1904 {
1905   const auto& lgen = this->LocalGenerators.at(0);
1906   return lgen->ConvertToOutputFormat(cmSystemTools::GetCMakeCommand(),
1907                                      cmOutputConverter::SHELL);
1908 }
1909 
NinjaCmd() const1910 std::string cmGlobalNinjaGenerator::NinjaCmd() const
1911 {
1912   const auto& lgen = this->LocalGenerators[0];
1913   if (lgen != nullptr) {
1914     return lgen->ConvertToOutputFormat(this->NinjaCommand,
1915                                        cmOutputConverter::SHELL);
1916   }
1917   return "ninja";
1918 }
1919 
SupportsDirectConsole() const1920 bool cmGlobalNinjaGenerator::SupportsDirectConsole() const
1921 {
1922   return this->NinjaSupportsConsolePool;
1923 }
1924 
SupportsImplicitOuts() const1925 bool cmGlobalNinjaGenerator::SupportsImplicitOuts() const
1926 {
1927   return this->NinjaSupportsImplicitOuts;
1928 }
1929 
SupportsManifestRestat() const1930 bool cmGlobalNinjaGenerator::SupportsManifestRestat() const
1931 {
1932   return this->NinjaSupportsManifestRestat;
1933 }
1934 
SupportsMultilineDepfile() const1935 bool cmGlobalNinjaGenerator::SupportsMultilineDepfile() const
1936 {
1937   return this->NinjaSupportsMultilineDepfile;
1938 }
1939 
WriteTargetCleanAdditional(std::ostream & os)1940 bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
1941 {
1942   const auto& lgr = this->LocalGenerators.at(0);
1943   std::string cleanScriptRel = "CMakeFiles/clean_additional.cmake";
1944   std::string cleanScriptAbs =
1945     cmStrCat(lgr->GetBinaryDirectory(), '/', cleanScriptRel);
1946   std::vector<std::string> configs =
1947     this->Makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
1948 
1949   // Check if there are additional files to clean
1950   bool empty = true;
1951   for (auto const& config : configs) {
1952     auto const it = this->Configs.find(config);
1953     if (it != this->Configs.end() &&
1954         !it->second.AdditionalCleanFiles.empty()) {
1955       empty = false;
1956       break;
1957     }
1958   }
1959   if (empty) {
1960     // Remove cmake clean script file if it exists
1961     cmSystemTools::RemoveFile(cleanScriptAbs);
1962     return false;
1963   }
1964 
1965   // Write cmake clean script file
1966   {
1967     cmGeneratedFileStream fout(cleanScriptAbs);
1968     if (!fout) {
1969       return false;
1970     }
1971     fout << "# Additional clean files\ncmake_minimum_required(VERSION 3.16)\n";
1972     for (auto const& config : configs) {
1973       auto const it = this->Configs.find(config);
1974       if (it != this->Configs.end() &&
1975           !it->second.AdditionalCleanFiles.empty()) {
1976         fout << "\nif(\"${CONFIG}\" STREQUAL \"\" OR \"${CONFIG}\" STREQUAL \""
1977              << config << "\")\n";
1978         fout << "  file(REMOVE_RECURSE\n";
1979         for (std::string const& acf : it->second.AdditionalCleanFiles) {
1980           fout << "  "
1981                << cmOutputConverter::EscapeForCMake(
1982                     this->ConvertToNinjaPath(acf))
1983                << '\n';
1984         }
1985         fout << "  )\n";
1986         fout << "endif()\n";
1987       }
1988     }
1989   }
1990   // Register clean script file
1991   lgr->GetMakefile()->AddCMakeOutputFile(cleanScriptAbs);
1992 
1993   // Write rule
1994   {
1995     cmNinjaRule rule("CLEAN_ADDITIONAL");
1996     rule.Command = cmStrCat(
1997       this->CMakeCmd(), " -DCONFIG=$CONFIG -P ",
1998       lgr->ConvertToOutputFormat(this->NinjaOutputPath(cleanScriptRel),
1999                                  cmOutputConverter::SHELL));
2000     rule.Description = "Cleaning additional files...";
2001     rule.Comment = "Rule for cleaning additional files.";
2002     WriteRule(*this->RulesFileStream, rule);
2003   }
2004 
2005   // Write build
2006   {
2007     cmNinjaBuild build("CLEAN_ADDITIONAL");
2008     build.Comment = "Clean additional files.";
2009     build.Outputs.emplace_back();
2010     for (auto const& config : configs) {
2011       build.Outputs.front() = this->BuildAlias(
2012         this->NinjaOutputPath(this->GetAdditionalCleanTargetName()), config);
2013       build.Variables["CONFIG"] = config;
2014       this->WriteBuild(os, build);
2015     }
2016     if (this->IsMultiConfig()) {
2017       build.Outputs.front() =
2018         this->NinjaOutputPath(this->GetAdditionalCleanTargetName());
2019       build.Variables["CONFIG"] = "";
2020       this->WriteBuild(os, build);
2021     }
2022   }
2023   // Return success
2024   return true;
2025 }
2026 
WriteTargetClean(std::ostream & os)2027 void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os)
2028 {
2029   // -- Additional clean target
2030   bool additionalFiles = this->WriteTargetCleanAdditional(os);
2031 
2032   // -- Default clean target
2033   // Write rule
2034   {
2035     cmNinjaRule rule("CLEAN");
2036     rule.Command = cmStrCat(this->NinjaCmd(), " $FILE_ARG -t clean $TARGETS");
2037     rule.Description = "Cleaning all built files...";
2038     rule.Comment = "Rule for cleaning all built files.";
2039     WriteRule(*this->RulesFileStream, rule);
2040   }
2041 
2042   auto const configs = this->Makefiles.front()->GetGeneratorConfigs(
2043     cmMakefile::IncludeEmptyConfig);
2044 
2045   // Write build
2046   {
2047     cmNinjaBuild build("CLEAN");
2048     build.Comment = "Clean all the built files.";
2049     build.Outputs.emplace_back();
2050 
2051     for (auto const& config : configs) {
2052       build.Outputs.front() = this->BuildAlias(
2053         this->NinjaOutputPath(this->GetCleanTargetName()), config);
2054       if (this->IsMultiConfig()) {
2055         build.Variables["TARGETS"] =
2056           cmStrCat(this->BuildAlias(GetByproductsForCleanTargetName(), config),
2057                    " ", GetByproductsForCleanTargetName());
2058       }
2059       build.ExplicitDeps.clear();
2060       if (additionalFiles) {
2061         build.ExplicitDeps.push_back(this->BuildAlias(
2062           this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
2063           config));
2064       }
2065       for (auto const& fileConfig : configs) {
2066         if (fileConfig != config && !this->EnableCrossConfigBuild()) {
2067           continue;
2068         }
2069         if (this->IsMultiConfig()) {
2070           build.Variables["FILE_ARG"] = cmStrCat(
2071             "-f ",
2072             cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig));
2073         }
2074         this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
2075       }
2076     }
2077 
2078     if (this->EnableCrossConfigBuild()) {
2079       build.Outputs.front() = this->BuildAlias(
2080         this->NinjaOutputPath(this->GetCleanTargetName()), "all");
2081       build.ExplicitDeps.clear();
2082 
2083       if (additionalFiles) {
2084         for (auto const& config : this->CrossConfigs) {
2085           build.ExplicitDeps.push_back(this->BuildAlias(
2086             this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
2087             config));
2088         }
2089       }
2090 
2091       std::vector<std::string> byproducts;
2092       for (auto const& config : this->CrossConfigs) {
2093         byproducts.push_back(
2094           this->BuildAlias(GetByproductsForCleanTargetName(), config));
2095       }
2096       byproducts.emplace_back(GetByproductsForCleanTargetName());
2097       build.Variables["TARGETS"] = cmJoin(byproducts, " ");
2098 
2099       for (auto const& fileConfig : configs) {
2100         build.Variables["FILE_ARG"] = cmStrCat(
2101           "-f ",
2102           cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig));
2103         this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
2104       }
2105     }
2106   }
2107 
2108   if (this->IsMultiConfig()) {
2109     cmNinjaBuild build("phony");
2110     build.Outputs.emplace_back(
2111       this->NinjaOutputPath(this->GetCleanTargetName()));
2112     build.ExplicitDeps.emplace_back();
2113 
2114     for (auto const& config : configs) {
2115       build.ExplicitDeps.front() = this->BuildAlias(
2116         this->NinjaOutputPath(this->GetCleanTargetName()), config);
2117       this->WriteBuild(*this->GetConfigFileStream(config), build);
2118     }
2119 
2120     if (!this->DefaultConfigs.empty()) {
2121       build.ExplicitDeps.clear();
2122       for (auto const& config : this->DefaultConfigs) {
2123         build.ExplicitDeps.push_back(this->BuildAlias(
2124           this->NinjaOutputPath(this->GetCleanTargetName()), config));
2125       }
2126       this->WriteBuild(*this->GetDefaultFileStream(), build);
2127     }
2128   }
2129 
2130   // Write byproducts
2131   if (this->IsMultiConfig()) {
2132     cmNinjaBuild build("phony");
2133     build.Comment = "Clean byproducts.";
2134     build.Outputs.emplace_back(
2135       this->ConvertToNinjaPath(GetByproductsForCleanTargetName()));
2136     build.ExplicitDeps = this->ByproductsForCleanTarget;
2137     this->WriteBuild(os, build);
2138 
2139     for (auto const& config : configs) {
2140       build.Outputs.front() = this->BuildAlias(
2141         this->ConvertToNinjaPath(GetByproductsForCleanTargetName()), config);
2142       build.ExplicitDeps = this->Configs[config].ByproductsForCleanTarget;
2143       this->WriteBuild(os, build);
2144     }
2145   }
2146 }
2147 
WriteTargetHelp(std::ostream & os)2148 void cmGlobalNinjaGenerator::WriteTargetHelp(std::ostream& os)
2149 {
2150   {
2151     cmNinjaRule rule("HELP");
2152     rule.Command = cmStrCat(this->NinjaCmd(), " -t targets");
2153     rule.Description = "All primary targets available:";
2154     rule.Comment = "Rule for printing all primary targets available.";
2155     WriteRule(*this->RulesFileStream, rule);
2156   }
2157   {
2158     cmNinjaBuild build("HELP");
2159     build.Comment = "Print all primary targets available.";
2160     build.Outputs.push_back(this->NinjaOutputPath("help"));
2161     this->WriteBuild(os, build);
2162   }
2163 }
2164 
InitOutputPathPrefix()2165 void cmGlobalNinjaGenerator::InitOutputPathPrefix()
2166 {
2167   this->OutputPathPrefix =
2168     this->LocalGenerators[0]->GetMakefile()->GetSafeDefinition(
2169       "CMAKE_NINJA_OUTPUT_PATH_PREFIX");
2170   EnsureTrailingSlash(this->OutputPathPrefix);
2171 }
2172 
NinjaOutputPath(std::string const & path) const2173 std::string cmGlobalNinjaGenerator::NinjaOutputPath(
2174   std::string const& path) const
2175 {
2176   if (!this->HasOutputPathPrefix() || cmSystemTools::FileIsFullPath(path)) {
2177     return path;
2178   }
2179   return cmStrCat(this->OutputPathPrefix, path);
2180 }
2181 
StripNinjaOutputPathPrefixAsSuffix(std::string & path)2182 void cmGlobalNinjaGenerator::StripNinjaOutputPathPrefixAsSuffix(
2183   std::string& path)
2184 {
2185   if (path.empty()) {
2186     return;
2187   }
2188   EnsureTrailingSlash(path);
2189   cmStripSuffixIfExists(path, this->OutputPathPrefix);
2190 }
2191 
2192 #if !defined(CMAKE_BOOTSTRAP)
2193 
2194 /*
2195 
2196 We use the following approach to support Fortran.  Each target already
2197 has a <target>.dir/ directory used to hold intermediate files for CMake.
2198 For each target, a FortranDependInfo.json file is generated by CMake with
2199 information about include directories, module directories, and the locations
2200 the per-target directories for target dependencies.
2201 
2202 Compilation of source files within a target is split into the following steps:
2203 
2204 1. Preprocess all sources, scan preprocessed output for module dependencies.
2205    This step is done with independent build statements for each source,
2206    and can therefore be done in parallel.
2207 
2208     rule Fortran_PREPROCESS
2209       depfile = $DEP_FILE
2210       command = gfortran -cpp $DEFINES $INCLUDES $FLAGS -E $in -o $out &&
2211                 cmake -E cmake_ninja_depends \
2212                   --tdi=FortranDependInfo.json --pp=$out --dep=$DEP_FILE \
2213                   --obj=$OBJ_FILE --ddi=$DYNDEP_INTERMEDIATE_FILE \
2214                   --lang=Fortran
2215 
2216     build src.f90-pp.f90 | src.f90.o.ddi: Fortran_PREPROCESS src.f90
2217       OBJ_FILE = src.f90.o
2218       DEP_FILE = src.f90.o.d
2219       DYNDEP_INTERMEDIATE_FILE = src.f90.o.ddi
2220 
2221    The ``cmake -E cmake_ninja_depends`` tool reads the preprocessed output
2222    and generates the ninja depfile for preprocessor dependencies.  It also
2223    generates a "ddi" file (in a format private to CMake) that lists the
2224    object file that compilation will produce along with the module names
2225    it provides and/or requires.  The "ddi" file is an implicit output
2226    because it should not appear in "$out" but is generated by the rule.
2227 
2228 2. Consolidate the per-source module dependencies saved in the "ddi"
2229    files from all sources to produce a ninja "dyndep" file, ``Fortran.dd``.
2230 
2231     rule Fortran_DYNDEP
2232       command = cmake -E cmake_ninja_dyndep \
2233                   --tdi=FortranDependInfo.json --lang=Fortran --dd=$out $in
2234 
2235     build Fortran.dd: Fortran_DYNDEP src1.f90.o.ddi src2.f90.o.ddi
2236 
2237    The ``cmake -E cmake_ninja_dyndep`` tool reads the "ddi" files from all
2238    sources in the target and the ``FortranModules.json`` files from targets
2239    on which the target depends.  It computes dependency edges on compilations
2240    that require modules to those that provide the modules.  This information
2241    is placed in the ``Fortran.dd`` file for ninja to load later.  It also
2242    writes the expected location of modules provided by this target into
2243    ``FortranModules.json`` for use by dependent targets.
2244 
2245 3. Compile all sources after loading dynamically discovered dependencies
2246    of the compilation build statements from their ``dyndep`` bindings.
2247 
2248     rule Fortran_COMPILE
2249       command = gfortran $INCLUDES $FLAGS -c $in -o $out
2250 
2251     build src1.f90.o: Fortran_COMPILE src1.f90-pp.f90 || Fortran.dd
2252       dyndep = Fortran.dd
2253 
2254    The "dyndep" binding tells ninja to load dynamically discovered
2255    dependency information from ``Fortran.dd``.  This adds information
2256    such as:
2257 
2258     build src1.f90.o | mod1.mod: dyndep
2259       restat = 1
2260 
2261    This tells ninja that ``mod1.mod`` is an implicit output of compiling
2262    the object file ``src1.f90.o``.  The ``restat`` binding tells it that
2263    the timestamp of the output may not always change.  Additionally:
2264 
2265     build src2.f90.o: dyndep | mod1.mod
2266 
2267    This tells ninja that ``mod1.mod`` is a dependency of compiling the
2268    object file ``src2.f90.o``.  This ensures that ``src1.f90.o`` and
2269    ``mod1.mod`` will always be up to date before ``src2.f90.o`` is built
2270    (because the latter consumes the module).
2271 */
2272 
2273 namespace {
2274 
2275 struct cmSourceInfo
2276 {
2277   cmScanDepInfo ScanDep;
2278   std::vector<std::string> Includes;
2279 };
2280 
2281 cm::optional<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
2282   std::string const& arg_tdi, std::string const& arg_pp);
2283 }
2284 
cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg,std::vector<std::string>::const_iterator argEnd)2285 int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg,
2286                               std::vector<std::string>::const_iterator argEnd)
2287 {
2288   std::string arg_tdi;
2289   std::string arg_pp;
2290   std::string arg_dep;
2291   std::string arg_obj;
2292   std::string arg_ddi;
2293   std::string arg_lang;
2294   for (std::string const& arg : cmMakeRange(argBeg, argEnd)) {
2295     if (cmHasLiteralPrefix(arg, "--tdi=")) {
2296       arg_tdi = arg.substr(6);
2297     } else if (cmHasLiteralPrefix(arg, "--pp=")) {
2298       arg_pp = arg.substr(5);
2299     } else if (cmHasLiteralPrefix(arg, "--dep=")) {
2300       arg_dep = arg.substr(6);
2301     } else if (cmHasLiteralPrefix(arg, "--obj=")) {
2302       arg_obj = arg.substr(6);
2303     } else if (cmHasLiteralPrefix(arg, "--ddi=")) {
2304       arg_ddi = arg.substr(6);
2305     } else if (cmHasLiteralPrefix(arg, "--lang=")) {
2306       arg_lang = arg.substr(7);
2307     } else {
2308       cmSystemTools::Error(
2309         cmStrCat("-E cmake_ninja_depends unknown argument: ", arg));
2310       return 1;
2311     }
2312   }
2313   if (arg_tdi.empty()) {
2314     cmSystemTools::Error("-E cmake_ninja_depends requires value for --tdi=");
2315     return 1;
2316   }
2317   if (arg_pp.empty()) {
2318     cmSystemTools::Error("-E cmake_ninja_depends requires value for --pp=");
2319     return 1;
2320   }
2321   if (arg_dep.empty()) {
2322     cmSystemTools::Error("-E cmake_ninja_depends requires value for --dep=");
2323     return 1;
2324   }
2325   if (arg_obj.empty()) {
2326     cmSystemTools::Error("-E cmake_ninja_depends requires value for --obj=");
2327     return 1;
2328   }
2329   if (arg_ddi.empty()) {
2330     cmSystemTools::Error("-E cmake_ninja_depends requires value for --ddi=");
2331     return 1;
2332   }
2333   if (arg_lang.empty()) {
2334     cmSystemTools::Error("-E cmake_ninja_depends requires value for --lang=");
2335     return 1;
2336   }
2337 
2338   cm::optional<cmSourceInfo> info;
2339   if (arg_lang == "Fortran") {
2340     info = cmcmd_cmake_ninja_depends_fortran(arg_tdi, arg_pp);
2341   } else {
2342     cmSystemTools::Error(
2343       cmStrCat("-E cmake_ninja_depends does not understand the ", arg_lang,
2344                " language"));
2345     return 1;
2346   }
2347 
2348   if (!info) {
2349     // The error message is already expected to have been output.
2350     return 1;
2351   }
2352 
2353   info->ScanDep.PrimaryOutput = arg_obj;
2354 
2355   {
2356     cmGeneratedFileStream depfile(arg_dep);
2357     depfile << cmSystemTools::ConvertToUnixOutputPath(arg_pp) << ":";
2358     for (std::string const& include : info->Includes) {
2359       depfile << " \\\n " << cmSystemTools::ConvertToUnixOutputPath(include);
2360     }
2361     depfile << "\n";
2362   }
2363 
2364   if (!cmScanDepFormat_P1689_Write(arg_ddi, info->ScanDep)) {
2365     cmSystemTools::Error(
2366       cmStrCat("-E cmake_ninja_depends failed to write ", arg_ddi));
2367     return 1;
2368   }
2369   return 0;
2370 }
2371 
2372 namespace {
2373 
cmcmd_cmake_ninja_depends_fortran(std::string const & arg_tdi,std::string const & arg_pp)2374 cm::optional<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
2375   std::string const& arg_tdi, std::string const& arg_pp)
2376 {
2377   cm::optional<cmSourceInfo> info;
2378   cmFortranCompiler fc;
2379   std::vector<std::string> includes;
2380   std::string dir_top_bld;
2381   std::string module_dir;
2382   {
2383     Json::Value tdio;
2384     Json::Value const& tdi = tdio;
2385     {
2386       cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary);
2387       Json::Reader reader;
2388       if (!reader.parse(tdif, tdio, false)) {
2389         cmSystemTools::Error(
2390           cmStrCat("-E cmake_ninja_depends failed to parse ", arg_tdi,
2391                    reader.getFormattedErrorMessages()));
2392         return info;
2393       }
2394     }
2395 
2396     dir_top_bld = tdi["dir-top-bld"].asString();
2397     if (!dir_top_bld.empty() && !cmHasLiteralSuffix(dir_top_bld, "/")) {
2398       dir_top_bld += '/';
2399     }
2400 
2401     Json::Value const& tdi_include_dirs = tdi["include-dirs"];
2402     if (tdi_include_dirs.isArray()) {
2403       for (auto const& tdi_include_dir : tdi_include_dirs) {
2404         includes.push_back(tdi_include_dir.asString());
2405       }
2406     }
2407 
2408     Json::Value const& tdi_module_dir = tdi["module-dir"];
2409     module_dir = tdi_module_dir.asString();
2410     if (!module_dir.empty() && !cmHasLiteralSuffix(module_dir, "/")) {
2411       module_dir += '/';
2412     }
2413 
2414     Json::Value const& tdi_compiler_id = tdi["compiler-id"];
2415     fc.Id = tdi_compiler_id.asString();
2416 
2417     Json::Value const& tdi_submodule_sep = tdi["submodule-sep"];
2418     fc.SModSep = tdi_submodule_sep.asString();
2419 
2420     Json::Value const& tdi_submodule_ext = tdi["submodule-ext"];
2421     fc.SModExt = tdi_submodule_ext.asString();
2422   }
2423 
2424   cmFortranSourceInfo finfo;
2425   std::set<std::string> defines;
2426   cmFortranParser parser(fc, includes, defines, finfo);
2427   if (!cmFortranParser_FilePush(&parser, arg_pp.c_str())) {
2428     cmSystemTools::Error(
2429       cmStrCat("-E cmake_ninja_depends failed to open ", arg_pp));
2430     return info;
2431   }
2432   if (cmFortran_yyparse(parser.Scanner) != 0) {
2433     // Failed to parse the file.
2434     return info;
2435   }
2436 
2437   info = cmSourceInfo();
2438   for (std::string const& provide : finfo.Provides) {
2439     cmSourceReqInfo src_info;
2440     src_info.LogicalName = provide;
2441     if (!module_dir.empty()) {
2442       std::string mod = cmStrCat(module_dir, provide);
2443       if (!dir_top_bld.empty() && cmHasPrefix(mod, dir_top_bld)) {
2444         mod = mod.substr(dir_top_bld.size());
2445       }
2446       src_info.CompiledModulePath = std::move(mod);
2447     }
2448     info->ScanDep.Provides.emplace_back(src_info);
2449   }
2450   for (std::string const& require : finfo.Requires) {
2451     // Require modules not provided in the same source.
2452     if (finfo.Provides.count(require)) {
2453       continue;
2454     }
2455     cmSourceReqInfo src_info;
2456     src_info.LogicalName = require;
2457     info->ScanDep.Requires.emplace_back(src_info);
2458   }
2459   for (std::string const& include : finfo.Includes) {
2460     info->Includes.push_back(include);
2461   }
2462   return info;
2463 }
2464 }
2465 
WriteDyndepFile(std::string const & dir_top_src,std::string const & dir_top_bld,std::string const & dir_cur_src,std::string const & dir_cur_bld,std::string const & arg_dd,std::vector<std::string> const & arg_ddis,std::string const & module_dir,std::vector<std::string> const & linked_target_dirs,std::string const & arg_lang,std::string const & arg_modmapfmt)2466 bool cmGlobalNinjaGenerator::WriteDyndepFile(
2467   std::string const& dir_top_src, std::string const& dir_top_bld,
2468   std::string const& dir_cur_src, std::string const& dir_cur_bld,
2469   std::string const& arg_dd, std::vector<std::string> const& arg_ddis,
2470   std::string const& module_dir,
2471   std::vector<std::string> const& linked_target_dirs,
2472   std::string const& arg_lang, std::string const& arg_modmapfmt)
2473 {
2474   // Setup path conversions.
2475   {
2476     cmStateSnapshot snapshot = this->GetCMakeInstance()->GetCurrentSnapshot();
2477     snapshot.GetDirectory().SetCurrentSource(dir_cur_src);
2478     snapshot.GetDirectory().SetCurrentBinary(dir_cur_bld);
2479     auto mfd = cm::make_unique<cmMakefile>(this, snapshot);
2480     auto lgd = this->CreateLocalGenerator(mfd.get());
2481     lgd->SetRelativePathTopSource(dir_top_src);
2482     lgd->SetRelativePathTopBinary(dir_top_bld);
2483     this->Makefiles.push_back(std::move(mfd));
2484     this->LocalGenerators.push_back(std::move(lgd));
2485   }
2486 
2487   std::vector<cmScanDepInfo> objects;
2488   for (std::string const& arg_ddi : arg_ddis) {
2489     cmScanDepInfo info;
2490     if (!cmScanDepFormat_P1689_Parse(arg_ddi, &info)) {
2491       cmSystemTools::Error(
2492         cmStrCat("-E cmake_ninja_dyndep failed to parse ddi file ", arg_ddi));
2493       return false;
2494     }
2495     objects.push_back(std::move(info));
2496   }
2497 
2498   // Map from module name to module file path, if known.
2499   std::map<std::string, std::string> mod_files;
2500 
2501   // Populate the module map with those provided by linked targets first.
2502   for (std::string const& linked_target_dir : linked_target_dirs) {
2503     std::string const ltmn =
2504       cmStrCat(linked_target_dir, "/", arg_lang, "Modules.json");
2505     Json::Value ltm;
2506     cmsys::ifstream ltmf(ltmn.c_str(), std::ios::in | std::ios::binary);
2507     Json::Reader reader;
2508     if (ltmf && !reader.parse(ltmf, ltm, false)) {
2509       cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
2510                                     linked_target_dir,
2511                                     reader.getFormattedErrorMessages()));
2512       return false;
2513     }
2514     if (ltm.isObject()) {
2515       for (Json::Value::iterator i = ltm.begin(); i != ltm.end(); ++i) {
2516         mod_files[i.key().asString()] = i->asString();
2517       }
2518     }
2519   }
2520 
2521   // Extend the module map with those provided by this target.
2522   // We do this after loading the modules provided by linked targets
2523   // in case we have one of the same name that must be preferred.
2524   Json::Value tm = Json::objectValue;
2525   for (cmScanDepInfo const& object : objects) {
2526     for (auto const& p : object.Provides) {
2527       std::string mod;
2528       if (!p.CompiledModulePath.empty()) {
2529         // The scanner provided the path to the module file.
2530         mod = p.CompiledModulePath;
2531         if (!cmSystemTools::FileIsFullPath(mod)) {
2532           // Treat relative to work directory (top of build tree).
2533           mod = cmSystemTools::CollapseFullPath(mod, dir_top_bld);
2534         }
2535       } else {
2536         // Assume the module file path matches the logical module name.
2537         mod = cmStrCat(module_dir, p.LogicalName);
2538       }
2539       mod_files[p.LogicalName] = mod;
2540       tm[p.LogicalName] = mod;
2541     }
2542   }
2543 
2544   cmGeneratedFileStream ddf(arg_dd);
2545   ddf << "ninja_dyndep_version = 1.0\n";
2546 
2547   {
2548     cmNinjaBuild build("dyndep");
2549     build.Outputs.emplace_back("");
2550     for (cmScanDepInfo const& object : objects) {
2551       build.Outputs[0] = this->ConvertToNinjaPath(object.PrimaryOutput);
2552       build.ImplicitOuts.clear();
2553       for (auto const& p : object.Provides) {
2554         build.ImplicitOuts.push_back(
2555           this->ConvertToNinjaPath(mod_files[p.LogicalName]));
2556       }
2557       build.ImplicitDeps.clear();
2558       for (auto const& r : object.Requires) {
2559         auto mit = mod_files.find(r.LogicalName);
2560         if (mit != mod_files.end()) {
2561           build.ImplicitDeps.push_back(this->ConvertToNinjaPath(mit->second));
2562         }
2563       }
2564       build.Variables.clear();
2565       if (!object.Provides.empty()) {
2566         build.Variables.emplace("restat", "1");
2567       }
2568 
2569       if (arg_modmapfmt.empty()) {
2570         // nothing to do.
2571       } else {
2572         std::stringstream mm;
2573         if (arg_modmapfmt == "gcc") {
2574           // Documented in GCC's documentation. The format is a series of lines
2575           // with a module name and the associated filename separated by
2576           // spaces. The first line may use `$root` as the module name to
2577           // specify a "repository root". That is used to anchor any relative
2578           // paths present in the file (CMake should never generate any).
2579 
2580           // Write the root directory to use for module paths.
2581           mm << "$root .\n";
2582 
2583           for (auto const& l : object.Provides) {
2584             auto m = mod_files.find(l.LogicalName);
2585             if (m != mod_files.end()) {
2586               mm << l.LogicalName << " " << this->ConvertToNinjaPath(m->second)
2587                  << "\n";
2588             }
2589           }
2590           for (auto const& r : object.Requires) {
2591             auto m = mod_files.find(r.LogicalName);
2592             if (m != mod_files.end()) {
2593               mm << r.LogicalName << " " << this->ConvertToNinjaPath(m->second)
2594                  << "\n";
2595             }
2596           }
2597         } else {
2598           cmSystemTools::Error(
2599             cmStrCat("-E cmake_ninja_dyndep does not understand the ",
2600                      arg_modmapfmt, " module map format"));
2601           return false;
2602         }
2603 
2604         // XXX(modmap): If changing this path construction, change
2605         // `cmNinjaTargetGenerator::WriteObjectBuildStatements` to generate the
2606         // corresponding file path.
2607         cmGeneratedFileStream mmf(cmStrCat(object.PrimaryOutput, ".modmap"));
2608         mmf << mm.str();
2609       }
2610 
2611       this->WriteBuild(ddf, build);
2612     }
2613   }
2614 
2615   // Store the map of modules provided by this target in a file for
2616   // use by dependents that reference this target in linked-target-dirs.
2617   std::string const target_mods_file = cmStrCat(
2618     cmSystemTools::GetFilenamePath(arg_dd), '/', arg_lang, "Modules.json");
2619   cmGeneratedFileStream tmf(target_mods_file);
2620   tmf << tm;
2621 
2622   return true;
2623 }
2624 
cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,std::vector<std::string>::const_iterator argEnd)2625 int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
2626                              std::vector<std::string>::const_iterator argEnd)
2627 {
2628   std::vector<std::string> arg_full =
2629     cmSystemTools::HandleResponseFile(argBeg, argEnd);
2630 
2631   std::string arg_dd;
2632   std::string arg_lang;
2633   std::string arg_tdi;
2634   std::string arg_modmapfmt;
2635   std::vector<std::string> arg_ddis;
2636   for (std::string const& arg : arg_full) {
2637     if (cmHasLiteralPrefix(arg, "--tdi=")) {
2638       arg_tdi = arg.substr(6);
2639     } else if (cmHasLiteralPrefix(arg, "--lang=")) {
2640       arg_lang = arg.substr(7);
2641     } else if (cmHasLiteralPrefix(arg, "--dd=")) {
2642       arg_dd = arg.substr(5);
2643     } else if (cmHasLiteralPrefix(arg, "--modmapfmt=")) {
2644       arg_modmapfmt = arg.substr(12);
2645     } else if (!cmHasLiteralPrefix(arg, "--") &&
2646                cmHasLiteralSuffix(arg, ".ddi")) {
2647       arg_ddis.push_back(arg);
2648     } else {
2649       cmSystemTools::Error(
2650         cmStrCat("-E cmake_ninja_dyndep unknown argument: ", arg));
2651       return 1;
2652     }
2653   }
2654   if (arg_tdi.empty()) {
2655     cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --tdi=");
2656     return 1;
2657   }
2658   if (arg_lang.empty()) {
2659     cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --lang=");
2660     return 1;
2661   }
2662   if (arg_dd.empty()) {
2663     cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --dd=");
2664     return 1;
2665   }
2666 
2667   Json::Value tdio;
2668   Json::Value const& tdi = tdio;
2669   {
2670     cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary);
2671     Json::Reader reader;
2672     if (!reader.parse(tdif, tdio, false)) {
2673       cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
2674                                     arg_tdi,
2675                                     reader.getFormattedErrorMessages()));
2676       return 1;
2677     }
2678   }
2679 
2680   std::string const dir_cur_bld = tdi["dir-cur-bld"].asString();
2681   std::string const dir_cur_src = tdi["dir-cur-src"].asString();
2682   std::string const dir_top_bld = tdi["dir-top-bld"].asString();
2683   std::string const dir_top_src = tdi["dir-top-src"].asString();
2684   std::string module_dir = tdi["module-dir"].asString();
2685   if (!module_dir.empty() && !cmHasLiteralSuffix(module_dir, "/")) {
2686     module_dir += '/';
2687   }
2688   std::vector<std::string> linked_target_dirs;
2689   Json::Value const& tdi_linked_target_dirs = tdi["linked-target-dirs"];
2690   if (tdi_linked_target_dirs.isArray()) {
2691     for (auto const& tdi_linked_target_dir : tdi_linked_target_dirs) {
2692       linked_target_dirs.push_back(tdi_linked_target_dir.asString());
2693     }
2694   }
2695 
2696   cmake cm(cmake::RoleInternal, cmState::Unknown);
2697   cm.SetHomeDirectory(dir_top_src);
2698   cm.SetHomeOutputDirectory(dir_top_bld);
2699   auto ggd = cm.CreateGlobalGenerator("Ninja");
2700   if (!ggd ||
2701       !cm::static_reference_cast<cmGlobalNinjaGenerator>(ggd).WriteDyndepFile(
2702         dir_top_src, dir_top_bld, dir_cur_src, dir_cur_bld, arg_dd, arg_ddis,
2703         module_dir, linked_target_dirs, arg_lang, arg_modmapfmt)) {
2704     return 1;
2705   }
2706   return 0;
2707 }
2708 
2709 #endif
2710 
EnableCrossConfigBuild() const2711 bool cmGlobalNinjaGenerator::EnableCrossConfigBuild() const
2712 {
2713   return !this->CrossConfigs.empty();
2714 }
2715 
AppendDirectoryForConfig(const std::string & prefix,const std::string & config,const std::string & suffix,std::string & dir)2716 void cmGlobalNinjaGenerator::AppendDirectoryForConfig(
2717   const std::string& prefix, const std::string& config,
2718   const std::string& suffix, std::string& dir)
2719 {
2720   if (!config.empty() && this->IsMultiConfig()) {
2721     dir += cmStrCat(prefix, config, suffix);
2722   }
2723 }
2724 
GetCrossConfigs(const std::string & fileConfig) const2725 std::set<std::string> cmGlobalNinjaGenerator::GetCrossConfigs(
2726   const std::string& fileConfig) const
2727 {
2728   auto result = this->CrossConfigs;
2729   result.insert(fileConfig);
2730   return result;
2731 }
2732 
IsSingleConfigUtility(cmGeneratorTarget const * target) const2733 bool cmGlobalNinjaGenerator::IsSingleConfigUtility(
2734   cmGeneratorTarget const* target) const
2735 {
2736   return target->GetType() == cmStateEnums::UTILITY &&
2737     !this->PerConfigUtilityTargets.count(target->GetName());
2738 }
2739 
2740 const char* cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE =
2741   "CMakeFiles/common.ninja";
2742 const char* cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION = ".ninja";
2743 
cmGlobalNinjaMultiGenerator(cmake * cm)2744 cmGlobalNinjaMultiGenerator::cmGlobalNinjaMultiGenerator(cmake* cm)
2745   : cmGlobalNinjaGenerator(cm)
2746 {
2747   cm->GetState()->SetIsGeneratorMultiConfig(true);
2748   cm->GetState()->SetNinjaMulti(true);
2749 }
2750 
GetDocumentation(cmDocumentationEntry & entry)2751 void cmGlobalNinjaMultiGenerator::GetDocumentation(cmDocumentationEntry& entry)
2752 {
2753   entry.Name = cmGlobalNinjaMultiGenerator::GetActualName();
2754   entry.Brief = "Generates build-<Config>.ninja files.";
2755 }
2756 
ExpandCFGIntDir(const std::string & str,const std::string & config) const2757 std::string cmGlobalNinjaMultiGenerator::ExpandCFGIntDir(
2758   const std::string& str, const std::string& config) const
2759 {
2760   std::string result = str;
2761   cmSystemTools::ReplaceString(result, this->GetCMakeCFGIntDir(), config);
2762   return result;
2763 }
2764 
OpenBuildFileStreams()2765 bool cmGlobalNinjaMultiGenerator::OpenBuildFileStreams()
2766 {
2767   if (!this->OpenFileStream(this->CommonFileStream,
2768                             cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE)) {
2769     return false;
2770   }
2771 
2772   if (!this->OpenFileStream(this->DefaultFileStream, NINJA_BUILD_FILE)) {
2773     return false;
2774   }
2775   *this->DefaultFileStream << "# Build using rules for '"
2776                            << this->DefaultFileConfig << "'.\n\n"
2777                            << "include "
2778                            << GetNinjaImplFilename(this->DefaultFileConfig)
2779                            << "\n\n";
2780 
2781   // Write a comment about this file.
2782   *this->CommonFileStream
2783     << "# This file contains build statements common to all "
2784        "configurations.\n\n";
2785 
2786   auto const& configs =
2787     this->Makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
2788   return std::all_of(
2789     configs.begin(), configs.end(), [this](std::string const& config) -> bool {
2790       // Open impl file.
2791       if (!this->OpenFileStream(this->ImplFileStreams[config],
2792                                 GetNinjaImplFilename(config))) {
2793         return false;
2794       }
2795 
2796       // Write a comment about this file.
2797       *this->ImplFileStreams[config]
2798         << "# This file contains build statements specific to the \"" << config
2799         << "\"\n# configuration.\n\n";
2800 
2801       // Open config file.
2802       if (!this->OpenFileStream(this->ConfigFileStreams[config],
2803                                 GetNinjaConfigFilename(config))) {
2804         return false;
2805       }
2806 
2807       // Write a comment about this file.
2808       *this->ConfigFileStreams[config]
2809         << "# This file contains aliases specific to the \"" << config
2810         << "\"\n# configuration.\n\n"
2811         << "include " << GetNinjaImplFilename(config) << "\n\n";
2812 
2813       return true;
2814     });
2815 }
2816 
CloseBuildFileStreams()2817 void cmGlobalNinjaMultiGenerator::CloseBuildFileStreams()
2818 {
2819   if (this->CommonFileStream) {
2820     this->CommonFileStream.reset();
2821   } else {
2822     cmSystemTools::Error("Common file stream was not open.");
2823   }
2824 
2825   if (this->DefaultFileStream) {
2826     this->DefaultFileStream.reset();
2827   } // No error if it wasn't open
2828 
2829   for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs(
2830          cmMakefile::IncludeEmptyConfig)) {
2831     if (this->ImplFileStreams[config]) {
2832       this->ImplFileStreams[config].reset();
2833     } else {
2834       cmSystemTools::Error(
2835         cmStrCat("Impl file stream for \"", config, "\" was not open."));
2836     }
2837     if (this->ConfigFileStreams[config]) {
2838       this->ConfigFileStreams[config].reset();
2839     } else {
2840       cmSystemTools::Error(
2841         cmStrCat("Config file stream for \"", config, "\" was not open."));
2842     }
2843   }
2844 }
2845 
AppendNinjaFileArgument(GeneratedMakeCommand & command,const std::string & config) const2846 void cmGlobalNinjaMultiGenerator::AppendNinjaFileArgument(
2847   GeneratedMakeCommand& command, const std::string& config) const
2848 {
2849   if (!config.empty()) {
2850     command.Add("-f");
2851     command.Add(GetNinjaConfigFilename(config));
2852   }
2853 }
2854 
GetNinjaImplFilename(const std::string & config)2855 std::string cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(
2856   const std::string& config)
2857 {
2858   return cmStrCat("CMakeFiles/impl-", config,
2859                   cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
2860 }
2861 
GetNinjaConfigFilename(const std::string & config)2862 std::string cmGlobalNinjaMultiGenerator::GetNinjaConfigFilename(
2863   const std::string& config)
2864 {
2865   return cmStrCat("build-", config,
2866                   cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
2867 }
2868 
AddRebuildManifestOutputs(cmNinjaDeps & outputs) const2869 void cmGlobalNinjaMultiGenerator::AddRebuildManifestOutputs(
2870   cmNinjaDeps& outputs) const
2871 {
2872   for (auto const& config : this->Makefiles.front()->GetGeneratorConfigs(
2873          cmMakefile::IncludeEmptyConfig)) {
2874     outputs.push_back(this->NinjaOutputPath(GetNinjaImplFilename(config)));
2875     outputs.push_back(this->NinjaOutputPath(GetNinjaConfigFilename(config)));
2876   }
2877   if (!this->DefaultFileConfig.empty()) {
2878     outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
2879   }
2880 }
2881 
GetQtAutoGenConfigs(std::vector<std::string> & configs) const2882 void cmGlobalNinjaMultiGenerator::GetQtAutoGenConfigs(
2883   std::vector<std::string>& configs) const
2884 {
2885   auto allConfigs =
2886     this->Makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
2887   configs.insert(configs.end(), cm::cbegin(allConfigs), cm::cend(allConfigs));
2888 }
2889 
InspectConfigTypeVariables()2890 bool cmGlobalNinjaMultiGenerator::InspectConfigTypeVariables()
2891 {
2892   std::vector<std::string> configsVec;
2893   cmExpandList(
2894     this->Makefiles.front()->GetSafeDefinition("CMAKE_CONFIGURATION_TYPES"),
2895     configsVec);
2896   if (configsVec.empty()) {
2897     configsVec.emplace_back();
2898   }
2899   std::set<std::string> configs(configsVec.cbegin(), configsVec.cend());
2900 
2901   this->DefaultFileConfig =
2902     this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_BUILD_TYPE");
2903   if (this->DefaultFileConfig.empty()) {
2904     this->DefaultFileConfig = configsVec.front();
2905   }
2906   if (!configs.count(this->DefaultFileConfig)) {
2907     std::ostringstream msg;
2908     msg << "The configuration specified by "
2909         << "CMAKE_DEFAULT_BUILD_TYPE (" << this->DefaultFileConfig
2910         << ") is not present in CMAKE_CONFIGURATION_TYPES";
2911     this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2912                                            msg.str());
2913     return false;
2914   }
2915 
2916   std::vector<std::string> crossConfigsVec;
2917   cmExpandList(
2918     this->Makefiles.front()->GetSafeDefinition("CMAKE_CROSS_CONFIGS"),
2919     crossConfigsVec);
2920   auto crossConfigs = ListSubsetWithAll(configs, configs, crossConfigsVec);
2921   if (!crossConfigs) {
2922     std::ostringstream msg;
2923     msg << "CMAKE_CROSS_CONFIGS is not a subset of "
2924         << "CMAKE_CONFIGURATION_TYPES";
2925     this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2926                                            msg.str());
2927     return false;
2928   }
2929   this->CrossConfigs = *crossConfigs;
2930 
2931   auto defaultConfigsString =
2932     this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_CONFIGS");
2933   if (defaultConfigsString.empty()) {
2934     defaultConfigsString = this->DefaultFileConfig;
2935   }
2936   if (!defaultConfigsString.empty() &&
2937       defaultConfigsString != this->DefaultFileConfig &&
2938       (this->DefaultFileConfig.empty() || this->CrossConfigs.empty())) {
2939     std::ostringstream msg;
2940     msg << "CMAKE_DEFAULT_CONFIGS cannot be used without "
2941         << "CMAKE_DEFAULT_BUILD_TYPE or CMAKE_CROSS_CONFIGS";
2942     this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2943                                            msg.str());
2944     return false;
2945   }
2946 
2947   std::vector<std::string> defaultConfigsVec;
2948   cmExpandList(defaultConfigsString, defaultConfigsVec);
2949   if (!this->DefaultFileConfig.empty()) {
2950     auto defaultConfigs =
2951       ListSubsetWithAll(this->GetCrossConfigs(this->DefaultFileConfig),
2952                         this->CrossConfigs, defaultConfigsVec);
2953     if (!defaultConfigs) {
2954       std::ostringstream msg;
2955       msg << "CMAKE_DEFAULT_CONFIGS is not a subset of CMAKE_CROSS_CONFIGS";
2956       this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2957                                              msg.str());
2958       return false;
2959     }
2960     this->DefaultConfigs = *defaultConfigs;
2961   }
2962 
2963   return true;
2964 }
2965 
GetDefaultBuildConfig() const2966 std::string cmGlobalNinjaMultiGenerator::GetDefaultBuildConfig() const
2967 {
2968   return "";
2969 }
2970 
OrderDependsTargetForTarget(cmGeneratorTarget const * target,const std::string & config) const2971 std::string cmGlobalNinjaMultiGenerator::OrderDependsTargetForTarget(
2972   cmGeneratorTarget const* target, const std::string& config) const
2973 {
2974   return cmStrCat("cmake_object_order_depends_target_", target->GetName(), '_',
2975                   cmSystemTools::UpperCase(config));
2976 }
2977