Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmGeneratorRule.cxx
Line
Count
Source
1
2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
3
   file LICENSE.rst or https://cmake.org/licensing for details.  */
4
#include "cmGeneratorRule.h"
5
6
#include <algorithm>
7
#include <iterator>
8
#include <utility>
9
#include <vector>
10
11
#include <cm/memory>
12
#include <cm/optional>
13
#include <cm/string_view>
14
#include <cmext/algorithm>
15
#include <cmext/string_view>
16
17
#include "cmCMakePath.h"
18
#include "cmCustomCommand.h"
19
#include "cmCustomCommandLines.h"
20
#include "cmDiagnostics.h"
21
#include "cmFileSet.h"
22
#include "cmGeneratorExpression.h"
23
#include "cmList.h"
24
#include "cmListFileCache.h"
25
#include "cmMakefile.h"
26
#include "cmSourceFile.h"
27
#include "cmStringAlgorithms.h"
28
#include "cmSystemTools.h"
29
#include "cmTarget.h"
30
31
namespace {
32
std::vector<cm::string_view> ReservedPatterns{ "RULE"_s,
33
                                               "TARGET"_s,
34
                                               "FILE_SET"_s,
35
                                               "SOURCE_DIR"_s,
36
                                               "BINARY_DIR"_s,
37
                                               "CURRENT_SOURCE_DIR"_s,
38
                                               "CURRENT_BINARY_DIR"_s,
39
                                               "OUTPUT_CURRENT_SOURCE_DIR"_s,
40
                                               "OUTPUT_CURRENT_BINARY_DIR"_s,
41
                                               "SOURCE"_s,
42
                                               "INPUT_DIR"_s,
43
                                               "FILE_NAME"_s,
44
                                               "BASE_NAME"_s,
45
                                               "INCLUDE_DIRECTORIES"_s,
46
                                               "COMPILE_OPTIONS"_s,
47
                                               "COMPILE_DEFINITIONS"_s };
48
}
49
50
std::string cmGeneratorRule::RulePlaceholderExpander::ExpandVariable(
51
  std::string const& variable)
52
0
{
53
0
  if (cm::contains(this->Values, variable)) {
54
0
    return this->Values[variable];
55
0
  }
56
  // If there is no variable defined, mark unresolved variable by '{' and '}'
57
0
  return cmStrCat('{', variable, '}');
58
0
}
59
60
cmGeneratorRule::RulePlaceholderExpander::VariableMap const
61
  cmGeneratorRule::DefaultProperties{
62
    { "INCLUDE_DIRECTORIES",
63
      "$<LIST:FILTER,$<FILE_SET_PROPERTY:<FILE_SET>,TARGET:<TARGET>,INCLUDE_"
64
      "DIRECTORIES>;$<SOURCE_PROPERTY:<SOURCE>,TARGET_DIRECTORY:<TARGET>,"
65
      "INCLUDE_DIRECTORIES>;$<RULE_PROPERTY:<RULE>,INCLUDE_DIRECTORIES>,"
66
      "EXCLUDE,^$>" },
67
    { "COMPILE_OPTIONS",
68
      "$<LIST:FILTER,$<RULE_PROPERTY:<RULE>,COMPILE_OPTIONS>;$<SOURCE_"
69
      "PROPERTY:<SOURCE>,"
70
      "TARGET_DIRECTORY:<TARGET>,COMPILE_OPTIONS>;$<FILE_SET_PROPERTY:<FILE_"
71
      "SET>,TARGET:<TARGET>,COMPILE_OPTIONS>,EXCLUDE,^$>" },
72
    { "COMPILE_DEFINITIONS",
73
      "$<LIST:FILTER,$<RULE_PROPERTY:<RULE>,COMPILE_DEFINITIONS>;$<SOURCE_"
74
      "PROPERTY:<"
75
      "SOURCE>,"
76
      "TARGET_DIRECTORY:<TARGET>,COMPILE_DEFINITIONS>;$<FILE_SET_PROPERTY:<"
77
      "FILE_SET>,TARGET:<TARGET>,COMPILE_DEFINITIONS>,EXCLUDE,^$>" }
78
  };
79
80
cmGeneratorRule::cmGeneratorRule(cmRule const* rule, cmTarget const* target,
81
                                 cmFileSet const* fileSet,
82
                                 cmFileSet const* outputFileSet,
83
                                 cmSourceFile const* source,
84
                                 cmRule::PatternSet const& patterns)
85
0
  : Rule(rule)
86
0
  , Target(target)
87
0
  , FileSet(fileSet)
88
0
  , OutputFileSet(outputFileSet)
89
0
  , Source(source)
90
0
  , Makefile(*fileSet->GetMakefile())
91
0
{
92
0
  this->Name = cmStrCat(rule->GetName(), '_',
93
0
                        std::hash<std::string>{}(cmStrCat(
94
0
                          rule->GetName(), '-', target->GetName(), '-',
95
0
                          fileSet->GetName(), '-', source->GetFullPath())));
96
97
0
  auto& values = this->RuleExpander.Values;
98
99
0
  cmCMakePath file = cmCMakePath{ this->Source->GetFullPath() }.Normal();
100
0
  cmCMakePath sourceDir = file.IsAbsolute()
101
0
    ? file.GetParentPath()
102
0
    : this->GetMakefile().GetCurrentSourceDirectory();
103
104
  /* clang-format off */
105
0
  values.insert({ "RULE", this->GetName() });
106
0
  values.insert({ "TARGET", this->Target->GetName() });
107
0
  values.insert({ "FILE_SET", this->FileSet->GetName() });
108
0
  values.insert({ "SOURCE_DIR", this->GetMakefile().GetHomeDirectory() });
109
0
  values.insert({ "BINARY_DIR", this->GetMakefile().GetHomeOutputDirectory() });
110
0
  values.insert({ "CURRENT_SOURCE_DIR", this->GetMakefile().GetCurrentSourceDirectory() });
111
0
  values.insert({ "CURRENT_BINARY_DIR", this->GetMakefile().GetCurrentBinaryDirectory() });
112
0
  values.insert({ "OUTPUT_CURRENT_SOURCE_DIR", outputFileSet->GetMakefile()->GetCurrentSourceDirectory() });
113
0
  values.insert({ "OUTPUT_CURRENT_BINARY_DIR", outputFileSet->GetMakefile()->GetCurrentBinaryDirectory() });
114
0
  values.insert({ "SOURCE", file.GenericString() });
115
0
  values.insert({ "INPUT_DIR", file.GetParentPath().GenericString() });
116
0
  values.insert({ "FILE_NAME", file.GetFileName().GenericString() });
117
0
  values.insert({ "BASE_NAME", file.GetFileName().RemoveWideExtension().GenericString() });
118
  /* clang-format on */
119
120
  // instantiate default properties pattern
121
0
  for (auto const& item : DefaultProperties) {
122
0
    values.insert(
123
0
      { item.first, this->RuleExpander.ExpandVariables(item.second) });
124
0
  }
125
126
0
  this->UpdateRuleExpander(patterns);
127
0
}
128
129
void cmGeneratorRule::UpdateRuleExpander(cmRule::PatternSet const& patterns)
130
0
{
131
0
  if (patterns.empty()) {
132
0
    return;
133
0
  }
134
135
0
  auto& values = this->RuleExpander.Values;
136
137
0
  for (cmRule::Pattern const& pattern : patterns) {
138
0
    if (cm::contains(ReservedPatterns, pattern.Name)) {
139
      // overwriting a reserved pattern is not allowed
140
0
      continue;
141
0
    }
142
143
0
    std::string data{ pattern.Value };
144
0
    values[pattern.Name] = this->RuleExpander.ExpandVariables(data);
145
0
  }
146
0
}
147
148
cmValue cmGeneratorRule::GetProperty(std::string const& property) const
149
0
{
150
0
  cmValue value = this->Properties.GetPropertyValue(property);
151
0
  if (value) {
152
0
    return value;
153
0
  }
154
155
0
  if (property == "OUTPUT_FILE_SET"_s) {
156
0
    this->Properties.SetProperty(
157
0
      property,
158
0
      cmList{ this->OutputFileSet->GetName(), this->OutputFileSet->GetType() }
159
0
        .to_string());
160
0
    return this->Properties.GetPropertyValue(property);
161
0
  }
162
163
0
  if (property == "WORKING_DIRECTORY") {
164
    // property was not set by the user, so use the default
165
0
    this->Properties.SetProperty(
166
0
      property, this->GetMakefile().GetCurrentBinaryDirectory());
167
0
    return this->Properties.GetPropertyValue(property);
168
0
  }
169
170
  // property not yet instantiated, retrieve it from cmRule
171
0
  value = this->Rule->GetProperty(property);
172
0
  if (value) {
173
0
    std::string expandedValue{ *value };
174
0
    this->Properties.SetProperty(
175
0
      property, this->RuleExpander.ExpandVariables(expandedValue));
176
0
    return this->Properties.GetPropertyValue(property);
177
0
  }
178
179
0
  return value;
180
0
}
181
182
std::unique_ptr<cmCustomCommand> cmGeneratorRule::CreateCustomCommand() const
183
0
{
184
0
  auto expandVariables = [this](std::string const& item) -> std::string {
185
0
    return this->RuleExpander.ExpandVariables(item);
186
0
  };
187
188
0
  auto expandVector =
189
0
    [&expandVariables](
190
0
      std::vector<std::string> const& data) -> std::vector<std::string> {
191
0
    std::vector<std::string> result;
192
0
    result.reserve(data.size());
193
0
    std::transform(data.begin(), data.end(), std::back_inserter(result),
194
0
                   expandVariables);
195
0
    return result;
196
0
  };
197
198
0
  auto expandPath =
199
0
    [&expandVariables](std::string const& binaryDirectory,
200
0
                       std::string const& data) -> std::string {
201
0
    std::string path = expandVariables(data);
202
0
    if (cmGeneratorExpression::StartsWithGeneratorExpression(path)) {
203
0
      cmSystemTools::ConvertToUnixSlashes(path);
204
0
    } else {
205
0
      if (cmSystemTools::FileIsFullPath(path)) {
206
0
        path = cmSystemTools::CollapseFullPath(path);
207
0
      } else {
208
0
        path = cmSystemTools::CollapseFullPath(path, binaryDirectory);
209
0
      }
210
0
    }
211
0
    return path;
212
0
  };
213
214
0
  auto expandPaths =
215
0
    [&expandPath](
216
0
      std::string const& binaryDirectory,
217
0
      std::vector<std::string> const& data) -> std::vector<std::string> {
218
0
    std::vector<std::string> result;
219
0
    result.reserve(data.size());
220
0
    std::transform(
221
0
      data.begin(), data.end(), std::back_inserter(result),
222
0
      [&expandPath, &binaryDirectory](std::string const& item) -> std::string {
223
0
        return expandPath(binaryDirectory, item);
224
0
      });
225
0
    return result;
226
0
  };
227
228
0
  auto cc = cm::make_unique<cmCustomCommand>();
229
230
0
  cmCustomCommandLines commandLines;
231
0
  for (cmRule::CustomCommand const& line : this->Rule->GetCommands()) {
232
0
    cmCustomCommandLine command;
233
0
    command.reserve(line.size());
234
0
    std::transform(line.begin(), line.end(), std::back_inserter(command),
235
0
                   expandVariables);
236
0
    commandLines.push_back(command);
237
0
  }
238
0
  cc->SetCommandLines(std::move(commandLines));
239
240
0
  cc->SetOutputs(expandPaths(this->GetMakefile().GetCurrentBinaryDirectory(),
241
0
                             this->Rule->GetOutputs()));
242
243
0
  if (!this->Rule->GetByproducts().empty()) {
244
0
    cc->SetByproducts(expandVector(this->Rule->GetByproducts()));
245
0
  }
246
247
0
  if (!this->Rule->GetDepfile().empty()) {
248
    // store normalized path as property
249
0
    this->Properties.SetProperty(
250
0
      "DEPFILE",
251
0
      expandPath(this->GetMakefile().GetCurrentBinaryDirectory(),
252
0
                 this->Rule->GetDepfile()));
253
0
    cc->SetDepfile(expandVariables(this->Rule->GetDepfile()));
254
0
  }
255
0
  std::vector<std::string> depends{ 1, this->Source->GetFullPath() };
256
0
  if (!this->Rule->GetDepends().empty()) {
257
0
    depends.insert(depends.end(), this->Rule->GetDepends().begin(),
258
0
                   this->Rule->GetDepends().end());
259
0
  }
260
0
  cc->SetDepends(expandVector(depends));
261
0
  if (cmValue deps_explicit =
262
0
        this->Rule->GetProperty("DEPENDS_EXPLICIT_ONLY")) {
263
0
    cc->SetDependsExplicitOnly(deps_explicit.IsOn());
264
0
  } else {
265
0
    cc->SetDependsExplicitOnly(this->GetMakefile().IsOn(
266
0
      "CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY"));
267
0
  }
268
269
0
  if (cmValue wd = this->Rule->GetProperty("WORKING_DIRECTORY")) {
270
    // store normalized path as property
271
0
    this->Properties.SetProperty(
272
0
      "WORKING_DIRECTORY",
273
0
      expandPath(this->GetMakefile().GetCurrentBinaryDirectory(), *wd));
274
0
    cc->SetWorkingDirectory(expandVariables(*wd));
275
0
  }
276
277
0
  if (this->Rule->GetProperty("USES_TERMINAL").IsOn() &&
278
0
      !this->Rule->GetProperty("JOB_POOL_COMPILE")->empty()) {
279
0
    this->GetMakefile().IssueDiagnostic(
280
0
      cmDiagnostics::CMD_AUTHOR,
281
0
      cmStrCat("RULE \"", this->GetName(), "\": JOB_POOL \"",
282
0
               expandVariables(this->Rule->GetProperty("JOB_POOL_COMPILE")),
283
0
               "\" is shadowed by USES_TERMINAL."));
284
0
  }
285
0
  if (!this->Rule->GetProperty("JOB_POOL_COMPILE")->empty()) {
286
0
    cc->SetJobPool(
287
0
      expandVariables(*this->Rule->GetProperty("JOB_POOL_COMPILE")));
288
0
  } else if (this->Rule->GetProperty("USES_TERMINAL").IsOn()) {
289
0
    cc->SetUsesTerminal(true);
290
0
  }
291
292
0
  cc->SetJobserverAware(this->Rule->GetProperty("JOB_SERVER_AWARE").IsOn());
293
294
0
  cc->SetEscapeOldStyle(!this->Rule->GetProperty("VERBATIM").IsOn());
295
0
  cc->SetCommandExpandLists(
296
0
    this->Rule->GetProperty("COMMAND_EXPAND_LISTS").IsOn());
297
298
0
  if (cmValue comment = this->Rule->GetProperty("COMMENT")) {
299
0
    cc->SetComment(expandVariables(*comment));
300
0
  }
301
302
0
  return cc;
303
0
}
304
305
std::unique_ptr<cmCustomCommand> cmGeneratorRule::Generate(
306
  cmFileSet* outFileSet) const
307
0
{
308
0
  auto cc = this->CreateCustomCommand();
309
310
  // populate file set with outputs from custom command
311
0
  for (auto const& output : cc->GetOutputs()) {
312
0
    outFileSet->AddFileEntry(BT<std::string>{ output });
313
0
  }
314
315
0
  return cc;
316
0
}