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