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