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