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