resolve cyclic dependency with zstd
[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 "cmArgumentParserTypes.h"
18 #include "cmDependencyProvider.h"
19 #include "cmExecutionStatus.h"
20 #include "cmGlobalGenerator.h"
21 #include "cmListFileCache.h"
22 #include "cmMakefile.h"
23 #include "cmMessageType.h"
24 #include "cmRange.h"
25 #include "cmState.h"
26 #include "cmStringAlgorithms.h"
27 #include "cmSystemTools.h"
28 #include "cmake.h"
29
30 namespace {
31
32 bool FatalError(cmExecutionStatus& status, std::string const& error)
33 {
34   status.SetError(error);
35   cmSystemTools::SetFatalErrorOccurred();
36   return false;
37 }
38
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
48 };
49
50 std::array<cm::static_string_view, 1> InvalidDeferCommands{
51   {
52     // clang-format off
53   "return"_s,
54   } // clang-format on
55 };
56
57 struct Defer
58 {
59   std::string Id;
60   std::string IdVar;
61   cmMakefile* Directory = nullptr;
62 };
63
64 bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
65                                 std::string const& callCommand,
66                                 size_t startArg, cm::optional<Defer> defer,
67                                 cmExecutionStatus& status)
68 {
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));
76   }
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));
82   }
83
84   cmMakefile& makefile = status.GetMakefile();
85   cmListFileContext context = makefile.GetBacktrace().Top();
86
87   std::vector<cmListFileArgument> funcArgs;
88   funcArgs.reserve(args.size() - startArg);
89
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);
93   }
94   cmListFileFunction func{ callCommand, context.Line, context.Line,
95                            std::move(funcArgs) };
96
97   if (defer) {
98     if (defer->Id.empty()) {
99       defer->Id = makefile.NewDeferId();
100     }
101     if (!defer->IdVar.empty()) {
102       makefile.AddDefinition(defer->IdVar, defer->Id);
103     }
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));
112     }
113     return true;
114   }
115   return makefile.ExecuteCommand(func, status);
116 }
117
118 bool cmCMakeLanguageCommandDEFER(Defer const& defer,
119                                  std::vector<std::string> const& args,
120                                  size_t arg, cmExecutionStatus& status)
121 {
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));
131       }
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));
138       }
139     }
140     return true;
141   }
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");
146     }
147     std::string const& var = args[arg++];
148     if (arg != args.size()) {
149       return FatalError(status, "DEFER GET_CALL_IDS given too many arguments");
150     }
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));
158     }
159     status.GetMakefile().AddDefinition(var, *ids);
160     return true;
161   }
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");
166     }
167     std::string const& id = args[arg++];
168     if (arg == args.size()) {
169       return FatalError(status, "DEFER GET_CALL missing output variable");
170     }
171     std::string const& var = args[arg++];
172     if (arg != args.size()) {
173       return FatalError(status, "DEFER GET_CALL given too many arguments");
174     }
175     if (id.empty()) {
176       return FatalError(status, "DEFER GET_CALL id may not be empty");
177     }
178     if (id[0] >= 'A' && id[0] <= 'Z') {
179       return FatalError(status,
180                         cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id));
181     }
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));
189     }
190     status.GetMakefile().AddDefinition(var, *call);
191     return true;
192   }
193   return FatalError(status,
194                     cmStrCat("DEFER operation unknown: "_s, args[arg]));
195 }
196
197 bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
198                                 cmExecutionStatus& status)
199 {
200   cmMakefile& makefile = status.GetMakefile();
201   cmListFileContext context = makefile.GetBacktrace().Top();
202   std::vector<std::string> expandedArgs;
203   makefile.ExpandArguments(args, expandedArgs);
204
205   if (expandedArgs.size() < 2) {
206     return FatalError(status, "called with incorrect number of arguments");
207   }
208
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");
214     }
215     return FatalError(
216       status,
217       "called with unsupported arguments between EVAL and CODE arguments");
218   }
219
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"));
224 }
225
226 bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(
227   std::vector<std::string> const& args, cmExecutionStatus& status)
228 {
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.");
237   }
238
239   struct SetProviderArgs
240   {
241     std::string Command;
242     ArgumentParser::NonEmpty<std::vector<std::string>> Methods;
243   };
244
245   auto const ArgsParser =
246     cmArgumentParser<SetProviderArgs>()
247       .Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command)
248       .Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods);
249
250   std::vector<std::string> unparsed;
251   auto parsedArgs = ArgsParser.Parse(args, &unparsed);
252
253   if (!unparsed.empty()) {
254     return FatalError(
255       status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), "\""));
256   }
257
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";
264
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");
270     }
271     state->ClearDependencyProvider();
272     state->SetGlobalProperty(fcmasProperty, "");
273     return true;
274   }
275
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"));
281   }
282
283   if (parsedArgs.Methods.empty()) {
284     return FatalError(status, "Must specify at least one provider method");
285   }
286
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, "\""));
300     }
301   }
302
303   state->SetDependencyProvider({ parsedArgs.Command, methods });
304   state->SetGlobalProperty(
305     fcmasProperty,
306     supportsFetchContentMakeAvailableSerial ? parsedArgs.Command.c_str() : "");
307
308   return true;
309 }
310
311 bool cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(
312   std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
313 {
314   cmMakefile& makefile = status.GetMakefile();
315   std::vector<std::string> expandedArgs;
316   makefile.ExpandArguments(args, expandedArgs);
317
318   if (args.size() < 2 || expandedArgs.size() > 2) {
319     return FatalError(
320       status,
321       "sub-command GET_MESSAGE_LOG_LEVEL expects exactly one argument");
322   }
323
324   Message::LogLevel logLevel = makefile.GetCurrentLogLevel();
325   std::string outputValue = cmake::LogLevelToString(logLevel);
326
327   const std::string& outputVariable = expandedArgs[1];
328   makefile.AddDefinition(outputVariable, outputValue);
329   return true;
330 }
331 }
332
333 bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
334                             cmExecutionStatus& status)
335 {
336   std::vector<std::string> expArgs;
337   size_t rawArg = 0;
338   size_t expArg = 0;
339
340   // Helper to consume and expand one raw argument at a time.
341   auto moreArgs = [&]() -> bool {
342     while (expArg >= expArgs.size()) {
343       if (rawArg >= args.size()) {
344         return false;
345       }
346       std::vector<cmListFileArgument> tmpArg;
347       tmpArg.emplace_back(args[rawArg++]);
348       status.GetMakefile().ExpandArguments(tmpArg, expArgs);
349     }
350     return true;
351   };
352   auto finishArgs = [&]() {
353     std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
354     status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
355     rawArg = args.size();
356   };
357
358   if (!moreArgs()) {
359     return FatalError(status, "called with incorrect number of arguments");
360   }
361
362   if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) {
363     finishArgs();
364     return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status);
365   }
366
367   cm::optional<Defer> maybeDefer;
368   if (expArgs[expArg] == "DEFER"_s) {
369     ++expArg; // Consume "DEFER".
370
371     if (!moreArgs()) {
372       return FatalError(status, "DEFER requires at least one argument");
373     }
374
375     Defer defer;
376
377     // Process optional arguments.
378     while (moreArgs()) {
379       if (expArgs[expArg] == "CALL"_s) {
380         break;
381       }
382       if (expArgs[expArg] == "CANCEL_CALL"_s ||
383           expArgs[expArg] == "GET_CALL_IDS"_s ||
384           expArgs[expArg] == "GET_CALL"_s) {
385         if (!defer.Id.empty() || !defer.IdVar.empty()) {
386           return FatalError(status,
387                             cmStrCat("DEFER "_s, expArgs[expArg],
388                                      " does not accept ID or ID_VAR."_s));
389         }
390         finishArgs();
391         return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
392       }
393       if (expArgs[expArg] == "DIRECTORY"_s) {
394         ++expArg; // Consume "DIRECTORY".
395         if (defer.Directory) {
396           return FatalError(status,
397                             "DEFER given multiple DIRECTORY arguments");
398         }
399         if (!moreArgs()) {
400           return FatalError(status, "DEFER DIRECTORY missing value");
401         }
402         std::string dir = expArgs[expArg++];
403         if (dir.empty()) {
404           return FatalError(status, "DEFER DIRECTORY may not be empty");
405         }
406         dir = cmSystemTools::CollapseFullPath(
407           dir, status.GetMakefile().GetCurrentSourceDirectory());
408         defer.Directory =
409           status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
410         if (!defer.Directory) {
411           return FatalError(status,
412                             cmStrCat("DEFER DIRECTORY:\n  "_s, dir,
413                                      "\nis not known.  "_s,
414                                      "It may not have been processed yet."_s));
415         }
416       } else if (expArgs[expArg] == "ID"_s) {
417         ++expArg; // Consume "ID".
418         if (!defer.Id.empty()) {
419           return FatalError(status, "DEFER given multiple ID arguments");
420         }
421         if (!moreArgs()) {
422           return FatalError(status, "DEFER ID missing value");
423         }
424         defer.Id = expArgs[expArg++];
425         if (defer.Id.empty()) {
426           return FatalError(status, "DEFER ID may not be empty");
427         }
428         if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
429           return FatalError(status, "DEFER ID may not start in A-Z.");
430         }
431       } else if (expArgs[expArg] == "ID_VAR"_s) {
432         ++expArg; // Consume "ID_VAR".
433         if (!defer.IdVar.empty()) {
434           return FatalError(status, "DEFER given multiple ID_VAR arguments");
435         }
436         if (!moreArgs()) {
437           return FatalError(status, "DEFER ID_VAR missing variable name");
438         }
439         defer.IdVar = expArgs[expArg++];
440         if (defer.IdVar.empty()) {
441           return FatalError(status, "DEFER ID_VAR may not be empty");
442         }
443       } else {
444         return FatalError(
445           status, cmStrCat("DEFER unknown option:\n  "_s, expArgs[expArg]));
446       }
447     }
448
449     if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
450       return FatalError(status, "DEFER must be followed by a CALL argument");
451     }
452
453     maybeDefer = std::move(defer);
454   }
455
456   if (expArgs[expArg] == "CALL") {
457     ++expArg; // Consume "CALL".
458
459     // CALL requires a command name.
460     if (!moreArgs()) {
461       return FatalError(status, "CALL missing command name");
462     }
463     std::string const& callCommand = expArgs[expArg++];
464
465     // CALL accepts no further expanded arguments.
466     if (expArg != expArgs.size()) {
467       return FatalError(status, "CALL command's arguments must be literal");
468     }
469
470     // Run the CALL.
471     return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
472                                       std::move(maybeDefer), status);
473   }
474
475   if (expArgs[expArg] == "EVAL") {
476     return cmCMakeLanguageCommandEVAL(args, status);
477   }
478
479   if (expArgs[expArg] == "GET_MESSAGE_LOG_LEVEL") {
480     return cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(args, status);
481   }
482
483   return FatalError(status, "called with unknown meta-operation");
484 }