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