CMake Nightly Date Stamp
[kiteware-cmake.git] / Source / cmCMakeLanguageCommand.cxx
blobfe9257ec9b8e7508ed9f7f06f872128e89c2cc53
1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCMakeLanguageCommand.h"
5 #include <algorithm>
6 #include <array>
7 #include <cstddef>
8 #include <string>
9 #include <utility>
11 #include <cm/optional>
12 #include <cm/string_view>
13 #include <cmext/string_view>
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 "cmake.h"
30 namespace {
32 bool FatalError(cmExecutionStatus& status, std::string const& error)
34 status.SetError(error);
35 cmSystemTools::SetFatalErrorOccurred();
36 return false;
39 std::array<cm::static_string_view, 14> InvalidCommands{
40 { // clang-format off
41 "function"_s, "endfunction"_s,
42 "macro"_s, "endmacro"_s,
43 "if"_s, "elseif"_s, "else"_s, "endif"_s,
44 "while"_s, "endwhile"_s,
45 "foreach"_s, "endforeach"_s,
46 "block"_s, "endblock"_s
47 } // clang-format on
50 std::array<cm::static_string_view, 1> InvalidDeferCommands{
52 // clang-format off
53 "return"_s,
54 } // clang-format on
57 struct Defer
59 std::string Id;
60 std::string IdVar;
61 cmMakefile* Directory = nullptr;
64 bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
65 std::string const& callCommand,
66 size_t startArg, cm::optional<Defer> defer,
67 cmExecutionStatus& status)
69 // ensure specified command is valid
70 // start/end flow control commands are not allowed
71 auto cmd = cmSystemTools::LowerCase(callCommand);
72 if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) !=
73 InvalidCommands.cend()) {
74 return FatalError(status,
75 cmStrCat("invalid command specified: "_s, callCommand));
77 if (defer &&
78 std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(),
79 cmd) != InvalidDeferCommands.cend()) {
80 return FatalError(status,
81 cmStrCat("invalid command specified: "_s, callCommand));
84 cmMakefile& makefile = status.GetMakefile();
85 cmListFileContext context = makefile.GetBacktrace().Top();
87 std::vector<cmListFileArgument> funcArgs;
88 funcArgs.reserve(args.size() - startArg);
90 // The rest of the arguments are passed to the function call above
91 for (size_t i = startArg; i < args.size(); ++i) {
92 funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line);
94 cmListFileFunction func{ callCommand, context.Line, context.Line,
95 std::move(funcArgs) };
97 if (defer) {
98 if (defer->Id.empty()) {
99 defer->Id = makefile.NewDeferId();
101 if (!defer->IdVar.empty()) {
102 makefile.AddDefinition(defer->IdVar, defer->Id);
104 cmMakefile* deferMakefile =
105 defer->Directory ? defer->Directory : &makefile;
106 if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) {
107 return FatalError(
108 status,
109 cmStrCat("DEFER CALL may not be scheduled in directory:\n "_s,
110 deferMakefile->GetCurrentBinaryDirectory(),
111 "\nat this time."_s));
113 return true;
115 return makefile.ExecuteCommand(func, status);
118 bool cmCMakeLanguageCommandDEFER(Defer const& defer,
119 std::vector<std::string> const& args,
120 size_t arg, cmExecutionStatus& status)
122 cmMakefile* deferMakefile =
123 defer.Directory ? defer.Directory : &status.GetMakefile();
124 if (args[arg] == "CANCEL_CALL"_s) {
125 ++arg; // Consume CANCEL_CALL.
126 auto ids = cmMakeRange(args).advance(arg);
127 for (std::string const& id : ids) {
128 if (id[0] >= 'A' && id[0] <= 'Z') {
129 return FatalError(
130 status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n "_s, id));
132 if (!deferMakefile->DeferCancelCall(id)) {
133 return FatalError(
134 status,
135 cmStrCat("DEFER CANCEL_CALL may not update directory:\n "_s,
136 deferMakefile->GetCurrentBinaryDirectory(),
137 "\nat this time."_s));
140 return true;
142 if (args[arg] == "GET_CALL_IDS"_s) {
143 ++arg; // Consume GET_CALL_IDS.
144 if (arg == args.size()) {
145 return FatalError(status, "DEFER GET_CALL_IDS missing output variable");
147 std::string const& var = args[arg++];
148 if (arg != args.size()) {
149 return FatalError(status, "DEFER GET_CALL_IDS given too many arguments");
151 cm::optional<std::string> ids = deferMakefile->DeferGetCallIds();
152 if (!ids) {
153 return FatalError(
154 status,
155 cmStrCat("DEFER GET_CALL_IDS may not access directory:\n "_s,
156 deferMakefile->GetCurrentBinaryDirectory(),
157 "\nat this time."_s));
159 status.GetMakefile().AddDefinition(var, *ids);
160 return true;
162 if (args[arg] == "GET_CALL"_s) {
163 ++arg; // Consume GET_CALL.
164 if (arg == args.size()) {
165 return FatalError(status, "DEFER GET_CALL missing id");
167 std::string const& id = args[arg++];
168 if (arg == args.size()) {
169 return FatalError(status, "DEFER GET_CALL missing output variable");
171 std::string const& var = args[arg++];
172 if (arg != args.size()) {
173 return FatalError(status, "DEFER GET_CALL given too many arguments");
175 if (id.empty()) {
176 return FatalError(status, "DEFER GET_CALL id may not be empty");
178 if (id[0] >= 'A' && id[0] <= 'Z') {
179 return FatalError(status,
180 cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id));
182 cm::optional<std::string> call = deferMakefile->DeferGetCall(id);
183 if (!call) {
184 return FatalError(
185 status,
186 cmStrCat("DEFER GET_CALL may not access directory:\n "_s,
187 deferMakefile->GetCurrentBinaryDirectory(),
188 "\nat this time."_s));
190 status.GetMakefile().AddDefinition(var, *call);
191 return true;
193 return FatalError(status,
194 cmStrCat("DEFER operation unknown: "_s, args[arg]));
197 bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
198 cmExecutionStatus& status)
200 cmMakefile& makefile = status.GetMakefile();
201 cmListFileContext context = makefile.GetBacktrace().Top();
202 std::vector<std::string> expandedArgs;
203 makefile.ExpandArguments(args, expandedArgs);
205 if (expandedArgs.size() < 2) {
206 return FatalError(status, "called with incorrect number of arguments");
209 if (expandedArgs[1] != "CODE") {
210 auto code_iter =
211 std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE");
212 if (code_iter == expandedArgs.end()) {
213 return FatalError(status, "called without CODE argument");
215 return FatalError(
216 status,
217 "called with unsupported arguments between EVAL and CODE arguments");
220 const std::string code =
221 cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " ");
222 return makefile.ReadListFileAsString(
223 code, cmStrCat(context.FilePath, ":", context.Line, ":EVAL"));
226 bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(
227 std::vector<std::string> const& args, cmExecutionStatus& status)
229 cmState* state = status.GetMakefile().GetState();
230 if (!state->InTopLevelIncludes()) {
231 return FatalError(
232 status,
233 "Dependency providers can only be set as part of the first call to "
234 "project(). More specifically, cmake_language(SET_DEPENDENCY_PROVIDER) "
235 "can only be called while the first project() command processes files "
236 "listed in CMAKE_PROJECT_TOP_LEVEL_INCLUDES.");
239 struct SetProviderArgs
241 std::string Command;
242 ArgumentParser::NonEmpty<std::vector<std::string>> Methods;
245 auto const ArgsParser =
246 cmArgumentParser<SetProviderArgs>()
247 .Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command)
248 .Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods);
250 std::vector<std::string> unparsed;
251 auto parsedArgs = ArgsParser.Parse(args, &unparsed);
253 if (!unparsed.empty()) {
254 return FatalError(
255 status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), "\""));
258 // We store the command that FetchContent_MakeAvailable() can call in a
259 // global (but considered internal) property. If the provider doesn't
260 // support this method, we set this property to an empty string instead.
261 // This simplifies the logic in FetchContent_MakeAvailable() and doesn't
262 // require us to define a new internal command or sub-command.
263 std::string fcmasProperty = "__FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER";
265 if (parsedArgs.Command.empty()) {
266 if (!parsedArgs.Methods.empty()) {
267 return FatalError(status,
268 "Must specify a non-empty command name when provider "
269 "methods are given");
271 state->ClearDependencyProvider();
272 state->SetGlobalProperty(fcmasProperty, "");
273 return true;
276 cmState::Command command = state->GetCommand(parsedArgs.Command);
277 if (!command) {
278 return FatalError(status,
279 cmStrCat("Command \"", parsedArgs.Command,
280 "\" is not a defined command"));
283 if (parsedArgs.Methods.empty()) {
284 return FatalError(status, "Must specify at least one provider method");
287 bool supportsFetchContentMakeAvailableSerial = false;
288 std::vector<cmDependencyProvider::Method> methods;
289 for (auto const& method : parsedArgs.Methods) {
290 if (method == "FIND_PACKAGE") {
291 methods.emplace_back(cmDependencyProvider::Method::FindPackage);
292 } else if (method == "FETCHCONTENT_MAKEAVAILABLE_SERIAL") {
293 supportsFetchContentMakeAvailableSerial = true;
294 methods.emplace_back(
295 cmDependencyProvider::Method::FetchContentMakeAvailableSerial);
296 } else {
297 return FatalError(
298 status,
299 cmStrCat("Unknown dependency provider method \"", method, "\""));
303 state->SetDependencyProvider({ parsedArgs.Command, methods });
304 state->SetGlobalProperty(
305 fcmasProperty,
306 supportsFetchContentMakeAvailableSerial ? parsedArgs.Command : "");
308 return true;
311 bool cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(
312 std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
314 cmMakefile& makefile = status.GetMakefile();
315 std::vector<std::string> expandedArgs;
316 makefile.ExpandArguments(args, expandedArgs);
318 if (args.size() < 2 || expandedArgs.size() > 2) {
319 return FatalError(
320 status,
321 "sub-command GET_MESSAGE_LOG_LEVEL expects exactly one argument");
324 Message::LogLevel logLevel = makefile.GetCurrentLogLevel();
325 std::string outputValue = cmake::LogLevelToString(logLevel);
327 const std::string& outputVariable = expandedArgs[1];
328 makefile.AddDefinition(outputVariable, outputValue);
329 return true;
332 bool cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(
333 std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
335 cmMakefile& makefile = status.GetMakefile();
336 std::vector<std::string> expandedArgs;
337 makefile.ExpandArguments(args, expandedArgs);
339 if (expandedArgs.size() != 3) {
340 return FatalError(status,
341 "sub-command GET_EXPERIMENTAL_FEATURE_ENABLED expects "
342 "exactly two arguments");
345 auto const& featureName = expandedArgs[1];
346 auto const& variableName = expandedArgs[2];
348 if (auto feature = cmExperimental::FeatureByName(featureName)) {
349 if (cmExperimental::HasSupportEnabled(makefile, *feature)) {
350 makefile.AddDefinition(variableName, "TRUE");
351 } else {
352 makefile.AddDefinition(variableName, "FALSE");
354 } else {
355 return FatalError(status,
356 cmStrCat("Experimental feature name \"", featureName,
357 "\" does not exist."));
360 return true;
364 bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
365 cmExecutionStatus& status)
367 std::vector<std::string> expArgs;
368 size_t rawArg = 0;
369 size_t expArg = 0;
371 // Helper to consume and expand one raw argument at a time.
372 auto moreArgs = [&]() -> bool {
373 while (expArg >= expArgs.size()) {
374 if (rawArg >= args.size()) {
375 return false;
377 std::vector<cmListFileArgument> tmpArg;
378 tmpArg.emplace_back(args[rawArg++]);
379 status.GetMakefile().ExpandArguments(tmpArg, expArgs);
381 return true;
383 auto finishArgs = [&]() {
384 std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
385 status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
386 rawArg = args.size();
389 if (!moreArgs()) {
390 return FatalError(status, "called with incorrect number of arguments");
392 if (expArgs[expArg] == "EXIT"_s) {
393 ++expArg; // consume "EXIT".
395 if (!moreArgs()) {
396 return FatalError(status, "EXIT requires one argument");
399 auto workingMode =
400 status.GetMakefile().GetCMakeInstance()->GetWorkingMode();
401 if (workingMode != cmake::SCRIPT_MODE) {
402 return FatalError(status, "EXIT can be used only in SCRIPT mode");
405 long retCode = 0;
407 if (!cmStrToLong(expArgs[expArg], &retCode)) {
408 return FatalError(status,
409 cmStrCat("EXIT requires one integral argument, got \"",
410 expArgs[expArg], '\"'));
413 if (workingMode == cmake::SCRIPT_MODE) {
414 status.SetExitCode(static_cast<int>(retCode));
416 return true;
419 if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) {
420 finishArgs();
421 return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status);
424 cm::optional<Defer> maybeDefer;
425 if (expArgs[expArg] == "DEFER"_s) {
426 ++expArg; // Consume "DEFER".
428 if (!moreArgs()) {
429 return FatalError(status, "DEFER requires at least one argument");
432 Defer defer;
434 // Process optional arguments.
435 while (moreArgs()) {
436 if (expArgs[expArg] == "CALL"_s) {
437 break;
439 if (expArgs[expArg] == "CANCEL_CALL"_s ||
440 expArgs[expArg] == "GET_CALL_IDS"_s ||
441 expArgs[expArg] == "GET_CALL"_s) {
442 if (!defer.Id.empty() || !defer.IdVar.empty()) {
443 return FatalError(status,
444 cmStrCat("DEFER "_s, expArgs[expArg],
445 " does not accept ID or ID_VAR."_s));
447 finishArgs();
448 return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
450 if (expArgs[expArg] == "DIRECTORY"_s) {
451 ++expArg; // Consume "DIRECTORY".
452 if (defer.Directory) {
453 return FatalError(status,
454 "DEFER given multiple DIRECTORY arguments");
456 if (!moreArgs()) {
457 return FatalError(status, "DEFER DIRECTORY missing value");
459 std::string dir = expArgs[expArg++];
460 if (dir.empty()) {
461 return FatalError(status, "DEFER DIRECTORY may not be empty");
463 dir = cmSystemTools::CollapseFullPath(
464 dir, status.GetMakefile().GetCurrentSourceDirectory());
465 defer.Directory =
466 status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
467 if (!defer.Directory) {
468 return FatalError(status,
469 cmStrCat("DEFER DIRECTORY:\n "_s, dir,
470 "\nis not known. "_s,
471 "It may not have been processed yet."_s));
473 } else if (expArgs[expArg] == "ID"_s) {
474 ++expArg; // Consume "ID".
475 if (!defer.Id.empty()) {
476 return FatalError(status, "DEFER given multiple ID arguments");
478 if (!moreArgs()) {
479 return FatalError(status, "DEFER ID missing value");
481 defer.Id = expArgs[expArg++];
482 if (defer.Id.empty()) {
483 return FatalError(status, "DEFER ID may not be empty");
485 if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
486 return FatalError(status, "DEFER ID may not start in A-Z.");
488 } else if (expArgs[expArg] == "ID_VAR"_s) {
489 ++expArg; // Consume "ID_VAR".
490 if (!defer.IdVar.empty()) {
491 return FatalError(status, "DEFER given multiple ID_VAR arguments");
493 if (!moreArgs()) {
494 return FatalError(status, "DEFER ID_VAR missing variable name");
496 defer.IdVar = expArgs[expArg++];
497 if (defer.IdVar.empty()) {
498 return FatalError(status, "DEFER ID_VAR may not be empty");
500 } else {
501 return FatalError(
502 status, cmStrCat("DEFER unknown option:\n "_s, expArgs[expArg]));
506 if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
507 return FatalError(status, "DEFER must be followed by a CALL argument");
510 maybeDefer = std::move(defer);
513 if (expArgs[expArg] == "CALL") {
514 ++expArg; // Consume "CALL".
516 // CALL requires a command name.
517 if (!moreArgs()) {
518 return FatalError(status, "CALL missing command name");
520 std::string const& callCommand = expArgs[expArg++];
522 // CALL accepts no further expanded arguments.
523 if (expArg != expArgs.size()) {
524 return FatalError(status, "CALL command's arguments must be literal");
527 // Run the CALL.
528 return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
529 std::move(maybeDefer), status);
532 if (expArgs[expArg] == "EVAL") {
533 return cmCMakeLanguageCommandEVAL(args, status);
536 if (expArgs[expArg] == "GET_MESSAGE_LOG_LEVEL") {
537 return cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(args, status);
540 if (expArgs[expArg] == "GET_EXPERIMENTAL_FEATURE_ENABLED") {
541 return cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(args,
542 status);
545 return FatalError(status, "called with unknown meta-operation");