Coverage Report

Created: 2026-09-14 06:43

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