/src/CMake/Source/cmConditionEvaluator.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 "cmConditionEvaluator.h" |
4 | | |
5 | | #include <array> |
6 | | #include <cstdio> |
7 | | #include <cstdlib> |
8 | | #include <functional> |
9 | | #include <iterator> |
10 | | #include <list> |
11 | | #include <sstream> |
12 | | #include <utility> |
13 | | |
14 | | #include <cm/string_view> |
15 | | #include <cmext/algorithm> |
16 | | |
17 | | #include "cmsys/RegularExpression.hxx" |
18 | | |
19 | | #include "cmCMakePath.h" |
20 | | #include "cmExpandedCommandArgument.h" |
21 | | #include "cmList.h" |
22 | | #include "cmMakefile.h" |
23 | | #include "cmMessageType.h" |
24 | | #include "cmState.h" |
25 | | #include "cmStringAlgorithms.h" |
26 | | #include "cmSystemTools.h" |
27 | | #include "cmValue.h" |
28 | | |
29 | | namespace { |
30 | | auto const keyAND = "AND"_s; |
31 | | auto const keyCOMMAND = "COMMAND"_s; |
32 | | auto const keyDEFINED = "DEFINED"_s; |
33 | | auto const keyEQUAL = "EQUAL"_s; |
34 | | auto const keyEXISTS = "EXISTS"_s; |
35 | | auto const keyIS_READABLE = "IS_READABLE"_s; |
36 | | auto const keyIS_WRITABLE = "IS_WRITABLE"_s; |
37 | | auto const keyIS_EXECUTABLE = "IS_EXECUTABLE"_s; |
38 | | auto const keyGREATER = "GREATER"_s; |
39 | | auto const keyGREATER_EQUAL = "GREATER_EQUAL"_s; |
40 | | auto const keyIN_LIST = "IN_LIST"_s; |
41 | | auto const keyIS_ABSOLUTE = "IS_ABSOLUTE"_s; |
42 | | auto const keyIS_DIRECTORY = "IS_DIRECTORY"_s; |
43 | | auto const keyIS_NEWER_THAN = "IS_NEWER_THAN"_s; |
44 | | auto const keyIS_SYMLINK = "IS_SYMLINK"_s; |
45 | | auto const keyLESS = "LESS"_s; |
46 | | auto const keyLESS_EQUAL = "LESS_EQUAL"_s; |
47 | | auto const keyMATCHES = "MATCHES"_s; |
48 | | auto const keyNOT = "NOT"_s; |
49 | | auto const keyOR = "OR"_s; |
50 | | auto const keyParenL = "("_s; |
51 | | auto const keyParenR = ")"_s; |
52 | | auto const keyPOLICY = "POLICY"_s; |
53 | | auto const keySTREQUAL = "STREQUAL"_s; |
54 | | auto const keySTRGREATER = "STRGREATER"_s; |
55 | | auto const keySTRGREATER_EQUAL = "STRGREATER_EQUAL"_s; |
56 | | auto const keySTRLESS = "STRLESS"_s; |
57 | | auto const keySTRLESS_EQUAL = "STRLESS_EQUAL"_s; |
58 | | auto const keyTARGET = "TARGET"_s; |
59 | | auto const keyTEST = "TEST"_s; |
60 | | auto const keyVERSION_EQUAL = "VERSION_EQUAL"_s; |
61 | | auto const keyVERSION_GREATER = "VERSION_GREATER"_s; |
62 | | auto const keyVERSION_GREATER_EQUAL = "VERSION_GREATER_EQUAL"_s; |
63 | | auto const keyVERSION_LESS = "VERSION_LESS"_s; |
64 | | auto const keyVERSION_LESS_EQUAL = "VERSION_LESS_EQUAL"_s; |
65 | | auto const keyPATH_EQUAL = "PATH_EQUAL"_s; |
66 | | |
67 | | cmSystemTools::CompareOp const MATCH2CMPOP[5] = { |
68 | | cmSystemTools::OP_LESS, cmSystemTools::OP_LESS_EQUAL, |
69 | | cmSystemTools::OP_GREATER, cmSystemTools::OP_GREATER_EQUAL, |
70 | | cmSystemTools::OP_EQUAL |
71 | | }; |
72 | | |
73 | | // Run-Time to Compile-Time template selector |
74 | | template <template <typename> class Comp, template <typename> class... Ops> |
75 | | struct cmRt2CtSelector |
76 | | { |
77 | | template <typename T> |
78 | | static bool eval(int r, T lhs, T rhs) |
79 | 0 | { |
80 | 0 | switch (r) { |
81 | 0 | case 0: |
82 | 0 | return false; |
83 | 0 | case 1: |
84 | 0 | return Comp<T>()(lhs, rhs); |
85 | 0 | default: |
86 | 0 | return cmRt2CtSelector<Ops...>::eval(r - 1, lhs, rhs); |
87 | 0 | } |
88 | 0 | } Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::less, std::__1::less_equal, std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<double>(int, double, double) Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::less_equal, std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<double>(int, double, double) Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<double>(int, double, double) Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::greater_equal, std::__1::equal_to>::eval<double>(int, double, double) Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::less, std::__1::less_equal, std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<int>(int, int, int) Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::less_equal, std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<int>(int, int, int) Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<int>(int, int, int) Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::greater_equal, std::__1::equal_to>::eval<int>(int, int, int) Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::logical_and, std::__1::logical_or>::eval<bool>(int, bool, bool) |
89 | | }; |
90 | | |
91 | | template <template <typename> class Comp> |
92 | | struct cmRt2CtSelector<Comp> |
93 | | { |
94 | | template <typename T> |
95 | | static bool eval(int r, T lhs, T rhs) |
96 | 0 | { |
97 | 0 | return r == 1 && Comp<T>()(lhs, rhs); |
98 | 0 | } Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::equal_to>::eval<double>(int, double, double) Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::equal_to>::eval<int>(int, int, int) Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::logical_or>::eval<bool>(int, bool, bool) |
99 | | }; |
100 | | |
101 | | std::string bool2string(bool const value) |
102 | 0 | { |
103 | 0 | return std::string(static_cast<std::size_t>(1), |
104 | 0 | static_cast<char>('0' + static_cast<int>(value))); |
105 | 0 | } |
106 | | |
107 | | bool looksLikeSpecialVariable(std::string const& var, |
108 | | cm::static_string_view prefix, |
109 | | std::size_t const varNameLen) |
110 | 0 | { |
111 | | // NOTE Expecting a variable name at least 1 char length: |
112 | | // <prefix> + `{` + <varname> + `}` |
113 | 0 | return ((prefix.size() + 3) <= varNameLen) && |
114 | 0 | cmHasPrefix(var, cmStrCat(prefix, '{')) && var[varNameLen - 1] == '}'; |
115 | 0 | } |
116 | | } // anonymous namespace |
117 | | |
118 | | #if defined(__SUNPRO_CC) |
119 | | # define CM_INHERIT_CTOR(Class, Base, Tpl) \ |
120 | | template <typename... Args> \ |
121 | | Class(Args&&... args) \ |
122 | | : Base Tpl(std::forward<Args>(args)...) \ |
123 | | { \ |
124 | | } |
125 | | #else |
126 | | # define CM_INHERIT_CTOR(Class, Base, Tpl) using Base Tpl ::Base |
127 | | #endif |
128 | | |
129 | | // BEGIN cmConditionEvaluator::cmArgumentList |
130 | | class cmConditionEvaluator::cmArgumentList |
131 | | : public std::list<cmExpandedCommandArgument> |
132 | | { |
133 | | using base_t = std::list<cmExpandedCommandArgument>; |
134 | | |
135 | | public: |
136 | | CM_INHERIT_CTOR(cmArgumentList, list, <cmExpandedCommandArgument>); |
137 | | |
138 | | class CurrentAndNextIter |
139 | | { |
140 | | friend class cmConditionEvaluator::cmArgumentList; |
141 | | |
142 | | public: |
143 | | base_t::iterator current; |
144 | | base_t::iterator next; |
145 | | |
146 | | CurrentAndNextIter advance(base_t& args) |
147 | 0 | { |
148 | 0 | this->current = std::next(this->current); |
149 | 0 | this->next = |
150 | 0 | std::next(this->current, |
151 | 0 | static_cast<difference_type>(this->current != args.end())); |
152 | 0 | return *this; |
153 | 0 | } |
154 | | |
155 | | private: |
156 | | CurrentAndNextIter(base_t& args) |
157 | 0 | : current(args.begin()) |
158 | | , next( |
159 | 0 | std::next(this->current, |
160 | 0 | static_cast<difference_type>(this->current != args.end()))) |
161 | 0 | { |
162 | 0 | } |
163 | | }; |
164 | | |
165 | | class CurrentAndTwoMoreIter |
166 | | { |
167 | | friend class cmConditionEvaluator::cmArgumentList; |
168 | | |
169 | | public: |
170 | | base_t::iterator current; |
171 | | base_t::iterator next; |
172 | | base_t::iterator nextnext; |
173 | | |
174 | | CurrentAndTwoMoreIter advance(base_t& args) |
175 | 0 | { |
176 | 0 | this->current = std::next(this->current); |
177 | 0 | this->next = |
178 | 0 | std::next(this->current, |
179 | 0 | static_cast<difference_type>(this->current != args.end())); |
180 | 0 | this->nextnext = std::next( |
181 | 0 | this->next, static_cast<difference_type>(this->next != args.end())); |
182 | 0 | return *this; |
183 | 0 | } |
184 | | |
185 | | private: |
186 | | CurrentAndTwoMoreIter(base_t& args) |
187 | 0 | : current(args.begin()) |
188 | | , next( |
189 | 0 | std::next(this->current, |
190 | 0 | static_cast<difference_type>(this->current != args.end()))) |
191 | 0 | , nextnext(std::next( |
192 | 0 | this->next, static_cast<difference_type>(this->next != args.end()))) |
193 | 0 | { |
194 | 0 | } |
195 | | }; |
196 | | |
197 | 0 | CurrentAndNextIter make2ArgsIterator() { return *this; } |
198 | 0 | CurrentAndTwoMoreIter make3ArgsIterator() { return *this; } |
199 | | |
200 | | template <typename Iter> |
201 | | void ReduceOneArg(bool const value, Iter args) |
202 | 0 | { |
203 | 0 | *args.current = cmExpandedCommandArgument(bool2string(value), true); |
204 | 0 | this->erase(args.next); |
205 | 0 | } Unexecuted instantiation: void cmConditionEvaluator::cmArgumentList::ReduceOneArg<cmConditionEvaluator::cmArgumentList::CurrentAndNextIter>(bool, cmConditionEvaluator::cmArgumentList::CurrentAndNextIter) Unexecuted instantiation: void cmConditionEvaluator::cmArgumentList::ReduceOneArg<cmConditionEvaluator::cmArgumentList::CurrentAndTwoMoreIter>(bool, cmConditionEvaluator::cmArgumentList::CurrentAndTwoMoreIter) |
206 | | |
207 | | void ReduceTwoArgs(bool const value, CurrentAndTwoMoreIter args) |
208 | 0 | { |
209 | 0 | *args.current = cmExpandedCommandArgument(bool2string(value), true); |
210 | 0 | this->erase(args.nextnext); |
211 | 0 | this->erase(args.next); |
212 | 0 | } |
213 | | }; |
214 | | |
215 | | // END cmConditionEvaluator::cmArgumentList |
216 | | |
217 | | cmConditionEvaluator::cmConditionEvaluator(cmMakefile& makefile, |
218 | | cmListFileBacktrace bt) |
219 | 0 | : Makefile(makefile) |
220 | 0 | , Backtrace(std::move(bt)) |
221 | 0 | , Policy139Status(makefile.GetPolicyStatus(cmPolicies::CMP0139)) |
222 | 0 | { |
223 | 0 | } |
224 | | |
225 | | //========================================================================= |
226 | | // order of operations, |
227 | | // 1. ( ) -- parenthetical groups |
228 | | // 2. IS_DIRECTORY EXISTS COMMAND DEFINED etc predicates |
229 | | // 3. MATCHES LESS GREATER EQUAL STRLESS STRGREATER STREQUAL etc binary ops |
230 | | // 4. NOT |
231 | | // 5. AND OR |
232 | | // |
233 | | // There is an issue on whether the arguments should be values of references, |
234 | | // for example IF (FOO AND BAR) should that compare the strings FOO and BAR |
235 | | // or should it really do IF (${FOO} AND ${BAR}) Currently IS_DIRECTORY |
236 | | // EXISTS COMMAND and DEFINED all take values. EQUAL, LESS and GREATER can |
237 | | // take numeric values or variable names. STRLESS and STRGREATER take |
238 | | // variable names but if the variable name is not found it will use the name |
239 | | // directly. AND OR take variables or the values 0 or 1. |
240 | | |
241 | | bool cmConditionEvaluator::IsTrue( |
242 | | std::vector<cmExpandedCommandArgument> const& args, std::string& errorString, |
243 | | MessageType& status) |
244 | 0 | { |
245 | 0 | errorString.clear(); |
246 | | |
247 | | // handle empty invocation |
248 | 0 | if (args.empty()) { |
249 | 0 | return false; |
250 | 0 | } |
251 | | |
252 | | // store the reduced args in this vector |
253 | 0 | cmArgumentList newArgs(args.begin(), args.end()); |
254 | | |
255 | | // now loop through the arguments and see if we can reduce any of them |
256 | | // we do this multiple times. Once for each level of precedence |
257 | | // parens |
258 | 0 | using handlerFn_t = bool (cmConditionEvaluator::*)( |
259 | 0 | cmArgumentList&, std::string&, MessageType&); |
260 | 0 | std::array<handlerFn_t, 5> const handlers = { { |
261 | 0 | &cmConditionEvaluator::HandleLevel0, // parenthesis |
262 | 0 | &cmConditionEvaluator::HandleLevel1, // predicates |
263 | 0 | &cmConditionEvaluator::HandleLevel2, // binary ops |
264 | 0 | &cmConditionEvaluator::HandleLevel3, // NOT |
265 | 0 | &cmConditionEvaluator::HandleLevel4 // AND OR |
266 | 0 | } }; |
267 | 0 | for (auto fn : handlers) { |
268 | | // Call the reducer 'till there is anything to reduce... |
269 | | // (i.e., if after an iteration the size becomes smaller) |
270 | 0 | auto levelResult = true; |
271 | 0 | for (auto beginSize = newArgs.size(); |
272 | 0 | (levelResult = (this->*fn)(newArgs, errorString, status)) && |
273 | 0 | newArgs.size() < beginSize; |
274 | 0 | beginSize = newArgs.size()) { |
275 | 0 | } |
276 | |
|
277 | 0 | if (!levelResult) { |
278 | | // NOTE `errorString` supposed to be set already |
279 | 0 | return false; |
280 | 0 | } |
281 | 0 | } |
282 | | |
283 | | // now at the end there should only be one argument left |
284 | 0 | if (newArgs.size() != 1) { |
285 | 0 | errorString = "Unknown arguments specified"; |
286 | 0 | status = MessageType::FATAL_ERROR; |
287 | 0 | return false; |
288 | 0 | } |
289 | | |
290 | 0 | return this->GetBooleanValue(newArgs.front()); |
291 | 0 | } |
292 | | |
293 | | //========================================================================= |
294 | | cmValue cmConditionEvaluator::GetDefinitionIfUnquoted( |
295 | | cmExpandedCommandArgument const& argument) const |
296 | 0 | { |
297 | 0 | if (argument.WasQuoted()) { |
298 | 0 | return nullptr; |
299 | 0 | } |
300 | | |
301 | 0 | return this->Makefile.GetDefinition(argument.GetValue()); |
302 | 0 | } |
303 | | |
304 | | //========================================================================= |
305 | | cmValue cmConditionEvaluator::GetVariableOrString( |
306 | | cmExpandedCommandArgument const& argument) const |
307 | 0 | { |
308 | 0 | cmValue def = this->GetDefinitionIfUnquoted(argument); |
309 | |
|
310 | 0 | if (!def) { |
311 | 0 | def = cmValue(argument.GetValue()); |
312 | 0 | } |
313 | |
|
314 | 0 | return def; |
315 | 0 | } |
316 | | |
317 | | //========================================================================= |
318 | | bool cmConditionEvaluator::IsKeyword( |
319 | | cm::static_string_view keyword, |
320 | | cmExpandedCommandArgument const& argument) const |
321 | 0 | { |
322 | 0 | if (argument.WasQuoted()) { |
323 | 0 | return false; |
324 | 0 | } |
325 | | |
326 | 0 | return argument.GetValue() == keyword; |
327 | 0 | } |
328 | | |
329 | | //========================================================================= |
330 | | bool cmConditionEvaluator::GetBooleanValue( |
331 | | cmExpandedCommandArgument& arg) const |
332 | 0 | { |
333 | | // Check basic and named constants. |
334 | 0 | if (cmIsOn(arg.GetValue())) { |
335 | 0 | return true; |
336 | 0 | } |
337 | 0 | if (cmIsOff(arg.GetValue())) { |
338 | 0 | return false; |
339 | 0 | } |
340 | | |
341 | | // Check for numbers. |
342 | 0 | if (!arg.empty()) { |
343 | 0 | char* end; |
344 | 0 | double const d = std::strtod(arg.GetValue().c_str(), &end); |
345 | 0 | if (*end == '\0') { |
346 | | // The whole string is a number. Use C conversion to bool. |
347 | 0 | return static_cast<bool>(d); |
348 | 0 | } |
349 | 0 | } |
350 | | |
351 | | // Check definition. |
352 | 0 | cmValue def = this->GetDefinitionIfUnquoted(arg); |
353 | 0 | return !def.IsOff(); |
354 | 0 | } |
355 | | |
356 | | template <int N> |
357 | | inline int cmConditionEvaluator::matchKeysImpl( |
358 | | cmExpandedCommandArgument const&) |
359 | 0 | { |
360 | | // Zero means "not found" |
361 | 0 | return 0; |
362 | 0 | } Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<6>(cmExpandedCommandArgument const&) Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<3>(cmExpandedCommandArgument const&) |
363 | | |
364 | | template <int N, typename T, typename... Keys> |
365 | | inline int cmConditionEvaluator::matchKeysImpl( |
366 | | cmExpandedCommandArgument const& arg, T current, Keys... key) |
367 | 0 | { |
368 | 0 | if (this->IsKeyword(current, arg)) { |
369 | | // Stop searching as soon as smth has found |
370 | 0 | return N; |
371 | 0 | } |
372 | 0 | return matchKeysImpl<N + 1>(arg, key...); |
373 | 0 | } Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<1, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view) Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<2, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view) Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<3, cm::static_string_view, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view, cm::static_string_view) Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<4, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view) Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<5, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view) Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<1, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view) Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<2, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view) |
374 | | |
375 | | template <typename... Keys> |
376 | | inline int cmConditionEvaluator::matchKeys( |
377 | | cmExpandedCommandArgument const& arg, Keys... key) |
378 | 0 | { |
379 | | // Get index of the matched key (1-based) |
380 | 0 | return matchKeysImpl<1>(arg, key...); |
381 | 0 | } Unexecuted instantiation: int cmConditionEvaluator::matchKeys<cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view) Unexecuted instantiation: int cmConditionEvaluator::matchKeys<cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view) |
382 | | |
383 | | //========================================================================= |
384 | | // level 0 processes parenthetical expressions |
385 | | bool cmConditionEvaluator::HandleLevel0(cmArgumentList& newArgs, |
386 | | std::string& errorString, |
387 | | MessageType& status) |
388 | 0 | { |
389 | 0 | for (auto arg = newArgs.begin(); arg != newArgs.end(); ++arg) { |
390 | 0 | if (this->IsKeyword(keyParenL, *arg)) { |
391 | | // search for the closing paren for this opening one |
392 | 0 | auto depth = 1; |
393 | 0 | auto argClose = std::next(arg); |
394 | 0 | for (; argClose != newArgs.end() && depth; ++argClose) { |
395 | 0 | depth += int(this->IsKeyword(keyParenL, *argClose)) - |
396 | 0 | int(this->IsKeyword(keyParenR, *argClose)); |
397 | 0 | } |
398 | 0 | if (depth) { |
399 | 0 | errorString = "mismatched parenthesis in condition"; |
400 | 0 | status = MessageType::FATAL_ERROR; |
401 | 0 | return false; |
402 | 0 | } |
403 | | |
404 | | // store the reduced args in this vector |
405 | 0 | auto argOpen = std::next(arg); |
406 | 0 | std::vector<cmExpandedCommandArgument> const subExpr( |
407 | 0 | argOpen, std::prev(argClose)); |
408 | | |
409 | | // now recursively invoke IsTrue to handle the values inside the |
410 | | // parenthetical expression |
411 | 0 | auto const value = this->IsTrue(subExpr, errorString, status); |
412 | 0 | *arg = cmExpandedCommandArgument(bool2string(value), true); |
413 | 0 | argOpen = std::next(arg); |
414 | | // remove the now evaluated parenthetical expression |
415 | 0 | newArgs.erase(argOpen, argClose); |
416 | 0 | } |
417 | 0 | } |
418 | 0 | return true; |
419 | 0 | } |
420 | | |
421 | | //========================================================================= |
422 | | // level one handles most predicates except for NOT |
423 | | bool cmConditionEvaluator::HandleLevel1(cmArgumentList& newArgs, std::string&, |
424 | | MessageType&) |
425 | 0 | { |
426 | 0 | for (auto args = newArgs.make2ArgsIterator(); args.current != newArgs.end(); |
427 | 0 | args.advance(newArgs)) { |
428 | | // NOTE Fail fast: All the predicates below require the next arg to be |
429 | | // valid |
430 | 0 | if (args.next == newArgs.end()) { |
431 | 0 | continue; |
432 | 0 | } |
433 | | |
434 | | // does a file exist |
435 | 0 | if (this->IsKeyword(keyEXISTS, *args.current)) { |
436 | 0 | newArgs.ReduceOneArg(cmSystemTools::FileExists(args.next->GetValue()), |
437 | 0 | args); |
438 | 0 | } |
439 | | // check if a file is readable |
440 | 0 | else if (this->IsKeyword(keyIS_READABLE, *args.current)) { |
441 | 0 | newArgs.ReduceOneArg(cmSystemTools::TestFileAccess( |
442 | 0 | args.next->GetValue(), cmsys::TEST_FILE_READ), |
443 | 0 | args); |
444 | 0 | } |
445 | | // check if a file is writable |
446 | 0 | else if (this->IsKeyword(keyIS_WRITABLE, *args.current)) { |
447 | 0 | newArgs.ReduceOneArg(cmSystemTools::TestFileAccess( |
448 | 0 | args.next->GetValue(), cmsys::TEST_FILE_WRITE), |
449 | 0 | args); |
450 | 0 | } |
451 | | // check if a file is executable |
452 | 0 | else if (this->IsKeyword(keyIS_EXECUTABLE, *args.current)) { |
453 | 0 | newArgs.ReduceOneArg(cmSystemTools::TestFileAccess( |
454 | 0 | args.next->GetValue(), cmsys::TEST_FILE_EXECUTE), |
455 | 0 | args); |
456 | 0 | } |
457 | | // does a directory with this name exist |
458 | 0 | else if (this->IsKeyword(keyIS_DIRECTORY, *args.current)) { |
459 | 0 | newArgs.ReduceOneArg( |
460 | 0 | cmSystemTools::FileIsDirectory(args.next->GetValue()), args); |
461 | 0 | } |
462 | | // does a symlink with this name exist |
463 | 0 | else if (this->IsKeyword(keyIS_SYMLINK, *args.current)) { |
464 | 0 | newArgs.ReduceOneArg(cmSystemTools::FileIsSymlink(args.next->GetValue()), |
465 | 0 | args); |
466 | 0 | } |
467 | | // is the given path an absolute path ? |
468 | 0 | else if (this->IsKeyword(keyIS_ABSOLUTE, *args.current)) { |
469 | 0 | newArgs.ReduceOneArg( |
470 | 0 | cmSystemTools::FileIsFullPath(args.next->GetValue()), args); |
471 | 0 | } |
472 | | // does a command exist |
473 | 0 | else if (this->IsKeyword(keyCOMMAND, *args.current)) { |
474 | 0 | newArgs.ReduceOneArg( |
475 | 0 | static_cast<bool>( |
476 | 0 | this->Makefile.GetState()->GetCommand(args.next->GetValue())), |
477 | 0 | args); |
478 | 0 | } |
479 | | // does a policy exist |
480 | 0 | else if (this->IsKeyword(keyPOLICY, *args.current)) { |
481 | 0 | cmPolicies::PolicyID pid; |
482 | 0 | newArgs.ReduceOneArg( |
483 | 0 | cmPolicies::GetPolicyID(args.next->GetValue().c_str(), pid), args); |
484 | 0 | } |
485 | | // does a target exist |
486 | 0 | else if (this->IsKeyword(keyTARGET, *args.current)) { |
487 | 0 | newArgs.ReduceOneArg(static_cast<bool>(this->Makefile.FindTargetToUse( |
488 | 0 | args.next->GetValue())), |
489 | 0 | args); |
490 | 0 | } |
491 | | // is a variable defined |
492 | 0 | else if (this->IsKeyword(keyDEFINED, *args.current)) { |
493 | 0 | auto const& var = args.next->GetValue(); |
494 | 0 | auto const varNameLen = var.size(); |
495 | |
|
496 | 0 | auto result = false; |
497 | 0 | if (looksLikeSpecialVariable(var, "ENV"_s, varNameLen)) { |
498 | 0 | auto const env = args.next->GetValue().substr(4, varNameLen - 5); |
499 | 0 | result = cmSystemTools::HasEnv(env); |
500 | 0 | } |
501 | | |
502 | 0 | else if (looksLikeSpecialVariable(var, "CACHE"_s, varNameLen)) { |
503 | 0 | auto const cache = args.next->GetValue().substr(6, varNameLen - 7); |
504 | 0 | result = static_cast<bool>( |
505 | 0 | this->Makefile.GetState()->GetCacheEntryValue(cache)); |
506 | 0 | } |
507 | | |
508 | 0 | else { |
509 | 0 | result = this->Makefile.IsDefinitionSet(args.next->GetValue()); |
510 | 0 | } |
511 | 0 | newArgs.ReduceOneArg(result, args); |
512 | 0 | } |
513 | | // does a test exist |
514 | 0 | else if (this->IsKeyword(keyTEST, *args.current)) { |
515 | 0 | newArgs.ReduceOneArg( |
516 | 0 | static_cast<bool>(this->Makefile.GetTest(args.next->GetValue())), |
517 | 0 | args); |
518 | 0 | } |
519 | 0 | } |
520 | 0 | return true; |
521 | 0 | } |
522 | | |
523 | | //========================================================================= |
524 | | // level two handles most binary operations except for AND OR |
525 | | bool cmConditionEvaluator::HandleLevel2(cmArgumentList& newArgs, |
526 | | std::string& errorString, |
527 | | MessageType& status) |
528 | 0 | { |
529 | 0 | for (auto args = newArgs.make3ArgsIterator(); args.current != newArgs.end(); |
530 | 0 | args.advance(newArgs)) { |
531 | |
|
532 | 0 | int matchNo; |
533 | | |
534 | | // NOTE Handle special case `if(... BLAH_BLAH MATCHES)` |
535 | | // (i.e., w/o regex to match which is possibly result of |
536 | | // variable expansion to an empty string) |
537 | 0 | if (args.next != newArgs.end() && |
538 | 0 | this->IsKeyword(keyMATCHES, *args.current)) { |
539 | 0 | newArgs.ReduceOneArg(false, args); |
540 | 0 | } |
541 | | |
542 | | // NOTE Fail fast: All the binary ops below require 2 arguments. |
543 | 0 | else if (args.next == newArgs.end() || args.nextnext == newArgs.end()) { |
544 | 0 | continue; |
545 | 0 | } |
546 | | |
547 | 0 | else if (this->IsKeyword(keyMATCHES, *args.next)) { |
548 | 0 | cmValue def = this->GetDefinitionIfUnquoted(*args.current); |
549 | |
|
550 | 0 | std::string def_buf; |
551 | 0 | if (!def) { |
552 | 0 | def = cmValue(args.current->GetValue()); |
553 | 0 | } else if (cmHasLiteralPrefix(args.current->GetValue(), |
554 | 0 | "CMAKE_MATCH_")) { |
555 | | // The string to match is owned by our match result variables. |
556 | | // Move it to our own buffer before clearing them. |
557 | 0 | def_buf = *def; |
558 | 0 | def = cmValue(def_buf); |
559 | 0 | } |
560 | |
|
561 | 0 | this->Makefile.ClearMatches(); |
562 | |
|
563 | 0 | auto const& rex = args.nextnext->GetValue(); |
564 | 0 | cmsys::RegularExpression regEntry; |
565 | 0 | if (!regEntry.compile(rex)) { |
566 | 0 | std::ostringstream error; |
567 | 0 | error << "Regular expression \"" << rex << "\" cannot compile"; |
568 | 0 | errorString = error.str(); |
569 | 0 | status = MessageType::FATAL_ERROR; |
570 | 0 | return false; |
571 | 0 | } |
572 | | |
573 | 0 | auto const match = regEntry.find(*def); |
574 | 0 | if (match) { |
575 | 0 | this->Makefile.StoreMatches(regEntry); |
576 | 0 | } |
577 | 0 | newArgs.ReduceTwoArgs(match, args); |
578 | 0 | } |
579 | | |
580 | 0 | else if ((matchNo = |
581 | 0 | this->matchKeys(*args.next, keyLESS, keyLESS_EQUAL, keyGREATER, |
582 | 0 | keyGREATER_EQUAL, keyEQUAL))) { |
583 | |
|
584 | 0 | cmValue ldef = this->GetVariableOrString(*args.current); |
585 | 0 | cmValue rdef = this->GetVariableOrString(*args.nextnext); |
586 | |
|
587 | 0 | double lhs; |
588 | 0 | double rhs; |
589 | 0 | auto parseDoubles = [&]() { |
590 | 0 | return std::sscanf(ldef->c_str(), "%lg", &lhs) == 1 && |
591 | 0 | std::sscanf(rdef->c_str(), "%lg", &rhs) == 1; |
592 | 0 | }; |
593 | | // clang-format off |
594 | 0 | const auto result = parseDoubles() && |
595 | 0 | cmRt2CtSelector< |
596 | 0 | std::less, std::less_equal, |
597 | 0 | std::greater, std::greater_equal, |
598 | 0 | std::equal_to |
599 | 0 | >::eval(matchNo, lhs, rhs); |
600 | | // clang-format on |
601 | 0 | newArgs.ReduceTwoArgs(result, args); |
602 | 0 | } |
603 | | |
604 | 0 | else if ((matchNo = this->matchKeys(*args.next, keySTRLESS, |
605 | 0 | keySTRLESS_EQUAL, keySTRGREATER, |
606 | 0 | keySTRGREATER_EQUAL, keySTREQUAL))) { |
607 | |
|
608 | 0 | cmValue const lhs = this->GetVariableOrString(*args.current); |
609 | 0 | cmValue const rhs = this->GetVariableOrString(*args.nextnext); |
610 | 0 | auto const val = (*lhs).compare(*rhs); |
611 | | // clang-format off |
612 | 0 | const auto result = cmRt2CtSelector< |
613 | 0 | std::less, std::less_equal, |
614 | 0 | std::greater, std::greater_equal, |
615 | 0 | std::equal_to |
616 | 0 | >::eval(matchNo, val, 0); |
617 | | // clang-format on |
618 | 0 | newArgs.ReduceTwoArgs(result, args); |
619 | 0 | } |
620 | | |
621 | 0 | else if ((matchNo = |
622 | 0 | this->matchKeys(*args.next, keyVERSION_LESS, |
623 | 0 | keyVERSION_LESS_EQUAL, keyVERSION_GREATER, |
624 | 0 | keyVERSION_GREATER_EQUAL, keyVERSION_EQUAL))) { |
625 | 0 | auto const op = MATCH2CMPOP[matchNo - 1]; |
626 | 0 | cmValue const lhs = this->GetVariableOrString(*args.current); |
627 | 0 | cmValue const rhs = this->GetVariableOrString(*args.nextnext); |
628 | 0 | auto const result = cmSystemTools::VersionCompare(op, lhs, rhs); |
629 | 0 | newArgs.ReduceTwoArgs(result, args); |
630 | 0 | } |
631 | | |
632 | | // is file A newer than file B |
633 | 0 | else if (this->IsKeyword(keyIS_NEWER_THAN, *args.next)) { |
634 | 0 | auto fileIsNewer = 0; |
635 | 0 | cmsys::Status ftcStatus = cmSystemTools::FileTimeCompare( |
636 | 0 | args.current->GetValue(), args.nextnext->GetValue(), &fileIsNewer); |
637 | 0 | newArgs.ReduceTwoArgs( |
638 | 0 | (!ftcStatus || fileIsNewer == 1 || fileIsNewer == 0), args); |
639 | 0 | } |
640 | | |
641 | 0 | else if (this->IsKeyword(keyIN_LIST, *args.next)) { |
642 | 0 | cmValue lhs = this->GetVariableOrString(*args.current); |
643 | 0 | cmValue rhs = this->Makefile.GetDefinition(args.nextnext->GetValue()); |
644 | |
|
645 | 0 | newArgs.ReduceTwoArgs( |
646 | 0 | rhs && cm::contains(cmList{ *rhs, cmList::EmptyElements::Yes }, *lhs), |
647 | 0 | args); |
648 | 0 | } |
649 | | |
650 | 0 | else if (this->IsKeyword(keyPATH_EQUAL, *args.next)) { |
651 | |
|
652 | 0 | if (this->Policy139Status != cmPolicies::OLD && |
653 | 0 | this->Policy139Status != cmPolicies::WARN) { |
654 | |
|
655 | 0 | cmValue lhs = this->GetVariableOrString(*args.current); |
656 | 0 | cmValue rhs = this->GetVariableOrString(*args.nextnext); |
657 | 0 | auto const result = cmCMakePath{ *lhs } == cmCMakePath{ *rhs }; |
658 | 0 | newArgs.ReduceTwoArgs(result, args); |
659 | 0 | } |
660 | | |
661 | 0 | else if (this->Policy139Status == cmPolicies::WARN) { |
662 | 0 | std::ostringstream e; |
663 | 0 | e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0139) |
664 | 0 | << "\n" |
665 | 0 | "PATH_EQUAL will be interpreted as an operator " |
666 | 0 | "when the policy is set to NEW. " |
667 | 0 | "Since the policy is not set the OLD behavior will be used."; |
668 | |
|
669 | 0 | this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str()); |
670 | 0 | } |
671 | 0 | } |
672 | 0 | } |
673 | 0 | return true; |
674 | 0 | } |
675 | | |
676 | | //========================================================================= |
677 | | // level 3 handles NOT |
678 | | bool cmConditionEvaluator::HandleLevel3(cmArgumentList& newArgs, std::string&, |
679 | | MessageType&) |
680 | 0 | { |
681 | 0 | for (auto args = newArgs.make2ArgsIterator(); args.next != newArgs.end(); |
682 | 0 | args.advance(newArgs)) { |
683 | 0 | if (this->IsKeyword(keyNOT, *args.current)) { |
684 | 0 | auto const rhs = this->GetBooleanValue(*args.next); |
685 | 0 | newArgs.ReduceOneArg(!rhs, args); |
686 | 0 | } |
687 | 0 | } |
688 | 0 | return true; |
689 | 0 | } |
690 | | |
691 | | //========================================================================= |
692 | | // level 4 handles AND OR |
693 | | bool cmConditionEvaluator::HandleLevel4(cmArgumentList& newArgs, std::string&, |
694 | | MessageType&) |
695 | 0 | { |
696 | 0 | for (auto args = newArgs.make3ArgsIterator(); args.nextnext != newArgs.end(); |
697 | 0 | args.advance(newArgs)) { |
698 | |
|
699 | 0 | int matchNo; |
700 | |
|
701 | 0 | if ((matchNo = this->matchKeys(*args.next, keyAND, keyOR))) { |
702 | 0 | auto const lhs = this->GetBooleanValue(*args.current); |
703 | 0 | auto const rhs = this->GetBooleanValue(*args.nextnext); |
704 | | // clang-format off |
705 | 0 | const auto result = |
706 | 0 | cmRt2CtSelector< |
707 | 0 | std::logical_and, std::logical_or |
708 | 0 | >::eval(matchNo, lhs, rhs); |
709 | | // clang-format on |
710 | 0 | newArgs.ReduceTwoArgs(result, args); |
711 | 0 | } |
712 | 0 | } |
713 | 0 | return true; |
714 | 0 | } |