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"
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
26 #include "cmStringAlgorithms.h"
27 #include "cmSystemTools.h"
32 bool FatalError(cmExecutionStatus
& status
, std::string
const& error
)
34 status
.SetError(error
);
35 cmSystemTools::SetFatalErrorOccurred();
39 std::array
<cm::static_string_view
, 14> InvalidCommands
{
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
50 std::array
<cm::static_string_view
, 1> InvalidDeferCommands
{
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
));
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
) };
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
)) {
109 cmStrCat("DEFER CALL may not be scheduled in directory:\n "_s
,
110 deferMakefile
->GetCurrentBinaryDirectory(),
111 "\nat this time."_s
));
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') {
130 status
, cmStrCat("DEFER CANCEL_CALL unknown argument:\n "_s
, id
));
132 if (!deferMakefile
->DeferCancelCall(id
)) {
135 cmStrCat("DEFER CANCEL_CALL may not update directory:\n "_s
,
136 deferMakefile
->GetCurrentBinaryDirectory(),
137 "\nat this time."_s
));
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();
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
);
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");
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
);
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
);
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") {
211 std::find(expandedArgs
.begin() + 2, expandedArgs
.end(), "CODE");
212 if (code_iter
== expandedArgs
.end()) {
213 return FatalError(status
, "called without CODE argument");
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()) {
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
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()) {
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
, "");
276 cmState::Command command
= state
->GetCommand(parsedArgs
.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
);
299 cmStrCat("Unknown dependency provider method \"", method
, "\""));
303 state
->SetDependencyProvider({ parsedArgs
.Command
, methods
});
304 state
->SetGlobalProperty(
306 supportsFetchContentMakeAvailableSerial
? parsedArgs
.Command
: "");
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) {
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
);
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");
352 makefile
.AddDefinition(variableName
, "FALSE");
355 return FatalError(status
,
356 cmStrCat("Experimental feature name \"", featureName
,
357 "\" does not exist."));
364 bool cmCMakeLanguageCommand(std::vector
<cmListFileArgument
> const& args
,
365 cmExecutionStatus
& status
)
367 std::vector
<std::string
> expArgs
;
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()) {
377 std::vector
<cmListFileArgument
> tmpArg
;
378 tmpArg
.emplace_back(args
[rawArg
++]);
379 status
.GetMakefile().ExpandArguments(tmpArg
, expArgs
);
383 auto finishArgs
= [&]() {
384 std::vector
<cmListFileArgument
> tmpArgs(args
.begin() + rawArg
, args
.end());
385 status
.GetMakefile().ExpandArguments(tmpArgs
, expArgs
);
386 rawArg
= args
.size();
390 return FatalError(status
, "called with incorrect number of arguments");
392 if (expArgs
[expArg
] == "EXIT"_s
) {
393 ++expArg
; // consume "EXIT".
396 return FatalError(status
, "EXIT requires one argument");
400 status
.GetMakefile().GetCMakeInstance()->GetWorkingMode();
401 if (workingMode
!= cmake::SCRIPT_MODE
) {
402 return FatalError(status
, "EXIT can be used only in SCRIPT mode");
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
));
419 if (expArgs
[expArg
] == "SET_DEPENDENCY_PROVIDER"_s
) {
421 return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs
, status
);
424 cm::optional
<Defer
> maybeDefer
;
425 if (expArgs
[expArg
] == "DEFER"_s
) {
426 ++expArg
; // Consume "DEFER".
429 return FatalError(status
, "DEFER requires at least one argument");
434 // Process optional arguments.
436 if (expArgs
[expArg
] == "CALL"_s
) {
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
));
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");
457 return FatalError(status
, "DEFER DIRECTORY missing value");
459 std::string dir
= expArgs
[expArg
++];
461 return FatalError(status
, "DEFER DIRECTORY may not be empty");
463 dir
= cmSystemTools::CollapseFullPath(
464 dir
, status
.GetMakefile().GetCurrentSourceDirectory());
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");
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");
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");
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.
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");
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
,
545 return FatalError(status
, "called with unknown meta-operation");