Coverage Report

Created: 2026-06-15 07:03

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