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