diff options
Diffstat (limited to 'Source/cmCMakeLanguageCommand.cxx')
-rw-r--r-- | Source/cmCMakeLanguageCommand.cxx | 378 |
1 files changed, 301 insertions, 77 deletions
diff --git a/Source/cmCMakeLanguageCommand.cxx b/Source/cmCMakeLanguageCommand.cxx index eb9269f7f..789c78d4b 100644 --- a/Source/cmCMakeLanguageCommand.cxx +++ b/Source/cmCMakeLanguageCommand.cxx @@ -7,11 +7,14 @@ #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" @@ -19,6 +22,14 @@ #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, @@ -28,110 +39,323 @@ std::array<cm::static_string_view, 12> InvalidCommands{ "foreach"_s, "endforeach"_s } // clang-format on }; -} -bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args, - cmExecutionStatus& status) +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) { - if (args.empty()) { - status.SetError("called with incorrect number of arguments"); - return false; + // 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.GetExecutionContext(); + cmListFileContext context = makefile.GetBacktrace().Top(); - bool result = false; + std::vector<cmListFileArgument> funcArgs; + funcArgs.reserve(args.size() - startArg); - std::vector<std::string> dispatchExpandedArgs; - std::vector<cmListFileArgument> dispatchArgs; - dispatchArgs.emplace_back(args[0]); - makefile.ExpandArguments(dispatchArgs, dispatchExpandedArgs); + // The rest of the arguments are passed to the function call above + for (size_t i = startArg; i < args.size(); ++i) { + funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line); + } + cmListFileFunction func{ callCommand, context.Line, std::move(funcArgs) }; - if (dispatchExpandedArgs.empty()) { - status.SetError("called with incorrect number of arguments"); - return false; + 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); +} - if (dispatchExpandedArgs[0] == "CALL") { - if ((args.size() == 1 && dispatchExpandedArgs.size() != 2) || - dispatchExpandedArgs.size() > 2) { - status.SetError("called with incorrect number of arguments"); - return false; +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])); +} - // First argument is the name of the function to call - std::string callCommand; - size_t startArg; - if (dispatchExpandedArgs.size() == 1) { - std::vector<std::string> functionExpandedArg; - std::vector<cmListFileArgument> functionArg; - functionArg.emplace_back(args[1]); - makefile.ExpandArguments(functionArg, functionExpandedArg); +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 (functionExpandedArg.size() != 1) { - status.SetError("called with incorrect number of arguments"); - return false; - } + if (expandedArgs.size() < 2) { + return FatalError(status, "called with incorrect number of arguments"); + } - callCommand = functionExpandedArg[0]; - startArg = 2; - } else { - callCommand = dispatchExpandedArgs[1]; - startArg = 1; + 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"); + } - // 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()) { - status.SetError(cmStrCat("invalid command specified: "_s, callCommand)); - return false; - } + const std::string code = + cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " "); + return makefile.ReadListFileAsString( + code, cmStrCat(context.FilePath, ":", context.Line, ":EVAL")); +} +} - cmListFileFunction func; - func.Name = callCommand; - func.Line = context.Line; +bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args, + cmExecutionStatus& status) +{ + std::vector<std::string> expArgs; + size_t rawArg = 0; + size_t expArg = 0; - // 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); + // 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(); + }; - result = makefile.ExecuteCommand(func, status); - } else if (dispatchExpandedArgs[0] == "EVAL") { - std::vector<std::string> expandedArgs; - makefile.ExpandArguments(args, expandedArgs); + if (!moreArgs()) { + return FatalError(status, "called with incorrect number of arguments"); + } + + cm::optional<Defer> maybeDefer; + if (expArgs[expArg] == "DEFER"_s) { + ++expArg; // Consume "DEFER". - if (expandedArgs.size() < 2) { - status.SetError("called with incorrect number of arguments"); - return false; + if (!moreArgs()) { + return FatalError(status, "DEFER requires at least one argument"); } - if (expandedArgs[1] != "CODE") { - auto code_iter = - std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE"); - if (code_iter == expandedArgs.end()) { - status.SetError("called without CODE 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 { - status.SetError( - "called with unsupported arguments between EVAL and CODE arguments"); + return FatalError( + status, cmStrCat("DEFER unknown option:\n "_s, expArgs[expArg])); } - return false; } - const std::string code = - cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " "); - result = makefile.ReadListFileAsString( - code, cmStrCat(context.FilePath, ":", context.Line, ":EVAL")); - } else { - status.SetError("called with unknown meta-operation"); + 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 result; + return FatalError(status, "called with unknown meta-operation"); } |