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
82using namespace QMakeInternal;
83
84QT_BEGIN_NAMESPACE
85
86#define fL1S(s) QString::fromLatin1(s)
87
88enum 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
99enum 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
108QMakeBuiltin::QMakeBuiltin(const QMakeBuiltinInit &d)
109 : index(d.func), minArgs(qMax(a: 0, b: 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(t: 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
143void QMakeEvaluator::initFunctionStatics()
144{
145 static const QMakeBuiltinInit expandInits[] = {
146 { .name: "member", .func: E_MEMBER, .min_args: 1, .max_args: 3, .args: "var, [start, [end]]" },
147 { .name: "str_member", .func: E_STR_MEMBER, .min_args: -1, .max_args: 3, .args: "str, [start, [end]]" },
148 { .name: "first", .func: E_FIRST, .min_args: 1, .max_args: 1, .args: "var" },
149 { .name: "take_first", .func: E_TAKE_FIRST, .min_args: 1, .max_args: 1, .args: "var" },
150 { .name: "last", .func: E_LAST, .min_args: 1, .max_args: 1, .args: "var" },
151 { .name: "take_last", .func: E_TAKE_LAST, .min_args: 1, .max_args: 1, .args: "var" },
152 { .name: "size", .func: E_SIZE, .min_args: 1, .max_args: 1, .args: "var" },
153 { .name: "str_size", .func: E_STR_SIZE, .min_args: -1, .max_args: 1, .args: "str" },
154 { .name: "cat", .func: E_CAT, .min_args: 1, .max_args: 2, .args: "file, [mode=true|blob|lines]" },
155 { .name: "fromfile", .func: E_FROMFILE, .min_args: 2, .max_args: 2, .args: "file, var" },
156 { .name: "eval", .func: E_EVAL, .min_args: 1, .max_args: 1, .args: "var" },
157 { .name: "list", .func: E_LIST, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
158 { .name: "sprintf", .func: E_SPRINTF, .min_args: 1, .max_args: QMakeBuiltinInit::VarArgs, .args: "format, ..." },
159 { .name: "format_number", .func: E_FORMAT_NUMBER, .min_args: 1, .max_args: 2, .args: "number, [options...]" },
160 { .name: "num_add", .func: E_NUM_ADD, .min_args: 1, .max_args: QMakeBuiltinInit::VarArgs, .args: "num, ..." },
161 { .name: "join", .func: E_JOIN, .min_args: 1, .max_args: 4, .args: "var, [glue, [before, [after]]]" },
162 { .name: "split", .func: E_SPLIT, .min_args: 1, .max_args: 2, .args: "var, sep" },
163 { .name: "basename", .func: E_BASENAME, .min_args: 1, .max_args: 1, .args: "var" },
164 { .name: "dirname", .func: E_DIRNAME, .min_args: 1, .max_args: 1, .args: "var" },
165 { .name: "section", .func: E_SECTION, .min_args: 3, .max_args: 4, .args: "var, sep, begin, [end]" },
166 { .name: "find", .func: E_FIND, .min_args: 2, .max_args: 2, .args: "var, str" },
167 { .name: "system", .func: E_SYSTEM, .min_args: 1, .max_args: 3, .args: "command, [mode], [stsvar]" },
168 { .name: "unique", .func: E_UNIQUE, .min_args: 1, .max_args: 1, .args: "var" },
169 { .name: "sorted", .func: E_SORTED, .min_args: 1, .max_args: 1, .args: "var" },
170 { .name: "reverse", .func: E_REVERSE, .min_args: 1, .max_args: 1, .args: "var" },
171 { .name: "quote", .func: E_QUOTE, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
172 { .name: "escape_expand", .func: E_ESCAPE_EXPAND, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
173 { .name: "upper", .func: E_UPPER, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
174 { .name: "lower", .func: E_LOWER, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
175 { .name: "title", .func: E_TITLE, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
176 { .name: "re_escape", .func: E_RE_ESCAPE, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
177 { .name: "val_escape", .func: E_VAL_ESCAPE, .min_args: 1, .max_args: 1, .args: "var" },
178 { .name: "files", .func: E_FILES, .min_args: 1, .max_args: 2, .args: "pattern, [recursive=false]" },
179 { .name: "prompt", .func: E_PROMPT, .min_args: 1, .max_args: 2, .args: "question, [decorate=true]" },
180 { .name: "replace", .func: E_REPLACE, .min_args: 3, .max_args: 3, .args: "var, before, after" },
181 { .name: "sort_depends", .func: E_SORT_DEPENDS, .min_args: 1, .max_args: 4, .args: "var, [prefix, [suffixes, [prio-suffix]]]" },
182 { .name: "resolve_depends", .func: E_RESOLVE_DEPENDS, .min_args: 1, .max_args: 4, .args: "var, [prefix, [suffixes, [prio-suffix]]]" },
183 { .name: "enumerate_vars", .func: E_ENUMERATE_VARS, .min_args: 0, .max_args: 0, .args: "" },
184 { .name: "shadowed", .func: E_SHADOWED, .min_args: 1, .max_args: 1, .args: "path" },
185 { .name: "absolute_path", .func: E_ABSOLUTE_PATH, .min_args: -1, .max_args: 2, .args: "path, [base]" },
186 { .name: "relative_path", .func: E_RELATIVE_PATH, .min_args: -1, .max_args: 2, .args: "path, [base]" },
187 { .name: "clean_path", .func: E_CLEAN_PATH, .min_args: -1, .max_args: 1, .args: "path" },
188 { .name: "system_path", .func: E_SYSTEM_PATH, .min_args: -1, .max_args: 1, .args: "path" },
189 { .name: "shell_path", .func: E_SHELL_PATH, .min_args: -1, .max_args: 1, .args: "path" },
190 { .name: "system_quote", .func: E_SYSTEM_QUOTE, .min_args: -1, .max_args: 1, .args: "arg" },
191 { .name: "shell_quote", .func: E_SHELL_QUOTE, .min_args: -1, .max_args: 1, .args: "arg" },
192 { .name: "getenv", .func: E_GETENV, .min_args: 1, .max_args: 1, .args: "arg" },
193 };
194 statics.expands.reserve(asize: (int)(sizeof(expandInits)/sizeof(expandInits[0])));
195 for (unsigned i = 0; i < sizeof(expandInits)/sizeof(expandInits[0]); ++i)
196 statics.expands.insert(akey: ProKey(expandInits[i].name), avalue: QMakeBuiltin(expandInits[i]));
197
198 static const QMakeBuiltinInit testInits[] = {
199 { .name: "requires", .func: T_REQUIRES, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
200 { .name: "greaterThan", .func: T_GREATERTHAN, .min_args: 2, .max_args: 2, .args: "var, val" },
201 { .name: "lessThan", .func: T_LESSTHAN, .min_args: 2, .max_args: 2, .args: "var, val" },
202 { .name: "equals", .func: T_EQUALS, .min_args: 2, .max_args: 2, .args: "var, val" },
203 { .name: "isEqual", .func: T_EQUALS, .min_args: 2, .max_args: 2, .args: "var, val" },
204 { .name: "versionAtLeast", .func: T_VERSION_AT_LEAST, .min_args: 2, .max_args: 2, .args: "var, version" },
205 { .name: "versionAtMost", .func: T_VERSION_AT_MOST, .min_args: 2, .max_args: 2, .args: "var, version" },
206 { .name: "exists", .func: T_EXISTS, .min_args: 1, .max_args: 1, .args: "file" },
207 { .name: "export", .func: T_EXPORT, .min_args: 1, .max_args: 1, .args: "var" },
208 { .name: "clear", .func: T_CLEAR, .min_args: 1, .max_args: 1, .args: "var" },
209 { .name: "unset", .func: T_UNSET, .min_args: 1, .max_args: 1, .args: "var" },
210 { .name: "eval", .func: T_EVAL, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
211 { .name: "CONFIG", .func: T_CONFIG, .min_args: 1, .max_args: 2, .args: "config, [mutuals]" },
212 { .name: "if", .func: T_IF, .min_args: 1, .max_args: 1, .args: "condition" },
213 { .name: "isActiveConfig", .func: T_CONFIG, .min_args: 1, .max_args: 2, .args: "config, [mutuals]" },
214 { .name: "system", .func: T_SYSTEM, .min_args: 1, .max_args: 1, .args: "exec" },
215 { .name: "discard_from", .func: T_DISCARD_FROM, .min_args: 1, .max_args: 1, .args: "file" },
216 { .name: "defined", .func: T_DEFINED, .min_args: 1, .max_args: 2, .args: "object, [\"test\"|\"replace\"|\"var\"]" },
217 { .name: "contains", .func: T_CONTAINS, .min_args: 2, .max_args: 3, .args: "var, val, [mutuals]" },
218 { .name: "infile", .func: T_INFILE, .min_args: 2, .max_args: 3, .args: "file, var, [values]" },
219 { .name: "count", .func: T_COUNT, .min_args: 2, .max_args: 3, .args: "var, count, [op=operator]" },
220 { .name: "isEmpty", .func: T_ISEMPTY, .min_args: 1, .max_args: 1, .args: "var" },
221#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
222 { .name: "parseJson", .func: T_PARSE_JSON, .min_args: 2, .max_args: 2, .args: "var, into" },
223#endif
224 { .name: "load", .func: T_LOAD, .min_args: 1, .max_args: 2, .args: "feature, [ignore_errors=false]" },
225 { .name: "include", .func: T_INCLUDE, .min_args: 1, .max_args: 3, .args: "file, [into, [silent]]" },
226 { .name: "debug", .func: T_DEBUG, .min_args: 2, .max_args: 2, .args: "level, message" },
227 { .name: "log", .func: T_LOG, .min_args: 1, .max_args: 1, .args: "message" },
228 { .name: "message", .func: T_MESSAGE, .min_args: 1, .max_args: 1, .args: "message" },
229 { .name: "warning", .func: T_WARNING, .min_args: 1, .max_args: 1, .args: "message" },
230 { .name: "error", .func: T_ERROR, .min_args: 0, .max_args: 1, .args: "message" },
231 { .name: "mkpath", .func: T_MKPATH, .min_args: 1, .max_args: 1, .args: "path" },
232 { .name: "write_file", .func: T_WRITE_FILE, .min_args: 1, .max_args: 3, .args: "name, [content var, [append] [exe]]" },
233 { .name: "touch", .func: T_TOUCH, .min_args: 2, .max_args: 2, .args: "file, reffile" },
234 { .name: "cache", .func: T_CACHE, .min_args: 0, .max_args: 3, .args: "[var], [set|add|sub] [transient] [super|stash], [srcvar]" },
235 { .name: "reload_properties", .func: T_RELOAD_PROPERTIES, .min_args: 0, .max_args: 0, .args: "" },
236 };
237 statics.functions.reserve(asize: (int)(sizeof(testInits)/sizeof(testInits[0])));
238 for (unsigned i = 0; i < sizeof(testInits)/sizeof(testInits[0]); ++i)
239 statics.functions.insert(akey: ProKey(testInits[i].name), avalue: QMakeBuiltin(testInits[i]));
240}
241
242static bool isTrue(const ProString &str)
243{
244 return !str.compare(sub: statics.strtrue, cs: Qt::CaseInsensitive) || str.toInt();
245}
246
247bool
248QMakeEvaluator::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(i: 1);
255 *start = start_str.toInt(ok: &ok);
256 if (!ok) {
257 if (args.count() == 2) {
258 int dotdot = start_str.indexOf(s: statics.strDotDot);
259 if (dotdot != -1) {
260 *start = start_str.left(len: dotdot).toInt(ok: &ok);
261 if (ok)
262 *end = start_str.mid(off: dotdot+2).toInt(ok: &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(args&: u1.str(), args&: u2.str()));
269 return false;
270 }
271 } else {
272 *end = *start;
273 if (args.count() == 3)
274 *end = args.at(i: 2).toInt(ok: &ok);
275 if (!ok) {
276 ProStringRoUser u1(func, m_tmp1);
277 ProStringRoUser u2(args.at(i: 2), m_tmp2);
278 evalError(fL1S("%1() argument 3 (end) '%2' invalid.").arg(args&: u1.str(), args&: 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
292QString
293QMakeEvaluator::quoteValue(const ProString &val)
294{
295 QString ret;
296 ret.reserve(asize: 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(str: "\\\\x%1").arg(a: uc, fieldWidth: 2, base: 16, fillChar: 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(c: QLatin1Char('"'));
356 ret.append(c: QLatin1Char('"'));
357 }
358 return ret;
359}
360
361#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
362static void addJsonValue(const QJsonValue &value, const QString &keyPrefix, ProValueMap *map);
363
364static void insertJsonKeyValue(const QString &key, const QStringList &values, ProValueMap *map)
365{
366 map->insert(akey: ProKey(key), avalue: ProStringList(values));
367}
368
369static void addJsonArray(const QJsonArray &array, const QString &keyPrefix, ProValueMap *map)
370{
371 QStringList keys;
372 const int size = array.count();
373 keys.reserve(alloc: size);
374 for (int i = 0; i < size; ++i) {
375 const QString number = QString::number(i);
376 keys.append(t: number);
377 addJsonValue(value: array.at(i), keyPrefix: keyPrefix + number, map);
378 }
379 insertJsonKeyValue(key: keyPrefix + QLatin1String("_KEYS_"), values: keys, map);
380}
381
382static void addJsonObject(const QJsonObject &object, const QString &keyPrefix, ProValueMap *map)
383{
384 QStringList keys;
385 keys.reserve(alloc: object.size());
386 for (auto it = object.begin(), end = object.end(); it != end; ++it) {
387 const QString key = it.key();
388 keys.append(t: key);
389 addJsonValue(value: it.value(), keyPrefix: keyPrefix + key, map);
390 }
391 insertJsonKeyValue(key: keyPrefix + QLatin1String("_KEYS_"), values: keys, map);
392}
393
394static void addJsonValue(const QJsonValue &value, const QString &keyPrefix, ProValueMap *map)
395{
396 switch (value.type()) {
397 case QJsonValue::Bool:
398 insertJsonKeyValue(key: keyPrefix, values: QStringList() << (value.toBool() ? QLatin1String("true") : QLatin1String("false")), map);
399 break;
400 case QJsonValue::Double:
401 insertJsonKeyValue(key: keyPrefix, values: QStringList() << QString::number(value.toDouble()), map);
402 break;
403 case QJsonValue::String:
404 insertJsonKeyValue(key: keyPrefix, values: QStringList() << value.toString(), map);
405 break;
406 case QJsonValue::Array:
407 addJsonArray(array: value.toArray(), keyPrefix: keyPrefix + QLatin1Char('.'), map);
408 break;
409 case QJsonValue::Object:
410 addJsonObject(object: value.toObject(), keyPrefix: keyPrefix + QLatin1Char('.'), map);
411 break;
412 default:
413 break;
414 }
415}
416
417struct ErrorPosition {
418 int line;
419 int column;
420};
421
422static ErrorPosition calculateErrorPosition(const QByteArray &json, int offset)
423{
424 ErrorPosition pos = { .line: 0, .column: 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
448QMakeEvaluator::VisitReturn QMakeEvaluator::parseJsonInto(const QByteArray &json, const QString &into, ProValueMap *value)
449{
450 QJsonParseError error;
451 QJsonDocument document = QJsonDocument::fromJson(json, error: &error);
452 if (document.isNull()) {
453 if (error.error != QJsonParseError::NoError) {
454 ErrorPosition errorPos = calculateErrorPosition(json, offset: error.offset);
455 evalError(fL1S("Error parsing JSON at %1:%2: %3")
456 .arg(a: errorPos.line).arg(a: errorPos.column).arg(a: 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(array: document.array(), keyPrefix: currentKey, map: value);
466 else if (document.isObject())
467 addJsonObject(object: document.object(), keyPrefix: currentKey, map: value);
468 else
469 return QMakeEvaluator::ReturnFalse;
470
471 return QMakeEvaluator::ReturnTrue;
472}
473#endif
474
475QMakeEvaluator::VisitReturn
476QMakeEvaluator::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: flags | QMakeVfs::VfsAccessedOnly);
480 int id = m_vfs->idForFileName(fn, flags: flags | QMakeVfs::VfsCreate);
481 QString errStr;
482 if (!m_vfs->writeFile(id, mode, flags, contents, errStr: &errStr)) {
483 evalError(fL1S("Cannot write %1file %2: %3")
484 .arg(args: ctx, args: QDir::toNativeSeparators(pathName: fn), args&: errStr));
485 return ReturnFalse;
486 }
487 if (oldId)
488 m_parser->discardFileFromCache(id: oldId);
489 return ReturnTrue;
490}
491
492#if QT_CONFIG(process)
493void 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(program: QLatin1String("/bin/sh"), arguments: QStringList() << QLatin1String("-c") << command);
505# endif
506 proc->waitForFinished(msecs: -1);
507}
508#endif
509
510QByteArray QMakeEvaluator::getCommandOutput(const QString &args, int *exitCode) const
511{
512 QByteArray out;
513#if QT_CONFIG(process)
514 QProcess proc;
515 runProcess(proc: &proc, command: 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(c: '\n'))
524 errout.chop(n: 1);
525 m_handler->message(
526 type: QMakeHandler::EvalError | (m_cumulative ? QMakeHandler::CumulativeEvalMessage : 0),
527 msg: QString::fromLocal8Bit(str: 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
560void 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(akey: item.toKey())) {
568 QSet<ProKey> &dset = dependencies[item.toKey()]; // Always create entry
569 ProStringList depends;
570 for (const ProString &suffix : suffixes)
571 depends += values(variableName: ProKey(prefix + item + suffix));
572 if (depends.isEmpty()) {
573 rootSet.insert(akey: first(variableName: ProKey(prefix + item + priosfx)).toInt(), avalue: item);
574 } else {
575 for (const ProString &dep : qAsConst(t&: depends)) {
576 dset.insert(value: dep.toKey());
577 dependees[dep.toKey()] << item;
578 }
579 populateDeps(deps: depends, prefix, suffixes, priosfx, dependencies, dependees, rootSet);
580 }
581 }
582}
583
584QString QMakeEvaluator::filePathArg0(const ProStringList &args)
585{
586 ProStringRoUser u1(args.at(i: 0), m_tmp1);
587 QString fn = resolvePath(fileName: u1.str());
588 fn.detach();
589 return fn;
590}
591
592QString QMakeEvaluator::filePathEnvArg0(const ProStringList &args)
593{
594 ProStringRoUser u1(args.at(i: 0), m_tmp1);
595 QString fn = resolvePath(fileName: m_option->expandEnvVars(str: u1.str()));
596 fn.detach();
597 return fn;
598}
599
600QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinExpand(
601 const QMakeBuiltin &adef, const ProKey &func, const ProStringList &args, ProStringList &ret)
602{
603 traceMsg(fmt: "calling built-in $$%s(%s)", dbgKey(func), dbgSepStrList(args));
604 int asz = args.size() > 1 ? args.size() : args.at(i: 0).isEmpty() ? 0 : 1;
605 if (asz < adef.minArgs || asz > adef.maxArgs) {
606 evalError(msg: 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(i: 1).toQString();
623 beg = args.at(i: 2).toInt();
624 if (args.count() == 4)
625 end = args.at(i: 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(variableName: 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(s: u1.str().section(reg: sepRx, start: beg, end));
642 }
643 } else {
644 for (const ProString &str : strings) {
645 ProStringRwUser u1(str, m_tmp1);
646 ret << u1.extract(s: u1.str().section(in_sep: sep, start: beg, end));
647 }
648 }
649 }
650 break;
651 }
652 case E_SPRINTF: {
653 ProStringRwUser u1(args.at(i: 0), m_tmp1);
654 QString tmp = u1.str();
655 for (int i = 1; i < args.count(); ++i)
656 tmp = tmp.arg(a: args.at(i).toQStringView());
657 ret << u1.extract(s: 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(vals: args.at(i: 1).toQStringRef());
669 for (const ProString &opt : opts) {
670 if (opt.startsWith(sub: QLatin1String("ibase="))) {
671 ibase = opt.mid(off: 6).toInt();
672 } else if (opt.startsWith(sub: QLatin1String("obase="))) {
673 obase = opt.mid(off: 6).toInt();
674 } else if (opt.startsWith(sub: QLatin1String("width="))) {
675 width = opt.mid(off: 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(a: opt.toQStringView()));
687 goto allfail;
688 }
689 }
690 }
691 if (args.at(i: 0).contains(c: QLatin1Char('.'))) {
692 evalError(fL1S("format_number(): floats are currently not supported."));
693 break;
694 }
695 bool ok;
696 qlonglong num = args.at(i: 0).toLongLong(ok: &ok, base: ibase);
697 if (!ok) {
698 evalError(fL1S("format_number(): malformed number %2 for base %1.")
699 .arg(a: ibase).arg(a: args.at(i: 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, base: 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(s: 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(t: args)) {
729 if (arg.contains(c: QLatin1Char('.'))) {
730 evalError(fL1S("num_add(): floats are currently not supported."));
731 goto allfail;
732 }
733 bool ok;
734 qlonglong num = arg.toLongLong(ok: &ok);
735 if (!ok) {
736 evalError(fL1S("num_add(): malformed number %1.")
737 .arg(a: 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(i: 1);
749 if (args.count() >= 3)
750 before = args[2];
751 if (args.count() == 4)
752 after = args[3];
753 const ProStringList &var = values(variableName: map(var: args.at(i: 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(sep: 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(i: 1)) : statics.field_sep;
768 const auto vars = values(variableName: map(var: args.at(i: 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, behavior: 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(variableName: map(var: args.at(i: 0)));
779 int start, end;
780 if (getMemberArgs(func, srclen: src.size(), args, start: &start, end: &end)) {
781 ret.reserve(asize: qAbs(t: 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(i: 0);
794 int start, end;
795 if (getMemberArgs(func, srclen: src.size(), args, start: &start, end: &end)) {
796 QString res;
797 res.reserve(asize: qAbs(t: 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(variableName: map(var: args.at(i: 0)));
812 if (!var.isEmpty()) {
813 if (func_t == E_FIRST)
814 ret.append(t: var[0]);
815 else
816 ret.append(t: var.last());
817 }
818 break;
819 }
820 case E_TAKE_FIRST:
821 case E_TAKE_LAST: {
822 ProStringList &var = valuesRef(variableName: map(var: args.at(i: 0)));
823 if (!var.isEmpty()) {
824 if (func_t == E_TAKE_FIRST)
825 ret.append(t: var.takeFirst());
826 else
827 ret.append(t: var.takeLast());
828 }
829 break;
830 }
831 case E_SIZE:
832 ret.append(t: ProString(QString::number(values(variableName: map(var: args.at(i: 0))).size())));
833 break;
834 case E_STR_SIZE:
835 ret.append(t: ProString(QString::number(args.at(i: 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(i: 1).compare(sub: QLatin1String("false"), cs: Qt::CaseInsensitive))
843 singleLine = false;
844 else if (!args.at(i: 1).compare(sub: QLatin1String("blob"), cs: Qt::CaseInsensitive))
845 blob = true;
846 else if (!args.at(i: 1).compare(sub: QLatin1String("lines"), cs: Qt::CaseInsensitive))
847 lines = true;
848 }
849 QString fn = filePathEnvArg0(args);
850 QFile qfile(fn);
851 if (qfile.open(flags: 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(vals: 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(fileName: fn, values: &vars, flags: LoadProOnly) == ReturnTrue)
874 ret = vars.value(akey: map(var: args.at(i: 1)));
875 break;
876 }
877 case E_EVAL:
878 ret += values(variableName: map(var: args.at(i: 0)));
879 break;
880 case E_LIST: {
881 QString tmp = QString::asprintf(format: ".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(vals: arg.toQStringRef(), source: arg.sourceFile()); // Relies on deep copy
886 m_valuemapStack.top()[ret.at(i: 0).toKey()] = lst;
887 break; }
888 case E_FIND: {
889 QRegExp regx(args.at(i: 1).toQString());
890 const auto vals = values(variableName: map(var: args.at(i: 0)));
891 for (const ProString &val : vals) {
892 ProStringRoUser u1(val, m_tmp[m_toggle ^= 1]);
893 if (regx.indexIn(str: 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(i: 1).compare(sub: QLatin1String("false"), cs: Qt::CaseInsensitive))
906 singleLine = false;
907 else if (!args.at(i: 1).compare(sub: QLatin1String("blob"), cs: Qt::CaseInsensitive))
908 blob = true;
909 else if (!args.at(i: 1).compare(sub: QLatin1String("lines"), cs: Qt::CaseInsensitive))
910 lines = true;
911 }
912 int exitCode;
913 QByteArray bytes = getCommandOutput(args: args.at(i: 0).toQString(), exitCode: &exitCode);
914 if (args.count() > 2 && !args.at(i: 2).isEmpty()) {
915 m_valuemapStack.top()[args.at(i: 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(str: bytes);
924 if (blob) {
925 ret += ProString(output);
926 } else {
927 output.replace(before: QLatin1Char('\t'), after: QLatin1Char(' '));
928 if (singleLine)
929 output.replace(before: QLatin1Char('\n'), after: QLatin1Char(' '));
930 ret += split_value_list(vals: QStringRef(&output));
931 }
932 }
933 break;
934 }
935 case E_UNIQUE:
936 ret = values(variableName: map(var: args.at(i: 0)));
937 ret.removeDuplicates();
938 break;
939 case E_SORTED:
940 ret = values(variableName: map(var: args.at(i: 0)));
941 std::sort(first: ret.begin(), last: ret.end());
942 break;
943 case E_REVERSE: {
944 ProStringList var = values(variableName: args.at(i: 0).toKey());
945 for (int i = 0; i < var.size() / 2; i++)
946 qSwap(value1&: var[i], value2&: 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 { .in: 'n', .out: '\n' },
967 { .in: 't', .out: '\t' },
968 { .in: 'r', .out: '\r' },
969 { .in: 0, .out: 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(dest: i_data+x+1, src: i_data+x+2, n: (i_len-x-2)*sizeof(QChar));
976 --i_len;
977 break;
978 }
979 }
980 }
981 }
982 }
983 ret.append(t: 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(s: QRegExp::escape(str: u1.str()));
990 }
991 break;
992 case E_VAL_ESCAPE: {
993 const ProStringList &vals = values(variableName: args.at(i: 0).toKey());
994 ret.reserve(asize: vals.size());
995 for (const ProString &str : vals)
996 ret += ProString(quoteValue(val: 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(i: 0).toTitleCase();
1011 }
1012 ret << u1.extract(s: rstr);
1013 }
1014 break;
1015 case E_FILES: {
1016 bool recursive = false;
1017 if (args.count() == 2)
1018 recursive = isTrue(str: args.at(i: 1));
1019 QStringList dirs;
1020 ProStringRoUser u1(args.at(i: 0), m_tmp1);
1021 QString r = m_option->expandEnvVars(str: u1.str())
1022 .replace(before: QLatin1Char('\\'), after: QLatin1Char('/'));
1023 QString pfx;
1024 if (IoUtils::isRelativePath(fileName: r)) {
1025 pfx = currentDirectory();
1026 if (!pfx.endsWith(c: QLatin1Char('/')))
1027 pfx += QLatin1Char('/');
1028 }
1029 int slash = r.lastIndexOf(c: QLatin1Char('/'));
1030 if (slash != -1) {
1031 dirs.append(t: r.left(n: slash+1));
1032 r = r.mid(position: slash+1);
1033 } else {
1034 dirs.append(t: 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(fileName: pfx + fname) == IoUtils::FileIsDir) {
1047 if (recursive)
1048 dirs.append(t: fname + QLatin1Char('/'));
1049 }
1050 if (regex.exactMatch(str: 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(i: 1).toQString());
1086 ProStringRwUser u2(args.at(i: 2), m_tmp2);
1087 const QString &after = u2.str();
1088 const auto vals = values(variableName: map(var: args.at(i: 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(rx: before, after);
1094 ret << u1.extract(s: rstr, other: 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(variableName: args.at(i: 0).toKey());
1104 ProString prefix = args.count() < 2 ? ProString() : args.at(i: 1);
1105 ProString priosfx = args.count() < 4 ? ProString(".priority") : args.at(i: 3);
1106 populateDeps(deps: orgList, prefix,
1107 suffixes: args.count() < 3 ? ProStringList(ProString(".depends"))
1108 : split_value_list(vals: args.at(i: 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(str: item))
1115 ret.prepend(t: item);
1116 for (const ProString &dep : qAsConst(t&: dependees[item.toKey()])) {
1117 QSet<ProKey> &dset = dependencies[dep.toKey()];
1118 dset.remove(value: item.toKey());
1119 if (dset.isEmpty())
1120 rootSet.insert(akey: first(variableName: ProKey(prefix + dep + priosfx)).toInt(), avalue: dep);
1121 }
1122 }
1123 break;
1124 }
1125 case E_ENUMERATE_VARS: {
1126 QSet<ProString> keys;
1127 for (const ProValueMap &vmap : qAsConst(t&: m_valuemapStack))
1128 for (ProValueMap::ConstIterator it = vmap.constBegin(); it != vmap.constEnd(); ++it)
1129 keys.insert(value: it.key());
1130 ret.reserve(asize: keys.size());
1131 for (const ProString &key : qAsConst(t&: keys))
1132 ret << key;
1133 break; }
1134 case E_SHADOWED: {
1135 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1136 QString rstr = m_option->shadowedPath(fileName: resolvePath(fileName: u1.str()));
1137 if (!rstr.isEmpty())
1138 ret << u1.extract(s: rstr);
1139 break;
1140 }
1141 case E_ABSOLUTE_PATH: {
1142 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1143 ProStringRwUser u2(m_tmp2);
1144 QString baseDir = args.count() > 1
1145 ? IoUtils::resolvePath(baseDir: currentDirectory(), fileName: u2.set(args.at(i: 1)))
1146 : currentDirectory();
1147 QString rstr = u1.str().isEmpty() ? baseDir : IoUtils::resolvePath(baseDir, fileName: u1.str());
1148 ret << u1.extract(s: rstr, other: u2);
1149 break;
1150 }
1151 case E_RELATIVE_PATH: {
1152 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1153 ProStringRoUser u2(m_tmp2);
1154 QString baseDir = args.count() > 1
1155 ? IoUtils::resolvePath(baseDir: currentDirectory(), fileName: u2.set(args.at(i: 1)))
1156 : currentDirectory();
1157 QString absArg = u1.str().isEmpty() ? baseDir : IoUtils::resolvePath(baseDir, fileName: u1.str());
1158 QString rstr = QDir(baseDir).relativeFilePath(fileName: absArg);
1159 ret << u1.extract(s: rstr);
1160 break;
1161 }
1162 case E_CLEAN_PATH: {
1163 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1164 ret << u1.extract(s: QDir::cleanPath(path: u1.str()));
1165 break;
1166 }
1167 case E_SYSTEM_PATH: {
1168 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1169 QString rstr = u1.str();
1170#ifdef Q_OS_WIN
1171 rstr.replace(QLatin1Char('/'), QLatin1Char('\\'));
1172#else
1173 rstr.replace(before: QLatin1Char('\\'), after: QLatin1Char('/'));
1174#endif
1175 ret << u1.extract(s: rstr);
1176 break;
1177 }
1178 case E_SHELL_PATH: {
1179 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1180 QString rstr = u1.str();
1181 if (m_dirSep.startsWith(c: QLatin1Char('\\'))) {
1182 rstr.replace(before: QLatin1Char('/'), after: QLatin1Char('\\'));
1183 } else {
1184 rstr.replace(before: QLatin1Char('\\'), after: 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(s: rstr);
1194 break;
1195 }
1196 case E_SYSTEM_QUOTE: {
1197 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1198 ret << u1.extract(s: IoUtils::shellQuote(arg: u1.str()));
1199 break;
1200 }
1201 case E_SHELL_QUOTE: {
1202 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1203 QString rstr = u1.str();
1204 if (m_dirSep.startsWith(c: QLatin1Char('\\')))
1205 rstr = IoUtils::shellQuoteWin(arg: rstr);
1206 else
1207 rstr = IoUtils::shellQuoteUnix(arg: rstr);
1208 ret << u1.extract(s: rstr);
1209 break;
1210 }
1211 case E_GETENV: {
1212 ProStringRoUser u1(args.at(i: 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(a: func.toQStringView()));
1218 break;
1219 }
1220
1221 allfail:
1222 return ReturnTrue;
1223}
1224
1225QMakeEvaluator::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(vals: args.at(i: 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(a: opt.toQStringView()));
1248 return ReturnFalse;
1249 }
1250 }
1251 if (args.count() >= 3) {
1252 srcvar = args.at(i: 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(i: 0).toKey();
1260 if (!dstvar.isEmpty()) {
1261 if (srcvar.isEmpty())
1262 srcvar = dstvar;
1263 ProValueMap::Iterator srcvarIt;
1264 if (!findValues(variableName: srcvar, it: &srcvarIt)) {
1265 evalError(fL1S("Variable %1 is not defined.").arg(a: 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(akey: 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(variableName: 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(value: diffval);
1309 }
1310 if (oldval != newval) {
1311 if (target != TargetStash || !m_stashfile.isEmpty()) {
1312 baseEval->valuesRef(variableName: 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(val: diffval.at(i: 0));
1349 } else if (!diffval.isEmpty()) {
1350 for (const ProString &vval : diffval) {
1351 varstr += QLatin1String(" \\\n ");
1352 varstr += quoteValue(val: 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(path: m_outputDir + QLatin1String("/.qmake.super"));
1362 printf(format: "Info: creating super cache file %s\n", qPrintable(QDir::toNativeSeparators(m_superfile)));
1363 valuesRef(variableName: 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(path: m_outputDir + QLatin1String("/.qmake.cache"));
1369 printf(format: "Info: creating cache file %s\n", qPrintable(QDir::toNativeSeparators(m_cachefile)));
1370 valuesRef(variableName: 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(path: m_outputDir + QLatin1String("/.qmake.stash"));
1382 if (!m_vfs->exists(fn, flags)) {
1383 printf(format: "Info: creating stash file %s\n", qPrintable(QDir::toNativeSeparators(fn)));
1384 valuesRef(variableName: ProKey("_QMAKE_STASH_")) << ProString(fn);
1385 }
1386 }
1387 return writeFile(fL1S("cache "), fn, mode: QIODevice::Append, flags, contents: varstr);
1388}
1389
1390QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional(
1391 const QMakeInternal::QMakeBuiltin &adef, const ProKey &function, const ProStringList &args)
1392{
1393 traceMsg(fmt: "calling built-in %s(%s)", dbgKey(function), dbgSepStrList(args));
1394 int asz = args.size() > 1 ? args.size() : args.at(i: 0).isEmpty() ? 0 : 1;
1395 if (asz < adef.minArgs || asz > adef.maxArgs) {
1396 evalError(msg: 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(i: 0).toKey();
1404 if (args.count() > 1) {
1405 if (args[1] == QLatin1String("test")) {
1406 return returnBool(b: m_functionDefs.testFunctions.contains(akey: var));
1407 } else if (args[1] == QLatin1String("replace")) {
1408 return returnBool(b: m_functionDefs.replaceFunctions.contains(akey: var));
1409 } else if (args[1] == QLatin1String("var")) {
1410 ProValueMap::Iterator it;
1411 return returnBool(b: findValues(variableName: var, it: &it));
1412 }
1413 evalError(fL1S("defined(function, type): unexpected type [%1].")
1414 .arg(a: args.at(i: 1).toQStringView()));
1415 return ReturnFalse;
1416 }
1417 return returnBool(b: m_functionDefs.replaceFunctions.contains(akey: var)
1418 || m_functionDefs.testFunctions.contains(akey: var));
1419 }
1420 case T_EXPORT: {
1421 const ProKey &var = map(var: args.at(i: 0));
1422 for (ProValueMapStack::iterator vmi = m_valuemapStack.end();
1423 --vmi != m_valuemapStack.begin(); ) {
1424 ProValueMap::Iterator it = (*vmi).find(akey: 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(akey: 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(i: 0), m_tmp1);
1446 QString fn = resolvePath(fileName: u1.str());
1447 QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1448 int pro = m_vfs->idForFileName(fn, flags: 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(abegin: std::remove_if(first: vit->begin(), last: vit->end(), pred: isFrom), aend: vit->end());
1458 if (vit->isEmpty()) {
1459 // When an initially non-empty variable becomes entirely empty,
1460 // undefine it altogether.
1461 vit = vmap.erase(it: 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(it: 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(it: fit);
1476 else
1477 ++fit;
1478 }
1479 ProStringList &iif = m_valuemapStack.front()[ProKey("QMAKE_INTERNAL_INCLUDED_FILES")];
1480 int idx = iif.indexOf(t: 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(fileName: fn, values: &vars, flags: LoadProOnly);
1489 if (ok != ReturnTrue)
1490 return ok;
1491 if (args.count() == 2)
1492 return returnBool(b: vars.contains(akey: map(var: args.at(i: 1))));
1493 QRegExp regx;
1494 ProStringRoUser u1(args.at(i: 2), m_tmp1);
1495 const QString &qry = u1.str();
1496 if (qry != QRegExp::escape(str: qry)) {
1497 QString copy = qry;
1498 copy.detach();
1499 regx.setPattern(copy);
1500 }
1501 const auto strings = vars.value(akey: map(var: args.at(i: 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(str: 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(sep: statics.field_sep);
1522 ProFile *pro = m_parser->parsedProBlock(contents: QStringRef(&contents),
1523 id: 0, name: m_current.pro->fileName(), line: m_current.line);
1524 if (m_cumulative || pro->isOk()) {
1525 m_locationStack.push(t: m_current);
1526 visitProBlock(pro, tokPtr: 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(cond: args.at(i: 0).toQStringRef(),
1535 where: m_current.pro->fileName(), line: m_current.line);
1536 }
1537 case T_CONFIG: {
1538 if (args.count() == 1)
1539 return returnBool(b: isActiveConfig(config: args.at(i: 0).toQStringRef()));
1540 const auto &mutuals = args.at(i: 1).toQStringRef().split(sep: QLatin1Char('|'),
1541 behavior: Qt::SkipEmptyParts);
1542 const ProStringList &configs = values(variableName: 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(b: configs[i] == args[0]);
1548 }
1549 }
1550 return ReturnFalse;
1551 }
1552 case T_CONTAINS: {
1553 ProStringRoUser u1(args.at(i: 1), m_tmp1);
1554 const QString &qry = u1.str();
1555 QRegExp regx;
1556 if (qry != QRegExp::escape(str: qry)) {
1557 QString copy = qry;
1558 copy.detach();
1559 regx.setPattern(copy);
1560 }
1561 const ProStringList &l = values(variableName: map(var: args.at(i: 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(str: u2.str()))
1570 return ReturnTrue;
1571 }
1572 }
1573 } else {
1574 const auto mutuals = args.at(i: 2).toQStringRef().split(sep: QLatin1Char('|'),
1575 behavior: 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(str: 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(variableName: map(var: args.at(i: 0))).count();
1596 int val = args.at(i: 1).toInt();
1597 if (args.count() == 3) {
1598 const ProString &comp = args.at(i: 2);
1599 if (comp == QLatin1String(">") || comp == QLatin1String("greaterThan")) {
1600 return returnBool(b: cnt > val);
1601 } else if (comp == QLatin1String(">=")) {
1602 return returnBool(b: cnt >= val);
1603 } else if (comp == QLatin1String("<") || comp == QLatin1String("lessThan")) {
1604 return returnBool(b: cnt < val);
1605 } else if (comp == QLatin1String("<=")) {
1606 return returnBool(b: 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(a: comp.toQStringView()));
1612 return ReturnFalse;
1613 }
1614 }
1615 return returnBool(b: cnt == val);
1616 }
1617 case T_GREATERTHAN:
1618 case T_LESSTHAN: {
1619 const ProString &rhs = args.at(i: 1);
1620 const QString &lhs = values(variableName: map(var: args.at(i: 0))).join(sep: statics.field_sep);
1621 bool ok;
1622 int rhs_int = rhs.toInt(ok: &ok);
1623 if (ok) { // do integer compare
1624 int lhs_int = lhs.toInt(ok: &ok);
1625 if (ok) {
1626 if (func_t == T_GREATERTHAN)
1627 return returnBool(b: lhs_int > rhs_int);
1628 return returnBool(b: lhs_int < rhs_int);
1629 }
1630 }
1631 if (func_t == T_GREATERTHAN)
1632 return returnBool(b: lhs > rhs.toQStringRef());
1633 return returnBool(b: lhs < rhs.toQStringRef());
1634 }
1635 case T_EQUALS:
1636 return returnBool(b: values(variableName: map(var: args.at(i: 0))).join(sep: statics.field_sep)
1637 == args.at(i: 1).toQStringView());
1638 case T_VERSION_AT_LEAST:
1639 case T_VERSION_AT_MOST: {
1640 const QVersionNumber lvn = QVersionNumber::fromString(string: values(variableName: args.at(i: 0).toKey()).join(sep: QLatin1Char('.')));
1641 const QVersionNumber rvn = QVersionNumber::fromString(string: args.at(i: 1).toQStringView());
1642 if (func_t == T_VERSION_AT_LEAST)
1643 return returnBool(b: lvn >= rvn);
1644 return returnBool(b: lvn <= rvn);
1645 }
1646 case T_CLEAR: {
1647 ProValueMap *hsh;
1648 ProValueMap::Iterator it;
1649 const ProKey &var = map(var: args.at(i: 0));
1650 if (!(hsh = findValues(variableName: var, it: &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(var: args.at(i: 0));
1662 if (!(hsh = findValues(variableName: var, it: &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(variableName: args.at(i: 0).toKey()).join(sep: QLatin1Char(' ')).toUtf8();
1675 ProStringRoUser u1(args.at(i: 1), m_tmp2);
1676 QString parseInto = u1.str();
1677 return parseJsonInto(json, into: parseInto, value: &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(i: 1).isEmpty())
1687 parseInto = args.at(i: 1) + QLatin1Char('.');
1688 if (args.count() >= 3 && isTrue(str: args.at(i: 2)))
1689 flags = LoadSilent;
1690 }
1691 QString fn = filePathEnvArg0(args);
1692 VisitReturn ok;
1693 if (parseInto.isEmpty()) {
1694 ok = evaluateFileChecked(fileName: fn, type: QMakeHandler::EvalIncludeFile, flags: LoadProOnly | flags);
1695 } else {
1696 ProValueMap symbols;
1697 if ((ok = evaluateFileInto(fileName: fn, values: &symbols, flags: 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(sub: 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(c: QLatin1Char('.')))
1710 newMap.insert(akey: ProKey(parseInto + it.key()), avalue: 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(str: args.at(i: 1)));
1721 VisitReturn ok = evaluateFeatureFile(fileName: m_option->expandEnvVars(str: args.at(i: 0).toQString()),
1722 silent: 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(i: 0).toInt();
1730 if (level <= m_debugLevel) {
1731 ProStringRoUser u1(args.at(i: 1), m_tmp1);
1732 const QString &msg = m_option->expandEnvVars(str: u1.str());
1733 debugMsg(level, fmt: "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(i: 0), m_tmp1);
1743 const QString &msg = m_option->expandEnvVars(str: 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 type: (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(args: u2.str().toUpper(), args: 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(b: values(variableName: map(var: args.at(i: 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(fileName: file))
1793 return ReturnTrue;
1794 int slsh = file.lastIndexOf(c: QLatin1Char('/'));
1795 QString fn = file.mid(position: slsh+1);
1796 if (fn.contains(c: QLatin1Char('*')) || fn.contains(c: QLatin1Char('?'))) {
1797 QString dirstr = file.left(n: slsh+1);
1798 dirstr.detach();
1799 if (!QDir(dirstr).entryList(nameFilters: 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(variableName: args.at(i: 1).toKey());
1821 if (!vals.isEmpty())
1822 contents = vals.join(sep: QLatin1Char('\n')) + QLatin1Char('\n');
1823 if (args.count() >= 3) {
1824 const auto opts = split_value_list(vals: args.at(i: 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(a: opt.toQStringView()));
1832 return ReturnFalse;
1833 }
1834 }
1835 }
1836 }
1837 QString path = filePathArg0(args);
1838 return writeFile(ctx: QString(), fn: 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(a: function.toQStringView()));
1867 return ReturnFalse;
1868 }
1869}
1870
1871QT_END_NAMESPACE
1872

source code of qttools/src/linguist/shared/qmakebuiltins.cpp