Coverage Report

Created: 2026-07-30 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmCustomCommandGenerator.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 "cmCustomCommandGenerator.h"
4
5
#include <cstddef>
6
#include <memory>
7
#include <utility>
8
9
#include <cm/optional>
10
#include <cm/string_view>
11
#include <cmext/algorithm>
12
#include <cmext/string_view>
13
14
#include "cmsys/FStream.hxx"
15
16
#include "cmCryptoHash.h"
17
#include "cmCustomCommand.h"
18
#include "cmCustomCommandLines.h"
19
#include "cmGeneratorExpression.h"
20
#include "cmGeneratorTarget.h"
21
#include "cmGlobalGenerator.h"
22
#include "cmList.h"
23
#include "cmLocalGenerator.h"
24
#include "cmMakefile.h"
25
#include "cmStringAlgorithms.h"
26
#include "cmSystemTools.h"
27
#include "cmTargetTypes.h"
28
#include "cmTransformDepfile.h"
29
#include "cmValue.h"
30
31
namespace {
32
std::string EvaluateSplitConfigGenex(
33
  cm::string_view input, cmGeneratorExpression const& ge, cmLocalGenerator* lg,
34
  bool useOutputConfig, std::string const& outputConfig,
35
  std::string const& commandConfig, cmGeneratorTarget const* target,
36
  std::set<BT<std::pair<std::string, bool>>>* utils = nullptr)
37
0
{
38
0
  std::string result;
39
40
0
  while (!input.empty()) {
41
    // Copy non-genex content directly to the result.
42
0
    std::string::size_type pos = input.find("$<");
43
0
    result += input.substr(0, pos);
44
0
    if (pos == std::string::npos) {
45
0
      break;
46
0
    }
47
0
    input = input.substr(pos);
48
49
    // Find the balanced end of this regex.
50
0
    size_t nestingLevel = 1;
51
0
    for (pos = 2; pos < input.size(); ++pos) {
52
0
      cm::string_view cur = input.substr(pos);
53
0
      if (cmHasLiteralPrefix(cur, "$<")) {
54
0
        ++nestingLevel;
55
0
        ++pos;
56
0
        continue;
57
0
      }
58
0
      if (cmHasPrefix(cur, '>')) {
59
0
        --nestingLevel;
60
0
        if (nestingLevel == 0) {
61
0
          ++pos;
62
0
          break;
63
0
        }
64
0
      }
65
0
    }
66
67
    // Split this genex from following input.
68
0
    cm::string_view genex = input.substr(0, pos);
69
0
    input = input.substr(pos);
70
71
    // Convert an outer COMMAND_CONFIG or OUTPUT_CONFIG to the matching config.
72
0
    std::string const* config =
73
0
      useOutputConfig ? &outputConfig : &commandConfig;
74
0
    if (nestingLevel == 0) {
75
0
      static cm::string_view const COMMAND_CONFIG = "$<COMMAND_CONFIG:"_s;
76
0
      static cm::string_view const OUTPUT_CONFIG = "$<OUTPUT_CONFIG:"_s;
77
0
      if (cmHasPrefix(genex, COMMAND_CONFIG)) {
78
0
        genex.remove_prefix(COMMAND_CONFIG.size());
79
0
        genex.remove_suffix(1);
80
0
        useOutputConfig = false;
81
0
        config = &commandConfig;
82
0
      } else if (cmHasPrefix(genex, OUTPUT_CONFIG)) {
83
0
        genex.remove_prefix(OUTPUT_CONFIG.size());
84
0
        genex.remove_suffix(1);
85
0
        useOutputConfig = true;
86
0
        config = &outputConfig;
87
0
      }
88
0
    }
89
90
    // Evaluate this genex in the selected configuration.
91
0
    std::unique_ptr<cmCompiledGeneratorExpression> cge =
92
0
      ge.Parse(std::string(genex));
93
0
    result += cge->Evaluate(lg, *config, target);
94
95
    // Record targets referenced by the genex.
96
0
    if (utils) {
97
      // Use a cross-dependency if we referenced the command config.
98
0
      bool const cross = !useOutputConfig;
99
0
      for (cmGeneratorTarget* gt : cge->GetTargets()) {
100
0
        utils->emplace(BT<std::pair<std::string, bool>>(
101
0
          { gt->GetName(), cross }, cge->GetBacktrace()));
102
0
      }
103
0
    }
104
0
  }
105
106
0
  return result;
107
0
}
108
109
std::vector<std::string> EvaluateDepends(std::vector<std::string> const& paths,
110
                                         cmGeneratorExpression const& ge,
111
                                         cmLocalGenerator* lg,
112
                                         std::string const& outputConfig,
113
                                         std::string const& commandConfig)
114
0
{
115
0
  std::vector<std::string> depends;
116
0
  for (std::string const& p : paths) {
117
0
    std::string const& ep =
118
0
      EvaluateSplitConfigGenex(p, ge, lg, /*useOutputConfig=*/true,
119
0
                               /*outputConfig=*/outputConfig,
120
0
                               /*commandConfig=*/commandConfig,
121
0
                               /*target=*/nullptr);
122
0
    cm::append(depends, cmList{ ep });
123
0
  }
124
0
  for (std::string& p : depends) {
125
0
    if (cmSystemTools::FileIsFullPath(p)) {
126
0
      p = cmSystemTools::CollapseFullPath(p);
127
0
    } else {
128
0
      cmSystemTools::ConvertToUnixSlashes(p);
129
0
    }
130
0
  }
131
0
  return depends;
132
0
}
133
134
std::vector<std::string> EvaluateOutputs(std::vector<std::string> const& paths,
135
                                         cmGeneratorExpression const& ge,
136
                                         cmLocalGenerator* lg,
137
                                         std::string const& config)
138
0
{
139
0
  std::vector<std::string> outputs;
140
0
  for (std::string const& p : paths) {
141
0
    std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p);
142
0
    cm::append(outputs, lg->ExpandCustomCommandOutputPaths(*cge, config));
143
0
  }
144
0
  return outputs;
145
0
}
146
147
std::string EvaluateDepfile(std::string const& path,
148
                            cmGeneratorExpression const& ge,
149
                            cmLocalGenerator* lg, std::string const& config)
150
0
{
151
0
  std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(path);
152
0
  return cge->Evaluate(lg, config);
153
0
}
154
155
std::string EvaluateComment(char const* comment,
156
                            cmGeneratorExpression const& ge,
157
                            cmLocalGenerator* lg, std::string const& config)
158
0
{
159
0
  std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(comment);
160
0
  return cge->Evaluate(lg, config);
161
0
}
162
}
163
164
cmCustomCommandGenerator::cmCustomCommandGenerator(
165
  cmCustomCommand const& cc, std::string config, cmLocalGenerator* lg,
166
  bool transformDepfile, cm::optional<std::string> crossConfig,
167
  std::function<std::string(std::string const&, std::string const&)>
168
    computeInternalDepfile)
169
0
  : CC(&cc)
170
0
  , OutputConfig(crossConfig ? *crossConfig : config)
171
0
  , CommandConfig(std::move(config))
172
0
  , Target(cc.GetTarget())
173
0
  , LG(lg)
174
0
  , OldStyle(cc.GetEscapeOldStyle())
175
0
  , MakeVars(cc.GetEscapeAllowMakeVars())
176
0
  , EmulatorsWithArguments(cc.GetCommandLines().size())
177
0
  , ComputeInternalDepfile(std::move(computeInternalDepfile))
178
0
{
179
180
0
  cmGeneratorExpression ge(*lg->GetCMakeInstance(), cc.GetBacktrace());
181
0
  cmGeneratorTarget const* target{ lg->FindGeneratorTargetToUse(
182
0
    this->Target) };
183
184
0
  bool const distinctConfigs = this->OutputConfig != this->CommandConfig;
185
186
0
  cmCustomCommandLines const& cmdlines = this->CC->GetCommandLines();
187
0
  for (cmCustomCommandLine const& cmdline : cmdlines) {
188
0
    cmCustomCommandLine argv;
189
    // For the command itself, we default to the COMMAND_CONFIG.
190
0
    bool useOutputConfig = false;
191
0
    for (std::string const& clarg : cmdline) {
192
0
      std::string parsed_arg = EvaluateSplitConfigGenex(
193
0
        clarg, ge, this->LG, useOutputConfig, this->OutputConfig,
194
0
        this->CommandConfig, target, &this->Utilities);
195
0
      if (this->CC->GetCommandExpandLists()) {
196
0
        cm::append(argv, cmList{ parsed_arg });
197
0
      } else {
198
0
        argv.push_back(std::move(parsed_arg));
199
0
      }
200
201
0
      if (distinctConfigs) {
202
        // For remaining arguments, we default to the OUTPUT_CONFIG.
203
0
        useOutputConfig = true;
204
0
      }
205
0
    }
206
207
0
    if (!argv.empty()) {
208
      // If the command references an executable target by name,
209
      // collect the target to add a target-level dependency on it.
210
0
      cmGeneratorTarget* gt = this->LG->FindGeneratorTargetToUse(argv.front());
211
0
      if (gt && gt->GetType() == cm::TargetType::EXECUTABLE) {
212
        // GetArgv0Location uses the command config, so use a cross-dependency.
213
0
        bool const cross = true;
214
0
        this->Utilities.emplace(BT<std::pair<std::string, bool>>(
215
0
          { gt->GetName(), cross }, cc.GetBacktrace()));
216
0
      }
217
0
    } else {
218
      // Later code assumes at least one entry exists, but expanding
219
      // lists on an empty command may have left this empty.
220
      // FIXME: Should we define behavior for removing empty commands?
221
0
      argv.emplace_back();
222
0
    }
223
224
0
    this->CommandLines.push_back(std::move(argv));
225
0
  }
226
227
0
  if (transformDepfile && !this->CommandLines.empty() &&
228
0
      !cc.GetDepfile().empty() &&
229
0
      this->LG->GetGlobalGenerator()->DepfileFormat()) {
230
0
    cmCustomCommandLine argv;
231
0
    argv.push_back(cmSystemTools::GetCMakeCommand());
232
0
    argv.emplace_back("-E");
233
0
    argv.emplace_back("cmake_transform_depfile");
234
0
    argv.push_back(this->LG->GetGlobalGenerator()->GetName());
235
0
    switch (*this->LG->GetGlobalGenerator()->DepfileFormat()) {
236
0
      case cmDepfileFormat::GccDepfile:
237
0
        argv.emplace_back("gccdepfile");
238
0
        break;
239
0
      case cmDepfileFormat::MakeDepfile:
240
0
        argv.emplace_back("makedepfile");
241
0
        break;
242
0
      case cmDepfileFormat::MSBuildAdditionalInputs:
243
0
        argv.emplace_back("MSBuildAdditionalInputs");
244
0
        break;
245
0
    }
246
0
    argv.push_back(this->LG->GetSourceDirectory());
247
0
    argv.push_back(this->LG->GetCurrentSourceDirectory());
248
0
    argv.push_back(this->LG->GetBinaryDirectory());
249
0
    argv.push_back(this->LG->GetCurrentBinaryDirectory());
250
0
    argv.push_back(this->GetFullDepfile());
251
0
    argv.push_back(this->GetInternalDepfile());
252
253
0
    this->CommandLines.push_back(std::move(argv));
254
0
  }
255
256
0
  this->Outputs =
257
0
    EvaluateOutputs(cc.GetOutputs(), ge, this->LG, this->OutputConfig);
258
0
  this->Byproducts =
259
0
    EvaluateOutputs(cc.GetByproducts(), ge, this->LG, this->OutputConfig);
260
0
  this->Depends = EvaluateDepends(cc.GetDepends(), ge, this->LG,
261
0
                                  this->OutputConfig, this->CommandConfig);
262
263
0
  std::string const& workingdirectory = this->CC->GetWorkingDirectory();
264
0
  if (!workingdirectory.empty()) {
265
0
    this->WorkingDirectory = EvaluateSplitConfigGenex(
266
0
      workingdirectory, ge, this->LG, true, this->OutputConfig,
267
0
      this->CommandConfig, target);
268
    // Convert working directory to a full path.
269
0
    if (!this->WorkingDirectory.empty()) {
270
0
      std::string const& build_dir = this->LG->GetCurrentBinaryDirectory();
271
0
      this->WorkingDirectory =
272
0
        cmSystemTools::CollapseFullPath(this->WorkingDirectory, build_dir);
273
0
    }
274
0
  }
275
276
0
  this->FillEmulatorsWithArguments();
277
0
}
278
279
unsigned int cmCustomCommandGenerator::GetNumberOfCommands() const
280
0
{
281
0
  return static_cast<unsigned int>(this->CommandLines.size());
282
0
}
283
284
void cmCustomCommandGenerator::FillEmulatorsWithArguments()
285
0
{
286
0
  if (!this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING")) {
287
0
    return;
288
0
  }
289
0
  cmGeneratorExpression ge(*this->LG->GetCMakeInstance(),
290
0
                           this->CC->GetBacktrace());
291
292
0
  for (unsigned int c = 0; c < this->GetNumberOfCommands(); ++c) {
293
    // If the command is the plain name of an executable target,
294
    // launch it with its emulator.
295
0
    std::string const& argv0 = this->CommandLines[c][0];
296
0
    cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(argv0);
297
0
    if (target && target->GetType() == cm::TargetType::EXECUTABLE &&
298
0
        !target->IsImported()) {
299
300
0
      cmValue emulator_property =
301
0
        target->GetProperty("CROSSCOMPILING_EMULATOR");
302
0
      if (!emulator_property) {
303
0
        continue;
304
0
      }
305
306
      // Plain target names are replaced by GetArgv0Location with the
307
      // path to the executable artifact in the command config, so
308
      // evaluate the launcher's location in the command config too.
309
0
      std::string const emulator =
310
0
        ge.Parse(*emulator_property)->Evaluate(this->LG, this->CommandConfig);
311
0
      cmExpandList(emulator, this->EmulatorsWithArguments[c]);
312
0
    }
313
0
  }
314
0
}
315
316
std::vector<std::string> cmCustomCommandGenerator::GetCrossCompilingEmulator(
317
  unsigned int c) const
318
0
{
319
0
  if (c >= this->EmulatorsWithArguments.size()) {
320
0
    return std::vector<std::string>();
321
0
  }
322
0
  return this->EmulatorsWithArguments[c];
323
0
}
324
325
char const* cmCustomCommandGenerator::GetArgv0Location(unsigned int c) const
326
0
{
327
  // If the command is the plain name of an executable target, we replace it
328
  // with the path to the executable artifact in the command config.
329
0
  std::string const& argv0 = this->CommandLines[c][0];
330
0
  cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(argv0);
331
0
  if (target && target->GetType() == cm::TargetType::EXECUTABLE &&
332
0
      (target->IsImported() ||
333
0
       target->GetProperty("CROSSCOMPILING_EMULATOR") ||
334
0
       !this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING"))) {
335
0
    return target->GetLocation(this->CommandConfig).c_str();
336
0
  }
337
0
  return nullptr;
338
0
}
339
340
bool cmCustomCommandGenerator::HasOnlyEmptyCommandLines() const
341
0
{
342
0
  for (cmCustomCommandLine const& ccl : this->CommandLines) {
343
0
    for (std::string const& cl : ccl) {
344
0
      if (!cl.empty()) {
345
0
        return false;
346
0
      }
347
0
    }
348
0
  }
349
0
  return true;
350
0
}
351
352
std::string cmCustomCommandGenerator::GetCommand(unsigned int c) const
353
0
{
354
0
  std::vector<std::string> emulator = this->GetCrossCompilingEmulator(c);
355
0
  if (!emulator.empty()) {
356
0
    return emulator[0];
357
0
  }
358
0
  if (char const* location = this->GetArgv0Location(c)) {
359
0
    return std::string(location);
360
0
  }
361
362
0
  return this->CommandLines[c][0];
363
0
}
364
365
static std::string escapeForShellOldStyle(std::string const& str)
366
0
{
367
0
  std::string result;
368
#if defined(_WIN32) && !defined(__CYGWIN__)
369
  // if there are spaces
370
  std::string temp = str;
371
  if (temp.find(" ") != std::string::npos &&
372
      temp.find("\"") == std::string::npos) {
373
    result = cmStrCat('"', str, '"');
374
    return result;
375
  }
376
  return str;
377
#else
378
0
  for (char const* ch = str.c_str(); *ch != '\0'; ++ch) {
379
0
    if (*ch == ' ') {
380
0
      result += '\\';
381
0
    }
382
0
    result += *ch;
383
0
  }
384
0
  return result;
385
0
#endif
386
0
}
387
388
void cmCustomCommandGenerator::AppendArguments(unsigned int c,
389
                                               std::string& cmd) const
390
0
{
391
0
  unsigned int offset = 1;
392
0
  std::vector<std::string> emulator = this->GetCrossCompilingEmulator(c);
393
0
  if (!emulator.empty()) {
394
0
    for (unsigned j = 1; j < emulator.size(); ++j) {
395
0
      cmd += " ";
396
0
      if (this->OldStyle) {
397
0
        cmd += escapeForShellOldStyle(emulator[j]);
398
0
      } else {
399
0
        cmd +=
400
0
          this->LG->EscapeForShell(emulator[j], this->MakeVars, false, false,
401
0
                                   this->MakeVars && this->LG->IsNinjaMulti());
402
0
      }
403
0
    }
404
405
0
    offset = 0;
406
0
  }
407
408
0
  cmCustomCommandLine const& commandLine = this->CommandLines[c];
409
0
  for (unsigned int j = offset; j < commandLine.size(); ++j) {
410
0
    std::string arg;
411
0
    if (char const* location = j == 0 ? this->GetArgv0Location(c) : nullptr) {
412
      // GetCommand returned the emulator instead of the argv0 location,
413
      // so transform the latter now.
414
0
      arg = location;
415
0
    } else {
416
0
      arg = commandLine[j];
417
0
    }
418
0
    cmd += " ";
419
0
    if (this->OldStyle) {
420
0
      cmd += escapeForShellOldStyle(arg);
421
0
    } else {
422
0
      cmd +=
423
0
        this->LG->EscapeForShell(arg, this->MakeVars, false, false,
424
0
                                 this->MakeVars && this->LG->IsNinjaMulti());
425
0
    }
426
0
  }
427
0
}
428
429
std::string cmCustomCommandGenerator::GetDepfile() const
430
0
{
431
0
  auto const& depfile = this->CC->GetDepfile();
432
0
  if (depfile.empty()) {
433
0
    return "";
434
0
  }
435
436
0
  cmGeneratorExpression ge(*this->LG->GetCMakeInstance(),
437
0
                           this->CC->GetBacktrace());
438
0
  return EvaluateDepfile(depfile, ge, this->LG, this->OutputConfig);
439
0
}
440
441
std::string cmCustomCommandGenerator::GetFullDepfile() const
442
0
{
443
0
  std::string depfile = this->GetDepfile();
444
0
  if (depfile.empty()) {
445
0
    return "";
446
0
  }
447
448
0
  if (!cmSystemTools::FileIsFullPath(depfile)) {
449
0
    depfile = cmStrCat(this->LG->GetCurrentBinaryDirectory(), '/', depfile);
450
0
  }
451
0
  return cmSystemTools::CollapseFullPath(depfile);
452
0
}
453
454
std::string cmCustomCommandGenerator::GetInternalDepfileName(
455
  std::string const& /*config*/, std::string const& depfile) const
456
0
{
457
0
  cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
458
0
  std::string extension;
459
0
  switch (*this->LG->GetGlobalGenerator()->DepfileFormat()) {
460
0
    case cmDepfileFormat::GccDepfile:
461
0
    case cmDepfileFormat::MakeDepfile:
462
0
      extension = ".d";
463
0
      break;
464
0
    case cmDepfileFormat::MSBuildAdditionalInputs:
465
0
      extension = ".AdditionalInputs";
466
0
      break;
467
0
  }
468
0
  return cmStrCat(this->LG->GetBinaryDirectory(), "/CMakeFiles/d/",
469
0
                  hash.HashString(depfile), extension);
470
0
}
471
472
std::string cmCustomCommandGenerator::GetInternalDepfile() const
473
0
{
474
0
  std::string depfile = this->GetFullDepfile();
475
0
  if (depfile.empty()) {
476
0
    return "";
477
0
  }
478
479
0
  if (this->ComputeInternalDepfile) {
480
0
    return this->ComputeInternalDepfile(this->OutputConfig, depfile);
481
0
  }
482
0
  return this->GetInternalDepfileName(this->OutputConfig, depfile);
483
0
}
484
485
cm::optional<std::string> cmCustomCommandGenerator::GetComment() const
486
0
{
487
0
  char const* comment = this->CC->GetComment();
488
0
  if (!comment) {
489
0
    return cm::nullopt;
490
0
  }
491
0
  if (!*comment) {
492
0
    return std::string();
493
0
  }
494
495
0
  cmGeneratorExpression ge(*this->LG->GetCMakeInstance(),
496
0
                           this->CC->GetBacktrace());
497
0
  return EvaluateComment(comment, ge, this->LG, this->OutputConfig);
498
0
}
499
500
std::string cmCustomCommandGenerator::GetWorkingDirectory() const
501
0
{
502
0
  return this->WorkingDirectory;
503
0
}
504
505
std::vector<std::string> const& cmCustomCommandGenerator::GetOutputs() const
506
0
{
507
0
  return this->Outputs;
508
0
}
509
510
std::vector<std::string> const& cmCustomCommandGenerator::GetByproducts() const
511
0
{
512
0
  return this->Byproducts;
513
0
}
514
515
std::vector<std::string> const& cmCustomCommandGenerator::GetDepends() const
516
0
{
517
0
  return this->Depends;
518
0
}
519
520
std::set<BT<std::pair<std::string, bool>>> const&
521
cmCustomCommandGenerator::GetUtilities() const
522
0
{
523
0
  return this->Utilities;
524
0
}
525
526
std::string cmCustomCommandGenerator::StoreContentToFile(
527
  std::string const& content) const
528
0
{
529
0
  cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
530
531
0
  std::string fileDir =
532
0
    cmStrCat(this->LG->GetBinaryDirectory(), "/CMakeFiles/c");
533
0
  if (!cmSystemTools::MakeDirectory(fileDir)) {
534
0
    return "";
535
0
  }
536
537
0
  std::string fileName = cmStrCat(fileDir, "/", hash.HashString(content));
538
539
0
  cmsys::ofstream file(fileName.c_str(), std::ios::out);
540
0
  if (!file) {
541
0
    return "";
542
0
  }
543
0
  file.write(content.data(), content.size());
544
0
  return fileName;
545
0
}