| /* 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) { | |
| sprintf(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(), | |
| 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(); | |
| 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; | |
| } |