Coverage Report

Created: 2026-10-28 06:38

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 <initializer_list>
9
#include <map>
10
#include <memory>
11
#include <set>
12
#include <string>
13
#include <unordered_map>
14
#include <utility>
15
#include <vector>
16
17
#include <cm/optional>
18
#include <cm/string_view>
19
#include <cmext/string_view>
20
21
#include "cmsys/RegularExpression.hxx"
22
23
#include "cmArgumentParser.h"
24
#include "cmArgumentParserTypes.h"
25
#include "cmDependencyProvider.h"
26
#include "cmExecutionStatus.h"
27
#include "cmExperimental.h"
28
#include "cmGeneratorTarget.h"
29
#include "cmGetPropertyCommand.h"
30
#include "cmGlobalGenerator.h"
31
#include "cmLinkItem.h"
32
#include "cmListFileCache.h"
33
#include "cmLocalGenerator.h"
34
#include "cmMakefile.h"
35
#include "cmMessageType.h" // IWYU pragma: keep
36
#include "cmPropertyMap.h"
37
#include "cmRange.h"
38
#include "cmState.h"
39
#include "cmStateSnapshot.h"
40
#include "cmStateTypes.h"
41
#include "cmStringAlgorithms.h"
42
#include "cmSystemTools.h"
43
#include "cmTarget.h"
44
#include "cmTargetPropertyComputer.h"
45
#include "cmTargetPropertyHelper.h"
46
#include "cmValue.h"
47
#include "cmake.h"
48
49
namespace cm {
50
enum class TargetType;
51
}
52
53
namespace {
54
55
bool FatalError(cmExecutionStatus& status, std::string const& error)
56
0
{
57
0
  status.SetError(error);
58
0
  cmSystemTools::SetFatalErrorOccurred();
59
0
  return false;
60
0
}
61
62
std::array<cm::static_string_view, 14> InvalidCommands{
63
  { // clang-format off
64
  "function"_s, "endfunction"_s,
65
  "macro"_s, "endmacro"_s,
66
  "if"_s, "elseif"_s, "else"_s, "endif"_s,
67
  "while"_s, "endwhile"_s,
68
  "foreach"_s, "endforeach"_s,
69
  "block"_s, "endblock"_s
70
  } // clang-format on
71
};
72
73
std::array<cm::static_string_view, 1> InvalidDeferCommands{
74
  {
75
    // clang-format off
76
  "return"_s,
77
  } // clang-format on
78
};
79
80
struct Defer
81
{
82
  std::string Id;
83
  std::string IdVar;
84
  cmMakefile* Directory = nullptr;
85
};
86
87
bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
88
                                std::string const& callCommand,
89
                                size_t startArg, cm::optional<Defer> defer,
90
                                cmExecutionStatus& status)
91
0
{
92
  // ensure specified command is valid
93
  // start/end flow control commands are not allowed
94
0
  auto cmd = cmSystemTools::LowerCase(callCommand);
95
0
  if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) !=
96
0
      InvalidCommands.cend()) {
97
0
    return FatalError(status,
98
0
                      cmStrCat("invalid command specified: "_s, callCommand));
99
0
  }
100
0
  if (defer &&
101
0
      std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(),
102
0
                cmd) != InvalidDeferCommands.cend()) {
103
0
    return FatalError(status,
104
0
                      cmStrCat("invalid command specified: "_s, callCommand));
105
0
  }
106
107
0
  cmMakefile& makefile = status.GetMakefile();
108
0
  cmListFileContext context = makefile.GetBacktrace().Top();
109
110
0
  std::vector<cmListFileArgument> funcArgs;
111
0
  funcArgs.reserve(args.size() - startArg);
112
113
  // The rest of the arguments are passed to the function call above
114
0
  for (size_t i = startArg; i < args.size(); ++i) {
115
0
    funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line);
116
0
  }
117
0
  cmListFileFunction func{ callCommand, context.Line, context.Line,
118
0
                           std::move(funcArgs) };
119
120
0
  if (defer) {
121
0
    if (defer->Id.empty()) {
122
0
      defer->Id = makefile.NewDeferId();
123
0
    }
124
0
    if (!defer->IdVar.empty()) {
125
0
      makefile.AddDefinition(defer->IdVar, defer->Id);
126
0
    }
127
0
    cmMakefile* deferMakefile =
128
0
      defer->Directory ? defer->Directory : &makefile;
129
0
    if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) {
130
0
      return FatalError(
131
0
        status,
132
0
        cmStrCat("DEFER CALL may not be scheduled in directory:\n  "_s,
133
0
                 deferMakefile->GetCurrentBinaryDirectory(),
134
0
                 "\nat this time."_s));
135
0
    }
136
0
    return true;
137
0
  }
138
0
  return makefile.ExecuteCommand(func, status);
139
0
}
140
141
bool cmCMakeLanguageCommandDEFER(Defer const& defer,
142
                                 std::vector<std::string> const& args,
143
                                 size_t arg, cmExecutionStatus& status)
144
0
{
145
0
  cmMakefile* deferMakefile =
146
0
    defer.Directory ? defer.Directory : &status.GetMakefile();
147
0
  if (args[arg] == "CANCEL_CALL"_s) {
148
0
    ++arg; // Consume CANCEL_CALL.
149
0
    auto ids = cmMakeRange(args).advance(arg);
150
0
    for (std::string const& id : ids) {
151
0
      if (id[0] >= 'A' && id[0] <= 'Z') {
152
0
        return FatalError(
153
0
          status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n  "_s, id));
154
0
      }
155
0
      if (!deferMakefile->DeferCancelCall(id)) {
156
0
        return FatalError(
157
0
          status,
158
0
          cmStrCat("DEFER CANCEL_CALL may not update directory:\n  "_s,
159
0
                   deferMakefile->GetCurrentBinaryDirectory(),
160
0
                   "\nat this time."_s));
161
0
      }
162
0
    }
163
0
    return true;
164
0
  }
165
0
  if (args[arg] == "GET_CALL_IDS"_s) {
166
0
    ++arg; // Consume GET_CALL_IDS.
167
0
    if (arg == args.size()) {
168
0
      return FatalError(status, "DEFER GET_CALL_IDS missing output variable");
169
0
    }
170
0
    std::string const& var = args[arg++];
171
0
    if (arg != args.size()) {
172
0
      return FatalError(status, "DEFER GET_CALL_IDS given too many arguments");
173
0
    }
174
0
    cm::optional<std::string> ids = deferMakefile->DeferGetCallIds();
175
0
    if (!ids) {
176
0
      return FatalError(
177
0
        status,
178
0
        cmStrCat("DEFER GET_CALL_IDS may not access directory:\n  "_s,
179
0
                 deferMakefile->GetCurrentBinaryDirectory(),
180
0
                 "\nat this time."_s));
181
0
    }
182
0
    status.GetMakefile().AddDefinition(var, *ids);
183
0
    return true;
184
0
  }
185
0
  if (args[arg] == "GET_CALL"_s) {
186
0
    ++arg; // Consume GET_CALL.
187
0
    if (arg == args.size()) {
188
0
      return FatalError(status, "DEFER GET_CALL missing id");
189
0
    }
190
0
    std::string const& id = args[arg++];
191
0
    if (arg == args.size()) {
192
0
      return FatalError(status, "DEFER GET_CALL missing output variable");
193
0
    }
194
0
    std::string const& var = args[arg++];
195
0
    if (arg != args.size()) {
196
0
      return FatalError(status, "DEFER GET_CALL given too many arguments");
197
0
    }
198
0
    if (id.empty()) {
199
0
      return FatalError(status, "DEFER GET_CALL id may not be empty");
200
0
    }
201
0
    if (id[0] >= 'A' && id[0] <= 'Z') {
202
0
      return FatalError(status,
203
0
                        cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id));
204
0
    }
205
0
    cm::optional<std::string> call = deferMakefile->DeferGetCall(id);
206
0
    if (!call) {
207
0
      return FatalError(
208
0
        status,
209
0
        cmStrCat("DEFER GET_CALL may not access directory:\n  "_s,
210
0
                 deferMakefile->GetCurrentBinaryDirectory(),
211
0
                 "\nat this time."_s));
212
0
    }
213
0
    status.GetMakefile().AddDefinition(var, *call);
214
0
    return true;
215
0
  }
216
0
  return FatalError(status,
217
0
                    cmStrCat("DEFER operation unknown: "_s, args[arg]));
218
0
}
219
220
bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
221
                                cmExecutionStatus& status)
222
0
{
223
0
  cmMakefile& makefile = status.GetMakefile();
224
0
  cmListFileContext context = makefile.GetBacktrace().Top();
225
0
  std::vector<std::string> expandedArgs;
226
0
  makefile.ExpandArguments(args, expandedArgs);
227
228
0
  if (expandedArgs.size() < 2) {
229
0
    return FatalError(status, "called with incorrect number of arguments");
230
0
  }
231
232
0
  if (expandedArgs[1] != "CODE") {
233
0
    auto code_iter =
234
0
      std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE");
235
0
    if (code_iter == expandedArgs.end()) {
236
0
      return FatalError(status, "called without CODE argument");
237
0
    }
238
0
    return FatalError(
239
0
      status,
240
0
      "called with unsupported arguments between EVAL and CODE arguments");
241
0
  }
242
243
0
  std::string const code =
244
0
    cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " ");
245
0
  return makefile.ReadListFileAsString(
246
0
    code, cmStrCat(context.FilePath, ':', context.Line, ":EVAL"));
247
0
}
248
249
bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(
250
  std::vector<std::string> const& args, cmExecutionStatus& status)
251
0
{
252
0
  cmState* state = status.GetMakefile().GetState();
253
0
  if (!state->InTopLevelIncludes()) {
254
0
    return FatalError(
255
0
      status,
256
0
      "Dependency providers can only be set as part of the first call to "
257
0
      "project(). More specifically, cmake_language(SET_DEPENDENCY_PROVIDER) "
258
0
      "can only be called while the first project() command processes files "
259
0
      "listed in CMAKE_PROJECT_TOP_LEVEL_INCLUDES.");
260
0
  }
261
262
0
  struct SetProviderArgs
263
0
  {
264
0
    std::string Command;
265
0
    ArgumentParser::NonEmpty<std::vector<std::string>> Methods;
266
0
  };
267
268
0
  auto const ArgsParser =
269
0
    cmArgumentParser<SetProviderArgs>()
270
0
      .Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command)
271
0
      .Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods);
272
273
0
  std::vector<std::string> unparsed;
274
0
  auto parsedArgs = ArgsParser.Parse(args, &unparsed);
275
276
0
  if (!unparsed.empty()) {
277
0
    return FatalError(
278
0
      status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), '"'));
279
0
  }
280
281
  // We store the command that FetchContent_MakeAvailable() can call in a
282
  // global (but considered internal) property. If the provider doesn't
283
  // support this method, we set this property to an empty string instead.
284
  // This simplifies the logic in FetchContent_MakeAvailable() and doesn't
285
  // require us to define a new internal command or sub-command.
286
0
  std::string fcmasProperty = "__FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER";
287
288
0
  if (parsedArgs.Command.empty()) {
289
0
    if (!parsedArgs.Methods.empty()) {
290
0
      return FatalError(status,
291
0
                        "Must specify a non-empty command name when provider "
292
0
                        "methods are given");
293
0
    }
294
0
    state->ClearDependencyProvider();
295
0
    state->SetGlobalProperty(fcmasProperty, "");
296
0
    return true;
297
0
  }
298
299
0
  cmState::Command command = state->GetCommand(parsedArgs.Command);
300
0
  if (!command) {
301
0
    return FatalError(status,
302
0
                      cmStrCat("Command \"", parsedArgs.Command,
303
0
                               "\" is not a defined command"));
304
0
  }
305
306
0
  if (parsedArgs.Methods.empty()) {
307
0
    return FatalError(status, "Must specify at least one provider method");
308
0
  }
309
310
0
  bool supportsFetchContentMakeAvailableSerial = false;
311
0
  std::vector<cmDependencyProvider::Method> methods;
312
0
  for (auto const& method : parsedArgs.Methods) {
313
0
    if (method == "FIND_PACKAGE") {
314
0
      methods.emplace_back(cmDependencyProvider::Method::FindPackage);
315
0
    } else if (method == "FETCHCONTENT_MAKEAVAILABLE_SERIAL") {
316
0
      supportsFetchContentMakeAvailableSerial = true;
317
0
      methods.emplace_back(
318
0
        cmDependencyProvider::Method::FetchContentMakeAvailableSerial);
319
0
    } else {
320
0
      return FatalError(
321
0
        status,
322
0
        cmStrCat("Unknown dependency provider method \"", method, '"'));
323
0
    }
324
0
  }
325
326
0
  state->SetDependencyProvider({ parsedArgs.Command, methods });
327
0
  state->SetGlobalProperty(
328
0
    fcmasProperty,
329
0
    supportsFetchContentMakeAvailableSerial ? parsedArgs.Command : "");
330
331
0
  return true;
332
0
}
333
334
bool cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(
335
  std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
336
0
{
337
0
  cmMakefile& makefile = status.GetMakefile();
338
0
  std::vector<std::string> expandedArgs;
339
0
  makefile.ExpandArguments(args, expandedArgs);
340
341
0
  if (args.size() < 2 || expandedArgs.size() > 2) {
342
0
    return FatalError(
343
0
      status,
344
0
      "sub-command GET_MESSAGE_LOG_LEVEL expects exactly one argument");
345
0
  }
346
347
0
  Message::LogLevel logLevel = makefile.GetCurrentLogLevel();
348
0
  std::string outputValue = cmake::LogLevelToString(logLevel);
349
350
0
  std::string const& outputVariable = expandedArgs[1];
351
0
  makefile.AddDefinition(outputVariable, outputValue);
352
0
  return true;
353
0
}
354
355
bool cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(
356
  std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
357
0
{
358
0
  cmMakefile& makefile = status.GetMakefile();
359
0
  std::vector<std::string> expandedArgs;
360
0
  makefile.ExpandArguments(args, expandedArgs);
361
362
0
  if (expandedArgs.size() != 3) {
363
0
    return FatalError(status,
364
0
                      "sub-command GET_EXPERIMENTAL_FEATURE_ENABLED expects "
365
0
                      "exactly two arguments");
366
0
  }
367
368
0
  auto const& featureName = expandedArgs[1];
369
0
  auto const& variableName = expandedArgs[2];
370
371
0
  if (auto feature = cmExperimental::FeatureByName(featureName)) {
372
0
    if (cmExperimental::HasSupportEnabled(makefile, *feature)) {
373
0
      makefile.AddDefinition(variableName, "TRUE");
374
0
    } else {
375
0
      makefile.AddDefinition(variableName, "FALSE");
376
0
    }
377
0
  } else {
378
0
    return FatalError(status,
379
0
                      cmStrCat("Experimental feature name \"", featureName,
380
0
                               "\" does not exist."));
381
0
  }
382
383
0
  return true;
384
0
}
385
386
struct PrintTargetsArgs : public ArgumentParser::ParseResult
387
{
388
  cm::optional<std::string> Regex;
389
  bool ImportedOnly = false;
390
  bool NoImported = false;
391
  bool IgnoreCase = false;
392
};
393
394
// Lists every target that currently exists, optionally filtered by a
395
// name REGEX and by imported state.  "Currently exists" means anything
396
// CMake has defined up to this call: targets in the current directory,
397
// its ancestors, and any already-processed subdirectories.  Walking the
398
// global generator's makefiles captures exactly that set; a name-keyed
399
// map sorts the output and dedupes imported targets, which are inherited
400
// into child makefiles and would otherwise be seen many times.
401
bool cmCMakeLanguageCommandPRINT_TARGETS(
402
  std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
403
0
{
404
0
  cmMakefile& makefile = status.GetMakefile();
405
0
  std::vector<std::string> expandedArgs;
406
0
  makefile.ExpandArguments(args, expandedArgs);
407
408
  // Drop the leading "PRINT_TARGETS" subcommand keyword.
409
0
  std::vector<std::string> body(expandedArgs.begin() + 1, expandedArgs.end());
410
411
0
  auto const ArgsParser =
412
0
    cmArgumentParser<PrintTargetsArgs>()
413
0
      .Bind("REGEX"_s, &PrintTargetsArgs::Regex)
414
0
      .Bind("IMPORTED_ONLY"_s, &PrintTargetsArgs::ImportedOnly)
415
0
      .Bind("NO_IMPORTED"_s, &PrintTargetsArgs::NoImported)
416
0
      .Bind("IGNORE_CASE"_s, &PrintTargetsArgs::IgnoreCase);
417
418
0
  std::vector<std::string> unparsed;
419
0
  auto parsedArgs = ArgsParser.Parse(body, &unparsed);
420
421
0
  if (!unparsed.empty()) {
422
0
    return FatalError(
423
0
      status,
424
0
      cmStrCat(
425
0
        "Unknown argument(s) given to cmake_language(PRINT_TARGETS) call: \"",
426
0
        cmJoin(unparsed, "\" \""), "\"."));
427
0
  }
428
0
  if (parsedArgs.MaybeReportError(makefile)) {
429
0
    cmSystemTools::SetFatalErrorOccurred();
430
0
    return true;
431
0
  }
432
433
0
  if (parsedArgs.ImportedOnly && parsedArgs.NoImported) {
434
0
    return FatalError(status,
435
0
                      "IMPORTED_ONLY and NO_IMPORTED keywords are mutually "
436
0
                      "exclusive in cmake_language(PRINT_TARGETS) call.");
437
0
  }
438
439
0
  if (parsedArgs.IgnoreCase && !parsedArgs.Regex) {
440
0
    return FatalError(status,
441
0
                      "IGNORE_CASE keyword in cmake_language(PRINT_TARGETS) "
442
0
                      "call is only valid with REGEX.");
443
0
  }
444
445
  // Compile the optional REGEX up front so a bad pattern fails fast.  With
446
  // IGNORE_CASE the pattern and the candidate names are both lower-cased.
447
0
  cm::optional<cmsys::RegularExpression> regex;
448
0
  if (parsedArgs.Regex) {
449
0
    cmsys::RegularExpression re;
450
0
    std::string const pat = parsedArgs.IgnoreCase
451
0
      ? cmSystemTools::LowerCase(*parsedArgs.Regex)
452
0
      : *parsedArgs.Regex;
453
0
    if (!re.compile(pat)) {
454
0
      return FatalError(status,
455
0
                        cmStrCat("REGEX regular expression \"",
456
0
                                 *parsedArgs.Regex, "\" cannot compile."));
457
0
    }
458
0
    regex = std::move(re);
459
0
  }
460
461
0
  bool const includeNormal = !parsedArgs.ImportedOnly;
462
0
  bool const includeImported = !parsedArgs.NoImported;
463
464
0
  struct TargetInfo
465
0
  {
466
0
    cm::TargetType Type;
467
0
    bool Imported;
468
0
  };
469
0
  std::map<std::string, TargetInfo> targets;
470
0
  for (auto const& mf : makefile.GetGlobalGenerator()->GetMakefiles()) {
471
0
    if (includeNormal) {
472
0
      for (auto const& ti : mf->GetTargets()) {
473
0
        cmTarget const& t = ti.second;
474
0
        targets.insert({ t.GetName(), { t.GetType(), false } });
475
0
      }
476
0
    }
477
0
    if (includeImported) {
478
0
      for (cmTarget const* t : mf->GetImportedTargets()) {
479
0
        targets.insert({ t->GetName(), { t->GetType(), true } });
480
0
      }
481
0
    }
482
0
  }
483
484
  // Build the body first so the header is suppressed when a REGEX filters
485
  // everything out (matches cmake_language(PRINT_VARIABLES) behavior).
486
0
  std::string lines;
487
0
  bool anyMatched = false;
488
0
  for (auto const& t : targets) {
489
0
    if (regex) {
490
0
      std::string const subj =
491
0
        parsedArgs.IgnoreCase ? cmSystemTools::LowerCase(t.first) : t.first;
492
0
      if (!regex->find(subj)) {
493
0
        continue;
494
0
      }
495
0
    }
496
0
    lines = cmStrCat(std::move(lines), "   ", t.first, " (",
497
0
                     cmState::GetTargetTypeName(t.second.Type),
498
0
                     t.second.Imported ? ", IMPORTED" : "", ")\n");
499
0
    anyMatched = true;
500
0
  }
501
502
0
  if (anyMatched) {
503
    // The message opens with a banner line.
504
0
    std::string out = "Printing targets...\n";
505
    // The header reflects the imported-filter mode and any REGEX in effect.
506
0
    char const* label = "All targets";
507
0
    if (parsedArgs.ImportedOnly) {
508
0
      label = "Imported targets";
509
0
    } else if (parsedArgs.NoImported) {
510
0
      label = "Non-imported targets";
511
0
    }
512
0
    out = cmStrCat(std::move(out), ' ', label);
513
0
    if (parsedArgs.Regex) {
514
0
      out = cmStrCat(
515
0
        std::move(out), " matching REGEX '", *parsedArgs.Regex, "' (",
516
0
        parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive", ')');
517
0
    }
518
0
    out = cmStrCat(std::move(out), ":\n", lines);
519
0
    makefile.DisplayStatus(out, -1);
520
0
  }
521
522
0
  if (!anyMatched && parsedArgs.Regex) {
523
0
    makefile.IssueMessage(
524
0
      MessageType::WARNING,
525
0
      cmStrCat("No targets matching REGEX '", *parsedArgs.Regex, "' (",
526
0
               parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive",
527
0
               ") in cmake_language(PRINT_TARGETS ...)."));
528
0
  }
529
0
  return true;
530
0
}
531
532
struct PrintVariablesArgs : public ArgumentParser::ParseResult
533
{
534
  bool All = false;
535
  ArgumentParser::NonEmpty<std::vector<std::string>> Named;
536
  cm::optional<std::string> NameRegex;
537
  cm::optional<std::string> ValueRegex;
538
  bool IgnoreCase = false;
539
  // Internal: set by the deprecated cmake_print_variables() module wrapper to
540
  // request the historical flush single-line output (no banner, undefined
541
  // names printed as name="").  Not part of the public interface.
542
  bool CmakePrintVariables = false;
543
};
544
545
// Banner opening a cmake_language(PRINT_VARIABLES) status message.  Suppressed
546
// in the deprecated cmake_print_variables() (legacy) path.
547
cm::static_string_view const PrintVariablesBanner =
548
  "Printing variables...\n"_s;
549
550
// NAMED mode.  By default (matching cmake_language(PRINT_PROPERTIES)) this
551
// emits a " Named variables:" header followed by one `name = "value"` entry
552
// per line; a name that is not defined prints as `name = <NOTFOUND>` in the
553
// order given.  When `legacy` is set (the deprecated cmake_print_variables()
554
// wrapper) it instead emits the historical flush single line
555
// "name=\"value\" ; ..." with no header and undefined names as name="".
556
// Values go through GetDefinition so users see the same
557
// in-scope/cache-fallback value they'd get from ${var}.
558
void PrintVariablesNamed(cmMakefile& makefile,
559
                         std::vector<std::string> const& names, bool legacy)
560
0
{
561
0
  if (legacy) {
562
0
    std::string msg;
563
0
    bool first = true;
564
0
    for (std::string const& name : names) {
565
0
      if (!first) {
566
0
        msg += " ; ";
567
0
      }
568
0
      first = false;
569
0
      cmValue v = makefile.GetDefinition(name);
570
0
      msg = cmStrCat(std::move(msg), name, "=\"", v ? *v : std::string(), '"');
571
0
    }
572
0
    makefile.DisplayStatus(msg, -1);
573
0
    return;
574
0
  }
575
576
0
  std::string out = cmStrCat(PrintVariablesBanner, " Named variables:\n");
577
0
  for (std::string const& name : names) {
578
0
    cmValue v = makefile.GetDefinition(name);
579
0
    if (v) {
580
0
      out = cmStrCat(std::move(out), "   ", name, " = \"", *v, "\"\n");
581
0
    } else {
582
0
      out = cmStrCat(std::move(out), "   ", name, " = <NOTFOUND>\n");
583
0
    }
584
0
  }
585
0
  makefile.DisplayStatus(out, -1);
586
0
}
587
588
// ALL mode: enumerate every regular variable in scope and
589
// every cache entry.  A name with both a regular variable and a cache
590
// entry is printed on two distinct lines so the user sees the full
591
// picture, including the value the cache holds even when a regular
592
// variable shadows it.  Values are read via the snapshot and cache APIs
593
// directly to avoid firing variable-watch callbacks (which would
594
// otherwise trip CMP0218 when CMAKE_WARN_DEPRECATED or
595
// CMAKE_ERROR_DEPRECATED are in scope).
596
bool PrintVariablesAll(cmMakefile& makefile, PrintVariablesArgs const& parsed,
597
                       cmExecutionStatus& status)
598
0
{
599
0
  cm::optional<cmsys::RegularExpression> nameRegex;
600
0
  cm::optional<cmsys::RegularExpression> valueRegex;
601
0
  if (parsed.NameRegex) {
602
0
    cmsys::RegularExpression re;
603
0
    std::string const pat = parsed.IgnoreCase
604
0
      ? cmSystemTools::LowerCase(*parsed.NameRegex)
605
0
      : *parsed.NameRegex;
606
0
    if (!re.compile(pat)) {
607
0
      return FatalError(status,
608
0
                        cmStrCat("NAME_REGEX regular expression \"",
609
0
                                 *parsed.NameRegex, "\" cannot compile."));
610
0
    }
611
0
    nameRegex = std::move(re);
612
0
  }
613
0
  if (parsed.ValueRegex) {
614
0
    cmsys::RegularExpression re;
615
0
    std::string const pat = parsed.IgnoreCase
616
0
      ? cmSystemTools::LowerCase(*parsed.ValueRegex)
617
0
      : *parsed.ValueRegex;
618
0
    if (!re.compile(pat)) {
619
0
      return FatalError(status,
620
0
                        cmStrCat("VALUE_REGEX regular expression \"",
621
0
                                 *parsed.ValueRegex, "\" cannot compile."));
622
0
    }
623
0
    valueRegex = std::move(re);
624
0
  }
625
626
0
  auto matches = [&](std::string const& name, std::string const& value) {
627
0
    if (nameRegex) {
628
0
      std::string const subj =
629
0
        parsed.IgnoreCase ? cmSystemTools::LowerCase(name) : name;
630
0
      if (!nameRegex->find(subj)) {
631
0
        return false;
632
0
      }
633
0
    }
634
0
    if (valueRegex) {
635
0
      std::string const subj =
636
0
        parsed.IgnoreCase ? cmSystemTools::LowerCase(value) : value;
637
0
      if (!valueRegex->find(subj)) {
638
0
        return false;
639
0
      }
640
0
    }
641
0
    return true;
642
0
  };
643
644
0
  cmStateSnapshot const snapshot = makefile.GetStateSnapshot();
645
0
  cmState* state = makefile.GetState();
646
647
  // GetDefinitions() already unions ClosureKeys() with the cache keys and
648
  // sorts the result; just dedupe so a name that lives in both lists isn't
649
  // visited twice.
650
0
  auto names = makefile.GetDefinitions();
651
0
  names.erase(std::unique(names.begin(), names.end()), names.end());
652
653
  // Build the body first so nothing is printed when a regex filters
654
  // everything out; the warning below covers that case.
655
0
  std::string body;
656
0
  bool anyMatched = false;
657
0
  for (std::string const& name : names) {
658
0
    cmValue regular = snapshot.GetDefinition(name);
659
0
    if (regular && matches(name, *regular)) {
660
0
      body = cmStrCat(std::move(body), "   ", name, " = \"", *regular, "\"\n");
661
0
      anyMatched = true;
662
0
    }
663
0
    cmValue cached = state->GetInitializedCacheValue(name);
664
0
    if (cached && matches(name, *cached)) {
665
0
      auto const type = state->GetCacheEntryType(name);
666
0
      body = cmStrCat(std::move(body), "   CACHE{", name, '}');
667
0
      if (type != cmStateEnums::UNINITIALIZED) {
668
0
        body = cmStrCat(std::move(body), ':',
669
0
                        cmState::CacheEntryTypeToString(type));
670
0
      }
671
0
      body = cmStrCat(std::move(body), " = \"", *cached, "\"\n");
672
0
      anyMatched = true;
673
0
    }
674
0
  }
675
676
0
  if (anyMatched) {
677
0
    cmValue listFile = snapshot.GetDefinition("CMAKE_CURRENT_LIST_FILE");
678
0
    std::string out =
679
0
      cmStrCat(PrintVariablesBanner, " Variables in scope at '",
680
0
               listFile ? *listFile : std::string("<unknown>"), '\'');
681
0
    if (parsed.NameRegex || parsed.ValueRegex) {
682
0
      out += " matching";
683
0
      if (parsed.NameRegex) {
684
0
        out = cmStrCat(std::move(out), " name '", *parsed.NameRegex, '\'');
685
0
      }
686
0
      if (parsed.NameRegex && parsed.ValueRegex) {
687
0
        out += " and";
688
0
      }
689
0
      if (parsed.ValueRegex) {
690
0
        out = cmStrCat(std::move(out), " value '", *parsed.ValueRegex, '\'');
691
0
      }
692
0
      out += parsed.IgnoreCase ? " (case insensitive)" : " (case sensitive)";
693
0
    }
694
0
    out = cmStrCat(std::move(out), ":\n", body);
695
0
    makefile.DisplayStatus(out, -1);
696
0
  }
697
698
0
  if (!anyMatched && (parsed.NameRegex || parsed.ValueRegex)) {
699
0
    std::string msg = "No variables in scope matching";
700
0
    if (parsed.NameRegex) {
701
0
      msg = cmStrCat(std::move(msg), " name '", *parsed.NameRegex, '\'');
702
0
    }
703
0
    if (parsed.NameRegex && parsed.ValueRegex) {
704
0
      msg += " and";
705
0
    }
706
0
    if (parsed.ValueRegex) {
707
0
      msg = cmStrCat(std::move(msg), " value '", *parsed.ValueRegex, '\'');
708
0
    }
709
0
    msg += parsed.IgnoreCase ? " (case insensitive)" : " (case sensitive)";
710
0
    msg += " in cmake_language(PRINT_VARIABLES ...).";
711
0
    makefile.IssueMessage(MessageType::WARNING, msg);
712
0
  }
713
0
  return true;
714
0
}
715
716
bool cmCMakeLanguageCommandPRINT_VARIABLES(
717
  std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
718
0
{
719
0
  cmMakefile& makefile = status.GetMakefile();
720
0
  std::vector<std::string> expandedArgs;
721
0
  makefile.ExpandArguments(args, expandedArgs);
722
723
  // Drop the leading "PRINT_VARIABLES" subcommand keyword.
724
0
  std::vector<std::string> body(expandedArgs.begin() + 1, expandedArgs.end());
725
726
0
  auto const ArgsParser =
727
0
    cmArgumentParser<PrintVariablesArgs>()
728
0
      .Bind("ALL"_s, &PrintVariablesArgs::All)
729
0
      .Bind("NAMED"_s, &PrintVariablesArgs::Named)
730
0
      .Bind("NAME_REGEX"_s, &PrintVariablesArgs::NameRegex)
731
0
      .Bind("VALUE_REGEX"_s, &PrintVariablesArgs::ValueRegex)
732
0
      .Bind("IGNORE_CASE"_s, &PrintVariablesArgs::IgnoreCase)
733
0
      .Bind("__CMAKE_PRINT_VARIABLES"_s,
734
0
            &PrintVariablesArgs::CmakePrintVariables);
735
736
0
  std::vector<std::string> unparsed;
737
0
  auto parsedArgs = ArgsParser.Parse(body, &unparsed);
738
739
  // No bareword form: every token must belong to ALL, NAMED, or a filter.
740
0
  if (!unparsed.empty()) {
741
0
    return FatalError(
742
0
      status,
743
0
      cmStrCat("Unknown argument(s) given to cmake_language(PRINT_VARIABLES)"
744
0
               ": \"",
745
0
               cmJoin(unparsed, "\" \""), "\"."));
746
0
  }
747
748
0
  if (parsedArgs.MaybeReportError(makefile)) {
749
0
    cmSystemTools::SetFatalErrorOccurred();
750
0
    return true;
751
0
  }
752
753
0
  bool const hasNamed = !parsedArgs.Named.empty();
754
0
  bool const hasFilters =
755
0
    parsedArgs.NameRegex || parsedArgs.ValueRegex || parsedArgs.IgnoreCase;
756
757
  // ALL and NAMED are mutually exclusive modes.
758
0
  if (parsedArgs.All && hasNamed) {
759
0
    return FatalError(status,
760
0
                      "ALL and NAMED keywords in "
761
0
                      "cmake_language(PRINT_VARIABLES) call "
762
0
                      "are mutually exclusive.");
763
0
  }
764
765
  // The filter keywords narrow an enumeration, so they only make sense with
766
  // ALL (explicit or implicit), never with NAMED.
767
0
  if (hasNamed && hasFilters) {
768
0
    return FatalError(status,
769
0
                      "NAME_REGEX, VALUE_REGEX, and IGNORE_CASE in "
770
0
                      "cmake_language(PRINT_VARIABLES) call "
771
0
                      "are only valid with ALL, not NAMED.");
772
0
  }
773
774
  // __CMAKE_PRINT_VARIABLES selects the legacy single-line NAMED output; it is
775
  // meaningless when enumerating variables (ALL, explicit or implicit).
776
0
  if (!hasNamed && parsedArgs.CmakePrintVariables) {
777
0
    return FatalError(status,
778
0
                      "__CMAKE_PRINT_VARIABLES in "
779
0
                      "cmake_language(PRINT_VARIABLES) call is only valid "
780
0
                      "with NAMED.");
781
0
  }
782
783
  // Default to ALL when neither mode keyword is given.
784
0
  bool const all = parsedArgs.All || !hasNamed;
785
0
  if (all) {
786
0
    return PrintVariablesAll(makefile, parsedArgs, status);
787
0
  }
788
789
0
  PrintVariablesNamed(makefile, parsedArgs.Named,
790
0
                      parsedArgs.CmakePrintVariables);
791
0
  return true;
792
0
}
793
// Walks every target reachable from `root` through any linkage - PUBLIC,
794
// INTERFACE, PRIVATE, and `$<LINK_ONLY:>`-wrapped deps.  At each level we
795
// union the link implementation and link interface, each queried with both
796
// UseTo::Compile and UseTo::Link, so deps that only appear under LINK_ONLY
797
// (which evaluates to empty under UseTo::Compile) still come through.
798
std::vector<cmGeneratorTarget const*> CollectDependentTargets(
799
  cmGeneratorTarget const* root, std::string const& config)
800
0
{
801
0
  std::vector<cmGeneratorTarget const*> deps;
802
0
  std::set<cmGeneratorTarget const*> visited;
803
0
  visited.insert(root);
804
0
  std::vector<cmGeneratorTarget const*> queue;
805
0
  queue.push_back(root);
806
0
  while (!queue.empty()) {
807
0
    cmGeneratorTarget const* cur = queue.back();
808
0
    queue.pop_back();
809
0
    auto visit = [&](std::vector<cmLinkItem> const& libs) {
810
0
      for (cmLinkItem const& item : libs) {
811
0
        cmGeneratorTarget const* dep = item.Target;
812
0
        if (!dep || !visited.insert(dep).second) {
813
0
          continue;
814
0
        }
815
0
        deps.push_back(dep);
816
0
        queue.push_back(dep);
817
0
      }
818
0
    };
819
0
    for (auto useTo : { cmGeneratorTarget::UseTo::Compile,
820
0
                        cmGeneratorTarget::UseTo::Link }) {
821
0
      if (cmLinkImplementationLibraries const* impl =
822
0
            cur->GetLinkImplementationLibraries(config, useTo)) {
823
0
        visit(impl->Libraries);
824
0
      }
825
0
      if (cmLinkInterfaceLibraries const* iface =
826
0
            cur->GetLinkInterfaceLibraries(config, root, useTo)) {
827
0
        visit(iface->Libraries);
828
0
      }
829
0
    }
830
0
  }
831
0
  return deps;
832
0
}
833
834
// Append one property line to `out`:
835
//   "   <entityName>.<propertyName> = \"<value>\""
836
// A null `value` writes "<NOTFOUND>" instead of "= \"...\"".
837
void WritePropertyLine(std::string& out, std::string const& entityName,
838
                       std::string const& propertyName, cmValue value)
839
0
{
840
0
  out = cmStrCat(std::move(out), "   ", entityName, '.', propertyName);
841
0
  if (value) {
842
0
    out = cmStrCat(std::move(out), " = \"", *value, '"');
843
0
  } else {
844
0
    out += " = <NOTFOUND>";
845
0
  }
846
0
  out += "\n";
847
0
}
848
849
enum class BlockKind
850
{
851
  Explicit,
852
  All,
853
};
854
855
enum class HeaderSuffix
856
{
857
  None,
858
  Reachable,
859
};
860
861
void EmitBlockHeader(
862
  std::string& out, std::string const& entityName,
863
  std::string const& entityType, BlockKind kind, HeaderSuffix suffix,
864
  cm::optional<std::string> const& nameRegexStr = cm::nullopt,
865
  cm::optional<std::string> const& valueRegexStr = cm::nullopt)
866
0
{
867
0
  out = cmStrCat(std::move(out), ' ',
868
0
                 (kind == BlockKind::All ? "All properties" : "Properties"),
869
0
                 " for ", entityType, ' ', entityName);
870
0
  if (suffix == HeaderSuffix::Reachable) {
871
0
    out += " (and all reachable)";
872
0
  }
873
0
  if (kind == BlockKind::All && (nameRegexStr || valueRegexStr)) {
874
0
    out += " matching";
875
0
    if (nameRegexStr) {
876
0
      out = cmStrCat(std::move(out), " name '", *nameRegexStr, '\'');
877
0
    }
878
0
    if (nameRegexStr && valueRegexStr) {
879
0
      out += " and";
880
0
    }
881
0
    if (valueRegexStr) {
882
0
      out = cmStrCat(std::move(out), " value '", *valueRegexStr, '\'');
883
0
    }
884
0
  }
885
0
  out += ":\n";
886
0
}
887
888
// Build the "no properties matched" warning text used when the regex filter
889
// wipes everything out for a target's ALL block.  Returned without any
890
// "Warning:" prefix - callers feed it to IssueMessage, which adds the
891
// standard "CMake Warning at <file>:<line>" framing.
892
std::string EmptyMatchWarningMessage(
893
  std::string const& entityName, std::string const& entityType,
894
  cm::optional<std::string> const& nameRegexStr,
895
  cm::optional<std::string> const& valueRegexStr)
896
0
{
897
0
  std::string msg =
898
0
    cmStrCat("No properties for ", entityType, ' ', entityName, " matching");
899
0
  if (nameRegexStr) {
900
0
    msg = cmStrCat(std::move(msg), " name '", *nameRegexStr, '\'');
901
0
  }
902
0
  if (nameRegexStr && valueRegexStr) {
903
0
    msg += " and";
904
0
  }
905
0
  if (valueRegexStr) {
906
0
    msg = cmStrCat(std::move(msg), " value '", *valueRegexStr, '\'');
907
0
  }
908
0
  msg += " in cmake_language(PRINT_PROPERTIES ...).";
909
0
  return msg;
910
0
}
911
912
// Entity kinds supported by cmake_language(PRINT_PROPERTIES).
913
enum class EntityKind
914
{
915
  Target,
916
  Source,
917
  Test,
918
  Directory,
919
  Cache
920
};
921
922
// Display name used in PRINT_PROPERTIES output ("TARGET", "SOURCE", etc.).
923
char const* EntityTypeName(EntityKind kind)
924
0
{
925
0
  switch (kind) {
926
0
    case EntityKind::Target:
927
0
      return "TARGET";
928
0
    case EntityKind::Source:
929
0
      return "SOURCE";
930
0
    case EntityKind::Test:
931
0
      return "TEST";
932
0
    case EntityKind::Directory:
933
0
      return "DIRECTORY";
934
0
    case EntityKind::Cache:
935
0
      return "CACHE";
936
0
  }
937
0
  return "";
938
0
}
939
940
// Dispatch a property lookup to the appropriate per-entity helper.
941
bool GetPropertyHelper(cmExecutionStatus& status, EntityKind kind,
942
                       std::string const& name,
943
                       std::string const& propertyName, cmValue& out)
944
0
{
945
0
  switch (kind) {
946
0
    case EntityKind::Target:
947
0
      return GetPropertyCommand::LookupTargetProperty(status, name,
948
0
                                                      propertyName, out);
949
0
    case EntityKind::Source:
950
0
      return GetPropertyCommand::LookupSourceProperty(status, name,
951
0
                                                      propertyName, out);
952
0
    case EntityKind::Test:
953
0
      return GetPropertyCommand::LookupTestProperty(status, name, propertyName,
954
0
                                                    out);
955
0
    case EntityKind::Directory:
956
0
      return GetPropertyCommand::LookupDirectoryProperty(status, name,
957
0
                                                         propertyName, out);
958
0
    case EntityKind::Cache:
959
0
      return GetPropertyCommand::LookupCacheProperty(status, name,
960
0
                                                     propertyName, out);
961
0
  }
962
0
  return false;
963
0
}
964
965
// Emit one ALL-mode block: prints all properties matching the specified
966
// regexes for a head target (`targets[0]`) and optionally any reachable
967
// dependencies (`targets[1]` and on).
968
bool EmitAllPropertiesBlock(std::string& out, std::string const& headerName,
969
                            std::vector<cmTarget*> const& targets,
970
                            HeaderSuffix suffix,
971
                            cm::optional<cmsys::RegularExpression>& nameRegex,
972
                            cm::optional<cmsys::RegularExpression>& valueRegex,
973
                            cm::optional<std::string> const& nameRegexStr,
974
                            cm::optional<std::string> const& valueRegexStr)
975
0
{
976
0
  std::string groupBuf;
977
0
  bool groupEmitted = false;
978
0
  for (cmTarget* t : targets) {
979
0
    std::string const& rowName =
980
0
      (t == targets.front()) ? headerName : t->GetName();
981
0
    for (auto const& kv : t->GetExtendedProperties().GetList()) {
982
0
      if (nameRegex && !nameRegex->find(kv.first)) {
983
0
        continue;
984
0
      }
985
0
      if (valueRegex && !valueRegex->find(kv.second)) {
986
0
        continue;
987
0
      }
988
0
      WritePropertyLine(groupBuf, rowName, kv.first, cmValue(kv.second));
989
0
      groupEmitted = true;
990
0
    }
991
0
  }
992
0
  if (!groupEmitted) {
993
0
    return false;
994
0
  }
995
0
  EmitBlockHeader(out, headerName, "TARGET", BlockKind::All, suffix,
996
0
                  nameRegexStr, valueRegexStr);
997
0
  out += groupBuf;
998
0
  return true;
999
0
}
1000
1001
// Outcome of emitting a NAMED-mode block.
1002
enum class NamedBlockResult
1003
{
1004
  Failed,  // a property lookup hard-failed
1005
  Emitted, // at least one property line was written
1006
  Empty,   // every requested property was skipped; header suppressed
1007
};
1008
1009
// Emit one block of user-specified properties for a head target (`targets[0]`)
1010
// and optionally any reachable dependencies (`targets[1]` and on).
1011
NamedBlockResult EmitNamedPropertiesBlock(
1012
  std::string& out, std::string const& headerName,
1013
  std::vector<cmTarget*> const& targets,
1014
  std::vector<std::string> const& namedProperties, HeaderSuffix suffix,
1015
  cmMakefile& mf)
1016
0
{
1017
0
  bool headerWritten = false;
1018
0
  for (cmTarget* t : targets) {
1019
0
    std::string const& rowName =
1020
0
      (t == targets.front()) ? headerName : t->GetName();
1021
0
    for (std::string const& propName : namedProperties) {
1022
      // A computed location property may not be read from a non-imported
1023
      // target; skip it with a warning rather than triggering the getter's
1024
      // fatal error.
1025
0
      if (!t->IsImported() &&
1026
0
          cmTargetPropertyComputer::IsComputedLocationProperty(t->GetType(),
1027
0
                                                               propName)) {
1028
0
        mf.IssueMessage(
1029
0
          MessageType::WARNING,
1030
0
          cmStrCat("The ", propName,
1031
0
                   " property may not be read from non-imported target \"",
1032
0
                   rowName, "\"; skipping."));
1033
0
        continue;
1034
0
      }
1035
0
      cmValue v;
1036
0
      if (cmGetTargetProperty(rowName, t, propName, mf, v) !=
1037
0
          cmGetTargetPropertyResult::Success) {
1038
0
        return NamedBlockResult::Failed;
1039
0
      }
1040
0
      if (!headerWritten) {
1041
0
        EmitBlockHeader(out, headerName, "TARGET", BlockKind::Explicit,
1042
0
                        suffix);
1043
0
        headerWritten = true;
1044
0
      }
1045
0
      WritePropertyLine(out, rowName, propName, v);
1046
0
    }
1047
0
  }
1048
0
  return headerWritten ? NamedBlockResult::Emitted : NamedBlockResult::Empty;
1049
0
}
1050
1051
bool PrintPropertiesConfigureTime(
1052
  cmExecutionStatus& status, std::vector<std::string> const& namedProperties,
1053
  bool all, std::vector<std::string> const& entityNames, EntityKind kind,
1054
  cm::optional<std::string> const& propertyNameRegexStr,
1055
  cm::optional<std::string> const& propertyValueRegexStr,
1056
  cm::optional<cmsys::RegularExpression>& propertyNameRegex,
1057
  cm::optional<cmsys::RegularExpression>& propertyValueRegex,
1058
  std::string const& messagePrefix)
1059
0
{
1060
0
  cmMakefile& makefile = status.GetMakefile();
1061
0
  bool const hasRegex = propertyNameRegexStr || propertyValueRegexStr;
1062
0
  std::string out = messagePrefix;
1063
0
  bool anyEmitted = false;
1064
0
  for (auto const& entityName : entityNames) {
1065
0
    if (kind == EntityKind::Target) {
1066
0
      cmTarget* target = makefile.FindTargetToUse(entityName);
1067
0
      if (!target) {
1068
0
        out = cmStrCat(std::move(out), "\n No such TARGET \"", entityName,
1069
0
                       "\" !\n\n");
1070
0
        anyEmitted = true;
1071
0
        continue;
1072
0
      }
1073
0
      if (all) {
1074
0
        bool const emitted = EmitAllPropertiesBlock(
1075
0
          out, entityName, { target }, HeaderSuffix::None, propertyNameRegex,
1076
0
          propertyValueRegex, propertyNameRegexStr, propertyValueRegexStr);
1077
0
        if (emitted) {
1078
0
          anyEmitted = true;
1079
0
        } else if (hasRegex) {
1080
0
          makefile.IssueMessage(MessageType::WARNING,
1081
0
                                EmptyMatchWarningMessage(
1082
0
                                  entityName, "TARGET", propertyNameRegexStr,
1083
0
                                  propertyValueRegexStr));
1084
0
        }
1085
0
      } else {
1086
0
        NamedBlockResult const result = EmitNamedPropertiesBlock(
1087
0
          out, entityName, { target }, namedProperties, HeaderSuffix::None,
1088
0
          makefile);
1089
0
        if (result == NamedBlockResult::Failed) {
1090
0
          status.SetError(cmStrCat(
1091
0
            "failed to retrieve properties for TARGET \"", entityName, '"'));
1092
0
          return false;
1093
0
        }
1094
0
        if (result == NamedBlockResult::Emitted) {
1095
0
          anyEmitted = true;
1096
0
        }
1097
0
      }
1098
0
    } else {
1099
0
      EmitBlockHeader(out, entityName, EntityTypeName(kind),
1100
0
                      BlockKind::Explicit, HeaderSuffix::None);
1101
0
      for (auto const& propertyName : namedProperties) {
1102
0
        cmValue v;
1103
0
        if (!GetPropertyHelper(status, kind, entityName, propertyName, v)) {
1104
0
          return false;
1105
0
        }
1106
0
        WritePropertyLine(out, entityName, propertyName, v);
1107
0
      }
1108
0
      anyEmitted = true;
1109
0
    }
1110
0
  }
1111
0
  if (anyEmitted) {
1112
0
    makefile.DisplayStatus(out, -1);
1113
0
  }
1114
0
  return true;
1115
0
}
1116
1117
bool PrintTargetPropertiesDeferred(
1118
  std::vector<std::string> targetNames,
1119
  std::vector<std::string> namedProperties, bool all,
1120
  cm::optional<std::string> propertyNameRegexStr,
1121
  cm::optional<std::string> propertyValueRegexStr,
1122
  cm::optional<cmsys::RegularExpression> propertyNameRegex,
1123
  cm::optional<cmsys::RegularExpression> propertyValueRegex,
1124
  bool followDependencies, std::string messagePrefix,
1125
  cmExecutionStatus& status)
1126
0
{
1127
0
  cmListFileBacktrace const bt = status.GetMakefile().GetBacktrace();
1128
0
  status.GetMakefile().AddGeneratorAction(
1129
0
    [targetNames, namedProperties, all, propertyNameRegexStr,
1130
0
     propertyValueRegexStr, propertyNameRegex, propertyValueRegex,
1131
0
     followDependencies, messagePrefix, bt](
1132
0
      cmLocalGenerator& lg, cmListFileBacktrace const& /*lambdaBt*/) mutable {
1133
0
      std::vector<std::string> const configs =
1134
0
        lg.GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
1135
0
      std::string const config =
1136
0
        configs.empty() ? std::string() : configs.front();
1137
0
      bool const hasRegex = propertyNameRegexStr || propertyValueRegexStr;
1138
0
      cmake* cmakeInst = lg.GetMakefile()->GetCMakeInstance();
1139
1140
0
      std::string out = messagePrefix;
1141
0
      bool anyEmitted = false;
1142
0
      for (std::string const& name : targetNames) {
1143
0
        cmGeneratorTarget* root = lg.FindGeneratorTargetToUse(name);
1144
0
        if (!root) {
1145
0
          out =
1146
0
            cmStrCat(std::move(out), "\n No such TARGET \"", name, "\" !\n\n");
1147
0
          anyEmitted = true;
1148
0
          continue;
1149
0
        }
1150
0
        std::vector<cmTarget*> targets;
1151
0
        targets.push_back(root->Target);
1152
0
        HeaderSuffix const suffix =
1153
0
          followDependencies ? HeaderSuffix::Reachable : HeaderSuffix::None;
1154
0
        if (followDependencies) {
1155
0
          for (cmGeneratorTarget const* dep :
1156
0
               CollectDependentTargets(root, config)) {
1157
0
            targets.push_back(dep->Target);
1158
0
          }
1159
0
        }
1160
0
        if (all) {
1161
0
          bool const emitted = EmitAllPropertiesBlock(
1162
0
            out, name, targets, suffix, propertyNameRegex, propertyValueRegex,
1163
0
            propertyNameRegexStr, propertyValueRegexStr);
1164
0
          if (emitted) {
1165
0
            anyEmitted = true;
1166
0
          } else if (hasRegex) {
1167
0
            cmakeInst->IssueMessage(
1168
0
              MessageType::WARNING,
1169
0
              EmptyMatchWarningMessage(name, "TARGET", propertyNameRegexStr,
1170
0
                                       propertyValueRegexStr),
1171
0
              bt);
1172
0
          }
1173
0
        } else {
1174
0
          NamedBlockResult const result = EmitNamedPropertiesBlock(
1175
0
            out, name, targets, namedProperties, suffix, *lg.GetMakefile());
1176
0
          if (result == NamedBlockResult::Failed) {
1177
0
            cmakeInst->IssueMessage(
1178
0
              MessageType::FATAL_ERROR,
1179
0
              cmStrCat("failed to retrieve properties for TARGET \"", name,
1180
0
                       '"'),
1181
0
              bt);
1182
0
            return;
1183
0
          }
1184
0
          if (result == NamedBlockResult::Emitted) {
1185
0
            anyEmitted = true;
1186
0
          }
1187
0
        }
1188
0
      }
1189
0
      if (anyEmitted) {
1190
0
        lg.GetMakefile()->DisplayStatus(out, -1);
1191
0
      }
1192
0
    },
1193
0
    cmMakefile::GeneratorActionWhen::AfterGeneratorTargets);
1194
0
  return true;
1195
0
}
1196
1197
bool cmCMakeLanguageCommandPRINT_PROPERTIES(
1198
  std::vector<std::string> const& args, cmExecutionStatus& status)
1199
0
{
1200
0
  struct PrintPropertiesArg : public ArgumentParser::ParseResult
1201
0
  {
1202
0
    bool All = false;
1203
0
    ArgumentParser::NonEmpty<std::vector<std::string>> Named;
1204
0
    cm::optional<std::string> PropertyNameRegex;
1205
0
    cm::optional<std::string> PropertyValueRegex;
1206
    // Internal: set by the deprecated cmake_print_properties() module wrapper
1207
    // to select the historical leading-blank-line output instead of the
1208
    // banner.  Not part of the public interface.
1209
0
    bool CmakePrintProperties = false;
1210
0
  };
1211
1212
0
  auto ArgsParser =
1213
0
    cmArgumentParser<PrintPropertiesArg>()
1214
0
      .Bind("ALL"_s, &PrintPropertiesArg::All)
1215
0
      .Bind("NAMED"_s, &PrintPropertiesArg::Named)
1216
0
      .Bind("PROPERTY_NAME_REGEX"_s, &PrintPropertiesArg::PropertyNameRegex)
1217
0
      .Bind("PROPERTY_VALUE_REGEX"_s, &PrintPropertiesArg::PropertyValueRegex)
1218
0
      .Bind("__CMAKE_PRINT_PROPERTIES"_s,
1219
0
            &PrintPropertiesArg::CmakePrintProperties);
1220
1221
0
  std::vector<std::string> unparsed;
1222
0
  auto parsedArgs = ArgsParser.Parse(args, &unparsed);
1223
1224
0
  if (unparsed.empty()) {
1225
0
    return FatalError(status,
1226
0
                      "mode keyword missing in "
1227
0
                      "cmake_language(PRINT_PROPERTIES) call, "
1228
0
                      "there must be exactly one of TARGETS SOURCES "
1229
0
                      "TESTS DIRECTORIES CACHE_ENTRIES");
1230
0
  }
1231
1232
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
1233
0
    cmSystemTools::SetFatalErrorOccurred();
1234
0
    return true;
1235
0
  }
1236
1237
  // Second parse args to get the mode
1238
0
  struct PrintPropertiesModesArgs : public ArgumentParser::ParseResult
1239
0
  {
1240
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Targets;
1241
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Sources;
1242
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Tests;
1243
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories;
1244
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> CacheEntries;
1245
0
    bool Deferred = false;
1246
0
    bool FollowDependencies = false;
1247
0
  };
1248
0
  auto const ArgsParserMode =
1249
0
    cmArgumentParser<PrintPropertiesModesArgs>()
1250
0
      .Bind("TARGETS"_s, &PrintPropertiesModesArgs::Targets)
1251
0
      .Bind("SOURCES"_s, &PrintPropertiesModesArgs::Sources)
1252
0
      .Bind("TESTS"_s, &PrintPropertiesModesArgs::Tests)
1253
0
      .Bind("DIRECTORIES"_s, &PrintPropertiesModesArgs::Directories)
1254
0
      .Bind("CACHE_ENTRIES"_s, &PrintPropertiesModesArgs::CacheEntries)
1255
0
      .Bind("DEFERRED"_s, &PrintPropertiesModesArgs::Deferred)
1256
0
      .Bind("FOLLOW_DEPENDENCIES"_s,
1257
0
            &PrintPropertiesModesArgs::FollowDependencies);
1258
1259
0
  std::vector<std::string> modeArgs = unparsed;
1260
0
  unparsed.clear();
1261
0
  auto parsedArgsMode = ArgsParserMode.Parse(modeArgs, &unparsed);
1262
1263
0
  if (!unparsed.empty()) {
1264
0
    return FatalError(
1265
0
      status, cmStrCat("Unknown keywords: \"", cmJoin(unparsed, " "), '"'));
1266
0
  }
1267
1268
0
  if (parsedArgsMode.MaybeReportError(status.GetMakefile())) {
1269
0
    cmSystemTools::SetFatalErrorOccurred();
1270
0
    return true;
1271
0
  }
1272
0
  std::vector<std::string> modes;
1273
0
  std::vector<std::string> items;
1274
0
  EntityKind kind = EntityKind::Target;
1275
0
  if (!parsedArgsMode.Targets.empty()) {
1276
0
    modes.push_back("TARGETS");
1277
0
    items = parsedArgsMode.Targets;
1278
0
    kind = EntityKind::Target;
1279
0
  }
1280
0
  if (!parsedArgsMode.Sources.empty()) {
1281
0
    modes.push_back("SOURCES");
1282
0
    items = parsedArgsMode.Sources;
1283
0
    kind = EntityKind::Source;
1284
0
  }
1285
0
  if (!parsedArgsMode.Tests.empty()) {
1286
0
    modes.push_back("TESTS");
1287
0
    items = parsedArgsMode.Tests;
1288
0
    kind = EntityKind::Test;
1289
0
  }
1290
0
  if (!parsedArgsMode.Directories.empty()) {
1291
0
    modes.push_back("DIRECTORIES");
1292
0
    items = parsedArgsMode.Directories;
1293
0
    kind = EntityKind::Directory;
1294
0
  }
1295
0
  if (!parsedArgsMode.CacheEntries.empty()) {
1296
0
    modes.push_back("CACHE_ENTRIES");
1297
0
    items = parsedArgsMode.CacheEntries;
1298
0
    kind = EntityKind::Cache;
1299
0
  }
1300
1301
0
  if (modes.empty()) {
1302
0
    return FatalError(status,
1303
0
                      "mode keyword missing in "
1304
0
                      "cmake_language(PRINT_PROPERTIES) call, "
1305
0
                      "there must be exactly one of TARGETS SOURCES "
1306
0
                      "TESTS DIRECTORIES CACHE_ENTRIES");
1307
0
  }
1308
0
  if (modes.size() > 1) {
1309
0
    return FatalError(status,
1310
0
                      "multiple mode keywords used in "
1311
0
                      "cmake_language(PRINT_PROPERTIES) call, "
1312
0
                      "there must be exactly one of TARGETS SOURCES "
1313
0
                      "TESTS DIRECTORIES CACHE_ENTRIES.");
1314
0
  }
1315
0
  std::string const mode = modes[0];
1316
0
  bool const isTargets = (mode == "TARGETS");
1317
0
  bool const hasNamed = !parsedArgs.Named.empty();
1318
0
  bool const hasRegex =
1319
0
    parsedArgs.PropertyNameRegex || parsedArgs.PropertyValueRegex;
1320
1321
0
  if (!isTargets) {
1322
0
    if (parsedArgs.All) {
1323
0
      return FatalError(status,
1324
0
                        "ALL keyword in "
1325
0
                        "cmake_language(PRINT_PROPERTIES) call "
1326
0
                        "is only valid with the TARGETS scope.");
1327
0
    }
1328
0
    if (hasRegex) {
1329
0
      return FatalError(status,
1330
0
                        "PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in "
1331
0
                        "cmake_language(PRINT_PROPERTIES) call "
1332
0
                        "are only valid with the TARGETS scope and ALL.");
1333
0
    }
1334
0
    if (parsedArgsMode.Deferred) {
1335
0
      return FatalError(status,
1336
0
                        "DEFERRED keyword in "
1337
0
                        "cmake_language(PRINT_PROPERTIES) call "
1338
0
                        "is only valid with the TARGETS scope.");
1339
0
    }
1340
0
    if (parsedArgsMode.FollowDependencies) {
1341
0
      return FatalError(status,
1342
0
                        "FOLLOW_DEPENDENCIES keyword in "
1343
0
                        "cmake_language(PRINT_PROPERTIES) call "
1344
0
                        "is only valid with the TARGETS scope.");
1345
0
    }
1346
0
    if (!hasNamed) {
1347
0
      return FatalError(status,
1348
0
                        cmStrCat("NAMED keyword missing in "
1349
0
                                 "cmake_language(PRINT_PROPERTIES) call "
1350
0
                                 "with ",
1351
0
                                 mode, " scope."));
1352
0
    }
1353
0
  } else {
1354
    // ALL and NAMED are mutually exclusive on TARGETS.
1355
0
    if (parsedArgs.All && hasNamed) {
1356
0
      return FatalError(status,
1357
0
                        "ALL and NAMED keywords in "
1358
0
                        "cmake_language(PRINT_PROPERTIES) call "
1359
0
                        "are mutually exclusive.");
1360
0
    }
1361
    // Regex filters require ALL - explicit or implicit.  Combining regex
1362
    // with NAMED is an error.
1363
0
    if (hasNamed && hasRegex) {
1364
0
      return FatalError(status,
1365
0
                        "PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in "
1366
0
                        "cmake_language(PRINT_PROPERTIES) call "
1367
0
                        "are only valid with ALL, not NAMED.");
1368
0
    }
1369
0
  }
1370
1371
  // FOLLOW_DEPENDENCIES implies DEFERRED.
1372
0
  if (parsedArgsMode.FollowDependencies) {
1373
0
    parsedArgsMode.Deferred = true;
1374
0
  }
1375
1376
  // Default to ALL when neither ALL nor NAMED is given (TARGETS only - the
1377
  // non-TARGETS path already rejected this combination above).
1378
0
  bool const all = parsedArgs.All || !hasNamed;
1379
1380
  // __CMAKE_PRINT_PROPERTIES marks the deprecated cmake_print_properties()
1381
  // wrapper, which is always NAMED; reject it in ALL enumeration mode.
1382
0
  if (all && parsedArgs.CmakePrintProperties) {
1383
0
    return FatalError(status,
1384
0
                      "__CMAKE_PRINT_PROPERTIES in "
1385
0
                      "cmake_language(PRINT_PROPERTIES) call "
1386
0
                      "is only valid with NAMED.");
1387
0
  }
1388
1389
  // Compile regexes once up front so syntax errors are reported here rather
1390
  // than from inside helpers (in particular, from the deferred lambda at
1391
  // generate time).
1392
0
  cm::optional<cmsys::RegularExpression> propertyNameRegex;
1393
0
  cm::optional<cmsys::RegularExpression> propertyValueRegex;
1394
0
  if (parsedArgs.PropertyNameRegex) {
1395
0
    cmsys::RegularExpression re;
1396
0
    if (!re.compile(*parsedArgs.PropertyNameRegex)) {
1397
0
      return FatalError(status,
1398
0
                        cmStrCat("PROPERTY_NAME_REGEX regular expression \"",
1399
0
                                 *parsedArgs.PropertyNameRegex,
1400
0
                                 "\" cannot compile."));
1401
0
    }
1402
0
    propertyNameRegex = std::move(re);
1403
0
  }
1404
0
  if (parsedArgs.PropertyValueRegex) {
1405
0
    cmsys::RegularExpression re;
1406
0
    if (!re.compile(*parsedArgs.PropertyValueRegex)) {
1407
0
      return FatalError(status,
1408
0
                        cmStrCat("PROPERTY_VALUE_REGEX regular expression \"",
1409
0
                                 *parsedArgs.PropertyValueRegex,
1410
0
                                 "\" cannot compile."));
1411
0
    }
1412
0
    propertyValueRegex = std::move(re);
1413
0
  }
1414
1415
  // The message opens with a leading banner line.  The internal
1416
  // __CMAKE_PRINT_PROPERTIES marker (not part of the public interface) selects
1417
  // the legacy format instead; cmake_print_properties() sets it to reproduce
1418
  // its historical leading blank line.
1419
0
  std::string const messagePrefix = parsedArgs.CmakePrintProperties
1420
0
    ? std::string("\n")
1421
0
    : std::string("Printing properties...\n");
1422
1423
0
  if (parsedArgsMode.Deferred) {
1424
0
    return PrintTargetPropertiesDeferred(
1425
0
      items, parsedArgs.Named, all, parsedArgs.PropertyNameRegex,
1426
0
      parsedArgs.PropertyValueRegex, std::move(propertyNameRegex),
1427
0
      std::move(propertyValueRegex), parsedArgsMode.FollowDependencies,
1428
0
      messagePrefix, status);
1429
0
  }
1430
1431
0
  return PrintPropertiesConfigureTime(
1432
0
    status, parsedArgs.Named, all, items, kind, parsedArgs.PropertyNameRegex,
1433
0
    parsedArgs.PropertyValueRegex, propertyNameRegex, propertyValueRegex,
1434
0
    messagePrefix);
1435
0
}
1436
}
1437
bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
1438
                            cmExecutionStatus& status)
1439
0
{
1440
0
  std::vector<std::string> expArgs;
1441
0
  size_t rawArg = 0;
1442
0
  size_t expArg = 0;
1443
1444
  // Helper to consume and expand one raw argument at a time.
1445
0
  auto moreArgs = [&]() -> bool {
1446
0
    while (expArg >= expArgs.size()) {
1447
0
      if (rawArg >= args.size()) {
1448
0
        return false;
1449
0
      }
1450
0
      std::vector<cmListFileArgument> tmpArg;
1451
0
      tmpArg.emplace_back(args[rawArg++]);
1452
0
      status.GetMakefile().ExpandArguments(tmpArg, expArgs);
1453
0
    }
1454
0
    return true;
1455
0
  };
1456
0
  auto finishArgs = [&]() {
1457
0
    std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
1458
0
    status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
1459
0
    rawArg = args.size();
1460
0
  };
1461
1462
0
  if (!moreArgs()) {
1463
0
    return FatalError(status, "called with incorrect number of arguments");
1464
0
  }
1465
0
  if (expArgs[expArg] == "EXIT"_s) {
1466
0
    ++expArg; // consume "EXIT".
1467
1468
0
    if (!moreArgs()) {
1469
0
      return FatalError(status, "EXIT requires one argument");
1470
0
    }
1471
1472
0
    if (!status.GetMakefile().GetCMakeInstance()->RoleSupportsExitCode()) {
1473
0
      return FatalError(status, "EXIT can be used only in SCRIPT mode");
1474
0
    }
1475
1476
0
    long retCode = 0;
1477
1478
0
    if (!cmStrToLong(expArgs[expArg], &retCode)) {
1479
0
      return FatalError(status,
1480
0
                        cmStrCat("EXIT requires one integral argument, got \"",
1481
0
                                 expArgs[expArg], '\"'));
1482
0
    }
1483
1484
0
    status.SetExitCode(static_cast<int>(retCode));
1485
0
    return true;
1486
0
  }
1487
1488
0
  if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) {
1489
0
    finishArgs();
1490
0
    return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status);
1491
0
  }
1492
1493
0
  cm::optional<Defer> maybeDefer;
1494
0
  if (expArgs[expArg] == "DEFER"_s) {
1495
0
    ++expArg; // Consume "DEFER".
1496
1497
0
    if (!moreArgs()) {
1498
0
      return FatalError(status, "DEFER requires at least one argument");
1499
0
    }
1500
1501
0
    Defer defer;
1502
1503
    // Process optional arguments.
1504
0
    while (moreArgs()) {
1505
0
      if (expArgs[expArg] == "CALL"_s) {
1506
0
        break;
1507
0
      }
1508
0
      if (expArgs[expArg] == "CANCEL_CALL"_s ||
1509
0
          expArgs[expArg] == "GET_CALL_IDS"_s ||
1510
0
          expArgs[expArg] == "GET_CALL"_s) {
1511
0
        if (!defer.Id.empty() || !defer.IdVar.empty()) {
1512
0
          return FatalError(status,
1513
0
                            cmStrCat("DEFER "_s, expArgs[expArg],
1514
0
                                     " does not accept ID or ID_VAR."_s));
1515
0
        }
1516
0
        finishArgs();
1517
0
        return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
1518
0
      }
1519
0
      if (expArgs[expArg] == "DIRECTORY"_s) {
1520
0
        ++expArg; // Consume "DIRECTORY".
1521
0
        if (defer.Directory) {
1522
0
          return FatalError(status,
1523
0
                            "DEFER given multiple DIRECTORY arguments");
1524
0
        }
1525
0
        if (!moreArgs()) {
1526
0
          return FatalError(status, "DEFER DIRECTORY missing value");
1527
0
        }
1528
0
        std::string dir = expArgs[expArg++];
1529
0
        if (dir.empty()) {
1530
0
          return FatalError(status, "DEFER DIRECTORY may not be empty");
1531
0
        }
1532
0
        dir = cmSystemTools::CollapseFullPath(
1533
0
          dir, status.GetMakefile().GetCurrentSourceDirectory());
1534
0
        defer.Directory =
1535
0
          status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
1536
0
        if (!defer.Directory) {
1537
0
          return FatalError(status,
1538
0
                            cmStrCat("DEFER DIRECTORY:\n  "_s, dir,
1539
0
                                     "\nis not known.  "
1540
0
                                     "It may not have been processed yet."_s));
1541
0
        }
1542
0
      } else if (expArgs[expArg] == "ID"_s) {
1543
0
        ++expArg; // Consume "ID".
1544
0
        if (!defer.Id.empty()) {
1545
0
          return FatalError(status, "DEFER given multiple ID arguments");
1546
0
        }
1547
0
        if (!moreArgs()) {
1548
0
          return FatalError(status, "DEFER ID missing value");
1549
0
        }
1550
0
        defer.Id = expArgs[expArg++];
1551
0
        if (defer.Id.empty()) {
1552
0
          return FatalError(status, "DEFER ID may not be empty");
1553
0
        }
1554
0
        if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
1555
0
          return FatalError(status, "DEFER ID may not start in A-Z.");
1556
0
        }
1557
0
      } else if (expArgs[expArg] == "ID_VAR"_s) {
1558
0
        ++expArg; // Consume "ID_VAR".
1559
0
        if (!defer.IdVar.empty()) {
1560
0
          return FatalError(status, "DEFER given multiple ID_VAR arguments");
1561
0
        }
1562
0
        if (!moreArgs()) {
1563
0
          return FatalError(status, "DEFER ID_VAR missing variable name");
1564
0
        }
1565
0
        defer.IdVar = expArgs[expArg++];
1566
0
        if (defer.IdVar.empty()) {
1567
0
          return FatalError(status, "DEFER ID_VAR may not be empty");
1568
0
        }
1569
0
      } else {
1570
0
        return FatalError(
1571
0
          status, cmStrCat("DEFER unknown option:\n  "_s, expArgs[expArg]));
1572
0
      }
1573
0
    }
1574
1575
0
    if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
1576
0
      return FatalError(status, "DEFER must be followed by a CALL argument");
1577
0
    }
1578
1579
0
    maybeDefer = std::move(defer);
1580
0
  }
1581
1582
0
  if (expArgs[expArg] == "CALL") {
1583
0
    ++expArg; // Consume "CALL".
1584
1585
    // CALL requires a command name.
1586
0
    if (!moreArgs()) {
1587
0
      return FatalError(status, "CALL missing command name");
1588
0
    }
1589
0
    std::string const& callCommand = expArgs[expArg++];
1590
1591
    // CALL accepts no further expanded arguments.
1592
0
    if (expArg != expArgs.size()) {
1593
0
      return FatalError(status, "CALL command's arguments must be literal");
1594
0
    }
1595
1596
    // Run the CALL.
1597
0
    return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
1598
0
                                      std::move(maybeDefer), status);
1599
0
  }
1600
1601
0
  if (expArgs[expArg] == "EVAL") {
1602
0
    return cmCMakeLanguageCommandEVAL(args, status);
1603
0
  }
1604
1605
0
  if (expArgs[expArg] == "GET_MESSAGE_LOG_LEVEL") {
1606
0
    return cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(args, status);
1607
0
  }
1608
1609
0
  if (expArgs[expArg] == "GET_EXPERIMENTAL_FEATURE_ENABLED") {
1610
0
    return cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(args,
1611
0
                                                                  status);
1612
0
  }
1613
1614
0
  if (expArgs[expArg] == "PRINT_TARGETS") {
1615
0
    return cmCMakeLanguageCommandPRINT_TARGETS(args, status);
1616
0
  }
1617
1618
0
  if (expArgs[expArg] == "PRINT_VARIABLES") {
1619
0
    return cmCMakeLanguageCommandPRINT_VARIABLES(args, status);
1620
0
  }
1621
1622
0
  if (expArgs[expArg] == "TRACE") {
1623
0
    ++expArg; // Consume "TRACE".
1624
1625
0
    if (!moreArgs()) {
1626
0
      return FatalError(status, "TRACE missing a boolean value");
1627
0
    }
1628
1629
0
    bool const value = cmValue::IsOn(expArgs[expArg++]);
1630
0
    bool expand = false;
1631
1632
0
    if (value && moreArgs()) {
1633
0
      expand = (expArgs[expArg] == "EXPAND");
1634
0
      if (!expand) {
1635
0
        return FatalError(
1636
0
          status,
1637
0
          cmStrCat("TRACE ON given an invalid argument ", expArgs[expArg]));
1638
0
      }
1639
0
      ++expArg;
1640
0
    }
1641
1642
0
    if (moreArgs()) {
1643
0
      return FatalError(
1644
0
        status,
1645
0
        cmStrCat("TRACE O", value ? "N" : "FF", " given too many arguments"));
1646
0
    }
1647
1648
0
    cmMakefile& makefile = status.GetMakefile();
1649
0
    if (value) {
1650
0
      makefile.GetCMakeInstance()->PushTraceCmd(expand);
1651
0
      return true;
1652
0
    }
1653
0
    return makefile.GetCMakeInstance()->PopTraceCmd() ||
1654
0
      FatalError(status, "TRACE OFF request without a corresponding TRACE ON");
1655
0
  }
1656
1657
0
  if (expArgs[expArg] == "PRINT_PROPERTIES") {
1658
0
    ++expArg;
1659
0
    finishArgs();
1660
0
    std::vector<std::string> const printPropertyArgs(expArgs.begin() + expArg,
1661
0
                                                     expArgs.end());
1662
0
    return cmCMakeLanguageCommandPRINT_PROPERTIES(printPropertyArgs, status);
1663
0
  }
1664
1665
0
  return FatalError(status, "called with unknown meta-operation");
1666
0
}