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 <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#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(a: 0, b: 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(t: 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 { .name: "member", .func: E_MEMBER, .min_args: 1, .max_args: 3, .args: "var, [start, [end]]" },
150 { .name: "str_member", .func: E_STR_MEMBER, .min_args: -1, .max_args: 3, .args: "str, [start, [end]]" },
151 { .name: "first", .func: E_FIRST, .min_args: 1, .max_args: 1, .args: "var" },
152 { .name: "take_first", .func: E_TAKE_FIRST, .min_args: 1, .max_args: 1, .args: "var" },
153 { .name: "last", .func: E_LAST, .min_args: 1, .max_args: 1, .args: "var" },
154 { .name: "take_last", .func: E_TAKE_LAST, .min_args: 1, .max_args: 1, .args: "var" },
155 { .name: "size", .func: E_SIZE, .min_args: 1, .max_args: 1, .args: "var" },
156 { .name: "str_size", .func: E_STR_SIZE, .min_args: -1, .max_args: 1, .args: "str" },
157 { .name: "cat", .func: E_CAT, .min_args: 1, .max_args: 2, .args: "file, [mode=true|blob|lines]" },
158 { .name: "fromfile", .func: E_FROMFILE, .min_args: 2, .max_args: 2, .args: "file, var" },
159 { .name: "eval", .func: E_EVAL, .min_args: 1, .max_args: 1, .args: "var" },
160 { .name: "list", .func: E_LIST, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
161 { .name: "sprintf", .func: E_SPRINTF, .min_args: 1, .max_args: QMakeBuiltinInit::VarArgs, .args: "format, ..." },
162 { .name: "format_number", .func: E_FORMAT_NUMBER, .min_args: 1, .max_args: 2, .args: "number, [options...]" },
163 { .name: "num_add", .func: E_NUM_ADD, .min_args: 1, .max_args: QMakeBuiltinInit::VarArgs, .args: "num, ..." },
164 { .name: "join", .func: E_JOIN, .min_args: 1, .max_args: 4, .args: "var, [glue, [before, [after]]]" },
165 { .name: "split", .func: E_SPLIT, .min_args: 1, .max_args: 2, .args: "var, sep" },
166 { .name: "basename", .func: E_BASENAME, .min_args: 1, .max_args: 1, .args: "var" },
167 { .name: "dirname", .func: E_DIRNAME, .min_args: 1, .max_args: 1, .args: "var" },
168 { .name: "section", .func: E_SECTION, .min_args: 3, .max_args: 4, .args: "var, sep, begin, [end]" },
169 { .name: "find", .func: E_FIND, .min_args: 2, .max_args: 2, .args: "var, str" },
170 { .name: "system", .func: E_SYSTEM, .min_args: 1, .max_args: 3, .args: "command, [mode], [stsvar]" },
171 { .name: "unique", .func: E_UNIQUE, .min_args: 1, .max_args: 1, .args: "var" },
172 { .name: "sorted", .func: E_SORTED, .min_args: 1, .max_args: 1, .args: "var" },
173 { .name: "reverse", .func: E_REVERSE, .min_args: 1, .max_args: 1, .args: "var" },
174 { .name: "quote", .func: E_QUOTE, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
175 { .name: "escape_expand", .func: E_ESCAPE_EXPAND, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
176 { .name: "upper", .func: E_UPPER, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
177 { .name: "lower", .func: E_LOWER, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
178 { .name: "title", .func: E_TITLE, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
179 { .name: "re_escape", .func: E_RE_ESCAPE, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
180 { .name: "val_escape", .func: E_VAL_ESCAPE, .min_args: 1, .max_args: 1, .args: "var" },
181 { .name: "files", .func: E_FILES, .min_args: 1, .max_args: 2, .args: "pattern, [recursive=false]" },
182 { .name: "prompt", .func: E_PROMPT, .min_args: 1, .max_args: 2, .args: "question, [decorate=true]" },
183 { .name: "replace", .func: E_REPLACE, .min_args: 3, .max_args: 3, .args: "var, before, after" },
184 { .name: "sort_depends", .func: E_SORT_DEPENDS, .min_args: 1, .max_args: 4, .args: "var, [prefix, [suffixes, [prio-suffix]]]" },
185 { .name: "resolve_depends", .func: E_RESOLVE_DEPENDS, .min_args: 1, .max_args: 4, .args: "var, [prefix, [suffixes, [prio-suffix]]]" },
186 { .name: "enumerate_vars", .func: E_ENUMERATE_VARS, .min_args: 0, .max_args: 0, .args: "" },
187 { .name: "shadowed", .func: E_SHADOWED, .min_args: 1, .max_args: 1, .args: "path" },
188 { .name: "absolute_path", .func: E_ABSOLUTE_PATH, .min_args: -1, .max_args: 2, .args: "path, [base]" },
189 { .name: "relative_path", .func: E_RELATIVE_PATH, .min_args: -1, .max_args: 2, .args: "path, [base]" },
190 { .name: "clean_path", .func: E_CLEAN_PATH, .min_args: -1, .max_args: 1, .args: "path" },
191 { .name: "system_path", .func: E_SYSTEM_PATH, .min_args: -1, .max_args: 1, .args: "path" },
192 { .name: "shell_path", .func: E_SHELL_PATH, .min_args: -1, .max_args: 1, .args: "path" },
193 { .name: "system_quote", .func: E_SYSTEM_QUOTE, .min_args: -1, .max_args: 1, .args: "arg" },
194 { .name: "shell_quote", .func: E_SHELL_QUOTE, .min_args: -1, .max_args: 1, .args: "arg" },
195 { .name: "getenv", .func: E_GETENV, .min_args: 1, .max_args: 1, .args: "arg" },
196 { .name: "read_registry", .func: E_READ_REGISTRY, .min_args: 2, .max_args: 3, .args: "tree, key, [wow64]" },
197 };
198 statics.expands.reserve(asize: (int)(sizeof(expandInits)/sizeof(expandInits[0])));
199 for (unsigned i = 0; i < sizeof(expandInits)/sizeof(expandInits[0]); ++i)
200 statics.expands.insert(akey: ProKey(expandInits[i].name), avalue: QMakeBuiltin(expandInits[i]));
201
202 static const QMakeBuiltinInit testInits[] = {
203 { .name: "requires", .func: T_REQUIRES, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
204 { .name: "greaterThan", .func: T_GREATERTHAN, .min_args: 2, .max_args: 2, .args: "var, val" },
205 { .name: "lessThan", .func: T_LESSTHAN, .min_args: 2, .max_args: 2, .args: "var, val" },
206 { .name: "equals", .func: T_EQUALS, .min_args: 2, .max_args: 2, .args: "var, val" },
207 { .name: "isEqual", .func: T_EQUALS, .min_args: 2, .max_args: 2, .args: "var, val" },
208 { .name: "versionAtLeast", .func: T_VERSION_AT_LEAST, .min_args: 2, .max_args: 2, .args: "var, version" },
209 { .name: "versionAtMost", .func: T_VERSION_AT_MOST, .min_args: 2, .max_args: 2, .args: "var, version" },
210 { .name: "exists", .func: T_EXISTS, .min_args: 1, .max_args: 1, .args: "file" },
211 { .name: "export", .func: T_EXPORT, .min_args: 1, .max_args: 1, .args: "var" },
212 { .name: "clear", .func: T_CLEAR, .min_args: 1, .max_args: 1, .args: "var" },
213 { .name: "unset", .func: T_UNSET, .min_args: 1, .max_args: 1, .args: "var" },
214 { .name: "eval", .func: T_EVAL, .min_args: 0, .max_args: QMakeBuiltinInit::VarArgs, .args: nullptr },
215 { .name: "CONFIG", .func: T_CONFIG, .min_args: 1, .max_args: 2, .args: "config, [mutuals]" },
216 { .name: "if", .func: T_IF, .min_args: 1, .max_args: 1, .args: "condition" },
217 { .name: "isActiveConfig", .func: T_CONFIG, .min_args: 1, .max_args: 2, .args: "config, [mutuals]" },
218 { .name: "system", .func: T_SYSTEM, .min_args: 1, .max_args: 1, .args: "exec" },
219 { .name: "discard_from", .func: T_DISCARD_FROM, .min_args: 1, .max_args: 1, .args: "file" },
220 { .name: "defined", .func: T_DEFINED, .min_args: 1, .max_args: 2, .args: "object, [\"test\"|\"replace\"|\"var\"]" },
221 { .name: "contains", .func: T_CONTAINS, .min_args: 2, .max_args: 3, .args: "var, val, [mutuals]" },
222 { .name: "infile", .func: T_INFILE, .min_args: 2, .max_args: 3, .args: "file, var, [values]" },
223 { .name: "count", .func: T_COUNT, .min_args: 2, .max_args: 3, .args: "var, count, [op=operator]" },
224 { .name: "isEmpty", .func: T_ISEMPTY, .min_args: 1, .max_args: 1, .args: "var" },
225#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
226 { .name: "parseJson", .func: T_PARSE_JSON, .min_args: 2, .max_args: 2, .args: "var, into" },
227#endif
228 { .name: "load", .func: T_LOAD, .min_args: 1, .max_args: 2, .args: "feature, [ignore_errors=false]" },
229 { .name: "include", .func: T_INCLUDE, .min_args: 1, .max_args: 3, .args: "file, [into, [silent]]" },
230 { .name: "debug", .func: T_DEBUG, .min_args: 2, .max_args: 2, .args: "level, message" },
231 { .name: "log", .func: T_LOG, .min_args: 1, .max_args: 1, .args: "message" },
232 { .name: "message", .func: T_MESSAGE, .min_args: 1, .max_args: 1, .args: "message" },
233 { .name: "warning", .func: T_WARNING, .min_args: 1, .max_args: 1, .args: "message" },
234 { .name: "error", .func: T_ERROR, .min_args: 0, .max_args: 1, .args: "message" },
235 { .name: "mkpath", .func: T_MKPATH, .min_args: 1, .max_args: 1, .args: "path" },
236 { .name: "write_file", .func: T_WRITE_FILE, .min_args: 1, .max_args: 3, .args: "name, [content var, [append] [exe]]" },
237 { .name: "touch", .func: T_TOUCH, .min_args: 2, .max_args: 2, .args: "file, reffile" },
238 { .name: "cache", .func: T_CACHE, .min_args: 0, .max_args: 3, .args: "[var], [set|add|sub] [transient] [super|stash], [srcvar]" },
239 { .name: "reload_properties", .func: T_RELOAD_PROPERTIES, .min_args: 0, .max_args: 0, .args: "" },
240 };
241 statics.functions.reserve(asize: (int)(sizeof(testInits)/sizeof(testInits[0])));
242 for (unsigned i = 0; i < sizeof(testInits)/sizeof(testInits[0]); ++i)
243 statics.functions.insert(akey: ProKey(testInits[i].name), avalue: QMakeBuiltin(testInits[i]));
244}
245
246static bool isTrue(const ProString &str)
247{
248 return !str.compare(sub: statics.strtrue, cs: 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(i: 1);
259 *start = start_str.toInt(ok: &ok);
260 if (!ok) {
261 if (args.count() == 2) {
262 int dotdot = start_str.indexOf(s: statics.strDotDot);
263 if (dotdot != -1) {
264 *start = start_str.left(len: dotdot).toInt(ok: &ok);
265 if (ok)
266 *end = start_str.mid(off: dotdot+2).toInt(ok: &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(args&: u1.str(), args&: u2.str()));
273 return false;
274 }
275 } else {
276 *end = *start;
277 if (args.count() == 3)
278 *end = args.at(i: 2).toInt(ok: &ok);
279 if (!ok) {
280 ProStringRoUser u1(func, m_tmp1);
281 ProStringRoUser u2(args.at(i: 2), m_tmp2);
282 evalError(fL1S("%1() argument 3 (end) '%2' invalid.").arg(args&: u1.str(), args&: 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(asize: 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(str: "\\\\x%1").arg(a: uc, fieldWidth: 2, base: 16, fillChar: 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(c: QLatin1Char('"'));
360 ret.append(c: 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(akey: ProKey(key), avalue: 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(alloc: size);
378 for (int i = 0; i < size; ++i) {
379 const QString number = QString::number(i);
380 keys.append(t: number);
381 addJsonValue(value: array.at(i), keyPrefix: keyPrefix + number, map);
382 }
383 insertJsonKeyValue(key: keyPrefix + QLatin1String("_KEYS_"), values: keys, map);
384}
385
386static void addJsonObject(const QJsonObject &object, const QString &keyPrefix, ProValueMap *map)
387{
388 QStringList keys;
389 keys.reserve(alloc: object.size());
390 for (auto it = object.begin(), end = object.end(); it != end; ++it) {
391 const QString key = it.key();
392 keys.append(t: key);
393 addJsonValue(value: it.value(), keyPrefix: keyPrefix + key, map);
394 }
395 insertJsonKeyValue(key: keyPrefix + QLatin1String("_KEYS_"), values: 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(key: keyPrefix, values: QStringList() << (value.toBool() ? QLatin1String("true") : QLatin1String("false")), map);
403 break;
404 case QJsonValue::Double:
405 insertJsonKeyValue(key: keyPrefix, values: QStringList() << QString::number(value.toDouble()), map);
406 break;
407 case QJsonValue::String:
408 insertJsonKeyValue(key: keyPrefix, values: QStringList() << value.toString(), map);
409 break;
410 case QJsonValue::Array:
411 addJsonArray(array: value.toArray(), keyPrefix: keyPrefix + QLatin1Char('.'), map);
412 break;
413 case QJsonValue::Object:
414 addJsonObject(object: value.toObject(), keyPrefix: 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 = { .line: 0, .column: 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: &error);
456 if (document.isNull()) {
457 if (error.error != QJsonParseError::NoError) {
458 ErrorPosition errorPos = calculateErrorPosition(json, offset: error.offset);
459 evalError(fL1S("Error parsing JSON at %1:%2: %3")
460 .arg(a: errorPos.line).arg(a: errorPos.column).arg(a: 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(array: document.array(), keyPrefix: currentKey, map: value);
470 else if (document.isObject())
471 addJsonObject(object: document.object(), keyPrefix: currentKey, map: 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: flags | QMakeVfs::VfsAccessedOnly);
484 int id = m_vfs->idForFileName(fn, flags: flags | QMakeVfs::VfsCreate);
485 QString errStr;
486 if (!m_vfs->writeFile(id, mode, flags, contents, errStr: &errStr)) {
487 evalError(fL1S("Cannot write %1file %2: %3")
488 .arg(args: ctx, args: QDir::toNativeSeparators(pathName: fn), args&: errStr));
489 return ReturnFalse;
490 }
491 if (oldId)
492 m_parser->discardFileFromCache(id: 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(program: QLatin1String("/bin/sh"), arguments: QStringList() << QLatin1String("-c") << command);
509# endif
510 proc->waitForFinished(msecs: -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: &proc, command: 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(s: 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(akey: item.toKey())) {
572 QSet<ProKey> &dset = dependencies[item.toKey()]; // Always create entry
573 ProStringList depends;
574 for (const ProString &suffix : suffixes)
575 depends += values(variableName: ProKey(prefix + item + suffix));
576 if (depends.isEmpty()) {
577 rootSet.insert(akey: first(variableName: ProKey(prefix + item + priosfx)).toInt(), avalue: item);
578 } else {
579 for (const ProString &dep : qAsConst(t&: depends)) {
580 dset.insert(value: dep.toKey());
581 dependees[dep.toKey()] << item;
582 }
583 populateDeps(deps: depends, prefix, suffixes, priosfx, dependencies, dependees, rootSet);
584 }
585 }
586}
587
588QString QMakeEvaluator::filePathArg0(const ProStringList &args)
589{
590 ProStringRoUser u1(args.at(i: 0), m_tmp1);
591 QString fn = resolvePath(fileName: u1.str());
592 fn.detach();
593 return fn;
594}
595
596QString QMakeEvaluator::filePathEnvArg0(const ProStringList &args)
597{
598 ProStringRoUser u1(args.at(i: 0), m_tmp1);
599 QString fn = resolvePath(fileName: m_option->expandEnvVars(str: 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(fmt: "calling built-in $$%s(%s)", dbgKey(func), dbgSepStrList(args));
608 int asz = args.size() > 1 ? args.size() : args.at(i: 0).isEmpty() ? 0 : 1;
609 if (asz < adef.minArgs || asz > adef.maxArgs) {
610 evalError(msg: 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(i: 1).toQString();
627 beg = args.at(i: 2).toInt();
628 if (args.count() == 4)
629 end = args.at(i: 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(variableName: map(var));
641 if (regexp) {
642 QRegExp sepRx(sep);
643 for (const ProString &str : strings) {
644 ProStringRwUser u1(str, m_tmp[m_toggle ^= 1]);
645 ret << u1.extract(s: u1.str().section(reg: sepRx, start: beg, end));
646 }
647 } else {
648 for (const ProString &str : strings) {
649 ProStringRwUser u1(str, m_tmp1);
650 ret << u1.extract(s: u1.str().section(in_sep: sep, start: beg, end));
651 }
652 }
653 }
654 break;
655 }
656 case E_SPRINTF: {
657 ProStringRwUser u1(args.at(i: 0), m_tmp1);
658 QString tmp = u1.str();
659 for (int i = 1; i < args.count(); ++i)
660 tmp = tmp.arg(a: args.at(i).toQStringView());
661 ret << u1.extract(s: tmp);
662 break;
663 }
664 case E_FORMAT_NUMBER: {
665 int ibase = 10;
666 int obase = 10;
667 int width = 0;
668 bool zeropad = false;
669 bool leftalign = false;
670 enum { DefaultSign, PadSign, AlwaysSign } sign = DefaultSign;
671 if (args.count() >= 2) {
672 const auto opts = split_value_list(vals: args.at(i: 1).toQStringRef());
673 for (const ProString &opt : opts) {
674 if (opt.startsWith(sub: QLatin1String("ibase="))) {
675 ibase = opt.mid(off: 6).toInt();
676 } else if (opt.startsWith(sub: QLatin1String("obase="))) {
677 obase = opt.mid(off: 6).toInt();
678 } else if (opt.startsWith(sub: QLatin1String("width="))) {
679 width = opt.mid(off: 6).toInt();
680 } else if (opt == QLatin1String("zeropad")) {
681 zeropad = true;
682 } else if (opt == QLatin1String("padsign")) {
683 sign = PadSign;
684 } else if (opt == QLatin1String("alwayssign")) {
685 sign = AlwaysSign;
686 } else if (opt == QLatin1String("leftalign")) {
687 leftalign = true;
688 } else {
689 evalError(fL1S("format_number(): invalid format option %1.")
690 .arg(a: opt.toQStringView()));
691 goto allfail;
692 }
693 }
694 }
695 if (args.at(i: 0).contains(c: QLatin1Char('.'))) {
696 evalError(fL1S("format_number(): floats are currently not supported."));
697 break;
698 }
699 bool ok;
700 qlonglong num = args.at(i: 0).toLongLong(ok: &ok, base: ibase);
701 if (!ok) {
702 evalError(fL1S("format_number(): malformed number %2 for base %1.")
703 .arg(a: ibase).arg(a: args.at(i: 0).toQStringView()));
704 break;
705 }
706 QString outstr;
707 if (num < 0) {
708 num = -num;
709 outstr = QLatin1Char('-');
710 } else if (sign == AlwaysSign) {
711 outstr = QLatin1Char('+');
712 } else if (sign == PadSign) {
713 outstr = QLatin1Char(' ');
714 }
715 QString numstr = QString::number(num, base: obase);
716 int space = width - outstr.length() - numstr.length();
717 if (space <= 0) {
718 outstr += numstr;
719 } else if (leftalign) {
720 outstr += numstr + QString(space, QLatin1Char(' '));
721 } else if (zeropad) {
722 outstr += QString(space, QLatin1Char('0')) + numstr;
723 } else {
724 outstr.prepend(s: QString(space, QLatin1Char(' ')));
725 outstr += numstr;
726 }
727 ret += ProString(outstr);
728 break;
729 }
730 case E_NUM_ADD: {
731 qlonglong sum = 0;
732 for (const ProString &arg : qAsConst(t: args)) {
733 if (arg.contains(c: QLatin1Char('.'))) {
734 evalError(fL1S("num_add(): floats are currently not supported."));
735 goto allfail;
736 }
737 bool ok;
738 qlonglong num = arg.toLongLong(ok: &ok);
739 if (!ok) {
740 evalError(fL1S("num_add(): malformed number %1.")
741 .arg(a: arg.toQStringView()));
742 goto allfail;
743 }
744 sum += num;
745 }
746 ret += ProString(QString::number(sum));
747 break;
748 }
749 case E_JOIN: {
750 ProString glue, before, after;
751 if (args.count() >= 2)
752 glue = args.at(i: 1);
753 if (args.count() >= 3)
754 before = args[2];
755 if (args.count() == 4)
756 after = args[3];
757 const ProStringList &var = values(variableName: map(var: args.at(i: 0)));
758 if (!var.isEmpty()) {
759 int src = currentFileId();
760 for (const ProString &v : var)
761 if (int s = v.sourceFile()) {
762 src = s;
763 break;
764 }
765 ret << ProString(before + var.join(sep: glue) + after).setSource(src);
766 }
767 break;
768 }
769 case E_SPLIT: {
770 ProStringRoUser u1(m_tmp1);
771 const QString &sep = (args.count() == 2) ? u1.set(args.at(i: 1)) : statics.field_sep;
772 const auto vars = values(variableName: map(var: args.at(i: 0)));
773 for (const ProString &var : vars) {
774 // FIXME: this is inconsistent with the "there are no empty strings" dogma.
775 const auto splits = var.toQStringRef().split(sep, behavior: Qt::KeepEmptyParts);
776 for (const auto &splt : splits)
777 ret << ProString(splt).setSource(var);
778 }
779 break;
780 }
781 case E_MEMBER: {
782 const ProStringList &src = values(variableName: map(var: args.at(i: 0)));
783 int start, end;
784 if (getMemberArgs(func, srclen: src.size(), args, start: &start, end: &end)) {
785 ret.reserve(asize: qAbs(t: end - start) + 1);
786 if (start < end) {
787 for (int i = start; i <= end && src.size() >= i; i++)
788 ret += src.at(i);
789 } else {
790 for (int i = start; i >= end && src.size() >= i && i >= 0; i--)
791 ret += src.at(i);
792 }
793 }
794 break;
795 }
796 case E_STR_MEMBER: {
797 const ProString &src = args.at(i: 0);
798 int start, end;
799 if (getMemberArgs(func, srclen: src.size(), args, start: &start, end: &end)) {
800 QString res;
801 res.reserve(asize: qAbs(t: end - start) + 1);
802 if (start < end) {
803 for (int i = start; i <= end && src.size() >= i; i++)
804 res += src.at(i);
805 } else {
806 for (int i = start; i >= end && src.size() >= i && i >= 0; i--)
807 res += src.at(i);
808 }
809 ret += ProString(res);
810 }
811 break;
812 }
813 case E_FIRST:
814 case E_LAST: {
815 const ProStringList &var = values(variableName: map(var: args.at(i: 0)));
816 if (!var.isEmpty()) {
817 if (func_t == E_FIRST)
818 ret.append(t: var[0]);
819 else
820 ret.append(t: var.last());
821 }
822 break;
823 }
824 case E_TAKE_FIRST:
825 case E_TAKE_LAST: {
826 ProStringList &var = valuesRef(variableName: map(var: args.at(i: 0)));
827 if (!var.isEmpty()) {
828 if (func_t == E_TAKE_FIRST)
829 ret.append(t: var.takeFirst());
830 else
831 ret.append(t: var.takeLast());
832 }
833 break;
834 }
835 case E_SIZE:
836 ret.append(t: ProString(QString::number(values(variableName: map(var: args.at(i: 0))).size())));
837 break;
838 case E_STR_SIZE:
839 ret.append(t: ProString(QString::number(args.at(i: 0).size())));
840 break;
841 case E_CAT: {
842 bool blob = false;
843 bool lines = false;
844 bool singleLine = true;
845 if (args.count() > 1) {
846 if (!args.at(i: 1).compare(sub: QLatin1String("false"), cs: Qt::CaseInsensitive))
847 singleLine = false;
848 else if (!args.at(i: 1).compare(sub: QLatin1String("blob"), cs: Qt::CaseInsensitive))
849 blob = true;
850 else if (!args.at(i: 1).compare(sub: QLatin1String("lines"), cs: Qt::CaseInsensitive))
851 lines = true;
852 }
853 QString fn = filePathEnvArg0(args);
854 QFile qfile(fn);
855 if (qfile.open(flags: QIODevice::ReadOnly)) {
856 QTextStream stream(&qfile);
857 if (blob) {
858 ret += ProString(stream.readAll());
859 } else {
860 while (!stream.atEnd()) {
861 if (lines) {
862 ret += ProString(stream.readLine());
863 } else {
864 const QString &line = stream.readLine();
865 ret += split_value_list(vals: QStringRef(&line).trimmed());
866 if (!singleLine)
867 ret += ProString("\n");
868 }
869 }
870 }
871 }
872 break;
873 }
874 case E_FROMFILE: {
875 ProValueMap vars;
876 QString fn = filePathEnvArg0(args);
877 if (evaluateFileInto(fileName: fn, values: &vars, flags: LoadProOnly) == ReturnTrue)
878 ret = vars.value(akey: map(var: args.at(i: 1)));
879 break;
880 }
881 case E_EVAL:
882 ret += values(variableName: map(var: args.at(i: 0)));
883 break;
884 case E_LIST: {
885 QString tmp(QString::asprintf(format: ".QMAKE_INTERNAL_TMP_variableName_%d", m_listCount++));
886 ret = ProStringList(ProString(tmp));
887 ProStringList lst;
888 for (const ProString &arg : args)
889 lst += split_value_list(vals: arg.toQStringRef(), source: arg.sourceFile()); // Relies on deep copy
890 m_valuemapStack.top()[ret.at(i: 0).toKey()] = lst;
891 break; }
892 case E_FIND: {
893 QRegExp regx(args.at(i: 1).toQString());
894 const auto vals = values(variableName: map(var: args.at(i: 0)));
895 for (const ProString &val : vals) {
896 ProStringRoUser u1(val, m_tmp[m_toggle ^= 1]);
897 if (regx.indexIn(str: u1.str()) != -1)
898 ret += val;
899 }
900 break;
901 }
902 case E_SYSTEM: {
903 if (m_skipLevel)
904 break;
905 bool blob = false;
906 bool lines = false;
907 bool singleLine = true;
908 if (args.count() > 1) {
909 if (!args.at(i: 1).compare(sub: QLatin1String("false"), cs: Qt::CaseInsensitive))
910 singleLine = false;
911 else if (!args.at(i: 1).compare(sub: QLatin1String("blob"), cs: Qt::CaseInsensitive))
912 blob = true;
913 else if (!args.at(i: 1).compare(sub: QLatin1String("lines"), cs: Qt::CaseInsensitive))
914 lines = true;
915 }
916 int exitCode;
917 QByteArray bytes = getCommandOutput(args: args.at(i: 0).toQString(), exitCode: &exitCode);
918 if (args.count() > 2 && !args.at(i: 2).isEmpty()) {
919 m_valuemapStack.top()[args.at(i: 2).toKey()] =
920 ProStringList(ProString(QString::number(exitCode)));
921 }
922 if (lines) {
923 QTextStream stream(bytes);
924 while (!stream.atEnd())
925 ret += ProString(stream.readLine());
926 } else {
927 QString output = QString::fromLocal8Bit(str: bytes);
928 if (blob) {
929 ret += ProString(output);
930 } else {
931 output.replace(before: QLatin1Char('\t'), after: QLatin1Char(' '));
932 if (singleLine)
933 output.replace(before: QLatin1Char('\n'), after: QLatin1Char(' '));
934 ret += split_value_list(vals: QStringRef(&output));
935 }
936 }
937 break;
938 }
939 case E_UNIQUE:
940 ret = values(variableName: map(var: args.at(i: 0)));
941 ret.removeDuplicates();
942 break;
943 case E_SORTED:
944 ret = values(variableName: map(var: args.at(i: 0)));
945 std::sort(first: ret.begin(), last: ret.end());
946 break;
947 case E_REVERSE: {
948 ProStringList var = values(variableName: args.at(i: 0).toKey());
949 for (int i = 0; i < var.size() / 2; i++)
950 qSwap(value1&: var[i], value2&: var[var.size() - i - 1]);
951 ret += var;
952 break;
953 }
954 case E_QUOTE:
955 ret += args;
956 break;
957 case E_ESCAPE_EXPAND:
958 for (int i = 0; i < args.size(); ++i) {
959 QString str = args.at(i).toQString();
960 QChar *i_data = str.data();
961 int i_len = str.length();
962 for (int x = 0; x < i_len; ++x) {
963 if (*(i_data+x) == QLatin1Char('\\') && x < i_len-1) {
964 if (*(i_data+x+1) == QLatin1Char('\\')) {
965 ++x;
966 } else {
967 struct {
968 char in, out;
969 } mapped_quotes[] = {
970 { .in: 'n', .out: '\n' },
971 { .in: 't', .out: '\t' },
972 { .in: 'r', .out: '\r' },
973 { .in: 0, .out: 0 }
974 };
975 for (int i = 0; mapped_quotes[i].in; ++i) {
976 if (*(i_data+x+1) == QLatin1Char(mapped_quotes[i].in)) {
977 *(i_data+x) = QLatin1Char(mapped_quotes[i].out);
978 if (x < i_len-2)
979 memmove(dest: i_data+x+1, src: i_data+x+2, n: (i_len-x-2)*sizeof(QChar));
980 --i_len;
981 break;
982 }
983 }
984 }
985 }
986 }
987 ret.append(t: ProString(QString(i_data, i_len)).setSource(args.at(i)));
988 }
989 break;
990 case E_RE_ESCAPE:
991 for (int i = 0; i < args.size(); ++i) {
992 ProStringRwUser u1(args.at(i), m_tmp1);
993 ret << u1.extract(s: QRegExp::escape(str: u1.str()));
994 }
995 break;
996 case E_VAL_ESCAPE: {
997 const ProStringList &vals = values(variableName: args.at(i: 0).toKey());
998 ret.reserve(asize: vals.size());
999 for (const ProString &str : vals)
1000 ret += ProString(quoteValue(val: str));
1001 break;
1002 }
1003 case E_UPPER:
1004 case E_LOWER:
1005 case E_TITLE:
1006 for (int i = 0; i < args.count(); ++i) {
1007 ProStringRwUser u1(args.at(i), m_tmp1);
1008 QString rstr = u1.str();
1009 if (func_t == E_UPPER) {
1010 rstr = rstr.toUpper();
1011 } else {
1012 rstr = rstr.toLower();
1013 if (func_t == E_TITLE && rstr.length() > 0)
1014 rstr[0] = rstr.at(i: 0).toTitleCase();
1015 }
1016 ret << u1.extract(s: rstr);
1017 }
1018 break;
1019 case E_FILES: {
1020 bool recursive = false;
1021 if (args.count() == 2)
1022 recursive = isTrue(str: args.at(i: 1));
1023 QStringList dirs;
1024 ProStringRoUser u1(args.at(i: 0), m_tmp1);
1025 QString r = m_option->expandEnvVars(str: u1.str())
1026 .replace(before: QLatin1Char('\\'), after: QLatin1Char('/'));
1027 QString pfx;
1028 if (IoUtils::isRelativePath(fileName: r)) {
1029 pfx = currentDirectory();
1030 if (!pfx.endsWith(c: QLatin1Char('/')))
1031 pfx += QLatin1Char('/');
1032 }
1033 int slash = r.lastIndexOf(c: QLatin1Char('/'));
1034 if (slash != -1) {
1035 dirs.append(t: r.left(n: slash+1));
1036 r = r.mid(position: slash+1);
1037 } else {
1038 dirs.append(t: QString());
1039 }
1040
1041 r.detach(); // Keep m_tmp out of QRegExp's cache
1042 QRegExp regex(r, Qt::CaseSensitive, QRegExp::Wildcard);
1043 for (int d = 0; d < dirs.count(); d++) {
1044 QString dir = dirs[d];
1045 QDir qdir(pfx + dir);
1046 for (int i = 0, count = int(qdir.count()); i < count; ++i) {
1047 if (qdir[i] == statics.strDot || qdir[i] == statics.strDotDot)
1048 continue;
1049 QString fname = dir + qdir[i];
1050 if (IoUtils::fileType(fileName: pfx + fname) == IoUtils::FileIsDir) {
1051 if (recursive)
1052 dirs.append(t: fname + QLatin1Char('/'));
1053 }
1054 if (regex.exactMatch(str: qdir[i]))
1055 ret += ProString(fname).setSource(currentFileId());
1056 }
1057 }
1058 break;
1059 }
1060#ifdef PROEVALUATOR_FULL
1061 case E_PROMPT: {
1062 ProStringRoUser u1(args.at(i: 0), m_tmp1);
1063 QString msg = m_option->expandEnvVars(str: u1.str());
1064 bool decorate = true;
1065 if (args.count() == 2)
1066 decorate = isTrue(str: args.at(i: 1));
1067 if (decorate) {
1068 if (!msg.endsWith(c: QLatin1Char('?')))
1069 msg += QLatin1Char('?');
1070 fprintf(stderr, format: "Project PROMPT: %s ", qPrintable(msg));
1071 } else {
1072 fputs(qPrintable(msg), stderr);
1073 }
1074 QFile qfile;
1075 if (qfile.open(stdin, ioFlags: QIODevice::ReadOnly)) {
1076 QTextStream t(&qfile);
1077 const QString &line = t.readLine();
1078 if (t.atEnd()) {
1079 fputs(s: "\n", stderr);
1080 evalError(fL1S("Unexpected EOF."));
1081 return ReturnError;
1082 }
1083 ret = split_value_list(vals: QStringRef(&line));
1084 }
1085 break;
1086 }
1087#endif
1088 case E_REPLACE: {
1089 const QRegExp before(args.at(i: 1).toQString());
1090 ProStringRwUser u2(args.at(i: 2), m_tmp2);
1091 const QString &after = u2.str();
1092 const auto vals = values(variableName: map(var: args.at(i: 0)));
1093 for (const ProString &val : vals) {
1094 ProStringRwUser u1(val, m_tmp1);
1095 QString rstr = u1.str();
1096 QString copy = rstr; // Force a detach on modify
1097 rstr.replace(rx: before, after);
1098 ret << u1.extract(s: rstr, other: u2);
1099 }
1100 break;
1101 }
1102 case E_SORT_DEPENDS:
1103 case E_RESOLVE_DEPENDS: {
1104 QHash<ProKey, QSet<ProKey> > dependencies;
1105 ProValueMap dependees;
1106 QMultiMap<int, ProString> rootSet;
1107 ProStringList orgList = values(variableName: args.at(i: 0).toKey());
1108 ProString prefix = args.count() < 2 ? ProString() : args.at(i: 1);
1109 ProString priosfx = args.count() < 4 ? ProString(".priority") : args.at(i: 3);
1110 populateDeps(deps: orgList, prefix,
1111 suffixes: args.count() < 3 ? ProStringList(ProString(".depends"))
1112 : split_value_list(vals: args.at(i: 2).toQStringRef()),
1113 priosfx, dependencies, dependees, rootSet);
1114 while (!rootSet.isEmpty()) {
1115 QMultiMap<int, ProString>::iterator it = rootSet.begin();
1116 const ProString item = *it;
1117 rootSet.erase(it);
1118 if ((func_t == E_RESOLVE_DEPENDS) || orgList.contains(str: item))
1119 ret.prepend(t: item);
1120 for (const ProString &dep : qAsConst(t&: dependees[item.toKey()])) {
1121 QSet<ProKey> &dset = dependencies[dep.toKey()];
1122 dset.remove(value: item.toKey());
1123 if (dset.isEmpty())
1124 rootSet.insert(akey: first(variableName: ProKey(prefix + dep + priosfx)).toInt(), avalue: dep);
1125 }
1126 }
1127 break;
1128 }
1129 case E_ENUMERATE_VARS: {
1130 QSet<ProString> keys;
1131 for (const ProValueMap &vmap : qAsConst(t&: m_valuemapStack))
1132 for (ProValueMap::ConstIterator it = vmap.constBegin(); it != vmap.constEnd(); ++it)
1133 keys.insert(value: it.key());
1134 ret.reserve(asize: keys.size());
1135 for (const ProString &key : qAsConst(t&: keys))
1136 ret << key;
1137 break; }
1138 case E_SHADOWED: {
1139 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1140 QString rstr = m_option->shadowedPath(fileName: resolvePath(fileName: u1.str()));
1141 if (!rstr.isEmpty())
1142 ret << u1.extract(s: rstr);
1143 break;
1144 }
1145 case E_ABSOLUTE_PATH: {
1146 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1147 ProStringRwUser u2(m_tmp2);
1148 QString baseDir = args.count() > 1
1149 ? IoUtils::resolvePath(baseDir: currentDirectory(), fileName: u2.set(args.at(i: 1)))
1150 : currentDirectory();
1151 QString rstr = u1.str().isEmpty() ? baseDir : IoUtils::resolvePath(baseDir, fileName: u1.str());
1152 ret << u1.extract(s: rstr, other: u2);
1153 break;
1154 }
1155 case E_RELATIVE_PATH: {
1156 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1157 ProStringRoUser u2(m_tmp2);
1158 QString baseDir = args.count() > 1
1159 ? IoUtils::resolvePath(baseDir: currentDirectory(), fileName: u2.set(args.at(i: 1)))
1160 : currentDirectory();
1161 QString absArg = u1.str().isEmpty() ? baseDir : IoUtils::resolvePath(baseDir, fileName: u1.str());
1162 QString rstr = QDir(baseDir).relativeFilePath(fileName: absArg);
1163 ret << u1.extract(s: rstr);
1164 break;
1165 }
1166 case E_CLEAN_PATH: {
1167 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1168 ret << u1.extract(s: QDir::cleanPath(path: u1.str()));
1169 break;
1170 }
1171 case E_SYSTEM_PATH: {
1172 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1173 QString rstr = u1.str();
1174#ifdef Q_OS_WIN
1175 rstr.replace(QLatin1Char('/'), QLatin1Char('\\'));
1176#else
1177 rstr.replace(before: QLatin1Char('\\'), after: QLatin1Char('/'));
1178#endif
1179 ret << u1.extract(s: rstr);
1180 break;
1181 }
1182 case E_SHELL_PATH: {
1183 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1184 QString rstr = u1.str();
1185 if (m_dirSep.startsWith(c: QLatin1Char('\\'))) {
1186 rstr.replace(before: QLatin1Char('/'), after: QLatin1Char('\\'));
1187 } else {
1188 rstr.replace(before: QLatin1Char('\\'), after: QLatin1Char('/'));
1189#ifdef Q_OS_WIN
1190 // Convert d:/foo/bar to msys-style /d/foo/bar.
1191 if (rstr.length() > 2 && rstr.at(1) == QLatin1Char(':') && rstr.at(2) == QLatin1Char('/')) {
1192 rstr[1] = rstr.at(0);
1193 rstr[0] = QLatin1Char('/');
1194 }
1195#endif
1196 }
1197 ret << u1.extract(s: rstr);
1198 break;
1199 }
1200 case E_SYSTEM_QUOTE: {
1201 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1202 ret << u1.extract(s: IoUtils::shellQuote(arg: u1.str()));
1203 break;
1204 }
1205 case E_SHELL_QUOTE: {
1206 ProStringRwUser u1(args.at(i: 0), m_tmp1);
1207 QString rstr = u1.str();
1208 if (m_dirSep.startsWith(c: QLatin1Char('\\')))
1209 rstr = IoUtils::shellQuoteWin(arg: rstr);
1210 else
1211 rstr = IoUtils::shellQuoteUnix(arg: rstr);
1212 ret << u1.extract(s: rstr);
1213 break;
1214 }
1215 case E_GETENV: {
1216 ProStringRoUser u1(args.at(i: 0), m_tmp1);
1217 ret << ProString(m_option->getEnv(u1.str()));
1218 break;
1219 }
1220#ifdef Q_OS_WIN
1221 case E_READ_REGISTRY: {
1222 HKEY tree;
1223 const auto par = args.at(0);
1224 if (!par.compare(QLatin1String("HKCU"), Qt::CaseInsensitive)
1225 || !par.compare(QLatin1String("HKEY_CURRENT_USER"), Qt::CaseInsensitive)) {
1226 tree = HKEY_CURRENT_USER;
1227 } else if (!par.compare(QLatin1String("HKLM"), Qt::CaseInsensitive)
1228 || !par.compare(QLatin1String("HKEY_LOCAL_MACHINE"), Qt::CaseInsensitive)) {
1229 tree = HKEY_LOCAL_MACHINE;
1230 } else {
1231 evalError(fL1S("read_registry(): invalid or unsupported registry tree %1.")
1232 .arg(par.toQStringView()));
1233 goto allfail;
1234 }
1235 int flags = 0;
1236 if (args.count() > 2) {
1237 const auto opt = args.at(2);
1238 if (opt == "32"
1239 || !opt.compare(QLatin1String("wow64_32key"), Qt::CaseInsensitive)) {
1240 flags = KEY_WOW64_32KEY;
1241 } else if (opt == "64"
1242 || !opt.compare(QLatin1String("wow64_64key"), Qt::CaseInsensitive)) {
1243 flags = KEY_WOW64_64KEY;
1244 } else {
1245 evalError(fL1S("read_registry(): invalid option %1.")
1246 .arg(opt.toQStringView()));
1247 goto allfail;
1248 }
1249 }
1250 ret << ProString(qt_readRegistryKey(tree, args.at(1).toQString(m_tmp1), flags));
1251 break;
1252 }
1253#endif
1254 default:
1255 evalError(fL1S("Function '%1' is not implemented.").arg(a: func.toQStringView()));
1256 break;
1257 }
1258
1259 allfail:
1260 return ReturnTrue;
1261}
1262
1263QMakeEvaluator::VisitReturn QMakeEvaluator::testFunc_cache(const ProStringList &args)
1264{
1265 bool persist = true;
1266 enum { TargetStash, TargetCache, TargetSuper } target = TargetCache;
1267 enum { CacheSet, CacheAdd, CacheSub } mode = CacheSet;
1268 ProKey srcvar;
1269 if (args.count() >= 2) {
1270 const auto opts = split_value_list(vals: args.at(i: 1).toQStringRef());
1271 for (const ProString &opt : opts) {
1272 if (opt == QLatin1String("transient")) {
1273 persist = false;
1274 } else if (opt == QLatin1String("super")) {
1275 target = TargetSuper;
1276 } else if (opt == QLatin1String("stash")) {
1277 target = TargetStash;
1278 } else if (opt == QLatin1String("set")) {
1279 mode = CacheSet;
1280 } else if (opt == QLatin1String("add")) {
1281 mode = CacheAdd;
1282 } else if (opt == QLatin1String("sub")) {
1283 mode = CacheSub;
1284 } else {
1285 evalError(fL1S("cache(): invalid flag %1.").arg(a: opt.toQStringView()));
1286 return ReturnFalse;
1287 }
1288 }
1289 if (args.count() >= 3) {
1290 srcvar = args.at(i: 2).toKey();
1291 } else if (mode != CacheSet) {
1292 evalError(fL1S("cache(): modes other than 'set' require a source variable."));
1293 return ReturnFalse;
1294 }
1295 }
1296 QString varstr;
1297 ProKey dstvar = args.at(i: 0).toKey();
1298 if (!dstvar.isEmpty()) {
1299 if (srcvar.isEmpty())
1300 srcvar = dstvar;
1301 ProValueMap::Iterator srcvarIt;
1302 if (!findValues(variableName: srcvar, it: &srcvarIt)) {
1303 evalError(fL1S("Variable %1 is not defined.").arg(a: srcvar.toQStringView()));
1304 return ReturnFalse;
1305 }
1306 // The caches for the host and target may differ (e.g., when we are manipulating
1307 // CONFIG), so we cannot compute a common new value for both.
1308 const ProStringList &diffval = *srcvarIt;
1309 ProStringList newval;
1310 bool changed = false;
1311 for (bool hostBuild = false; ; hostBuild = true) {
1312#ifdef PROEVALUATOR_THREAD_SAFE
1313 m_option->mutex.lock();
1314#endif
1315 QMakeBaseEnv *baseEnv =
1316 m_option->baseEnvs.value(akey: QMakeBaseKey(m_buildRoot, m_stashfile, hostBuild));
1317#ifdef PROEVALUATOR_THREAD_SAFE
1318 // It's ok to unlock this before locking baseEnv,
1319 // as we have no intention to initialize the env.
1320 m_option->mutex.unlock();
1321#endif
1322 do {
1323 if (!baseEnv)
1324 break;
1325#ifdef PROEVALUATOR_THREAD_SAFE
1326 QMutexLocker locker(&baseEnv->mutex);
1327 if (baseEnv->inProgress && baseEnv->evaluator != this) {
1328 // The env is still in the works, but it may be already past the cache
1329 // loading. So we need to wait for completion and amend it as usual.
1330 QThreadPool::globalInstance()->releaseThread();
1331 baseEnv->cond.wait(&baseEnv->mutex);
1332 QThreadPool::globalInstance()->reserveThread();
1333 }
1334 if (!baseEnv->isOk)
1335 break;
1336#endif
1337 QMakeEvaluator *baseEval = baseEnv->evaluator;
1338 const ProStringList &oldval = baseEval->values(variableName: dstvar);
1339 if (mode == CacheSet) {
1340 newval = diffval;
1341 } else {
1342 newval = oldval;
1343 if (mode == CacheAdd)
1344 newval += diffval;
1345 else
1346 newval.removeEach(value: diffval);
1347 }
1348 if (oldval != newval) {
1349 if (target != TargetStash || !m_stashfile.isEmpty()) {
1350 baseEval->valuesRef(variableName: dstvar) = newval;
1351 if (target == TargetSuper) {
1352 do {
1353 if (dstvar == QLatin1String("QMAKEPATH")) {
1354 baseEval->m_qmakepath = newval.toQStringList();
1355 baseEval->updateMkspecPaths();
1356 } else if (dstvar == QLatin1String("QMAKEFEATURES")) {
1357 baseEval->m_qmakefeatures = newval.toQStringList();
1358 } else {
1359 break;
1360 }
1361 baseEval->updateFeaturePaths();
1362 if (hostBuild == m_hostBuild)
1363 m_featureRoots = baseEval->m_featureRoots;
1364 } while (false);
1365 }
1366 }
1367 changed = true;
1368 }
1369 } while (false);
1370 if (hostBuild)
1371 break;
1372 }
1373 // We assume that whatever got the cached value to be what it is now will do so
1374 // the next time as well, so we just skip the persisting if nothing changed.
1375 if (!persist || !changed)
1376 return ReturnTrue;
1377 varstr = dstvar.toQString();
1378 if (mode == CacheAdd)
1379 varstr += QLatin1String(" +=");
1380 else if (mode == CacheSub)
1381 varstr += QLatin1String(" -=");
1382 else
1383 varstr += QLatin1String(" =");
1384 if (diffval.count() == 1) {
1385 varstr += QLatin1Char(' ');
1386 varstr += quoteValue(val: diffval.at(i: 0));
1387 } else if (!diffval.isEmpty()) {
1388 for (const ProString &vval : diffval) {
1389 varstr += QLatin1String(" \\\n ");
1390 varstr += quoteValue(val: vval);
1391 }
1392 }
1393 varstr += QLatin1Char('\n');
1394 }
1395 QString fn;
1396 QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1397 if (target == TargetSuper) {
1398 if (m_superfile.isEmpty()) {
1399 m_superfile = QDir::cleanPath(path: m_outputDir + QLatin1String("/.qmake.super"));
1400 printf(format: "Info: creating super cache file %s\n", qPrintable(QDir::toNativeSeparators(m_superfile)));
1401 valuesRef(variableName: ProKey("_QMAKE_SUPER_CACHE_")) << ProString(m_superfile);
1402 }
1403 fn = m_superfile;
1404 } else if (target == TargetCache) {
1405 if (m_cachefile.isEmpty()) {
1406 m_cachefile = QDir::cleanPath(path: m_outputDir + QLatin1String("/.qmake.cache"));
1407 printf(format: "Info: creating cache file %s\n", qPrintable(QDir::toNativeSeparators(m_cachefile)));
1408 valuesRef(variableName: ProKey("_QMAKE_CACHE_")) << ProString(m_cachefile);
1409 // We could update m_{source,build}Root and m_featureRoots here, or even
1410 // "re-home" our rootEnv, but this doesn't sound too useful - if somebody
1411 // wanted qmake to find something in the build directory, he could have
1412 // done so "from the outside".
1413 // The sub-projects will find the new cache all by themselves.
1414 }
1415 fn = m_cachefile;
1416 } else {
1417 fn = m_stashfile;
1418 if (fn.isEmpty())
1419 fn = QDir::cleanPath(path: m_outputDir + QLatin1String("/.qmake.stash"));
1420 if (!m_vfs->exists(fn, flags)) {
1421 printf(format: "Info: creating stash file %s\n", qPrintable(QDir::toNativeSeparators(fn)));
1422 valuesRef(variableName: ProKey("_QMAKE_STASH_")) << ProString(fn);
1423 }
1424 }
1425 return writeFile(fL1S("cache "), fn, mode: QIODevice::Append, flags, contents: varstr);
1426}
1427
1428QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional(
1429 const QMakeInternal::QMakeBuiltin &adef, const ProKey &function, const ProStringList &args)
1430{
1431 traceMsg(fmt: "calling built-in %s(%s)", dbgKey(function), dbgSepStrList(args));
1432 int asz = args.size() > 1 ? args.size() : args.at(i: 0).isEmpty() ? 0 : 1;
1433 if (asz < adef.minArgs || asz > adef.maxArgs) {
1434 evalError(msg: adef.usage);
1435 return ReturnFalse;
1436 }
1437
1438 int func_t = adef.index;
1439 switch (func_t) {
1440 case T_DEFINED: {
1441 const ProKey &var = args.at(i: 0).toKey();
1442 if (args.count() > 1) {
1443 if (args[1] == QLatin1String("test")) {
1444 return returnBool(b: m_functionDefs.testFunctions.contains(akey: var));
1445 } else if (args[1] == QLatin1String("replace")) {
1446 return returnBool(b: m_functionDefs.replaceFunctions.contains(akey: var));
1447 } else if (args[1] == QLatin1String("var")) {
1448 ProValueMap::Iterator it;
1449 return returnBool(b: findValues(variableName: var, it: &it));
1450 }
1451 evalError(fL1S("defined(function, type): unexpected type [%1].")
1452 .arg(a: args.at(i: 1).toQStringView()));
1453 return ReturnFalse;
1454 }
1455 return returnBool(b: m_functionDefs.replaceFunctions.contains(akey: var)
1456 || m_functionDefs.testFunctions.contains(akey: var));
1457 }
1458 case T_EXPORT: {
1459 const ProKey &var = map(var: args.at(i: 0));
1460 for (ProValueMapStack::iterator vmi = m_valuemapStack.end();
1461 --vmi != m_valuemapStack.begin(); ) {
1462 ProValueMap::Iterator it = (*vmi).find(akey: var);
1463 if (it != (*vmi).end()) {
1464 if (it->constBegin() == statics.fakeValue.constBegin()) {
1465 // This is stupid, but qmake doesn't propagate deletions
1466 m_valuemapStack.front()[var] = ProStringList();
1467 } else {
1468 m_valuemapStack.front()[var] = *it;
1469 }
1470 (*vmi).erase(it);
1471 while (--vmi != m_valuemapStack.begin())
1472 (*vmi).remove(akey: var);
1473 break;
1474 }
1475 }
1476 return ReturnTrue;
1477 }
1478 case T_DISCARD_FROM: {
1479 if (m_valuemapStack.size() != 1) {
1480 evalError(fL1S("discard_from() cannot be called from functions."));
1481 return ReturnFalse;
1482 }
1483 ProStringRoUser u1(args.at(i: 0), m_tmp1);
1484 QString fn = resolvePath(fileName: u1.str());
1485 QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1486 int pro = m_vfs->idForFileName(fn, flags: flags | QMakeVfs::VfsAccessedOnly);
1487 if (!pro)
1488 return ReturnFalse;
1489 ProValueMap &vmap = m_valuemapStack.front();
1490 for (auto vit = vmap.begin(); vit != vmap.end(); ) {
1491 if (!vit->isEmpty()) {
1492 auto isFrom = [pro](const ProString &s) {
1493 return s.sourceFile() == pro;
1494 };
1495 vit->erase(abegin: std::remove_if(first: vit->begin(), last: vit->end(), pred: isFrom), aend: vit->end());
1496 if (vit->isEmpty()) {
1497 // When an initially non-empty variable becomes entirely empty,
1498 // undefine it altogether.
1499 vit = vmap.erase(it: vit);
1500 continue;
1501 }
1502 }
1503 ++vit;
1504 }
1505 for (auto fit = m_functionDefs.testFunctions.begin(); fit != m_functionDefs.testFunctions.end(); ) {
1506 if (fit->pro()->id() == pro)
1507 fit = m_functionDefs.testFunctions.erase(it: fit);
1508 else
1509 ++fit;
1510 }
1511 for (auto fit = m_functionDefs.replaceFunctions.begin(); fit != m_functionDefs.replaceFunctions.end(); ) {
1512 if (fit->pro()->id() == pro)
1513 fit = m_functionDefs.replaceFunctions.erase(it: fit);
1514 else
1515 ++fit;
1516 }
1517 ProStringList &iif = m_valuemapStack.front()[ProKey("QMAKE_INTERNAL_INCLUDED_FILES")];
1518 int idx = iif.indexOf(t: ProString(fn));
1519 if (idx >= 0)
1520 iif.removeAt(idx);
1521 return ReturnTrue;
1522 }
1523 case T_INFILE: {
1524 ProValueMap vars;
1525 QString fn = filePathEnvArg0(args);
1526 VisitReturn ok = evaluateFileInto(fileName: fn, values: &vars, flags: LoadProOnly);
1527 if (ok != ReturnTrue)
1528 return ok;
1529 if (args.count() == 2)
1530 return returnBool(b: vars.contains(akey: map(var: args.at(i: 1))));
1531 QRegExp regx;
1532 ProStringRoUser u1(args.at(i: 2), m_tmp1);
1533 const QString &qry = u1.str();
1534 if (qry != QRegExp::escape(str: qry)) {
1535 QString copy = qry;
1536 copy.detach();
1537 regx.setPattern(copy);
1538 }
1539 const auto strings = vars.value(akey: map(var: args.at(i: 1)));
1540 for (const ProString &s : strings) {
1541 if (s == qry)
1542 return ReturnTrue;
1543 if (!regx.isEmpty()) {
1544 ProStringRoUser u2(s, m_tmp[m_toggle ^= 1]);
1545 if (regx.exactMatch(str: u2.str()))
1546 return ReturnTrue;
1547 }
1548 }
1549 return ReturnFalse;
1550 }
1551 case T_REQUIRES:
1552#ifdef PROEVALUATOR_FULL
1553 if (checkRequirements(deps: args) == ReturnError)
1554 return ReturnError;
1555#endif
1556 return ReturnFalse; // Another qmake breakage
1557 case T_EVAL: {
1558 VisitReturn ret = ReturnFalse;
1559 QString contents = args.join(sep: statics.field_sep);
1560 ProFile *pro = m_parser->parsedProBlock(contents: QStringRef(&contents),
1561 id: 0, name: m_current.pro->fileName(), line: m_current.line);
1562 if (m_cumulative || pro->isOk()) {
1563 m_locationStack.push(t: m_current);
1564 visitProBlock(pro, tokPtr: pro->tokPtr());
1565 ret = ReturnTrue; // This return value is not too useful, but that's qmake
1566 m_current = m_locationStack.pop();
1567 }
1568 pro->deref();
1569 return ret;
1570 }
1571 case T_IF: {
1572 return evaluateConditional(cond: args.at(i: 0).toQStringRef(),
1573 where: m_current.pro->fileName(), line: m_current.line);
1574 }
1575 case T_CONFIG: {
1576 if (args.count() == 1)
1577 return returnBool(b: isActiveConfig(config: args.at(i: 0).toQStringRef()));
1578 const auto mutuals = args.at(i: 1).toQStringRef().split(sep: QLatin1Char('|'),
1579 behavior: Qt::SkipEmptyParts);
1580 const ProStringList &configs = values(variableName: statics.strCONFIG);
1581
1582 for (int i = configs.size() - 1; i >= 0; i--) {
1583 for (int mut = 0; mut < mutuals.count(); mut++) {
1584 if (configs[i].toQStringRef() == mutuals[mut].trimmed())
1585 return returnBool(b: configs[i] == args[0]);
1586 }
1587 }
1588 return ReturnFalse;
1589 }
1590 case T_CONTAINS: {
1591 ProStringRoUser u1(args.at(i: 1), m_tmp1);
1592 const QString &qry = u1.str();
1593 QRegExp regx;
1594 if (qry != QRegExp::escape(str: qry)) {
1595 QString copy = qry;
1596 copy.detach();
1597 regx.setPattern(copy);
1598 }
1599 const ProStringList &l = values(variableName: map(var: args.at(i: 0)));
1600 if (args.count() == 2) {
1601 for (int i = 0; i < l.size(); ++i) {
1602 const ProString &val = l[i];
1603 if (val == qry)
1604 return ReturnTrue;
1605 if (!regx.isEmpty()) {
1606 ProStringRoUser u2(val, m_tmp[m_toggle ^= 1]);
1607 if (regx.exactMatch(str: u2.str()))
1608 return ReturnTrue;
1609 }
1610 }
1611 } else {
1612 const auto mutuals = args.at(i: 2).toQStringRef().split(sep: QLatin1Char('|'),
1613 behavior: Qt::SkipEmptyParts);
1614 for (int i = l.size() - 1; i >= 0; i--) {
1615 const ProString &val = l[i];
1616 for (int mut = 0; mut < mutuals.count(); mut++) {
1617 if (val.toQStringRef() == mutuals[mut].trimmed()) {
1618 if (val == qry)
1619 return ReturnTrue;
1620 if (!regx.isEmpty()) {
1621 ProStringRoUser u2(val, m_tmp[m_toggle ^= 1]);
1622 if (regx.exactMatch(str: u2.str()))
1623 return ReturnTrue;
1624 }
1625 return ReturnFalse;
1626 }
1627 }
1628 }
1629 }
1630 return ReturnFalse;
1631 }
1632 case T_COUNT: {
1633 int cnt = values(variableName: map(var: args.at(i: 0))).count();
1634 int val = args.at(i: 1).toInt();
1635 if (args.count() == 3) {
1636 const ProString &comp = args.at(i: 2);
1637 if (comp == QLatin1String(">") || comp == QLatin1String("greaterThan")) {
1638 return returnBool(b: cnt > val);
1639 } else if (comp == QLatin1String(">=")) {
1640 return returnBool(b: cnt >= val);
1641 } else if (comp == QLatin1String("<") || comp == QLatin1String("lessThan")) {
1642 return returnBool(b: cnt < val);
1643 } else if (comp == QLatin1String("<=")) {
1644 return returnBool(b: cnt <= val);
1645 } else if (comp == QLatin1String("equals") || comp == QLatin1String("isEqual")
1646 || comp == QLatin1String("=") || comp == QLatin1String("==")) {
1647 // fallthrough
1648 } else {
1649 evalError(fL1S("Unexpected modifier to count(%2).").arg(a: comp.toQStringView()));
1650 return ReturnFalse;
1651 }
1652 }
1653 return returnBool(b: cnt == val);
1654 }
1655 case T_GREATERTHAN:
1656 case T_LESSTHAN: {
1657 const ProString &rhs = args.at(i: 1);
1658 const QString &lhs = values(variableName: map(var: args.at(i: 0))).join(sep: statics.field_sep);
1659 bool ok;
1660 int rhs_int = rhs.toInt(ok: &ok);
1661 if (ok) { // do integer compare
1662 int lhs_int = lhs.toInt(ok: &ok);
1663 if (ok) {
1664 if (func_t == T_GREATERTHAN)
1665 return returnBool(b: lhs_int > rhs_int);
1666 return returnBool(b: lhs_int < rhs_int);
1667 }
1668 }
1669 if (func_t == T_GREATERTHAN)
1670 return returnBool(b: lhs > rhs.toQStringRef());
1671 return returnBool(b: lhs < rhs.toQStringRef());
1672 }
1673 case T_EQUALS:
1674 return returnBool(b: values(variableName: map(var: args.at(i: 0))).join(sep: statics.field_sep)
1675 == args.at(i: 1).toQStringView());
1676 case T_VERSION_AT_LEAST:
1677 case T_VERSION_AT_MOST: {
1678 const QVersionNumber lvn = QVersionNumber::fromString(string: values(variableName: args.at(i: 0).toKey()).join(sep: '.'));
1679 const QVersionNumber rvn = QVersionNumber::fromString(string: args.at(i: 1).toQStringView());
1680 if (func_t == T_VERSION_AT_LEAST)
1681 return returnBool(b: lvn >= rvn);
1682 return returnBool(b: lvn <= rvn);
1683 }
1684 case T_CLEAR: {
1685 ProValueMap *hsh;
1686 ProValueMap::Iterator it;
1687 const ProKey &var = map(var: args.at(i: 0));
1688 if (!(hsh = findValues(variableName: var, it: &it)))
1689 return ReturnFalse;
1690 if (hsh == &m_valuemapStack.top())
1691 it->clear();
1692 else
1693 m_valuemapStack.top()[var].clear();
1694 return ReturnTrue;
1695 }
1696 case T_UNSET: {
1697 ProValueMap *hsh;
1698 ProValueMap::Iterator it;
1699 const ProKey &var = map(var: args.at(i: 0));
1700 if (!(hsh = findValues(variableName: var, it: &it)))
1701 return ReturnFalse;
1702 if (m_valuemapStack.size() == 1)
1703 hsh->erase(it);
1704 else if (hsh == &m_valuemapStack.top())
1705 *it = statics.fakeValue;
1706 else
1707 m_valuemapStack.top()[var] = statics.fakeValue;
1708 return ReturnTrue;
1709 }
1710#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
1711 case T_PARSE_JSON: {
1712 QByteArray json = values(variableName: args.at(i: 0).toKey()).join(sep: QLatin1Char(' ')).toUtf8();
1713 ProStringRoUser u1(args.at(i: 1), m_tmp2);
1714 QString parseInto = u1.str();
1715 return parseJsonInto(json, into: parseInto, value: &m_valuemapStack.top());
1716 }
1717#endif
1718 case T_INCLUDE: {
1719 QString parseInto;
1720 LoadFlags flags;
1721 if (m_cumulative)
1722 flags = LoadSilent;
1723 if (args.count() >= 2) {
1724 if (!args.at(i: 1).isEmpty())
1725 parseInto = args.at(i: 1) + QLatin1Char('.');
1726 if (args.count() >= 3 && isTrue(str: args.at(i: 2)))
1727 flags = LoadSilent;
1728 }
1729 QString fn = filePathEnvArg0(args);
1730 VisitReturn ok;
1731 if (parseInto.isEmpty()) {
1732 ok = evaluateFileChecked(fileName: fn, type: QMakeHandler::EvalIncludeFile, flags: LoadProOnly | flags);
1733 } else {
1734 ProValueMap symbols;
1735 if ((ok = evaluateFileInto(fileName: fn, values: &symbols, flags: LoadAll | flags)) == ReturnTrue) {
1736 ProValueMap newMap;
1737 for (ProValueMap::ConstIterator
1738 it = m_valuemapStack.top().constBegin(),
1739 end = m_valuemapStack.top().constEnd();
1740 it != end; ++it) {
1741 const ProString &ky = it.key();
1742 if (!ky.startsWith(sub: parseInto))
1743 newMap[it.key()] = it.value();
1744 }
1745 for (ProValueMap::ConstIterator it = symbols.constBegin();
1746 it != symbols.constEnd(); ++it) {
1747 if (!it.key().startsWith(c: QLatin1Char('.')))
1748 newMap.insert(akey: ProKey(parseInto + it.key()), avalue: it.value());
1749 }
1750 m_valuemapStack.top() = newMap;
1751 }
1752 }
1753 if (ok == ReturnFalse && (flags & LoadSilent))
1754 ok = ReturnTrue;
1755 return ok;
1756 }
1757 case T_LOAD: {
1758 bool ignore_error = (args.count() == 2 && isTrue(str: args.at(i: 1)));
1759 VisitReturn ok = evaluateFeatureFile(fileName: m_option->expandEnvVars(str: args.at(i: 0).toQString()),
1760 silent: ignore_error);
1761 if (ok == ReturnFalse && ignore_error)
1762 ok = ReturnTrue;
1763 return ok;
1764 }
1765 case T_DEBUG: {
1766#ifdef PROEVALUATOR_DEBUG
1767 int level = args.at(i: 0).toInt();
1768 if (level <= m_debugLevel) {
1769 ProStringRoUser u1(args.at(i: 1), m_tmp1);
1770 const QString &msg = m_option->expandEnvVars(str: u1.str());
1771 debugMsg(level, fmt: "Project DEBUG: %s", qPrintable(msg));
1772 }
1773#endif
1774 return ReturnTrue;
1775 }
1776 case T_LOG:
1777 case T_ERROR:
1778 case T_WARNING:
1779 case T_MESSAGE: {
1780 ProStringRoUser u1(args.at(i: 0), m_tmp1);
1781 const QString &msg = m_option->expandEnvVars(str: u1.str());
1782 if (!m_skipLevel) {
1783 if (func_t == T_LOG) {
1784#ifdef PROEVALUATOR_FULL
1785 fputs(s: msg.toLatin1().constData(), stderr);
1786#endif
1787 } else if (!msg.isEmpty() || func_t != T_ERROR) {
1788 ProStringRoUser u2(function, m_tmp2);
1789 m_handler->fileMessage(
1790 type: (func_t == T_ERROR ? QMakeHandler::ErrorMessage :
1791 func_t == T_WARNING ? QMakeHandler::WarningMessage :
1792 QMakeHandler::InfoMessage)
1793 | (m_cumulative ? QMakeHandler::CumulativeEvalMessage : 0),
1794 fL1S("Project %1: %2").arg(args: u2.str().toUpper(), args: msg));
1795 }
1796 }
1797 return (func_t == T_ERROR && !m_cumulative) ? ReturnError : ReturnTrue;
1798 }
1799 case T_SYSTEM: {
1800#ifdef PROEVALUATOR_FULL
1801 if (m_cumulative) // Anything else would be insanity
1802 return ReturnFalse;
1803#if QT_CONFIG(process)
1804 QProcess proc;
1805 proc.setProcessChannelMode(QProcess::ForwardedChannels);
1806 runProcess(proc: &proc, command: args.at(i: 0).toQString());
1807 return returnBool(b: proc.exitStatus() == QProcess::NormalExit && proc.exitCode() == 0);
1808#else
1809 int ec = system((QLatin1String("cd ")
1810 + IoUtils::shellQuote(QDir::toNativeSeparators(currentDirectory()))
1811 + QLatin1String(" && ") + args.at(0)).toLocal8Bit().constData());
1812# ifdef Q_OS_UNIX
1813 if (ec != -1 && WIFSIGNALED(ec) && (WTERMSIG(ec) == SIGQUIT || WTERMSIG(ec) == SIGINT))
1814 raise(WTERMSIG(ec));
1815# endif
1816 return returnBool(ec == 0);
1817#endif
1818#else
1819 return ReturnTrue;
1820#endif
1821 }
1822 case T_ISEMPTY: {
1823 return returnBool(b: values(variableName: map(var: args.at(i: 0))).isEmpty());
1824 }
1825 case T_EXISTS: {
1826 QString file = filePathEnvArg0(args);
1827 // Don't use VFS here:
1828 // - it supports neither listing nor even directories
1829 // - it's unlikely that somebody would test for files they created themselves
1830 if (IoUtils::exists(fileName: file))
1831 return ReturnTrue;
1832 int slsh = file.lastIndexOf(c: QLatin1Char('/'));
1833 QString fn = file.mid(position: slsh+1);
1834 if (fn.contains(c: QLatin1Char('*')) || fn.contains(c: QLatin1Char('?'))) {
1835 QString dirstr = file.left(n: slsh+1);
1836 dirstr.detach();
1837 if (!QDir(dirstr).entryList(nameFilters: QStringList(fn)).isEmpty())
1838 return ReturnTrue;
1839 }
1840
1841 return ReturnFalse;
1842 }
1843 case T_MKPATH: {
1844#ifdef PROEVALUATOR_FULL
1845 QString fn = filePathArg0(args);
1846 if (!QDir::current().mkpath(dirPath: fn)) {
1847 evalError(fL1S("Cannot create directory %1.").arg(a: QDir::toNativeSeparators(pathName: fn)));
1848 return ReturnFalse;
1849 }
1850#endif
1851 return ReturnTrue;
1852 }
1853 case T_WRITE_FILE: {
1854 QIODevice::OpenMode mode = QIODevice::Truncate;
1855 QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1856 QString contents;
1857 if (args.count() >= 2) {
1858 const ProStringList &vals = values(variableName: args.at(i: 1).toKey());
1859 if (!vals.isEmpty())
1860 contents = vals.join(sep: QLatin1Char('\n')) + QLatin1Char('\n');
1861 if (args.count() >= 3) {
1862 const auto opts = split_value_list(vals: args.at(i: 2).toQStringRef());
1863 for (const ProString &opt : opts) {
1864 if (opt == QLatin1String("append")) {
1865 mode = QIODevice::Append;
1866 } else if (opt == QLatin1String("exe")) {
1867 flags |= QMakeVfs::VfsExecutable;
1868 } else {
1869 evalError(fL1S("write_file(): invalid flag %1.").arg(a: opt.toQStringView()));
1870 return ReturnFalse;
1871 }
1872 }
1873 }
1874 }
1875 QString path = filePathArg0(args);
1876 return writeFile(ctx: QString(), fn: path, mode, flags, contents);
1877 }
1878 case T_TOUCH: {
1879#ifdef PROEVALUATOR_FULL
1880 ProStringRoUser u1(args.at(i: 0), m_tmp1);
1881 ProStringRoUser u2(args.at(i: 1), m_tmp2);
1882 const QString &tfn = resolvePath(fileName: u1.str());
1883 const QString &rfn = resolvePath(fileName: u2.str());
1884 QString error;
1885 if (!IoUtils::touchFile(targetFileName: tfn, referenceFileName: rfn, errorString: &error)) {
1886 evalError(msg: error);
1887 return ReturnFalse;
1888 }
1889#endif
1890 return ReturnTrue;
1891 }
1892 case T_CACHE:
1893 return testFunc_cache(args);
1894 case T_RELOAD_PROPERTIES:
1895#ifdef QT_BUILD_QMAKE
1896 m_option->reloadProperties();
1897#endif
1898 return ReturnTrue;
1899 default:
1900 evalError(fL1S("Function '%1' is not implemented.").arg(a: function.toQStringView()));
1901 return ReturnFalse;
1902 }
1903}
1904
1905QT_END_NAMESPACE
1906

source code of qtbase/qmake/library/qmakebuiltins.cpp