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