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