Coverage Report

Created: 2026-10-28 06:38

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