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