ef1248792e0e6849c432c384acb54b13d39bc395
[platform/upstream/cmake.git] / Source / cmMacroCommand.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 "cmMacroCommand.h"
4
5 #include <cstdio>
6 #include <utility>
7
8 #include <cm/memory>
9 #include <cm/string_view>
10 #include <cmext/algorithm>
11 #include <cmext/string_view>
12
13 #include "cmExecutionStatus.h"
14 #include "cmFunctionBlocker.h"
15 #include "cmListFileCache.h"
16 #include "cmMakefile.h"
17 #include "cmPolicies.h"
18 #include "cmRange.h"
19 #include "cmState.h"
20 #include "cmStringAlgorithms.h"
21 #include "cmSystemTools.h"
22
23 namespace {
24
25 // define the class for macro commands
26 class cmMacroHelperCommand
27 {
28 public:
29   /**
30    * This is called when the command is first encountered in
31    * the CMakeLists.txt file.
32    */
33   bool operator()(std::vector<cmListFileArgument> const& args,
34                   cmExecutionStatus& inStatus) const;
35
36   std::vector<std::string> Args;
37   std::vector<cmListFileFunction> Functions;
38   cmPolicies::PolicyMap Policies;
39   std::string FilePath;
40 };
41
42 bool cmMacroHelperCommand::operator()(
43   std::vector<cmListFileArgument> const& args,
44   cmExecutionStatus& inStatus) const
45 {
46   cmMakefile& makefile = inStatus.GetMakefile();
47
48   // Expand the argument list to the macro.
49   std::vector<std::string> expandedArgs;
50   makefile.ExpandArguments(args, expandedArgs);
51
52   // make sure the number of arguments passed is at least the number
53   // required by the signature
54   if (expandedArgs.size() < this->Args.size() - 1) {
55     std::string errorMsg =
56       cmStrCat("Macro invoked with incorrect arguments for macro named: ",
57                this->Args[0]);
58     inStatus.SetError(errorMsg);
59     return false;
60   }
61
62   cmMakefile::MacroPushPop macroScope(&makefile, this->FilePath,
63                                       this->Policies);
64
65   // set the value of argc
66   std::string argcDef = std::to_string(expandedArgs.size());
67
68   auto eit = expandedArgs.begin() + (this->Args.size() - 1);
69   std::string expandedArgn = cmJoin(cmMakeRange(eit, expandedArgs.end()), ";");
70   std::string expandedArgv = cmJoin(expandedArgs, ";");
71   std::vector<std::string> variables;
72   variables.reserve(this->Args.size() - 1);
73   for (unsigned int j = 1; j < this->Args.size(); ++j) {
74     variables.push_back("${" + this->Args[j] + "}");
75   }
76   std::vector<std::string> argVs;
77   argVs.reserve(expandedArgs.size());
78   char argvName[60];
79   for (unsigned int j = 0; j < expandedArgs.size(); ++j) {
80     snprintf(argvName, sizeof(argvName), "${ARGV%u}", j);
81     argVs.emplace_back(argvName);
82   }
83   // Invoke all the functions that were collected in the block.
84   // for each function
85   for (cmListFileFunction const& func : this->Functions) {
86     // Replace the formal arguments and then invoke the command.
87     std::vector<cmListFileArgument> newLFFArgs;
88     newLFFArgs.reserve(func.Arguments().size());
89
90     // for each argument of the current function
91     for (cmListFileArgument const& k : func.Arguments()) {
92       cmListFileArgument arg;
93       arg.Value = k.Value;
94       if (k.Delim != cmListFileArgument::Bracket) {
95         // replace formal arguments
96         for (unsigned int j = 0; j < variables.size(); ++j) {
97           cmSystemTools::ReplaceString(arg.Value, variables[j],
98                                        expandedArgs[j]);
99         }
100         // replace argc
101         cmSystemTools::ReplaceString(arg.Value, "${ARGC}", argcDef);
102
103         cmSystemTools::ReplaceString(arg.Value, "${ARGN}", expandedArgn);
104         cmSystemTools::ReplaceString(arg.Value, "${ARGV}", expandedArgv);
105
106         // if the current argument of the current function has ${ARGV in it
107         // then try replacing ARGV values
108         if (arg.Value.find("${ARGV") != std::string::npos) {
109           for (unsigned int t = 0; t < expandedArgs.size(); ++t) {
110             cmSystemTools::ReplaceString(arg.Value, argVs[t], expandedArgs[t]);
111           }
112         }
113       }
114       arg.Delim = k.Delim;
115       arg.Line = k.Line;
116       newLFFArgs.push_back(std::move(arg));
117     }
118     cmListFileFunction newLFF{ func.OriginalName(), func.Line(),
119                                func.LineEnd(), std::move(newLFFArgs) };
120     cmExecutionStatus status(makefile);
121     if (!makefile.ExecuteCommand(newLFF, status) || status.GetNestedError()) {
122       // The error message should have already included the call stack
123       // so we do not need to report an error here.
124       macroScope.Quiet();
125       inStatus.SetNestedError();
126       return false;
127     }
128     if (status.GetReturnInvoked()) {
129       inStatus.SetReturnInvoked();
130       return true;
131     }
132     if (status.GetBreakInvoked()) {
133       inStatus.SetBreakInvoked();
134       return true;
135     }
136   }
137   return true;
138 }
139
140 class cmMacroFunctionBlocker : public cmFunctionBlocker
141 {
142 public:
143   cm::string_view StartCommandName() const override { return "macro"_s; }
144   cm::string_view EndCommandName() const override { return "endmacro"_s; }
145
146   bool ArgumentsMatch(cmListFileFunction const&,
147                       cmMakefile& mf) const override;
148
149   bool Replay(std::vector<cmListFileFunction> functions,
150               cmExecutionStatus& status) override;
151
152   std::vector<std::string> Args;
153 };
154
155 bool cmMacroFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
156                                             cmMakefile& mf) const
157 {
158   std::vector<std::string> expandedArguments;
159   mf.ExpandArguments(lff.Arguments(), expandedArguments);
160   return expandedArguments.empty() || expandedArguments[0] == this->Args[0];
161 }
162
163 bool cmMacroFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
164                                     cmExecutionStatus& status)
165 {
166   cmMakefile& mf = status.GetMakefile();
167   mf.AppendProperty("MACROS", this->Args[0]);
168   // create a new command and add it to cmake
169   cmMacroHelperCommand f;
170   f.Args = this->Args;
171   f.Functions = std::move(functions);
172   f.FilePath = this->GetStartingContext().FilePath;
173   mf.RecordPolicies(f.Policies);
174   return mf.GetState()->AddScriptedCommand(
175     this->Args[0],
176     BT<cmState::Command>(std::move(f),
177                          mf.GetBacktrace().Push(this->GetStartingContext())),
178     mf);
179 }
180 }
181
182 bool cmMacroCommand(std::vector<std::string> const& args,
183                     cmExecutionStatus& status)
184 {
185   if (args.empty()) {
186     status.SetError("called with incorrect number of arguments");
187     return false;
188   }
189
190   // create a function blocker
191   {
192     auto fb = cm::make_unique<cmMacroFunctionBlocker>();
193     cm::append(fb->Args, args);
194     status.GetMakefile().AddFunctionBlocker(std::move(fb));
195   }
196   return true;
197 }