a2aaa2aee8265de4d98870b4bff4a0f447cb5cf1
[platform/upstream/cmake.git] / Source / cmCMakeLanguageCommand.cxx
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"
4
5 #include <algorithm>
6 #include <array>
7 #include <cstddef>
8 #include <memory>
9 #include <string>
10 #include <utility>
11
12 #include <cm/optional>
13 #include <cm/string_view>
14 #include <cmext/string_view>
15
16 #include "cmArgumentParser.h"
17 #include "cmDependencyProvider.h"
18 #include "cmExecutionStatus.h"
19 #include "cmGlobalGenerator.h"
20 #include "cmListFileCache.h"
21 #include "cmMakefile.h"
22 #include "cmRange.h"
23 #include "cmState.h"
24 #include "cmStringAlgorithms.h"
25 #include "cmSystemTools.h"
26
27 namespace {
28
29 bool FatalError(cmExecutionStatus& status, std::string const& error)
30 {
31   status.SetError(error);
32   cmSystemTools::SetFatalErrorOccurred();
33   return false;
34 }
35
36 std::array<cm::static_string_view, 12> InvalidCommands{
37   { // clang-format off
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
43   } // clang-format on
44 };
45
46 std::array<cm::static_string_view, 1> InvalidDeferCommands{
47   {
48     // clang-format off
49   "return"_s,
50   } // clang-format on
51 };
52
53 struct Defer
54 {
55   std::string Id;
56   std::string IdVar;
57   cmMakefile* Directory = nullptr;
58 };
59
60 bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
61                                 std::string const& callCommand,
62                                 size_t startArg, cm::optional<Defer> defer,
63                                 cmExecutionStatus& status)
64 {
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));
72   }
73   if (defer &&
74       std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(),
75                 cmd) != InvalidDeferCommands.cend()) {
76     return FatalError(status,
77                       cmStrCat("invalid command specified: "_s, callCommand));
78   }
79
80   cmMakefile& makefile = status.GetMakefile();
81   cmListFileContext context = makefile.GetBacktrace().Top();
82
83   std::vector<cmListFileArgument> funcArgs;
84   funcArgs.reserve(args.size() - startArg);
85
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);
89   }
90   cmListFileFunction func{ callCommand, context.Line, context.Line,
91                            std::move(funcArgs) };
92
93   if (defer) {
94     if (defer->Id.empty()) {
95       defer->Id = makefile.NewDeferId();
96     }
97     if (!defer->IdVar.empty()) {
98       makefile.AddDefinition(defer->IdVar, defer->Id);
99     }
100     cmMakefile* deferMakefile =
101       defer->Directory ? defer->Directory : &makefile;
102     if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) {
103       return FatalError(
104         status,
105         cmStrCat("DEFER CALL may not be scheduled in directory:\n  "_s,
106                  deferMakefile->GetCurrentBinaryDirectory(),
107                  "\nat this time."_s));
108     }
109     return true;
110   }
111   return makefile.ExecuteCommand(func, status);
112 }
113
114 bool cmCMakeLanguageCommandDEFER(Defer const& defer,
115                                  std::vector<std::string> const& args,
116                                  size_t arg, cmExecutionStatus& status)
117 {
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') {
125         return FatalError(
126           status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n  "_s, id));
127       }
128       if (!deferMakefile->DeferCancelCall(id)) {
129         return FatalError(
130           status,
131           cmStrCat("DEFER CANCEL_CALL may not update directory:\n  "_s,
132                    deferMakefile->GetCurrentBinaryDirectory(),
133                    "\nat this time."_s));
134       }
135     }
136     return true;
137   }
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");
142     }
143     std::string const& var = args[arg++];
144     if (arg != args.size()) {
145       return FatalError(status, "DEFER GET_CALL_IDS given too many arguments");
146     }
147     cm::optional<std::string> ids = deferMakefile->DeferGetCallIds();
148     if (!ids) {
149       return FatalError(
150         status,
151         cmStrCat("DEFER GET_CALL_IDS may not access directory:\n  "_s,
152                  deferMakefile->GetCurrentBinaryDirectory(),
153                  "\nat this time."_s));
154     }
155     status.GetMakefile().AddDefinition(var, *ids);
156     return true;
157   }
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");
162     }
163     std::string const& id = args[arg++];
164     if (arg == args.size()) {
165       return FatalError(status, "DEFER GET_CALL missing output variable");
166     }
167     std::string const& var = args[arg++];
168     if (arg != args.size()) {
169       return FatalError(status, "DEFER GET_CALL given too many arguments");
170     }
171     if (id.empty()) {
172       return FatalError(status, "DEFER GET_CALL id may not be empty");
173     }
174     if (id[0] >= 'A' && id[0] <= 'Z') {
175       return FatalError(status,
176                         cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id));
177     }
178     cm::optional<std::string> call = deferMakefile->DeferGetCall(id);
179     if (!call) {
180       return FatalError(
181         status,
182         cmStrCat("DEFER GET_CALL may not access directory:\n  "_s,
183                  deferMakefile->GetCurrentBinaryDirectory(),
184                  "\nat this time."_s));
185     }
186     status.GetMakefile().AddDefinition(var, *call);
187     return true;
188   }
189   return FatalError(status,
190                     cmStrCat("DEFER operation unknown: "_s, args[arg]));
191 }
192
193 bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
194                                 cmExecutionStatus& status)
195 {
196   cmMakefile& makefile = status.GetMakefile();
197   cmListFileContext context = makefile.GetBacktrace().Top();
198   std::vector<std::string> expandedArgs;
199   makefile.ExpandArguments(args, expandedArgs);
200
201   if (expandedArgs.size() < 2) {
202     return FatalError(status, "called with incorrect number of arguments");
203   }
204
205   if (expandedArgs[1] != "CODE") {
206     auto code_iter =
207       std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE");
208     if (code_iter == expandedArgs.end()) {
209       return FatalError(status, "called without CODE argument");
210     }
211     return FatalError(
212       status,
213       "called with unsupported arguments between EVAL and CODE arguments");
214   }
215
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"));
220 }
221
222 bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(
223   std::vector<std::string> const& args, cmExecutionStatus& status)
224 {
225   cmState* state = status.GetMakefile().GetState();
226   if (!state->InTopLevelIncludes()) {
227     return FatalError(
228       status,
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.");
233   }
234
235   struct SetProviderArgs
236   {
237     std::string Command;
238     std::vector<std::string> Methods;
239   };
240
241   auto const ArgsParser =
242     cmArgumentParser<SetProviderArgs>()
243       .Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command)
244       .Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods);
245
246   std::vector<std::string> unparsed;
247   auto parsedArgs = ArgsParser.Parse(args, &unparsed);
248
249   if (!unparsed.empty()) {
250     return FatalError(
251       status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), "\""));
252   }
253
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";
260
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");
266     }
267     state->ClearDependencyProvider();
268     state->SetGlobalProperty(fcmasProperty, "");
269     return true;
270   }
271
272   cmState::Command command = state->GetCommand(parsedArgs.Command);
273   if (!command) {
274     return FatalError(status,
275                       cmStrCat("Command \"", parsedArgs.Command,
276                                "\" is not a defined command"));
277   }
278
279   if (parsedArgs.Methods.empty()) {
280     return FatalError(status, "Must specify at least one provider method");
281   }
282
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);
292     } else {
293       return FatalError(
294         status,
295         cmStrCat("Unknown dependency provider method \"", method, "\""));
296     }
297   }
298
299   state->SetDependencyProvider({ parsedArgs.Command, methods });
300   state->SetGlobalProperty(
301     fcmasProperty,
302     supportsFetchContentMakeAvailableSerial ? parsedArgs.Command.c_str() : "");
303
304   return true;
305 }
306 }
307
308 bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
309                             cmExecutionStatus& status)
310 {
311   std::vector<std::string> expArgs;
312   size_t rawArg = 0;
313   size_t expArg = 0;
314
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()) {
319         return false;
320       }
321       std::vector<cmListFileArgument> tmpArg;
322       tmpArg.emplace_back(args[rawArg++]);
323       status.GetMakefile().ExpandArguments(tmpArg, expArgs);
324     }
325     return true;
326   };
327   auto finishArgs = [&]() {
328     std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
329     status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
330     rawArg = args.size();
331   };
332
333   if (!moreArgs()) {
334     return FatalError(status, "called with incorrect number of arguments");
335   }
336
337   if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) {
338     finishArgs();
339     return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status);
340   }
341
342   cm::optional<Defer> maybeDefer;
343   if (expArgs[expArg] == "DEFER"_s) {
344     ++expArg; // Consume "DEFER".
345
346     if (!moreArgs()) {
347       return FatalError(status, "DEFER requires at least one argument");
348     }
349
350     Defer defer;
351
352     // Process optional arguments.
353     while (moreArgs()) {
354       if (expArgs[expArg] == "CALL"_s) {
355         break;
356       }
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));
364         }
365         finishArgs();
366         return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
367       }
368       if (expArgs[expArg] == "DIRECTORY"_s) {
369         ++expArg; // Consume "DIRECTORY".
370         if (defer.Directory) {
371           return FatalError(status,
372                             "DEFER given multiple DIRECTORY arguments");
373         }
374         if (!moreArgs()) {
375           return FatalError(status, "DEFER DIRECTORY missing value");
376         }
377         std::string dir = expArgs[expArg++];
378         if (dir.empty()) {
379           return FatalError(status, "DEFER DIRECTORY may not be empty");
380         }
381         dir = cmSystemTools::CollapseFullPath(
382           dir, status.GetMakefile().GetCurrentSourceDirectory());
383         defer.Directory =
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));
390         }
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");
395         }
396         if (!moreArgs()) {
397           return FatalError(status, "DEFER ID missing value");
398         }
399         defer.Id = expArgs[expArg++];
400         if (defer.Id.empty()) {
401           return FatalError(status, "DEFER ID may not be empty");
402         }
403         if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
404           return FatalError(status, "DEFER ID may not start in A-Z.");
405         }
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");
410         }
411         if (!moreArgs()) {
412           return FatalError(status, "DEFER ID_VAR missing variable name");
413         }
414         defer.IdVar = expArgs[expArg++];
415         if (defer.IdVar.empty()) {
416           return FatalError(status, "DEFER ID_VAR may not be empty");
417         }
418       } else {
419         return FatalError(
420           status, cmStrCat("DEFER unknown option:\n  "_s, expArgs[expArg]));
421       }
422     }
423
424     if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
425       return FatalError(status, "DEFER must be followed by a CALL argument");
426     }
427
428     maybeDefer = std::move(defer);
429   }
430
431   if (expArgs[expArg] == "CALL") {
432     ++expArg; // Consume "CALL".
433
434     // CALL requires a command name.
435     if (!moreArgs()) {
436       return FatalError(status, "CALL missing command name");
437     }
438     std::string const& callCommand = expArgs[expArg++];
439
440     // CALL accepts no further expanded arguments.
441     if (expArg != expArgs.size()) {
442       return FatalError(status, "CALL command's arguments must be literal");
443     }
444
445     // Run the CALL.
446     return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
447                                       std::move(maybeDefer), status);
448   }
449
450   if (expArgs[expArg] == "EVAL") {
451     return cmCMakeLanguageCommandEVAL(args, status);
452   }
453
454   return FatalError(status, "called with unknown meta-operation");
455 }