Coverage Report

Created: 2026-07-30 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmCMakeLanguageCommand.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 "cmCMakeLanguageCommand.h"
4
5
#include <algorithm>
6
#include <array>
7
#include <cstddef>
8
#include <map>
9
#include <memory>
10
#include <string>
11
#include <unordered_map>
12
#include <utility>
13
#include <vector>
14
15
#include <cm/optional>
16
#include <cm/string_view>
17
#include <cmext/string_view>
18
19
#include "cmsys/RegularExpression.hxx"
20
21
#include "cmArgumentParser.h"
22
#include "cmArgumentParserTypes.h"
23
#include "cmDependencyProvider.h"
24
#include "cmExecutionStatus.h"
25
#include "cmExperimental.h"
26
#include "cmGlobalGenerator.h"
27
#include "cmListFileCache.h"
28
#include "cmMakefile.h"
29
#include "cmMessageType.h" // IWYU pragma: keep
30
#include "cmRange.h"
31
#include "cmState.h"
32
#include "cmStringAlgorithms.h"
33
#include "cmSystemTools.h"
34
#include "cmTarget.h"
35
#include "cmValue.h"
36
#include "cmake.h"
37
38
namespace cm {
39
enum class TargetType;
40
}
41
42
namespace {
43
44
bool FatalError(cmExecutionStatus& status, std::string const& error)
45
0
{
46
0
  status.SetError(error);
47
0
  cmSystemTools::SetFatalErrorOccurred();
48
0
  return false;
49
0
}
50
51
std::array<cm::static_string_view, 14> InvalidCommands{
52
  { // clang-format off
53
  "function"_s, "endfunction"_s,
54
  "macro"_s, "endmacro"_s,
55
  "if"_s, "elseif"_s, "else"_s, "endif"_s,
56
  "while"_s, "endwhile"_s,
57
  "foreach"_s, "endforeach"_s,
58
  "block"_s, "endblock"_s
59
  } // clang-format on
60
};
61
62
std::array<cm::static_string_view, 1> InvalidDeferCommands{
63
  {
64
    // clang-format off
65
  "return"_s,
66
  } // clang-format on
67
};
68
69
struct Defer
70
{
71
  std::string Id;
72
  std::string IdVar;
73
  cmMakefile* Directory = nullptr;
74
};
75
76
bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
77
                                std::string const& callCommand,
78
                                size_t startArg, cm::optional<Defer> defer,
79
                                cmExecutionStatus& status)
80
0
{
81
  // ensure specified command is valid
82
  // start/end flow control commands are not allowed
83
0
  auto cmd = cmSystemTools::LowerCase(callCommand);
84
0
  if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) !=
85
0
      InvalidCommands.cend()) {
86
0
    return FatalError(status,
87
0
                      cmStrCat("invalid command specified: "_s, callCommand));
88
0
  }
89
0
  if (defer &&
90
0
      std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(),
91
0
                cmd) != InvalidDeferCommands.cend()) {
92
0
    return FatalError(status,
93
0
                      cmStrCat("invalid command specified: "_s, callCommand));
94
0
  }
95
96
0
  cmMakefile& makefile = status.GetMakefile();
97
0
  cmListFileContext context = makefile.GetBacktrace().Top();
98
99
0
  std::vector<cmListFileArgument> funcArgs;
100
0
  funcArgs.reserve(args.size() - startArg);
101
102
  // The rest of the arguments are passed to the function call above
103
0
  for (size_t i = startArg; i < args.size(); ++i) {
104
0
    funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line);
105
0
  }
106
0
  cmListFileFunction func{ callCommand, context.Line, context.Line,
107
0
                           std::move(funcArgs) };
108
109
0
  if (defer) {
110
0
    if (defer->Id.empty()) {
111
0
      defer->Id = makefile.NewDeferId();
112
0
    }
113
0
    if (!defer->IdVar.empty()) {
114
0
      makefile.AddDefinition(defer->IdVar, defer->Id);
115
0
    }
116
0
    cmMakefile* deferMakefile =
117
0
      defer->Directory ? defer->Directory : &makefile;
118
0
    if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) {
119
0
      return FatalError(
120
0
        status,
121
0
        cmStrCat("DEFER CALL may not be scheduled in directory:\n  "_s,
122
0
                 deferMakefile->GetCurrentBinaryDirectory(),
123
0
                 "\nat this time."_s));
124
0
    }
125
0
    return true;
126
0
  }
127
0
  return makefile.ExecuteCommand(func, status);
128
0
}
129
130
bool cmCMakeLanguageCommandDEFER(Defer const& defer,
131
                                 std::vector<std::string> const& args,
132
                                 size_t arg, cmExecutionStatus& status)
133
0
{
134
0
  cmMakefile* deferMakefile =
135
0
    defer.Directory ? defer.Directory : &status.GetMakefile();
136
0
  if (args[arg] == "CANCEL_CALL"_s) {
137
0
    ++arg; // Consume CANCEL_CALL.
138
0
    auto ids = cmMakeRange(args).advance(arg);
139
0
    for (std::string const& id : ids) {
140
0
      if (id[0] >= 'A' && id[0] <= 'Z') {
141
0
        return FatalError(
142
0
          status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n  "_s, id));
143
0
      }
144
0
      if (!deferMakefile->DeferCancelCall(id)) {
145
0
        return FatalError(
146
0
          status,
147
0
          cmStrCat("DEFER CANCEL_CALL may not update directory:\n  "_s,
148
0
                   deferMakefile->GetCurrentBinaryDirectory(),
149
0
                   "\nat this time."_s));
150
0
      }
151
0
    }
152
0
    return true;
153
0
  }
154
0
  if (args[arg] == "GET_CALL_IDS"_s) {
155
0
    ++arg; // Consume GET_CALL_IDS.
156
0
    if (arg == args.size()) {
157
0
      return FatalError(status, "DEFER GET_CALL_IDS missing output variable");
158
0
    }
159
0
    std::string const& var = args[arg++];
160
0
    if (arg != args.size()) {
161
0
      return FatalError(status, "DEFER GET_CALL_IDS given too many arguments");
162
0
    }
163
0
    cm::optional<std::string> ids = deferMakefile->DeferGetCallIds();
164
0
    if (!ids) {
165
0
      return FatalError(
166
0
        status,
167
0
        cmStrCat("DEFER GET_CALL_IDS may not access directory:\n  "_s,
168
0
                 deferMakefile->GetCurrentBinaryDirectory(),
169
0
                 "\nat this time."_s));
170
0
    }
171
0
    status.GetMakefile().AddDefinition(var, *ids);
172
0
    return true;
173
0
  }
174
0
  if (args[arg] == "GET_CALL"_s) {
175
0
    ++arg; // Consume GET_CALL.
176
0
    if (arg == args.size()) {
177
0
      return FatalError(status, "DEFER GET_CALL missing id");
178
0
    }
179
0
    std::string const& id = args[arg++];
180
0
    if (arg == args.size()) {
181
0
      return FatalError(status, "DEFER GET_CALL missing output variable");
182
0
    }
183
0
    std::string const& var = args[arg++];
184
0
    if (arg != args.size()) {
185
0
      return FatalError(status, "DEFER GET_CALL given too many arguments");
186
0
    }
187
0
    if (id.empty()) {
188
0
      return FatalError(status, "DEFER GET_CALL id may not be empty");
189
0
    }
190
0
    if (id[0] >= 'A' && id[0] <= 'Z') {
191
0
      return FatalError(status,
192
0
                        cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id));
193
0
    }
194
0
    cm::optional<std::string> call = deferMakefile->DeferGetCall(id);
195
0
    if (!call) {
196
0
      return FatalError(
197
0
        status,
198
0
        cmStrCat("DEFER GET_CALL may not access directory:\n  "_s,
199
0
                 deferMakefile->GetCurrentBinaryDirectory(),
200
0
                 "\nat this time."_s));
201
0
    }
202
0
    status.GetMakefile().AddDefinition(var, *call);
203
0
    return true;
204
0
  }
205
0
  return FatalError(status,
206
0
                    cmStrCat("DEFER operation unknown: "_s, args[arg]));
207
0
}
208
209
bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
210
                                cmExecutionStatus& status)
211
0
{
212
0
  cmMakefile& makefile = status.GetMakefile();
213
0
  cmListFileContext context = makefile.GetBacktrace().Top();
214
0
  std::vector<std::string> expandedArgs;
215
0
  makefile.ExpandArguments(args, expandedArgs);
216
217
0
  if (expandedArgs.size() < 2) {
218
0
    return FatalError(status, "called with incorrect number of arguments");
219
0
  }
220
221
0
  if (expandedArgs[1] != "CODE") {
222
0
    auto code_iter =
223
0
      std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE");
224
0
    if (code_iter == expandedArgs.end()) {
225
0
      return FatalError(status, "called without CODE argument");
226
0
    }
227
0
    return FatalError(
228
0
      status,
229
0
      "called with unsupported arguments between EVAL and CODE arguments");
230
0
  }
231
232
0
  std::string const code =
233
0
    cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " ");
234
0
  return makefile.ReadListFileAsString(
235
0
    code, cmStrCat(context.FilePath, ':', context.Line, ":EVAL"));
236
0
}
237
238
bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(
239
  std::vector<std::string> const& args, cmExecutionStatus& status)
240
0
{
241
0
  cmState* state = status.GetMakefile().GetState();
242
0
  if (!state->InTopLevelIncludes()) {
243
0
    return FatalError(
244
0
      status,
245
0
      "Dependency providers can only be set as part of the first call to "
246
0
      "project(). More specifically, cmake_language(SET_DEPENDENCY_PROVIDER) "
247
0
      "can only be called while the first project() command processes files "
248
0
      "listed in CMAKE_PROJECT_TOP_LEVEL_INCLUDES.");
249
0
  }
250
251
0
  struct SetProviderArgs
252
0
  {
253
0
    std::string Command;
254
0
    ArgumentParser::NonEmpty<std::vector<std::string>> Methods;
255
0
  };
256
257
0
  auto const ArgsParser =
258
0
    cmArgumentParser<SetProviderArgs>()
259
0
      .Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command)
260
0
      .Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods);
261
262
0
  std::vector<std::string> unparsed;
263
0
  auto parsedArgs = ArgsParser.Parse(args, &unparsed);
264
265
0
  if (!unparsed.empty()) {
266
0
    return FatalError(
267
0
      status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), '"'));
268
0
  }
269
270
  // We store the command that FetchContent_MakeAvailable() can call in a
271
  // global (but considered internal) property. If the provider doesn't
272
  // support this method, we set this property to an empty string instead.
273
  // This simplifies the logic in FetchContent_MakeAvailable() and doesn't
274
  // require us to define a new internal command or sub-command.
275
0
  std::string fcmasProperty = "__FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER";
276
277
0
  if (parsedArgs.Command.empty()) {
278
0
    if (!parsedArgs.Methods.empty()) {
279
0
      return FatalError(status,
280
0
                        "Must specify a non-empty command name when provider "
281
0
                        "methods are given");
282
0
    }
283
0
    state->ClearDependencyProvider();
284
0
    state->SetGlobalProperty(fcmasProperty, "");
285
0
    return true;
286
0
  }
287
288
0
  cmState::Command command = state->GetCommand(parsedArgs.Command);
289
0
  if (!command) {
290
0
    return FatalError(status,
291
0
                      cmStrCat("Command \"", parsedArgs.Command,
292
0
                               "\" is not a defined command"));
293
0
  }
294
295
0
  if (parsedArgs.Methods.empty()) {
296
0
    return FatalError(status, "Must specify at least one provider method");
297
0
  }
298
299
0
  bool supportsFetchContentMakeAvailableSerial = false;
300
0
  std::vector<cmDependencyProvider::Method> methods;
301
0
  for (auto const& method : parsedArgs.Methods) {
302
0
    if (method == "FIND_PACKAGE") {
303
0
      methods.emplace_back(cmDependencyProvider::Method::FindPackage);
304
0
    } else if (method == "FETCHCONTENT_MAKEAVAILABLE_SERIAL") {
305
0
      supportsFetchContentMakeAvailableSerial = true;
306
0
      methods.emplace_back(
307
0
        cmDependencyProvider::Method::FetchContentMakeAvailableSerial);
308
0
    } else {
309
0
      return FatalError(
310
0
        status,
311
0
        cmStrCat("Unknown dependency provider method \"", method, '"'));
312
0
    }
313
0
  }
314
315
0
  state->SetDependencyProvider({ parsedArgs.Command, methods });
316
0
  state->SetGlobalProperty(
317
0
    fcmasProperty,
318
0
    supportsFetchContentMakeAvailableSerial ? parsedArgs.Command : "");
319
320
0
  return true;
321
0
}
322
323
bool cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(
324
  std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
325
0
{
326
0
  cmMakefile& makefile = status.GetMakefile();
327
0
  std::vector<std::string> expandedArgs;
328
0
  makefile.ExpandArguments(args, expandedArgs);
329
330
0
  if (args.size() < 2 || expandedArgs.size() > 2) {
331
0
    return FatalError(
332
0
      status,
333
0
      "sub-command GET_MESSAGE_LOG_LEVEL expects exactly one argument");
334
0
  }
335
336
0
  Message::LogLevel logLevel = makefile.GetCurrentLogLevel();
337
0
  std::string outputValue = cmake::LogLevelToString(logLevel);
338
339
0
  std::string const& outputVariable = expandedArgs[1];
340
0
  makefile.AddDefinition(outputVariable, outputValue);
341
0
  return true;
342
0
}
343
344
bool cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(
345
  std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
346
0
{
347
0
  cmMakefile& makefile = status.GetMakefile();
348
0
  std::vector<std::string> expandedArgs;
349
0
  makefile.ExpandArguments(args, expandedArgs);
350
351
0
  if (expandedArgs.size() != 3) {
352
0
    return FatalError(status,
353
0
                      "sub-command GET_EXPERIMENTAL_FEATURE_ENABLED expects "
354
0
                      "exactly two arguments");
355
0
  }
356
357
0
  auto const& featureName = expandedArgs[1];
358
0
  auto const& variableName = expandedArgs[2];
359
360
0
  if (auto feature = cmExperimental::FeatureByName(featureName)) {
361
0
    if (cmExperimental::HasSupportEnabled(makefile, *feature)) {
362
0
      makefile.AddDefinition(variableName, "TRUE");
363
0
    } else {
364
0
      makefile.AddDefinition(variableName, "FALSE");
365
0
    }
366
0
  } else {
367
0
    return FatalError(status,
368
0
                      cmStrCat("Experimental feature name \"", featureName,
369
0
                               "\" does not exist."));
370
0
  }
371
372
0
  return true;
373
0
}
374
375
struct PrintTargetsArgs : public ArgumentParser::ParseResult
376
{
377
  cm::optional<std::string> Regex;
378
  bool ImportedOnly = false;
379
  bool NoImported = false;
380
  bool IgnoreCase = false;
381
  cm::optional<std::string> MessagePrefix;
382
};
383
384
// Lists every target that currently exists, optionally filtered by a
385
// name REGEX and by imported state.  "Currently exists" means anything
386
// CMake has defined up to this call: targets in the current directory,
387
// its ancestors, and any already-processed subdirectories.  Walking the
388
// global generator's makefiles captures exactly that set; a name-keyed
389
// map sorts the output and dedupes imported targets, which are inherited
390
// into child makefiles and would otherwise be seen many times.
391
bool cmCMakeLanguageCommandPRINT_TARGETS(
392
  std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
393
0
{
394
0
  cmMakefile& makefile = status.GetMakefile();
395
0
  std::vector<std::string> expandedArgs;
396
0
  makefile.ExpandArguments(args, expandedArgs);
397
398
  // Drop the leading "PRINT_TARGETS" subcommand keyword.
399
0
  std::vector<std::string> body(expandedArgs.begin() + 1, expandedArgs.end());
400
401
0
  auto const ArgsParser =
402
0
    cmArgumentParser<PrintTargetsArgs>()
403
0
      .Bind("REGEX"_s, &PrintTargetsArgs::Regex)
404
0
      .Bind("IMPORTED_ONLY"_s, &PrintTargetsArgs::ImportedOnly)
405
0
      .Bind("NO_IMPORTED"_s, &PrintTargetsArgs::NoImported)
406
0
      .Bind("IGNORE_CASE"_s, &PrintTargetsArgs::IgnoreCase)
407
0
      .Bind("__MESSAGE_PREFIX"_s, &PrintTargetsArgs::MessagePrefix);
408
409
0
  std::vector<std::string> unparsed;
410
0
  auto parsedArgs = ArgsParser.Parse(body, &unparsed);
411
412
0
  if (!unparsed.empty()) {
413
0
    return FatalError(
414
0
      status,
415
0
      cmStrCat(
416
0
        "Unknown argument(s) given to cmake_language(PRINT_TARGETS) call: \"",
417
0
        cmJoin(unparsed, "\" \""), "\"."));
418
0
  }
419
0
  if (parsedArgs.MaybeReportError(makefile)) {
420
0
    cmSystemTools::SetFatalErrorOccurred();
421
0
    return true;
422
0
  }
423
424
0
  if (parsedArgs.ImportedOnly && parsedArgs.NoImported) {
425
0
    return FatalError(status,
426
0
                      "IMPORTED_ONLY and NO_IMPORTED keywords are mutually "
427
0
                      "exclusive in cmake_language(PRINT_TARGETS) call.");
428
0
  }
429
430
0
  if (parsedArgs.IgnoreCase && !parsedArgs.Regex) {
431
0
    return FatalError(status,
432
0
                      "IGNORE_CASE keyword in cmake_language(PRINT_TARGETS) "
433
0
                      "call is only valid with REGEX.");
434
0
  }
435
436
  // Compile the optional REGEX up front so a bad pattern fails fast.  With
437
  // IGNORE_CASE the pattern and the candidate names are both lower-cased.
438
0
  cm::optional<cmsys::RegularExpression> regex;
439
0
  if (parsedArgs.Regex) {
440
0
    cmsys::RegularExpression re;
441
0
    std::string const pat = parsedArgs.IgnoreCase
442
0
      ? cmSystemTools::LowerCase(*parsedArgs.Regex)
443
0
      : *parsedArgs.Regex;
444
0
    if (!re.compile(pat)) {
445
0
      return FatalError(status,
446
0
                        cmStrCat("REGEX regular expression \"",
447
0
                                 *parsedArgs.Regex, "\" cannot compile."));
448
0
    }
449
0
    regex = std::move(re);
450
0
  }
451
452
0
  bool const includeNormal = !parsedArgs.ImportedOnly;
453
0
  bool const includeImported = !parsedArgs.NoImported;
454
455
0
  struct TargetInfo
456
0
  {
457
0
    cm::TargetType Type;
458
0
    bool Imported;
459
0
  };
460
0
  std::map<std::string, TargetInfo> targets;
461
0
  for (auto const& mf : makefile.GetGlobalGenerator()->GetMakefiles()) {
462
0
    if (includeNormal) {
463
0
      for (auto const& ti : mf->GetTargets()) {
464
0
        cmTarget const& t = ti.second;
465
0
        targets.insert({ t.GetName(), { t.GetType(), false } });
466
0
      }
467
0
    }
468
0
    if (includeImported) {
469
0
      for (cmTarget const* t : mf->GetImportedTargets()) {
470
0
        targets.insert({ t->GetName(), { t->GetType(), true } });
471
0
      }
472
0
    }
473
0
  }
474
475
  // Build the body first so the header is suppressed when a REGEX filters
476
  // everything out (matches cmake_language(PRINT_VARIABLES) behavior).
477
0
  std::string lines;
478
0
  bool anyMatched = false;
479
0
  for (auto const& t : targets) {
480
0
    if (regex) {
481
0
      std::string const subj =
482
0
        parsedArgs.IgnoreCase ? cmSystemTools::LowerCase(t.first) : t.first;
483
0
      if (!regex->find(subj)) {
484
0
        continue;
485
0
      }
486
0
    }
487
0
    lines +=
488
0
      cmStrCat("   ", t.first, " (", cmState::GetTargetTypeName(t.second.Type),
489
0
               t.second.Imported ? ", IMPORTED" : "", ")\n");
490
0
    anyMatched = true;
491
0
  }
492
493
0
  if (anyMatched) {
494
    // The message opens with a banner line.  The internal __MESSAGE_PREFIX
495
    // keyword overrides it (used by wrappers to reproduce legacy output); it
496
    // is not part of the public interface.
497
0
    std::string const messagePrefix =
498
0
      parsedArgs.MessagePrefix.value_or("Printing targets...\n");
499
    // The header reflects the imported-filter mode and any REGEX in effect.
500
0
    char const* label = "All targets";
501
0
    if (parsedArgs.ImportedOnly) {
502
0
      label = "Imported targets";
503
0
    } else if (parsedArgs.NoImported) {
504
0
      label = "Non-imported targets";
505
0
    }
506
0
    std::string out = cmStrCat(messagePrefix, " ", label);
507
0
    if (parsedArgs.Regex) {
508
0
      out += cmStrCat(
509
0
        " matching REGEX '", *parsedArgs.Regex, "' (",
510
0
        parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive", ")");
511
0
    }
512
0
    out += cmStrCat(":\n", lines);
513
0
    makefile.DisplayStatus(out, -1);
514
0
  }
515
516
0
  if (!anyMatched && parsedArgs.Regex) {
517
0
    makefile.IssueMessage(
518
0
      MessageType::WARNING,
519
0
      cmStrCat("No targets matching REGEX '", *parsedArgs.Regex, "' (",
520
0
               parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive",
521
0
               ") in cmake_language(PRINT_TARGETS ...)."));
522
0
  }
523
0
  return true;
524
0
}
525
}
526
527
bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
528
                            cmExecutionStatus& status)
529
0
{
530
0
  std::vector<std::string> expArgs;
531
0
  size_t rawArg = 0;
532
0
  size_t expArg = 0;
533
534
  // Helper to consume and expand one raw argument at a time.
535
0
  auto moreArgs = [&]() -> bool {
536
0
    while (expArg >= expArgs.size()) {
537
0
      if (rawArg >= args.size()) {
538
0
        return false;
539
0
      }
540
0
      std::vector<cmListFileArgument> tmpArg;
541
0
      tmpArg.emplace_back(args[rawArg++]);
542
0
      status.GetMakefile().ExpandArguments(tmpArg, expArgs);
543
0
    }
544
0
    return true;
545
0
  };
546
0
  auto finishArgs = [&]() {
547
0
    std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
548
0
    status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
549
0
    rawArg = args.size();
550
0
  };
551
552
0
  if (!moreArgs()) {
553
0
    return FatalError(status, "called with incorrect number of arguments");
554
0
  }
555
0
  if (expArgs[expArg] == "EXIT"_s) {
556
0
    ++expArg; // consume "EXIT".
557
558
0
    if (!moreArgs()) {
559
0
      return FatalError(status, "EXIT requires one argument");
560
0
    }
561
562
0
    if (!status.GetMakefile().GetCMakeInstance()->RoleSupportsExitCode()) {
563
0
      return FatalError(status, "EXIT can be used only in SCRIPT mode");
564
0
    }
565
566
0
    long retCode = 0;
567
568
0
    if (!cmStrToLong(expArgs[expArg], &retCode)) {
569
0
      return FatalError(status,
570
0
                        cmStrCat("EXIT requires one integral argument, got \"",
571
0
                                 expArgs[expArg], '\"'));
572
0
    }
573
574
0
    status.SetExitCode(static_cast<int>(retCode));
575
0
    return true;
576
0
  }
577
578
0
  if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) {
579
0
    finishArgs();
580
0
    return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status);
581
0
  }
582
583
0
  cm::optional<Defer> maybeDefer;
584
0
  if (expArgs[expArg] == "DEFER"_s) {
585
0
    ++expArg; // Consume "DEFER".
586
587
0
    if (!moreArgs()) {
588
0
      return FatalError(status, "DEFER requires at least one argument");
589
0
    }
590
591
0
    Defer defer;
592
593
    // Process optional arguments.
594
0
    while (moreArgs()) {
595
0
      if (expArgs[expArg] == "CALL"_s) {
596
0
        break;
597
0
      }
598
0
      if (expArgs[expArg] == "CANCEL_CALL"_s ||
599
0
          expArgs[expArg] == "GET_CALL_IDS"_s ||
600
0
          expArgs[expArg] == "GET_CALL"_s) {
601
0
        if (!defer.Id.empty() || !defer.IdVar.empty()) {
602
0
          return FatalError(status,
603
0
                            cmStrCat("DEFER "_s, expArgs[expArg],
604
0
                                     " does not accept ID or ID_VAR."_s));
605
0
        }
606
0
        finishArgs();
607
0
        return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
608
0
      }
609
0
      if (expArgs[expArg] == "DIRECTORY"_s) {
610
0
        ++expArg; // Consume "DIRECTORY".
611
0
        if (defer.Directory) {
612
0
          return FatalError(status,
613
0
                            "DEFER given multiple DIRECTORY arguments");
614
0
        }
615
0
        if (!moreArgs()) {
616
0
          return FatalError(status, "DEFER DIRECTORY missing value");
617
0
        }
618
0
        std::string dir = expArgs[expArg++];
619
0
        if (dir.empty()) {
620
0
          return FatalError(status, "DEFER DIRECTORY may not be empty");
621
0
        }
622
0
        dir = cmSystemTools::CollapseFullPath(
623
0
          dir, status.GetMakefile().GetCurrentSourceDirectory());
624
0
        defer.Directory =
625
0
          status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
626
0
        if (!defer.Directory) {
627
0
          return FatalError(status,
628
0
                            cmStrCat("DEFER DIRECTORY:\n  "_s, dir,
629
0
                                     "\nis not known.  "
630
0
                                     "It may not have been processed yet."_s));
631
0
        }
632
0
      } else if (expArgs[expArg] == "ID"_s) {
633
0
        ++expArg; // Consume "ID".
634
0
        if (!defer.Id.empty()) {
635
0
          return FatalError(status, "DEFER given multiple ID arguments");
636
0
        }
637
0
        if (!moreArgs()) {
638
0
          return FatalError(status, "DEFER ID missing value");
639
0
        }
640
0
        defer.Id = expArgs[expArg++];
641
0
        if (defer.Id.empty()) {
642
0
          return FatalError(status, "DEFER ID may not be empty");
643
0
        }
644
0
        if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
645
0
          return FatalError(status, "DEFER ID may not start in A-Z.");
646
0
        }
647
0
      } else if (expArgs[expArg] == "ID_VAR"_s) {
648
0
        ++expArg; // Consume "ID_VAR".
649
0
        if (!defer.IdVar.empty()) {
650
0
          return FatalError(status, "DEFER given multiple ID_VAR arguments");
651
0
        }
652
0
        if (!moreArgs()) {
653
0
          return FatalError(status, "DEFER ID_VAR missing variable name");
654
0
        }
655
0
        defer.IdVar = expArgs[expArg++];
656
0
        if (defer.IdVar.empty()) {
657
0
          return FatalError(status, "DEFER ID_VAR may not be empty");
658
0
        }
659
0
      } else {
660
0
        return FatalError(
661
0
          status, cmStrCat("DEFER unknown option:\n  "_s, expArgs[expArg]));
662
0
      }
663
0
    }
664
665
0
    if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
666
0
      return FatalError(status, "DEFER must be followed by a CALL argument");
667
0
    }
668
669
0
    maybeDefer = std::move(defer);
670
0
  }
671
672
0
  if (expArgs[expArg] == "CALL") {
673
0
    ++expArg; // Consume "CALL".
674
675
    // CALL requires a command name.
676
0
    if (!moreArgs()) {
677
0
      return FatalError(status, "CALL missing command name");
678
0
    }
679
0
    std::string const& callCommand = expArgs[expArg++];
680
681
    // CALL accepts no further expanded arguments.
682
0
    if (expArg != expArgs.size()) {
683
0
      return FatalError(status, "CALL command's arguments must be literal");
684
0
    }
685
686
    // Run the CALL.
687
0
    return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
688
0
                                      std::move(maybeDefer), status);
689
0
  }
690
691
0
  if (expArgs[expArg] == "EVAL") {
692
0
    return cmCMakeLanguageCommandEVAL(args, status);
693
0
  }
694
695
0
  if (expArgs[expArg] == "GET_MESSAGE_LOG_LEVEL") {
696
0
    return cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(args, status);
697
0
  }
698
699
0
  if (expArgs[expArg] == "GET_EXPERIMENTAL_FEATURE_ENABLED") {
700
0
    return cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(args,
701
0
                                                                  status);
702
0
  }
703
704
0
  if (expArgs[expArg] == "PRINT_TARGETS") {
705
0
    return cmCMakeLanguageCommandPRINT_TARGETS(args, status);
706
0
  }
707
708
0
  if (expArgs[expArg] == "TRACE") {
709
0
    ++expArg; // Consume "TRACE".
710
711
0
    if (!moreArgs()) {
712
0
      return FatalError(status, "TRACE missing a boolean value");
713
0
    }
714
715
0
    bool const value = cmValue::IsOn(expArgs[expArg++]);
716
0
    bool expand = false;
717
718
0
    if (value && moreArgs()) {
719
0
      expand = (expArgs[expArg] == "EXPAND");
720
0
      if (!expand) {
721
0
        return FatalError(
722
0
          status,
723
0
          cmStrCat("TRACE ON given an invalid argument ", expArgs[expArg]));
724
0
      }
725
0
      ++expArg;
726
0
    }
727
728
0
    if (moreArgs()) {
729
0
      return FatalError(
730
0
        status,
731
0
        cmStrCat("TRACE O", value ? "N" : "FF", " given too many arguments"));
732
0
    }
733
734
0
    cmMakefile& makefile = status.GetMakefile();
735
0
    if (value) {
736
0
      makefile.GetCMakeInstance()->PushTraceCmd(expand);
737
0
      return true;
738
0
    }
739
0
    return makefile.GetCMakeInstance()->PopTraceCmd() ||
740
0
      FatalError(status, "TRACE OFF request without a corresponding TRACE ON");
741
0
  }
742
743
0
  return FatalError(status, "called with unknown meta-operation");
744
0
}