123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file Copyright.txt or https://cmake.org/licensing for details. */
- #include "cmCMakeLanguageCommand.h"
- #include <algorithm>
- #include <array>
- #include <cstddef>
- #include <memory>
- #include <string>
- #include <utility>
- #include <cm/optional>
- #include <cm/string_view>
- #include <cmext/string_view>
- #include "cmExecutionStatus.h"
- #include "cmGlobalGenerator.h"
- #include "cmListFileCache.h"
- #include "cmMakefile.h"
- #include "cmRange.h"
- #include "cmStringAlgorithms.h"
- #include "cmSystemTools.h"
- namespace {
- bool FatalError(cmExecutionStatus& status, std::string const& error)
- {
- status.SetError(error);
- cmSystemTools::SetFatalErrorOccured();
- return false;
- }
- std::array<cm::static_string_view, 12> InvalidCommands{
- { // clang-format off
- "function"_s, "endfunction"_s,
- "macro"_s, "endmacro"_s,
- "if"_s, "elseif"_s, "else"_s, "endif"_s,
- "while"_s, "endwhile"_s,
- "foreach"_s, "endforeach"_s
- } // clang-format on
- };
- std::array<cm::static_string_view, 1> InvalidDeferCommands{
- {
- // clang-format off
- "return"_s,
- } // clang-format on
- };
- struct Defer
- {
- std::string Id;
- std::string IdVar;
- cmMakefile* Directory = nullptr;
- };
- bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
- std::string const& callCommand,
- size_t startArg, cm::optional<Defer> defer,
- cmExecutionStatus& status)
- {
- // ensure specified command is valid
- // start/end flow control commands are not allowed
- auto cmd = cmSystemTools::LowerCase(callCommand);
- if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) !=
- InvalidCommands.cend()) {
- return FatalError(status,
- cmStrCat("invalid command specified: "_s, callCommand));
- }
- if (defer &&
- std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(),
- cmd) != InvalidDeferCommands.cend()) {
- return FatalError(status,
- cmStrCat("invalid command specified: "_s, callCommand));
- }
- cmMakefile& makefile = status.GetMakefile();
- cmListFileContext context = makefile.GetBacktrace().Top();
- cmListFileFunction func;
- func.Name = callCommand;
- func.Line = context.Line;
- // The rest of the arguments are passed to the function call above
- for (size_t i = startArg; i < args.size(); ++i) {
- cmListFileArgument lfarg;
- lfarg.Delim = args[i].Delim;
- lfarg.Line = context.Line;
- lfarg.Value = args[i].Value;
- func.Arguments.emplace_back(lfarg);
- }
- if (defer) {
- if (defer->Id.empty()) {
- defer->Id = makefile.NewDeferId();
- }
- if (!defer->IdVar.empty()) {
- makefile.AddDefinition(defer->IdVar, defer->Id);
- }
- cmMakefile* deferMakefile =
- defer->Directory ? defer->Directory : &makefile;
- if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) {
- return FatalError(
- status,
- cmStrCat("DEFER CALL may not be scheduled in directory:\n "_s,
- deferMakefile->GetCurrentBinaryDirectory(),
- "\nat this time."_s));
- }
- return true;
- }
- return makefile.ExecuteCommand(func, status);
- }
- bool cmCMakeLanguageCommandDEFER(Defer const& defer,
- std::vector<std::string> const& args,
- size_t arg, cmExecutionStatus& status)
- {
- cmMakefile* deferMakefile =
- defer.Directory ? defer.Directory : &status.GetMakefile();
- if (args[arg] == "CANCEL_CALL"_s) {
- ++arg; // Consume CANCEL_CALL.
- auto ids = cmMakeRange(args).advance(arg);
- for (std::string const& id : ids) {
- if (id[0] >= 'A' && id[0] <= 'Z') {
- return FatalError(
- status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n "_s, id));
- }
- if (!deferMakefile->DeferCancelCall(id)) {
- return FatalError(
- status,
- cmStrCat("DEFER CANCEL_CALL may not update directory:\n "_s,
- deferMakefile->GetCurrentBinaryDirectory(),
- "\nat this time."_s));
- }
- }
- return true;
- }
- if (args[arg] == "GET_CALL_IDS"_s) {
- ++arg; // Consume GET_CALL_IDS.
- if (arg == args.size()) {
- return FatalError(status, "DEFER GET_CALL_IDS missing output variable");
- }
- std::string const& var = args[arg++];
- if (arg != args.size()) {
- return FatalError(status, "DEFER GET_CALL_IDS given too many arguments");
- }
- cm::optional<std::string> ids = deferMakefile->DeferGetCallIds();
- if (!ids) {
- return FatalError(
- status,
- cmStrCat("DEFER GET_CALL_IDS may not access directory:\n "_s,
- deferMakefile->GetCurrentBinaryDirectory(),
- "\nat this time."_s));
- }
- status.GetMakefile().AddDefinition(var, *ids);
- return true;
- }
- if (args[arg] == "GET_CALL"_s) {
- ++arg; // Consume GET_CALL.
- if (arg == args.size()) {
- return FatalError(status, "DEFER GET_CALL missing id");
- }
- std::string const& id = args[arg++];
- if (arg == args.size()) {
- return FatalError(status, "DEFER GET_CALL missing output variable");
- }
- std::string const& var = args[arg++];
- if (arg != args.size()) {
- return FatalError(status, "DEFER GET_CALL given too many arguments");
- }
- if (id.empty()) {
- return FatalError(status, "DEFER GET_CALL id may not be empty");
- }
- if (id[0] >= 'A' && id[0] <= 'Z') {
- return FatalError(status,
- cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id));
- }
- cm::optional<std::string> call = deferMakefile->DeferGetCall(id);
- if (!call) {
- return FatalError(
- status,
- cmStrCat("DEFER GET_CALL may not access directory:\n "_s,
- deferMakefile->GetCurrentBinaryDirectory(),
- "\nat this time."_s));
- }
- status.GetMakefile().AddDefinition(var, *call);
- return true;
- }
- return FatalError(status,
- cmStrCat("DEFER operation unknown: "_s, args[arg]));
- }
- bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
- cmExecutionStatus& status)
- {
- cmMakefile& makefile = status.GetMakefile();
- cmListFileContext context = makefile.GetBacktrace().Top();
- std::vector<std::string> expandedArgs;
- makefile.ExpandArguments(args, expandedArgs);
- if (expandedArgs.size() < 2) {
- return FatalError(status, "called with incorrect number of arguments");
- }
- if (expandedArgs[1] != "CODE") {
- auto code_iter =
- std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE");
- if (code_iter == expandedArgs.end()) {
- return FatalError(status, "called without CODE argument");
- }
- return FatalError(
- status,
- "called with unsupported arguments between EVAL and CODE arguments");
- }
- const std::string code =
- cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " ");
- return makefile.ReadListFileAsString(
- code, cmStrCat(context.FilePath, ":", context.Line, ":EVAL"));
- }
- }
- bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
- cmExecutionStatus& status)
- {
- std::vector<std::string> expArgs;
- size_t rawArg = 0;
- size_t expArg = 0;
- // Helper to consume and expand one raw argument at a time.
- auto moreArgs = [&]() -> bool {
- while (expArg >= expArgs.size()) {
- if (rawArg >= args.size()) {
- return false;
- }
- std::vector<cmListFileArgument> tmpArg;
- tmpArg.emplace_back(args[rawArg++]);
- status.GetMakefile().ExpandArguments(tmpArg, expArgs);
- }
- return true;
- };
- auto finishArgs = [&]() {
- std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
- status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
- rawArg = args.size();
- };
- if (!moreArgs()) {
- return FatalError(status, "called with incorrect number of arguments");
- }
- cm::optional<Defer> maybeDefer;
- if (expArgs[expArg] == "DEFER"_s) {
- ++expArg; // Consume "DEFER".
- if (!moreArgs()) {
- return FatalError(status, "DEFER requires at least one argument");
- }
- Defer defer;
- // Process optional arguments.
- while (moreArgs()) {
- if (expArgs[expArg] == "CALL"_s) {
- break;
- }
- if (expArgs[expArg] == "CANCEL_CALL"_s ||
- expArgs[expArg] == "GET_CALL_IDS"_s ||
- expArgs[expArg] == "GET_CALL"_s) {
- if (!defer.Id.empty() || !defer.IdVar.empty()) {
- return FatalError(status,
- cmStrCat("DEFER "_s, expArgs[expArg],
- " does not accept ID or ID_VAR."_s));
- }
- finishArgs();
- return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
- }
- if (expArgs[expArg] == "DIRECTORY"_s) {
- ++expArg; // Consume "DIRECTORY".
- if (defer.Directory) {
- return FatalError(status,
- "DEFER given multiple DIRECTORY arguments");
- }
- if (!moreArgs()) {
- return FatalError(status, "DEFER DIRECTORY missing value");
- }
- std::string dir = expArgs[expArg++];
- if (dir.empty()) {
- return FatalError(status, "DEFER DIRECTORY may not be empty");
- }
- dir = cmSystemTools::CollapseFullPath(
- dir, status.GetMakefile().GetCurrentSourceDirectory());
- defer.Directory =
- status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
- if (!defer.Directory) {
- return FatalError(status,
- cmStrCat("DEFER DIRECTORY:\n "_s, dir,
- "\nis not known. "_s,
- "It may not have been processed yet."_s));
- }
- } else if (expArgs[expArg] == "ID"_s) {
- ++expArg; // Consume "ID".
- if (!defer.Id.empty()) {
- return FatalError(status, "DEFER given multiple ID arguments");
- }
- if (!moreArgs()) {
- return FatalError(status, "DEFER ID missing value");
- }
- defer.Id = expArgs[expArg++];
- if (defer.Id.empty()) {
- return FatalError(status, "DEFER ID may not be empty");
- }
- if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
- return FatalError(status, "DEFER ID may not start in A-Z.");
- }
- } else if (expArgs[expArg] == "ID_VAR"_s) {
- ++expArg; // Consume "ID_VAR".
- if (!defer.IdVar.empty()) {
- return FatalError(status, "DEFER given multiple ID_VAR arguments");
- }
- if (!moreArgs()) {
- return FatalError(status, "DEFER ID_VAR missing variable name");
- }
- defer.IdVar = expArgs[expArg++];
- if (defer.IdVar.empty()) {
- return FatalError(status, "DEFER ID_VAR may not be empty");
- }
- } else {
- return FatalError(
- status, cmStrCat("DEFER unknown option:\n "_s, expArgs[expArg]));
- }
- }
- if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
- return FatalError(status, "DEFER must be followed by a CALL argument");
- }
- maybeDefer = std::move(defer);
- }
- if (expArgs[expArg] == "CALL") {
- ++expArg; // Consume "CALL".
- // CALL requires a command name.
- if (!moreArgs()) {
- return FatalError(status, "CALL missing command name");
- }
- std::string const& callCommand = expArgs[expArg++];
- // CALL accepts no further expanded arguments.
- if (expArg != expArgs.size()) {
- return FatalError(status, "CALL command's arguments must be literal");
- }
- // Run the CALL.
- return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
- std::move(maybeDefer), status);
- }
- if (expArgs[expArg] == "EVAL") {
- return cmCMakeLanguageCommandEVAL(args, status);
- }
- return FatalError(status, "called with unknown meta-operation");
- }
|