95f3e7e162b6a6160115b22c282eca837bcfc678
[platform/upstream/cmake.git] / Source / cmParseArgumentsCommand.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 "cmParseArgumentsCommand.h"
4
5 #include <map>
6 #include <set>
7 #include <sstream>
8 #include <utility>
9
10 #include <cm/string_view>
11
12 #include "cmArgumentParser.h"
13 #include "cmExecutionStatus.h"
14 #include "cmMakefile.h"
15 #include "cmMessageType.h"
16 #include "cmRange.h"
17 #include "cmStringAlgorithms.h"
18 #include "cmSystemTools.h"
19 #include "cmValue.h"
20
21 static std::string EscapeArg(const std::string& arg)
22 {
23   // replace ";" with "\;" so output argument lists will split correctly
24   std::string escapedArg;
25   for (char i : arg) {
26     if (i == ';') {
27       escapedArg += '\\';
28     }
29     escapedArg += i;
30   }
31   return escapedArg;
32 }
33
34 static std::string JoinList(std::vector<std::string> const& arg, bool escape)
35 {
36   return escape ? cmJoin(cmMakeRange(arg).transform(EscapeArg), ";")
37                 : cmJoin(cmMakeRange(arg), ";");
38 }
39
40 namespace {
41
42 using options_map = std::map<std::string, bool>;
43 using single_map = std::map<std::string, std::string>;
44 using multi_map = std::map<std::string, std::vector<std::string>>;
45 using options_set = std::set<std::string>;
46
47 struct UserArgumentParser : public cmArgumentParser<void>
48 {
49   template <typename T, typename H>
50   void Bind(std::vector<std::string> const& names,
51             std::map<std::string, T>& ref, H duplicateKey)
52   {
53     for (std::string const& key : names) {
54       auto const it = ref.emplace(key, T{}).first;
55       bool const inserted = this->cmArgumentParser<void>::Bind(
56         cm::string_view(it->first), it->second);
57       if (!inserted) {
58         duplicateKey(key);
59       }
60     }
61   }
62 };
63
64 } // namespace
65
66 static void PassParsedArguments(
67   const std::string& prefix, cmMakefile& makefile, const options_map& options,
68   const single_map& singleValArgs, const multi_map& multiValArgs,
69   const std::vector<std::string>& unparsed,
70   const options_set& keywordsMissingValues, bool parseFromArgV)
71 {
72   for (auto const& iter : options) {
73     makefile.AddDefinition(prefix + iter.first,
74                            iter.second ? "TRUE" : "FALSE");
75   }
76
77   for (auto const& iter : singleValArgs) {
78     if (!iter.second.empty()) {
79       makefile.AddDefinition(prefix + iter.first, iter.second);
80     } else {
81       makefile.RemoveDefinition(prefix + iter.first);
82     }
83   }
84
85   for (auto const& iter : multiValArgs) {
86     if (!iter.second.empty()) {
87       makefile.AddDefinition(prefix + iter.first,
88                              JoinList(iter.second, parseFromArgV));
89     } else {
90       makefile.RemoveDefinition(prefix + iter.first);
91     }
92   }
93
94   if (!unparsed.empty()) {
95     makefile.AddDefinition(prefix + "UNPARSED_ARGUMENTS",
96                            JoinList(unparsed, parseFromArgV));
97   } else {
98     makefile.RemoveDefinition(prefix + "UNPARSED_ARGUMENTS");
99   }
100
101   if (!keywordsMissingValues.empty()) {
102     makefile.AddDefinition(prefix + "KEYWORDS_MISSING_VALUES",
103                            cmJoin(cmMakeRange(keywordsMissingValues), ";"));
104   } else {
105     makefile.RemoveDefinition(prefix + "KEYWORDS_MISSING_VALUES");
106   }
107 }
108
109 bool cmParseArgumentsCommand(std::vector<std::string> const& args,
110                              cmExecutionStatus& status)
111 {
112   // cmake_parse_arguments(prefix options single multi <ARGN>)
113   //                         1       2      3      4
114   // or
115   // cmake_parse_arguments(PARSE_ARGV N prefix options single multi)
116   if (args.size() < 4) {
117     status.SetError("must be called with at least 4 arguments.");
118     return false;
119   }
120
121   auto argIter = args.begin();
122   auto argEnd = args.end();
123   bool parseFromArgV = false;
124   unsigned long argvStart = 0;
125   if (*argIter == "PARSE_ARGV") {
126     if (args.size() != 6) {
127       status.GetMakefile().IssueMessage(
128         MessageType::FATAL_ERROR,
129         "PARSE_ARGV must be called with exactly 6 arguments.");
130       cmSystemTools::SetFatalErrorOccurred();
131       return true;
132     }
133     parseFromArgV = true;
134     argIter++; // move past PARSE_ARGV
135     if (!cmStrToULong(*argIter, &argvStart)) {
136       status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR,
137                                         "PARSE_ARGV index '" + *argIter +
138                                           "' is not an unsigned integer");
139       cmSystemTools::SetFatalErrorOccurred();
140       return true;
141     }
142     argIter++; // move past N
143   }
144   // the first argument is the prefix
145   const std::string prefix = (*argIter++) + "_";
146
147   UserArgumentParser parser;
148
149   // define the result maps holding key/value pairs for
150   // options, single values and multi values
151   options_map options;
152   single_map singleValArgs;
153   multi_map multiValArgs;
154
155   // anything else is put into a vector of unparsed strings
156   std::vector<std::string> unparsed;
157
158   auto const duplicateKey = [&status](std::string const& key) {
159     status.GetMakefile().IssueMessage(
160       MessageType::WARNING, "keyword defined more than once: " + key);
161   };
162
163   // the second argument is a (cmake) list of options without argument
164   std::vector<std::string> list = cmExpandedList(*argIter++);
165   parser.Bind(list, options, duplicateKey);
166
167   // the third argument is a (cmake) list of single argument options
168   list.clear();
169   cmExpandList(*argIter++, list);
170   parser.Bind(list, singleValArgs, duplicateKey);
171
172   // the fourth argument is a (cmake) list of multi argument options
173   list.clear();
174   cmExpandList(*argIter++, list);
175   parser.Bind(list, multiValArgs, duplicateKey);
176
177   list.clear();
178   if (!parseFromArgV) {
179     // Flatten ;-lists in the arguments into a single list as was done
180     // by the original function(CMAKE_PARSE_ARGUMENTS).
181     for (; argIter != argEnd; ++argIter) {
182       cmExpandList(*argIter, list);
183     }
184   } else {
185     // in the PARSE_ARGV move read the arguments from ARGC and ARGV#
186     std::string argc = status.GetMakefile().GetSafeDefinition("ARGC");
187     unsigned long count;
188     if (!cmStrToULong(argc, &count)) {
189       status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR,
190                                         "PARSE_ARGV called with ARGC='" +
191                                           argc +
192                                           "' that is not an unsigned integer");
193       cmSystemTools::SetFatalErrorOccurred();
194       return true;
195     }
196     for (unsigned long i = argvStart; i < count; ++i) {
197       std::ostringstream argName;
198       argName << "ARGV" << i;
199       cmValue arg = status.GetMakefile().GetDefinition(argName.str());
200       if (!arg) {
201         status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR,
202                                           "PARSE_ARGV called with " +
203                                             argName.str() + " not set");
204         cmSystemTools::SetFatalErrorOccurred();
205         return true;
206       }
207       list.emplace_back(*arg);
208     }
209   }
210
211   std::vector<std::string> keywordsMissingValues;
212
213   parser.Parse(list, &unparsed, &keywordsMissingValues);
214
215   PassParsedArguments(
216     prefix, status.GetMakefile(), options, singleValArgs, multiValArgs,
217     unparsed,
218     options_set(keywordsMissingValues.begin(), keywordsMissingValues.end()),
219     parseFromArgV);
220
221   return true;
222 }