Coverage Report

Created: 2026-09-14 06:43

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 +=
497
0
      cmStrCat("   ", t.first, " (", 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(" ", label);
513
0
    if (parsedArgs.Regex) {
514
0
      out += cmStrCat(
515
0
        " matching REGEX '", *parsedArgs.Regex, "' (",
516
0
        parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive", ")");
517
0
    }
518
0
    out += cmStrCat(":\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(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("   ", name, " = \"", *v, "\"\n");
581
0
    } else {
582
0
      out += cmStrCat("   ", 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("   ", 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("   CACHE{", name, "}");
667
0
      if (type != cmStateEnums::UNINITIALIZED) {
668
0
        body += cmStrCat(":", cmState::CacheEntryTypeToString(type));
669
0
      }
670
0
      body += cmStrCat(" = \"", *cached, "\"\n");
671
0
      anyMatched = true;
672
0
    }
673
0
  }
674
675
0
  if (anyMatched) {
676
0
    cmValue listFile = snapshot.GetDefinition("CMAKE_CURRENT_LIST_FILE");
677
0
    std::string out =
678
0
      cmStrCat(PrintVariablesBanner, " Variables in scope at '",
679
0
               listFile ? *listFile : std::string("<unknown>"), "'");
680
0
    if (parsed.NameRegex || parsed.ValueRegex) {
681
0
      out += " matching";
682
0
      if (parsed.NameRegex) {
683
0
        out += cmStrCat(" name '", *parsed.NameRegex, "'");
684
0
      }
685
0
      if (parsed.NameRegex && parsed.ValueRegex) {
686
0
        out += " and";
687
0
      }
688
0
      if (parsed.ValueRegex) {
689
0
        out += cmStrCat(" value '", *parsed.ValueRegex, "'");
690
0
      }
691
0
      out += parsed.IgnoreCase ? " (case insensitive)" : " (case sensitive)";
692
0
    }
693
0
    out += cmStrCat(":\n", body);
694
0
    makefile.DisplayStatus(out, -1);
695
0
  }
696
697
0
  if (!anyMatched && (parsed.NameRegex || parsed.ValueRegex)) {
698
0
    std::string msg = "No variables in scope matching";
699
0
    if (parsed.NameRegex) {
700
0
      msg += cmStrCat(" name '", *parsed.NameRegex, "'");
701
0
    }
702
0
    if (parsed.NameRegex && parsed.ValueRegex) {
703
0
      msg += " and";
704
0
    }
705
0
    if (parsed.ValueRegex) {
706
0
      msg += cmStrCat(" value '", *parsed.ValueRegex, "'");
707
0
    }
708
0
    msg += parsed.IgnoreCase ? " (case insensitive)" : " (case sensitive)";
709
0
    msg += " in cmake_language(PRINT_VARIABLES ...).";
710
0
    makefile.IssueMessage(MessageType::WARNING, msg);
711
0
  }
712
0
  return true;
713
0
}
714
715
bool cmCMakeLanguageCommandPRINT_VARIABLES(
716
  std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
717
0
{
718
0
  cmMakefile& makefile = status.GetMakefile();
719
0
  std::vector<std::string> expandedArgs;
720
0
  makefile.ExpandArguments(args, expandedArgs);
721
722
  // Drop the leading "PRINT_VARIABLES" subcommand keyword.
723
0
  std::vector<std::string> body(expandedArgs.begin() + 1, expandedArgs.end());
724
725
0
  auto const ArgsParser =
726
0
    cmArgumentParser<PrintVariablesArgs>()
727
0
      .Bind("ALL"_s, &PrintVariablesArgs::All)
728
0
      .Bind("NAMED"_s, &PrintVariablesArgs::Named)
729
0
      .Bind("NAME_REGEX"_s, &PrintVariablesArgs::NameRegex)
730
0
      .Bind("VALUE_REGEX"_s, &PrintVariablesArgs::ValueRegex)
731
0
      .Bind("IGNORE_CASE"_s, &PrintVariablesArgs::IgnoreCase)
732
0
      .Bind("__CMAKE_PRINT_VARIABLES"_s,
733
0
            &PrintVariablesArgs::CmakePrintVariables);
734
735
0
  std::vector<std::string> unparsed;
736
0
  auto parsedArgs = ArgsParser.Parse(body, &unparsed);
737
738
  // No bareword form: every token must belong to ALL, NAMED, or a filter.
739
0
  if (!unparsed.empty()) {
740
0
    return FatalError(
741
0
      status,
742
0
      cmStrCat("Unknown argument(s) given to cmake_language(PRINT_VARIABLES)"
743
0
               ": \"",
744
0
               cmJoin(unparsed, "\" \""), "\"."));
745
0
  }
746
747
0
  if (parsedArgs.MaybeReportError(makefile)) {
748
0
    cmSystemTools::SetFatalErrorOccurred();
749
0
    return true;
750
0
  }
751
752
0
  bool const hasNamed = !parsedArgs.Named.empty();
753
0
  bool const hasFilters =
754
0
    parsedArgs.NameRegex || parsedArgs.ValueRegex || parsedArgs.IgnoreCase;
755
756
  // ALL and NAMED are mutually exclusive modes.
757
0
  if (parsedArgs.All && hasNamed) {
758
0
    return FatalError(status,
759
0
                      "ALL and NAMED keywords in "
760
0
                      "cmake_language(PRINT_VARIABLES) call "
761
0
                      "are mutually exclusive.");
762
0
  }
763
764
  // The filter keywords narrow an enumeration, so they only make sense with
765
  // ALL (explicit or implicit), never with NAMED.
766
0
  if (hasNamed && hasFilters) {
767
0
    return FatalError(status,
768
0
                      "NAME_REGEX, VALUE_REGEX, and IGNORE_CASE in "
769
0
                      "cmake_language(PRINT_VARIABLES) call "
770
0
                      "are only valid with ALL, not NAMED.");
771
0
  }
772
773
  // __CMAKE_PRINT_VARIABLES selects the legacy single-line NAMED output; it is
774
  // meaningless when enumerating variables (ALL, explicit or implicit).
775
0
  if (!hasNamed && parsedArgs.CmakePrintVariables) {
776
0
    return FatalError(status,
777
0
                      "__CMAKE_PRINT_VARIABLES in "
778
0
                      "cmake_language(PRINT_VARIABLES) call is only valid "
779
0
                      "with NAMED.");
780
0
  }
781
782
  // Default to ALL when neither mode keyword is given.
783
0
  bool const all = parsedArgs.All || !hasNamed;
784
0
  if (all) {
785
0
    return PrintVariablesAll(makefile, parsedArgs, status);
786
0
  }
787
788
0
  PrintVariablesNamed(makefile, parsedArgs.Named,
789
0
                      parsedArgs.CmakePrintVariables);
790
0
  return true;
791
0
}
792
// Walks every target reachable from `root` through any linkage - PUBLIC,
793
// INTERFACE, PRIVATE, and `$<LINK_ONLY:>`-wrapped deps.  At each level we
794
// union the link implementation and link interface, each queried with both
795
// UseTo::Compile and UseTo::Link, so deps that only appear under LINK_ONLY
796
// (which evaluates to empty under UseTo::Compile) still come through.
797
std::vector<cmGeneratorTarget const*> CollectDependentTargets(
798
  cmGeneratorTarget const* root, std::string const& config)
799
0
{
800
0
  std::vector<cmGeneratorTarget const*> deps;
801
0
  std::set<cmGeneratorTarget const*> visited;
802
0
  visited.insert(root);
803
0
  std::vector<cmGeneratorTarget const*> queue;
804
0
  queue.push_back(root);
805
0
  while (!queue.empty()) {
806
0
    cmGeneratorTarget const* cur = queue.back();
807
0
    queue.pop_back();
808
0
    auto visit = [&](std::vector<cmLinkItem> const& libs) {
809
0
      for (cmLinkItem const& item : libs) {
810
0
        cmGeneratorTarget const* dep = item.Target;
811
0
        if (!dep || !visited.insert(dep).second) {
812
0
          continue;
813
0
        }
814
0
        deps.push_back(dep);
815
0
        queue.push_back(dep);
816
0
      }
817
0
    };
818
0
    for (auto useTo : { cmGeneratorTarget::UseTo::Compile,
819
0
                        cmGeneratorTarget::UseTo::Link }) {
820
0
      if (cmLinkImplementationLibraries const* impl =
821
0
            cur->GetLinkImplementationLibraries(config, useTo)) {
822
0
        visit(impl->Libraries);
823
0
      }
824
0
      if (cmLinkInterfaceLibraries const* iface =
825
0
            cur->GetLinkInterfaceLibraries(config, root, useTo)) {
826
0
        visit(iface->Libraries);
827
0
      }
828
0
    }
829
0
  }
830
0
  return deps;
831
0
}
832
833
// Append one property line to `out`:
834
//   "   <entityName>.<propertyName> = \"<value>\""
835
// A null `value` writes "<NOTFOUND>" instead of "= \"...\"".
836
void WritePropertyLine(std::string& out, std::string const& entityName,
837
                       std::string const& propertyName, cmValue value)
838
0
{
839
0
  out += cmStrCat("   ", entityName, ".", propertyName);
840
0
  if (value) {
841
0
    out += cmStrCat(" = \"", *value, "\"");
842
0
  } else {
843
0
    out += " = <NOTFOUND>";
844
0
  }
845
0
  out += "\n";
846
0
}
847
848
enum class BlockKind
849
{
850
  Explicit,
851
  All,
852
};
853
854
enum class HeaderSuffix
855
{
856
  None,
857
  Reachable,
858
};
859
860
void EmitBlockHeader(
861
  std::string& out, std::string const& entityName,
862
  std::string const& entityType, BlockKind kind, HeaderSuffix suffix,
863
  cm::optional<std::string> const& nameRegexStr = cm::nullopt,
864
  cm::optional<std::string> const& valueRegexStr = cm::nullopt)
865
0
{
866
0
  out +=
867
0
    cmStrCat(" ", (kind == BlockKind::All ? "All properties" : "Properties"),
868
0
             " for ", entityType, " ", entityName);
869
0
  if (suffix == HeaderSuffix::Reachable) {
870
0
    out += " (and all reachable)";
871
0
  }
872
0
  if (kind == BlockKind::All && (nameRegexStr || valueRegexStr)) {
873
0
    out += " matching";
874
0
    if (nameRegexStr) {
875
0
      out += cmStrCat(" name '", *nameRegexStr, "'");
876
0
    }
877
0
    if (nameRegexStr && valueRegexStr) {
878
0
      out += " and";
879
0
    }
880
0
    if (valueRegexStr) {
881
0
      out += cmStrCat(" value '", *valueRegexStr, "'");
882
0
    }
883
0
  }
884
0
  out += ":\n";
885
0
}
886
887
// Build the "no properties matched" warning text used when the regex filter
888
// wipes everything out for a target's ALL block.  Returned without any
889
// "Warning:" prefix - callers feed it to IssueMessage, which adds the
890
// standard "CMake Warning at <file>:<line>" framing.
891
std::string EmptyMatchWarningMessage(
892
  std::string const& entityName, std::string const& entityType,
893
  cm::optional<std::string> const& nameRegexStr,
894
  cm::optional<std::string> const& valueRegexStr)
895
0
{
896
0
  std::string msg =
897
0
    cmStrCat("No properties for ", entityType, " ", entityName, " matching");
898
0
  if (nameRegexStr) {
899
0
    msg += cmStrCat(" name '", *nameRegexStr, "'");
900
0
  }
901
0
  if (nameRegexStr && valueRegexStr) {
902
0
    msg += " and";
903
0
  }
904
0
  if (valueRegexStr) {
905
0
    msg += cmStrCat(" value '", *valueRegexStr, "'");
906
0
  }
907
0
  msg += " in cmake_language(PRINT_PROPERTIES ...).";
908
0
  return msg;
909
0
}
910
911
// Entity kinds supported by cmake_language(PRINT_PROPERTIES).
912
enum class EntityKind
913
{
914
  Target,
915
  Source,
916
  Test,
917
  Directory,
918
  Cache
919
};
920
921
// Display name used in PRINT_PROPERTIES output ("TARGET", "SOURCE", etc.).
922
char const* EntityTypeName(EntityKind kind)
923
0
{
924
0
  switch (kind) {
925
0
    case EntityKind::Target:
926
0
      return "TARGET";
927
0
    case EntityKind::Source:
928
0
      return "SOURCE";
929
0
    case EntityKind::Test:
930
0
      return "TEST";
931
0
    case EntityKind::Directory:
932
0
      return "DIRECTORY";
933
0
    case EntityKind::Cache:
934
0
      return "CACHE";
935
0
  }
936
0
  return "";
937
0
}
938
939
// Dispatch a property lookup to the appropriate per-entity helper.
940
bool GetPropertyHelper(cmExecutionStatus& status, EntityKind kind,
941
                       std::string const& name,
942
                       std::string const& propertyName, cmValue& out)
943
0
{
944
0
  switch (kind) {
945
0
    case EntityKind::Target:
946
0
      return GetPropertyCommand::LookupTargetProperty(status, name,
947
0
                                                      propertyName, out);
948
0
    case EntityKind::Source:
949
0
      return GetPropertyCommand::LookupSourceProperty(status, name,
950
0
                                                      propertyName, out);
951
0
    case EntityKind::Test:
952
0
      return GetPropertyCommand::LookupTestProperty(status, name, propertyName,
953
0
                                                    out);
954
0
    case EntityKind::Directory:
955
0
      return GetPropertyCommand::LookupDirectoryProperty(status, name,
956
0
                                                         propertyName, out);
957
0
    case EntityKind::Cache:
958
0
      return GetPropertyCommand::LookupCacheProperty(status, name,
959
0
                                                     propertyName, out);
960
0
  }
961
0
  return false;
962
0
}
963
964
// Emit one ALL-mode block: prints all properties matching the specified
965
// regexes for a head target (`targets[0]`) and optionally any reachable
966
// dependencies (`targets[1]` and on).
967
bool EmitAllPropertiesBlock(std::string& out, std::string const& headerName,
968
                            std::vector<cmTarget*> const& targets,
969
                            HeaderSuffix suffix,
970
                            cm::optional<cmsys::RegularExpression>& nameRegex,
971
                            cm::optional<cmsys::RegularExpression>& valueRegex,
972
                            cm::optional<std::string> const& nameRegexStr,
973
                            cm::optional<std::string> const& valueRegexStr)
974
0
{
975
0
  std::string groupBuf;
976
0
  bool groupEmitted = false;
977
0
  for (cmTarget* t : targets) {
978
0
    std::string const& rowName =
979
0
      (t == targets.front()) ? headerName : t->GetName();
980
0
    for (auto const& kv : t->GetExtendedProperties().GetList()) {
981
0
      if (nameRegex && !nameRegex->find(kv.first)) {
982
0
        continue;
983
0
      }
984
0
      if (valueRegex && !valueRegex->find(kv.second)) {
985
0
        continue;
986
0
      }
987
0
      WritePropertyLine(groupBuf, rowName, kv.first, cmValue(kv.second));
988
0
      groupEmitted = true;
989
0
    }
990
0
  }
991
0
  if (!groupEmitted) {
992
0
    return false;
993
0
  }
994
0
  EmitBlockHeader(out, headerName, "TARGET", BlockKind::All, suffix,
995
0
                  nameRegexStr, valueRegexStr);
996
0
  out += groupBuf;
997
0
  return true;
998
0
}
999
1000
// Outcome of emitting a NAMED-mode block.
1001
enum class NamedBlockResult
1002
{
1003
  Failed,  // a property lookup hard-failed
1004
  Emitted, // at least one property line was written
1005
  Empty,   // every requested property was skipped; header suppressed
1006
};
1007
1008
// Emit one block of user-specified properties for a head target (`targets[0]`)
1009
// and optionally any reachable dependencies (`targets[1]` and on).
1010
NamedBlockResult EmitNamedPropertiesBlock(
1011
  std::string& out, std::string const& headerName,
1012
  std::vector<cmTarget*> const& targets,
1013
  std::vector<std::string> const& namedProperties, HeaderSuffix suffix,
1014
  cmMakefile& mf)
1015
0
{
1016
0
  bool headerWritten = false;
1017
0
  for (cmTarget* t : targets) {
1018
0
    std::string const& rowName =
1019
0
      (t == targets.front()) ? headerName : t->GetName();
1020
0
    for (std::string const& propName : namedProperties) {
1021
      // A computed location property may not be read from a non-imported
1022
      // target; skip it with a warning rather than triggering the getter's
1023
      // fatal error.
1024
0
      if (!t->IsImported() &&
1025
0
          cmTargetPropertyComputer::IsComputedLocationProperty(t->GetType(),
1026
0
                                                               propName)) {
1027
0
        mf.IssueMessage(
1028
0
          MessageType::WARNING,
1029
0
          cmStrCat("The ", propName,
1030
0
                   " property may not be read from non-imported target \"",
1031
0
                   rowName, "\"; skipping."));
1032
0
        continue;
1033
0
      }
1034
0
      cmValue v;
1035
0
      if (cmGetTargetProperty(rowName, t, propName, mf, v) !=
1036
0
          cmGetTargetPropertyResult::Success) {
1037
0
        return NamedBlockResult::Failed;
1038
0
      }
1039
0
      if (!headerWritten) {
1040
0
        EmitBlockHeader(out, headerName, "TARGET", BlockKind::Explicit,
1041
0
                        suffix);
1042
0
        headerWritten = true;
1043
0
      }
1044
0
      WritePropertyLine(out, rowName, propName, v);
1045
0
    }
1046
0
  }
1047
0
  return headerWritten ? NamedBlockResult::Emitted : NamedBlockResult::Empty;
1048
0
}
1049
1050
bool PrintPropertiesConfigureTime(
1051
  cmExecutionStatus& status, std::vector<std::string> const& namedProperties,
1052
  bool all, std::vector<std::string> const& entityNames, EntityKind kind,
1053
  cm::optional<std::string> const& propertyNameRegexStr,
1054
  cm::optional<std::string> const& propertyValueRegexStr,
1055
  cm::optional<cmsys::RegularExpression>& propertyNameRegex,
1056
  cm::optional<cmsys::RegularExpression>& propertyValueRegex,
1057
  std::string const& messagePrefix)
1058
0
{
1059
0
  cmMakefile& makefile = status.GetMakefile();
1060
0
  bool const hasRegex = propertyNameRegexStr || propertyValueRegexStr;
1061
0
  std::string out = messagePrefix;
1062
0
  bool anyEmitted = false;
1063
0
  for (auto const& entityName : entityNames) {
1064
0
    if (kind == EntityKind::Target) {
1065
0
      cmTarget* target = makefile.FindTargetToUse(entityName);
1066
0
      if (!target) {
1067
0
        out += cmStrCat("\n No such TARGET \"", entityName, "\" !\n\n");
1068
0
        anyEmitted = true;
1069
0
        continue;
1070
0
      }
1071
0
      if (all) {
1072
0
        bool const emitted = EmitAllPropertiesBlock(
1073
0
          out, entityName, { target }, HeaderSuffix::None, propertyNameRegex,
1074
0
          propertyValueRegex, propertyNameRegexStr, propertyValueRegexStr);
1075
0
        if (emitted) {
1076
0
          anyEmitted = true;
1077
0
        } else if (hasRegex) {
1078
0
          makefile.IssueMessage(MessageType::WARNING,
1079
0
                                EmptyMatchWarningMessage(
1080
0
                                  entityName, "TARGET", propertyNameRegexStr,
1081
0
                                  propertyValueRegexStr));
1082
0
        }
1083
0
      } else {
1084
0
        NamedBlockResult const result = EmitNamedPropertiesBlock(
1085
0
          out, entityName, { target }, namedProperties, HeaderSuffix::None,
1086
0
          makefile);
1087
0
        if (result == NamedBlockResult::Failed) {
1088
0
          status.SetError(cmStrCat(
1089
0
            "failed to retrieve properties for TARGET \"", entityName, "\""));
1090
0
          return false;
1091
0
        }
1092
0
        if (result == NamedBlockResult::Emitted) {
1093
0
          anyEmitted = true;
1094
0
        }
1095
0
      }
1096
0
    } else {
1097
0
      EmitBlockHeader(out, entityName, EntityTypeName(kind),
1098
0
                      BlockKind::Explicit, HeaderSuffix::None);
1099
0
      for (auto const& propertyName : namedProperties) {
1100
0
        cmValue v;
1101
0
        if (!GetPropertyHelper(status, kind, entityName, propertyName, v)) {
1102
0
          return false;
1103
0
        }
1104
0
        WritePropertyLine(out, entityName, propertyName, v);
1105
0
      }
1106
0
      anyEmitted = true;
1107
0
    }
1108
0
  }
1109
0
  if (anyEmitted) {
1110
0
    makefile.DisplayStatus(out, -1);
1111
0
  }
1112
0
  return true;
1113
0
}
1114
1115
bool PrintTargetPropertiesDeferred(
1116
  std::vector<std::string> targetNames,
1117
  std::vector<std::string> namedProperties, bool all,
1118
  cm::optional<std::string> propertyNameRegexStr,
1119
  cm::optional<std::string> propertyValueRegexStr,
1120
  cm::optional<cmsys::RegularExpression> propertyNameRegex,
1121
  cm::optional<cmsys::RegularExpression> propertyValueRegex,
1122
  bool followDependencies, std::string messagePrefix,
1123
  cmExecutionStatus& status)
1124
0
{
1125
0
  cmListFileBacktrace const bt = status.GetMakefile().GetBacktrace();
1126
0
  status.GetMakefile().AddGeneratorAction(
1127
0
    [targetNames, namedProperties, all, propertyNameRegexStr,
1128
0
     propertyValueRegexStr, propertyNameRegex, propertyValueRegex,
1129
0
     followDependencies, messagePrefix, bt](
1130
0
      cmLocalGenerator& lg, cmListFileBacktrace const& /*lambdaBt*/) mutable {
1131
0
      std::vector<std::string> const configs =
1132
0
        lg.GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
1133
0
      std::string const config =
1134
0
        configs.empty() ? std::string() : configs.front();
1135
0
      bool const hasRegex = propertyNameRegexStr || propertyValueRegexStr;
1136
0
      cmake* cmakeInst = lg.GetMakefile()->GetCMakeInstance();
1137
1138
0
      std::string out = messagePrefix;
1139
0
      bool anyEmitted = false;
1140
0
      for (std::string const& name : targetNames) {
1141
0
        cmGeneratorTarget* root = lg.FindGeneratorTargetToUse(name);
1142
0
        if (!root) {
1143
0
          out += cmStrCat("\n No such TARGET \"", name, "\" !\n\n");
1144
0
          anyEmitted = true;
1145
0
          continue;
1146
0
        }
1147
0
        std::vector<cmTarget*> targets;
1148
0
        targets.push_back(root->Target);
1149
0
        HeaderSuffix const suffix =
1150
0
          followDependencies ? HeaderSuffix::Reachable : HeaderSuffix::None;
1151
0
        if (followDependencies) {
1152
0
          for (cmGeneratorTarget const* dep :
1153
0
               CollectDependentTargets(root, config)) {
1154
0
            targets.push_back(dep->Target);
1155
0
          }
1156
0
        }
1157
0
        if (all) {
1158
0
          bool const emitted = EmitAllPropertiesBlock(
1159
0
            out, name, targets, suffix, propertyNameRegex, propertyValueRegex,
1160
0
            propertyNameRegexStr, propertyValueRegexStr);
1161
0
          if (emitted) {
1162
0
            anyEmitted = true;
1163
0
          } else if (hasRegex) {
1164
0
            cmakeInst->IssueMessage(
1165
0
              MessageType::WARNING,
1166
0
              EmptyMatchWarningMessage(name, "TARGET", propertyNameRegexStr,
1167
0
                                       propertyValueRegexStr),
1168
0
              bt);
1169
0
          }
1170
0
        } else {
1171
0
          NamedBlockResult const result = EmitNamedPropertiesBlock(
1172
0
            out, name, targets, namedProperties, suffix, *lg.GetMakefile());
1173
0
          if (result == NamedBlockResult::Failed) {
1174
0
            cmakeInst->IssueMessage(
1175
0
              MessageType::FATAL_ERROR,
1176
0
              cmStrCat("failed to retrieve properties for TARGET \"", name,
1177
0
                       "\""),
1178
0
              bt);
1179
0
            return;
1180
0
          }
1181
0
          if (result == NamedBlockResult::Emitted) {
1182
0
            anyEmitted = true;
1183
0
          }
1184
0
        }
1185
0
      }
1186
0
      if (anyEmitted) {
1187
0
        lg.GetMakefile()->DisplayStatus(out, -1);
1188
0
      }
1189
0
    },
1190
0
    cmMakefile::GeneratorActionWhen::AfterGeneratorTargets);
1191
0
  return true;
1192
0
}
1193
1194
bool cmCMakeLanguageCommandPRINT_PROPERTIES(
1195
  std::vector<std::string> const& args, cmExecutionStatus& status)
1196
0
{
1197
0
  struct PrintPropertiesArg : public ArgumentParser::ParseResult
1198
0
  {
1199
0
    bool All = false;
1200
0
    ArgumentParser::NonEmpty<std::vector<std::string>> Named;
1201
0
    cm::optional<std::string> PropertyNameRegex;
1202
0
    cm::optional<std::string> PropertyValueRegex;
1203
    // Internal: set by the deprecated cmake_print_properties() module wrapper
1204
    // to select the historical leading-blank-line output instead of the
1205
    // banner.  Not part of the public interface.
1206
0
    bool CmakePrintProperties = false;
1207
0
  };
1208
1209
0
  auto ArgsParser =
1210
0
    cmArgumentParser<PrintPropertiesArg>()
1211
0
      .Bind("ALL"_s, &PrintPropertiesArg::All)
1212
0
      .Bind("NAMED"_s, &PrintPropertiesArg::Named)
1213
0
      .Bind("PROPERTY_NAME_REGEX"_s, &PrintPropertiesArg::PropertyNameRegex)
1214
0
      .Bind("PROPERTY_VALUE_REGEX"_s, &PrintPropertiesArg::PropertyValueRegex)
1215
0
      .Bind("__CMAKE_PRINT_PROPERTIES"_s,
1216
0
            &PrintPropertiesArg::CmakePrintProperties);
1217
1218
0
  std::vector<std::string> unparsed;
1219
0
  auto parsedArgs = ArgsParser.Parse(args, &unparsed);
1220
1221
0
  if (unparsed.empty()) {
1222
0
    return FatalError(status,
1223
0
                      cmStrCat("mode keyword missing in ",
1224
0
                               "cmake_language(PRINT_PROPERTIES) call, ",
1225
0
                               "there must be exactly one of TARGETS SOURCES "
1226
0
                               "TESTS DIRECTORIES CACHE_ENTRIES"));
1227
0
  }
1228
1229
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
1230
0
    cmSystemTools::SetFatalErrorOccurred();
1231
0
    return true;
1232
0
  }
1233
1234
  // Second parse args to get the mode
1235
0
  struct PrintPropertiesModesArgs : public ArgumentParser::ParseResult
1236
0
  {
1237
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Targets;
1238
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Sources;
1239
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Tests;
1240
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories;
1241
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> CacheEntries;
1242
0
    bool Deferred = false;
1243
0
    bool FollowDependencies = false;
1244
0
  };
1245
0
  auto const ArgsParserMode =
1246
0
    cmArgumentParser<PrintPropertiesModesArgs>()
1247
0
      .Bind("TARGETS"_s, &PrintPropertiesModesArgs::Targets)
1248
0
      .Bind("SOURCES"_s, &PrintPropertiesModesArgs::Sources)
1249
0
      .Bind("TESTS"_s, &PrintPropertiesModesArgs::Tests)
1250
0
      .Bind("DIRECTORIES"_s, &PrintPropertiesModesArgs::Directories)
1251
0
      .Bind("CACHE_ENTRIES"_s, &PrintPropertiesModesArgs::CacheEntries)
1252
0
      .Bind("DEFERRED"_s, &PrintPropertiesModesArgs::Deferred)
1253
0
      .Bind("FOLLOW_DEPENDENCIES"_s,
1254
0
            &PrintPropertiesModesArgs::FollowDependencies);
1255
1256
0
  std::vector<std::string> modeArgs = unparsed;
1257
0
  unparsed.clear();
1258
0
  auto parsedArgsMode = ArgsParserMode.Parse(modeArgs, &unparsed);
1259
1260
0
  if (!unparsed.empty()) {
1261
0
    return FatalError(
1262
0
      status, cmStrCat("Unknown keywords: \"", cmJoin(unparsed, " "), "\""));
1263
0
  }
1264
1265
0
  if (parsedArgsMode.MaybeReportError(status.GetMakefile())) {
1266
0
    cmSystemTools::SetFatalErrorOccurred();
1267
0
    return true;
1268
0
  }
1269
0
  std::vector<std::string> modes;
1270
0
  std::vector<std::string> items;
1271
0
  EntityKind kind = EntityKind::Target;
1272
0
  if (!parsedArgsMode.Targets.empty()) {
1273
0
    modes.push_back("TARGETS");
1274
0
    items = parsedArgsMode.Targets;
1275
0
    kind = EntityKind::Target;
1276
0
  }
1277
0
  if (!parsedArgsMode.Sources.empty()) {
1278
0
    modes.push_back("SOURCES");
1279
0
    items = parsedArgsMode.Sources;
1280
0
    kind = EntityKind::Source;
1281
0
  }
1282
0
  if (!parsedArgsMode.Tests.empty()) {
1283
0
    modes.push_back("TESTS");
1284
0
    items = parsedArgsMode.Tests;
1285
0
    kind = EntityKind::Test;
1286
0
  }
1287
0
  if (!parsedArgsMode.Directories.empty()) {
1288
0
    modes.push_back("DIRECTORIES");
1289
0
    items = parsedArgsMode.Directories;
1290
0
    kind = EntityKind::Directory;
1291
0
  }
1292
0
  if (!parsedArgsMode.CacheEntries.empty()) {
1293
0
    modes.push_back("CACHE_ENTRIES");
1294
0
    items = parsedArgsMode.CacheEntries;
1295
0
    kind = EntityKind::Cache;
1296
0
  }
1297
1298
0
  if (modes.empty()) {
1299
0
    return FatalError(status,
1300
0
                      cmStrCat("mode keyword missing in ",
1301
0
                               "cmake_language(PRINT_PROPERTIES) call, ",
1302
0
                               "there must be exactly one of TARGETS SOURCES "
1303
0
                               "TESTS DIRECTORIES CACHE_ENTRIES"));
1304
0
  }
1305
0
  if (modes.size() > 1) {
1306
0
    return FatalError(status,
1307
0
                      cmStrCat("multiple mode keywords used in ",
1308
0
                               "cmake_language(PRINT_PROPERTIES) call, ",
1309
0
                               "there must be exactly one of TARGETS SOURCES "
1310
0
                               "TESTS DIRECTORIES CACHE_ENTRIES."));
1311
0
  }
1312
0
  std::string const mode = modes[0];
1313
0
  bool const isTargets = (mode == "TARGETS");
1314
0
  bool const hasNamed = !parsedArgs.Named.empty();
1315
0
  bool const hasRegex =
1316
0
    parsedArgs.PropertyNameRegex || parsedArgs.PropertyValueRegex;
1317
1318
0
  if (!isTargets) {
1319
0
    if (parsedArgs.All) {
1320
0
      return FatalError(status,
1321
0
                        cmStrCat("ALL keyword in ",
1322
0
                                 "cmake_language(PRINT_PROPERTIES) call ",
1323
0
                                 "is only valid with the TARGETS scope."));
1324
0
    }
1325
0
    if (hasRegex) {
1326
0
      return FatalError(
1327
0
        status,
1328
0
        cmStrCat("PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in ",
1329
0
                 "cmake_language(PRINT_PROPERTIES) call ",
1330
0
                 "are only valid with the TARGETS scope and ALL."));
1331
0
    }
1332
0
    if (parsedArgsMode.Deferred) {
1333
0
      return FatalError(status,
1334
0
                        cmStrCat("DEFERRED keyword in ",
1335
0
                                 "cmake_language(PRINT_PROPERTIES) call ",
1336
0
                                 "is only valid with the TARGETS scope."));
1337
0
    }
1338
0
    if (parsedArgsMode.FollowDependencies) {
1339
0
      return FatalError(status,
1340
0
                        cmStrCat("FOLLOW_DEPENDENCIES keyword in ",
1341
0
                                 "cmake_language(PRINT_PROPERTIES) call ",
1342
0
                                 "is only valid with the TARGETS scope."));
1343
0
    }
1344
0
    if (!hasNamed) {
1345
0
      return FatalError(status,
1346
0
                        cmStrCat("NAMED keyword missing in ",
1347
0
                                 "cmake_language(PRINT_PROPERTIES) call ",
1348
0
                                 "with ", mode, " scope."));
1349
0
    }
1350
0
  } else {
1351
    // ALL and NAMED are mutually exclusive on TARGETS.
1352
0
    if (parsedArgs.All && hasNamed) {
1353
0
      return FatalError(status,
1354
0
                        cmStrCat("ALL and NAMED keywords in ",
1355
0
                                 "cmake_language(PRINT_PROPERTIES) call ",
1356
0
                                 "are mutually exclusive."));
1357
0
    }
1358
    // Regex filters require ALL - explicit or implicit.  Combining regex
1359
    // with NAMED is an error.
1360
0
    if (hasNamed && hasRegex) {
1361
0
      return FatalError(
1362
0
        status,
1363
0
        cmStrCat("PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in ",
1364
0
                 "cmake_language(PRINT_PROPERTIES) call ",
1365
0
                 "are only valid with ALL, not NAMED."));
1366
0
    }
1367
0
  }
1368
1369
  // FOLLOW_DEPENDENCIES implies DEFERRED.
1370
0
  if (parsedArgsMode.FollowDependencies) {
1371
0
    parsedArgsMode.Deferred = true;
1372
0
  }
1373
1374
  // Default to ALL when neither ALL nor NAMED is given (TARGETS only - the
1375
  // non-TARGETS path already rejected this combination above).
1376
0
  bool const all = parsedArgs.All || !hasNamed;
1377
1378
  // __CMAKE_PRINT_PROPERTIES marks the deprecated cmake_print_properties()
1379
  // wrapper, which is always NAMED; reject it in ALL enumeration mode.
1380
0
  if (all && parsedArgs.CmakePrintProperties) {
1381
0
    return FatalError(status,
1382
0
                      cmStrCat("__CMAKE_PRINT_PROPERTIES in ",
1383
0
                               "cmake_language(PRINT_PROPERTIES) call ",
1384
0
                               "is only valid with NAMED."));
1385
0
  }
1386
1387
  // Compile regexes once up front so syntax errors are reported here rather
1388
  // than from inside helpers (in particular, from the deferred lambda at
1389
  // generate time).
1390
0
  cm::optional<cmsys::RegularExpression> propertyNameRegex;
1391
0
  cm::optional<cmsys::RegularExpression> propertyValueRegex;
1392
0
  if (parsedArgs.PropertyNameRegex) {
1393
0
    cmsys::RegularExpression re;
1394
0
    if (!re.compile(*parsedArgs.PropertyNameRegex)) {
1395
0
      return FatalError(status,
1396
0
                        cmStrCat("PROPERTY_NAME_REGEX regular expression \"",
1397
0
                                 *parsedArgs.PropertyNameRegex,
1398
0
                                 "\" cannot compile."));
1399
0
    }
1400
0
    propertyNameRegex = std::move(re);
1401
0
  }
1402
0
  if (parsedArgs.PropertyValueRegex) {
1403
0
    cmsys::RegularExpression re;
1404
0
    if (!re.compile(*parsedArgs.PropertyValueRegex)) {
1405
0
      return FatalError(status,
1406
0
                        cmStrCat("PROPERTY_VALUE_REGEX regular expression \"",
1407
0
                                 *parsedArgs.PropertyValueRegex,
1408
0
                                 "\" cannot compile."));
1409
0
    }
1410
0
    propertyValueRegex = std::move(re);
1411
0
  }
1412
1413
  // The message opens with a leading banner line.  The internal
1414
  // __CMAKE_PRINT_PROPERTIES marker (not part of the public interface) selects
1415
  // the legacy format instead; cmake_print_properties() sets it to reproduce
1416
  // its historical leading blank line.
1417
0
  std::string const messagePrefix = parsedArgs.CmakePrintProperties
1418
0
    ? std::string("\n")
1419
0
    : std::string("Printing properties...\n");
1420
1421
0
  if (parsedArgsMode.Deferred) {
1422
0
    return PrintTargetPropertiesDeferred(
1423
0
      items, parsedArgs.Named, all, parsedArgs.PropertyNameRegex,
1424
0
      parsedArgs.PropertyValueRegex, std::move(propertyNameRegex),
1425
0
      std::move(propertyValueRegex), parsedArgsMode.FollowDependencies,
1426
0
      messagePrefix, status);
1427
0
  }
1428
1429
0
  return PrintPropertiesConfigureTime(
1430
0
    status, parsedArgs.Named, all, items, kind, parsedArgs.PropertyNameRegex,
1431
0
    parsedArgs.PropertyValueRegex, propertyNameRegex, propertyValueRegex,
1432
0
    messagePrefix);
1433
0
}
1434
}
1435
bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
1436
                            cmExecutionStatus& status)
1437
0
{
1438
0
  std::vector<std::string> expArgs;
1439
0
  size_t rawArg = 0;
1440
0
  size_t expArg = 0;
1441
1442
  // Helper to consume and expand one raw argument at a time.
1443
0
  auto moreArgs = [&]() -> bool {
1444
0
    while (expArg >= expArgs.size()) {
1445
0
      if (rawArg >= args.size()) {
1446
0
        return false;
1447
0
      }
1448
0
      std::vector<cmListFileArgument> tmpArg;
1449
0
      tmpArg.emplace_back(args[rawArg++]);
1450
0
      status.GetMakefile().ExpandArguments(tmpArg, expArgs);
1451
0
    }
1452
0
    return true;
1453
0
  };
1454
0
  auto finishArgs = [&]() {
1455
0
    std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
1456
0
    status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
1457
0
    rawArg = args.size();
1458
0
  };
1459
1460
0
  if (!moreArgs()) {
1461
0
    return FatalError(status, "called with incorrect number of arguments");
1462
0
  }
1463
0
  if (expArgs[expArg] == "EXIT"_s) {
1464
0
    ++expArg; // consume "EXIT".
1465
1466
0
    if (!moreArgs()) {
1467
0
      return FatalError(status, "EXIT requires one argument");
1468
0
    }
1469
1470
0
    if (!status.GetMakefile().GetCMakeInstance()->RoleSupportsExitCode()) {
1471
0
      return FatalError(status, "EXIT can be used only in SCRIPT mode");
1472
0
    }
1473
1474
0
    long retCode = 0;
1475
1476
0
    if (!cmStrToLong(expArgs[expArg], &retCode)) {
1477
0
      return FatalError(status,
1478
0
                        cmStrCat("EXIT requires one integral argument, got \"",
1479
0
                                 expArgs[expArg], '\"'));
1480
0
    }
1481
1482
0
    status.SetExitCode(static_cast<int>(retCode));
1483
0
    return true;
1484
0
  }
1485
1486
0
  if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) {
1487
0
    finishArgs();
1488
0
    return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status);
1489
0
  }
1490
1491
0
  cm::optional<Defer> maybeDefer;
1492
0
  if (expArgs[expArg] == "DEFER"_s) {
1493
0
    ++expArg; // Consume "DEFER".
1494
1495
0
    if (!moreArgs()) {
1496
0
      return FatalError(status, "DEFER requires at least one argument");
1497
0
    }
1498
1499
0
    Defer defer;
1500
1501
    // Process optional arguments.
1502
0
    while (moreArgs()) {
1503
0
      if (expArgs[expArg] == "CALL"_s) {
1504
0
        break;
1505
0
      }
1506
0
      if (expArgs[expArg] == "CANCEL_CALL"_s ||
1507
0
          expArgs[expArg] == "GET_CALL_IDS"_s ||
1508
0
          expArgs[expArg] == "GET_CALL"_s) {
1509
0
        if (!defer.Id.empty() || !defer.IdVar.empty()) {
1510
0
          return FatalError(status,
1511
0
                            cmStrCat("DEFER "_s, expArgs[expArg],
1512
0
                                     " does not accept ID or ID_VAR."_s));
1513
0
        }
1514
0
        finishArgs();
1515
0
        return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
1516
0
      }
1517
0
      if (expArgs[expArg] == "DIRECTORY"_s) {
1518
0
        ++expArg; // Consume "DIRECTORY".
1519
0
        if (defer.Directory) {
1520
0
          return FatalError(status,
1521
0
                            "DEFER given multiple DIRECTORY arguments");
1522
0
        }
1523
0
        if (!moreArgs()) {
1524
0
          return FatalError(status, "DEFER DIRECTORY missing value");
1525
0
        }
1526
0
        std::string dir = expArgs[expArg++];
1527
0
        if (dir.empty()) {
1528
0
          return FatalError(status, "DEFER DIRECTORY may not be empty");
1529
0
        }
1530
0
        dir = cmSystemTools::CollapseFullPath(
1531
0
          dir, status.GetMakefile().GetCurrentSourceDirectory());
1532
0
        defer.Directory =
1533
0
          status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
1534
0
        if (!defer.Directory) {
1535
0
          return FatalError(status,
1536
0
                            cmStrCat("DEFER DIRECTORY:\n  "_s, dir,
1537
0
                                     "\nis not known.  "
1538
0
                                     "It may not have been processed yet."_s));
1539
0
        }
1540
0
      } else if (expArgs[expArg] == "ID"_s) {
1541
0
        ++expArg; // Consume "ID".
1542
0
        if (!defer.Id.empty()) {
1543
0
          return FatalError(status, "DEFER given multiple ID arguments");
1544
0
        }
1545
0
        if (!moreArgs()) {
1546
0
          return FatalError(status, "DEFER ID missing value");
1547
0
        }
1548
0
        defer.Id = expArgs[expArg++];
1549
0
        if (defer.Id.empty()) {
1550
0
          return FatalError(status, "DEFER ID may not be empty");
1551
0
        }
1552
0
        if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
1553
0
          return FatalError(status, "DEFER ID may not start in A-Z.");
1554
0
        }
1555
0
      } else if (expArgs[expArg] == "ID_VAR"_s) {
1556
0
        ++expArg; // Consume "ID_VAR".
1557
0
        if (!defer.IdVar.empty()) {
1558
0
          return FatalError(status, "DEFER given multiple ID_VAR arguments");
1559
0
        }
1560
0
        if (!moreArgs()) {
1561
0
          return FatalError(status, "DEFER ID_VAR missing variable name");
1562
0
        }
1563
0
        defer.IdVar = expArgs[expArg++];
1564
0
        if (defer.IdVar.empty()) {
1565
0
          return FatalError(status, "DEFER ID_VAR may not be empty");
1566
0
        }
1567
0
      } else {
1568
0
        return FatalError(
1569
0
          status, cmStrCat("DEFER unknown option:\n  "_s, expArgs[expArg]));
1570
0
      }
1571
0
    }
1572
1573
0
    if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
1574
0
      return FatalError(status, "DEFER must be followed by a CALL argument");
1575
0
    }
1576
1577
0
    maybeDefer = std::move(defer);
1578
0
  }
1579
1580
0
  if (expArgs[expArg] == "CALL") {
1581
0
    ++expArg; // Consume "CALL".
1582
1583
    // CALL requires a command name.
1584
0
    if (!moreArgs()) {
1585
0
      return FatalError(status, "CALL missing command name");
1586
0
    }
1587
0
    std::string const& callCommand = expArgs[expArg++];
1588
1589
    // CALL accepts no further expanded arguments.
1590
0
    if (expArg != expArgs.size()) {
1591
0
      return FatalError(status, "CALL command's arguments must be literal");
1592
0
    }
1593
1594
    // Run the CALL.
1595
0
    return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
1596
0
                                      std::move(maybeDefer), status);
1597
0
  }
1598
1599
0
  if (expArgs[expArg] == "EVAL") {
1600
0
    return cmCMakeLanguageCommandEVAL(args, status);
1601
0
  }
1602
1603
0
  if (expArgs[expArg] == "GET_MESSAGE_LOG_LEVEL") {
1604
0
    return cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(args, status);
1605
0
  }
1606
1607
0
  if (expArgs[expArg] == "GET_EXPERIMENTAL_FEATURE_ENABLED") {
1608
0
    return cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(args,
1609
0
                                                                  status);
1610
0
  }
1611
1612
0
  if (expArgs[expArg] == "PRINT_TARGETS") {
1613
0
    return cmCMakeLanguageCommandPRINT_TARGETS(args, status);
1614
0
  }
1615
1616
0
  if (expArgs[expArg] == "PRINT_VARIABLES") {
1617
0
    return cmCMakeLanguageCommandPRINT_VARIABLES(args, status);
1618
0
  }
1619
1620
0
  if (expArgs[expArg] == "TRACE") {
1621
0
    ++expArg; // Consume "TRACE".
1622
1623
0
    if (!moreArgs()) {
1624
0
      return FatalError(status, "TRACE missing a boolean value");
1625
0
    }
1626
1627
0
    bool const value = cmValue::IsOn(expArgs[expArg++]);
1628
0
    bool expand = false;
1629
1630
0
    if (value && moreArgs()) {
1631
0
      expand = (expArgs[expArg] == "EXPAND");
1632
0
      if (!expand) {
1633
0
        return FatalError(
1634
0
          status,
1635
0
          cmStrCat("TRACE ON given an invalid argument ", expArgs[expArg]));
1636
0
      }
1637
0
      ++expArg;
1638
0
    }
1639
1640
0
    if (moreArgs()) {
1641
0
      return FatalError(
1642
0
        status,
1643
0
        cmStrCat("TRACE O", value ? "N" : "FF", " given too many arguments"));
1644
0
    }
1645
1646
0
    cmMakefile& makefile = status.GetMakefile();
1647
0
    if (value) {
1648
0
      makefile.GetCMakeInstance()->PushTraceCmd(expand);
1649
0
      return true;
1650
0
    }
1651
0
    return makefile.GetCMakeInstance()->PopTraceCmd() ||
1652
0
      FatalError(status, "TRACE OFF request without a corresponding TRACE ON");
1653
0
  }
1654
1655
0
  if (expArgs[expArg] == "PRINT_PROPERTIES") {
1656
0
    ++expArg;
1657
0
    finishArgs();
1658
0
    std::vector<std::string> const printPropertyArgs(expArgs.begin() + expArg,
1659
0
                                                     expArgs.end());
1660
0
    return cmCMakeLanguageCommandPRINT_PROPERTIES(printPropertyArgs, status);
1661
0
  }
1662
1663
0
  return FatalError(status, "called with unknown meta-operation");
1664
0
}