Coverage Report

Created: 2026-09-14 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmFastbuildTargetGenerator.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 "cmFastbuildTargetGenerator.h"
4
5
#include <algorithm>
6
#include <cstddef>
7
#include <unordered_map>
8
#include <unordered_set>
9
10
#include <cm/memory>
11
#include <cm/optional>
12
#include <cm/string_view>
13
14
#include "cmCryptoHash.h"
15
#include "cmCustomCommand.h"
16
#include "cmCustomCommandGenerator.h"
17
#include "cmCustomCommandLines.h"
18
#include "cmFastbuildNormalTargetGenerator.h"
19
#include "cmFastbuildUtilityTargetGenerator.h"
20
#include "cmGeneratorExpression.h"
21
#include "cmGeneratorTarget.h"
22
#include "cmGlobalCommonGenerator.h"
23
#include "cmGlobalFastbuildGenerator.h"
24
#include "cmList.h"
25
#include "cmListFileCache.h"
26
#include "cmLocalCommonGenerator.h"
27
#include "cmLocalFastbuildGenerator.h"
28
#include "cmLocalGenerator.h"
29
#include "cmMakefile.h"
30
#include "cmOSXBundleGenerator.h"
31
#include "cmOutputConverter.h"
32
#include "cmRulePlaceholderExpander.h"
33
#include "cmSourceFile.h"
34
#include "cmState.h"
35
#include "cmStateTypes.h"
36
#include "cmStringAlgorithms.h"
37
#include "cmSystemTools.h"
38
#include "cmTarget.h"
39
#include "cmTargetTypes.h"
40
#include "cmValue.h"
41
42
#define FASTBUILD_DOLLAR_TAG "FASTBUILD_DOLLAR_TAG"
43
44
constexpr auto FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT =
45
  "CMAKE_FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT";
46
constexpr auto FASTBUILD_DISABLE_OUTPUT_PRECHECK_EXEC =
47
  "CMAKE_FASTBUILD_DISABLE_OUTPUT_PRECHECK_EXEC";
48
49
cmFastbuildTargetGenerator* cmFastbuildTargetGenerator::New(
50
  cmGeneratorTarget* target, std::string config)
51
0
{
52
0
  switch (target->GetType()) {
53
0
    case cm::TargetType::EXECUTABLE:
54
0
    case cm::TargetType::SHARED_LIBRARY:
55
0
    case cm::TargetType::STATIC_LIBRARY:
56
0
    case cm::TargetType::MODULE_LIBRARY:
57
0
    case cm::TargetType::OBJECT_LIBRARY:
58
0
      return new cmFastbuildNormalTargetGenerator(target, std::move(config));
59
60
0
    case cm::TargetType::UTILITY:
61
0
    case cm::TargetType::GLOBAL_TARGET:
62
0
    case cm::TargetType::INTERFACE_LIBRARY:
63
0
      return new cmFastbuildUtilityTargetGenerator(target, std::move(config));
64
65
0
    default:
66
0
      return nullptr;
67
0
  }
68
0
}
69
70
cmFastbuildTargetGenerator::cmFastbuildTargetGenerator(
71
  cmGeneratorTarget* target, std::string configParam)
72
0
  : cmCommonTargetGenerator(target)
73
  , LocalGenerator(
74
0
      static_cast<cmLocalFastbuildGenerator*>(target->GetLocalGenerator()))
75
0
  , TargetDirectDependencies(
76
0
      this->GlobalCommonGenerator->GetTargetDirectDepends(GeneratorTarget))
77
0
  , Config(std::move(configParam))
78
0
{
79
0
  this->MacOSXContentGenerator =
80
0
    cm::make_unique<MacOSXContentGeneratorType>(this, Config);
81
0
}
82
83
void cmFastbuildTargetGenerator::LogMessage(std::string const& m) const
84
0
{
85
0
  this->GetGlobalGenerator()->LogMessage(m);
86
0
}
87
88
std::string cmFastbuildTargetGenerator::GetUtilityAliasFromBuildStep(
89
  FastbuildBuildStep step) const
90
0
{
91
0
  if (step == FastbuildBuildStep::PRE_BUILD) {
92
0
    return GetTargetName() + FASTBUILD_PRE_BUILD_ALIAS_POSTFIX;
93
0
  }
94
0
  if (step == FastbuildBuildStep::PRE_LINK) {
95
0
    return GetTargetName() + FASTBUILD_PRE_LINK_ALIAS_POSTFIX;
96
0
  }
97
0
  if (step == FastbuildBuildStep::POST_BUILD) {
98
0
    return GetTargetName() + FASTBUILD_POST_BUILD_ALIAS_POSTFIX;
99
0
  }
100
0
  return GetTargetName() + FASTBUILD_CUSTOM_COMMAND_ALIAS_POSTFIX;
101
0
}
102
103
void cmFastbuildTargetGenerator::MacOSXContentGeneratorType::operator()(
104
  cmSourceFile const& source, char const* pkgloc,
105
  std::string const& configName)
106
0
{
107
  // Skip OS X content when not building a Framework or Bundle.
108
0
  if (!this->Generator->GetGeneratorTarget()->IsBundleOnApple()) {
109
0
    return;
110
0
  }
111
112
  // Get the input file location.
113
0
  std::string input = source.GetFullPath();
114
0
  input = this->Generator->GetGlobalGenerator()->ConvertToFastbuildPath(input);
115
116
  // Get the output file location.
117
0
  std::string output =
118
0
    this->Generator->OSXBundleGenerator->InitMacOSXContentDirectory(
119
0
      pkgloc, configName);
120
121
0
  output += "/";
122
0
  output += cmSystemTools::GetFilenameName(input);
123
0
  output =
124
0
    this->Generator->GetGlobalGenerator()->ConvertToFastbuildPath(output);
125
126
0
  FastbuildCopyNode node;
127
0
  node.Name = "Copy_" + output;
128
0
  node.Source = std::move(input);
129
0
  if (cmSystemTools::FileIsDirectory(node.Source)) {
130
0
    node.CopyDir = true;
131
0
  }
132
0
  node.Dest = std::move(output);
133
  // Just in case if "from" is generated by some custom command.
134
  // Tested in "BundleTest" test.
135
0
  node.PreBuildDependencies =
136
0
    this->Generator->GetTargetName() + FASTBUILD_CUSTOM_COMMAND_ALIAS_POSTFIX;
137
138
0
  this->Generator->CopyNodes.emplace_back(std::move(node));
139
0
}
140
141
std::string cmFastbuildTargetGenerator::GetCustomCommandTargetName(
142
  cmCustomCommand const& cc, FastbuildBuildStep step) const
143
0
{
144
0
  std::string const extra = this->Makefile->GetCurrentBinaryDirectory();
145
0
  std::string targetName = "cc";
146
147
0
  std::string extras = extra;
148
149
  // Compute hash based on commands & args & output.
150
0
  for (cmCustomCommandLine const& commandLine : cc.GetCommandLines()) {
151
0
    extras += cmJoin(commandLine, "");
152
0
  }
153
0
  for (std::string const& output : cc.GetOutputs()) {
154
0
    extras += output;
155
0
  }
156
157
0
  extras += std::to_string(static_cast<int>(step));
158
159
0
  cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
160
0
  targetName += "-" + hash.HashString(extras).substr(0, 14);
161
162
0
  return targetName;
163
0
}
164
165
void cmFastbuildTargetGenerator::WriteScriptProlog(cmsys::ofstream& file) const
166
0
{
167
#ifdef _WIN32
168
  file << "@echo off\n";
169
#else
170
0
  file << "set -e\n\n";
171
0
#endif
172
0
}
173
void cmFastbuildTargetGenerator::WriteScriptEpilog(cmsys::ofstream& file) const
174
0
{
175
0
  (void)file;
176
#ifdef _WIN32
177
  file << "goto :EOF\n\n"
178
          ":ABORT\n"
179
          "set ERROR_CODE=%ERRORLEVEL%\n"
180
          "echo Batch file failed at line %FAIL_LINE% "
181
          "with errorcode %ERRORLEVEL%\n"
182
          "exit /b %ERROR_CODE%";
183
#endif
184
0
}
185
186
std::string cmFastbuildTargetGenerator::GetScriptWorkingDir(
187
  cmCustomCommandGenerator const& ccg) const
188
0
{
189
0
  std::string workingDirectory = ccg.GetWorkingDirectory();
190
0
  if (workingDirectory.empty()) {
191
0
    return this->LocalCommonGenerator->GetCurrentBinaryDirectory();
192
0
  }
193
0
  return workingDirectory;
194
0
}
195
196
std::string cmFastbuildTargetGenerator::GetScriptFilename(
197
  std::string const& utilityTargetName) const
198
0
{
199
0
  std::string scriptFileName = Makefile->GetCurrentBinaryDirectory();
200
0
  scriptFileName += "/CMakeFiles/";
201
0
  scriptFileName += utilityTargetName;
202
0
  scriptFileName += FASTBUILD_SCRIPT_FILE_EXTENSION;
203
0
  return scriptFileName;
204
0
}
205
206
void cmFastbuildTargetGenerator::AddCommentPrinting(
207
  std::vector<std::string>& cmdLines,
208
  cmCustomCommandGenerator const& ccg) const
209
0
{
210
0
  std::string cmakeCommand = this->GetLocalGenerator()->ConvertToOutputFormat(
211
0
    cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL);
212
0
  auto const comment = ccg.GetComment();
213
0
  if (comment) {
214
    // Comment printing should be first. Tested in
215
    // RunCMake.ExternalProject:EnvVars-build test.
216
0
    cmdLines.insert(
217
0
      cmdLines.begin(),
218
0
      cmakeCommand.append(" -E echo ")
219
0
        .append(LocalGenerator->EscapeForShell(cmGeneratorExpression::Evaluate(
220
0
          *comment, this->LocalGenerator, Config))));
221
0
  }
222
0
}
223
224
std::string cmFastbuildTargetGenerator::GetCdCommand(
225
  cmCustomCommandGenerator const& ccg) const
226
0
{
227
0
  return cmStrCat(FASTBUILD_SCRIPT_CD,
228
0
                  this->LocalGenerator->ConvertToOutputFormat(
229
0
                    GetScriptWorkingDir(ccg), cmOutputConverter::SHELL));
230
0
}
231
232
void cmFastbuildTargetGenerator::WriteCmdsToFile(
233
  cmsys::ofstream& file, std::vector<std::string> const& cmds) const
234
0
{
235
#ifdef _WIN32
236
  int line = 1;
237
  for (auto cmd : cmds) {
238
    // On Windows batch, '%' is a special character that needs to be
239
    // doubled to be escaped
240
    cmSystemTools::ReplaceString(cmd, "%", "%%");
241
    file << cmd << " || (set FAIL_LINE=" << ++line << "& goto :ABORT)" << '\n';
242
#else
243
0
  for (auto const& cmd : cmds) {
244
0
    file << cmd << '\n';
245
0
#endif
246
0
  }
247
0
}
248
249
void cmFastbuildTargetGenerator::AddOutput(cmCustomCommandGenerator const& ccg,
250
                                           FastbuildExecNode& exec)
251
0
{
252
0
  std::string dummyOutput = cmSystemTools::JoinPath(
253
0
    { LocalCommonGenerator->GetMakefile()->GetHomeOutputDirectory(),
254
0
      "/_fbuild_dummy" });
255
0
  this->GetGlobalGenerator()->AllFoldersToClean.insert(dummyOutput);
256
257
0
  dummyOutput.append("/").append(exec.Name).append(
258
0
    FASTBUILD_DUMMY_OUTPUT_EXTENSION);
259
260
0
  std::vector<std::string> const& outputs = ccg.GetOutputs();
261
0
  std::vector<std::string> const& byproducts = ccg.GetByproducts();
262
263
0
  exec.OutputsAlias.Name = exec.Name + FASTBUILD_OUTPUTS_ALIAS_POSTFIX;
264
  // If CC doesn't have any output - we should always run it.
265
  // Tested in "RunCMake.CMakePresetsBuild" test.
266
0
  bool hasAnyNonSymbolicOutput = false;
267
268
0
  bool const trackByproducts =
269
0
    this->Makefile->IsDefinitionSet(FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT);
270
271
0
  auto const isSymbolic = [this](std::string const& file) {
272
0
    cmSourceFile* sf = this->Makefile->GetSource(file);
273
0
    if (sf && sf->GetPropertyAsBool("SYMBOLIC")) {
274
0
      LogMessage("Skipping symbolic file: " + file);
275
0
      return true;
276
0
    }
277
0
    return false;
278
0
  };
279
280
0
  for (std::string const& output : outputs) {
281
    // Tested in "RunCMake.BuildDepends".
282
0
    if (isSymbolic(output)) {
283
0
      continue;
284
0
    }
285
0
    hasAnyNonSymbolicOutput = true;
286
0
    std::string const outputPath = this->ConvertToFastbuildPath(output);
287
0
    LogMessage("CC's output: " + outputPath);
288
0
    exec.OutputsAlias.PreBuildDependencies.emplace(outputPath);
289
    // Ensure output path exists. For some reason, "CMake -E touch" fails with
290
    // "cmake -E touch: failed to update "...
291
0
    cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(outputPath));
292
0
    this->GetGlobalGenerator()->AddFileToClean(outputPath);
293
0
  }
294
295
0
  exec.ByproductsAlias.Name = exec.Name + FASTBUILD_BYPRODUCTS_ALIAS_POSTFIX;
296
0
  for (std::string const& byproduct : byproducts) {
297
0
    if (trackByproducts) {
298
0
      hasAnyNonSymbolicOutput = true;
299
0
    }
300
0
    std::string const byproductPath = this->ConvertToFastbuildPath(byproduct);
301
0
    exec.ByproductsAlias.PreBuildDependencies.emplace(byproductPath);
302
0
    this->GetGlobalGenerator()->AddFileToClean(byproductPath);
303
0
  }
304
305
0
  auto const addDummyOutput = [&] {
306
    // So that the dummy file is always created.
307
0
    exec.ExecUseStdOutAsOutput = true;
308
0
    exec.ExecOutput = this->ConvertToFastbuildPath(dummyOutput);
309
0
    for (auto const& output : exec.OutputsAlias.PreBuildDependencies) {
310
0
      OutputsToReplace[output.Name] = exec.ExecOutput;
311
0
      LogMessage(cmStrCat("Adding replace from ", output.Name, " to ",
312
0
                          exec.ExecOutput));
313
0
    }
314
0
  };
315
316
  // We don't have any output that is expected to appear on disk -> run always.
317
  // Tested in "RunCMake.ExternalProject":BUILD_ALWAYS
318
0
  if (!hasAnyNonSymbolicOutput) {
319
0
    exec.ExecAlways = true;
320
0
    addDummyOutput();
321
0
    return;
322
0
  }
323
324
0
  if (!exec.OutputsAlias.PreBuildDependencies.empty()) {
325
0
    exec.ExecOutput = this->ConvertToFastbuildPath(
326
0
      exec.OutputsAlias.PreBuildDependencies.begin()->Name);
327
0
  } else {
328
0
    exec.ExecOutput = this->ConvertToFastbuildPath(
329
0
      exec.ByproductsAlias.PreBuildDependencies.begin()->Name);
330
0
  }
331
332
  // Optionally add the "deps-check" Exec if we have more than 1 OUTPUT, but
333
  // allow user to opt out.
334
0
  if (exec.OutputsAlias.PreBuildDependencies.size() > 1 &&
335
0
      !this->Makefile->IsDefinitionSet(
336
0
        FASTBUILD_DISABLE_OUTPUT_PRECHECK_EXEC)) {
337
0
    exec.NeedsDepsCheckExec = true;
338
0
  }
339
0
}
340
341
void cmFastbuildTargetGenerator::AddExecArguments(
342
  FastbuildExecNode& exec, std::string const& scriptFilename) const
343
0
{
344
0
  exec.ExecArguments =
345
0
    cmStrCat(FASTBUILD_SCRIPT_FILE_ARG,
346
0
             cmGlobalFastbuildGenerator::QuoteIfHasSpaces(scriptFilename));
347
348
0
  exec.ScriptFile = scriptFilename;
349
0
  exec.ExecExecutable =
350
0
    cmGlobalFastbuildGenerator::GetExternalShellExecutable();
351
0
}
352
353
void cmFastbuildTargetGenerator::GetDepends(
354
  cmCustomCommandGenerator const& ccg, std::string const& currentCCName,
355
  std::vector<std::string>& fileLevelDeps,
356
  std::set<FastbuildTargetDep>& targetDep) const
357
0
{
358
0
  for (auto dep : ccg.GetDepends()) {
359
0
    LogMessage("Dep: " + dep);
360
0
    auto orig = dep;
361
0
    if (this->LocalCommonGenerator->GetRealDependency(
362
0
          dep, Config, dep, ccg.GetCC().GetCMP0212Status())) {
363
0
      LogMessage("Real dep: " + dep);
364
0
      if (!dep.empty()) {
365
0
        LogMessage("Custom command real dep: " + dep);
366
0
        for (auto const& item : cmList{ cmGeneratorExpression::Evaluate(
367
0
               this->ConvertToFastbuildPath(dep), this->LocalGenerator,
368
0
               Config) }) {
369
0
          fileLevelDeps.emplace_back(item);
370
0
        }
371
0
      }
372
0
    }
373
0
    dep = this->ConvertToFastbuildPath(dep);
374
0
    LogMessage("Real dep converted: " + dep);
375
376
0
    auto const targetInfo = this->LocalGenerator->GetSourcesWithOutput(dep);
377
0
    if (targetInfo.Target) {
378
0
      LogMessage(
379
0
        cmStrCat("dep: ", dep, ", target: ", targetInfo.Target->GetName()));
380
0
      auto const& target = targetInfo.Target;
381
0
      auto const processCCs = [this, &currentCCName, &targetDep,
382
0
                               dep](std::vector<cmCustomCommand> const& ccs,
383
0
                                    FastbuildBuildStep step) {
384
0
        for (auto const& cc : ccs) {
385
0
          for (auto const& output : cc.GetOutputs()) {
386
0
            LogMessage(cmStrCat("dep: ", dep, ", post output: ",
387
0
                                this->ConvertToFastbuildPath(output)));
388
0
            if (this->ConvertToFastbuildPath(output) == dep) {
389
0
              auto ccName = this->GetCustomCommandTargetName(cc, step);
390
0
              if (ccName != currentCCName) {
391
0
                LogMessage("Additional CC dep from target: " + ccName);
392
0
                targetDep.emplace(std::move(ccName));
393
0
              }
394
0
            }
395
0
          }
396
0
          for (auto const& byproduct : cc.GetByproducts()) {
397
0
            LogMessage(cmStrCat("dep: ", dep, ", post byproduct: ",
398
0
                                this->ConvertToFastbuildPath(byproduct)));
399
0
            if (this->ConvertToFastbuildPath(byproduct) == dep) {
400
0
              auto ccName = this->GetCustomCommandTargetName(cc, step);
401
0
              if (ccName != currentCCName) {
402
0
                LogMessage("Additional CC dep from target: " + ccName);
403
0
                targetDep.emplace(std::move(ccName));
404
0
              }
405
0
            }
406
0
          }
407
0
        }
408
0
      };
409
0
      processCCs(target->GetPreBuildCommands(), FastbuildBuildStep::PRE_BUILD);
410
0
      processCCs(target->GetPreLinkCommands(), FastbuildBuildStep::PRE_LINK);
411
0
      processCCs(target->GetPostBuildCommands(),
412
0
                 FastbuildBuildStep::POST_BUILD);
413
0
      continue;
414
0
    }
415
0
    if (!targetInfo.Source) {
416
0
      LogMessage(cmStrCat("dep: ", dep, ", no source, byproduct: ",
417
0
                          targetInfo.SourceIsByproduct));
418
      // Tested in "OutDir" test.
419
0
      if (!cmSystemTools::FileIsFullPath(orig)) {
420
0
        targetDep.emplace(std::move(orig));
421
0
      }
422
0
      continue;
423
0
    }
424
0
    if (!targetInfo.Source->GetCustomCommand()) {
425
0
      LogMessage(cmStrCat("dep: ", dep, ", no GetCustomCommand"));
426
0
      continue;
427
0
    }
428
0
    if (targetInfo.Source && targetInfo.Source->GetCustomCommand()) {
429
0
      auto ccName = this->GetCustomCommandTargetName(
430
0
        *targetInfo.Source->GetCustomCommand(), FastbuildBuildStep::REST);
431
0
      if (ccName != currentCCName) {
432
0
        LogMessage("Additional CC dep: " + ccName);
433
0
        targetDep.emplace(std::move(ccName));
434
0
      }
435
0
    }
436
0
  }
437
0
}
438
439
void cmFastbuildTargetGenerator::ReplaceProblematicMakeVars(
440
  std::string& command) const
441
0
{
442
  // TODO: fix problematic global targets.  For now, search and replace the
443
  // makefile vars.
444
0
  cmSystemTools::ReplaceString(
445
0
    command, "$(CMAKE_SOURCE_DIR)",
446
0
    this->LocalGenerator->ConvertToOutputFormat(
447
0
      this->LocalGenerator->GetSourceDirectory(), cmOutputConverter::SHELL));
448
0
  cmSystemTools::ReplaceString(
449
0
    command, "$(CMAKE_BINARY_DIR)",
450
0
    this->LocalGenerator->ConvertToOutputFormat(
451
0
      this->LocalGenerator->GetBinaryDirectory(), cmOutputConverter::SHELL));
452
0
  cmSystemTools::ReplaceString(command, "$(ARGS)", "");
453
0
}
454
455
FastbuildExecNode cmFastbuildTargetGenerator::GetAppleTextStubCommand() const
456
0
{
457
0
  FastbuildExecNode res;
458
0
  if (!this->GeneratorTarget->IsApple() ||
459
0
      !this->GeneratorTarget->HasImportLibrary(Config)) {
460
0
    return res;
461
0
  }
462
463
0
  auto const names = DetectOutput();
464
0
  std::string const outpathImp =
465
0
    this->ConvertToFastbuildPath(this->GeneratorTarget->GetDirectory(
466
0
      Config, cmStateEnums::ImportLibraryArtifact));
467
468
0
  std::string const binPath =
469
0
    this->ConvertToFastbuildPath(this->GeneratorTarget->GetDirectory(
470
0
      Config, cmStateEnums::RuntimeBinaryArtifact));
471
472
0
  cmSystemTools::MakeDirectory(outpathImp);
473
474
0
  std::string rule = this->LocalGenerator->GetMakefile()->GetSafeDefinition(
475
0
    "CMAKE_CREATE_TEXT_STUBS");
476
0
  LogMessage("CMAKE_CREATE_TEXT_STUBS:" + rule);
477
478
0
  auto rulePlaceholderExpander =
479
0
    this->GetLocalGenerator()->CreateRulePlaceholderExpander();
480
481
0
  cmRulePlaceholderExpander::RuleVariables vars;
482
0
  res.ExecOutput = cmStrCat(outpathImp, '/', names.ImportReal);
483
0
  res.ExecInput = { cmStrCat(binPath, '/', names.SharedObject) };
484
485
0
  vars.Target = res.ExecInput[0].c_str();
486
0
  rulePlaceholderExpander->SetTargetImpLib(res.ExecOutput);
487
0
  rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), rule,
488
0
                                               vars);
489
490
0
  LogMessage("CMAKE_CREATE_TEXT_STUBS expanded:" + rule);
491
0
  std::string executable;
492
0
  std::string args;
493
0
  if (!cmSystemTools::SplitProgramFromArgs(rule, executable, args)) {
494
0
    cmSystemTools::Error("Failed to split program from args: " + rule);
495
0
    return res;
496
0
  }
497
498
0
  res.Name = cmStrCat("create_", names.ImportOutput, "_text_stub");
499
0
  res.ExecExecutable = std::move(executable);
500
0
  res.ExecArguments = std::move(args);
501
0
  res.ExecWorkingDir = this->LocalCommonGenerator->GetCurrentBinaryDirectory();
502
503
  // Wait for the build.
504
0
  res.PreBuildDependencies.emplace(this->GetTargetName());
505
0
  return res;
506
0
}
507
508
namespace {
509
std::vector<std::vector<cm::string_view>> GroupDeps(
510
  FastbuildExecNode const& depender, size_t commandLineUsedLength)
511
0
{
512
0
  std::vector<cm::string_view> depsCheckExecNames;
513
0
  depsCheckExecNames.reserve(
514
0
    depender.OutputsAlias.PreBuildDependencies.size() +
515
0
    depender.ByproductsAlias.PreBuildDependencies.size());
516
0
  for (FastbuildTargetDep const& dep :
517
0
       depender.OutputsAlias.PreBuildDependencies) {
518
0
    depsCheckExecNames.emplace_back(dep.Name);
519
0
  }
520
0
  for (FastbuildTargetDep const& dep :
521
0
       depender.ByproductsAlias.PreBuildDependencies) {
522
0
    depsCheckExecNames.emplace_back(dep.Name);
523
0
  }
524
525
0
  size_t commandLineLimit = cmSystemTools::CalculateCommandLineLengthLimit();
526
527
0
  std::vector<std::vector<cm::string_view>> depsCheckExecNamesGrouped;
528
0
  if (commandLineLimit == 0) {
529
0
    depsCheckExecNamesGrouped.emplace_back(std::move(depsCheckExecNames));
530
0
    return depsCheckExecNamesGrouped;
531
0
  }
532
533
0
  commandLineLimit -= commandLineUsedLength;
534
535
0
  std::vector<cm::string_view> currentGroup;
536
0
  currentGroup.reserve(depsCheckExecNames.size());
537
0
  size_t currentGroupSizeMayUse = commandLineLimit;
538
539
0
  for (size_t i = 0; i < depsCheckExecNames.size(); ++i) {
540
0
    cm::string_view depsCheckExecName = depsCheckExecNames[i];
541
0
    if (currentGroupSizeMayUse > depsCheckExecName.size()) {
542
0
      currentGroup.emplace_back(depsCheckExecName);
543
544
      // +1 for the space between arguments
545
0
      currentGroupSizeMayUse -= depsCheckExecName.size() + 1;
546
0
      continue;
547
0
    }
548
549
    // Current dependency cannot be placed in the current group
550
0
    if (currentGroup.empty()) {
551
0
      cmSystemTools::Error(cmStrCat("Failed to group dependencies: command "
552
0
                                    "line limit exceeded. Filename too long: ",
553
0
                                    depsCheckExecName,
554
0
                                    ". Size limit: ", commandLineLimit));
555
0
      return {};
556
0
    }
557
558
0
    depsCheckExecNamesGrouped.emplace_back(std::move(currentGroup));
559
0
    currentGroup.clear();
560
0
    currentGroup.reserve(depsCheckExecNames.size() - i);
561
0
    currentGroupSizeMayUse = commandLineLimit;
562
563
    // Go back to the previous dependency to check if it can be placed in the
564
    // current group.
565
0
    --i;
566
0
  }
567
568
0
  if (!currentGroup.empty()) {
569
0
    depsCheckExecNamesGrouped.emplace_back(std::move(currentGroup));
570
0
  }
571
572
0
  return depsCheckExecNamesGrouped;
573
0
}
574
}
575
576
std::vector<FastbuildExecNode> cmFastbuildTargetGenerator::GetDepsCheckExecs(
577
  FastbuildExecNode const& depender)
578
0
{
579
0
  std::string const& executable = cmSystemTools::GetCMakeCommand();
580
0
  std::string execArgumentsBase =
581
0
    cmStrCat("-E cmake_fastbuild_check_depends ", depender.ExecOutput, ' ');
582
583
0
  std::vector<std::vector<cm::string_view>> depsCheckExecNamesGrouped =
584
0
    GroupDeps(depender, executable.size() + execArgumentsBase.size());
585
586
0
  std::vector<FastbuildExecNode> res;
587
0
  res.reserve(depsCheckExecNamesGrouped.size());
588
0
  for (size_t i = 0; i < depsCheckExecNamesGrouped.size(); ++i) {
589
0
    FastbuildExecNode exec;
590
0
    exec.Name = cmStrCat(depender.Name, "-check-depends-", i);
591
0
    exec.ExecAlways = true;
592
0
    exec.ExecUseStdOutAsOutput = true;
593
0
    exec.ExecOutput = cmStrCat(depender.ExecOutput, '.', i, ".deps-checker");
594
0
    exec.ExecExecutable = executable;
595
0
    exec.ExecArguments =
596
0
      cmJoinStrings(depsCheckExecNamesGrouped[i], " ", execArgumentsBase);
597
0
    res.emplace_back(std::move(exec));
598
0
  }
599
0
  return res;
600
0
}
601
602
FastbuildExecNodes cmFastbuildTargetGenerator::GenerateCommands(
603
  FastbuildBuildStep buildStep)
604
0
{
605
0
  FastbuildExecNodes execs;
606
0
  execs.Alias.Name = GetUtilityAliasFromBuildStep(buildStep);
607
608
0
  std::vector<cmCustomCommand> commands;
609
0
  if (buildStep == FastbuildBuildStep::PRE_BUILD) {
610
0
    commands = GeneratorTarget->GetPreBuildCommands();
611
0
    LogMessage("STEP: PRE_BUILD");
612
0
  } else if (buildStep == FastbuildBuildStep::PRE_LINK) {
613
0
    commands = GeneratorTarget->GetPreLinkCommands();
614
0
    LogMessage("STEP: PRE_LINK");
615
0
  } else if (buildStep == FastbuildBuildStep::POST_BUILD) {
616
0
    commands = GeneratorTarget->GetPostBuildCommands();
617
0
    LogMessage("STEP: POST_BUILD");
618
0
  } else {
619
0
    LogMessage("STEP: ALL CUSTOM COMMANDS");
620
0
    std::vector<cmSourceFile const*> customCommands;
621
0
    GeneratorTarget->GetCustomCommands(customCommands, Config);
622
0
    for (cmSourceFile const* source : customCommands) {
623
0
      cmCustomCommand const* cmd = source->GetCustomCommand();
624
0
      if (!cmd->GetCommandLines().empty()) {
625
0
        commands.emplace_back(*cmd);
626
0
      }
627
0
    }
628
0
  }
629
0
  LogMessage(cmStrCat("Number of custom commands: ", commands.size()));
630
0
  for (cmCustomCommand const& customCommand : commands) {
631
0
    cmCustomCommandGenerator ccg(customCommand, Config, LocalCommonGenerator);
632
0
    std::string launcher = this->MakeCustomLauncher(ccg);
633
634
0
    std::string const execName =
635
0
      GetCustomCommandTargetName(customCommand, buildStep);
636
637
0
    std::vector<std::string> cmdLines;
638
0
    if (ccg.GetNumberOfCommands() > 0) {
639
0
      cmdLines.push_back(GetCdCommand(ccg));
640
0
    }
641
642
    // Since we are not using FASTBuild Exec nodes natively, we need to
643
    // have shell specific escape.
644
0
    this->LocalGenerator->GetState()->SetFastbuildMake(false);
645
    // To avoid replacing $ with $$ in the command line.
646
0
    this->LocalGenerator->SetLinkScriptShell(true);
647
0
    for (unsigned j = 0; j != ccg.GetNumberOfCommands(); ++j) {
648
0
      std::string const command = ccg.GetCommand(j);
649
      // Tested in "CustomCommand" ("empty_command") test.
650
0
      if (!command.empty()) {
651
652
0
        cmdLines.emplace_back(launcher +
653
0
                              this->LocalGenerator->ConvertToOutputFormat(
654
0
                                command, cmOutputConverter::SHELL));
655
656
0
        std::string& cmd = cmdLines.back();
657
0
        ccg.AppendArguments(j, cmd);
658
0
        ReplaceProblematicMakeVars(cmd);
659
0
        LogMessage("cmCustomCommandLine: " + cmd);
660
0
      }
661
0
    }
662
0
    if (cmdLines.empty()) {
663
0
      return {};
664
0
    }
665
0
    this->LocalGenerator->GetState()->SetFastbuildMake(true);
666
667
0
    FastbuildExecNode execNode;
668
0
    execNode.Name = execName;
669
670
    // Add dependencies to "ExecInput" so that FASTBuild will re-run the Exec
671
    // when needed, but also add to "PreBuildDependencies" for correct sorting.
672
    // Tested in "ObjectLibrary / complexOneConfig" tests.
673
0
    GetDepends(ccg, execName, execNode.ExecInput,
674
0
               execNode.PreBuildDependencies);
675
0
    for (auto const& util : ccg.GetUtilities()) {
676
0
      auto const& utilTargetName = util.Value.first;
677
0
      LogMessage("Util: " + utilTargetName +
678
0
                 ", cross: " + std::to_string(util.Value.second));
679
0
      auto* const target = this->Makefile->FindTargetToUse(utilTargetName);
680
681
0
      if (target && target->IsImported()) {
682
0
        std::string importedLoc =
683
0
          this->ConvertToFastbuildPath(target->ImportedGetFullPath(
684
0
            Config, cmStateEnums::ArtifactType::RuntimeBinaryArtifact));
685
0
        if (importedLoc.empty()) {
686
0
          importedLoc =
687
0
            this->ConvertToFastbuildPath(target->ImportedGetFullPath(
688
0
              Config, cmStateEnums::ArtifactType::ImportLibraryArtifact));
689
0
        }
690
0
        LogMessage("adding file level dep on imported target: " + importedLoc);
691
0
        execNode.ExecInput.emplace_back(std::move(importedLoc));
692
0
        continue;
693
0
      }
694
      // This CC uses some executable produced by another target. Add explicit
695
      // dep. Tested in "CustomCommand" test.
696
0
      if (util.Value.second) {
697
0
        if (utilTargetName != customCommand.GetTarget()) {
698
0
          LogMessage("Adding util dep: " + utilTargetName);
699
0
          execNode.PreBuildDependencies.emplace(utilTargetName);
700
0
        }
701
0
      }
702
0
    }
703
704
0
    execs.Alias.PreBuildDependencies.emplace(execNode.Name);
705
706
0
    LogMessage(cmStrCat("cmdLines size ", cmdLines.size()));
707
708
0
    if (!cmdLines.empty()) {
709
0
      std::string const scriptFileName = GetScriptFilename(execName);
710
0
      cmsys::ofstream scriptFile(scriptFileName);
711
712
0
      AddOutput(ccg, execNode);
713
0
      AddExecArguments(execNode, scriptFileName);
714
0
      AddCommentPrinting(cmdLines, ccg);
715
716
0
      WriteScriptProlog(scriptFile);
717
0
      WriteCmdsToFile(scriptFile, cmdLines);
718
0
      WriteScriptEpilog(scriptFile);
719
0
    }
720
721
0
    if (buildStep == FastbuildBuildStep::POST_BUILD) {
722
      // Execute POST_BUILD in order in which they are declared.
723
      // Tested in "complex" test.
724
0
      for (auto& exec : execs.Nodes) {
725
0
        execNode.PreBuildDependencies.emplace(exec.Name);
726
0
      }
727
0
    }
728
0
    for (auto const& out : execNode.OutputsAlias.PreBuildDependencies) {
729
0
      LogMessage(cmStrCat("Adding replace from ", out.Name, " to ", execName));
730
0
      OutputToExecName[out.Name] = execName;
731
0
    }
732
0
    execs.Nodes.emplace_back(std::move(execNode));
733
0
  }
734
0
  for (auto& exec : execs.Nodes) {
735
0
    for (auto& inputFile : exec.ExecInput) {
736
0
      auto const iter = OutputsToReplace.find(inputFile);
737
0
      if (iter != OutputsToReplace.end()) {
738
0
        LogMessage(
739
0
          cmStrCat("Replacing input: ", inputFile, " with ", iter->second));
740
0
        inputFile = iter->second;
741
0
      }
742
0
      auto const depIter = std::find_if(
743
0
        exec.PreBuildDependencies.begin(), exec.PreBuildDependencies.end(),
744
0
        [this](FastbuildTargetDep const& dep) {
745
0
          return !OutputToExecName[dep.Name].empty();
746
0
        });
747
0
      if (depIter != exec.PreBuildDependencies.end()) {
748
0
        LogMessage(cmStrCat("Replacing dep ", depIter->Name, " with ",
749
0
                            OutputToExecName[depIter->Name]));
750
0
        exec.PreBuildDependencies.emplace(OutputToExecName[depIter->Name]);
751
0
        exec.PreBuildDependencies.erase(depIter);
752
0
      }
753
0
    }
754
0
    if (exec.NeedsDepsCheckExec) {
755
0
      std::vector<FastbuildExecNode> depsCheckExecs = GetDepsCheckExecs(exec);
756
757
0
      std::vector<cm::string_view> depsCheckExecNames;
758
0
      depsCheckExecNames.reserve(depsCheckExecs.size());
759
0
      for (FastbuildExecNode const& depsCheckExec : depsCheckExecs) {
760
0
        depsCheckExecNames.emplace_back(depsCheckExec.Name);
761
0
      }
762
0
      LogMessage(
763
0
        cmJoinStrings(depsCheckExecNames, ", ", "Adding deps check Exec: "));
764
765
0
      for (FastbuildExecNode& depsCheckExec : depsCheckExecs) {
766
0
        exec.PreBuildDependencies.emplace(depsCheckExec.Name);
767
0
        this->GetGlobalGenerator()->AddTarget(std::move(depsCheckExec));
768
0
      }
769
0
    }
770
0
  }
771
0
  return execs;
772
0
}
773
774
std::string cmFastbuildTargetGenerator::MakeCustomLauncher(
775
  cmCustomCommandGenerator const& ccg)
776
0
{
777
  // Copied from cmLocalNinjaGenerator::MakeCustomLauncher.
778
0
  cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM");
779
780
0
  if (!cmNonempty(property_value)) {
781
0
    return std::string();
782
0
  }
783
784
  // Expand rule variables referenced in the given launcher command.
785
0
  cmRulePlaceholderExpander::RuleVariables vars;
786
787
0
  std::string output;
788
0
  std::vector<std::string> const& outputs = ccg.GetOutputs();
789
0
  for (size_t i = 0; i < outputs.size(); ++i) {
790
0
    output =
791
0
      cmStrCat(output,
792
0
               this->LocalGenerator->ConvertToOutputFormat(
793
0
                 ccg.GetWorkingDirectory().empty()
794
0
                   ? this->LocalGenerator->MaybeRelativeToCurBinDir(outputs[i])
795
0
                   : outputs[i],
796
0
                 cmOutputConverter::SHELL));
797
0
    if (i != outputs.size() - 1) {
798
0
      output = cmStrCat(output, ',');
799
0
    }
800
0
  }
801
0
  vars.Output = output.c_str();
802
803
0
  std::string filePathWithOutput = ccg.StoreContentToFile(output);
804
0
  vars.FilePathWithOutput = filePathWithOutput.c_str();
805
806
0
  vars.Role = ccg.GetCC().GetRole().c_str();
807
0
  vars.CMTargetName = ccg.GetCC().GetTarget().c_str();
808
0
  vars.Config = ccg.GetOutputConfig().c_str();
809
810
0
  auto rulePlaceholderExpander =
811
0
    this->LocalGenerator->CreateRulePlaceholderExpander();
812
813
0
  std::string launcher = *property_value;
814
0
  rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator, launcher,
815
0
                                               vars);
816
0
  if (!launcher.empty()) {
817
0
    launcher += " ";
818
0
  }
819
820
0
  LogMessage("CC Launcher: " + launcher);
821
0
  return launcher;
822
0
}
823
824
std::string cmFastbuildTargetGenerator::GetTargetName() const
825
0
{
826
0
  if (this->GeneratorTarget->GetType() == cm::TargetType::GLOBAL_TARGET) {
827
0
    return this->GetGlobalGenerator()->GetTargetName(GeneratorTarget);
828
0
  }
829
0
  return this->GeneratorTarget->GetName();
830
0
}
831
832
cmGeneratorTarget::Names cmFastbuildTargetGenerator::DetectOutput() const
833
0
{
834
0
  if (GeneratorTarget->GetType() == cm::TargetType::EXECUTABLE) {
835
0
    return GeneratorTarget->GetExecutableNames(Config);
836
0
  }
837
0
  return GeneratorTarget->GetLibraryNames(Config);
838
0
}
839
840
void cmFastbuildTargetGenerator::AddObjectDependencies(
841
  FastbuildTarget& fastbuildTarget,
842
  std::vector<std::string>& allObjectDepends) const
843
0
{
844
0
  auto const FindObjListWhichOutputs = [&fastbuildTarget](
845
0
                                         std::string const& output) {
846
0
    for (FastbuildObjectListNode const& objList :
847
0
         fastbuildTarget.ObjectListNodes) {
848
0
      if (objList.ObjectOutputs.find(output) != objList.ObjectOutputs.end()) {
849
0
        return objList.Name;
850
0
      }
851
0
    }
852
0
    return std::string{};
853
0
  };
854
855
0
  for (FastbuildObjectListNode& objList : fastbuildTarget.ObjectListNodes) {
856
0
    for (auto const& objDep : objList.ObjectDepends) {
857
      // Check if there is another object list which outputs (OBJECT_OUTPUTS)
858
      // something that this object list needs (OBJECT_DEPENDS).
859
0
      auto anotherObjList = FindObjListWhichOutputs(objDep);
860
0
      if (!anotherObjList.empty()) {
861
0
        LogMessage("Adding explicit <OBJECT_DEPENDS> dep: " + anotherObjList);
862
0
        allObjectDepends.emplace_back(anotherObjList);
863
0
        objList.PreBuildDependencies.emplace(std::move(anotherObjList));
864
865
0
      } else {
866
0
        LogMessage("Adding <OBJECT_DEPENDS> dep: " + objDep);
867
0
        allObjectDepends.emplace_back(objDep);
868
0
        objList.PreBuildDependencies.emplace(objDep);
869
0
      }
870
0
    }
871
0
  }
872
0
  cmGlobalFastbuildGenerator::TopologicalSort(fastbuildTarget.ObjectListNodes);
873
0
}
874
875
void cmFastbuildTargetGenerator::AddLinkerNodeDependencies(
876
  FastbuildTarget& fastbuildTarget)
877
0
{
878
0
  for (auto& linkerNode : fastbuildTarget.LinkerNode) {
879
0
    if (!fastbuildTarget.PreLinkExecNodes.Nodes.empty()) {
880
0
      linkerNode.PreBuildDependencies.emplace(
881
0
        fastbuildTarget.Name + FASTBUILD_PRE_LINK_ALIAS_POSTFIX);
882
0
    }
883
0
  }
884
0
}
885
886
std::string cmFastbuildTargetGenerator::GetClangTidyReplacementsFilePath(
887
  std::string const& directory, cmSourceFile const& source,
888
  std::string const& /*config*/) const
889
0
{
890
891
0
  std::string objectDir =
892
0
    this->ConvertToFastbuildPath(this->GeneratorTarget->GetSupportDirectory());
893
0
  std::string const& objectName =
894
0
    this->GeneratorTarget->GetObjectName(&source);
895
0
  std::string path =
896
0
    cmStrCat(directory, '/', objectDir, '/', objectName, ".yaml");
897
0
  LogMessage("ClangTidy replacements file: " + path);
898
0
  return path;
899
0
}
900
901
void cmFastbuildTargetGenerator::AddIncludeFlags(std::string& languageFlags,
902
                                                 std::string const& language,
903
                                                 std::string const&)
904
0
{
905
0
  std::vector<std::string> includes;
906
0
  this->LocalGenerator->GetIncludeDirectories(includes, this->GeneratorTarget,
907
0
                                              language, Config);
908
  // Add include directory flags.
909
0
  std::string includeFlags = this->LocalGenerator->GetIncludeFlags(
910
0
    includes, this->GeneratorTarget, language, Config, false);
911
912
0
  this->LocalGenerator->AppendFlags(languageFlags, includeFlags);
913
0
}
914
915
std::string cmFastbuildTargetGenerator::GetName()
916
0
{
917
0
  return GeneratorTarget->GetName();
918
0
}
919
920
std::string cmFastbuildTargetGenerator::ConvertToFastbuildPath(
921
  std::string const& path) const
922
0
{
923
0
  return GetGlobalGenerator()->ConvertToFastbuildPath(path);
924
0
}
925
926
cmGlobalFastbuildGenerator* cmFastbuildTargetGenerator::GetGlobalGenerator()
927
  const
928
0
{
929
0
  return this->LocalGenerator->GetGlobalFastbuildGenerator();
930
0
}
931
932
void cmFastbuildTargetGenerator::AdditionalCleanFiles()
933
0
{
934
0
  if (cmValue prop_value =
935
0
        this->GeneratorTarget->GetProperty("ADDITIONAL_CLEAN_FILES")) {
936
0
    auto* lg = this->LocalGenerator;
937
0
    cmList cleanFiles(cmGeneratorExpression::Evaluate(*prop_value, lg, Config,
938
0
                                                      this->GeneratorTarget));
939
0
    std::string const& binaryDir = lg->GetCurrentBinaryDirectory();
940
0
    auto* gg = lg->GetGlobalFastbuildGenerator();
941
0
    for (auto const& cleanFile : cleanFiles) {
942
      // Support relative paths
943
0
      gg->AddFileToClean(gg->ConvertToFastbuildPath(
944
0
        cmSystemTools::CollapseFullPath(cleanFile, binaryDir)));
945
0
    }
946
0
  }
947
0
}