Coverage Report

Created: 2026-04-29 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmLocalNinjaGenerator.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 "cmLocalNinjaGenerator.h"
4
5
#include <algorithm>
6
#include <cassert>
7
#include <cstdio>
8
#include <memory>
9
#include <sstream>
10
#include <utility>
11
12
#include <cm/unordered_set>
13
#include <cmext/string_view>
14
15
#include "cmsys/FStream.hxx"
16
17
#include "cm_codecvt_Encoding.hxx"
18
19
#include "cmCryptoHash.h"
20
#include "cmCustomCommand.h"
21
#include "cmCustomCommandGenerator.h"
22
#include "cmDiagnostics.h"
23
#include "cmGeneratedFileStream.h"
24
#include "cmGeneratorExpression.h"
25
#include "cmGeneratorTarget.h"
26
#include "cmGlobalGenerator.h"
27
#include "cmGlobalNinjaGenerator.h"
28
#include "cmList.h"
29
#include "cmListFileCache.h"
30
#include "cmLocalGenerator.h"
31
#include "cmMakefile.h"
32
#include "cmNinjaTargetGenerator.h"
33
#include "cmNinjaTypes.h"
34
#include "cmPolicies.h"
35
#include "cmRulePlaceholderExpander.h"
36
#include "cmSourceFile.h"
37
#include "cmState.h"
38
#include "cmStateTypes.h"
39
#include "cmStringAlgorithms.h"
40
#include "cmSystemTools.h"
41
#include "cmTarget.h"
42
#include "cmValue.h"
43
#include "cmake.h"
44
45
cmLocalNinjaGenerator::cmLocalNinjaGenerator(cmGlobalGenerator* gg,
46
                                             cmMakefile* mf)
47
0
  : cmLocalCommonGenerator(gg, mf)
48
0
{
49
0
}
50
51
// Virtual public methods.
52
53
std::unique_ptr<cmRulePlaceholderExpander>
54
cmLocalNinjaGenerator::CreateRulePlaceholderExpander(
55
  cmBuildStep buildStep) const
56
0
{
57
0
  auto ret = this->cmLocalGenerator::CreateRulePlaceholderExpander(buildStep);
58
0
  ret->SetTargetImpLib("$TARGET_IMPLIB");
59
0
  return std::unique_ptr<cmRulePlaceholderExpander>(std::move(ret));
60
0
}
61
62
0
cmLocalNinjaGenerator::~cmLocalNinjaGenerator() = default;
63
64
void cmLocalNinjaGenerator::Generate()
65
0
{
66
  // Compute the path to use when referencing the current output
67
  // directory from the top output directory.
68
0
  this->HomeRelativeOutputPath =
69
0
    this->MaybeRelativeToTopBinDir(this->GetObjectOutputRoot());
70
0
  if (this->HomeRelativeOutputPath == ".") {
71
0
    this->HomeRelativeOutputPath.clear();
72
0
  }
73
74
0
  if (this->GetGlobalGenerator()->IsMultiConfig()) {
75
0
    for (auto const& config : this->GetConfigNames()) {
76
0
      this->WriteProcessedMakefile(this->GetImplFileStream(config));
77
0
    }
78
0
  }
79
0
  this->WriteProcessedMakefile(this->GetCommonFileStream());
80
#ifdef NINJA_GEN_VERBOSE_FILES
81
  this->WriteProcessedMakefile(this->GetRulesFileStream());
82
#endif
83
84
  // We do that only once for the top CMakeLists.txt file.
85
0
  if (this->IsRootMakefile()) {
86
0
    this->WriteBuildFileTop();
87
88
0
    this->WritePools(this->GetRulesFileStream());
89
90
0
    std::string const& showIncludesPrefix =
91
0
      this->GetMakefile()->GetSafeDefinition("CMAKE_CL_SHOWINCLUDES_PREFIX");
92
0
    if (!showIncludesPrefix.empty()) {
93
0
      cmGlobalNinjaGenerator::WriteComment(this->GetRulesFileStream(),
94
0
                                           "localized /showIncludes string");
95
0
      this->GetRulesFileStream() << "msvc_deps_prefix = ";
96
      // 'cl /showIncludes' encodes output in the console output code page.
97
      // It may differ from the encoding used for file paths in 'build.ninja'.
98
      // Ninja matches the showIncludes prefix using its raw byte sequence.
99
0
      this->GetRulesFileStream().WriteAltEncoding(
100
0
        showIncludesPrefix, cmGeneratedFileStream::Encoding::ConsoleOutput);
101
0
      this->GetRulesFileStream() << "\n\n";
102
0
    }
103
0
  }
104
105
0
  for (auto const& target : this->GetGeneratorTargets()) {
106
0
    if (!target->IsInBuildSystem()) {
107
0
      continue;
108
0
    }
109
0
    auto tg = cmNinjaTargetGenerator::New(target.get());
110
0
    if (tg) {
111
0
      if (target->Target->IsPerConfig()) {
112
0
        for (auto const& config : this->GetConfigNames()) {
113
0
          tg->Generate(config);
114
0
          if (target->GetType() == cmStateEnums::GLOBAL_TARGET &&
115
0
              this->GetGlobalGenerator()->IsMultiConfig()) {
116
0
            cmNinjaBuild phonyAlias("phony");
117
0
            this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
118
0
              target.get(), phonyAlias.Outputs, "", DependOnTargetArtifact);
119
0
            this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
120
0
              target.get(), phonyAlias.ExplicitDeps, config,
121
0
              DependOnTargetArtifact);
122
0
            this->GetGlobalNinjaGenerator()->WriteBuild(
123
0
              *this->GetGlobalNinjaGenerator()->GetConfigFileStream(config),
124
0
              phonyAlias);
125
0
          }
126
0
        }
127
0
        if (target->GetType() == cmStateEnums::GLOBAL_TARGET &&
128
0
            this->GetGlobalGenerator()->IsMultiConfig()) {
129
0
          if (!this->GetGlobalNinjaGenerator()->GetDefaultConfigs().empty()) {
130
0
            cmNinjaBuild phonyAlias("phony");
131
0
            this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
132
0
              target.get(), phonyAlias.Outputs, "", DependOnTargetArtifact);
133
0
            for (auto const& config :
134
0
                 this->GetGlobalNinjaGenerator()->GetDefaultConfigs()) {
135
0
              this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
136
0
                target.get(), phonyAlias.ExplicitDeps, config,
137
0
                DependOnTargetArtifact);
138
0
            }
139
0
            this->GetGlobalNinjaGenerator()->WriteBuild(
140
0
              *this->GetGlobalNinjaGenerator()->GetDefaultFileStream(),
141
0
              phonyAlias);
142
0
          }
143
0
          cmNinjaBuild phonyAlias("phony");
144
0
          this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
145
0
            target.get(), phonyAlias.Outputs, "all", DependOnTargetArtifact);
146
0
          for (auto const& config : this->GetConfigNames()) {
147
0
            this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
148
0
              target.get(), phonyAlias.ExplicitDeps, config,
149
0
              DependOnTargetArtifact);
150
0
          }
151
0
          this->GetGlobalNinjaGenerator()->WriteBuild(
152
0
            *this->GetGlobalNinjaGenerator()->GetDefaultFileStream(),
153
0
            phonyAlias);
154
0
        }
155
0
      } else {
156
0
        tg->Generate("");
157
0
      }
158
0
    }
159
0
  }
160
161
0
  for (auto const& config : this->GetConfigNames()) {
162
0
    this->WriteCustomCommandBuildStatements(config);
163
0
    this->AdditionalCleanFiles(config);
164
0
  }
165
0
}
166
167
std::string cmLocalNinjaGenerator::GetObjectOutputRoot(
168
  cmStateEnums::IntermediateDirKind kind) const
169
0
{
170
0
  if (this->UseShortObjectNames(kind)) {
171
0
    return cmStrCat(this->GetBinaryDirectory(), '/',
172
0
                    this->GetGlobalGenerator()->GetShortBinaryOutputDir());
173
0
  }
174
0
  return cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles");
175
0
}
176
177
// Non-virtual public methods.
178
179
cmGlobalNinjaGenerator const* cmLocalNinjaGenerator::GetGlobalNinjaGenerator()
180
  const
181
0
{
182
0
  return static_cast<cmGlobalNinjaGenerator const*>(
183
0
    this->GetGlobalGenerator());
184
0
}
185
186
cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator()
187
0
{
188
0
  return static_cast<cmGlobalNinjaGenerator*>(this->GetGlobalGenerator());
189
0
}
190
191
std::string const& cmLocalNinjaGenerator::GetWorkingDirectory() const
192
0
{
193
0
  return this->GetState()->GetBinaryDirectory();
194
0
}
195
196
std::string cmLocalNinjaGenerator::MaybeRelativeToWorkDir(
197
  std::string const& path) const
198
0
{
199
0
  return this->GetGlobalNinjaGenerator()->NinjaOutputPath(
200
0
    this->MaybeRelativeToTopBinDir(path));
201
0
}
202
203
std::string cmLocalNinjaGenerator::GetLinkDependencyFile(
204
  cmGeneratorTarget* target, std::string const& config) const
205
0
{
206
0
  return cmStrCat(target->GetSupportDirectory(),
207
0
                  this->GetGlobalNinjaGenerator()->ConfigDirectory(config),
208
0
                  "/link.d");
209
0
}
210
211
// Virtual protected methods.
212
213
std::string cmLocalNinjaGenerator::ConvertToIncludeReference(
214
  std::string const& path, cmOutputConverter::OutputFormat format)
215
0
{
216
0
  return this->ConvertToOutputFormat(path, format);
217
0
}
218
219
// Private methods.
220
221
cmGeneratedFileStream& cmLocalNinjaGenerator::GetImplFileStream(
222
  std::string const& config) const
223
0
{
224
0
  return *this->GetGlobalNinjaGenerator()->GetImplFileStream(config);
225
0
}
226
227
cmGeneratedFileStream& cmLocalNinjaGenerator::GetCommonFileStream() const
228
0
{
229
0
  return *this->GetGlobalNinjaGenerator()->GetCommonFileStream();
230
0
}
231
232
cmGeneratedFileStream& cmLocalNinjaGenerator::GetRulesFileStream() const
233
0
{
234
0
  return *this->GetGlobalNinjaGenerator()->GetRulesFileStream();
235
0
}
236
237
cmake const* cmLocalNinjaGenerator::GetCMakeInstance() const
238
0
{
239
0
  return this->GetGlobalGenerator()->GetCMakeInstance();
240
0
}
241
242
cmake* cmLocalNinjaGenerator::GetCMakeInstance()
243
0
{
244
0
  return this->GetGlobalGenerator()->GetCMakeInstance();
245
0
}
246
247
void cmLocalNinjaGenerator::WriteBuildFileTop()
248
0
{
249
0
  this->WriteProjectHeader(this->GetCommonFileStream());
250
251
0
  if (this->GetGlobalGenerator()->IsMultiConfig()) {
252
0
    for (auto const& config : this->GetConfigNames()) {
253
0
      auto& stream = this->GetImplFileStream(config);
254
0
      this->WriteProjectHeader(stream);
255
0
      this->WriteNinjaRequiredVersion(stream);
256
0
      this->WriteNinjaConfigurationVariable(stream, config);
257
0
      this->WriteNinjaFilesInclusionConfig(stream);
258
0
    }
259
0
  } else {
260
0
    this->WriteNinjaRequiredVersion(this->GetCommonFileStream());
261
0
    this->WriteNinjaConfigurationVariable(this->GetCommonFileStream(),
262
0
                                          this->GetConfigNames().front());
263
0
  }
264
0
  this->WriteNinjaFilesInclusionCommon(this->GetCommonFileStream());
265
0
  this->WriteNinjaWorkDir(this->GetCommonFileStream());
266
267
  // For the rule file.
268
0
  this->WriteProjectHeader(this->GetRulesFileStream());
269
0
}
270
271
void cmLocalNinjaGenerator::WriteProjectHeader(std::ostream& os)
272
0
{
273
0
  cmGlobalNinjaGenerator::WriteDivider(os);
274
0
  os << "# Project: " << this->GetProjectName()
275
0
     << "\n"
276
0
        "# Configurations: "
277
0
     << cmJoin(this->GetConfigNames(), ", ") << '\n';
278
0
  cmGlobalNinjaGenerator::WriteDivider(os);
279
0
}
280
281
void cmLocalNinjaGenerator::WriteNinjaRequiredVersion(std::ostream& os)
282
0
{
283
  // Default required version
284
0
  std::string requiredVersion = cmGlobalNinjaGenerator::RequiredNinjaVersion();
285
286
  // Ninja generator uses the 'console' pool if available (>= 1.5)
287
0
  if (this->GetGlobalNinjaGenerator()->SupportsDirectConsole()) {
288
0
    requiredVersion =
289
0
      cmGlobalNinjaGenerator::RequiredNinjaVersionForConsolePool();
290
0
  }
291
292
  // The Ninja generator writes rules which require support for restat
293
  // when rebuilding build.ninja manifest (>= 1.8)
294
0
  if (this->GetGlobalNinjaGenerator()->SupportsManifestRestat() &&
295
0
      this->GetCMakeInstance()->DoWriteGlobVerifyTarget() &&
296
0
      !this->GetGlobalNinjaGenerator()->GlobalSettingIsOn(
297
0
        "CMAKE_SUPPRESS_REGENERATION")) {
298
0
    requiredVersion =
299
0
      cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat();
300
0
  }
301
302
0
  cmGlobalNinjaGenerator::WriteComment(
303
0
    os, "Minimal version of Ninja required by this file");
304
0
  os << "ninja_required_version = " << requiredVersion << "\n\n";
305
0
}
306
307
void cmLocalNinjaGenerator::WriteNinjaConfigurationVariable(
308
  std::ostream& os, std::string const& config)
309
0
{
310
0
  cmGlobalNinjaGenerator::WriteVariable(
311
0
    os, "CONFIGURATION", config,
312
0
    "Set configuration variable for custom commands.");
313
0
}
314
315
void cmLocalNinjaGenerator::WritePools(std::ostream& os)
316
0
{
317
0
  cmGlobalNinjaGenerator::WriteDivider(os);
318
319
0
  cmValue jobpools =
320
0
    this->GetCMakeInstance()->GetState()->GetGlobalProperty("JOB_POOLS");
321
0
  if (!jobpools) {
322
0
    jobpools = this->GetMakefile()->GetDefinition("CMAKE_JOB_POOLS");
323
0
  }
324
0
  if (jobpools) {
325
0
    cmGlobalNinjaGenerator::WriteComment(
326
0
      os, "Pools defined by global property JOB_POOLS");
327
0
    cmList pools{ *jobpools };
328
0
    for (std::string const& pool : pools) {
329
0
      std::string::size_type const eq = pool.find('=');
330
0
      unsigned int jobs;
331
0
      if (eq != std::string::npos &&
332
0
          sscanf(pool.c_str() + eq, "=%u", &jobs) == 1) {
333
0
        os << "pool " << pool.substr(0, eq) << "\n  depth = " << jobs
334
0
           << "\n\n";
335
0
      } else {
336
0
        cmSystemTools::Error("Invalid pool defined by property 'JOB_POOLS': " +
337
0
                             pool);
338
0
      }
339
0
    }
340
0
  }
341
0
}
342
343
void cmLocalNinjaGenerator::WriteNinjaFilesInclusionConfig(std::ostream& os)
344
0
{
345
0
  cmGlobalNinjaGenerator::WriteDivider(os);
346
0
  os << "# Include auxiliary files.\n\n";
347
0
  cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
348
0
  std::string const ninjaCommonFile =
349
0
    ng->NinjaOutputPath(cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE);
350
0
  std::string const commonFilePath = ng->EncodePath(ninjaCommonFile);
351
0
  cmGlobalNinjaGenerator::WriteInclude(os, commonFilePath,
352
0
                                       "Include common file.");
353
0
  os << '\n';
354
0
}
355
356
void cmLocalNinjaGenerator::WriteNinjaFilesInclusionCommon(std::ostream& os)
357
0
{
358
0
  cmGlobalNinjaGenerator::WriteDivider(os);
359
0
  os << "# Include auxiliary files.\n\n";
360
0
  cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
361
0
  std::string const ninjaRulesFile =
362
0
    ng->NinjaOutputPath(cmGlobalNinjaGenerator::NINJA_RULES_FILE);
363
0
  std::string const rulesFilePath = ng->EncodePath(ninjaRulesFile);
364
0
  cmGlobalNinjaGenerator::WriteInclude(os, rulesFilePath,
365
0
                                       "Include rules file.");
366
0
  os << '\n';
367
0
}
368
369
void cmLocalNinjaGenerator::WriteNinjaWorkDir(std::ostream& os)
370
0
{
371
0
  cmGlobalNinjaGenerator::WriteDivider(os);
372
0
  cmGlobalNinjaGenerator::WriteComment(
373
0
    os, "Logical path to working directory; prefix for absolute paths.");
374
0
  cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
375
0
  std::string ninja_workdir = this->GetBinaryDirectory();
376
0
  ng->StripNinjaOutputPathPrefixAsSuffix(ninja_workdir); // Also appends '/'.
377
0
  os << "cmake_ninja_workdir = " << ng->EncodePath(ninja_workdir) << "\n";
378
0
}
379
380
void cmLocalNinjaGenerator::WriteProcessedMakefile(std::ostream& os)
381
0
{
382
0
  cmGlobalNinjaGenerator::WriteDivider(os);
383
0
  os << "# Write statements declared in CMakeLists.txt:\n"
384
0
        "# "
385
0
     << this->Makefile->GetSafeDefinition("CMAKE_CURRENT_LIST_FILE") << '\n';
386
0
  if (this->IsRootMakefile()) {
387
0
    os << "# Which is the root file.\n";
388
0
  }
389
0
  cmGlobalNinjaGenerator::WriteDivider(os);
390
0
  os << '\n';
391
0
}
392
393
void cmLocalNinjaGenerator::AppendTargetOutputs(cmGeneratorTarget* target,
394
                                                cmNinjaDeps& outputs,
395
                                                std::string const& config)
396
0
{
397
0
  this->GetGlobalNinjaGenerator()->AppendTargetOutputs(target, outputs, config,
398
0
                                                       DependOnTargetArtifact);
399
0
}
400
401
void cmLocalNinjaGenerator::AppendTargetDepends(cmGeneratorTarget* target,
402
                                                cmNinjaDeps& outputs,
403
                                                std::string const& config,
404
                                                std::string const& fileConfig,
405
                                                cmNinjaTargetDepends depends)
406
0
{
407
0
  this->GetGlobalNinjaGenerator()->AppendTargetDepends(target, outputs, config,
408
0
                                                       fileConfig, depends);
409
0
}
410
411
void cmLocalNinjaGenerator::AppendCustomCommandDeps(
412
  cmCustomCommandGenerator const& ccg, cmNinjaDeps& ninjaDeps,
413
  std::string const& config)
414
0
{
415
0
  for (std::string const& i : ccg.GetDepends()) {
416
0
    std::string dep;
417
0
    if (this->GetRealDependency(i, config, dep,
418
0
                                ccg.GetCC().GetCMP0212Status())) {
419
0
      ninjaDeps.push_back(
420
0
        this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(dep));
421
0
    }
422
0
  }
423
0
}
424
425
std::string cmLocalNinjaGenerator::WriteCommandScript(
426
  std::vector<std::string> const& cmdLines, std::string const& outputConfig,
427
  std::string const& commandConfig, std::string const& customStep,
428
  cmGeneratorTarget const* target) const
429
0
{
430
0
  std::string scriptPath;
431
0
  if (target) {
432
0
    scriptPath = target->GetSupportDirectory();
433
0
  } else {
434
0
    scriptPath = cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles");
435
0
  }
436
0
  scriptPath += this->GetGlobalNinjaGenerator()->ConfigDirectory(outputConfig);
437
0
  cmSystemTools::MakeDirectory(scriptPath);
438
0
  scriptPath += '/';
439
0
  scriptPath += customStep;
440
0
  if (this->GlobalGenerator->IsMultiConfig()) {
441
0
    scriptPath += cmStrCat('-', commandConfig);
442
0
  }
443
#ifdef _WIN32
444
  scriptPath += ".bat";
445
#else
446
0
  scriptPath += ".sh";
447
0
#endif
448
449
0
  cmsys::ofstream script(scriptPath.c_str());
450
451
#ifdef _WIN32
452
  script << "@echo off\n";
453
  int line = 1;
454
#else
455
0
  script << "set -e\n\n";
456
0
#endif
457
458
0
  for (auto const& i : cmdLines) {
459
0
    std::string cmd = i;
460
    // The command line was built assuming it would be written to
461
    // the build.ninja file, so it uses '$$' for '$'.  Remove this
462
    // for the raw shell script.
463
0
    cmSystemTools::ReplaceString(cmd, "$$", "$");
464
#ifdef _WIN32
465
    script << cmd << " || (set FAIL_LINE=" << ++line << "& goto :ABORT)"
466
           << '\n';
467
#else
468
0
    script << cmd << '\n';
469
0
#endif
470
0
  }
471
472
#ifdef _WIN32
473
  script << "goto :EOF\n\n"
474
            ":ABORT\n"
475
            "set ERROR_CODE=%ERRORLEVEL%\n"
476
            "echo Batch file failed at line %FAIL_LINE% "
477
            "with errorcode %ERRORLEVEL%\n"
478
            "exit /b %ERROR_CODE%";
479
#endif
480
481
0
  return scriptPath;
482
0
}
483
484
#ifdef _WIN32
485
namespace {
486
bool RuleNeedsCMD(std::string const& cmd)
487
{
488
  std::vector<std::string> args;
489
  cmSystemTools::ParseWindowsCommandLine(cmd.c_str(), args);
490
  auto it = std::find_if(args.cbegin(), args.cend(),
491
                         [](std::string const& arg) -> bool {
492
                           // FIXME: Detect more windows shell operators.
493
                           return cmHasPrefix(arg, '>');
494
                         });
495
  return it != args.cend();
496
}
497
}
498
#endif
499
500
std::string cmLocalNinjaGenerator::BuildCommandLine(
501
  std::vector<std::string> const& cmdLines, std::string const& outputConfig,
502
  std::string const& commandConfig, std::string const& customStep,
503
  cmGeneratorTarget const* target) const
504
0
{
505
  // If we have no commands but we need to build a command anyway, use noop.
506
  // This happens when building a POST_BUILD value for link targets that
507
  // don't use POST_BUILD.
508
0
  if (cmdLines.empty()) {
509
0
    return cmGlobalNinjaGenerator::SHELL_NOOP;
510
0
  }
511
512
  // If this is a custom step then we will have no '$VAR' ninja placeholders.
513
  // This means we can deal with long command sequences by writing to a script.
514
  // Do this if the command lines are on the scale of the OS limit.
515
0
  if (!customStep.empty()) {
516
0
    size_t cmdLinesTotal = 0;
517
0
    for (std::string const& cmd : cmdLines) {
518
0
      cmdLinesTotal += cmd.length() + 6;
519
0
    }
520
0
    if (cmdLinesTotal > cmSystemTools::CalculateCommandLineLengthLimit() / 2) {
521
0
      std::string const scriptPath = this->WriteCommandScript(
522
0
        cmdLines, outputConfig, commandConfig, customStep, target);
523
0
      std::string cmd
524
0
#ifndef _WIN32
525
0
        = "/bin/sh "
526
0
#endif
527
0
        ;
528
0
      cmd += this->ConvertToOutputFormat(
529
0
        this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(scriptPath),
530
0
        cmOutputConverter::SHELL);
531
532
      // Add an unused argument based on script content so that Ninja
533
      // knows when the command lines change.
534
0
      cmd += " ";
535
0
      cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
536
0
      cmd += hash.HashFile(scriptPath).substr(0, 16);
537
0
      return cmd;
538
0
    }
539
0
  }
540
541
0
  std::ostringstream cmd;
542
#ifdef _WIN32
543
  cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator();
544
  bool const needCMD =
545
    cmdLines.size() > 1 || (customStep.empty() && RuleNeedsCMD(cmdLines[0]));
546
  for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li) {
547
    if (li != cmdLines.begin()) {
548
      cmd << " && ";
549
    } else if (needCMD) {
550
      cmd << gg->GetComspec() << " /C \"";
551
    }
552
    // Put current cmdLine in brackets if it contains "||" because it has
553
    // higher precedence than "&&" in cmd.exe
554
    if (li->find("||") != std::string::npos) {
555
      cmd << "( " << *li << " )";
556
    } else {
557
      cmd << *li;
558
    }
559
  }
560
  if (needCMD) {
561
    cmd << "\"";
562
  }
563
#else
564
0
  for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li) {
565
0
    if (li != cmdLines.begin()) {
566
0
      cmd << " && ";
567
0
    }
568
0
    cmd << *li;
569
0
  }
570
0
#endif
571
0
  return cmd.str();
572
0
}
573
574
void cmLocalNinjaGenerator::AppendCustomCommandLines(
575
  cmCustomCommandGenerator const& ccg, std::vector<std::string>& cmdLines)
576
0
{
577
0
  auto* gg = this->GetGlobalNinjaGenerator();
578
579
0
  if (ccg.GetNumberOfCommands() > 0) {
580
0
    std::string wd = ccg.GetWorkingDirectory();
581
0
    if (wd.empty()) {
582
0
      wd = this->GetCurrentBinaryDirectory();
583
0
    }
584
585
0
    std::ostringstream cdCmd;
586
#ifdef _WIN32
587
    std::string cdStr = "cd /D ";
588
#else
589
0
    std::string cdStr = "cd ";
590
0
#endif
591
0
    cdCmd << cdStr
592
0
          << this->ConvertToOutputFormat(wd, cmOutputConverter::SHELL);
593
0
    cmdLines.push_back(cdCmd.str());
594
0
  }
595
596
0
  std::string launcher = this->MakeCustomLauncher(ccg);
597
598
0
  for (unsigned i = 0; i != ccg.GetNumberOfCommands(); ++i) {
599
0
    std::string c = ccg.GetCommand(i);
600
0
    if (c.empty()) {
601
0
      continue;
602
0
    }
603
0
    cmdLines.push_back(launcher +
604
0
                       this->ConvertToOutputFormat(
605
0
                         c,
606
0
                         gg->IsMultiConfig() ? cmOutputConverter::NINJAMULTI
607
0
                                             : cmOutputConverter::SHELL));
608
609
0
    std::string& cmd = cmdLines.back();
610
0
    ccg.AppendArguments(i, cmd);
611
0
  }
612
0
}
613
614
void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
615
  cmCustomCommand const* cc, std::set<cmGeneratorTarget*> const& targets,
616
  std::string const& fileConfig)
617
0
{
618
0
  cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
619
0
  if (gg->SeenCustomCommand(cc, fileConfig)) {
620
0
    return;
621
0
  }
622
623
0
  auto ccgs = this->MakeCustomCommandGenerators(*cc, fileConfig);
624
0
  for (cmCustomCommandGenerator const& ccg : ccgs) {
625
0
    if (ccg.GetOutputs().empty() && ccg.GetByproducts().empty()) {
626
      // Generator expressions evaluate to no output for this config.
627
0
      continue;
628
0
    }
629
630
0
    std::unordered_set<std::string> orderOnlyDeps;
631
632
0
    if (!cc->GetDependsExplicitOnly()) {
633
      // A custom command may appear on multiple targets.  However, some build
634
      // systems exist where the target dependencies on some of the targets are
635
      // overspecified, leading to a dependency cycle.  If we assume all target
636
      // dependencies are a superset of the true target dependencies for this
637
      // custom command, we can take the set intersection of all target
638
      // dependencies to obtain a correct dependency list.
639
      //
640
      // FIXME: This won't work in certain obscure scenarios involving indirect
641
      // dependencies.
642
0
      auto j = targets.begin();
643
0
      assert(j != targets.end());
644
0
      this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
645
0
        *j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
646
0
      ++j;
647
648
0
      for (; j != targets.end(); ++j) {
649
0
        std::unordered_set<std::string> jDeps;
650
0
        this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
651
0
          *j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
652
0
        cm::erase_if(orderOnlyDeps, [&jDeps](std::string const& dep) {
653
0
          return jDeps.find(dep) == jDeps.end();
654
0
        });
655
0
      }
656
0
    }
657
658
0
    std::vector<std::string> const& outputs = ccg.GetOutputs();
659
0
    std::vector<std::string> const& byproducts = ccg.GetByproducts();
660
661
0
    bool symbolic = false;
662
0
    for (std::string const& output : outputs) {
663
0
      if (cmSourceFile* sf = this->Makefile->GetSource(output)) {
664
0
        if (sf->GetPropertyAsBool("SYMBOLIC")) {
665
0
          symbolic = true;
666
0
          break;
667
0
        }
668
0
      }
669
0
    }
670
671
0
    cmGlobalNinjaGenerator::CCOutputs ccOutputs(gg);
672
0
    ccOutputs.Add(outputs);
673
0
    ccOutputs.Add(byproducts);
674
675
0
    std::string mainOutput = ccOutputs.ExplicitOuts[0];
676
677
0
    cmNinjaDeps ninjaDeps;
678
0
    this->AppendCustomCommandDeps(ccg, ninjaDeps, fileConfig);
679
680
0
    std::vector<std::string> cmdLines;
681
0
    this->AppendCustomCommandLines(ccg, cmdLines);
682
683
0
    cmNinjaDeps sortedOrderOnlyDeps(orderOnlyDeps.begin(),
684
0
                                    orderOnlyDeps.end());
685
0
    std::sort(sortedOrderOnlyDeps.begin(), sortedOrderOnlyDeps.end());
686
687
0
    if (cmdLines.empty()) {
688
0
      cmNinjaBuild build("phony");
689
0
      build.Comment = cmStrCat("Phony custom command for ", mainOutput);
690
0
      build.Outputs = std::move(ccOutputs.ExplicitOuts);
691
0
      build.WorkDirOuts = std::move(ccOutputs.WorkDirOuts);
692
0
      build.ExplicitDeps = std::move(ninjaDeps);
693
0
      build.OrderOnlyDeps = std::move(sortedOrderOnlyDeps);
694
0
      gg->WriteBuild(this->GetImplFileStream(fileConfig), build);
695
0
    } else {
696
0
      std::string customStep = cmSystemTools::GetFilenameName(mainOutput);
697
0
      if (this->GlobalGenerator->IsMultiConfig()) {
698
0
        customStep += '-';
699
0
        customStep += fileConfig;
700
0
        customStep += '-';
701
0
        customStep += ccg.GetOutputConfig();
702
0
      }
703
      // Hash full path to make unique.
704
0
      customStep += '-';
705
0
      cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
706
0
      customStep += hash.HashString(mainOutput).substr(0, 7);
707
708
0
      std::string depfile = ccg.GetDepfile();
709
0
      if (!depfile.empty()) {
710
0
        switch (cc->GetCMP0116Status()) {
711
0
          case cmPolicies::WARN:
712
0
            if (this->GetCurrentBinaryDirectory() !=
713
0
                  this->GetBinaryDirectory() ||
714
0
                this->Makefile->PolicyOptionalWarningEnabled(
715
0
                  "CMAKE_POLICY_WARNING_CMP0116")) {
716
0
              this->GetCMakeInstance()->IssueDiagnostic(
717
0
                cmDiagnostics::CMD_AUTHOR,
718
0
                cmPolicies::GetPolicyWarning(cmPolicies::CMP0116),
719
0
                cc->GetBacktrace());
720
0
            }
721
0
            CM_FALLTHROUGH;
722
0
          case cmPolicies::OLD:
723
0
            break;
724
0
          case cmPolicies::NEW:
725
0
            depfile = ccg.GetInternalDepfile();
726
0
            break;
727
0
        }
728
0
      }
729
730
0
      std::string comment = cmStrCat("Custom command for ", mainOutput);
731
0
      gg->WriteCustomCommandBuild(
732
0
        this->BuildCommandLine(cmdLines, ccg.GetOutputConfig(), fileConfig,
733
0
                               customStep),
734
0
        this->ConstructComment(ccg), comment, depfile, cc->GetJobPool(),
735
0
        cc->GetUsesTerminal(),
736
0
        /*restat*/ !symbolic || !byproducts.empty(), fileConfig,
737
0
        std::move(ccOutputs), std::move(ninjaDeps),
738
0
        std::move(sortedOrderOnlyDeps));
739
0
    }
740
0
  }
741
0
}
742
743
bool cmLocalNinjaGenerator::HasUniqueByproducts(
744
  std::vector<std::string> const& byproducts, cmListFileBacktrace const& bt)
745
0
{
746
0
  cmGeneratorExpression ge(*this->GetCMakeInstance(), bt);
747
0
  for (std::string const& p : byproducts) {
748
0
    if (cmGeneratorExpression::Find(p) == std::string::npos) {
749
0
      return false;
750
0
    }
751
0
    std::set<std::string> seen;
752
0
    std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p);
753
0
    for (std::string const& config : this->GetConfigNames()) {
754
0
      for (std::string const& b :
755
0
           this->ExpandCustomCommandOutputPaths(*cge, config)) {
756
0
        if (!seen.insert(b).second) {
757
0
          return false;
758
0
        }
759
0
      }
760
0
    }
761
0
  }
762
0
  return true;
763
0
}
764
765
namespace {
766
bool HasUniqueOutputs(std::vector<cmCustomCommandGenerator> const& ccgs)
767
0
{
768
0
  std::set<std::string> allOutputs;
769
0
  std::set<std::string> allByproducts;
770
0
  for (cmCustomCommandGenerator const& ccg : ccgs) {
771
0
    for (std::string const& output : ccg.GetOutputs()) {
772
0
      if (!allOutputs.insert(output).second) {
773
0
        return false;
774
0
      }
775
0
    }
776
0
    for (std::string const& byproduct : ccg.GetByproducts()) {
777
0
      if (!allByproducts.insert(byproduct).second) {
778
0
        return false;
779
0
      }
780
0
    }
781
0
  }
782
0
  return true;
783
0
}
784
}
785
786
std::string cmLocalNinjaGenerator::CreateUtilityOutput(
787
  std::string const& targetName, std::vector<std::string> const& byproducts,
788
  cmListFileBacktrace const& bt)
789
0
{
790
  // In Ninja Multi-Config, we can only produce cross-config utility
791
  // commands if all byproducts are per-config.
792
0
  if (!this->GetGlobalGenerator()->IsMultiConfig() ||
793
0
      !this->HasUniqueByproducts(byproducts, bt)) {
794
0
    return this->cmLocalGenerator::CreateUtilityOutput(targetName, byproducts,
795
0
                                                       bt);
796
0
  }
797
798
0
  std::string const base = cmStrCat(this->GetCurrentBinaryDirectory(),
799
0
                                    "/CMakeFiles/", targetName, '-');
800
  // The output is not actually created so mark it symbolic.
801
0
  for (std::string const& config : this->GetConfigNames()) {
802
0
    std::string const force = cmStrCat(base, config);
803
0
    if (cmSourceFile* sf = this->Makefile->GetOrCreateGeneratedSource(force)) {
804
0
      sf->SetProperty("SYMBOLIC", "1");
805
0
    } else {
806
0
      cmSystemTools::Error("Could not get source file entry for " + force);
807
0
    }
808
0
  }
809
0
  this->GetGlobalNinjaGenerator()->AddPerConfigUtilityTarget(targetName);
810
0
  return cmStrCat(base, "$<CONFIG>"_s);
811
0
}
812
813
std::vector<cmCustomCommandGenerator>
814
cmLocalNinjaGenerator::MakeCustomCommandGenerators(
815
  cmCustomCommand const& cc, std::string const& fileConfig)
816
0
{
817
0
  cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator();
818
819
0
  bool transformDepfile = false;
820
0
  switch (cc.GetCMP0116Status()) {
821
0
    case cmPolicies::WARN:
822
0
      CM_FALLTHROUGH;
823
0
    case cmPolicies::OLD:
824
0
      break;
825
0
    case cmPolicies::NEW:
826
0
      transformDepfile = true;
827
0
      break;
828
0
  }
829
830
  // Start with the build graph's configuration.
831
0
  std::vector<cmCustomCommandGenerator> ccgs;
832
0
  ccgs.emplace_back(cc, fileConfig, this, transformDepfile);
833
834
  // Consider adding cross configurations.
835
0
  if (!gg->EnableCrossConfigBuild()) {
836
0
    return ccgs;
837
0
  }
838
839
  // Outputs and byproducts must be expressed using generator expressions.
840
0
  for (std::string const& output : cc.GetOutputs()) {
841
0
    if (cmGeneratorExpression::Find(output) == std::string::npos) {
842
0
      return ccgs;
843
0
    }
844
0
  }
845
0
  for (std::string const& byproduct : cc.GetByproducts()) {
846
0
    if (cmGeneratorExpression::Find(byproduct) == std::string::npos) {
847
0
      return ccgs;
848
0
    }
849
0
  }
850
851
  // Tentatively add the other cross configurations.
852
0
  for (std::string const& config : gg->GetCrossConfigs(fileConfig)) {
853
0
    if (fileConfig != config) {
854
0
      ccgs.emplace_back(cc, fileConfig, this, transformDepfile, config);
855
0
    }
856
0
  }
857
858
  // If outputs and byproducts are not unique to each configuration,
859
  // drop the cross configurations.
860
0
  if (!HasUniqueOutputs(ccgs)) {
861
0
    ccgs.erase(ccgs.begin() + 1, ccgs.end());
862
0
  }
863
864
0
  return ccgs;
865
0
}
866
867
void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc,
868
                                                   cmGeneratorTarget* target)
869
0
{
870
0
  CustomCommandTargetMap::value_type v(cc, std::set<cmGeneratorTarget*>());
871
0
  std::pair<CustomCommandTargetMap::iterator, bool> ins =
872
0
    this->CustomCommandTargets.insert(v);
873
0
  if (ins.second) {
874
0
    this->CustomCommands.push_back(cc);
875
0
  }
876
0
  ins.first->second.insert(target);
877
0
}
878
879
void cmLocalNinjaGenerator::WriteCustomCommandBuildStatements(
880
  std::string const& fileConfig)
881
0
{
882
0
  for (cmCustomCommand const* customCommand : this->CustomCommands) {
883
0
    auto i = this->CustomCommandTargets.find(customCommand);
884
0
    assert(i != this->CustomCommandTargets.end());
885
886
0
    this->WriteCustomCommandBuildStatement(i->first, i->second, fileConfig);
887
0
  }
888
0
}
889
890
std::string cmLocalNinjaGenerator::MakeCustomLauncher(
891
  cmCustomCommandGenerator const& ccg)
892
0
{
893
0
  cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM");
894
895
0
  if (!cmNonempty(property_value)) {
896
0
    return std::string();
897
0
  }
898
899
  // Expand rule variables referenced in the given launcher command.
900
0
  cmRulePlaceholderExpander::RuleVariables vars;
901
902
0
  std::string output;
903
0
  std::vector<std::string> const& outputs = ccg.GetOutputs();
904
0
  for (size_t i = 0; i < outputs.size(); ++i) {
905
0
    output = cmStrCat(output,
906
0
                      this->ConvertToOutputFormat(
907
0
                        ccg.GetWorkingDirectory().empty()
908
0
                          ? this->MaybeRelativeToCurBinDir(outputs[i])
909
0
                          : outputs[i],
910
0
                        cmOutputConverter::SHELL));
911
0
    if (i != outputs.size() - 1) {
912
0
      output = cmStrCat(output, ',');
913
0
    }
914
0
  }
915
0
  vars.Output = output.c_str();
916
0
  vars.Role = ccg.GetCC().GetRole().c_str();
917
0
  vars.CMTargetName = ccg.GetCC().GetTarget().c_str();
918
0
  vars.Config = ccg.GetOutputConfig().c_str();
919
920
0
  auto rulePlaceholderExpander = this->CreateRulePlaceholderExpander();
921
922
0
  std::string launcher = *property_value;
923
0
  rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars);
924
0
  if (!launcher.empty()) {
925
0
    launcher += " ";
926
0
  }
927
928
0
  return launcher;
929
0
}
930
931
void cmLocalNinjaGenerator::AdditionalCleanFiles(std::string const& config)
932
0
{
933
0
  if (cmValue prop_value =
934
0
        this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) {
935
0
    cmList cleanFiles{ cmGeneratorExpression::Evaluate(*prop_value, this,
936
0
                                                       config) };
937
0
    std::string const& binaryDir = this->GetCurrentBinaryDirectory();
938
0
    cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
939
0
    for (auto const& cleanFile : cleanFiles) {
940
      // Support relative paths
941
0
      gg->AddAdditionalCleanFile(
942
0
        cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config);
943
0
    }
944
0
  }
945
0
}