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"
12 #include <cm/optional>
13 #include <cm/string_view>
14 #include <cmext/string_view>
16 #include "cmArgumentParser.h"
17 #include "cmDependencyProvider.h"
18 #include "cmExecutionStatus.h"
19 #include "cmGlobalGenerator.h"
20 #include "cmListFileCache.h"
21 #include "cmMakefile.h"
24 #include "cmStringAlgorithms.h"
25 #include "cmSystemTools.h"
29 bool FatalError(cmExecutionStatus& status, std::string const& error)
31 status.SetError(error);
32 cmSystemTools::SetFatalErrorOccurred();
36 std::array<cm::static_string_view, 12> InvalidCommands{
38 "function"_s, "endfunction"_s,
39 "macro"_s, "endmacro"_s,
40 "if"_s, "elseif"_s, "else"_s, "endif"_s,
41 "while"_s, "endwhile"_s,
42 "foreach"_s, "endforeach"_s
46 std::array<cm::static_string_view, 1> InvalidDeferCommands{
57 cmMakefile* Directory = nullptr;
60 bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
61 std::string const& callCommand,
62 size_t startArg, cm::optional<Defer> defer,
63 cmExecutionStatus& status)
65 // ensure specified command is valid
66 // start/end flow control commands are not allowed
67 auto cmd = cmSystemTools::LowerCase(callCommand);
68 if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) !=
69 InvalidCommands.cend()) {
70 return FatalError(status,
71 cmStrCat("invalid command specified: "_s, callCommand));
74 std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(),
75 cmd) != InvalidDeferCommands.cend()) {
76 return FatalError(status,
77 cmStrCat("invalid command specified: "_s, callCommand));
80 cmMakefile& makefile = status.GetMakefile();
81 cmListFileContext context = makefile.GetBacktrace().Top();
83 std::vector<cmListFileArgument> funcArgs;
84 funcArgs.reserve(args.size() - startArg);
86 // The rest of the arguments are passed to the function call above
87 for (size_t i = startArg; i < args.size(); ++i) {
88 funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line);
90 cmListFileFunction func{ callCommand, context.Line, context.Line,
91 std::move(funcArgs) };
94 if (defer->Id.empty()) {
95 defer->Id = makefile.NewDeferId();
97 if (!defer->IdVar.empty()) {
98 makefile.AddDefinition(defer->IdVar, defer->Id);
100 cmMakefile* deferMakefile =
101 defer->Directory ? defer->Directory : &makefile;
102 if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) {
105 cmStrCat("DEFER CALL may not be scheduled in directory:\n "_s,
106 deferMakefile->GetCurrentBinaryDirectory(),
107 "\nat this time."_s));
111 return makefile.ExecuteCommand(func, status);
114 bool cmCMakeLanguageCommandDEFER(Defer const& defer,
115 std::vector<std::string> const& args,
116 size_t arg, cmExecutionStatus& status)
118 cmMakefile* deferMakefile =
119 defer.Directory ? defer.Directory : &status.GetMakefile();
120 if (args[arg] == "CANCEL_CALL"_s) {
121 ++arg; // Consume CANCEL_CALL.
122 auto ids = cmMakeRange(args).advance(arg);
123 for (std::string const& id : ids) {
124 if (id[0] >= 'A' && id[0] <= 'Z') {
126 status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n "_s, id));
128 if (!deferMakefile->DeferCancelCall(id)) {
131 cmStrCat("DEFER CANCEL_CALL may not update directory:\n "_s,
132 deferMakefile->GetCurrentBinaryDirectory(),
133 "\nat this time."_s));
138 if (args[arg] == "GET_CALL_IDS"_s) {
139 ++arg; // Consume GET_CALL_IDS.
140 if (arg == args.size()) {
141 return FatalError(status, "DEFER GET_CALL_IDS missing output variable");
143 std::string const& var = args[arg++];
144 if (arg != args.size()) {
145 return FatalError(status, "DEFER GET_CALL_IDS given too many arguments");
147 cm::optional<std::string> ids = deferMakefile->DeferGetCallIds();
151 cmStrCat("DEFER GET_CALL_IDS may not access directory:\n "_s,
152 deferMakefile->GetCurrentBinaryDirectory(),
153 "\nat this time."_s));
155 status.GetMakefile().AddDefinition(var, *ids);
158 if (args[arg] == "GET_CALL"_s) {
159 ++arg; // Consume GET_CALL.
160 if (arg == args.size()) {
161 return FatalError(status, "DEFER GET_CALL missing id");
163 std::string const& id = args[arg++];
164 if (arg == args.size()) {
165 return FatalError(status, "DEFER GET_CALL missing output variable");
167 std::string const& var = args[arg++];
168 if (arg != args.size()) {
169 return FatalError(status, "DEFER GET_CALL given too many arguments");
172 return FatalError(status, "DEFER GET_CALL id may not be empty");
174 if (id[0] >= 'A' && id[0] <= 'Z') {
175 return FatalError(status,
176 cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id));
178 cm::optional<std::string> call = deferMakefile->DeferGetCall(id);
182 cmStrCat("DEFER GET_CALL may not access directory:\n "_s,
183 deferMakefile->GetCurrentBinaryDirectory(),
184 "\nat this time."_s));
186 status.GetMakefile().AddDefinition(var, *call);
189 return FatalError(status,
190 cmStrCat("DEFER operation unknown: "_s, args[arg]));
193 bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
194 cmExecutionStatus& status)
196 cmMakefile& makefile = status.GetMakefile();
197 cmListFileContext context = makefile.GetBacktrace().Top();
198 std::vector<std::string> expandedArgs;
199 makefile.ExpandArguments(args, expandedArgs);
201 if (expandedArgs.size() < 2) {
202 return FatalError(status, "called with incorrect number of arguments");
205 if (expandedArgs[1] != "CODE") {
207 std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE");
208 if (code_iter == expandedArgs.end()) {
209 return FatalError(status, "called without CODE argument");
213 "called with unsupported arguments between EVAL and CODE arguments");
216 const std::string code =
217 cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " ");
218 return makefile.ReadListFileAsString(
219 code, cmStrCat(context.FilePath, ":", context.Line, ":EVAL"));
222 bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(
223 std::vector<std::string> const& args, cmExecutionStatus& status)
225 cmState* state = status.GetMakefile().GetState();
226 if (!state->InTopLevelIncludes()) {
229 "Dependency providers can only be set as part of the first call to "
230 "project(). More specifically, cmake_language(SET_DEPENDENCY_PROVIDER) "
231 "can only be called while the first project() command processes files "
232 "listed in CMAKE_PROJECT_TOP_LEVEL_INCLUDES.");
235 struct SetProviderArgs
238 std::vector<std::string> Methods;
241 auto const ArgsParser =
242 cmArgumentParser<SetProviderArgs>()
243 .Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command)
244 .Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods);
246 std::vector<std::string> unparsed;
247 auto parsedArgs = ArgsParser.Parse(args, &unparsed);
249 if (!unparsed.empty()) {
251 status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), "\""));
254 // We store the command that FetchContent_MakeAvailable() can call in a
255 // global (but considered internal) property. If the provider doesn't
256 // support this method, we set this property to an empty string instead.
257 // This simplifies the logic in FetchContent_MakeAvailable() and doesn't
258 // require us to define a new internal command or sub-command.
259 std::string fcmasProperty = "__FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER";
261 if (parsedArgs.Command.empty()) {
262 if (!parsedArgs.Methods.empty()) {
263 return FatalError(status,
264 "Must specify a non-empty command name when provider "
265 "methods are given");
267 state->ClearDependencyProvider();
268 state->SetGlobalProperty(fcmasProperty, "");
272 cmState::Command command = state->GetCommand(parsedArgs.Command);
274 return FatalError(status,
275 cmStrCat("Command \"", parsedArgs.Command,
276 "\" is not a defined command"));
279 if (parsedArgs.Methods.empty()) {
280 return FatalError(status, "Must specify at least one provider method");
283 bool supportsFetchContentMakeAvailableSerial = false;
284 std::vector<cmDependencyProvider::Method> methods;
285 for (auto const& method : parsedArgs.Methods) {
286 if (method == "FIND_PACKAGE") {
287 methods.emplace_back(cmDependencyProvider::Method::FindPackage);
288 } else if (method == "FETCHCONTENT_MAKEAVAILABLE_SERIAL") {
289 supportsFetchContentMakeAvailableSerial = true;
290 methods.emplace_back(
291 cmDependencyProvider::Method::FetchContentMakeAvailableSerial);
295 cmStrCat("Unknown dependency provider method \"", method, "\""));
299 state->SetDependencyProvider({ parsedArgs.Command, methods });
300 state->SetGlobalProperty(
302 supportsFetchContentMakeAvailableSerial ? parsedArgs.Command.c_str() : "");
308 bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
309 cmExecutionStatus& status)
311 std::vector<std::string> expArgs;
315 // Helper to consume and expand one raw argument at a time.
316 auto moreArgs = [&]() -> bool {
317 while (expArg >= expArgs.size()) {
318 if (rawArg >= args.size()) {
321 std::vector<cmListFileArgument> tmpArg;
322 tmpArg.emplace_back(args[rawArg++]);
323 status.GetMakefile().ExpandArguments(tmpArg, expArgs);
327 auto finishArgs = [&]() {
328 std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
329 status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
330 rawArg = args.size();
334 return FatalError(status, "called with incorrect number of arguments");
337 if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) {
339 return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status);
342 cm::optional<Defer> maybeDefer;
343 if (expArgs[expArg] == "DEFER"_s) {
344 ++expArg; // Consume "DEFER".
347 return FatalError(status, "DEFER requires at least one argument");
352 // Process optional arguments.
354 if (expArgs[expArg] == "CALL"_s) {
357 if (expArgs[expArg] == "CANCEL_CALL"_s ||
358 expArgs[expArg] == "GET_CALL_IDS"_s ||
359 expArgs[expArg] == "GET_CALL"_s) {
360 if (!defer.Id.empty() || !defer.IdVar.empty()) {
361 return FatalError(status,
362 cmStrCat("DEFER "_s, expArgs[expArg],
363 " does not accept ID or ID_VAR."_s));
366 return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
368 if (expArgs[expArg] == "DIRECTORY"_s) {
369 ++expArg; // Consume "DIRECTORY".
370 if (defer.Directory) {
371 return FatalError(status,
372 "DEFER given multiple DIRECTORY arguments");
375 return FatalError(status, "DEFER DIRECTORY missing value");
377 std::string dir = expArgs[expArg++];
379 return FatalError(status, "DEFER DIRECTORY may not be empty");
381 dir = cmSystemTools::CollapseFullPath(
382 dir, status.GetMakefile().GetCurrentSourceDirectory());
384 status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
385 if (!defer.Directory) {
386 return FatalError(status,
387 cmStrCat("DEFER DIRECTORY:\n "_s, dir,
388 "\nis not known. "_s,
389 "It may not have been processed yet."_s));
391 } else if (expArgs[expArg] == "ID"_s) {
392 ++expArg; // Consume "ID".
393 if (!defer.Id.empty()) {
394 return FatalError(status, "DEFER given multiple ID arguments");
397 return FatalError(status, "DEFER ID missing value");
399 defer.Id = expArgs[expArg++];
400 if (defer.Id.empty()) {
401 return FatalError(status, "DEFER ID may not be empty");
403 if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
404 return FatalError(status, "DEFER ID may not start in A-Z.");
406 } else if (expArgs[expArg] == "ID_VAR"_s) {
407 ++expArg; // Consume "ID_VAR".
408 if (!defer.IdVar.empty()) {
409 return FatalError(status, "DEFER given multiple ID_VAR arguments");
412 return FatalError(status, "DEFER ID_VAR missing variable name");
414 defer.IdVar = expArgs[expArg++];
415 if (defer.IdVar.empty()) {
416 return FatalError(status, "DEFER ID_VAR may not be empty");
420 status, cmStrCat("DEFER unknown option:\n "_s, expArgs[expArg]));
424 if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
425 return FatalError(status, "DEFER must be followed by a CALL argument");
428 maybeDefer = std::move(defer);
431 if (expArgs[expArg] == "CALL") {
432 ++expArg; // Consume "CALL".
434 // CALL requires a command name.
436 return FatalError(status, "CALL missing command name");
438 std::string const& callCommand = expArgs[expArg++];
440 // CALL accepts no further expanded arguments.
441 if (expArg != expArgs.size()) {
442 return FatalError(status, "CALL command's arguments must be literal");
446 return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
447 std::move(maybeDefer), status);
450 if (expArgs[expArg] == "EVAL") {
451 return cmCMakeLanguageCommandEVAL(args, status);
454 return FatalError(status, "called with unknown meta-operation");