Coverage Report

Created: 2026-07-14 07:09

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