1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the qmake application of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "qmakeevaluator.h"
30 
31 #include "qmakeevaluator_p.h"
32 #include "qmakeglobals.h"
33 #include "qmakeparser.h"
34 #include "qmakevfs.h"
35 #include "ioutils.h"
36 
37 #include <qbytearray.h>
38 #include <qdir.h>
39 #include <qfile.h>
40 #include <qfileinfo.h>
41 #include <qlist.h>
42 #include <qregexp.h>
43 #include <qset.h>
44 #include <qstringlist.h>
45 #include <qtextstream.h>
46 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
47 # include <qjsondocument.h>
48 # include <qjsonobject.h>
49 # include <qjsonarray.h>
50 #endif
51 #ifdef PROEVALUATOR_THREAD_SAFE
52 # include <qthreadpool.h>
53 #endif
54 #include <qversionnumber.h>
55 #ifdef Q_OS_WIN
56 # include <registry_p.h>
57 #endif
58 
59 #include <algorithm>
60 
61 #ifdef Q_OS_UNIX
62 #include <time.h>
63 #include <errno.h>
64 #include <unistd.h>
65 #include <signal.h>
66 #include <sys/wait.h>
67 #include <sys/stat.h>
68 #include <sys/utsname.h>
69 #else
70 #include <windows.h>
71 #endif
72 #include <stdio.h>
73 #include <stdlib.h>
74 
75 #ifdef Q_OS_WIN32
76 #define QT_POPEN _popen
77 #define QT_POPEN_READ "rb"
78 #define QT_PCLOSE _pclose
79 #else
80 #define QT_POPEN popen
81 #define QT_POPEN_READ "r"
82 #define QT_PCLOSE pclose
83 #endif
84 
85 using namespace QMakeInternal;
86 
87 QT_BEGIN_NAMESPACE
88 
89 #define fL1S(s) QString::fromLatin1(s)
90 
91 enum ExpandFunc {
92     E_INVALID = 0, E_MEMBER, E_STR_MEMBER, E_FIRST, E_TAKE_FIRST, E_LAST, E_TAKE_LAST,
93     E_SIZE, E_STR_SIZE, E_CAT, E_FROMFILE, E_EVAL, E_LIST, E_SPRINTF, E_FORMAT_NUMBER,
94     E_NUM_ADD, E_JOIN, E_SPLIT, E_BASENAME, E_DIRNAME, E_SECTION,
95     E_FIND, E_SYSTEM, E_UNIQUE, E_SORTED, E_REVERSE, E_QUOTE, E_ESCAPE_EXPAND,
96     E_UPPER, E_LOWER, E_TITLE, E_FILES, E_PROMPT, E_RE_ESCAPE, E_VAL_ESCAPE,
97     E_REPLACE, E_SORT_DEPENDS, E_RESOLVE_DEPENDS, E_ENUMERATE_VARS,
98     E_SHADOWED, E_ABSOLUTE_PATH, E_RELATIVE_PATH, E_CLEAN_PATH,
99     E_SYSTEM_PATH, E_SHELL_PATH, E_SYSTEM_QUOTE, E_SHELL_QUOTE, E_GETENV, E_READ_REGISTRY
100 };
101 
102 enum TestFunc {
103     T_INVALID = 0, T_REQUIRES, T_GREATERTHAN, T_LESSTHAN, T_EQUALS,
104     T_VERSION_AT_LEAST, T_VERSION_AT_MOST,
105     T_EXISTS, T_EXPORT, T_CLEAR, T_UNSET, T_EVAL, T_CONFIG, T_SYSTEM,
106     T_DEFINED, T_DISCARD_FROM, T_CONTAINS, T_INFILE,
107     T_COUNT, T_ISEMPTY, T_PARSE_JSON, T_INCLUDE, T_LOAD, T_DEBUG, T_LOG, T_MESSAGE, T_WARNING, T_ERROR, T_IF,
108     T_MKPATH, T_WRITE_FILE, T_TOUCH, T_CACHE, T_RELOAD_PROPERTIES
109 };
110 
QMakeBuiltin(const QMakeBuiltinInit & d)111 QMakeBuiltin::QMakeBuiltin(const QMakeBuiltinInit &d)
112     : index(d.func), minArgs(qMax(0, d.min_args)), maxArgs(d.max_args)
113 {
114     static const char * const nstr[6] = { "no", "one", "two", "three", "four", "five" };
115     // For legacy reasons, there is actually no such thing as "no arguments"
116     // - there is only "empty first argument", which needs to be mapped back.
117     // -1 means "one, which may be empty", which is effectively zero, except
118     // for the error message if there are too many arguments.
119     int dmin = qAbs(d.min_args);
120     int dmax = d.max_args;
121     if (dmax == QMakeBuiltinInit::VarArgs) {
122         Q_ASSERT_X(dmin < 2, "init", d.name);
123         if (dmin == 1) {
124             Q_ASSERT_X(d.args != nullptr, "init", d.name);
125             usage = fL1S("%1(%2) requires at least one argument.")
126                     .arg(fL1S(d.name), fL1S(d.args));
127         }
128         return;
129     }
130     int arange = dmax - dmin;
131     Q_ASSERT_X(arange >= 0, "init", d.name);
132     Q_ASSERT_X(d.args != nullptr, "init", d.name);
133     usage = arange > 1
134                ? fL1S("%1(%2) requires %3 to %4 arguments.")
135                  .arg(fL1S(d.name), fL1S(d.args), fL1S(nstr[dmin]), fL1S(nstr[dmax]))
136                : arange > 0
137                    ? fL1S("%1(%2) requires %3 or %4 arguments.")
138                      .arg(fL1S(d.name), fL1S(d.args), fL1S(nstr[dmin]), fL1S(nstr[dmax]))
139                    : dmax != 1
140                        ? fL1S("%1(%2) requires %3 arguments.")
141                          .arg(fL1S(d.name), fL1S(d.args), fL1S(nstr[dmax]))
142                        : fL1S("%1(%2) requires one argument.")
143                          .arg(fL1S(d.name), fL1S(d.args));
144 }
145 
initFunctionStatics()146 void QMakeEvaluator::initFunctionStatics()
147 {
148     static const QMakeBuiltinInit expandInits[] = {
149         { "member", E_MEMBER, 1, 3, "var, [start, [end]]" },
150         { "str_member", E_STR_MEMBER, -1, 3, "str, [start, [end]]" },
151         { "first", E_FIRST, 1, 1, "var" },
152         { "take_first", E_TAKE_FIRST, 1, 1, "var" },
153         { "last", E_LAST, 1, 1, "var" },
154         { "take_last", E_TAKE_LAST, 1, 1, "var" },
155         { "size", E_SIZE, 1, 1, "var" },
156         { "str_size", E_STR_SIZE, -1, 1, "str" },
157         { "cat", E_CAT, 1, 2, "file, [mode=true|blob|lines]" },
158         { "fromfile", E_FROMFILE, 2, 2, "file, var" },
159         { "eval", E_EVAL, 1, 1, "var" },
160         { "list", E_LIST, 0, QMakeBuiltinInit::VarArgs, nullptr },
161         { "sprintf", E_SPRINTF, 1, QMakeBuiltinInit::VarArgs, "format, ..." },
162         { "format_number", E_FORMAT_NUMBER, 1, 2, "number, [options...]" },
163         { "num_add", E_NUM_ADD, 1, QMakeBuiltinInit::VarArgs, "num, ..." },
164         { "join", E_JOIN, 1, 4, "var, [glue, [before, [after]]]" },
165         { "split", E_SPLIT, 1, 2, "var, sep" },
166         { "basename", E_BASENAME, 1, 1, "var" },
167         { "dirname", E_DIRNAME, 1, 1, "var" },
168         { "section", E_SECTION, 3, 4, "var, sep, begin, [end]" },
169         { "find", E_FIND, 2, 2, "var, str" },
170         { "system", E_SYSTEM, 1, 3, "command, [mode], [stsvar]" },
171         { "unique", E_UNIQUE, 1, 1, "var" },
172         { "sorted", E_SORTED, 1, 1, "var" },
173         { "reverse", E_REVERSE, 1, 1, "var" },
174         { "quote", E_QUOTE, 0, QMakeBuiltinInit::VarArgs, nullptr },
175         { "escape_expand", E_ESCAPE_EXPAND, 0, QMakeBuiltinInit::VarArgs, nullptr },
176         { "upper", E_UPPER, 0, QMakeBuiltinInit::VarArgs, nullptr },
177         { "lower", E_LOWER, 0, QMakeBuiltinInit::VarArgs, nullptr },
178         { "title", E_TITLE, 0, QMakeBuiltinInit::VarArgs, nullptr },
179         { "re_escape", E_RE_ESCAPE, 0, QMakeBuiltinInit::VarArgs, nullptr },
180         { "val_escape", E_VAL_ESCAPE, 1, 1, "var" },
181         { "files", E_FILES, 1, 2, "pattern, [recursive=false]" },
182         { "prompt", E_PROMPT, 1, 2, "question, [decorate=true]" },
183         { "replace", E_REPLACE, 3, 3, "var, before, after" },
184         { "sort_depends", E_SORT_DEPENDS, 1, 4, "var, [prefix, [suffixes, [prio-suffix]]]" },
185         { "resolve_depends", E_RESOLVE_DEPENDS, 1, 4, "var, [prefix, [suffixes, [prio-suffix]]]" },
186         { "enumerate_vars", E_ENUMERATE_VARS, 0, 0, "" },
187         { "shadowed", E_SHADOWED, 1, 1, "path" },
188         { "absolute_path", E_ABSOLUTE_PATH, -1, 2, "path, [base]" },
189         { "relative_path", E_RELATIVE_PATH, -1, 2, "path, [base]" },
190         { "clean_path", E_CLEAN_PATH, -1, 1, "path" },
191         { "system_path", E_SYSTEM_PATH, -1, 1, "path" },
192         { "shell_path", E_SHELL_PATH, -1, 1, "path" },
193         { "system_quote", E_SYSTEM_QUOTE, -1, 1, "arg" },
194         { "shell_quote", E_SHELL_QUOTE, -1, 1, "arg" },
195         { "getenv", E_GETENV, 1, 1, "arg" },
196         { "read_registry", E_READ_REGISTRY, 2, 3, "tree, key, [wow64]" },
197     };
198     statics.expands.reserve((int)(sizeof(expandInits)/sizeof(expandInits[0])));
199     for (unsigned i = 0; i < sizeof(expandInits)/sizeof(expandInits[0]); ++i)
200         statics.expands.insert(ProKey(expandInits[i].name), QMakeBuiltin(expandInits[i]));
201 
202     static const QMakeBuiltinInit testInits[] = {
203         { "requires", T_REQUIRES, 0, QMakeBuiltinInit::VarArgs, nullptr },
204         { "greaterThan", T_GREATERTHAN, 2, 2, "var, val" },
205         { "lessThan", T_LESSTHAN, 2, 2, "var, val" },
206         { "equals", T_EQUALS, 2, 2, "var, val" },
207         { "isEqual", T_EQUALS, 2, 2, "var, val" },
208         { "versionAtLeast", T_VERSION_AT_LEAST, 2, 2, "var, version" },
209         { "versionAtMost", T_VERSION_AT_MOST, 2, 2, "var, version" },
210         { "exists", T_EXISTS, 1, 1, "file" },
211         { "export", T_EXPORT, 1, 1, "var" },
212         { "clear", T_CLEAR, 1, 1, "var" },
213         { "unset", T_UNSET, 1, 1, "var" },
214         { "eval", T_EVAL, 0, QMakeBuiltinInit::VarArgs, nullptr },
215         { "CONFIG", T_CONFIG, 1, 2, "config, [mutuals]" },
216         { "if", T_IF, 1, 1, "condition" },
217         { "isActiveConfig", T_CONFIG, 1, 2, "config, [mutuals]" },
218         { "system", T_SYSTEM, 1, 1, "exec" },
219         { "discard_from", T_DISCARD_FROM, 1, 1, "file" },
220         { "defined", T_DEFINED, 1, 2, "object, [\"test\"|\"replace\"|\"var\"]" },
221         { "contains", T_CONTAINS, 2, 3, "var, val, [mutuals]" },
222         { "infile", T_INFILE, 2, 3, "file, var, [values]" },
223         { "count", T_COUNT, 2, 3, "var, count, [op=operator]" },
224         { "isEmpty", T_ISEMPTY, 1, 1, "var" },
225 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
226         { "parseJson", T_PARSE_JSON, 2, 2, "var, into" },
227 #endif
228         { "load", T_LOAD, 1, 2, "feature, [ignore_errors=false]" },
229         { "include", T_INCLUDE, 1, 3, "file, [into, [silent]]" },
230         { "debug", T_DEBUG, 2, 2, "level, message" },
231         { "log", T_LOG, 1, 1, "message" },
232         { "message", T_MESSAGE, 1, 1, "message" },
233         { "warning", T_WARNING, 1, 1, "message" },
234         { "error", T_ERROR, 0, 1, "message" },
235         { "mkpath", T_MKPATH, 1, 1, "path" },
236         { "write_file", T_WRITE_FILE, 1, 3, "name, [content var, [append] [exe]]" },
237         { "touch", T_TOUCH, 2, 2, "file, reffile" },
238         { "cache", T_CACHE, 0, 3, "[var], [set|add|sub] [transient] [super|stash], [srcvar]" },
239         { "reload_properties", T_RELOAD_PROPERTIES, 0, 0, "" },
240     };
241     statics.functions.reserve((int)(sizeof(testInits)/sizeof(testInits[0])));
242     for (unsigned i = 0; i < sizeof(testInits)/sizeof(testInits[0]); ++i)
243         statics.functions.insert(ProKey(testInits[i].name), QMakeBuiltin(testInits[i]));
244 }
245 
isTrue(const ProString & str)246 static bool isTrue(const ProString &str)
247 {
248     return !str.compare(statics.strtrue, Qt::CaseInsensitive) || str.toInt();
249 }
250 
251 bool
getMemberArgs(const ProKey & func,int srclen,const ProStringList & args,int * start,int * end)252 QMakeEvaluator::getMemberArgs(const ProKey &func, int srclen, const ProStringList &args,
253                               int *start, int *end)
254 {
255     *start = 0, *end = 0;
256     if (args.count() >= 2) {
257         bool ok = true;
258         const ProString &start_str = args.at(1);
259         *start = start_str.toInt(&ok);
260         if (!ok) {
261             if (args.count() == 2) {
262                 int dotdot = start_str.indexOf(statics.strDotDot);
263                 if (dotdot != -1) {
264                     *start = start_str.left(dotdot).toInt(&ok);
265                     if (ok)
266                         *end = start_str.mid(dotdot+2).toInt(&ok);
267                 }
268             }
269             if (!ok) {
270                 ProStringRoUser u1(func, m_tmp1);
271                 ProStringRoUser u2(start_str, m_tmp2);
272                 evalError(fL1S("%1() argument 2 (start) '%2' invalid.").arg(u1.str(), u2.str()));
273                 return false;
274             }
275         } else {
276             *end = *start;
277             if (args.count() == 3)
278                 *end = args.at(2).toInt(&ok);
279             if (!ok) {
280                 ProStringRoUser u1(func, m_tmp1);
281                 ProStringRoUser u2(args.at(2), m_tmp2);
282                 evalError(fL1S("%1() argument 3 (end) '%2' invalid.").arg(u1.str(), u2.str()));
283                 return false;
284             }
285         }
286     }
287     if (*start < 0)
288         *start += srclen;
289     if (*end < 0)
290         *end += srclen;
291     if (*start < 0 || *start >= srclen || *end < 0 || *end >= srclen)
292         return false;
293     return true;
294 }
295 
296 QString
quoteValue(const ProString & val)297 QMakeEvaluator::quoteValue(const ProString &val)
298 {
299     QString ret;
300     ret.reserve(val.size());
301     const QChar *chars = val.constData();
302     bool quote = val.isEmpty();
303     bool escaping = false;
304     for (int i = 0, l = val.size(); i < l; i++) {
305         QChar c = chars[i];
306         ushort uc = c.unicode();
307         if (uc < 32) {
308             if (!escaping) {
309                 escaping = true;
310                 ret += QLatin1String("$$escape_expand(");
311             }
312             switch (uc) {
313             case '\r':
314                 ret += QLatin1String("\\\\r");
315                 break;
316             case '\n':
317                 ret += QLatin1String("\\\\n");
318                 break;
319             case '\t':
320                 ret += QLatin1String("\\\\t");
321                 break;
322             default:
323                 ret += QString::fromLatin1("\\\\x%1").arg(uc, 2, 16, QLatin1Char('0'));
324                 break;
325             }
326         } else {
327             if (escaping) {
328                 escaping = false;
329                 ret += QLatin1Char(')');
330             }
331             switch (uc) {
332             case '\\':
333                 ret += QLatin1String("\\\\");
334                 break;
335             case '"':
336                 ret += QLatin1String("\\\"");
337                 break;
338             case '\'':
339                 ret += QLatin1String("\\'");
340                 break;
341             case '$':
342                 ret += QLatin1String("\\$");
343                 break;
344             case '#':
345                 ret += QLatin1String("$${LITERAL_HASH}");
346                 break;
347             case 32:
348                 quote = true;
349                 Q_FALLTHROUGH();
350             default:
351                 ret += c;
352                 break;
353             }
354         }
355     }
356     if (escaping)
357         ret += QLatin1Char(')');
358     if (quote) {
359         ret.prepend(QLatin1Char('"'));
360         ret.append(QLatin1Char('"'));
361     }
362     return ret;
363 }
364 
365 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
366 static void addJsonValue(const QJsonValue &value, const QString &keyPrefix, ProValueMap *map);
367 
insertJsonKeyValue(const QString & key,const QStringList & values,ProValueMap * map)368 static void insertJsonKeyValue(const QString &key, const QStringList &values, ProValueMap *map)
369 {
370     map->insert(ProKey(key), ProStringList(values));
371 }
372 
addJsonArray(const QJsonArray & array,const QString & keyPrefix,ProValueMap * map)373 static void addJsonArray(const QJsonArray &array, const QString &keyPrefix, ProValueMap *map)
374 {
375     QStringList keys;
376     const int size = array.count();
377     keys.reserve(size);
378     for (int i = 0; i < size; ++i) {
379         const QString number = QString::number(i);
380         keys.append(number);
381         addJsonValue(array.at(i), keyPrefix + number, map);
382     }
383     insertJsonKeyValue(keyPrefix + QLatin1String("_KEYS_"), keys, map);
384 }
385 
addJsonObject(const QJsonObject & object,const QString & keyPrefix,ProValueMap * map)386 static void addJsonObject(const QJsonObject &object, const QString &keyPrefix, ProValueMap *map)
387 {
388     QStringList keys;
389     keys.reserve(object.size());
390     for (auto it = object.begin(), end = object.end(); it != end; ++it) {
391         const QString key = it.key();
392         keys.append(key);
393         addJsonValue(it.value(), keyPrefix + key, map);
394     }
395     insertJsonKeyValue(keyPrefix + QLatin1String("_KEYS_"), keys, map);
396 }
397 
addJsonValue(const QJsonValue & value,const QString & keyPrefix,ProValueMap * map)398 static void addJsonValue(const QJsonValue &value, const QString &keyPrefix, ProValueMap *map)
399 {
400     switch (value.type()) {
401     case QJsonValue::Bool:
402         insertJsonKeyValue(keyPrefix, QStringList() << (value.toBool() ? QLatin1String("true") : QLatin1String("false")), map);
403         break;
404     case QJsonValue::Double:
405         insertJsonKeyValue(keyPrefix, QStringList() << QString::number(value.toDouble()), map);
406         break;
407     case QJsonValue::String:
408         insertJsonKeyValue(keyPrefix, QStringList() << value.toString(), map);
409         break;
410     case QJsonValue::Array:
411         addJsonArray(value.toArray(), keyPrefix + QLatin1Char('.'), map);
412         break;
413     case QJsonValue::Object:
414         addJsonObject(value.toObject(), keyPrefix + QLatin1Char('.'), map);
415         break;
416     default:
417         break;
418     }
419 }
420 
421 struct ErrorPosition {
422     int line;
423     int column;
424 };
425 
calculateErrorPosition(const QByteArray & json,int offset)426 static ErrorPosition calculateErrorPosition(const QByteArray &json, int offset)
427 {
428     ErrorPosition pos = { 0, 0 };
429     offset--; // offset is 1-based, switching to 0-based
430     for (int i = 0; i < offset; ++i) {
431         switch (json.at(i)) {
432         case '\n':
433             pos.line++;
434             pos.column = 0;
435             break;
436         case '\r':
437             break;
438         case '\t':
439             pos.column = (pos.column + 8) & ~7;
440             break;
441         default:
442             pos.column++;
443             break;
444         }
445     }
446     // Lines and columns in text editors are 1-based:
447     pos.line++;
448     pos.column++;
449     return pos;
450 }
451 
parseJsonInto(const QByteArray & json,const QString & into,ProValueMap * value)452 QMakeEvaluator::VisitReturn QMakeEvaluator::parseJsonInto(const QByteArray &json, const QString &into, ProValueMap *value)
453 {
454     QJsonParseError error;
455     QJsonDocument document = QJsonDocument::fromJson(json, &error);
456     if (document.isNull()) {
457         if (error.error != QJsonParseError::NoError) {
458             ErrorPosition errorPos = calculateErrorPosition(json, error.offset);
459             evalError(fL1S("Error parsing JSON at %1:%2: %3")
460                       .arg(errorPos.line).arg(errorPos.column).arg(error.errorString()));
461         }
462         return QMakeEvaluator::ReturnFalse;
463     }
464 
465     QString currentKey = into + QLatin1Char('.');
466 
467     // top-level item is either an array or object
468     if (document.isArray())
469         addJsonArray(document.array(), currentKey, value);
470     else if (document.isObject())
471         addJsonObject(document.object(), currentKey, value);
472     else
473         return QMakeEvaluator::ReturnFalse;
474 
475     return QMakeEvaluator::ReturnTrue;
476 }
477 #endif
478 
479 QMakeEvaluator::VisitReturn
writeFile(const QString & ctx,const QString & fn,QIODevice::OpenMode mode,QMakeVfs::VfsFlags flags,const QString & contents)480 QMakeEvaluator::writeFile(const QString &ctx, const QString &fn, QIODevice::OpenMode mode,
481                           QMakeVfs::VfsFlags flags, const QString &contents)
482 {
483     int oldId = m_vfs->idForFileName(fn, flags | QMakeVfs::VfsAccessedOnly);
484     int id = m_vfs->idForFileName(fn, flags | QMakeVfs::VfsCreate);
485     QString errStr;
486     if (!m_vfs->writeFile(id, mode, flags, contents, &errStr)) {
487         evalError(fL1S("Cannot write %1file %2: %3")
488                   .arg(ctx, QDir::toNativeSeparators(fn), errStr));
489         return ReturnFalse;
490     }
491     if (oldId)
492         m_parser->discardFileFromCache(oldId);
493     return ReturnTrue;
494 }
495 
496 #if QT_CONFIG(process)
runProcess(QProcess * proc,const QString & command) const497 void QMakeEvaluator::runProcess(QProcess *proc, const QString &command) const
498 {
499     proc->setWorkingDirectory(currentDirectory());
500 # ifdef PROEVALUATOR_SETENV
501     if (!m_option->environment.isEmpty())
502         proc->setProcessEnvironment(m_option->environment);
503 # endif
504 # ifdef Q_OS_WIN
505     proc->setNativeArguments(QLatin1String("/v:off /s /c \"") + command + QLatin1Char('"'));
506     proc->start(m_option->getEnv(QLatin1String("COMSPEC")), QStringList());
507 # else
508     proc->start(QLatin1String("/bin/sh"), QStringList() << QLatin1String("-c") << command);
509 # endif
510     proc->waitForFinished(-1);
511 }
512 #endif
513 
getCommandOutput(const QString & args,int * exitCode) const514 QByteArray QMakeEvaluator::getCommandOutput(const QString &args, int *exitCode) const
515 {
516     QByteArray out;
517 #if QT_CONFIG(process)
518     QProcess proc;
519     runProcess(&proc, args);
520     *exitCode = (proc.exitStatus() == QProcess::NormalExit) ? proc.exitCode() : -1;
521     QByteArray errout = proc.readAllStandardError();
522 # ifdef PROEVALUATOR_FULL
523     // FIXME: Qt really should have the option to set forwarding per channel
524     fputs(errout.constData(), stderr);
525 # else
526     if (!errout.isEmpty()) {
527         if (errout.endsWith('\n'))
528             errout.chop(1);
529         m_handler->message(
530             QMakeHandler::EvalError | (m_cumulative ? QMakeHandler::CumulativeEvalMessage : 0),
531             QString::fromLocal8Bit(errout));
532     }
533 # endif
534     out = proc.readAllStandardOutput();
535 # ifdef Q_OS_WIN
536     // FIXME: Qt's line end conversion on sequential files should really be fixed
537     out.replace("\r\n", "\n");
538 # endif
539 #else
540     if (FILE *proc = QT_POPEN(QString(QLatin1String("cd ")
541                                + IoUtils::shellQuote(QDir::toNativeSeparators(currentDirectory()))
542                                + QLatin1String(" && ") + args).toLocal8Bit().constData(), QT_POPEN_READ)) {
543         while (!feof(proc)) {
544             char buff[10 * 1024];
545             int read_in = int(fread(buff, 1, sizeof(buff), proc));
546             if (!read_in)
547                 break;
548             out += QByteArray(buff, read_in);
549         }
550         int ec = QT_PCLOSE(proc);
551 # ifdef Q_OS_WIN
552         *exitCode = ec >= 0 ? ec : -1;
553 # else
554         *exitCode = WIFEXITED(ec) ? WEXITSTATUS(ec) : -1;
555 # endif
556     }
557 # ifdef Q_OS_WIN
558     out.replace("\r\n", "\n");
559 # endif
560 #endif
561     return out;
562 }
563 
populateDeps(const ProStringList & deps,const ProString & prefix,const ProStringList & suffixes,const ProString & priosfx,QHash<ProKey,QSet<ProKey>> & dependencies,ProValueMap & dependees,QMultiMap<int,ProString> & rootSet) const564 void QMakeEvaluator::populateDeps(
565         const ProStringList &deps, const ProString &prefix, const ProStringList &suffixes,
566         const ProString &priosfx,
567         QHash<ProKey, QSet<ProKey> > &dependencies, ProValueMap &dependees,
568         QMultiMap<int, ProString> &rootSet) const
569 {
570     for (const ProString &item : deps)
571         if (!dependencies.contains(item.toKey())) {
572             QSet<ProKey> &dset = dependencies[item.toKey()]; // Always create entry
573             ProStringList depends;
574             for (const ProString &suffix : suffixes)
575                 depends += values(ProKey(prefix + item + suffix));
576             if (depends.isEmpty()) {
577                 rootSet.insert(first(ProKey(prefix + item + priosfx)).toInt(), item);
578             } else {
579                 for (const ProString &dep : qAsConst(depends)) {
580                     dset.insert(dep.toKey());
581                     dependees[dep.toKey()] << item;
582                 }
583                 populateDeps(depends, prefix, suffixes, priosfx, dependencies, dependees, rootSet);
584             }
585         }
586 }
587 
filePathArg0(const ProStringList & args)588 QString QMakeEvaluator::filePathArg0(const ProStringList &args)
589 {
590     ProStringRoUser u1(args.at(0), m_tmp1);
591     QString fn = resolvePath(u1.str());
592     fn.detach();
593     return fn;
594 }
595 
filePathEnvArg0(const ProStringList & args)596 QString QMakeEvaluator::filePathEnvArg0(const ProStringList &args)
597 {
598     ProStringRoUser u1(args.at(0), m_tmp1);
599     QString fn = resolvePath(m_option->expandEnvVars(u1.str()));
600     fn.detach();
601     return fn;
602 }
603 
evaluateBuiltinExpand(const QMakeBuiltin & adef,const ProKey & func,const ProStringList & args,ProStringList & ret)604 QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinExpand(
605         const QMakeBuiltin &adef, const ProKey &func, const ProStringList &args, ProStringList &ret)
606 {
607     traceMsg("calling built-in $$%s(%s)", dbgKey(func), dbgSepStrList(args));
608     int asz = args.size() > 1 ? args.size() : args.at(0).isEmpty() ? 0 : 1;
609     if (asz < adef.minArgs || asz > adef.maxArgs) {
610         evalError(adef.usage);
611         return ReturnTrue;
612     }
613 
614     int func_t = adef.index;
615     switch (func_t) {
616     case E_BASENAME:
617     case E_DIRNAME:
618     case E_SECTION: {
619         bool regexp = false;
620         QString sep;
621         ProString var;
622         int beg = 0;
623         int end = -1;
624         if (func_t == E_SECTION) {
625             var = args[0];
626             sep = args.at(1).toQString();
627             beg = args.at(2).toInt();
628             if (args.count() == 4)
629                 end = args.at(3).toInt();
630         } else {
631             var = args[0];
632             regexp = true;
633             sep = QLatin1String("[\\\\/]");
634             if (func_t == E_DIRNAME)
635                 end = -2;
636             else
637                 beg = -1;
638         }
639         if (!var.isEmpty()) {
640             const auto strings = values(map(var));
641             if (regexp) {
642                 QRegExp sepRx(sep);
643                 for (const ProString &str : strings) {
644                     ProStringRwUser u1(str, m_tmp[m_toggle ^= 1]);
645                     ret << u1.extract(u1.str().section(sepRx, beg, end));
646                 }
647             } else {
648                 for (const ProString &str : strings) {
649                     ProStringRwUser u1(str, m_tmp1);
650                     ret << u1.extract(u1.str().section(sep, beg, end));
651                 }
652             }
653         }
654         break;
655     }
656     case E_SPRINTF: {
657         ProStringRwUser u1(args.at(0), m_tmp1);
658         QString tmp = u1.str();
659         for (int i = 1; i < args.count(); ++i)
660             tmp = tmp.arg(args.at(i).toQStringView());
661         ret << u1.extract(tmp);
662         break;
663     }
664     case E_FORMAT_NUMBER: {
665         int ibase = 10;
666         int obase = 10;
667         int width = 0;
668         bool zeropad = false;
669         bool leftalign = false;
670         enum { DefaultSign, PadSign, AlwaysSign } sign = DefaultSign;
671         if (args.count() >= 2) {
672             const auto opts = split_value_list(args.at(1).toQStringRef());
673             for (const ProString &opt : opts) {
674                 if (opt.startsWith(QLatin1String("ibase="))) {
675                     ibase = opt.mid(6).toInt();
676                 } else if (opt.startsWith(QLatin1String("obase="))) {
677                     obase = opt.mid(6).toInt();
678                 } else if (opt.startsWith(QLatin1String("width="))) {
679                     width = opt.mid(6).toInt();
680                 } else if (opt == QLatin1String("zeropad")) {
681                     zeropad = true;
682                 } else if (opt == QLatin1String("padsign")) {
683                     sign = PadSign;
684                 } else if (opt == QLatin1String("alwayssign")) {
685                     sign = AlwaysSign;
686                 } else if (opt == QLatin1String("leftalign")) {
687                     leftalign = true;
688                 } else {
689                     evalError(fL1S("format_number(): invalid format option %1.")
690                               .arg(opt.toQStringView()));
691                     goto allfail;
692                 }
693             }
694         }
695         if (args.at(0).contains(QLatin1Char('.'))) {
696             evalError(fL1S("format_number(): floats are currently not supported."));
697             break;
698         }
699         bool ok;
700         qlonglong num = args.at(0).toLongLong(&ok, ibase);
701         if (!ok) {
702             evalError(fL1S("format_number(): malformed number %2 for base %1.")
703                       .arg(ibase).arg(args.at(0).toQStringView()));
704             break;
705         }
706         QString outstr;
707         if (num < 0) {
708             num = -num;
709             outstr = QLatin1Char('-');
710         } else if (sign == AlwaysSign) {
711             outstr = QLatin1Char('+');
712         } else if (sign == PadSign) {
713             outstr = QLatin1Char(' ');
714         }
715         QString numstr = QString::number(num, obase);
716         int space = width - outstr.length() - numstr.length();
717         if (space <= 0) {
718             outstr += numstr;
719         } else if (leftalign) {
720             outstr += numstr + QString(space, QLatin1Char(' '));
721         } else if (zeropad) {
722             outstr += QString(space, QLatin1Char('0')) + numstr;
723         } else {
724             outstr.prepend(QString(space, QLatin1Char(' ')));
725             outstr += numstr;
726         }
727         ret += ProString(outstr);
728         break;
729     }
730     case E_NUM_ADD: {
731         qlonglong sum = 0;
732         for (const ProString &arg : qAsConst(args)) {
733             if (arg.contains(QLatin1Char('.'))) {
734                 evalError(fL1S("num_add(): floats are currently not supported."));
735                 goto allfail;
736             }
737             bool ok;
738             qlonglong num = arg.toLongLong(&ok);
739             if (!ok) {
740                 evalError(fL1S("num_add(): malformed number %1.")
741                           .arg(arg.toQStringView()));
742                 goto allfail;
743             }
744             sum += num;
745         }
746         ret += ProString(QString::number(sum));
747         break;
748     }
749     case E_JOIN: {
750         ProString glue, before, after;
751         if (args.count() >= 2)
752             glue = args.at(1);
753         if (args.count() >= 3)
754             before = args[2];
755         if (args.count() == 4)
756             after = args[3];
757         const ProStringList &var = values(map(args.at(0)));
758         if (!var.isEmpty()) {
759             int src = currentFileId();
760             for (const ProString &v : var)
761                 if (int s = v.sourceFile()) {
762                     src = s;
763                     break;
764                 }
765             ret << ProString(before + var.join(glue) + after).setSource(src);
766         }
767         break;
768     }
769     case E_SPLIT: {
770         ProStringRoUser u1(m_tmp1);
771         const QString &sep = (args.count() == 2) ? u1.set(args.at(1)) : statics.field_sep;
772         const auto vars = values(map(args.at(0)));
773         for (const ProString &var : vars) {
774             // FIXME: this is inconsistent with the "there are no empty strings" dogma.
775             const auto splits = var.toQStringRef().split(sep, Qt::KeepEmptyParts);
776             for (const auto &splt : splits)
777                 ret << ProString(splt).setSource(var);
778         }
779         break;
780     }
781     case E_MEMBER: {
782         const ProStringList &src = values(map(args.at(0)));
783         int start, end;
784         if (getMemberArgs(func, src.size(), args, &start, &end)) {
785             ret.reserve(qAbs(end - start) + 1);
786             if (start < end) {
787                 for (int i = start; i <= end && src.size() >= i; i++)
788                     ret += src.at(i);
789             } else {
790                 for (int i = start; i >= end && src.size() >= i && i >= 0; i--)
791                     ret += src.at(i);
792             }
793         }
794         break;
795     }
796     case E_STR_MEMBER: {
797         const ProString &src = args.at(0);
798         int start, end;
799         if (getMemberArgs(func, src.size(), args, &start, &end)) {
800             QString res;
801             res.reserve(qAbs(end - start) + 1);
802             if (start < end) {
803                 for (int i = start; i <= end && src.size() >= i; i++)
804                     res += src.at(i);
805             } else {
806                 for (int i = start; i >= end && src.size() >= i && i >= 0; i--)
807                     res += src.at(i);
808             }
809             ret += ProString(res);
810         }
811         break;
812     }
813     case E_FIRST:
814     case E_LAST: {
815         const ProStringList &var = values(map(args.at(0)));
816         if (!var.isEmpty()) {
817             if (func_t == E_FIRST)
818                 ret.append(var[0]);
819             else
820                 ret.append(var.last());
821         }
822         break;
823     }
824     case E_TAKE_FIRST:
825     case E_TAKE_LAST: {
826         ProStringList &var = valuesRef(map(args.at(0)));
827         if (!var.isEmpty()) {
828             if (func_t == E_TAKE_FIRST)
829                 ret.append(var.takeFirst());
830             else
831                 ret.append(var.takeLast());
832         }
833         break;
834     }
835     case E_SIZE:
836         ret.append(ProString(QString::number(values(map(args.at(0))).size())));
837         break;
838     case E_STR_SIZE:
839         ret.append(ProString(QString::number(args.at(0).size())));
840         break;
841     case E_CAT: {
842         bool blob = false;
843         bool lines = false;
844         bool singleLine = true;
845         if (args.count() > 1) {
846             if (!args.at(1).compare(QLatin1String("false"), Qt::CaseInsensitive))
847                 singleLine = false;
848             else if (!args.at(1).compare(QLatin1String("blob"), Qt::CaseInsensitive))
849                 blob = true;
850             else if (!args.at(1).compare(QLatin1String("lines"), Qt::CaseInsensitive))
851                 lines = true;
852         }
853         QString fn = filePathEnvArg0(args);
854         QFile qfile(fn);
855         if (qfile.open(QIODevice::ReadOnly)) {
856             QTextStream stream(&qfile);
857             if (blob) {
858                 ret += ProString(stream.readAll());
859             } else {
860                 while (!stream.atEnd()) {
861                     if (lines) {
862                         ret += ProString(stream.readLine());
863                     } else {
864                         const QString &line = stream.readLine();
865                         ret += split_value_list(QStringRef(&line).trimmed());
866                         if (!singleLine)
867                             ret += ProString("\n");
868                     }
869                 }
870             }
871         }
872         break;
873     }
874     case E_FROMFILE: {
875         ProValueMap vars;
876         QString fn = filePathEnvArg0(args);
877         if (evaluateFileInto(fn, &vars, LoadProOnly) == ReturnTrue)
878             ret = vars.value(map(args.at(1)));
879         break;
880     }
881     case E_EVAL:
882         ret += values(map(args.at(0)));
883         break;
884     case E_LIST: {
885         QString tmp(QString::asprintf(".QMAKE_INTERNAL_TMP_variableName_%d", m_listCount++));
886         ret = ProStringList(ProString(tmp));
887         ProStringList lst;
888         for (const ProString &arg : args)
889             lst += split_value_list(arg.toQStringRef(), arg.sourceFile()); // Relies on deep copy
890         m_valuemapStack.top()[ret.at(0).toKey()] = lst;
891         break; }
892     case E_FIND: {
893         QRegExp regx(args.at(1).toQString());
894         const auto vals = values(map(args.at(0)));
895         for (const ProString &val : vals) {
896             ProStringRoUser u1(val, m_tmp[m_toggle ^= 1]);
897             if (regx.indexIn(u1.str()) != -1)
898                 ret += val;
899         }
900         break;
901     }
902     case E_SYSTEM: {
903         if (m_skipLevel)
904             break;
905         bool blob = false;
906         bool lines = false;
907         bool singleLine = true;
908         if (args.count() > 1) {
909             if (!args.at(1).compare(QLatin1String("false"), Qt::CaseInsensitive))
910                 singleLine = false;
911             else if (!args.at(1).compare(QLatin1String("blob"), Qt::CaseInsensitive))
912                 blob = true;
913             else if (!args.at(1).compare(QLatin1String("lines"), Qt::CaseInsensitive))
914                 lines = true;
915         }
916         int exitCode;
917         QByteArray bytes = getCommandOutput(args.at(0).toQString(), &exitCode);
918         if (args.count() > 2 && !args.at(2).isEmpty()) {
919             m_valuemapStack.top()[args.at(2).toKey()] =
920                     ProStringList(ProString(QString::number(exitCode)));
921         }
922         if (lines) {
923             QTextStream stream(bytes);
924             while (!stream.atEnd())
925                 ret += ProString(stream.readLine());
926         } else {
927             QString output = QString::fromLocal8Bit(bytes);
928             if (blob) {
929                 ret += ProString(output);
930             } else {
931                 output.replace(QLatin1Char('\t'), QLatin1Char(' '));
932                 if (singleLine)
933                     output.replace(QLatin1Char('\n'), QLatin1Char(' '));
934                 ret += split_value_list(QStringRef(&output));
935             }
936         }
937         break;
938     }
939     case E_UNIQUE:
940         ret = values(map(args.at(0)));
941         ret.removeDuplicates();
942         break;
943     case E_SORTED:
944         ret = values(map(args.at(0)));
945         std::sort(ret.begin(), ret.end());
946         break;
947     case E_REVERSE: {
948         ProStringList var = values(args.at(0).toKey());
949         for (int i = 0; i < var.size() / 2; i++)
950             qSwap(var[i], var[var.size() - i - 1]);
951         ret += var;
952         break;
953     }
954     case E_QUOTE:
955         ret += args;
956         break;
957     case E_ESCAPE_EXPAND:
958         for (int i = 0; i < args.size(); ++i) {
959             QString str = args.at(i).toQString();
960             QChar *i_data = str.data();
961             int i_len = str.length();
962             for (int x = 0; x < i_len; ++x) {
963                 if (*(i_data+x) == QLatin1Char('\\') && x < i_len-1) {
964                     if (*(i_data+x+1) == QLatin1Char('\\')) {
965                         ++x;
966                     } else {
967                         struct {
968                             char in, out;
969                         } mapped_quotes[] = {
970                             { 'n', '\n' },
971                             { 't', '\t' },
972                             { 'r', '\r' },
973                             { 0, 0 }
974                         };
975                         for (int i = 0; mapped_quotes[i].in; ++i) {
976                             if (*(i_data+x+1) == QLatin1Char(mapped_quotes[i].in)) {
977                                 *(i_data+x) = QLatin1Char(mapped_quotes[i].out);
978                                 if (x < i_len-2)
979                                     memmove(i_data+x+1, i_data+x+2, (i_len-x-2)*sizeof(QChar));
980                                 --i_len;
981                                 break;
982                             }
983                         }
984                     }
985                 }
986             }
987             ret.append(ProString(QString(i_data, i_len)).setSource(args.at(i)));
988         }
989         break;
990     case E_RE_ESCAPE:
991         for (int i = 0; i < args.size(); ++i) {
992             ProStringRwUser u1(args.at(i), m_tmp1);
993             ret << u1.extract(QRegExp::escape(u1.str()));
994         }
995         break;
996     case E_VAL_ESCAPE: {
997         const ProStringList &vals = values(args.at(0).toKey());
998         ret.reserve(vals.size());
999         for (const ProString &str : vals)
1000             ret += ProString(quoteValue(str));
1001         break;
1002     }
1003     case E_UPPER:
1004     case E_LOWER:
1005     case E_TITLE:
1006         for (int i = 0; i < args.count(); ++i) {
1007             ProStringRwUser u1(args.at(i), m_tmp1);
1008             QString rstr = u1.str();
1009             if (func_t == E_UPPER) {
1010                 rstr = rstr.toUpper();
1011             } else {
1012                 rstr = rstr.toLower();
1013                 if (func_t == E_TITLE && rstr.length() > 0)
1014                     rstr[0] = rstr.at(0).toTitleCase();
1015             }
1016             ret << u1.extract(rstr);
1017         }
1018         break;
1019     case E_FILES: {
1020         bool recursive = false;
1021         if (args.count() == 2)
1022             recursive = isTrue(args.at(1));
1023         QStringList dirs;
1024         ProStringRoUser u1(args.at(0), m_tmp1);
1025         QString r = m_option->expandEnvVars(u1.str())
1026                     .replace(QLatin1Char('\\'), QLatin1Char('/'));
1027         QString pfx;
1028         if (IoUtils::isRelativePath(r)) {
1029             pfx = currentDirectory();
1030             if (!pfx.endsWith(QLatin1Char('/')))
1031                 pfx += QLatin1Char('/');
1032         }
1033         int slash = r.lastIndexOf(QLatin1Char('/'));
1034         if (slash != -1) {
1035             dirs.append(r.left(slash+1));
1036             r = r.mid(slash+1);
1037         } else {
1038             dirs.append(QString());
1039         }
1040 
1041         r.detach(); // Keep m_tmp out of QRegExp's cache
1042         QRegExp regex(r, Qt::CaseSensitive, QRegExp::Wildcard);
1043         for (int d = 0; d < dirs.count(); d++) {
1044             QString dir = dirs[d];
1045             QDir qdir(pfx + dir);
1046             for (int i = 0, count = int(qdir.count()); i < count; ++i) {
1047                 if (qdir[i] == statics.strDot || qdir[i] == statics.strDotDot)
1048                     continue;
1049                 QString fname = dir + qdir[i];
1050                 if (IoUtils::fileType(pfx + fname) == IoUtils::FileIsDir) {
1051                     if (recursive)
1052                         dirs.append(fname + QLatin1Char('/'));
1053                 }
1054                 if (regex.exactMatch(qdir[i]))
1055                       ret += ProString(fname).setSource(currentFileId());
1056             }
1057         }
1058         break;
1059     }
1060 #ifdef PROEVALUATOR_FULL
1061     case E_PROMPT: {
1062         ProStringRoUser u1(args.at(0), m_tmp1);
1063         QString msg = m_option->expandEnvVars(u1.str());
1064         bool decorate = true;
1065         if (args.count() == 2)
1066             decorate = isTrue(args.at(1));
1067         if (decorate) {
1068             if (!msg.endsWith(QLatin1Char('?')))
1069                 msg += QLatin1Char('?');
1070             fprintf(stderr, "Project PROMPT: %s ", qPrintable(msg));
1071         } else {
1072             fputs(qPrintable(msg), stderr);
1073         }
1074         QFile qfile;
1075         if (qfile.open(stdin, QIODevice::ReadOnly)) {
1076             QTextStream t(&qfile);
1077             const QString &line = t.readLine();
1078             if (t.atEnd()) {
1079                 fputs("\n", stderr);
1080                 evalError(fL1S("Unexpected EOF."));
1081                 return ReturnError;
1082             }
1083             ret = split_value_list(QStringRef(&line));
1084         }
1085         break;
1086     }
1087 #endif
1088     case E_REPLACE: {
1089         const QRegExp before(args.at(1).toQString());
1090         ProStringRwUser u2(args.at(2), m_tmp2);
1091         const QString &after = u2.str();
1092         const auto vals = values(map(args.at(0)));
1093         for (const ProString &val : vals) {
1094             ProStringRwUser u1(val, m_tmp1);
1095             QString rstr = u1.str();
1096             QString copy = rstr; // Force a detach on modify
1097             rstr.replace(before, after);
1098             ret << u1.extract(rstr, u2);
1099         }
1100         break;
1101     }
1102     case E_SORT_DEPENDS:
1103     case E_RESOLVE_DEPENDS: {
1104         QHash<ProKey, QSet<ProKey> > dependencies;
1105         ProValueMap dependees;
1106         QMultiMap<int, ProString> rootSet;
1107         ProStringList orgList = values(args.at(0).toKey());
1108         ProString prefix = args.count() < 2 ? ProString() : args.at(1);
1109         ProString priosfx = args.count() < 4 ? ProString(".priority") : args.at(3);
1110         populateDeps(orgList, prefix,
1111                      args.count() < 3 ? ProStringList(ProString(".depends"))
1112                                       : split_value_list(args.at(2).toQStringRef()),
1113                      priosfx, dependencies, dependees, rootSet);
1114         while (!rootSet.isEmpty()) {
1115             QMultiMap<int, ProString>::iterator it = rootSet.begin();
1116             const ProString item = *it;
1117             rootSet.erase(it);
1118             if ((func_t == E_RESOLVE_DEPENDS) || orgList.contains(item))
1119                 ret.prepend(item);
1120             for (const ProString &dep : qAsConst(dependees[item.toKey()])) {
1121                 QSet<ProKey> &dset = dependencies[dep.toKey()];
1122                 dset.remove(item.toKey());
1123                 if (dset.isEmpty())
1124                     rootSet.insert(first(ProKey(prefix + dep + priosfx)).toInt(), dep);
1125             }
1126         }
1127         break;
1128     }
1129     case E_ENUMERATE_VARS: {
1130         QSet<ProString> keys;
1131         for (const ProValueMap &vmap : qAsConst(m_valuemapStack))
1132             for (ProValueMap::ConstIterator it = vmap.constBegin(); it != vmap.constEnd(); ++it)
1133                 keys.insert(it.key());
1134         ret.reserve(keys.size());
1135         for (const ProString &key : qAsConst(keys))
1136             ret << key;
1137         break; }
1138     case E_SHADOWED: {
1139         ProStringRwUser u1(args.at(0), m_tmp1);
1140         QString rstr = m_option->shadowedPath(resolvePath(u1.str()));
1141         if (!rstr.isEmpty())
1142             ret << u1.extract(rstr);
1143         break;
1144     }
1145     case E_ABSOLUTE_PATH: {
1146         ProStringRwUser u1(args.at(0), m_tmp1);
1147         ProStringRwUser u2(m_tmp2);
1148         QString baseDir = args.count() > 1
1149                 ? IoUtils::resolvePath(currentDirectory(), u2.set(args.at(1)))
1150                 : currentDirectory();
1151         QString rstr = u1.str().isEmpty() ? baseDir : IoUtils::resolvePath(baseDir, u1.str());
1152         ret << u1.extract(rstr, u2);
1153         break;
1154     }
1155     case E_RELATIVE_PATH: {
1156         ProStringRwUser u1(args.at(0), m_tmp1);
1157         ProStringRoUser u2(m_tmp2);
1158         QString baseDir = args.count() > 1
1159                 ? IoUtils::resolvePath(currentDirectory(), u2.set(args.at(1)))
1160                 : currentDirectory();
1161         QString absArg = u1.str().isEmpty() ? baseDir : IoUtils::resolvePath(baseDir, u1.str());
1162         QString rstr = QDir(baseDir).relativeFilePath(absArg);
1163         ret << u1.extract(rstr);
1164         break;
1165     }
1166     case E_CLEAN_PATH: {
1167         ProStringRwUser u1(args.at(0), m_tmp1);
1168         ret << u1.extract(QDir::cleanPath(u1.str()));
1169         break;
1170     }
1171     case E_SYSTEM_PATH: {
1172         ProStringRwUser u1(args.at(0), m_tmp1);
1173         QString rstr = u1.str();
1174 #ifdef Q_OS_WIN
1175         rstr.replace(QLatin1Char('/'), QLatin1Char('\\'));
1176 #else
1177         rstr.replace(QLatin1Char('\\'), QLatin1Char('/'));
1178 #endif
1179         ret << u1.extract(rstr);
1180         break;
1181     }
1182     case E_SHELL_PATH: {
1183         ProStringRwUser u1(args.at(0), m_tmp1);
1184         QString rstr = u1.str();
1185         if (m_dirSep.startsWith(QLatin1Char('\\'))) {
1186             rstr.replace(QLatin1Char('/'), QLatin1Char('\\'));
1187         } else {
1188             rstr.replace(QLatin1Char('\\'), QLatin1Char('/'));
1189 #ifdef Q_OS_WIN
1190             // Convert d:/foo/bar to msys-style /d/foo/bar.
1191             if (rstr.length() > 2 && rstr.at(1) == QLatin1Char(':') && rstr.at(2) == QLatin1Char('/')) {
1192                 rstr[1] = rstr.at(0);
1193                 rstr[0] = QLatin1Char('/');
1194             }
1195 #endif
1196         }
1197         ret << u1.extract(rstr);
1198         break;
1199     }
1200     case E_SYSTEM_QUOTE: {
1201         ProStringRwUser u1(args.at(0), m_tmp1);
1202         ret << u1.extract(IoUtils::shellQuote(u1.str()));
1203         break;
1204     }
1205     case E_SHELL_QUOTE: {
1206         ProStringRwUser u1(args.at(0), m_tmp1);
1207         QString rstr = u1.str();
1208         if (m_dirSep.startsWith(QLatin1Char('\\')))
1209             rstr = IoUtils::shellQuoteWin(rstr);
1210         else
1211             rstr = IoUtils::shellQuoteUnix(rstr);
1212         ret << u1.extract(rstr);
1213         break;
1214     }
1215     case E_GETENV: {
1216         ProStringRoUser u1(args.at(0), m_tmp1);
1217         ret << ProString(m_option->getEnv(u1.str()));
1218         break;
1219     }
1220 #ifdef Q_OS_WIN
1221     case E_READ_REGISTRY: {
1222         HKEY tree;
1223         const auto par = args.at(0);
1224         if (!par.compare(QLatin1String("HKCU"), Qt::CaseInsensitive)
1225                 || !par.compare(QLatin1String("HKEY_CURRENT_USER"), Qt::CaseInsensitive)) {
1226             tree = HKEY_CURRENT_USER;
1227         } else if (!par.compare(QLatin1String("HKLM"), Qt::CaseInsensitive)
1228                 || !par.compare(QLatin1String("HKEY_LOCAL_MACHINE"), Qt::CaseInsensitive)) {
1229             tree = HKEY_LOCAL_MACHINE;
1230         } else {
1231             evalError(fL1S("read_registry(): invalid or unsupported registry tree %1.")
1232                       .arg(par.toQStringView()));
1233             goto allfail;
1234         }
1235         int flags = 0;
1236         if (args.count() > 2) {
1237             const auto opt = args.at(2);
1238             if (opt == "32"
1239                     || !opt.compare(QLatin1String("wow64_32key"), Qt::CaseInsensitive)) {
1240                 flags = KEY_WOW64_32KEY;
1241             } else if (opt == "64"
1242                     || !opt.compare(QLatin1String("wow64_64key"), Qt::CaseInsensitive)) {
1243                 flags = KEY_WOW64_64KEY;
1244             } else {
1245                 evalError(fL1S("read_registry(): invalid option %1.")
1246                           .arg(opt.toQStringView()));
1247                 goto allfail;
1248             }
1249         }
1250         ret << ProString(qt_readRegistryKey(tree, args.at(1).toQString(m_tmp1), flags));
1251         break;
1252     }
1253 #endif
1254     default:
1255         evalError(fL1S("Function '%1' is not implemented.").arg(func.toQStringView()));
1256         break;
1257     }
1258 
1259   allfail:
1260     return ReturnTrue;
1261 }
1262 
testFunc_cache(const ProStringList & args)1263 QMakeEvaluator::VisitReturn QMakeEvaluator::testFunc_cache(const ProStringList &args)
1264 {
1265     bool persist = true;
1266     enum { TargetStash, TargetCache, TargetSuper } target = TargetCache;
1267     enum { CacheSet, CacheAdd, CacheSub } mode = CacheSet;
1268     ProKey srcvar;
1269     if (args.count() >= 2) {
1270         const auto opts = split_value_list(args.at(1).toQStringRef());
1271         for (const ProString &opt : opts) {
1272             if (opt == QLatin1String("transient")) {
1273                 persist = false;
1274             } else if (opt == QLatin1String("super")) {
1275                 target = TargetSuper;
1276             } else if (opt == QLatin1String("stash")) {
1277                 target = TargetStash;
1278             } else if (opt == QLatin1String("set")) {
1279                 mode = CacheSet;
1280             } else if (opt == QLatin1String("add")) {
1281                 mode = CacheAdd;
1282             } else if (opt == QLatin1String("sub")) {
1283                 mode = CacheSub;
1284             } else {
1285                 evalError(fL1S("cache(): invalid flag %1.").arg(opt.toQStringView()));
1286                 return ReturnFalse;
1287             }
1288         }
1289         if (args.count() >= 3) {
1290             srcvar = args.at(2).toKey();
1291         } else if (mode != CacheSet) {
1292             evalError(fL1S("cache(): modes other than 'set' require a source variable."));
1293             return ReturnFalse;
1294         }
1295     }
1296     QString varstr;
1297     ProKey dstvar = args.at(0).toKey();
1298     if (!dstvar.isEmpty()) {
1299         if (srcvar.isEmpty())
1300             srcvar = dstvar;
1301         ProValueMap::Iterator srcvarIt;
1302         if (!findValues(srcvar, &srcvarIt)) {
1303             evalError(fL1S("Variable %1 is not defined.").arg(srcvar.toQStringView()));
1304             return ReturnFalse;
1305         }
1306         // The caches for the host and target may differ (e.g., when we are manipulating
1307         // CONFIG), so we cannot compute a common new value for both.
1308         const ProStringList &diffval = *srcvarIt;
1309         ProStringList newval;
1310         bool changed = false;
1311         for (bool hostBuild = false; ; hostBuild = true) {
1312 #ifdef PROEVALUATOR_THREAD_SAFE
1313             m_option->mutex.lock();
1314 #endif
1315             QMakeBaseEnv *baseEnv =
1316                 m_option->baseEnvs.value(QMakeBaseKey(m_buildRoot, m_stashfile, hostBuild));
1317 #ifdef PROEVALUATOR_THREAD_SAFE
1318             // It's ok to unlock this before locking baseEnv,
1319             // as we have no intention to initialize the env.
1320             m_option->mutex.unlock();
1321 #endif
1322             do {
1323                 if (!baseEnv)
1324                     break;
1325 #ifdef PROEVALUATOR_THREAD_SAFE
1326                 QMutexLocker locker(&baseEnv->mutex);
1327                 if (baseEnv->inProgress && baseEnv->evaluator != this) {
1328                     // The env is still in the works, but it may be already past the cache
1329                     // loading. So we need to wait for completion and amend it as usual.
1330                     QThreadPool::globalInstance()->releaseThread();
1331                     baseEnv->cond.wait(&baseEnv->mutex);
1332                     QThreadPool::globalInstance()->reserveThread();
1333                 }
1334                 if (!baseEnv->isOk)
1335                     break;
1336 #endif
1337                 QMakeEvaluator *baseEval = baseEnv->evaluator;
1338                 const ProStringList &oldval = baseEval->values(dstvar);
1339                 if (mode == CacheSet) {
1340                     newval = diffval;
1341                 } else {
1342                     newval = oldval;
1343                     if (mode == CacheAdd)
1344                         newval += diffval;
1345                     else
1346                         newval.removeEach(diffval);
1347                 }
1348                 if (oldval != newval) {
1349                     if (target != TargetStash || !m_stashfile.isEmpty()) {
1350                         baseEval->valuesRef(dstvar) = newval;
1351                         if (target == TargetSuper) {
1352                             do {
1353                                 if (dstvar == QLatin1String("QMAKEPATH")) {
1354                                     baseEval->m_qmakepath = newval.toQStringList();
1355                                     baseEval->updateMkspecPaths();
1356                                 } else if (dstvar == QLatin1String("QMAKEFEATURES")) {
1357                                     baseEval->m_qmakefeatures = newval.toQStringList();
1358                                 } else {
1359                                     break;
1360                                 }
1361                                 baseEval->updateFeaturePaths();
1362                                 if (hostBuild == m_hostBuild)
1363                                     m_featureRoots = baseEval->m_featureRoots;
1364                             } while (false);
1365                         }
1366                     }
1367                     changed = true;
1368                 }
1369             } while (false);
1370             if (hostBuild)
1371                 break;
1372         }
1373         // We assume that whatever got the cached value to be what it is now will do so
1374         // the next time as well, so we just skip the persisting if nothing changed.
1375         if (!persist || !changed)
1376             return ReturnTrue;
1377         varstr = dstvar.toQString();
1378         if (mode == CacheAdd)
1379             varstr += QLatin1String(" +=");
1380         else if (mode == CacheSub)
1381             varstr += QLatin1String(" -=");
1382         else
1383             varstr += QLatin1String(" =");
1384         if (diffval.count() == 1) {
1385             varstr += QLatin1Char(' ');
1386             varstr += quoteValue(diffval.at(0));
1387         } else if (!diffval.isEmpty()) {
1388             for (const ProString &vval : diffval) {
1389                 varstr += QLatin1String(" \\\n    ");
1390                 varstr += quoteValue(vval);
1391             }
1392         }
1393         varstr += QLatin1Char('\n');
1394     }
1395     QString fn;
1396     QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1397     if (target == TargetSuper) {
1398         if (m_superfile.isEmpty()) {
1399             m_superfile = QDir::cleanPath(m_outputDir + QLatin1String("/.qmake.super"));
1400             printf("Info: creating super cache file %s\n", qPrintable(QDir::toNativeSeparators(m_superfile)));
1401             valuesRef(ProKey("_QMAKE_SUPER_CACHE_")) << ProString(m_superfile);
1402         }
1403         fn = m_superfile;
1404     } else if (target == TargetCache) {
1405         if (m_cachefile.isEmpty()) {
1406             m_cachefile = QDir::cleanPath(m_outputDir + QLatin1String("/.qmake.cache"));
1407             printf("Info: creating cache file %s\n", qPrintable(QDir::toNativeSeparators(m_cachefile)));
1408             valuesRef(ProKey("_QMAKE_CACHE_")) << ProString(m_cachefile);
1409             // We could update m_{source,build}Root and m_featureRoots here, or even
1410             // "re-home" our rootEnv, but this doesn't sound too useful - if somebody
1411             // wanted qmake to find something in the build directory, he could have
1412             // done so "from the outside".
1413             // The sub-projects will find the new cache all by themselves.
1414         }
1415         fn = m_cachefile;
1416     } else {
1417         fn = m_stashfile;
1418         if (fn.isEmpty())
1419             fn = QDir::cleanPath(m_outputDir + QLatin1String("/.qmake.stash"));
1420         if (!m_vfs->exists(fn, flags)) {
1421             printf("Info: creating stash file %s\n", qPrintable(QDir::toNativeSeparators(fn)));
1422             valuesRef(ProKey("_QMAKE_STASH_")) << ProString(fn);
1423         }
1424     }
1425     return writeFile(fL1S("cache "), fn, QIODevice::Append, flags, varstr);
1426 }
1427 
evaluateBuiltinConditional(const QMakeInternal::QMakeBuiltin & adef,const ProKey & function,const ProStringList & args)1428 QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional(
1429         const QMakeInternal::QMakeBuiltin &adef, const ProKey &function, const ProStringList &args)
1430 {
1431     traceMsg("calling built-in %s(%s)", dbgKey(function), dbgSepStrList(args));
1432     int asz = args.size() > 1 ? args.size() : args.at(0).isEmpty() ? 0 : 1;
1433     if (asz < adef.minArgs || asz > adef.maxArgs) {
1434         evalError(adef.usage);
1435         return ReturnFalse;
1436     }
1437 
1438     int func_t = adef.index;
1439     switch (func_t) {
1440     case T_DEFINED: {
1441         const ProKey &var = args.at(0).toKey();
1442         if (args.count() > 1) {
1443             if (args[1] == QLatin1String("test")) {
1444                 return returnBool(m_functionDefs.testFunctions.contains(var));
1445             } else if (args[1] == QLatin1String("replace")) {
1446                 return returnBool(m_functionDefs.replaceFunctions.contains(var));
1447             } else if (args[1] == QLatin1String("var")) {
1448                 ProValueMap::Iterator it;
1449                 return returnBool(findValues(var, &it));
1450             }
1451             evalError(fL1S("defined(function, type): unexpected type [%1].")
1452                       .arg(args.at(1).toQStringView()));
1453             return ReturnFalse;
1454         }
1455         return returnBool(m_functionDefs.replaceFunctions.contains(var)
1456                           || m_functionDefs.testFunctions.contains(var));
1457     }
1458     case T_EXPORT: {
1459         const ProKey &var = map(args.at(0));
1460         for (ProValueMapStack::iterator vmi = m_valuemapStack.end();
1461              --vmi != m_valuemapStack.begin(); ) {
1462             ProValueMap::Iterator it = (*vmi).find(var);
1463             if (it != (*vmi).end()) {
1464                 if (it->constBegin() == statics.fakeValue.constBegin()) {
1465                     // This is stupid, but qmake doesn't propagate deletions
1466                     m_valuemapStack.front()[var] = ProStringList();
1467                 } else {
1468                     m_valuemapStack.front()[var] = *it;
1469                 }
1470                 (*vmi).erase(it);
1471                 while (--vmi != m_valuemapStack.begin())
1472                     (*vmi).remove(var);
1473                 break;
1474             }
1475         }
1476         return ReturnTrue;
1477     }
1478     case T_DISCARD_FROM: {
1479         if (m_valuemapStack.size() != 1) {
1480             evalError(fL1S("discard_from() cannot be called from functions."));
1481             return ReturnFalse;
1482         }
1483         ProStringRoUser u1(args.at(0), m_tmp1);
1484         QString fn = resolvePath(u1.str());
1485         QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1486         int pro = m_vfs->idForFileName(fn, flags | QMakeVfs::VfsAccessedOnly);
1487         if (!pro)
1488             return ReturnFalse;
1489         ProValueMap &vmap = m_valuemapStack.front();
1490         for (auto vit = vmap.begin(); vit != vmap.end(); ) {
1491             if (!vit->isEmpty()) {
1492                 auto isFrom = [pro](const ProString &s) {
1493                     return s.sourceFile() == pro;
1494                 };
1495                 vit->erase(std::remove_if(vit->begin(), vit->end(), isFrom), vit->end());
1496                 if (vit->isEmpty()) {
1497                     // When an initially non-empty variable becomes entirely empty,
1498                     // undefine it altogether.
1499                     vit = vmap.erase(vit);
1500                     continue;
1501                 }
1502             }
1503             ++vit;
1504         }
1505         for (auto fit = m_functionDefs.testFunctions.begin(); fit != m_functionDefs.testFunctions.end(); ) {
1506             if (fit->pro()->id() == pro)
1507                 fit = m_functionDefs.testFunctions.erase(fit);
1508             else
1509                 ++fit;
1510         }
1511         for (auto fit = m_functionDefs.replaceFunctions.begin(); fit != m_functionDefs.replaceFunctions.end(); ) {
1512             if (fit->pro()->id() == pro)
1513                 fit = m_functionDefs.replaceFunctions.erase(fit);
1514             else
1515                 ++fit;
1516         }
1517         ProStringList &iif = m_valuemapStack.front()[ProKey("QMAKE_INTERNAL_INCLUDED_FILES")];
1518         int idx = iif.indexOf(ProString(fn));
1519         if (idx >= 0)
1520             iif.removeAt(idx);
1521         return ReturnTrue;
1522     }
1523     case T_INFILE: {
1524         ProValueMap vars;
1525         QString fn = filePathEnvArg0(args);
1526         VisitReturn ok = evaluateFileInto(fn, &vars, LoadProOnly);
1527         if (ok != ReturnTrue)
1528             return ok;
1529         if (args.count() == 2)
1530             return returnBool(vars.contains(map(args.at(1))));
1531         QRegExp regx;
1532         ProStringRoUser u1(args.at(2), m_tmp1);
1533         const QString &qry = u1.str();
1534         if (qry != QRegExp::escape(qry)) {
1535             QString copy = qry;
1536             copy.detach();
1537             regx.setPattern(copy);
1538         }
1539         const auto strings = vars.value(map(args.at(1)));
1540         for (const ProString &s : strings) {
1541             if (s == qry)
1542                 return ReturnTrue;
1543             if (!regx.isEmpty()) {
1544                 ProStringRoUser u2(s, m_tmp[m_toggle ^= 1]);
1545                 if (regx.exactMatch(u2.str()))
1546                     return ReturnTrue;
1547             }
1548         }
1549         return ReturnFalse;
1550     }
1551     case T_REQUIRES:
1552 #ifdef PROEVALUATOR_FULL
1553         if (checkRequirements(args) == ReturnError)
1554             return ReturnError;
1555 #endif
1556         return ReturnFalse; // Another qmake breakage
1557     case T_EVAL: {
1558         VisitReturn ret = ReturnFalse;
1559         QString contents = args.join(statics.field_sep);
1560         ProFile *pro = m_parser->parsedProBlock(QStringRef(&contents),
1561                                                 0, m_current.pro->fileName(), m_current.line);
1562         if (m_cumulative || pro->isOk()) {
1563             m_locationStack.push(m_current);
1564             visitProBlock(pro, pro->tokPtr());
1565             ret = ReturnTrue; // This return value is not too useful, but that's qmake
1566             m_current = m_locationStack.pop();
1567         }
1568         pro->deref();
1569         return ret;
1570     }
1571     case T_IF: {
1572         return evaluateConditional(args.at(0).toQStringRef(),
1573                                    m_current.pro->fileName(), m_current.line);
1574     }
1575     case T_CONFIG: {
1576         if (args.count() == 1)
1577             return returnBool(isActiveConfig(args.at(0).toQStringRef()));
1578         const auto mutuals = args.at(1).toQStringRef().split(QLatin1Char('|'),
1579                                                              Qt::SkipEmptyParts);
1580         const ProStringList &configs = values(statics.strCONFIG);
1581 
1582         for (int i = configs.size() - 1; i >= 0; i--) {
1583             for (int mut = 0; mut < mutuals.count(); mut++) {
1584                 if (configs[i].toQStringRef() == mutuals[mut].trimmed())
1585                     return returnBool(configs[i] == args[0]);
1586             }
1587         }
1588         return ReturnFalse;
1589     }
1590     case T_CONTAINS: {
1591         ProStringRoUser u1(args.at(1), m_tmp1);
1592         const QString &qry = u1.str();
1593         QRegExp regx;
1594         if (qry != QRegExp::escape(qry)) {
1595             QString copy = qry;
1596             copy.detach();
1597             regx.setPattern(copy);
1598         }
1599         const ProStringList &l = values(map(args.at(0)));
1600         if (args.count() == 2) {
1601             for (int i = 0; i < l.size(); ++i) {
1602                 const ProString &val = l[i];
1603                 if (val == qry)
1604                     return ReturnTrue;
1605                 if (!regx.isEmpty()) {
1606                     ProStringRoUser u2(val, m_tmp[m_toggle ^= 1]);
1607                     if (regx.exactMatch(u2.str()))
1608                         return ReturnTrue;
1609                 }
1610             }
1611         } else {
1612             const auto mutuals = args.at(2).toQStringRef().split(QLatin1Char('|'),
1613                                                                  Qt::SkipEmptyParts);
1614             for (int i = l.size() - 1; i >= 0; i--) {
1615                 const ProString &val = l[i];
1616                 for (int mut = 0; mut < mutuals.count(); mut++) {
1617                     if (val.toQStringRef() == mutuals[mut].trimmed()) {
1618                         if (val == qry)
1619                             return ReturnTrue;
1620                         if (!regx.isEmpty()) {
1621                             ProStringRoUser u2(val, m_tmp[m_toggle ^= 1]);
1622                             if (regx.exactMatch(u2.str()))
1623                                 return ReturnTrue;
1624                         }
1625                         return ReturnFalse;
1626                     }
1627                 }
1628             }
1629         }
1630         return ReturnFalse;
1631     }
1632     case T_COUNT: {
1633         int cnt = values(map(args.at(0))).count();
1634         int val = args.at(1).toInt();
1635         if (args.count() == 3) {
1636             const ProString &comp = args.at(2);
1637             if (comp == QLatin1String(">") || comp == QLatin1String("greaterThan")) {
1638                 return returnBool(cnt > val);
1639             } else if (comp == QLatin1String(">=")) {
1640                 return returnBool(cnt >= val);
1641             } else if (comp == QLatin1String("<") || comp == QLatin1String("lessThan")) {
1642                 return returnBool(cnt < val);
1643             } else if (comp == QLatin1String("<=")) {
1644                 return returnBool(cnt <= val);
1645             } else if (comp == QLatin1String("equals") || comp == QLatin1String("isEqual")
1646                        || comp == QLatin1String("=") || comp == QLatin1String("==")) {
1647                 // fallthrough
1648             } else {
1649                 evalError(fL1S("Unexpected modifier to count(%2).").arg(comp.toQStringView()));
1650                 return ReturnFalse;
1651             }
1652         }
1653         return returnBool(cnt == val);
1654     }
1655     case T_GREATERTHAN:
1656     case T_LESSTHAN: {
1657         const ProString &rhs = args.at(1);
1658         const QString &lhs = values(map(args.at(0))).join(statics.field_sep);
1659         bool ok;
1660         int rhs_int = rhs.toInt(&ok);
1661         if (ok) { // do integer compare
1662             int lhs_int = lhs.toInt(&ok);
1663             if (ok) {
1664                 if (func_t == T_GREATERTHAN)
1665                     return returnBool(lhs_int > rhs_int);
1666                 return returnBool(lhs_int < rhs_int);
1667             }
1668         }
1669         if (func_t == T_GREATERTHAN)
1670             return returnBool(lhs > rhs.toQStringRef());
1671         return returnBool(lhs < rhs.toQStringRef());
1672     }
1673     case T_EQUALS:
1674         return returnBool(values(map(args.at(0))).join(statics.field_sep)
1675                           == args.at(1).toQStringView());
1676     case T_VERSION_AT_LEAST:
1677     case T_VERSION_AT_MOST: {
1678         const QVersionNumber lvn = QVersionNumber::fromString(values(args.at(0).toKey()).join('.'));
1679         const QVersionNumber rvn = QVersionNumber::fromString(args.at(1).toQStringView());
1680         if (func_t == T_VERSION_AT_LEAST)
1681             return returnBool(lvn >= rvn);
1682         return returnBool(lvn <= rvn);
1683     }
1684     case T_CLEAR: {
1685         ProValueMap *hsh;
1686         ProValueMap::Iterator it;
1687         const ProKey &var = map(args.at(0));
1688         if (!(hsh = findValues(var, &it)))
1689             return ReturnFalse;
1690         if (hsh == &m_valuemapStack.top())
1691             it->clear();
1692         else
1693             m_valuemapStack.top()[var].clear();
1694         return ReturnTrue;
1695     }
1696     case T_UNSET: {
1697         ProValueMap *hsh;
1698         ProValueMap::Iterator it;
1699         const ProKey &var = map(args.at(0));
1700         if (!(hsh = findValues(var, &it)))
1701             return ReturnFalse;
1702         if (m_valuemapStack.size() == 1)
1703             hsh->erase(it);
1704         else if (hsh == &m_valuemapStack.top())
1705             *it = statics.fakeValue;
1706         else
1707             m_valuemapStack.top()[var] = statics.fakeValue;
1708         return ReturnTrue;
1709     }
1710 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
1711     case T_PARSE_JSON: {
1712         QByteArray json = values(args.at(0).toKey()).join(QLatin1Char(' ')).toUtf8();
1713         ProStringRoUser u1(args.at(1), m_tmp2);
1714         QString parseInto = u1.str();
1715         return parseJsonInto(json, parseInto, &m_valuemapStack.top());
1716     }
1717 #endif
1718     case T_INCLUDE: {
1719         QString parseInto;
1720         LoadFlags flags;
1721         if (m_cumulative)
1722             flags = LoadSilent;
1723         if (args.count() >= 2) {
1724             if (!args.at(1).isEmpty())
1725                 parseInto = args.at(1) + QLatin1Char('.');
1726             if (args.count() >= 3 && isTrue(args.at(2)))
1727                 flags = LoadSilent;
1728         }
1729         QString fn = filePathEnvArg0(args);
1730         VisitReturn ok;
1731         if (parseInto.isEmpty()) {
1732             ok = evaluateFileChecked(fn, QMakeHandler::EvalIncludeFile, LoadProOnly | flags);
1733         } else {
1734             ProValueMap symbols;
1735             if ((ok = evaluateFileInto(fn, &symbols, LoadAll | flags)) == ReturnTrue) {
1736                 ProValueMap newMap;
1737                 for (ProValueMap::ConstIterator
1738                         it = m_valuemapStack.top().constBegin(),
1739                         end = m_valuemapStack.top().constEnd();
1740                         it != end; ++it) {
1741                     const ProString &ky = it.key();
1742                     if (!ky.startsWith(parseInto))
1743                         newMap[it.key()] = it.value();
1744                 }
1745                 for (ProValueMap::ConstIterator it = symbols.constBegin();
1746                      it != symbols.constEnd(); ++it) {
1747                     if (!it.key().startsWith(QLatin1Char('.')))
1748                         newMap.insert(ProKey(parseInto + it.key()), it.value());
1749                 }
1750                 m_valuemapStack.top() = newMap;
1751             }
1752         }
1753         if (ok == ReturnFalse && (flags & LoadSilent))
1754             ok = ReturnTrue;
1755         return ok;
1756     }
1757     case T_LOAD: {
1758         bool ignore_error = (args.count() == 2 && isTrue(args.at(1)));
1759         VisitReturn ok = evaluateFeatureFile(m_option->expandEnvVars(args.at(0).toQString()),
1760                                              ignore_error);
1761         if (ok == ReturnFalse && ignore_error)
1762             ok = ReturnTrue;
1763         return ok;
1764     }
1765     case T_DEBUG: {
1766 #ifdef PROEVALUATOR_DEBUG
1767         int level = args.at(0).toInt();
1768         if (level <= m_debugLevel) {
1769             ProStringRoUser u1(args.at(1), m_tmp1);
1770             const QString &msg = m_option->expandEnvVars(u1.str());
1771             debugMsg(level, "Project DEBUG: %s", qPrintable(msg));
1772         }
1773 #endif
1774         return ReturnTrue;
1775     }
1776     case T_LOG:
1777     case T_ERROR:
1778     case T_WARNING:
1779     case T_MESSAGE: {
1780         ProStringRoUser u1(args.at(0), m_tmp1);
1781         const QString &msg = m_option->expandEnvVars(u1.str());
1782         if (!m_skipLevel) {
1783             if (func_t == T_LOG) {
1784 #ifdef PROEVALUATOR_FULL
1785                 fputs(msg.toLatin1().constData(), stderr);
1786 #endif
1787             } else if (!msg.isEmpty() || func_t != T_ERROR) {
1788                 ProStringRoUser u2(function, m_tmp2);
1789                 m_handler->fileMessage(
1790                         (func_t == T_ERROR   ? QMakeHandler::ErrorMessage :
1791                          func_t == T_WARNING ? QMakeHandler::WarningMessage :
1792                                                QMakeHandler::InfoMessage)
1793                         | (m_cumulative ? QMakeHandler::CumulativeEvalMessage : 0),
1794                         fL1S("Project %1: %2").arg(u2.str().toUpper(), msg));
1795             }
1796         }
1797         return (func_t == T_ERROR && !m_cumulative) ? ReturnError : ReturnTrue;
1798     }
1799     case T_SYSTEM: {
1800 #ifdef PROEVALUATOR_FULL
1801         if (m_cumulative) // Anything else would be insanity
1802             return ReturnFalse;
1803 #if QT_CONFIG(process)
1804         QProcess proc;
1805         proc.setProcessChannelMode(QProcess::ForwardedChannels);
1806         runProcess(&proc, args.at(0).toQString());
1807         return returnBool(proc.exitStatus() == QProcess::NormalExit && proc.exitCode() == 0);
1808 #else
1809         int ec = system((QLatin1String("cd ")
1810                          + IoUtils::shellQuote(QDir::toNativeSeparators(currentDirectory()))
1811                          + QLatin1String(" && ") + args.at(0)).toLocal8Bit().constData());
1812 #  ifdef Q_OS_UNIX
1813         if (ec != -1 && WIFSIGNALED(ec) && (WTERMSIG(ec) == SIGQUIT || WTERMSIG(ec) == SIGINT))
1814             raise(WTERMSIG(ec));
1815 #  endif
1816         return returnBool(ec == 0);
1817 #endif
1818 #else
1819         return ReturnTrue;
1820 #endif
1821     }
1822     case T_ISEMPTY: {
1823         return returnBool(values(map(args.at(0))).isEmpty());
1824     }
1825     case T_EXISTS: {
1826         QString file = filePathEnvArg0(args);
1827         // Don't use VFS here:
1828         // - it supports neither listing nor even directories
1829         // - it's unlikely that somebody would test for files they created themselves
1830         if (IoUtils::exists(file))
1831             return ReturnTrue;
1832         int slsh = file.lastIndexOf(QLatin1Char('/'));
1833         QString fn = file.mid(slsh+1);
1834         if (fn.contains(QLatin1Char('*')) || fn.contains(QLatin1Char('?'))) {
1835             QString dirstr = file.left(slsh+1);
1836             dirstr.detach();
1837             if (!QDir(dirstr).entryList(QStringList(fn)).isEmpty())
1838                 return ReturnTrue;
1839         }
1840 
1841         return ReturnFalse;
1842     }
1843     case T_MKPATH: {
1844 #ifdef PROEVALUATOR_FULL
1845         QString fn = filePathArg0(args);
1846         if (!QDir::current().mkpath(fn)) {
1847             evalError(fL1S("Cannot create directory %1.").arg(QDir::toNativeSeparators(fn)));
1848             return ReturnFalse;
1849         }
1850 #endif
1851         return ReturnTrue;
1852     }
1853     case T_WRITE_FILE: {
1854         QIODevice::OpenMode mode = QIODevice::Truncate;
1855         QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1856         QString contents;
1857         if (args.count() >= 2) {
1858             const ProStringList &vals = values(args.at(1).toKey());
1859             if (!vals.isEmpty())
1860                 contents = vals.join(QLatin1Char('\n')) + QLatin1Char('\n');
1861             if (args.count() >= 3) {
1862                 const auto opts = split_value_list(args.at(2).toQStringRef());
1863                 for (const ProString &opt : opts) {
1864                     if (opt == QLatin1String("append")) {
1865                         mode = QIODevice::Append;
1866                     } else if (opt == QLatin1String("exe")) {
1867                         flags |= QMakeVfs::VfsExecutable;
1868                     } else {
1869                         evalError(fL1S("write_file(): invalid flag %1.").arg(opt.toQStringView()));
1870                         return ReturnFalse;
1871                     }
1872                 }
1873             }
1874         }
1875         QString path = filePathArg0(args);
1876         return writeFile(QString(), path, mode, flags, contents);
1877     }
1878     case T_TOUCH: {
1879 #ifdef PROEVALUATOR_FULL
1880         ProStringRoUser u1(args.at(0), m_tmp1);
1881         ProStringRoUser u2(args.at(1), m_tmp2);
1882         const QString &tfn = resolvePath(u1.str());
1883         const QString &rfn = resolvePath(u2.str());
1884         QString error;
1885         if (!IoUtils::touchFile(tfn, rfn, &error)) {
1886             evalError(error);
1887             return ReturnFalse;
1888         }
1889 #endif
1890         return ReturnTrue;
1891     }
1892     case T_CACHE:
1893         return testFunc_cache(args);
1894     case T_RELOAD_PROPERTIES:
1895 #ifdef QT_BUILD_QMAKE
1896         m_option->reloadProperties();
1897 #endif
1898         return ReturnTrue;
1899     default:
1900         evalError(fL1S("Function '%1' is not implemented.").arg(function.toQStringView()));
1901         return ReturnFalse;
1902     }
1903 }
1904 
1905 QT_END_NAMESPACE
1906