/src/CMake/Source/cmParseArgumentsCommand.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | #include "cmParseArgumentsCommand.h" |
4 | | |
5 | | #include <map> |
6 | | #include <set> |
7 | | #include <utility> |
8 | | |
9 | | #include <cm/string_view> |
10 | | |
11 | | #include "cmArgumentParser.h" |
12 | | #include "cmArgumentParserTypes.h" |
13 | | #include "cmExecutionStatus.h" |
14 | | #include "cmList.h" |
15 | | #include "cmMakefile.h" |
16 | | #include "cmMessageType.h" |
17 | | #include "cmPolicies.h" |
18 | | #include "cmRange.h" |
19 | | #include "cmStringAlgorithms.h" |
20 | | #include "cmSystemTools.h" |
21 | | #include "cmValue.h" |
22 | | |
23 | | namespace { |
24 | | |
25 | | std::string EscapeArg(std::string const& arg) |
26 | 0 | { |
27 | | // replace ";" with "\;" so output argument lists will split correctly |
28 | 0 | std::string escapedArg; |
29 | 0 | for (char i : arg) { |
30 | 0 | if (i == ';') { |
31 | 0 | escapedArg += '\\'; |
32 | 0 | } |
33 | 0 | escapedArg += i; |
34 | 0 | } |
35 | 0 | return escapedArg; |
36 | 0 | } |
37 | | |
38 | | std::string JoinList(std::vector<std::string> const& arg, bool escape) |
39 | 0 | { |
40 | 0 | return escape ? cmList::to_string(cmMakeRange(arg).transform(EscapeArg)) |
41 | 0 | : cmList::to_string(cmMakeRange(arg)); |
42 | 0 | } |
43 | | |
44 | | using options_map = std::map<std::string, bool>; |
45 | | using single_map = std::map<std::string, std::string>; |
46 | | using multi_map = |
47 | | std::map<std::string, ArgumentParser::NonEmpty<std::vector<std::string>>>; |
48 | | using options_set = std::set<cm::string_view>; |
49 | | |
50 | | struct UserArgumentParser : public cmArgumentParser<void> |
51 | | { |
52 | | void BindKeywordsMissingValue(std::vector<cm::string_view>& ref) |
53 | 0 | { |
54 | 0 | this->cmArgumentParser<void>::BindKeywordMissingValue( |
55 | 0 | [&ref](Instance&, cm::string_view arg) { ref.emplace_back(arg); }); |
56 | 0 | } |
57 | | |
58 | | template <typename T, typename H> |
59 | | void Bind(std::vector<std::string> const& names, |
60 | | std::map<std::string, T>& ref, H duplicateKey) |
61 | 0 | { |
62 | 0 | for (std::string const& key : names) { |
63 | 0 | auto const it = ref.emplace(key, T{}).first; |
64 | 0 | bool const inserted = this->cmArgumentParser<void>::Bind( |
65 | 0 | cm::string_view(it->first), it->second); |
66 | 0 | if (!inserted) { |
67 | 0 | duplicateKey(key); |
68 | 0 | } |
69 | 0 | } |
70 | 0 | } Unexecuted instantiation: cmParseArgumentsCommand.cxx:void (anonymous namespace)::UserArgumentParser::Bind<bool, cmParseArgumentsCommand(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, cmExecutionStatus&)::$_0>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, bool> > >&, cmParseArgumentsCommand(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, cmExecutionStatus&)::$_0) Unexecuted instantiation: cmParseArgumentsCommand.cxx:void (anonymous namespace)::UserArgumentParser::Bind<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, cmParseArgumentsCommand(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, cmExecutionStatus&)::$_0>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > >&, cmParseArgumentsCommand(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, cmExecutionStatus&)::$_0) Unexecuted instantiation: cmParseArgumentsCommand.cxx:void (anonymous namespace)::UserArgumentParser::Bind<ArgumentParser::NonEmpty<std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > >, cmParseArgumentsCommand(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, cmExecutionStatus&)::$_0>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, ArgumentParser::NonEmpty<std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > >, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, ArgumentParser::NonEmpty<std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > > > >&, cmParseArgumentsCommand(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, cmExecutionStatus&)::$_0) |
71 | | }; |
72 | | |
73 | | } // namespace |
74 | | |
75 | | static void PassParsedArguments( |
76 | | std::string const& prefix, cmMakefile& makefile, options_map const& options, |
77 | | single_map const& singleValArgs, multi_map const& multiValArgs, |
78 | | std::vector<std::string> const& unparsed, options_set const& keywordsSeen, |
79 | | options_set const& keywordsMissingValues, bool parseFromArgV) |
80 | 0 | { |
81 | 0 | for (auto const& iter : options) { |
82 | 0 | makefile.AddDefinition(cmStrCat(prefix, iter.first), |
83 | 0 | iter.second ? "TRUE" : "FALSE"); |
84 | 0 | } |
85 | |
|
86 | 0 | cmPolicies::PolicyStatus const cmp0174 = |
87 | 0 | makefile.GetPolicyStatus(cmPolicies::CMP0174); |
88 | 0 | for (auto const& iter : singleValArgs) { |
89 | 0 | if (keywordsSeen.find(iter.first) == keywordsSeen.end()) { |
90 | 0 | makefile.RemoveDefinition(cmStrCat(prefix, iter.first)); |
91 | 0 | } else if ((parseFromArgV && cmp0174 == cmPolicies::NEW) || |
92 | 0 | !iter.second.empty()) { |
93 | 0 | makefile.AddDefinition(cmStrCat(prefix, iter.first), iter.second); |
94 | 0 | } else { |
95 | | // The OLD policy behavior doesn't define a variable for an empty or |
96 | | // missing value, and we can't differentiate between those two cases. |
97 | 0 | if (parseFromArgV && (cmp0174 == cmPolicies::WARN)) { |
98 | 0 | makefile.IssueMessage( |
99 | 0 | MessageType::AUTHOR_WARNING, |
100 | 0 | cmStrCat("The ", iter.first, |
101 | 0 | " keyword was followed by an empty string or no value at " |
102 | 0 | "all. Policy CMP0174 is not set, so " |
103 | 0 | "cmake_parse_arguments() will unset the ", |
104 | 0 | prefix, iter.first, |
105 | 0 | " variable rather than setting it to an empty string.")); |
106 | 0 | } |
107 | 0 | makefile.RemoveDefinition(cmStrCat(prefix, iter.first)); |
108 | 0 | } |
109 | 0 | } |
110 | |
|
111 | 0 | for (auto const& iter : multiValArgs) { |
112 | 0 | if (!iter.second.empty()) { |
113 | 0 | makefile.AddDefinition(cmStrCat(prefix, iter.first), |
114 | 0 | JoinList(iter.second, parseFromArgV)); |
115 | 0 | } else { |
116 | 0 | makefile.RemoveDefinition(cmStrCat(prefix, iter.first)); |
117 | 0 | } |
118 | 0 | } |
119 | |
|
120 | 0 | if (!unparsed.empty()) { |
121 | 0 | makefile.AddDefinition(cmStrCat(prefix, "UNPARSED_ARGUMENTS"), |
122 | 0 | JoinList(unparsed, parseFromArgV)); |
123 | 0 | } else { |
124 | 0 | makefile.RemoveDefinition(cmStrCat(prefix, "UNPARSED_ARGUMENTS")); |
125 | 0 | } |
126 | |
|
127 | 0 | if (!keywordsMissingValues.empty()) { |
128 | 0 | makefile.AddDefinition( |
129 | 0 | cmStrCat(prefix, "KEYWORDS_MISSING_VALUES"), |
130 | 0 | cmList::to_string(cmMakeRange(keywordsMissingValues))); |
131 | 0 | } else { |
132 | 0 | makefile.RemoveDefinition(cmStrCat(prefix, "KEYWORDS_MISSING_VALUES")); |
133 | 0 | } |
134 | 0 | } |
135 | | |
136 | | bool cmParseArgumentsCommand(std::vector<std::string> const& args, |
137 | | cmExecutionStatus& status) |
138 | 0 | { |
139 | | // cmake_parse_arguments(prefix options single multi <ARGN>) |
140 | | // 1 2 3 4 |
141 | | // or |
142 | | // cmake_parse_arguments(PARSE_ARGV N prefix options single multi) |
143 | 0 | if (args.size() < 4) { |
144 | 0 | status.SetError("must be called with at least 4 arguments."); |
145 | 0 | return false; |
146 | 0 | } |
147 | | |
148 | 0 | auto argIter = args.begin(); |
149 | 0 | auto argEnd = args.end(); |
150 | 0 | bool parseFromArgV = false; |
151 | 0 | unsigned long argvStart = 0; |
152 | 0 | if (*argIter == "PARSE_ARGV") { |
153 | 0 | if (args.size() != 6) { |
154 | 0 | status.GetMakefile().IssueMessage( |
155 | 0 | MessageType::FATAL_ERROR, |
156 | 0 | "PARSE_ARGV must be called with exactly 6 arguments."); |
157 | 0 | cmSystemTools::SetFatalErrorOccurred(); |
158 | 0 | return true; |
159 | 0 | } |
160 | 0 | parseFromArgV = true; |
161 | 0 | argIter++; // move past PARSE_ARGV |
162 | 0 | if (!cmStrToULong(*argIter, &argvStart)) { |
163 | 0 | status.GetMakefile().IssueMessage( |
164 | 0 | MessageType::FATAL_ERROR, |
165 | 0 | cmStrCat("PARSE_ARGV index '", *argIter, |
166 | 0 | "' is not an unsigned integer")); |
167 | 0 | cmSystemTools::SetFatalErrorOccurred(); |
168 | 0 | return true; |
169 | 0 | } |
170 | 0 | argIter++; // move past N |
171 | 0 | } |
172 | | // the first argument is the prefix |
173 | 0 | std::string const prefix = (*argIter++) + "_"; |
174 | |
|
175 | 0 | UserArgumentParser parser; |
176 | | |
177 | | // define the result maps holding key/value pairs for |
178 | | // options, single values and multi values |
179 | 0 | options_map options; |
180 | 0 | single_map singleValArgs; |
181 | 0 | multi_map multiValArgs; |
182 | | |
183 | | // anything else is put into a vector of unparsed strings |
184 | 0 | std::vector<std::string> unparsed; |
185 | |
|
186 | 0 | auto const duplicateKey = [&status](std::string const& key) { |
187 | 0 | status.GetMakefile().IssueMessage( |
188 | 0 | MessageType::WARNING, cmStrCat("keyword defined more than once: ", key)); |
189 | 0 | }; |
190 | | |
191 | | // the second argument is a (cmake) list of options without argument |
192 | 0 | cmList list{ *argIter++ }; |
193 | 0 | parser.Bind(list, options, duplicateKey); |
194 | | |
195 | | // the third argument is a (cmake) list of single argument options |
196 | 0 | list.assign(*argIter++); |
197 | 0 | parser.Bind(list, singleValArgs, duplicateKey); |
198 | | |
199 | | // the fourth argument is a (cmake) list of multi argument options |
200 | 0 | list.assign(*argIter++); |
201 | 0 | parser.Bind(list, multiValArgs, duplicateKey); |
202 | |
|
203 | 0 | list.clear(); |
204 | 0 | if (!parseFromArgV) { |
205 | | // Flatten ;-lists in the arguments into a single list as was done |
206 | | // by the original function(CMAKE_PARSE_ARGUMENTS). |
207 | 0 | for (; argIter != argEnd; ++argIter) { |
208 | 0 | list.append(*argIter); |
209 | 0 | } |
210 | 0 | } else { |
211 | | // in the PARSE_ARGV move read the arguments from ARGC and ARGV# |
212 | 0 | std::string argc = status.GetMakefile().GetSafeDefinition("ARGC"); |
213 | 0 | unsigned long count; |
214 | 0 | if (!cmStrToULong(argc, &count)) { |
215 | 0 | status.GetMakefile().IssueMessage( |
216 | 0 | MessageType::FATAL_ERROR, |
217 | 0 | cmStrCat("PARSE_ARGV called with ARGC='", argc, |
218 | 0 | "' that is not an unsigned integer")); |
219 | 0 | cmSystemTools::SetFatalErrorOccurred(); |
220 | 0 | return true; |
221 | 0 | } |
222 | 0 | for (unsigned long i = argvStart; i < count; ++i) { |
223 | 0 | std::string const argName{ cmStrCat("ARGV", i) }; |
224 | 0 | cmValue arg = status.GetMakefile().GetDefinition(argName); |
225 | 0 | if (!arg) { |
226 | 0 | status.GetMakefile().IssueMessage( |
227 | 0 | MessageType::FATAL_ERROR, |
228 | 0 | cmStrCat("PARSE_ARGV called with ", argName, " not set")); |
229 | 0 | cmSystemTools::SetFatalErrorOccurred(); |
230 | 0 | return true; |
231 | 0 | } |
232 | 0 | list.emplace_back(*arg); |
233 | 0 | } |
234 | 0 | } |
235 | | |
236 | 0 | std::vector<cm::string_view> keywordsSeen; |
237 | 0 | parser.BindParsedKeywords(keywordsSeen); |
238 | | |
239 | | // For single-value keywords, only the last instance matters, since it |
240 | | // determines the value stored. But if a keyword is repeated, it will be |
241 | | // added to this vector if _any_ instance is missing a value. If one of the |
242 | | // earlier instances is missing a value but the last one isn't, its presence |
243 | | // in this vector will be misleading. |
244 | 0 | std::vector<cm::string_view> keywordsMissingValues; |
245 | 0 | parser.BindKeywordsMissingValue(keywordsMissingValues); |
246 | |
|
247 | 0 | parser.Parse(list, &unparsed); |
248 | |
|
249 | 0 | PassParsedArguments( |
250 | 0 | prefix, status.GetMakefile(), options, singleValArgs, multiValArgs, |
251 | 0 | unparsed, options_set(keywordsSeen.begin(), keywordsSeen.end()), |
252 | 0 | options_set(keywordsMissingValues.begin(), keywordsMissingValues.end()), |
253 | 0 | parseFromArgV); |
254 | |
|
255 | 0 | return true; |
256 | 0 | } |