Coverage Report

Created: 2026-02-09 06:05

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 "cmGeneratedFileStream.h"
23
#include "cmGeneratorExpression.h"
24
#include "cmGeneratorTarget.h"
25
#include "cmGlobalGenerator.h"
26
#include "cmGlobalNinjaGenerator.h"
27
#include "cmList.h"
28
#include "cmListFileCache.h"
29
#include "cmLocalGenerator.h"
30
#include "cmMakefile.h"
31
#include "cmMessageType.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
      ninjaDeps.push_back(
419
0
        this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(dep));
420
0
    }
421
0
  }
422
0
}
423
424
std::string cmLocalNinjaGenerator::WriteCommandScript(
425
  std::vector<std::string> const& cmdLines, std::string const& outputConfig,
426
  std::string const& commandConfig, std::string const& customStep,
427
  cmGeneratorTarget const* target) const
428
0
{
429
0
  std::string scriptPath;
430
0
  if (target) {
431
0
    scriptPath = target->GetSupportDirectory();
432
0
  } else {
433
0
    scriptPath = cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles");
434
0
  }
435
0
  scriptPath += this->GetGlobalNinjaGenerator()->ConfigDirectory(outputConfig);
436
0
  cmSystemTools::MakeDirectory(scriptPath);
437
0
  scriptPath += '/';
438
0
  scriptPath += customStep;
439
0
  if (this->GlobalGenerator->IsMultiConfig()) {
440
0
    scriptPath += cmStrCat('-', commandConfig);
441
0
  }
442
#ifdef _WIN32
443
  scriptPath += ".bat";
444
#else
445
0
  scriptPath += ".sh";
446
0
#endif
447
448
0
  cmsys::ofstream script(scriptPath.c_str());
449
450
#ifdef _WIN32
451
  script << "@echo off\n";
452
  int line = 1;
453
#else
454
0
  script << "set -e\n\n";
455
0
#endif
456
457
0
  for (auto const& i : cmdLines) {
458
0
    std::string cmd = i;
459
    // The command line was built assuming it would be written to
460
    // the build.ninja file, so it uses '$$' for '$'.  Remove this
461
    // for the raw shell script.
462
0
    cmSystemTools::ReplaceString(cmd, "$$", "$");
463
#ifdef _WIN32
464
    script << cmd << " || (set FAIL_LINE=" << ++line << "& goto :ABORT)"
465
           << '\n';
466
#else
467
0
    script << cmd << '\n';
468
0
#endif
469
0
  }
470
471
#ifdef _WIN32
472
  script << "goto :EOF\n\n"
473
            ":ABORT\n"
474
            "set ERROR_CODE=%ERRORLEVEL%\n"
475
            "echo Batch file failed at line %FAIL_LINE% "
476
            "with errorcode %ERRORLEVEL%\n"
477
            "exit /b %ERROR_CODE%";
478
#endif
479
480
0
  return scriptPath;
481
0
}
482
483
#ifdef _WIN32
484
namespace {
485
bool RuleNeedsCMD(std::string const& cmd)
486
{
487
  std::vector<std::string> args;
488
  cmSystemTools::ParseWindowsCommandLine(cmd.c_str(), args);
489
  auto it = std::find_if(args.cbegin(), args.cend(),
490
                         [](std::string const& arg) -> bool {
491
                           // FIXME: Detect more windows shell operators.
492
                           return cmHasPrefix(arg, '>');
493
                         });
494
  return it != args.cend();
495
}
496
}
497
#endif
498
499
std::string cmLocalNinjaGenerator::BuildCommandLine(
500
  std::vector<std::string> const& cmdLines, std::string const& outputConfig,
501
  std::string const& commandConfig, std::string const& customStep,
502
  cmGeneratorTarget const* target) const
503
0
{
504
  // If we have no commands but we need to build a command anyway, use noop.
505
  // This happens when building a POST_BUILD value for link targets that
506
  // don't use POST_BUILD.
507
0
  if (cmdLines.empty()) {
508
0
    return cmGlobalNinjaGenerator::SHELL_NOOP;
509
0
  }
510
511
  // If this is a custom step then we will have no '$VAR' ninja placeholders.
512
  // This means we can deal with long command sequences by writing to a script.
513
  // Do this if the command lines are on the scale of the OS limit.
514
0
  if (!customStep.empty()) {
515
0
    size_t cmdLinesTotal = 0;
516
0
    for (std::string const& cmd : cmdLines) {
517
0
      cmdLinesTotal += cmd.length() + 6;
518
0
    }
519
0
    if (cmdLinesTotal > cmSystemTools::CalculateCommandLineLengthLimit() / 2) {
520
0
      std::string const scriptPath = this->WriteCommandScript(
521
0
        cmdLines, outputConfig, commandConfig, customStep, target);
522
0
      std::string cmd
523
0
#ifndef _WIN32
524
0
        = "/bin/sh "
525
0
#endif
526
0
        ;
527
0
      cmd += this->ConvertToOutputFormat(
528
0
        this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(scriptPath),
529
0
        cmOutputConverter::SHELL);
530
531
      // Add an unused argument based on script content so that Ninja
532
      // knows when the command lines change.
533
0
      cmd += " ";
534
0
      cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
535
0
      cmd += hash.HashFile(scriptPath).substr(0, 16);
536
0
      return cmd;
537
0
    }
538
0
  }
539
540
0
  std::ostringstream cmd;
541
#ifdef _WIN32
542
  cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator();
543
  bool const needCMD =
544
    cmdLines.size() > 1 || (customStep.empty() && RuleNeedsCMD(cmdLines[0]));
545
  for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li) {
546
    if (li != cmdLines.begin()) {
547
      cmd << " && ";
548
    } else if (needCMD) {
549
      cmd << gg->GetComspec() << " /C \"";
550
    }
551
    // Put current cmdLine in brackets if it contains "||" because it has
552
    // higher precedence than "&&" in cmd.exe
553
    if (li->find("||") != std::string::npos) {
554
      cmd << "( " << *li << " )";
555
    } else {
556
      cmd << *li;
557
    }
558
  }
559
  if (needCMD) {
560
    cmd << "\"";
561
  }
562
#else
563
0
  for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li) {
564
0
    if (li != cmdLines.begin()) {
565
0
      cmd << " && ";
566
0
    }
567
0
    cmd << *li;
568
0
  }
569
0
#endif
570
0
  return cmd.str();
571
0
}
572
573
void cmLocalNinjaGenerator::AppendCustomCommandLines(
574
  cmCustomCommandGenerator const& ccg, std::vector<std::string>& cmdLines)
575
0
{
576
0
  auto* gg = this->GetGlobalNinjaGenerator();
577
578
0
  if (ccg.GetNumberOfCommands() > 0) {
579
0
    std::string wd = ccg.GetWorkingDirectory();
580
0
    if (wd.empty()) {
581
0
      wd = this->GetCurrentBinaryDirectory();
582
0
    }
583
584
0
    std::ostringstream cdCmd;
585
#ifdef _WIN32
586
    std::string cdStr = "cd /D ";
587
#else
588
0
    std::string cdStr = "cd ";
589
0
#endif
590
0
    cdCmd << cdStr
591
0
          << this->ConvertToOutputFormat(wd, cmOutputConverter::SHELL);
592
0
    cmdLines.push_back(cdCmd.str());
593
0
  }
594
595
0
  std::string launcher = this->MakeCustomLauncher(ccg);
596
597
0
  for (unsigned i = 0; i != ccg.GetNumberOfCommands(); ++i) {
598
0
    std::string c = ccg.GetCommand(i);
599
0
    if (c.empty()) {
600
0
      continue;
601
0
    }
602
0
    cmdLines.push_back(launcher +
603
0
                       this->ConvertToOutputFormat(
604
0
                         c,
605
0
                         gg->IsMultiConfig() ? cmOutputConverter::NINJAMULTI
606
0
                                             : cmOutputConverter::SHELL));
607
608
0
    std::string& cmd = cmdLines.back();
609
0
    ccg.AppendArguments(i, cmd);
610
0
  }
611
0
}
612
613
void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
614
  cmCustomCommand const* cc, std::set<cmGeneratorTarget*> const& targets,
615
  std::string const& fileConfig)
616
0
{
617
0
  cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
618
0
  if (gg->SeenCustomCommand(cc, fileConfig)) {
619
0
    return;
620
0
  }
621
622
0
  auto ccgs = this->MakeCustomCommandGenerators(*cc, fileConfig);
623
0
  for (cmCustomCommandGenerator const& ccg : ccgs) {
624
0
    if (ccg.GetOutputs().empty() && ccg.GetByproducts().empty()) {
625
      // Generator expressions evaluate to no output for this config.
626
0
      continue;
627
0
    }
628
629
0
    std::unordered_set<std::string> orderOnlyDeps;
630
631
0
    if (!cc->GetDependsExplicitOnly()) {
632
      // A custom command may appear on multiple targets.  However, some build
633
      // systems exist where the target dependencies on some of the targets are
634
      // overspecified, leading to a dependency cycle.  If we assume all target
635
      // dependencies are a superset of the true target dependencies for this
636
      // custom command, we can take the set intersection of all target
637
      // dependencies to obtain a correct dependency list.
638
      //
639
      // FIXME: This won't work in certain obscure scenarios involving indirect
640
      // dependencies.
641
0
      auto j = targets.begin();
642
0
      assert(j != targets.end());
643
0
      this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
644
0
        *j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
645
0
      ++j;
646
647
0
      for (; j != targets.end(); ++j) {
648
0
        std::unordered_set<std::string> jDeps;
649
0
        this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
650
0
          *j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
651
0
        cm::erase_if(orderOnlyDeps, [&jDeps](std::string const& dep) {
652
0
          return jDeps.find(dep) == jDeps.end();
653
0
        });
654
0
      }
655
0
    }
656
657
0
    std::vector<std::string> const& outputs = ccg.GetOutputs();
658
0
    std::vector<std::string> const& byproducts = ccg.GetByproducts();
659
660
0
    bool symbolic = false;
661
0
    for (std::string const& output : outputs) {
662
0
      if (cmSourceFile* sf = this->Makefile->GetSource(output)) {
663
0
        if (sf->GetPropertyAsBool("SYMBOLIC")) {
664
0
          symbolic = true;
665
0
          break;
666
0
        }
667
0
      }
668
0
    }
669
670
0
    cmGlobalNinjaGenerator::CCOutputs ccOutputs(gg);
671
0
    ccOutputs.Add(outputs);
672
0
    ccOutputs.Add(byproducts);
673
674
0
    std::string mainOutput = ccOutputs.ExplicitOuts[0];
675
676
0
    cmNinjaDeps ninjaDeps;
677
0
    this->AppendCustomCommandDeps(ccg, ninjaDeps, fileConfig);
678
679
0
    std::vector<std::string> cmdLines;
680
0
    this->AppendCustomCommandLines(ccg, cmdLines);
681
682
0
    cmNinjaDeps sortedOrderOnlyDeps(orderOnlyDeps.begin(),
683
0
                                    orderOnlyDeps.end());
684
0
    std::sort(sortedOrderOnlyDeps.begin(), sortedOrderOnlyDeps.end());
685
686
0
    if (cmdLines.empty()) {
687
0
      cmNinjaBuild build("phony");
688
0
      build.Comment = cmStrCat("Phony custom command for ", mainOutput);
689
0
      build.Outputs = std::move(ccOutputs.ExplicitOuts);
690
0
      build.WorkDirOuts = std::move(ccOutputs.WorkDirOuts);
691
0
      build.ExplicitDeps = std::move(ninjaDeps);
692
0
      build.OrderOnlyDeps = std::move(sortedOrderOnlyDeps);
693
0
      gg->WriteBuild(this->GetImplFileStream(fileConfig), build);
694
0
    } else {
695
0
      std::string customStep = cmSystemTools::GetFilenameName(mainOutput);
696
0
      if (this->GlobalGenerator->IsMultiConfig()) {
697
0
        customStep += '-';
698
0
        customStep += fileConfig;
699
0
        customStep += '-';
700
0
        customStep += ccg.GetOutputConfig();
701
0
      }
702
      // Hash full path to make unique.
703
0
      customStep += '-';
704
0
      cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
705
0
      customStep += hash.HashString(mainOutput).substr(0, 7);
706
707
0
      std::string depfile = ccg.GetDepfile();
708
0
      if (!depfile.empty()) {
709
0
        switch (cc->GetCMP0116Status()) {
710
0
          case cmPolicies::WARN:
711
0
            if (this->GetCurrentBinaryDirectory() !=
712
0
                  this->GetBinaryDirectory() ||
713
0
                this->Makefile->PolicyOptionalWarningEnabled(
714
0
                  "CMAKE_POLICY_WARNING_CMP0116")) {
715
0
              this->GetCMakeInstance()->IssueMessage(
716
0
                MessageType::AUTHOR_WARNING,
717
0
                cmPolicies::GetPolicyWarning(cmPolicies::CMP0116),
718
0
                cc->GetBacktrace());
719
0
            }
720
0
            CM_FALLTHROUGH;
721
0
          case cmPolicies::OLD:
722
0
            break;
723
0
          case cmPolicies::NEW:
724
0
            depfile = ccg.GetInternalDepfile();
725
0
            break;
726
0
        }
727
0
      }
728
729
0
      std::string comment = cmStrCat("Custom command for ", mainOutput);
730
0
      gg->WriteCustomCommandBuild(
731
0
        this->BuildCommandLine(cmdLines, ccg.GetOutputConfig(), fileConfig,
732
0
                               customStep),
733
0
        this->ConstructComment(ccg), comment, depfile, cc->GetJobPool(),
734
0
        cc->GetUsesTerminal(),
735
0
        /*restat*/ !symbolic || !byproducts.empty(), fileConfig,
736
0
        std::move(ccOutputs), std::move(ninjaDeps),
737
0
        std::move(sortedOrderOnlyDeps));
738
0
    }
739
0
  }
740
0
}
741
742
bool cmLocalNinjaGenerator::HasUniqueByproducts(
743
  std::vector<std::string> const& byproducts, cmListFileBacktrace const& bt)
744
0
{
745
0
  cmGeneratorExpression ge(*this->GetCMakeInstance(), bt);
746
0
  for (std::string const& p : byproducts) {
747
0
    if (cmGeneratorExpression::Find(p) == std::string::npos) {
748
0
      return false;
749
0
    }
750
0
    std::set<std::string> seen;
751
0
    std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p);
752
0
    for (std::string const& config : this->GetConfigNames()) {
753
0
      for (std::string const& b :
754
0
           this->ExpandCustomCommandOutputPaths(*cge, config)) {
755
0
        if (!seen.insert(b).second) {
756
0
          return false;
757
0
        }
758
0
      }
759
0
    }
760
0
  }
761
0
  return true;
762
0
}
763
764
namespace {
765
bool HasUniqueOutputs(std::vector<cmCustomCommandGenerator> const& ccgs)
766
0
{
767
0
  std::set<std::string> allOutputs;
768
0
  std::set<std::string> allByproducts;
769
0
  for (cmCustomCommandGenerator const& ccg : ccgs) {
770
0
    for (std::string const& output : ccg.GetOutputs()) {
771
0
      if (!allOutputs.insert(output).second) {
772
0
        return false;
773
0
      }
774
0
    }
775
0
    for (std::string const& byproduct : ccg.GetByproducts()) {
776
0
      if (!allByproducts.insert(byproduct).second) {
777
0
        return false;
778
0
      }
779
0
    }
780
0
  }
781
0
  return true;
782
0
}
783
}
784
785
std::string cmLocalNinjaGenerator::CreateUtilityOutput(
786
  std::string const& targetName, std::vector<std::string> const& byproducts,
787
  cmListFileBacktrace const& bt)
788
0
{
789
  // In Ninja Multi-Config, we can only produce cross-config utility
790
  // commands if all byproducts are per-config.
791
0
  if (!this->GetGlobalGenerator()->IsMultiConfig() ||
792
0
      !this->HasUniqueByproducts(byproducts, bt)) {
793
0
    return this->cmLocalGenerator::CreateUtilityOutput(targetName, byproducts,
794
0
                                                       bt);
795
0
  }
796
797
0
  std::string const base = cmStrCat(this->GetCurrentBinaryDirectory(),
798
0
                                    "/CMakeFiles/", targetName, '-');
799
  // The output is not actually created so mark it symbolic.
800
0
  for (std::string const& config : this->GetConfigNames()) {
801
0
    std::string const force = cmStrCat(base, config);
802
0
    if (cmSourceFile* sf = this->Makefile->GetOrCreateGeneratedSource(force)) {
803
0
      sf->SetProperty("SYMBOLIC", "1");
804
0
    } else {
805
0
      cmSystemTools::Error("Could not get source file entry for " + force);
806
0
    }
807
0
  }
808
0
  this->GetGlobalNinjaGenerator()->AddPerConfigUtilityTarget(targetName);
809
0
  return cmStrCat(base, "$<CONFIG>"_s);
810
0
}
811
812
std::vector<cmCustomCommandGenerator>
813
cmLocalNinjaGenerator::MakeCustomCommandGenerators(
814
  cmCustomCommand const& cc, std::string const& fileConfig)
815
0
{
816
0
  cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator();
817
818
0
  bool transformDepfile = false;
819
0
  switch (cc.GetCMP0116Status()) {
820
0
    case cmPolicies::WARN:
821
0
      CM_FALLTHROUGH;
822
0
    case cmPolicies::OLD:
823
0
      break;
824
0
    case cmPolicies::NEW:
825
0
      transformDepfile = true;
826
0
      break;
827
0
  }
828
829
  // Start with the build graph's configuration.
830
0
  std::vector<cmCustomCommandGenerator> ccgs;
831
0
  ccgs.emplace_back(cc, fileConfig, this, transformDepfile);
832
833
  // Consider adding cross configurations.
834
0
  if (!gg->EnableCrossConfigBuild()) {
835
0
    return ccgs;
836
0
  }
837
838
  // Outputs and byproducts must be expressed using generator expressions.
839
0
  for (std::string const& output : cc.GetOutputs()) {
840
0
    if (cmGeneratorExpression::Find(output) == std::string::npos) {
841
0
      return ccgs;
842
0
    }
843
0
  }
844
0
  for (std::string const& byproduct : cc.GetByproducts()) {
845
0
    if (cmGeneratorExpression::Find(byproduct) == std::string::npos) {
846
0
      return ccgs;
847
0
    }
848
0
  }
849
850
  // Tentatively add the other cross configurations.
851
0
  for (std::string const& config : gg->GetCrossConfigs(fileConfig)) {
852
0
    if (fileConfig != config) {
853
0
      ccgs.emplace_back(cc, fileConfig, this, transformDepfile, config);
854
0
    }
855
0
  }
856
857
  // If outputs and byproducts are not unique to each configuration,
858
  // drop the cross configurations.
859
0
  if (!HasUniqueOutputs(ccgs)) {
860
0
    ccgs.erase(ccgs.begin() + 1, ccgs.end());
861
0
  }
862
863
0
  return ccgs;
864
0
}
865
866
void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc,
867
                                                   cmGeneratorTarget* target)
868
0
{
869
0
  CustomCommandTargetMap::value_type v(cc, std::set<cmGeneratorTarget*>());
870
0
  std::pair<CustomCommandTargetMap::iterator, bool> ins =
871
0
    this->CustomCommandTargets.insert(v);
872
0
  if (ins.second) {
873
0
    this->CustomCommands.push_back(cc);
874
0
  }
875
0
  ins.first->second.insert(target);
876
0
}
877
878
void cmLocalNinjaGenerator::WriteCustomCommandBuildStatements(
879
  std::string const& fileConfig)
880
0
{
881
0
  for (cmCustomCommand const* customCommand : this->CustomCommands) {
882
0
    auto i = this->CustomCommandTargets.find(customCommand);
883
0
    assert(i != this->CustomCommandTargets.end());
884
885
0
    this->WriteCustomCommandBuildStatement(i->first, i->second, fileConfig);
886
0
  }
887
0
}
888
889
std::string cmLocalNinjaGenerator::MakeCustomLauncher(
890
  cmCustomCommandGenerator const& ccg)
891
0
{
892
0
  cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM");
893
894
0
  if (!cmNonempty(property_value)) {
895
0
    return std::string();
896
0
  }
897
898
  // Expand rule variables referenced in the given launcher command.
899
0
  cmRulePlaceholderExpander::RuleVariables vars;
900
901
0
  std::string output;
902
0
  std::vector<std::string> const& outputs = ccg.GetOutputs();
903
0
  for (size_t i = 0; i < outputs.size(); ++i) {
904
0
    output = cmStrCat(output,
905
0
                      this->ConvertToOutputFormat(
906
0
                        ccg.GetWorkingDirectory().empty()
907
0
                          ? this->MaybeRelativeToCurBinDir(outputs[i])
908
0
                          : outputs[i],
909
0
                        cmOutputConverter::SHELL));
910
0
    if (i != outputs.size() - 1) {
911
0
      output = cmStrCat(output, ',');
912
0
    }
913
0
  }
914
0
  vars.Output = output.c_str();
915
0
  vars.Role = ccg.GetCC().GetRole().c_str();
916
0
  vars.CMTargetName = ccg.GetCC().GetTarget().c_str();
917
0
  vars.Config = ccg.GetOutputConfig().c_str();
918
919
0
  auto rulePlaceholderExpander = this->CreateRulePlaceholderExpander();
920
921
0
  std::string launcher = *property_value;
922
0
  rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars);
923
0
  if (!launcher.empty()) {
924
0
    launcher += " ";
925
0
  }
926
927
0
  return launcher;
928
0
}
929
930
void cmLocalNinjaGenerator::AdditionalCleanFiles(std::string const& config)
931
0
{
932
0
  if (cmValue prop_value =
933
0
        this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) {
934
0
    cmList cleanFiles{ cmGeneratorExpression::Evaluate(*prop_value, this,
935
0
                                                       config) };
936
0
    std::string const& binaryDir = this->GetCurrentBinaryDirectory();
937
0
    cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
938
0
    for (auto const& cleanFile : cleanFiles) {
939
      // Support relative paths
940
0
      gg->AddAdditionalCleanFile(
941
0
        cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config);
942
0
    }
943
0
  }
944
0
}