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