| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmMacroCommand.h" |
| |
| #include <cstdio> |
| #include <utility> |
| |
| #include <cm/memory> |
| #include <cm/string_view> |
| #include <cmext/algorithm> |
| #include <cmext/string_view> |
| |
| #include "cmExecutionStatus.h" |
| #include "cmFunctionBlocker.h" |
| #include "cmListFileCache.h" |
| #include "cmMakefile.h" |
| #include "cmPolicies.h" |
| #include "cmRange.h" |
| #include "cmState.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| |
| namespace { |
| |
| // define the class for macro commands |
| class cmMacroHelperCommand |
| { |
| public: |
| /** |
| * This is called when the command is first encountered in |
| * the CMakeLists.txt file. |
| */ |
| bool operator()(std::vector<cmListFileArgument> const& args, |
| cmExecutionStatus& inStatus) const; |
| |
| std::vector<std::string> Args; |
| std::vector<cmListFileFunction> Functions; |
| cmPolicies::PolicyMap Policies; |
| std::string FilePath; |
| }; |
| |
| bool cmMacroHelperCommand::operator()( |
| std::vector<cmListFileArgument> const& args, |
| cmExecutionStatus& inStatus) const |
| { |
| cmMakefile& makefile = inStatus.GetMakefile(); |
| |
| // Expand the argument list to the macro. |
| std::vector<std::string> expandedArgs; |
| makefile.ExpandArguments(args, expandedArgs); |
| |
| // make sure the number of arguments passed is at least the number |
| // required by the signature |
| if (expandedArgs.size() < this->Args.size() - 1) { |
| std::string errorMsg = |
| cmStrCat("Macro invoked with incorrect arguments for macro named: ", |
| this->Args[0]); |
| inStatus.SetError(errorMsg); |
| return false; |
| } |
| |
| cmMakefile::MacroPushPop macroScope(&makefile, this->FilePath, |
| this->Policies); |
| |
| // set the value of argc |
| std::string argcDef = std::to_string(expandedArgs.size()); |
| |
| auto eit = expandedArgs.begin() + (this->Args.size() - 1); |
| std::string expandedArgn = cmJoin(cmMakeRange(eit, expandedArgs.end()), ";"); |
| std::string expandedArgv = cmJoin(expandedArgs, ";"); |
| std::vector<std::string> variables; |
| variables.reserve(this->Args.size() - 1); |
| for (unsigned int j = 1; j < this->Args.size(); ++j) { |
| variables.push_back("${" + this->Args[j] + "}"); |
| } |
| std::vector<std::string> argVs; |
| argVs.reserve(expandedArgs.size()); |
| char argvName[60]; |
| for (unsigned int j = 0; j < expandedArgs.size(); ++j) { |
| snprintf(argvName, sizeof(argvName), "${ARGV%u}", j); |
| argVs.emplace_back(argvName); |
| } |
| // Invoke all the functions that were collected in the block. |
| // for each function |
| for (cmListFileFunction const& func : this->Functions) { |
| // Replace the formal arguments and then invoke the command. |
| std::vector<cmListFileArgument> newLFFArgs; |
| newLFFArgs.reserve(func.Arguments().size()); |
| |
| // for each argument of the current function |
| for (cmListFileArgument const& k : func.Arguments()) { |
| cmListFileArgument arg; |
| arg.Value = k.Value; |
| if (k.Delim != cmListFileArgument::Bracket) { |
| // replace formal arguments |
| for (unsigned int j = 0; j < variables.size(); ++j) { |
| cmSystemTools::ReplaceString(arg.Value, variables[j], |
| expandedArgs[j]); |
| } |
| // replace argc |
| cmSystemTools::ReplaceString(arg.Value, "${ARGC}", argcDef); |
| |
| cmSystemTools::ReplaceString(arg.Value, "${ARGN}", expandedArgn); |
| cmSystemTools::ReplaceString(arg.Value, "${ARGV}", expandedArgv); |
| |
| // if the current argument of the current function has ${ARGV in it |
| // then try replacing ARGV values |
| if (arg.Value.find("${ARGV") != std::string::npos) { |
| for (unsigned int t = 0; t < expandedArgs.size(); ++t) { |
| cmSystemTools::ReplaceString(arg.Value, argVs[t], expandedArgs[t]); |
| } |
| } |
| } |
| arg.Delim = k.Delim; |
| arg.Line = k.Line; |
| newLFFArgs.push_back(std::move(arg)); |
| } |
| cmListFileFunction newLFF{ func.OriginalName(), func.Line(), |
| func.LineEnd(), std::move(newLFFArgs) }; |
| cmExecutionStatus status(makefile); |
| if (!makefile.ExecuteCommand(newLFF, status) || status.GetNestedError()) { |
| // The error message should have already included the call stack |
| // so we do not need to report an error here. |
| macroScope.Quiet(); |
| inStatus.SetNestedError(); |
| return false; |
| } |
| if (status.GetReturnInvoked()) { |
| inStatus.SetReturnInvoked(status.GetReturnVariables()); |
| return true; |
| } |
| if (status.GetBreakInvoked()) { |
| inStatus.SetBreakInvoked(); |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| class cmMacroFunctionBlocker : public cmFunctionBlocker |
| { |
| public: |
| cm::string_view StartCommandName() const override { return "macro"_s; } |
| cm::string_view EndCommandName() const override { return "endmacro"_s; } |
| |
| bool ArgumentsMatch(cmListFileFunction const&, |
| cmMakefile& mf) const override; |
| |
| bool Replay(std::vector<cmListFileFunction> functions, |
| cmExecutionStatus& status) override; |
| |
| std::vector<std::string> Args; |
| }; |
| |
| bool cmMacroFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff, |
| cmMakefile& mf) const |
| { |
| std::vector<std::string> expandedArguments; |
| mf.ExpandArguments(lff.Arguments(), expandedArguments); |
| return expandedArguments.empty() || expandedArguments[0] == this->Args[0]; |
| } |
| |
| bool cmMacroFunctionBlocker::Replay(std::vector<cmListFileFunction> functions, |
| cmExecutionStatus& status) |
| { |
| cmMakefile& mf = status.GetMakefile(); |
| mf.AppendProperty("MACROS", this->Args[0]); |
| // create a new command and add it to cmake |
| cmMacroHelperCommand f; |
| f.Args = this->Args; |
| f.Functions = std::move(functions); |
| f.FilePath = this->GetStartingContext().FilePath; |
| mf.RecordPolicies(f.Policies); |
| return mf.GetState()->AddScriptedCommand( |
| this->Args[0], |
| BT<cmState::Command>(std::move(f), |
| mf.GetBacktrace().Push(this->GetStartingContext())), |
| mf); |
| } |
| } |
| |
| bool cmMacroCommand(std::vector<std::string> const& args, |
| cmExecutionStatus& status) |
| { |
| if (args.empty()) { |
| status.SetError("called with incorrect number of arguments"); |
| return false; |
| } |
| |
| // create a function blocker |
| { |
| auto fb = cm::make_unique<cmMacroFunctionBlocker>(); |
| cm::append(fb->Args, args); |
| status.GetMakefile().AddFunctionBlocker(std::move(fb)); |
| } |
| return true; |
| } |