Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmAddCustomRuleCommand.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 "cmAddCustomRuleCommand.h"
4
5
#include <algorithm>
6
#include <utility>
7
8
#include <cm/memory>
9
#include <cm/optional>
10
#include <cm/string_view>
11
#include <cmext/algorithm>
12
#include <cmext/string_view>
13
14
#include "cmsys/RegularExpression.hxx"
15
16
#include "cmArgumentParser.h"
17
#include "cmArgumentParserTypes.h"
18
#include "cmExecutionStatus.h"
19
#include "cmGeneratorExpression.h"
20
#include "cmMakefile.h"
21
#include "cmRange.h"
22
#include "cmRule.h"
23
#include "cmState.h"
24
#include "cmStateTypes.h"
25
#include "cmStringAlgorithms.h"
26
#include "cmSystemTools.h"
27
28
namespace {
29
bool IsReservedName(std::string const& name)
30
0
{
31
0
  static cmsys::RegularExpression reservedNameValidator("^[A-Z_.:+-]+$");
32
33
0
  return reservedNameValidator.find(name);
34
0
}
35
36
template <typename Result>
37
class FromRuleArgumentParser : public cmArgumentParser<Result>
38
{
39
public:
40
  FromRuleArgumentParser()
41
0
    : cmArgumentParser<Result>()
42
0
  {
43
0
    this->Bind("CHAIN"_s, &Result::Chain)
44
0
      .Bind("OVERRIDE"_s, &Result::Override)
45
0
      .BindParsedKeywords(&Result::ParsedKeywords);
46
0
  }
47
};
48
}
49
50
bool cmAddCustomRuleCommand(std::vector<std::string> const& args,
51
                            cmExecutionStatus& status)
52
0
{
53
0
  if (args.size() < 2) {
54
0
    status.SetError("called with incorrect number of arguments");
55
0
    return false;
56
0
  }
57
58
  // keywords
59
0
  static cm::static_string_view const COMMAND{ "COMMAND"_s };
60
0
  static cm::static_string_view const OUTPUT{ "OUTPUT"_s };
61
0
  static cm::static_string_view const DEPENDS{ "DEPENDS"_s };
62
0
  static cm::static_string_view const DEPFILE{ "DEPFILE"_s };
63
0
  static cm::static_string_view const BYPRODUCTS{ "BYPRODUCTS"_s };
64
0
  static cm::static_string_view const GLOBAL{ "GLOBAL"_s };
65
0
  static cm::static_string_view const CONFIGURATOR{ "CONFIGURATOR"_s };
66
0
  static cm::static_string_view const FOR_FILE_SET{ "FOR_FILE_SET"_s };
67
0
  static cm::static_string_view const FOR_SOURCE{ "FOR_SOURCE"_s };
68
0
  static cm::static_string_view const FROM_RULE{ "FROM_RULE"_s };
69
70
0
  static cm::string_view const Keywords[]{ COMMAND,      OUTPUT,     DEPENDS,
71
0
                                           DEPFILE,      BYPRODUCTS, GLOBAL,
72
0
                                           CONFIGURATOR, FROM_RULE };
73
0
  cmMakefile& mf = status.GetMakefile();
74
0
  std::string const& ruleName = args[0];
75
76
  // Check the rule name.
77
0
  if (cm::contains(Keywords, ruleName)) {
78
0
    status.SetError("rule name is missing.");
79
0
    return false;
80
0
  }
81
  // check name validity
82
0
  if (IsReservedName(ruleName)) {
83
0
    status.SetError("names in all uppercase are reserved for CMake.");
84
0
    return false;
85
0
  }
86
0
  if (!cmGeneratorExpression::IsValidTargetName(ruleName)) {
87
0
    status.SetError(cmStrCat("invalid name for RULE: ", ruleName, '.'));
88
0
    return false;
89
0
  }
90
91
  // Make sure the rule does not already exist.
92
0
  if (mf.FindRuleToUse(ruleName)) {
93
0
    status.SetError(
94
0
      cmStrCat("cannot create RULE \"", ruleName,
95
0
               "\" because another RULE with the same name already exists."));
96
0
    return false;
97
0
  }
98
99
0
  struct BaseArguments : public ArgumentParser::ParseResult
100
0
  {
101
0
    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
102
0
      Configurators;
103
0
    bool Global = false;
104
0
    std::vector<cm::string_view> ParsedKeywords;
105
106
0
    cm::RuleScope GetScope()
107
0
    {
108
0
      return this->Global ? cm::RuleScope::Global : cm::RuleScope::Local;
109
0
    }
110
0
  };
111
112
0
  if (cm::contains(args, FROM_RULE)) {
113
0
    struct Arguments : public BaseArguments
114
0
    {
115
0
      std::string FromRule;
116
0
    };
117
118
0
    std::vector<std::string> unexpectedArgs;
119
0
    auto parser = cmArgumentParser<Arguments>{}
120
0
                    .Bind(FROM_RULE, &Arguments::FromRule)
121
0
                    .Bind(CONFIGURATOR, &Arguments::Configurators)
122
0
                    .Bind(GLOBAL, &Arguments::Global)
123
0
                    .BindParsedKeywords(&Arguments::ParsedKeywords);
124
0
    auto parsedArgs =
125
0
      parser.Parse(cmMakeRange(args).advance(1), &unexpectedArgs);
126
127
    // do various checks for arguments consistency
128
0
    if (!parsedArgs.Check("", &unexpectedArgs, status)) {
129
0
      cmSystemTools::SetFatalErrorOccurred();
130
0
      return false;
131
0
    }
132
133
0
    if ((std::count(parsedArgs.ParsedKeywords.cbegin(),
134
0
                    parsedArgs.ParsedKeywords.cend(), FROM_RULE) > 1) ||
135
0
        (std::count(parsedArgs.ParsedKeywords.cbegin(),
136
0
                    parsedArgs.ParsedKeywords.cend(), CONFIGURATOR) > 1)) {
137
0
      status.SetError(
138
0
        "only one occurrence of \"FROM_RULE\" or \"CONFIGURATOR\" "
139
0
        "options is allowed.");
140
0
      return false;
141
0
    }
142
143
    // Configurator syntax: <configurator> (CHAIN|OVERRIDE)
144
0
    struct ConfiguratorArguments : public ArgumentParser::ParseResult
145
0
    {
146
0
      std::string Configurator;
147
0
      bool Chain = false;
148
0
      bool Override = false;
149
0
      std::vector<cm::string_view> ParsedKeywords;
150
0
    };
151
152
    // configurators syntax: FOR_FILE_SET <configurator> <options>
153
    //                       FOR_SOURCE <configurator> <options>
154
0
    struct ConfiguratorsArguments : public ArgumentParser::ParseResult
155
0
    {
156
0
      cm::optional<ConfiguratorArguments> ForFileSet;
157
0
      cm::optional<ConfiguratorArguments> ForSource;
158
0
    } parsedConfigurators;
159
160
0
    if (parsedArgs.Configurators) {
161
0
      auto fileSetConfiguratorParser =
162
0
        FromRuleArgumentParser<ConfiguratorArguments>{}.Bind(
163
0
          FOR_FILE_SET, &ConfiguratorArguments::Configurator);
164
165
0
      auto sourceConfiguratorParser =
166
0
        FromRuleArgumentParser<ConfiguratorArguments>{}.Bind(
167
0
          FOR_SOURCE, &ConfiguratorArguments::Configurator);
168
169
0
      auto configuratorsParser =
170
0
        cmArgumentParser<ConfiguratorsArguments>{}
171
0
          .BindSubParser(FOR_FILE_SET, fileSetConfiguratorParser,
172
0
                         &ConfiguratorsArguments::ForFileSet)
173
0
          .BindSubParser(FOR_SOURCE, sourceConfiguratorParser,
174
0
                         &ConfiguratorsArguments::ForSource);
175
176
0
      unexpectedArgs.clear();
177
0
      configuratorsParser.Parse(parsedConfigurators, *parsedArgs.Configurators,
178
0
                                &unexpectedArgs);
179
180
      // do various checks for arguments consistency
181
0
      if (!parsedConfigurators.Check("", &unexpectedArgs, status)) {
182
0
        cmSystemTools::SetFatalErrorOccurred();
183
0
        return false;
184
0
      }
185
186
0
      if (!parsedConfigurators.ForFileSet && !parsedConfigurators.ForSource) {
187
0
        status.SetError(
188
0
          cmStrCat("cannot create RULE \"", ruleName,
189
0
                   "\" because the options \"FOR_FILE_SET\" or \"FOR_SOURCE\" "
190
0
                   "are expected for the \"CONFIGURATOR\" option."));
191
0
        cmSystemTools::SetFatalErrorOccurred();
192
0
        return false;
193
0
      }
194
195
0
      if ((parsedConfigurators.ForFileSet &&
196
0
           std::count(parsedConfigurators.ForFileSet->ParsedKeywords.cbegin(),
197
0
                      parsedConfigurators.ForFileSet->ParsedKeywords.cend(),
198
0
                      FOR_FILE_SET) > 1) ||
199
0
          (parsedConfigurators.ForSource &&
200
0
           std::count(parsedConfigurators.ForSource->ParsedKeywords.cbegin(),
201
0
                      parsedConfigurators.ForSource->ParsedKeywords.cend(),
202
0
                      FOR_SOURCE) > 1)) {
203
0
        status.SetError(
204
0
          "only one occurrence of \"FOR_FILE_SET\" or \"FOR_SOURCE\" "
205
0
          "sub-options of \"CONFIGURATOR\" option is allowed.");
206
0
        return false;
207
0
      }
208
209
0
      auto checkConfigurator =
210
0
        [&status, &mf,
211
0
         &ruleName](cm::optional<ConfiguratorArguments>& configurator,
212
0
                    cm::string_view type) -> bool {
213
0
        if (!configurator) {
214
0
          return true;
215
0
        }
216
0
        ConfiguratorArguments& ca = configurator.value();
217
218
0
        cm::optional<cmStateEnums::CommandType> commandType =
219
0
          mf.GetState()->GetCommandType(ca.Configurator);
220
0
        if (!commandType) {
221
0
          status.SetError(cmStrCat("command specified for \"", type,
222
0
                                   "\" does not exist: ", ca.Configurator,
223
0
                                   '.'));
224
0
          cmSystemTools::SetFatalErrorOccurred();
225
0
          return false;
226
0
        }
227
0
        if (*commandType != cmStateEnums::CommandType::Function) {
228
0
          status.SetError(cmStrCat("command specified for \"", type,
229
0
                                   "\" is not a function: ", ca.Configurator,
230
0
                                   '.'));
231
0
          cmSystemTools::SetFatalErrorOccurred();
232
0
          return false;
233
0
        }
234
0
        if (ca.Chain && ca.Override) {
235
0
          status.SetError(cmStrCat("cannot create RULE \"", ruleName,
236
0
                                   "\" because the \"CHAIN\" and \"OVERRIDE\" "
237
0
                                   "options of CONFIGURATOR \"",
238
0
                                   ca.Configurator,
239
0
                                   "\" are mutually exclusive."));
240
0
          cmSystemTools::SetFatalErrorOccurred();
241
0
          return false;
242
0
        }
243
244
0
        if (!ca.Chain && !ca.Override) {
245
0
          ca.Override = true;
246
0
        }
247
248
0
        return true;
249
0
      };
250
251
0
      if (!checkConfigurator(parsedConfigurators.ForFileSet, FOR_FILE_SET) ||
252
0
          !checkConfigurator(parsedConfigurators.ForSource, FOR_SOURCE)) {
253
0
        return false;
254
0
      }
255
0
    }
256
257
0
    cmRule const* rule = mf.FindRuleToUse(parsedArgs.FromRule);
258
0
    if (!rule) {
259
0
      status.SetError(cmStrCat("cannot create RULE \"", ruleName,
260
0
                               "\" because the RULE \"", parsedArgs.FromRule,
261
0
                               "\" does not exist or is not accessible."));
262
0
      cmSystemTools::SetFatalErrorOccurred();
263
0
      return false;
264
0
    }
265
266
0
    auto newRule = cm::make_unique<cmSpecializedRule>(mf, ruleName, *rule,
267
0
                                                      parsedArgs.GetScope());
268
269
0
    if (parsedArgs.Configurators) {
270
0
      if (parsedConfigurators.ForFileSet) {
271
0
        newRule->SetConfigurator(
272
0
          cmRule::ConfiguratorType::FileSet,
273
0
          std::move(parsedConfigurators.ForFileSet->Configurator),
274
0
          parsedConfigurators.ForFileSet->Chain
275
0
            ? cmRule::ChainConfigurators::Yes
276
0
            : cmRule::ChainConfigurators::No);
277
0
      }
278
0
      if (parsedConfigurators.ForSource) {
279
0
        newRule->SetConfigurator(
280
0
          cmRule::ConfiguratorType::Source,
281
0
          std::move(parsedConfigurators.ForSource->Configurator),
282
0
          parsedConfigurators.ForSource->Chain
283
0
            ? cmRule::ChainConfigurators::Yes
284
0
            : cmRule::ChainConfigurators::No);
285
0
      }
286
0
    }
287
288
0
    mf.AddRule(std::move(newRule));
289
290
0
    return true;
291
0
  }
292
293
0
  struct Arguments : public BaseArguments
294
0
  {
295
0
    ArgumentParser::NonEmpty<std::vector<std::string>> Output;
296
0
    cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>>
297
0
      Byproducts;
298
0
    ArgumentParser::NonEmpty<std::vector<std::vector<std::string>>> Commands;
299
0
    cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Depends;
300
0
    cm::optional<std::string> Depfile;
301
0
  };
302
303
0
  std::vector<std::string> unexpectedArgs;
304
0
  auto parser = cmArgumentParser<Arguments>{}
305
0
                  .Bind(OUTPUT, &Arguments::Output)
306
0
                  .Bind(BYPRODUCTS, &Arguments::Byproducts)
307
0
                  .Bind(COMMAND, &Arguments::Commands)
308
0
                  .Bind(DEPENDS, &Arguments::Depends)
309
0
                  .Bind(DEPFILE, &Arguments::Depfile)
310
0
                  .Bind(CONFIGURATOR, &Arguments::Configurators)
311
0
                  .Bind(GLOBAL, &Arguments::Global)
312
0
                  .BindParsedKeywords(&Arguments::ParsedKeywords);
313
314
0
  auto parsedArgs =
315
0
    parser.Parse(cmMakeRange(args).advance(1), &unexpectedArgs);
316
317
  // do various checks for arguments consistency
318
0
  if (!parsedArgs.Check("", &unexpectedArgs, status)) {
319
0
    cmSystemTools::SetFatalErrorOccurred();
320
0
    return false;
321
0
  }
322
323
0
  if (parsedArgs.Commands.empty() || parsedArgs.Output.empty()) {
324
0
    status.SetError(cmStrCat(
325
0
      "cannot create RULE \"", ruleName,
326
0
      "\" because the mandatory options \"COMMAND\" or \"OUTPUT\" are "
327
0
      "missing."));
328
0
    cmSystemTools::SetFatalErrorOccurred();
329
0
    return false;
330
0
  }
331
332
0
  if ((std::count(parsedArgs.ParsedKeywords.cbegin(),
333
0
                  parsedArgs.ParsedKeywords.cend(), DEPFILE) > 1) ||
334
0
      (std::count(parsedArgs.ParsedKeywords.cbegin(),
335
0
                  parsedArgs.ParsedKeywords.cend(), CONFIGURATOR) > 1)) {
336
0
    status.SetError("only one occurrence of \"DEPFILE\" or \"CONFIGURATOR\" "
337
0
                    "options is allowed.");
338
0
    return false;
339
0
  }
340
341
0
  struct ConfiguratorsArguments : public ArgumentParser::ParseResult
342
0
  {
343
0
    cm::optional<std::string> ForFileSet;
344
0
    cm::optional<std::string> ForSource;
345
0
    std::vector<cm::string_view> ParsedKeywords;
346
0
  } parsedConfigurators;
347
348
0
  if (parsedArgs.Configurators) {
349
    // parse the arguments of CONFIGURATOR option
350
    // CONFIGURATOR syntax: FOR_FILE_SET <configurator>
351
    //                      FOR_SOURCE <configurator>
352
0
    auto configuratorsParser =
353
0
      cmArgumentParser<ConfiguratorsArguments>{}
354
0
        .Bind(FOR_FILE_SET, &ConfiguratorsArguments::ForFileSet)
355
0
        .Bind(FOR_SOURCE, &ConfiguratorsArguments::ForSource)
356
0
        .BindParsedKeywords(&ConfiguratorsArguments::ParsedKeywords);
357
358
0
    unexpectedArgs.clear();
359
0
    configuratorsParser.Parse(parsedConfigurators, *parsedArgs.Configurators,
360
0
                              &unexpectedArgs);
361
362
    // do various checks for arguments consistency
363
0
    if (!parsedConfigurators.Check("", &unexpectedArgs, status)) {
364
0
      cmSystemTools::SetFatalErrorOccurred();
365
0
      return false;
366
0
    }
367
368
0
    if ((std::count(parsedConfigurators.ParsedKeywords.cbegin(),
369
0
                    parsedConfigurators.ParsedKeywords.cend(),
370
0
                    FOR_FILE_SET) > 1) ||
371
0
        (std::count(parsedConfigurators.ParsedKeywords.cbegin(),
372
0
                    parsedConfigurators.ParsedKeywords.cend(),
373
0
                    FOR_SOURCE) > 1)) {
374
0
      status.SetError(
375
0
        "only one occurrence of \"FOR_FILE_SET\" or \"FOR_SOURCE\" "
376
0
        "sub-options of \"CONFIGURATOR\" option is allowed.");
377
0
      return false;
378
0
    }
379
380
0
    if (!parsedConfigurators.ForFileSet && !parsedConfigurators.ForSource) {
381
0
      status.SetError(cmStrCat(
382
0
        "cannot create RULE \"", ruleName,
383
0
        "\" because the options \"FOR_FILE_SET\" or \"FOR_SOURCE\" are "
384
0
        "expected for the \"CONFIGURATOR\" option."));
385
0
      cmSystemTools::SetFatalErrorOccurred();
386
0
      return false;
387
0
    }
388
389
0
    auto checkConfigurator =
390
0
      [&status, &mf](cm::optional<std::string> const& configurator,
391
0
                     cm::string_view type) -> bool {
392
0
      if (!configurator) {
393
0
        return true;
394
0
      }
395
396
0
      auto commandType = mf.GetState()->GetCommandType(*configurator);
397
0
      if (!commandType) {
398
0
        status.SetError(cmStrCat("command specified for \"", type,
399
0
                                 "\" does not exist: ", *configurator, '.'));
400
0
        cmSystemTools::SetFatalErrorOccurred();
401
0
        return false;
402
0
      }
403
0
      if (*commandType != cmStateEnums::CommandType::Function) {
404
0
        status.SetError(cmStrCat("command specified for \"", type,
405
0
                                 "\" is not a function: ", *configurator,
406
0
                                 '.'));
407
0
        cmSystemTools::SetFatalErrorOccurred();
408
0
        return false;
409
0
      }
410
0
      return true;
411
0
    };
412
413
0
    if (!checkConfigurator(parsedConfigurators.ForFileSet, FOR_FILE_SET) ||
414
0
        !checkConfigurator(parsedConfigurators.ForSource, FOR_SOURCE)) {
415
0
      return false;
416
0
    }
417
0
  }
418
419
0
  auto rule =
420
0
    cm::make_unique<cmCustomRule>(mf, ruleName, parsedArgs.Commands,
421
0
                                  parsedArgs.Output, parsedArgs.GetScope());
422
0
  if (parsedArgs.Byproducts) {
423
0
    rule->SetByproducts(std::move(*parsedArgs.Byproducts));
424
0
  }
425
0
  if (parsedArgs.Depends) {
426
0
    rule->SetDepends(std::move(*parsedArgs.Depends));
427
0
  }
428
0
  if (parsedArgs.Depfile) {
429
0
    rule->SetDepfile(std::move(*parsedArgs.Depfile));
430
0
  }
431
0
  if (parsedArgs.Configurators) {
432
0
    if (parsedConfigurators.ForFileSet) {
433
0
      rule->SetConfigurator(cmRule::ConfiguratorType::FileSet,
434
0
                            std::move(*parsedConfigurators.ForFileSet));
435
0
    }
436
0
    if (parsedConfigurators.ForSource) {
437
0
      rule->SetConfigurator(cmRule::ConfiguratorType::Source,
438
0
                            std::move(*parsedConfigurators.ForSource));
439
0
    }
440
0
  }
441
442
0
  mf.AddRule(std::move(rule));
443
444
0
  return true;
445
0
}