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