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