Coverage Report

Created: 2026-02-09 06:05

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