/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 <string> |
9 | | #include <utility> |
10 | | |
11 | | #include <cm/optional> |
12 | | #include <cm/string_view> |
13 | | #include <cmext/string_view> |
14 | | |
15 | | #include "cmArgumentParser.h" |
16 | | #include "cmArgumentParserTypes.h" |
17 | | #include "cmDependencyProvider.h" |
18 | | #include "cmExecutionStatus.h" |
19 | | #include "cmExperimental.h" |
20 | | #include "cmGlobalGenerator.h" |
21 | | #include "cmListFileCache.h" |
22 | | #include "cmMakefile.h" |
23 | | #include "cmMessageType.h" // IWYU pragma: keep |
24 | | #include "cmRange.h" |
25 | | #include "cmState.h" |
26 | | #include "cmStringAlgorithms.h" |
27 | | #include "cmSystemTools.h" |
28 | | #include "cmValue.h" |
29 | | #include "cmake.h" |
30 | | |
31 | | namespace { |
32 | | |
33 | | bool FatalError(cmExecutionStatus& status, std::string const& error) |
34 | 0 | { |
35 | 0 | status.SetError(error); |
36 | 0 | cmSystemTools::SetFatalErrorOccurred(); |
37 | 0 | return false; |
38 | 0 | } |
39 | | |
40 | | std::array<cm::static_string_view, 14> InvalidCommands{ |
41 | | { // clang-format off |
42 | | "function"_s, "endfunction"_s, |
43 | | "macro"_s, "endmacro"_s, |
44 | | "if"_s, "elseif"_s, "else"_s, "endif"_s, |
45 | | "while"_s, "endwhile"_s, |
46 | | "foreach"_s, "endforeach"_s, |
47 | | "block"_s, "endblock"_s |
48 | | } // clang-format on |
49 | | }; |
50 | | |
51 | | std::array<cm::static_string_view, 1> InvalidDeferCommands{ |
52 | | { |
53 | | // clang-format off |
54 | | "return"_s, |
55 | | } // clang-format on |
56 | | }; |
57 | | |
58 | | struct Defer |
59 | | { |
60 | | std::string Id; |
61 | | std::string IdVar; |
62 | | cmMakefile* Directory = nullptr; |
63 | | }; |
64 | | |
65 | | bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args, |
66 | | std::string const& callCommand, |
67 | | size_t startArg, cm::optional<Defer> defer, |
68 | | cmExecutionStatus& status) |
69 | 0 | { |
70 | | // ensure specified command is valid |
71 | | // start/end flow control commands are not allowed |
72 | 0 | auto cmd = cmSystemTools::LowerCase(callCommand); |
73 | 0 | if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) != |
74 | 0 | InvalidCommands.cend()) { |
75 | 0 | return FatalError(status, |
76 | 0 | cmStrCat("invalid command specified: "_s, callCommand)); |
77 | 0 | } |
78 | 0 | if (defer && |
79 | 0 | std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(), |
80 | 0 | cmd) != InvalidDeferCommands.cend()) { |
81 | 0 | return FatalError(status, |
82 | 0 | cmStrCat("invalid command specified: "_s, callCommand)); |
83 | 0 | } |
84 | | |
85 | 0 | cmMakefile& makefile = status.GetMakefile(); |
86 | 0 | cmListFileContext context = makefile.GetBacktrace().Top(); |
87 | |
|
88 | 0 | std::vector<cmListFileArgument> funcArgs; |
89 | 0 | funcArgs.reserve(args.size() - startArg); |
90 | | |
91 | | // The rest of the arguments are passed to the function call above |
92 | 0 | for (size_t i = startArg; i < args.size(); ++i) { |
93 | 0 | funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line); |
94 | 0 | } |
95 | 0 | cmListFileFunction func{ callCommand, context.Line, context.Line, |
96 | 0 | std::move(funcArgs) }; |
97 | |
|
98 | 0 | if (defer) { |
99 | 0 | if (defer->Id.empty()) { |
100 | 0 | defer->Id = makefile.NewDeferId(); |
101 | 0 | } |
102 | 0 | if (!defer->IdVar.empty()) { |
103 | 0 | makefile.AddDefinition(defer->IdVar, defer->Id); |
104 | 0 | } |
105 | 0 | cmMakefile* deferMakefile = |
106 | 0 | defer->Directory ? defer->Directory : &makefile; |
107 | 0 | if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) { |
108 | 0 | return FatalError( |
109 | 0 | status, |
110 | 0 | cmStrCat("DEFER CALL may not be scheduled in directory:\n "_s, |
111 | 0 | deferMakefile->GetCurrentBinaryDirectory(), |
112 | 0 | "\nat this time."_s)); |
113 | 0 | } |
114 | 0 | return true; |
115 | 0 | } |
116 | 0 | return makefile.ExecuteCommand(func, status); |
117 | 0 | } |
118 | | |
119 | | bool cmCMakeLanguageCommandDEFER(Defer const& defer, |
120 | | std::vector<std::string> const& args, |
121 | | size_t arg, cmExecutionStatus& status) |
122 | 0 | { |
123 | 0 | cmMakefile* deferMakefile = |
124 | 0 | defer.Directory ? defer.Directory : &status.GetMakefile(); |
125 | 0 | if (args[arg] == "CANCEL_CALL"_s) { |
126 | 0 | ++arg; // Consume CANCEL_CALL. |
127 | 0 | auto ids = cmMakeRange(args).advance(arg); |
128 | 0 | for (std::string const& id : ids) { |
129 | 0 | if (id[0] >= 'A' && id[0] <= 'Z') { |
130 | 0 | return FatalError( |
131 | 0 | status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n "_s, id)); |
132 | 0 | } |
133 | 0 | if (!deferMakefile->DeferCancelCall(id)) { |
134 | 0 | return FatalError( |
135 | 0 | status, |
136 | 0 | cmStrCat("DEFER CANCEL_CALL may not update directory:\n "_s, |
137 | 0 | deferMakefile->GetCurrentBinaryDirectory(), |
138 | 0 | "\nat this time."_s)); |
139 | 0 | } |
140 | 0 | } |
141 | 0 | return true; |
142 | 0 | } |
143 | 0 | if (args[arg] == "GET_CALL_IDS"_s) { |
144 | 0 | ++arg; // Consume GET_CALL_IDS. |
145 | 0 | if (arg == args.size()) { |
146 | 0 | return FatalError(status, "DEFER GET_CALL_IDS missing output variable"); |
147 | 0 | } |
148 | 0 | std::string const& var = args[arg++]; |
149 | 0 | if (arg != args.size()) { |
150 | 0 | return FatalError(status, "DEFER GET_CALL_IDS given too many arguments"); |
151 | 0 | } |
152 | 0 | cm::optional<std::string> ids = deferMakefile->DeferGetCallIds(); |
153 | 0 | if (!ids) { |
154 | 0 | return FatalError( |
155 | 0 | status, |
156 | 0 | cmStrCat("DEFER GET_CALL_IDS may not access directory:\n "_s, |
157 | 0 | deferMakefile->GetCurrentBinaryDirectory(), |
158 | 0 | "\nat this time."_s)); |
159 | 0 | } |
160 | 0 | status.GetMakefile().AddDefinition(var, *ids); |
161 | 0 | return true; |
162 | 0 | } |
163 | 0 | if (args[arg] == "GET_CALL"_s) { |
164 | 0 | ++arg; // Consume GET_CALL. |
165 | 0 | if (arg == args.size()) { |
166 | 0 | return FatalError(status, "DEFER GET_CALL missing id"); |
167 | 0 | } |
168 | 0 | std::string const& id = args[arg++]; |
169 | 0 | if (arg == args.size()) { |
170 | 0 | return FatalError(status, "DEFER GET_CALL missing output variable"); |
171 | 0 | } |
172 | 0 | std::string const& var = args[arg++]; |
173 | 0 | if (arg != args.size()) { |
174 | 0 | return FatalError(status, "DEFER GET_CALL given too many arguments"); |
175 | 0 | } |
176 | 0 | if (id.empty()) { |
177 | 0 | return FatalError(status, "DEFER GET_CALL id may not be empty"); |
178 | 0 | } |
179 | 0 | if (id[0] >= 'A' && id[0] <= 'Z') { |
180 | 0 | return FatalError(status, |
181 | 0 | cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id)); |
182 | 0 | } |
183 | 0 | cm::optional<std::string> call = deferMakefile->DeferGetCall(id); |
184 | 0 | if (!call) { |
185 | 0 | return FatalError( |
186 | 0 | status, |
187 | 0 | cmStrCat("DEFER GET_CALL may not access directory:\n "_s, |
188 | 0 | deferMakefile->GetCurrentBinaryDirectory(), |
189 | 0 | "\nat this time."_s)); |
190 | 0 | } |
191 | 0 | status.GetMakefile().AddDefinition(var, *call); |
192 | 0 | return true; |
193 | 0 | } |
194 | 0 | return FatalError(status, |
195 | 0 | cmStrCat("DEFER operation unknown: "_s, args[arg])); |
196 | 0 | } |
197 | | |
198 | | bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args, |
199 | | cmExecutionStatus& status) |
200 | 0 | { |
201 | 0 | cmMakefile& makefile = status.GetMakefile(); |
202 | 0 | cmListFileContext context = makefile.GetBacktrace().Top(); |
203 | 0 | std::vector<std::string> expandedArgs; |
204 | 0 | makefile.ExpandArguments(args, expandedArgs); |
205 | |
|
206 | 0 | if (expandedArgs.size() < 2) { |
207 | 0 | return FatalError(status, "called with incorrect number of arguments"); |
208 | 0 | } |
209 | | |
210 | 0 | if (expandedArgs[1] != "CODE") { |
211 | 0 | auto code_iter = |
212 | 0 | std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE"); |
213 | 0 | if (code_iter == expandedArgs.end()) { |
214 | 0 | return FatalError(status, "called without CODE argument"); |
215 | 0 | } |
216 | 0 | return FatalError( |
217 | 0 | status, |
218 | 0 | "called with unsupported arguments between EVAL and CODE arguments"); |
219 | 0 | } |
220 | | |
221 | 0 | std::string const code = |
222 | 0 | cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " "); |
223 | 0 | return makefile.ReadListFileAsString( |
224 | 0 | code, cmStrCat(context.FilePath, ':', context.Line, ":EVAL")); |
225 | 0 | } |
226 | | |
227 | | bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER( |
228 | | std::vector<std::string> const& args, cmExecutionStatus& status) |
229 | 0 | { |
230 | 0 | cmState* state = status.GetMakefile().GetState(); |
231 | 0 | if (!state->InTopLevelIncludes()) { |
232 | 0 | return FatalError( |
233 | 0 | status, |
234 | 0 | "Dependency providers can only be set as part of the first call to " |
235 | 0 | "project(). More specifically, cmake_language(SET_DEPENDENCY_PROVIDER) " |
236 | 0 | "can only be called while the first project() command processes files " |
237 | 0 | "listed in CMAKE_PROJECT_TOP_LEVEL_INCLUDES."); |
238 | 0 | } |
239 | | |
240 | 0 | struct SetProviderArgs |
241 | 0 | { |
242 | 0 | std::string Command; |
243 | 0 | ArgumentParser::NonEmpty<std::vector<std::string>> Methods; |
244 | 0 | }; |
245 | |
|
246 | 0 | auto const ArgsParser = |
247 | 0 | cmArgumentParser<SetProviderArgs>() |
248 | 0 | .Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command) |
249 | 0 | .Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods); |
250 | |
|
251 | 0 | std::vector<std::string> unparsed; |
252 | 0 | auto parsedArgs = ArgsParser.Parse(args, &unparsed); |
253 | |
|
254 | 0 | if (!unparsed.empty()) { |
255 | 0 | return FatalError( |
256 | 0 | status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), '"')); |
257 | 0 | } |
258 | | |
259 | | // We store the command that FetchContent_MakeAvailable() can call in a |
260 | | // global (but considered internal) property. If the provider doesn't |
261 | | // support this method, we set this property to an empty string instead. |
262 | | // This simplifies the logic in FetchContent_MakeAvailable() and doesn't |
263 | | // require us to define a new internal command or sub-command. |
264 | 0 | std::string fcmasProperty = "__FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER"; |
265 | |
|
266 | 0 | if (parsedArgs.Command.empty()) { |
267 | 0 | if (!parsedArgs.Methods.empty()) { |
268 | 0 | return FatalError(status, |
269 | 0 | "Must specify a non-empty command name when provider " |
270 | 0 | "methods are given"); |
271 | 0 | } |
272 | 0 | state->ClearDependencyProvider(); |
273 | 0 | state->SetGlobalProperty(fcmasProperty, ""); |
274 | 0 | return true; |
275 | 0 | } |
276 | | |
277 | 0 | cmState::Command command = state->GetCommand(parsedArgs.Command); |
278 | 0 | if (!command) { |
279 | 0 | return FatalError(status, |
280 | 0 | cmStrCat("Command \"", parsedArgs.Command, |
281 | 0 | "\" is not a defined command")); |
282 | 0 | } |
283 | | |
284 | 0 | if (parsedArgs.Methods.empty()) { |
285 | 0 | return FatalError(status, "Must specify at least one provider method"); |
286 | 0 | } |
287 | | |
288 | 0 | bool supportsFetchContentMakeAvailableSerial = false; |
289 | 0 | std::vector<cmDependencyProvider::Method> methods; |
290 | 0 | for (auto const& method : parsedArgs.Methods) { |
291 | 0 | if (method == "FIND_PACKAGE") { |
292 | 0 | methods.emplace_back(cmDependencyProvider::Method::FindPackage); |
293 | 0 | } else if (method == "FETCHCONTENT_MAKEAVAILABLE_SERIAL") { |
294 | 0 | supportsFetchContentMakeAvailableSerial = true; |
295 | 0 | methods.emplace_back( |
296 | 0 | cmDependencyProvider::Method::FetchContentMakeAvailableSerial); |
297 | 0 | } else { |
298 | 0 | return FatalError( |
299 | 0 | status, |
300 | 0 | cmStrCat("Unknown dependency provider method \"", method, '"')); |
301 | 0 | } |
302 | 0 | } |
303 | | |
304 | 0 | state->SetDependencyProvider({ parsedArgs.Command, methods }); |
305 | 0 | state->SetGlobalProperty( |
306 | 0 | fcmasProperty, |
307 | 0 | supportsFetchContentMakeAvailableSerial ? parsedArgs.Command : ""); |
308 | |
|
309 | 0 | return true; |
310 | 0 | } |
311 | | |
312 | | bool cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL( |
313 | | std::vector<cmListFileArgument> const& args, cmExecutionStatus& status) |
314 | 0 | { |
315 | 0 | cmMakefile& makefile = status.GetMakefile(); |
316 | 0 | std::vector<std::string> expandedArgs; |
317 | 0 | makefile.ExpandArguments(args, expandedArgs); |
318 | |
|
319 | 0 | if (args.size() < 2 || expandedArgs.size() > 2) { |
320 | 0 | return FatalError( |
321 | 0 | status, |
322 | 0 | "sub-command GET_MESSAGE_LOG_LEVEL expects exactly one argument"); |
323 | 0 | } |
324 | | |
325 | 0 | Message::LogLevel logLevel = makefile.GetCurrentLogLevel(); |
326 | 0 | std::string outputValue = cmake::LogLevelToString(logLevel); |
327 | |
|
328 | 0 | std::string const& outputVariable = expandedArgs[1]; |
329 | 0 | makefile.AddDefinition(outputVariable, outputValue); |
330 | 0 | return true; |
331 | 0 | } |
332 | | |
333 | | bool cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED( |
334 | | std::vector<cmListFileArgument> const& args, cmExecutionStatus& status) |
335 | 0 | { |
336 | 0 | cmMakefile& makefile = status.GetMakefile(); |
337 | 0 | std::vector<std::string> expandedArgs; |
338 | 0 | makefile.ExpandArguments(args, expandedArgs); |
339 | |
|
340 | 0 | if (expandedArgs.size() != 3) { |
341 | 0 | return FatalError(status, |
342 | 0 | "sub-command GET_EXPERIMENTAL_FEATURE_ENABLED expects " |
343 | 0 | "exactly two arguments"); |
344 | 0 | } |
345 | | |
346 | 0 | auto const& featureName = expandedArgs[1]; |
347 | 0 | auto const& variableName = expandedArgs[2]; |
348 | |
|
349 | 0 | if (auto feature = cmExperimental::FeatureByName(featureName)) { |
350 | 0 | if (cmExperimental::HasSupportEnabled(makefile, *feature)) { |
351 | 0 | makefile.AddDefinition(variableName, "TRUE"); |
352 | 0 | } else { |
353 | 0 | makefile.AddDefinition(variableName, "FALSE"); |
354 | 0 | } |
355 | 0 | } else { |
356 | 0 | return FatalError(status, |
357 | 0 | cmStrCat("Experimental feature name \"", featureName, |
358 | 0 | "\" does not exist.")); |
359 | 0 | } |
360 | | |
361 | 0 | return true; |
362 | 0 | } |
363 | | } |
364 | | |
365 | | bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args, |
366 | | cmExecutionStatus& status) |
367 | 0 | { |
368 | 0 | std::vector<std::string> expArgs; |
369 | 0 | size_t rawArg = 0; |
370 | 0 | size_t expArg = 0; |
371 | | |
372 | | // Helper to consume and expand one raw argument at a time. |
373 | 0 | auto moreArgs = [&]() -> bool { |
374 | 0 | while (expArg >= expArgs.size()) { |
375 | 0 | if (rawArg >= args.size()) { |
376 | 0 | return false; |
377 | 0 | } |
378 | 0 | std::vector<cmListFileArgument> tmpArg; |
379 | 0 | tmpArg.emplace_back(args[rawArg++]); |
380 | 0 | status.GetMakefile().ExpandArguments(tmpArg, expArgs); |
381 | 0 | } |
382 | 0 | return true; |
383 | 0 | }; |
384 | 0 | auto finishArgs = [&]() { |
385 | 0 | std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end()); |
386 | 0 | status.GetMakefile().ExpandArguments(tmpArgs, expArgs); |
387 | 0 | rawArg = args.size(); |
388 | 0 | }; |
389 | |
|
390 | 0 | if (!moreArgs()) { |
391 | 0 | return FatalError(status, "called with incorrect number of arguments"); |
392 | 0 | } |
393 | 0 | if (expArgs[expArg] == "EXIT"_s) { |
394 | 0 | ++expArg; // consume "EXIT". |
395 | |
|
396 | 0 | if (!moreArgs()) { |
397 | 0 | return FatalError(status, "EXIT requires one argument"); |
398 | 0 | } |
399 | | |
400 | 0 | if (!status.GetMakefile().GetCMakeInstance()->RoleSupportsExitCode()) { |
401 | 0 | return FatalError(status, "EXIT can be used only in SCRIPT mode"); |
402 | 0 | } |
403 | | |
404 | 0 | long retCode = 0; |
405 | |
|
406 | 0 | if (!cmStrToLong(expArgs[expArg], &retCode)) { |
407 | 0 | return FatalError(status, |
408 | 0 | cmStrCat("EXIT requires one integral argument, got \"", |
409 | 0 | expArgs[expArg], '\"')); |
410 | 0 | } |
411 | | |
412 | 0 | status.SetExitCode(static_cast<int>(retCode)); |
413 | 0 | return true; |
414 | 0 | } |
415 | | |
416 | 0 | if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) { |
417 | 0 | finishArgs(); |
418 | 0 | return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status); |
419 | 0 | } |
420 | | |
421 | 0 | cm::optional<Defer> maybeDefer; |
422 | 0 | if (expArgs[expArg] == "DEFER"_s) { |
423 | 0 | ++expArg; // Consume "DEFER". |
424 | |
|
425 | 0 | if (!moreArgs()) { |
426 | 0 | return FatalError(status, "DEFER requires at least one argument"); |
427 | 0 | } |
428 | | |
429 | 0 | Defer defer; |
430 | | |
431 | | // Process optional arguments. |
432 | 0 | while (moreArgs()) { |
433 | 0 | if (expArgs[expArg] == "CALL"_s) { |
434 | 0 | break; |
435 | 0 | } |
436 | 0 | if (expArgs[expArg] == "CANCEL_CALL"_s || |
437 | 0 | expArgs[expArg] == "GET_CALL_IDS"_s || |
438 | 0 | expArgs[expArg] == "GET_CALL"_s) { |
439 | 0 | if (!defer.Id.empty() || !defer.IdVar.empty()) { |
440 | 0 | return FatalError(status, |
441 | 0 | cmStrCat("DEFER "_s, expArgs[expArg], |
442 | 0 | " does not accept ID or ID_VAR."_s)); |
443 | 0 | } |
444 | 0 | finishArgs(); |
445 | 0 | return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status); |
446 | 0 | } |
447 | 0 | if (expArgs[expArg] == "DIRECTORY"_s) { |
448 | 0 | ++expArg; // Consume "DIRECTORY". |
449 | 0 | if (defer.Directory) { |
450 | 0 | return FatalError(status, |
451 | 0 | "DEFER given multiple DIRECTORY arguments"); |
452 | 0 | } |
453 | 0 | if (!moreArgs()) { |
454 | 0 | return FatalError(status, "DEFER DIRECTORY missing value"); |
455 | 0 | } |
456 | 0 | std::string dir = expArgs[expArg++]; |
457 | 0 | if (dir.empty()) { |
458 | 0 | return FatalError(status, "DEFER DIRECTORY may not be empty"); |
459 | 0 | } |
460 | 0 | dir = cmSystemTools::CollapseFullPath( |
461 | 0 | dir, status.GetMakefile().GetCurrentSourceDirectory()); |
462 | 0 | defer.Directory = |
463 | 0 | status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir); |
464 | 0 | if (!defer.Directory) { |
465 | 0 | return FatalError(status, |
466 | 0 | cmStrCat("DEFER DIRECTORY:\n "_s, dir, |
467 | 0 | "\nis not known. " |
468 | 0 | "It may not have been processed yet."_s)); |
469 | 0 | } |
470 | 0 | } else if (expArgs[expArg] == "ID"_s) { |
471 | 0 | ++expArg; // Consume "ID". |
472 | 0 | if (!defer.Id.empty()) { |
473 | 0 | return FatalError(status, "DEFER given multiple ID arguments"); |
474 | 0 | } |
475 | 0 | if (!moreArgs()) { |
476 | 0 | return FatalError(status, "DEFER ID missing value"); |
477 | 0 | } |
478 | 0 | defer.Id = expArgs[expArg++]; |
479 | 0 | if (defer.Id.empty()) { |
480 | 0 | return FatalError(status, "DEFER ID may not be empty"); |
481 | 0 | } |
482 | 0 | if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') { |
483 | 0 | return FatalError(status, "DEFER ID may not start in A-Z."); |
484 | 0 | } |
485 | 0 | } else if (expArgs[expArg] == "ID_VAR"_s) { |
486 | 0 | ++expArg; // Consume "ID_VAR". |
487 | 0 | if (!defer.IdVar.empty()) { |
488 | 0 | return FatalError(status, "DEFER given multiple ID_VAR arguments"); |
489 | 0 | } |
490 | 0 | if (!moreArgs()) { |
491 | 0 | return FatalError(status, "DEFER ID_VAR missing variable name"); |
492 | 0 | } |
493 | 0 | defer.IdVar = expArgs[expArg++]; |
494 | 0 | if (defer.IdVar.empty()) { |
495 | 0 | return FatalError(status, "DEFER ID_VAR may not be empty"); |
496 | 0 | } |
497 | 0 | } else { |
498 | 0 | return FatalError( |
499 | 0 | status, cmStrCat("DEFER unknown option:\n "_s, expArgs[expArg])); |
500 | 0 | } |
501 | 0 | } |
502 | | |
503 | 0 | if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) { |
504 | 0 | return FatalError(status, "DEFER must be followed by a CALL argument"); |
505 | 0 | } |
506 | | |
507 | 0 | maybeDefer = std::move(defer); |
508 | 0 | } |
509 | | |
510 | 0 | if (expArgs[expArg] == "CALL") { |
511 | 0 | ++expArg; // Consume "CALL". |
512 | | |
513 | | // CALL requires a command name. |
514 | 0 | if (!moreArgs()) { |
515 | 0 | return FatalError(status, "CALL missing command name"); |
516 | 0 | } |
517 | 0 | std::string const& callCommand = expArgs[expArg++]; |
518 | | |
519 | | // CALL accepts no further expanded arguments. |
520 | 0 | if (expArg != expArgs.size()) { |
521 | 0 | return FatalError(status, "CALL command's arguments must be literal"); |
522 | 0 | } |
523 | | |
524 | | // Run the CALL. |
525 | 0 | return cmCMakeLanguageCommandCALL(args, callCommand, rawArg, |
526 | 0 | std::move(maybeDefer), status); |
527 | 0 | } |
528 | | |
529 | 0 | if (expArgs[expArg] == "EVAL") { |
530 | 0 | return cmCMakeLanguageCommandEVAL(args, status); |
531 | 0 | } |
532 | | |
533 | 0 | if (expArgs[expArg] == "GET_MESSAGE_LOG_LEVEL") { |
534 | 0 | return cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(args, status); |
535 | 0 | } |
536 | | |
537 | 0 | if (expArgs[expArg] == "GET_EXPERIMENTAL_FEATURE_ENABLED") { |
538 | 0 | return cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(args, |
539 | 0 | status); |
540 | 0 | } |
541 | | |
542 | 0 | if (expArgs[expArg] == "TRACE") { |
543 | 0 | ++expArg; // Consume "TRACE". |
544 | |
|
545 | 0 | if (!moreArgs()) { |
546 | 0 | return FatalError(status, "TRACE missing a boolean value"); |
547 | 0 | } |
548 | | |
549 | 0 | bool const value = cmValue::IsOn(expArgs[expArg++]); |
550 | 0 | bool expand = false; |
551 | |
|
552 | 0 | if (value && moreArgs()) { |
553 | 0 | expand = (expArgs[expArg] == "EXPAND"); |
554 | 0 | if (!expand) { |
555 | 0 | return FatalError( |
556 | 0 | status, |
557 | 0 | cmStrCat("TRACE ON given an invalid argument ", expArgs[expArg])); |
558 | 0 | } |
559 | 0 | ++expArg; |
560 | 0 | } |
561 | | |
562 | 0 | if (moreArgs()) { |
563 | 0 | return FatalError( |
564 | 0 | status, |
565 | 0 | cmStrCat("TRACE O", value ? "N" : "FF", " given too many arguments")); |
566 | 0 | } |
567 | | |
568 | 0 | cmMakefile& makefile = status.GetMakefile(); |
569 | 0 | if (value) { |
570 | 0 | makefile.GetCMakeInstance()->PushTraceCmd(expand); |
571 | 0 | return true; |
572 | 0 | } |
573 | 0 | return makefile.GetCMakeInstance()->PopTraceCmd() || |
574 | 0 | FatalError(status, "TRACE OFF request without a corresponding TRACE ON"); |
575 | 0 | } |
576 | | |
577 | 0 | return FatalError(status, "called with unknown meta-operation"); |
578 | 0 | } |