1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qmakeevaluator.h"
5#include "qmakeevaluator_p.h"
6
7#include "qmakeglobals.h"
8#include "qmakeparser.h"
9#include "qmakevfs.h"
10#include "ioutils.h"
11
12#include <qbytearray.h>
13#include <qdatetime.h>
14#include <qdebug.h>
15#include <qdir.h>
16#include <qfile.h>
17#include <qfileinfo.h>
18#include <qlist.h>
19#include <qregularexpression.h>
20#include <qset.h>
21#include <qstack.h>
22#include <qstring.h>
23#include <qstringlist.h>
24#ifdef PROEVALUATOR_THREAD_SAFE
25# include <qthreadpool.h>
26#endif
27
28#ifdef Q_OS_UNIX
29#include <unistd.h>
30#include <sys/utsname.h>
31# ifdef Q_OS_BSD4
32# include <sys/sysctl.h>
33# endif
34#else
35#include <qt_windows.h>
36#endif
37#include <stdio.h>
38#include <stdlib.h>
39
40using namespace QMakeInternal;
41
42QT_BEGIN_NAMESPACE
43
44#define fL1S(s) QString::fromLatin1(s)
45
46// we can't use QThread in qmake
47// this function is a merger of QThread::idealThreadCount from qthread_win.cpp and qthread_unix.cpp
48static int idealThreadCount()
49{
50#ifdef PROEVALUATOR_THREAD_SAFE
51 return QThread::idealThreadCount();
52#elif defined(Q_OS_WIN)
53 SYSTEM_INFO sysinfo;
54 GetSystemInfo(&sysinfo);
55 return sysinfo.dwNumberOfProcessors;
56#else
57 // there are a couple more definitions in the Unix QThread::idealThreadCount, but
58 // we don't need them all here
59 int cores = 1;
60# if defined(Q_OS_BSD4)
61 // FreeBSD, OpenBSD, NetBSD, BSD/OS, OS X
62 size_t len = sizeof(cores);
63 int mib[2];
64 mib[0] = CTL_HW;
65 mib[1] = HW_NCPU;
66 if (sysctl(mib, 2, &cores, &len, NULL, 0) != 0) {
67 perror("sysctl");
68 }
69# elif defined(_SC_NPROCESSORS_ONLN)
70 // the rest: Linux, Solaris, AIX, Tru64
71 cores = (int)sysconf(_SC_NPROCESSORS_ONLN);
72 if (cores == -1)
73 return 1;
74# endif
75 return cores;
76#endif
77}
78
79
80QMakeBaseKey::QMakeBaseKey(const QString &_root, const QString &_stash, bool _hostBuild)
81 : root(_root), stash(_stash), hostBuild(_hostBuild)
82{
83}
84
85size_t qHash(const QMakeBaseKey &key)
86{
87 return qHash(key: key.root) ^ qHash(key: key.stash) ^ (uint)key.hostBuild;
88}
89
90bool operator==(const QMakeBaseKey &one, const QMakeBaseKey &two)
91{
92 return one.root == two.root && one.stash == two.stash && one.hostBuild == two.hostBuild;
93}
94
95QMakeBaseEnv::QMakeBaseEnv()
96 : evaluator(nullptr)
97{
98#ifdef PROEVALUATOR_THREAD_SAFE
99 inProgress = false;
100#endif
101}
102
103QMakeBaseEnv::~QMakeBaseEnv()
104{
105 delete evaluator;
106}
107
108namespace QMakeInternal {
109QMakeStatics statics;
110}
111
112void QMakeEvaluator::initStatics()
113{
114 if (!statics.field_sep.isNull())
115 return;
116
117 statics.field_sep = QLatin1String(" ");
118 statics.strtrue = QLatin1String("true");
119 statics.strfalse = QLatin1String("false");
120 statics.strCONFIG = ProKey("CONFIG");
121 statics.strARGS = ProKey("ARGS");
122 statics.strARGC = ProKey("ARGC");
123 statics.strDot = QLatin1String(".");
124 statics.strDotDot = QLatin1String("..");
125 statics.strever = QLatin1String("ever");
126 statics.strforever = QLatin1String("forever");
127 statics.strhost_build = QLatin1String("host_build");
128 statics.strTEMPLATE = ProKey("TEMPLATE");
129 statics.strQMAKE_PLATFORM = ProKey("QMAKE_PLATFORM");
130 statics.strQMAKE_DIR_SEP = ProKey("QMAKE_DIR_SEP");
131 statics.strQMAKESPEC = ProKey("QMAKESPEC");
132#ifdef PROEVALUATOR_FULL
133 statics.strREQUIRES = ProKey("REQUIRES");
134#endif
135
136 statics.fakeValue = ProStringList(ProString("_FAKE_")); // It has to have a unique begin() value
137
138 initFunctionStatics();
139
140 static const struct {
141 const char * const oldname, * const newname;
142 } mapInits[] = {
143 { .oldname: "INTERFACES", .newname: "FORMS" },
144 { .oldname: "QMAKE_POST_BUILD", .newname: "QMAKE_POST_LINK" },
145 { .oldname: "TARGETDEPS", .newname: "POST_TARGETDEPS" },
146 { .oldname: "LIBPATH", .newname: "QMAKE_LIBDIR" },
147 { .oldname: "QMAKE_EXT_MOC", .newname: "QMAKE_EXT_CPP_MOC" },
148 { .oldname: "QMAKE_MOD_MOC", .newname: "QMAKE_H_MOD_MOC" },
149 { .oldname: "QMAKE_LFLAGS_SHAPP", .newname: "QMAKE_LFLAGS_APP" },
150 { .oldname: "PRECOMPH", .newname: "PRECOMPILED_HEADER" },
151 { .oldname: "PRECOMPCPP", .newname: "PRECOMPILED_SOURCE" },
152 { .oldname: "INCPATH", .newname: "INCLUDEPATH" },
153 { .oldname: "QMAKE_EXTRA_WIN_COMPILERS", .newname: "QMAKE_EXTRA_COMPILERS" },
154 { .oldname: "QMAKE_EXTRA_UNIX_COMPILERS", .newname: "QMAKE_EXTRA_COMPILERS" },
155 { .oldname: "QMAKE_EXTRA_WIN_TARGETS", .newname: "QMAKE_EXTRA_TARGETS" },
156 { .oldname: "QMAKE_EXTRA_UNIX_TARGETS", .newname: "QMAKE_EXTRA_TARGETS" },
157 { .oldname: "QMAKE_EXTRA_UNIX_INCLUDES", .newname: "QMAKE_EXTRA_INCLUDES" },
158 { .oldname: "QMAKE_EXTRA_UNIX_VARIABLES", .newname: "QMAKE_EXTRA_VARIABLES" },
159 { .oldname: "QMAKE_RPATH", .newname: "QMAKE_LFLAGS_RPATH" },
160 { .oldname: "QMAKE_FRAMEWORKDIR", .newname: "QMAKE_FRAMEWORKPATH" },
161 { .oldname: "QMAKE_FRAMEWORKDIR_FLAGS", .newname: "QMAKE_FRAMEWORKPATH_FLAGS" },
162 { .oldname: "IN_PWD", .newname: "PWD" },
163 { .oldname: "DEPLOYMENT", .newname: "INSTALLS" }
164 };
165 statics.varMap.reserve(size: (int)(sizeof(mapInits)/sizeof(mapInits[0])));
166 for (unsigned i = 0; i < sizeof(mapInits)/sizeof(mapInits[0]); ++i)
167 statics.varMap.insert(key: ProKey(mapInits[i].oldname), value: ProKey(mapInits[i].newname));
168}
169
170const ProKey &QMakeEvaluator::map(const ProKey &var)
171{
172 QHash<ProKey, ProKey>::ConstIterator it = statics.varMap.constFind(key: var);
173 if (it == statics.varMap.constEnd())
174 return var;
175 deprecationWarning(fL1S("Variable %1 is deprecated; use %2 instead.")
176 .arg(args: var.toQString(), args: it.value().toQString()));
177 return it.value();
178}
179
180
181QMakeEvaluator::QMakeEvaluator(QMakeGlobals *option, QMakeParser *parser, QMakeVfs *vfs,
182 QMakeHandler *handler)
183 :
184#ifdef PROEVALUATOR_DEBUG
185 m_debugLevel(option->debugLevel),
186#endif
187 m_option(option), m_parser(parser), m_handler(handler), m_vfs(vfs)
188{
189 // So that single-threaded apps don't have to call initialize() for now.
190 initStatics();
191
192 // Configuration, more or less
193 m_caller = nullptr;
194#ifdef PROEVALUATOR_CUMULATIVE
195 m_cumulative = false;
196#endif
197 m_hostBuild = false;
198
199 // Evaluator state
200#ifdef PROEVALUATOR_CUMULATIVE
201 m_skipLevel = 0;
202#endif
203 m_listCount = 0;
204 m_toggle = 0;
205 m_valuemapStack.push(t: ProValueMap());
206 m_valuemapInited = false;
207}
208
209QMakeEvaluator::~QMakeEvaluator()
210{
211}
212
213void QMakeEvaluator::initFrom(const QMakeEvaluator *other)
214{
215 Q_ASSERT_X(other, "QMakeEvaluator::visitProFile", "Project not prepared");
216 m_functionDefs = other->m_functionDefs;
217 m_valuemapStack = other->m_valuemapStack;
218 m_valuemapInited = true;
219 m_qmakespec = other->m_qmakespec;
220 m_qmakespecName = other->m_qmakespecName;
221 m_mkspecPaths = other->m_mkspecPaths;
222 m_featureRoots = other->m_featureRoots;
223 m_dirSep = other->m_dirSep;
224}
225
226//////// Evaluator tools /////////
227
228uint QMakeEvaluator::getBlockLen(const ushort *&tokPtr)
229{
230 uint len = *tokPtr++;
231 len |= (uint)*tokPtr++ << 16;
232 return len;
233}
234
235void QMakeEvaluator::skipStr(const ushort *&tokPtr)
236{
237 uint len = *tokPtr++;
238 tokPtr += len;
239}
240
241void QMakeEvaluator::skipHashStr(const ushort *&tokPtr)
242{
243 tokPtr += 2;
244 uint len = *tokPtr++;
245 tokPtr += len;
246}
247
248// FIXME: this should not build new strings for direct sections.
249// Note that the E_SPRINTF and E_LIST implementations rely on the deep copy.
250ProStringList QMakeEvaluator::split_value_list(QStringView vals, int source)
251{
252 QString build;
253 ProStringList ret;
254
255 if (!source)
256 source = currentFileId();
257
258 const QChar *vals_data = vals.data();
259 const int vals_len = vals.size();
260 char16_t quote = 0;
261 bool hadWord = false;
262 for (int x = 0; x < vals_len; x++) {
263 char16_t unicode = vals_data[x].unicode();
264 if (unicode == quote) {
265 quote = 0;
266 hadWord = true;
267 build += QChar(unicode);
268 continue;
269 }
270 switch (unicode) {
271 case '"':
272 case '\'':
273 if (!quote)
274 quote = unicode;
275 // FIXME: this is inconsistent with the "there are no empty strings" dogma.
276 hadWord = true;
277 break;
278 case ' ':
279 case '\t':
280 if (!quote) {
281 if (hadWord) {
282 ret << ProString(build).setSource(source);
283 build.clear();
284 hadWord = false;
285 }
286 continue;
287 }
288 break;
289 case '\\':
290 if (x + 1 != vals_len) {
291 char16_t next = vals_data[++x].unicode();
292 if (next == '\'' || next == '"' || next == '\\') {
293 build += QChar(unicode);
294 unicode = next;
295 } else {
296 --x;
297 }
298 }
299 Q_FALLTHROUGH();
300 default:
301 hadWord = true;
302 break;
303 }
304 build += QChar(unicode);
305 }
306 if (hadWord)
307 ret << ProString(build).setSource(source);
308 return ret;
309}
310
311static void replaceInList(ProStringList *varlist,
312 const QRegularExpression &regexp, const QString &replace, bool global, QString &tmp)
313{
314 for (ProStringList::Iterator varit = varlist->begin(); varit != varlist->end(); ) {
315 ProStringRoUser u1(*varit, tmp);
316 QString val = u1.str();
317 QString copy = val; // Force detach and have a reference value
318 val.replace(re: regexp, after: replace);
319 if (!val.isSharedWith(other: copy) && val != copy) {
320 if (val.isEmpty()) {
321 varit = varlist->erase(pos: varit);
322 } else {
323 (*varit).setValue(val);
324 ++varit;
325 }
326 if (!global)
327 break;
328 } else {
329 ++varit;
330 }
331 }
332}
333
334//////// Evaluator /////////
335
336static ALWAYS_INLINE void addStr(
337 const ProString &str, ProStringList *ret, bool &pending, bool joined)
338{
339 if (joined) {
340 ret->last().append(other: str, pending: &pending);
341 } else {
342 if (!pending) {
343 pending = true;
344 *ret << str;
345 } else {
346 ret->last().append(other: str);
347 }
348 }
349}
350
351static ALWAYS_INLINE void addStrList(
352 const ProStringList &list, ushort tok, ProStringList *ret, bool &pending, bool joined)
353{
354 if (!list.isEmpty()) {
355 if (joined) {
356 ret->last().append(other: list, pending: &pending, skipEmpty1st: !(tok & TokQuoted));
357 } else {
358 if (tok & TokQuoted) {
359 if (!pending) {
360 pending = true;
361 *ret << ProString();
362 }
363 ret->last().append(other: list);
364 } else {
365 if (!pending) {
366 // Another qmake bizzarity: if nothing is pending and the
367 // first element is empty, it will be eaten
368 if (!list.at(i: 0).isEmpty()) {
369 // The common case
370 pending = true;
371 *ret += list;
372 return;
373 }
374 } else {
375 ret->last().append(other: list.at(i: 0));
376 }
377 // This is somewhat slow, but a corner case
378 for (int j = 1; j < list.size(); ++j) {
379 pending = true;
380 *ret << list.at(i: j);
381 }
382 }
383 }
384 }
385}
386
387QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateExpression(
388 const ushort *&tokPtr, ProStringList *ret, bool joined)
389{
390 debugMsg(level: 2, fmt: joined ? "evaluating joined expression" : "evaluating expression");
391 ProFile *pro = m_current.pro;
392 if (joined)
393 *ret << ProString();
394 bool pending = false;
395 forever {
396 ushort tok = *tokPtr++;
397 if (tok & TokNewStr) {
398 debugMsg(level: 2, fmt: "new string");
399 pending = false;
400 }
401 ushort maskedTok = tok & TokMask;
402 switch (maskedTok) {
403 case TokLine:
404 m_current.line = *tokPtr++;
405 break;
406 case TokLiteral: {
407 const ProString &val = pro->getStr(tPtr&: tokPtr);
408 debugMsg(level: 2, fmt: "literal %s", dbgStr(val));
409 addStr(str: val, ret, pending, joined);
410 break; }
411 case TokHashLiteral: {
412 const ProKey &val = pro->getHashStr(tPtr&: tokPtr);
413 debugMsg(level: 2, fmt: "hashed literal %s", dbgStr(val.toString()));
414 addStr(str: val, ret, pending, joined);
415 break; }
416 case TokVariable: {
417 const ProKey &var = pro->getHashStr(tPtr&: tokPtr);
418 const ProStringList &val = values(variableName: map(var));
419 debugMsg(level: 2, fmt: "variable %s => %s", dbgKey(var), dbgStrList(val));
420 addStrList(list: val, tok, ret, pending, joined);
421 break; }
422 case TokProperty: {
423 const ProKey &var = pro->getHashStr(tPtr&: tokPtr);
424 const ProString &val = propertyValue(val: var);
425 debugMsg(level: 2, fmt: "property %s => %s", dbgKey(var), dbgStr(val));
426 addStr(str: val, ret, pending, joined);
427 break; }
428 case TokEnvVar: {
429 const ProString &var = pro->getStr(tPtr&: tokPtr);
430 const ProString &val = ProString(m_option->getEnv(var.toQString()));
431 debugMsg(level: 2, fmt: "env var %s => %s", dbgStr(var), dbgStr(val));
432 addStr(str: val, ret, pending, joined);
433 break; }
434 case TokFuncName: {
435 const ProKey &func = pro->getHashStr(tPtr&: tokPtr);
436 debugMsg(level: 2, fmt: "function %s", dbgKey(func));
437 ProStringList val;
438 if (evaluateExpandFunction(function: func, tokPtr, ret: &val) == ReturnError)
439 return ReturnError;
440 addStrList(list: val, tok, ret, pending, joined);
441 break; }
442 default:
443 debugMsg(level: 2, fmt: "evaluated expression => %s", dbgStrList(*ret));
444 tokPtr--;
445 return ReturnTrue;
446 }
447 }
448}
449
450void QMakeEvaluator::skipExpression(const ushort *&pTokPtr)
451{
452 const ushort *tokPtr = pTokPtr;
453 forever {
454 ushort tok = *tokPtr++;
455 switch (tok) {
456 case TokLine:
457 m_current.line = *tokPtr++;
458 break;
459 case TokValueTerminator:
460 case TokFuncTerminator:
461 pTokPtr = tokPtr;
462 return;
463 case TokArgSeparator:
464 break;
465 default:
466 switch (tok & TokMask) {
467 case TokLiteral:
468 case TokEnvVar:
469 skipStr(tokPtr);
470 break;
471 case TokHashLiteral:
472 case TokVariable:
473 case TokProperty:
474 skipHashStr(tokPtr);
475 break;
476 case TokFuncName:
477 skipHashStr(tokPtr);
478 pTokPtr = tokPtr;
479 skipExpression(pTokPtr);
480 tokPtr = pTokPtr;
481 break;
482 default:
483 Q_ASSERT_X(false, "skipExpression", "Unrecognized token");
484 break;
485 }
486 }
487 }
488}
489
490QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(
491 ProFile *pro, const ushort *tokPtr)
492{
493 m_current.pro = pro;
494 m_current.line = 0;
495 return visitProBlock(tokPtr);
496}
497
498QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(
499 const ushort *tokPtr)
500{
501 traceMsg(fmt: "entering block");
502 ProStringList curr;
503 ProFile *pro = m_current.pro;
504 bool okey = true, or_op = false, invert = false;
505 uint blockLen;
506 while (ushort tok = *tokPtr++) {
507 VisitReturn ret;
508 switch (tok) {
509 case TokLine:
510 m_current.line = *tokPtr++;
511 continue;
512 case TokAssign:
513 case TokAppend:
514 case TokAppendUnique:
515 case TokRemove:
516 case TokReplace:
517 ret = visitProVariable(tok, curr, tokPtr);
518 if (ret == ReturnError)
519 break;
520 curr.clear();
521 continue;
522 case TokBranch:
523 blockLen = getBlockLen(tokPtr);
524 if (m_cumulative) {
525#ifdef PROEVALUATOR_CUMULATIVE
526 if (!okey)
527 m_skipLevel++;
528 ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue;
529 tokPtr += blockLen;
530 blockLen = getBlockLen(tokPtr);
531 if (!okey)
532 m_skipLevel--;
533 else
534 m_skipLevel++;
535 if ((ret == ReturnTrue || ret == ReturnFalse) && blockLen)
536 ret = visitProBlock(tokPtr);
537 if (okey)
538 m_skipLevel--;
539#endif
540 } else {
541 if (okey) {
542 traceMsg(fmt: "taking 'then' branch");
543 ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue;
544 traceMsg(fmt: "finished 'then' branch");
545 }
546 tokPtr += blockLen;
547 blockLen = getBlockLen(tokPtr);
548 if (!okey) {
549 traceMsg(fmt: "taking 'else' branch");
550 ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue;
551 traceMsg(fmt: "finished 'else' branch");
552 }
553 }
554 tokPtr += blockLen;
555 okey = true, or_op = false; // force next evaluation
556 break;
557 case TokForLoop:
558 if (m_cumulative || okey != or_op) {
559 const ProKey &variable = pro->getHashStr(tPtr&: tokPtr);
560 uint exprLen = getBlockLen(tokPtr);
561 const ushort *exprPtr = tokPtr;
562 tokPtr += exprLen;
563 blockLen = getBlockLen(tokPtr);
564 ret = visitProLoop(variable, exprPtr, tokPtr);
565 } else {
566 skipHashStr(tokPtr);
567 uint exprLen = getBlockLen(tokPtr);
568 tokPtr += exprLen;
569 blockLen = getBlockLen(tokPtr);
570 traceMsg(fmt: "skipped loop");
571 ret = ReturnTrue;
572 }
573 tokPtr += blockLen;
574 okey = true, or_op = false; // force next evaluation
575 break;
576 case TokBypassNesting:
577 blockLen = getBlockLen(tokPtr);
578 if ((m_cumulative || okey != or_op) && blockLen) {
579 ProValueMapStack savedValuemapStack = std::move(m_valuemapStack);
580 m_valuemapStack.clear();
581 m_valuemapStack.splice(position: m_valuemapStack.end(),
582 x&: savedValuemapStack, i: savedValuemapStack.begin());
583 traceMsg(fmt: "visiting nesting-bypassing block");
584 ret = visitProBlock(tokPtr);
585 traceMsg(fmt: "visited nesting-bypassing block");
586 savedValuemapStack.splice(position: savedValuemapStack.begin(),
587 x&: m_valuemapStack, i: m_valuemapStack.begin());
588 m_valuemapStack = std::move(savedValuemapStack);
589 } else {
590 traceMsg(fmt: "skipped nesting-bypassing block");
591 ret = ReturnTrue;
592 }
593 tokPtr += blockLen;
594 okey = true, or_op = false; // force next evaluation
595 break;
596 case TokTestDef:
597 case TokReplaceDef:
598 if (m_cumulative || okey != or_op) {
599 const ProKey &name = pro->getHashStr(tPtr&: tokPtr);
600 blockLen = getBlockLen(tokPtr);
601 visitProFunctionDef(tok, name, tokPtr);
602 traceMsg(fmt: "defined %s function %s",
603 tok == TokTestDef ? "test" : "replace", dbgKey(name));
604 } else {
605 traceMsg(fmt: "skipped function definition");
606 skipHashStr(tokPtr);
607 blockLen = getBlockLen(tokPtr);
608 }
609 tokPtr += blockLen;
610 okey = true, or_op = false; // force next evaluation
611 continue;
612 case TokNot:
613 traceMsg(fmt: "NOT");
614 invert ^= true;
615 continue;
616 case TokAnd:
617 traceMsg(fmt: "AND");
618 or_op = false;
619 continue;
620 case TokOr:
621 traceMsg(fmt: "OR");
622 or_op = true;
623 continue;
624 case TokCondition:
625 if (!m_skipLevel && okey != or_op) {
626 if (curr.size() != 1) {
627 if (!m_cumulative || !curr.isEmpty())
628 evalError(fL1S("Conditional must expand to exactly one word."));
629 okey = false;
630 } else {
631 okey = isActiveConfig(config: curr.at(i: 0).toQStringView(), regex: true);
632 traceMsg(fmt: "condition %s is %s", dbgStr(curr.at(0)), dbgBool(okey));
633 okey ^= invert;
634 }
635 } else {
636 traceMsg(fmt: "skipped condition %s", curr.size() == 1 ? dbgStr(curr.at(0)) : "<invalid>");
637 }
638 or_op = !okey; // tentatively force next evaluation
639 invert = false;
640 curr.clear();
641 continue;
642 case TokTestCall:
643 if (!m_skipLevel && okey != or_op) {
644 if (curr.size() != 1) {
645 if (!m_cumulative || !curr.isEmpty())
646 evalError(fL1S("Test name must expand to exactly one word."));
647 skipExpression(pTokPtr&: tokPtr);
648 okey = false;
649 } else {
650 traceMsg(fmt: "evaluating test function %s", dbgStr(curr.at(0)));
651 ret = evaluateConditionalFunction(function: curr.at(i: 0).toKey(), tokPtr);
652 switch (ret) {
653 case ReturnTrue: okey = true; break;
654 case ReturnFalse: okey = false; break;
655 default:
656 traceMsg(fmt: "aborting block, function status: %s", dbgReturn(ret));
657 return ret;
658 }
659 traceMsg(fmt: "test function returned %s", dbgBool(okey));
660 okey ^= invert;
661 }
662 } else if (m_cumulative) {
663#ifdef PROEVALUATOR_CUMULATIVE
664 m_skipLevel++;
665 if (curr.size() != 1)
666 skipExpression(tokPtr);
667 else
668 evaluateConditionalFunction(curr.at(0).toKey(), tokPtr);
669 m_skipLevel--;
670#endif
671 } else {
672 skipExpression(pTokPtr&: tokPtr);
673 traceMsg(fmt: "skipped test function %s", curr.size() == 1 ? dbgStr(curr.at(0)) : "<invalid>");
674 }
675 or_op = !okey; // tentatively force next evaluation
676 invert = false;
677 curr.clear();
678 continue;
679 case TokReturn:
680 m_returnValue = curr;
681 curr.clear();
682 ret = ReturnReturn;
683 goto ctrlstm;
684 case TokBreak:
685 ret = ReturnBreak;
686 goto ctrlstm;
687 case TokNext:
688 ret = ReturnNext;
689 ctrlstm:
690 if (!m_skipLevel && okey != or_op) {
691 traceMsg(fmt: "flow control statement '%s', aborting block", dbgReturn(ret));
692 return ret;
693 }
694 traceMsg(fmt: "skipped flow control statement '%s'", dbgReturn(ret));
695 okey = false, or_op = true; // force next evaluation
696 continue;
697 default: {
698 const ushort *oTokPtr = --tokPtr;
699 ret = evaluateExpression(tokPtr, ret: &curr, joined: false);
700 if (ret == ReturnError || tokPtr != oTokPtr)
701 break;
702 }
703 Q_ASSERT_X(false, "visitProBlock", "unexpected item type");
704 continue;
705 }
706 if (ret != ReturnTrue && ret != ReturnFalse) {
707 traceMsg(fmt: "aborting block, status: %s", dbgReturn(ret));
708 return ret;
709 }
710 }
711 traceMsg(fmt: "leaving block, okey=%s", dbgBool(okey));
712 return returnBool(b: okey);
713}
714
715
716void QMakeEvaluator::visitProFunctionDef(
717 ushort tok, const ProKey &name, const ushort *tokPtr)
718{
719 QHash<ProKey, ProFunctionDef> *hash =
720 (tok == TokTestDef
721 ? &m_functionDefs.testFunctions
722 : &m_functionDefs.replaceFunctions);
723 hash->insert(key: name, value: ProFunctionDef(m_current.pro, tokPtr - m_current.pro->tokPtr()));
724}
725
726QMakeEvaluator::VisitReturn QMakeEvaluator::visitProLoop(
727 const ProKey &_variable, const ushort *exprPtr, const ushort *tokPtr)
728{
729 VisitReturn ret = ReturnTrue;
730 bool infinite = false;
731 int index = 0;
732 ProKey variable;
733 ProStringList oldVarVal;
734 ProStringList it_list_out;
735 if (expandVariableReferences(tokPtr&: exprPtr, sizeHint: 0, ret: &it_list_out, joined: true) == ReturnError)
736 return ReturnError;
737 ProString it_list = it_list_out.at(i: 0);
738 if (_variable.isEmpty()) {
739 if (it_list != statics.strever) {
740 evalError(fL1S("Invalid loop expression."));
741 return ReturnFalse;
742 }
743 it_list = ProString(statics.strforever);
744 } else {
745 variable = map(var: _variable);
746 oldVarVal = values(variableName: variable);
747 }
748 ProStringList list = values(variableName: it_list.toKey());
749 if (list.isEmpty()) {
750 if (it_list == statics.strforever) {
751 if (m_cumulative) {
752 // The termination conditions wouldn't be evaluated, so we must skip it.
753 traceMsg(fmt: "skipping forever loop in cumulative mode");
754 return ReturnFalse;
755 }
756 infinite = true;
757 } else {
758 QStringView itl = it_list.toQStringView();
759 int dotdot = itl.indexOf(s: statics.strDotDot);
760 if (dotdot != -1) {
761 bool ok;
762 int start = itl.left(n: dotdot).toInt(ok: &ok);
763 if (ok) {
764 int end = itl.mid(pos: dotdot+2).toInt(ok: &ok);
765 if (ok) {
766 const int absDiff = qAbs(t: end - start);
767 if (m_cumulative && absDiff > 100) {
768 // Such a loop is unlikely to contribute something useful to the
769 // file collection, and may cause considerable delay.
770 traceMsg(fmt: "skipping excessive loop in cumulative mode");
771 return ReturnFalse;
772 }
773 list.reserve(asize: absDiff + 1);
774 if (start < end) {
775 for (int i = start; i <= end; i++)
776 list << ProString(QString::number(i));
777 } else {
778 for (int i = start; i >= end; i--)
779 list << ProString(QString::number(i));
780 }
781 }
782 }
783 }
784 }
785 }
786
787 if (infinite)
788 traceMsg(fmt: "entering infinite loop for %s", dbgKey(variable));
789 else
790 traceMsg(fmt: "entering loop for %s over %s", dbgKey(variable), dbgStrList(list));
791
792 forever {
793 if (infinite) {
794 if (!variable.isEmpty())
795 m_valuemapStack.top()[variable] = ProStringList(ProString(QString::number(index)));
796 if (++index > 1000) {
797 evalError(fL1S("Ran into infinite loop (> 1000 iterations)."));
798 break;
799 }
800 traceMsg(fmt: "loop iteration %d", index);
801 } else {
802 ProString val;
803 do {
804 if (index >= list.size())
805 goto do_break;
806 val = list.at(i: index++);
807 } while (val.isEmpty()); // stupid, but qmake is like that
808 traceMsg(fmt: "loop iteration %s", dbgStr(val));
809 m_valuemapStack.top()[variable] = ProStringList(val);
810 }
811
812 ret = visitProBlock(tokPtr);
813 switch (ret) {
814 case ReturnTrue:
815 case ReturnFalse:
816 break;
817 case ReturnNext:
818 ret = ReturnTrue;
819 break;
820 case ReturnBreak:
821 ret = ReturnTrue;
822 goto do_break;
823 default:
824 goto do_break;
825 }
826 }
827 do_break:
828
829 traceMsg(fmt: "done looping");
830
831 if (!variable.isEmpty())
832 m_valuemapStack.top()[variable] = oldVarVal;
833 return ret;
834}
835
836QMakeEvaluator::VisitReturn QMakeEvaluator::visitProVariable(
837 ushort tok, const ProStringList &curr, const ushort *&tokPtr)
838{
839 int sizeHint = *tokPtr++;
840
841 if (curr.size() != 1) {
842 skipExpression(pTokPtr&: tokPtr);
843 if (!m_cumulative || !curr.isEmpty())
844 evalError(fL1S("Left hand side of assignment must expand to exactly one word."));
845 return ReturnTrue;
846 }
847 const ProKey &varName = map(var: curr.first());
848
849 if (tok == TokReplace) { // ~=
850 // DEFINES ~= s/a/b/?[gqi]
851
852 ProStringList varVal;
853 if (expandVariableReferences(tokPtr, sizeHint, ret: &varVal, joined: true) == ReturnError)
854 return ReturnError;
855 QStringView val = varVal.at(i: 0).toQStringView();
856 if (val.size() < 4 || val.at(n: 0) != QLatin1Char('s')) {
857 evalError(fL1S("The ~= operator can handle only the s/// function."));
858 return ReturnTrue;
859 }
860 QChar sep = val.at(n: 1);
861 auto func = val.split(sep, behavior: Qt::KeepEmptyParts);
862 if (func.size() < 3 || func.size() > 4) {
863 evalError(fL1S("The s/// function expects 3 or 4 arguments."));
864 return ReturnTrue;
865 }
866
867 bool global = false, quote = false, case_sense = false;
868 if (func.size() == 4) {
869 global = func[3].indexOf(c: QLatin1Char('g')) != -1;
870 case_sense = func[3].indexOf(c: QLatin1Char('i')) == -1;
871 quote = func[3].indexOf(c: QLatin1Char('q')) != -1;
872 }
873 QString pattern = func[1].toString();
874 QString replace = func[2].toString();
875 if (quote)
876 pattern = QRegularExpression::escape(str: pattern);
877
878 QRegularExpression regexp(pattern, case_sense ? QRegularExpression::NoPatternOption :
879 QRegularExpression::CaseInsensitiveOption);
880
881 // We could make a union of modified and unmodified values,
882 // but this will break just as much as it fixes, so leave it as is.
883 replaceInList(varlist: &valuesRef(variableName: varName), regexp, replace, global, tmp&: m_tmp2);
884 debugMsg(level: 2, fmt: "replaced %s with %s", dbgQStr(pattern), dbgQStr(replace));
885 } else {
886 ProStringList varVal;
887 if (expandVariableReferences(tokPtr, sizeHint, ret: &varVal, joined: false) == ReturnError)
888 return ReturnError;
889 switch (tok) {
890 default: // whatever - cannot happen
891 case TokAssign: // =
892 varVal.removeEmpty();
893 // FIXME: add check+warning about accidental value removal.
894 // This may be a bit too noisy, though.
895 m_valuemapStack.top()[varName] = varVal;
896 debugMsg(level: 2, fmt: "assigning");
897 break;
898 case TokAppendUnique: // *=
899 valuesRef(variableName: varName).insertUnique(value: varVal);
900 debugMsg(level: 2, fmt: "appending unique");
901 break;
902 case TokAppend: // +=
903 varVal.removeEmpty();
904 valuesRef(variableName: varName) += varVal;
905 debugMsg(level: 2, fmt: "appending");
906 break;
907 case TokRemove: // -=
908 if (!m_cumulative) {
909 valuesRef(variableName: varName).removeEach(value: varVal);
910 } else {
911 // We are stingy with our values.
912 }
913 debugMsg(level: 2, fmt: "removing");
914 break;
915 }
916 }
917 traceMsg(fmt: "%s := %s", dbgKey(varName), dbgStrList(values(varName)));
918
919 if (varName == statics.strTEMPLATE)
920 setTemplate();
921 else if (varName == statics.strQMAKE_PLATFORM)
922 m_featureRoots = nullptr;
923 else if (varName == statics.strQMAKE_DIR_SEP)
924 m_dirSep = first(variableName: varName);
925 else if (varName == statics.strQMAKESPEC) {
926 if (!values(variableName: varName).isEmpty()) {
927 QString spec = values(variableName: varName).first().toQString();
928 if (IoUtils::isAbsolutePath(fileName: spec)) {
929 m_qmakespec = spec;
930 m_qmakespecName = IoUtils::fileName(fileName: m_qmakespec).toString();
931 m_featureRoots = nullptr;
932 }
933 }
934 }
935#ifdef PROEVALUATOR_FULL
936 else if (varName == statics.strREQUIRES)
937 return checkRequirements(deps: values(variableName: varName));
938#endif
939
940 return ReturnTrue;
941}
942
943void QMakeEvaluator::setTemplate()
944{
945 ProStringList &values = valuesRef(variableName: statics.strTEMPLATE);
946 if (!m_option->user_template.isEmpty()) {
947 // Don't allow override
948 values = ProStringList(ProString(m_option->user_template));
949 } else {
950 if (values.isEmpty())
951 values.append(t: ProString("app"));
952 else
953 values.erase(abegin: values.begin() + 1, aend: values.end());
954 }
955 if (!m_option->user_template_prefix.isEmpty()) {
956 ProString val = values.first();
957 if (!val.startsWith(sub: m_option->user_template_prefix))
958 values = ProStringList(ProString(m_option->user_template_prefix + val));
959 }
960}
961
962#if defined(Q_CC_MSVC)
963static ProString msvcBinDirToQMakeArch(QString subdir)
964{
965 int idx = subdir.indexOf(QLatin1Char('\\'));
966 if (idx == -1)
967 return ProString("x86");
968 subdir.remove(0, idx + 1);
969 idx = subdir.indexOf(QLatin1Char('_'));
970 if (idx >= 0)
971 subdir.remove(0, idx + 1);
972 subdir = subdir.toLower();
973 if (subdir == QLatin1String("amd64"))
974 return ProString("x86_64");
975 // Since 2017 the folder structure from here is HostX64|X86/x64|x86
976 idx = subdir.indexOf(QLatin1Char('\\'));
977 if (idx == -1)
978 return ProString("x86");
979 subdir.remove(0, idx + 1);
980 if (subdir == QLatin1String("x64"))
981 return ProString("x86_64");
982 return ProString(subdir);
983}
984
985static ProString defaultMsvcArchitecture()
986{
987#if defined(Q_OS_WIN64)
988 return ProString("x86_64");
989#else
990 return ProString("x86");
991#endif
992}
993
994static ProString msvcArchitecture(const QString &vcInstallDir, const QString &pathVar)
995{
996 if (vcInstallDir.isEmpty())
997 return defaultMsvcArchitecture();
998 QString vcBinDir = vcInstallDir;
999 if (vcBinDir.endsWith(QLatin1Char('\\')))
1000 vcBinDir.chop(1);
1001 const auto dirs = pathVar.split(QLatin1Char(';'), Qt::SkipEmptyParts);
1002 for (const QString &dir : dirs) {
1003 if (!dir.startsWith(vcBinDir, Qt::CaseInsensitive))
1004 continue;
1005 const ProString arch = msvcBinDirToQMakeArch(dir.mid(vcBinDir.length() + 1));
1006 if (!arch.isEmpty())
1007 return arch;
1008 }
1009 return defaultMsvcArchitecture();
1010}
1011#endif // defined(Q_CC_MSVC)
1012
1013void QMakeEvaluator::loadDefaults()
1014{
1015 ProValueMap &vars = m_valuemapStack.top();
1016
1017 vars[ProKey("DIR_SEPARATOR")] << ProString(m_option->dir_sep);
1018 vars[ProKey("DIRLIST_SEPARATOR")] << ProString(m_option->dirlist_sep);
1019 vars[ProKey("_DATE_")] << ProString(QDateTime::currentDateTime().toString());
1020 if (!m_option->qmake_abslocation.isEmpty())
1021 vars[ProKey("QMAKE_QMAKE")] << ProString(m_option->qmake_abslocation);
1022 if (!m_option->qmake_args.isEmpty())
1023 vars[ProKey("QMAKE_ARGS")] = ProStringList(m_option->qmake_args);
1024 if (!m_option->qtconf.isEmpty())
1025 vars[ProKey("QMAKE_QTCONF")] = ProString(m_option->qtconf);
1026 vars[ProKey("QMAKE_HOST.cpu_count")] = ProString(QString::number(idealThreadCount()));
1027#if defined(Q_OS_WIN32)
1028 vars[ProKey("QMAKE_HOST.os")] << ProString("Windows");
1029
1030 DWORD name_length = 1024;
1031 wchar_t name[1024];
1032 if (GetComputerName(name, &name_length))
1033 vars[ProKey("QMAKE_HOST.name")] << ProString(QString::fromWCharArray(name));
1034
1035 vars[ProKey("QMAKE_HOST.version")] << ProString(QSysInfo::kernelVersion());
1036 vars[ProKey("QMAKE_HOST.version_string")] << ProString(QSysInfo::productVersion());
1037
1038 SYSTEM_INFO info;
1039 GetSystemInfo(&info);
1040 ProString archStr;
1041 switch (info.wProcessorArchitecture) {
1042# ifdef PROCESSOR_ARCHITECTURE_AMD64
1043 case PROCESSOR_ARCHITECTURE_AMD64:
1044 archStr = ProString("x86_64");
1045 break;
1046# endif
1047 case PROCESSOR_ARCHITECTURE_INTEL:
1048 archStr = ProString("x86");
1049 break;
1050 case PROCESSOR_ARCHITECTURE_IA64:
1051# ifdef PROCESSOR_ARCHITECTURE_IA32_ON_WIN64
1052 case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64:
1053# endif
1054 archStr = ProString("IA64");
1055 break;
1056 default:
1057 archStr = ProString("Unknown");
1058 break;
1059 }
1060 vars[ProKey("QMAKE_HOST.arch")] << archStr;
1061
1062# if defined(Q_CC_MSVC) // ### bogus condition, but nobody x-builds for msvc with a different qmake
1063 // Since VS 2017 we need VCToolsInstallDir instead of VCINSTALLDIR
1064 QString vcInstallDir = m_option->getEnv(QLatin1String("VCToolsInstallDir"));
1065 if (vcInstallDir.isEmpty())
1066 vcInstallDir = m_option->getEnv(QLatin1String("VCINSTALLDIR"));
1067 vars[ProKey("QMAKE_TARGET.arch")] = msvcArchitecture(
1068 vcInstallDir,
1069 m_option->getEnv(QLatin1String("PATH")));
1070# endif
1071#elif defined(Q_OS_UNIX)
1072 struct utsname name;
1073 if (uname(name: &name) != -1) {
1074 vars[ProKey("QMAKE_HOST.os")] << ProString(name.sysname);
1075 vars[ProKey("QMAKE_HOST.name")] << ProString(QString::fromLocal8Bit(ba: name.nodename));
1076 vars[ProKey("QMAKE_HOST.version")] << ProString(name.release);
1077 vars[ProKey("QMAKE_HOST.version_string")] << ProString(name.version);
1078 vars[ProKey("QMAKE_HOST.arch")] << ProString(name.machine);
1079 }
1080#endif
1081
1082 m_valuemapInited = true;
1083}
1084
1085bool QMakeEvaluator::prepareProject(const QString &inDir)
1086{
1087 QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1088 QString superdir;
1089 if (m_option->do_cache) {
1090 QString conffile;
1091 QString cachefile = m_option->cachefile;
1092 if (cachefile.isEmpty()) { //find it as it has not been specified
1093 if (m_outputDir.isEmpty())
1094 goto no_cache;
1095 superdir = m_outputDir;
1096 forever {
1097 QString superfile = superdir + QLatin1String("/.qmake.super");
1098 if (m_vfs->exists(fn: superfile, flags)) {
1099 m_superfile = QDir::cleanPath(path: superfile);
1100 break;
1101 }
1102 QFileInfo qdfi(superdir);
1103 if (qdfi.isRoot()) {
1104 superdir.clear();
1105 break;
1106 }
1107 superdir = qdfi.path();
1108 }
1109 QString sdir = inDir;
1110 QString dir = m_outputDir;
1111 forever {
1112 conffile = sdir + QLatin1String("/.qmake.conf");
1113 if (!m_vfs->exists(fn: conffile, flags))
1114 conffile.clear();
1115 cachefile = dir + QLatin1String("/.qmake.cache");
1116 if (!m_vfs->exists(fn: cachefile, flags))
1117 cachefile.clear();
1118 if (!conffile.isEmpty() || !cachefile.isEmpty()) {
1119 if (dir != sdir)
1120 m_sourceRoot = sdir;
1121 m_buildRoot = dir;
1122 break;
1123 }
1124 if (dir == superdir)
1125 goto no_cache;
1126 QFileInfo qsdfi(sdir);
1127 QFileInfo qdfi(dir);
1128 if (qsdfi.isRoot() || qdfi.isRoot())
1129 goto no_cache;
1130 sdir = qsdfi.path();
1131 dir = qdfi.path();
1132 }
1133 } else {
1134 m_buildRoot = QFileInfo(cachefile).path();
1135 }
1136 m_conffile = QDir::cleanPath(path: conffile);
1137 m_cachefile = QDir::cleanPath(path: cachefile);
1138 }
1139 no_cache:
1140
1141 QString dir = m_outputDir;
1142 forever {
1143 QString stashfile = dir + QLatin1String("/.qmake.stash");
1144 if (dir == (!superdir.isEmpty() ? superdir : m_buildRoot) || m_vfs->exists(fn: stashfile, flags)) {
1145 m_stashfile = QDir::cleanPath(path: stashfile);
1146 break;
1147 }
1148 QFileInfo qdfi(dir);
1149 if (qdfi.isRoot())
1150 break;
1151 dir = qdfi.path();
1152 }
1153
1154 return true;
1155}
1156
1157bool QMakeEvaluator::loadSpecInternal()
1158{
1159 if (evaluateFeatureFile(fileName: QLatin1String("spec_pre.prf")) != ReturnTrue)
1160 return false;
1161 QString spec = m_qmakespec + QLatin1String("/qmake.conf");
1162 if (evaluateFile(fileName: spec, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly) != ReturnTrue) {
1163 evalError(fL1S("Could not read qmake configuration file %1.").arg(a: spec));
1164 return false;
1165 }
1166#ifndef QT_BUILD_QMAKE
1167 // Legacy support for Qt4 default specs
1168# ifdef Q_OS_UNIX
1169 if (m_qmakespec.endsWith(QLatin1String("/default-host"))
1170 || m_qmakespec.endsWith(QLatin1String("/default"))) {
1171 QString rspec = QFileInfo(m_qmakespec).symLinkTarget();
1172 if (!rspec.isEmpty())
1173 m_qmakespec = QDir::cleanPath(QDir(m_qmakespec).absoluteFilePath(rspec));
1174 }
1175# else
1176 // We can't resolve symlinks as they do on Unix, so configure.exe puts
1177 // the source of the qmake.conf at the end of the default/qmake.conf in
1178 // the QMAKESPEC_ORIGINAL variable.
1179 const ProString &orig_spec = first(ProKey("QMAKESPEC_ORIGINAL"));
1180 if (!orig_spec.isEmpty()) {
1181 QString spec = orig_spec.toQString();
1182 if (IoUtils::isAbsolutePath(spec))
1183 m_qmakespec = spec;
1184 }
1185# endif
1186#endif
1187 valuesRef(variableName: ProKey("QMAKESPEC")) = ProString(m_qmakespec);
1188 m_qmakespecName = IoUtils::fileName(fileName: m_qmakespec).toString();
1189 // This also ensures that m_featureRoots is valid.
1190 if (evaluateFeatureFile(fileName: QLatin1String("spec_post.prf")) != ReturnTrue)
1191 return false;
1192 return true;
1193}
1194
1195bool QMakeEvaluator::loadSpec()
1196{
1197 QString qmakespec = m_option->expandEnvVars(
1198 str: m_hostBuild ? m_option->qmakespec : m_option->xqmakespec);
1199
1200 {
1201 QMakeEvaluator evaluator(m_option, m_parser, m_vfs, m_handler);
1202 evaluator.m_sourceRoot = m_sourceRoot;
1203 evaluator.m_buildRoot = m_buildRoot;
1204
1205 if (!m_superfile.isEmpty() && evaluator.evaluateFile(
1206 fileName: m_superfile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly|LoadHidden) != ReturnTrue) {
1207 return false;
1208 }
1209 if (!m_conffile.isEmpty() && evaluator.evaluateFile(
1210 fileName: m_conffile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly|LoadHidden) != ReturnTrue) {
1211 return false;
1212 }
1213 if (!m_cachefile.isEmpty() && evaluator.evaluateFile(
1214 fileName: m_cachefile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly|LoadHidden) != ReturnTrue) {
1215 return false;
1216 }
1217 if (qmakespec.isEmpty()) {
1218 if (!m_hostBuild)
1219 qmakespec = evaluator.first(variableName: ProKey("XQMAKESPEC")).toQString();
1220 if (qmakespec.isEmpty())
1221 qmakespec = evaluator.first(variableName: ProKey("QMAKESPEC")).toQString();
1222 }
1223 m_qmakepath = evaluator.values(variableName: ProKey("QMAKEPATH")).toQStringList();
1224 m_qmakefeatures = evaluator.values(variableName: ProKey("QMAKEFEATURES")).toQStringList();
1225 }
1226
1227 updateMkspecPaths();
1228 if (qmakespec.isEmpty())
1229 qmakespec = propertyValue(val: ProKey(m_hostBuild ? "QMAKE_SPEC" : "QMAKE_XSPEC")).toQString();
1230#ifndef QT_BUILD_QMAKE
1231 // Legacy support for Qt4 qmake in Qt Creator, etc.
1232 if (qmakespec.isEmpty())
1233 qmakespec = m_hostBuild ? QLatin1String("default-host") : QLatin1String("default");
1234#endif
1235 if (IoUtils::isRelativePath(fileName: qmakespec)) {
1236 for (const QString &root : std::as_const(t&: m_mkspecPaths)) {
1237 QString mkspec = root + QLatin1Char('/') + qmakespec;
1238 if (IoUtils::exists(fileName: mkspec)) {
1239 qmakespec = mkspec;
1240 goto cool;
1241 }
1242 }
1243 evalError(fL1S("Could not find qmake spec '%1'.").arg(a: qmakespec));
1244 return false;
1245 }
1246 cool:
1247 m_qmakespec = QDir::cleanPath(path: qmakespec);
1248
1249 if (!m_superfile.isEmpty()) {
1250 valuesRef(variableName: ProKey("_QMAKE_SUPER_CACHE_")) << ProString(m_superfile);
1251 if (evaluateFile(
1252 fileName: m_superfile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly|LoadHidden) != ReturnTrue)
1253 return false;
1254 }
1255 if (!loadSpecInternal())
1256 return false;
1257 if (!m_conffile.isEmpty()) {
1258 valuesRef(variableName: ProKey("_QMAKE_CONF_")) << ProString(m_conffile);
1259 if (evaluateFile(
1260 fileName: m_conffile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly) != ReturnTrue)
1261 return false;
1262 }
1263 if (!m_cachefile.isEmpty()) {
1264 valuesRef(variableName: ProKey("_QMAKE_CACHE_")) << ProString(m_cachefile);
1265 if (evaluateFile(
1266 fileName: m_cachefile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly) != ReturnTrue)
1267 return false;
1268 }
1269 QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1270 if (!m_stashfile.isEmpty() && m_vfs->exists(fn: m_stashfile, flags)) {
1271 valuesRef(variableName: ProKey("_QMAKE_STASH_")) << ProString(m_stashfile);
1272 if (evaluateFile(
1273 fileName: m_stashfile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly) != ReturnTrue)
1274 return false;
1275 }
1276 return true;
1277}
1278
1279void QMakeEvaluator::setupProject()
1280{
1281 setTemplate();
1282 ProValueMap &vars = m_valuemapStack.top();
1283 int proFile = currentFileId();
1284 vars[ProKey("TARGET")] << ProString(QFileInfo(currentFileName()).baseName()).setSource(proFile);
1285 vars[ProKey("_PRO_FILE_")] << ProString(currentFileName()).setSource(proFile);
1286 vars[ProKey("_PRO_FILE_PWD_")] << ProString(currentDirectory()).setSource(proFile);
1287 vars[ProKey("OUT_PWD")] << ProString(m_outputDir).setSource(proFile);
1288}
1289
1290void QMakeEvaluator::evaluateCommand(const QString &cmds, const QString &where)
1291{
1292 if (!cmds.isEmpty()) {
1293 ProFile *pro = m_parser->parsedProBlock(contents: QStringView(cmds), id: 0, name: where, line: -1);
1294 if (pro->isOk()) {
1295 m_locationStack.push(t: m_current);
1296 visitProBlock(pro, tokPtr: pro->tokPtr());
1297 m_current = m_locationStack.pop();
1298 }
1299 pro->deref();
1300 }
1301}
1302
1303void QMakeEvaluator::applyExtraConfigs()
1304{
1305 if (m_extraConfigs.isEmpty())
1306 return;
1307
1308 evaluateCommand(fL1S("CONFIG += ") + m_extraConfigs.join(sep: QLatin1Char(' ')), fL1S("(extra configs)"));
1309}
1310
1311QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConfigFeatures()
1312{
1313 QSet<QString> processed;
1314 forever {
1315 bool finished = true;
1316 ProStringList configs = values(variableName: statics.strCONFIG);
1317 for (int i = configs.size() - 1; i >= 0; --i) {
1318 ProStringRoUser u1(configs.at(i), m_tmp1);
1319 QString config = u1.str().toLower();
1320 if (!processed.contains(value: config)) {
1321 config.detach();
1322 processed.insert(value: config);
1323 VisitReturn vr = evaluateFeatureFile(fileName: config, silent: true);
1324 if (vr == ReturnError && !m_cumulative)
1325 return vr;
1326 if (vr == ReturnTrue) {
1327 finished = false;
1328 break;
1329 }
1330 }
1331 }
1332 if (finished)
1333 break;
1334 }
1335 return ReturnTrue;
1336}
1337
1338QMakeEvaluator::VisitReturn QMakeEvaluator::visitProFile(
1339 ProFile *pro, QMakeHandler::EvalFileType type, LoadFlags flags)
1340{
1341 if (!m_cumulative && !pro->isOk())
1342 return ReturnFalse;
1343
1344 if (flags & LoadPreFiles) {
1345 if (!prepareProject(inDir: pro->directoryName()))
1346 return ReturnFalse;
1347
1348 m_hostBuild = pro->isHostBuild();
1349
1350#ifdef PROEVALUATOR_THREAD_SAFE
1351 m_option->mutex.lock();
1352#endif
1353 QMakeBaseEnv **baseEnvPtr = &m_option->baseEnvs[QMakeBaseKey(m_buildRoot, m_stashfile, m_hostBuild)];
1354 if (!*baseEnvPtr)
1355 *baseEnvPtr = new QMakeBaseEnv;
1356 QMakeBaseEnv *baseEnv = *baseEnvPtr;
1357
1358#ifdef PROEVALUATOR_THREAD_SAFE
1359 QMutexLocker locker(&baseEnv->mutex);
1360 m_option->mutex.unlock();
1361 if (baseEnv->inProgress) {
1362 QThreadPool::globalInstance()->releaseThread();
1363 baseEnv->cond.wait(&baseEnv->mutex);
1364 QThreadPool::globalInstance()->reserveThread();
1365 if (!baseEnv->isOk)
1366 return ReturnFalse;
1367 } else
1368#endif
1369 if (!baseEnv->evaluator) {
1370#ifdef PROEVALUATOR_THREAD_SAFE
1371 baseEnv->inProgress = true;
1372 locker.unlock();
1373#endif
1374
1375 QMakeEvaluator *baseEval = new QMakeEvaluator(m_option, m_parser, m_vfs, m_handler);
1376 baseEnv->evaluator = baseEval;
1377 baseEval->m_superfile = m_superfile;
1378 baseEval->m_conffile = m_conffile;
1379 baseEval->m_cachefile = m_cachefile;
1380 baseEval->m_stashfile = m_stashfile;
1381 baseEval->m_sourceRoot = m_sourceRoot;
1382 baseEval->m_buildRoot = m_buildRoot;
1383 baseEval->m_hostBuild = m_hostBuild;
1384 bool ok = baseEval->loadSpec();
1385
1386#ifdef PROEVALUATOR_THREAD_SAFE
1387 locker.relock();
1388 baseEnv->isOk = ok;
1389 baseEnv->inProgress = false;
1390 baseEnv->cond.wakeAll();
1391#endif
1392
1393 if (!ok)
1394 return ReturnFalse;
1395 }
1396#ifdef PROEVALUATOR_THREAD_SAFE
1397 else if (!baseEnv->isOk)
1398 return ReturnFalse;
1399#endif
1400
1401 initFrom(other: baseEnv->evaluator);
1402 } else {
1403 if (!m_valuemapInited)
1404 loadDefaults();
1405 }
1406
1407 VisitReturn vr;
1408
1409 m_handler->aboutToEval(parent: currentProFile(), proFile: pro, type);
1410 m_profileStack.push(t: pro);
1411 valuesRef(variableName: ProKey("PWD")) = ProStringList(ProString(currentDirectory()));
1412 if (flags & LoadPreFiles) {
1413 setupProject();
1414
1415 if (!m_option->extra_cmds[QMakeEvalEarly].isEmpty())
1416 evaluateCommand(cmds: m_option->extra_cmds[QMakeEvalEarly], fL1S("(command line -early)"));
1417
1418 for (ProValueMap::ConstIterator it = m_extraVars.constBegin();
1419 it != m_extraVars.constEnd(); ++it)
1420 m_valuemapStack.front().insert(key: it.key(), value: it.value());
1421
1422 // In case default_pre needs to make decisions based on the current
1423 // build pass configuration.
1424 applyExtraConfigs();
1425
1426 if ((vr = evaluateFeatureFile(fileName: QLatin1String("default_pre.prf"))) == ReturnError)
1427 goto failed;
1428
1429 if (!m_option->extra_cmds[QMakeEvalBefore].isEmpty()) {
1430 evaluateCommand(cmds: m_option->extra_cmds[QMakeEvalBefore], fL1S("(command line)"));
1431
1432 // Again, after user configs, to override them
1433 applyExtraConfigs();
1434 }
1435 }
1436
1437 debugMsg(level: 1, fmt: "visiting file %s", qPrintable(pro->fileName()));
1438 if ((vr = visitProBlock(pro, tokPtr: pro->tokPtr())) == ReturnError)
1439 goto failed;
1440 debugMsg(level: 1, fmt: "done visiting file %s", qPrintable(pro->fileName()));
1441
1442 if (flags & LoadPostFiles) {
1443 evaluateCommand(cmds: m_option->extra_cmds[QMakeEvalAfter], fL1S("(command line -after)"));
1444
1445 // Again, to ensure the project does not mess with us.
1446 // Specifically, do not allow a project to override debug/release within a
1447 // debug_and_release build pass - it's too late for that at this point anyway.
1448 applyExtraConfigs();
1449
1450 if ((vr = evaluateFeatureFile(fileName: QLatin1String("default_post.prf"))) == ReturnError)
1451 goto failed;
1452
1453 if (!m_option->extra_cmds[QMakeEvalLate].isEmpty())
1454 evaluateCommand(cmds: m_option->extra_cmds[QMakeEvalLate], fL1S("(command line -late)"));
1455
1456 if ((vr = evaluateConfigFeatures()) == ReturnError)
1457 goto failed;
1458 }
1459 vr = ReturnTrue;
1460 failed:
1461 m_profileStack.pop();
1462 valuesRef(variableName: ProKey("PWD")) = ProStringList(ProString(currentDirectory()));
1463 m_handler->doneWithEval(parent: currentProFile());
1464
1465 return vr;
1466}
1467
1468
1469void QMakeEvaluator::updateMkspecPaths()
1470{
1471 QStringList ret;
1472 const QString concat = QLatin1String("/mkspecs");
1473
1474 const auto paths = m_option->getPathListEnv(var: QLatin1String("QMAKEPATH"));
1475 for (const QString &it : paths)
1476 ret << it + concat;
1477
1478 for (const QString &it : std::as_const(t&: m_qmakepath))
1479 ret << it + concat;
1480
1481 if (!m_buildRoot.isEmpty())
1482 ret << m_buildRoot + concat;
1483 if (!m_sourceRoot.isEmpty())
1484 ret << m_sourceRoot + concat;
1485
1486 ret << m_option->propertyValue(name: ProKey("QT_HOST_DATA/get")) + concat;
1487 ret << m_option->propertyValue(name: ProKey("QT_HOST_DATA/src")) + concat;
1488
1489 ret.removeDuplicates();
1490 m_mkspecPaths = ret;
1491}
1492
1493void QMakeEvaluator::updateFeaturePaths()
1494{
1495 QString mkspecs_concat = QLatin1String("/mkspecs");
1496 QString features_concat = QLatin1String("/features/");
1497
1498 QStringList feature_roots;
1499
1500 feature_roots += m_option->getPathListEnv(var: QLatin1String("QMAKEFEATURES"));
1501 feature_roots += m_qmakefeatures;
1502 feature_roots += m_option->splitPathList(
1503 value: m_option->propertyValue(name: ProKey("QMAKEFEATURES")).toQString());
1504
1505 QStringList feature_bases;
1506 if (!m_buildRoot.isEmpty()) {
1507 feature_bases << m_buildRoot + mkspecs_concat;
1508 feature_bases << m_buildRoot;
1509 }
1510 if (!m_sourceRoot.isEmpty()) {
1511 feature_bases << m_sourceRoot + mkspecs_concat;
1512 feature_bases << m_sourceRoot;
1513 }
1514
1515 const auto items = m_option->getPathListEnv(var: QLatin1String("QMAKEPATH"));
1516 for (const QString &item : items)
1517 feature_bases << (item + mkspecs_concat);
1518
1519 for (const QString &item : std::as_const(t&: m_qmakepath))
1520 feature_bases << (item + mkspecs_concat);
1521
1522 if (!m_qmakespec.isEmpty()) {
1523 // The spec is already platform-dependent, so no subdirs here.
1524 feature_roots << (m_qmakespec + features_concat);
1525
1526 // Also check directly under the root directory of the mkspecs collection
1527 QDir specdir(m_qmakespec);
1528 while (!specdir.isRoot() && specdir.cdUp()) {
1529 const QString specpath = specdir.path();
1530 if (specpath.endsWith(s: mkspecs_concat)) {
1531 if (IoUtils::exists(fileName: specpath + features_concat))
1532 feature_bases << specpath;
1533 break;
1534 }
1535 }
1536 }
1537
1538 feature_bases << (m_option->propertyValue(name: ProKey("QT_HOST_DATA/get")) + mkspecs_concat);
1539 feature_bases << (m_option->propertyValue(name: ProKey("QT_HOST_DATA/src")) + mkspecs_concat);
1540
1541 for (const QString &fb : std::as_const(t&: feature_bases)) {
1542 const auto sfxs = values(variableName: ProKey("QMAKE_PLATFORM"));
1543 for (const ProString &sfx : sfxs)
1544 feature_roots << (fb + features_concat + sfx + QLatin1Char('/'));
1545 feature_roots << (fb + features_concat);
1546 }
1547
1548 for (int i = 0; i < feature_roots.size(); ++i)
1549 if (!feature_roots.at(i).endsWith(c: QLatin1Char('/')))
1550 feature_roots[i].append(c: QLatin1Char('/'));
1551
1552 feature_roots.removeDuplicates();
1553
1554 QStringList ret;
1555 for (const QString &root : std::as_const(t&: feature_roots))
1556 if (IoUtils::exists(fileName: root))
1557 ret << root;
1558 m_featureRoots = new QMakeFeatureRoots(ret);
1559}
1560
1561ProString QMakeEvaluator::propertyValue(const ProKey &name) const
1562{
1563 if (name == QLatin1String("QMAKE_MKSPECS"))
1564 return ProString(m_mkspecPaths.join(sep: m_option->dirlist_sep));
1565 ProString ret = m_option->propertyValue(name);
1566// if (ret.isNull())
1567// evalError(fL1S("Querying unknown property %1").arg(name.toQStringView()));
1568 return ret;
1569}
1570
1571ProFile *QMakeEvaluator::currentProFile() const
1572{
1573 if (m_profileStack.size() > 0)
1574 return m_profileStack.top();
1575 return nullptr;
1576}
1577
1578int QMakeEvaluator::currentFileId() const
1579{
1580 ProFile *pro = currentProFile();
1581 if (pro)
1582 return pro->id();
1583 return 0;
1584}
1585
1586QString QMakeEvaluator::currentFileName() const
1587{
1588 ProFile *pro = currentProFile();
1589 if (pro)
1590 return pro->fileName();
1591 return QString();
1592}
1593
1594QString QMakeEvaluator::currentDirectory() const
1595{
1596 ProFile *pro = currentProFile();
1597 if (pro)
1598 return pro->directoryName();
1599 return QString();
1600}
1601
1602bool QMakeEvaluator::isActiveConfig(QStringView config, bool regex)
1603{
1604 // magic types for easy flipping
1605 if (config == statics.strtrue)
1606 return true;
1607 if (config == statics.strfalse)
1608 return false;
1609
1610 if (config == statics.strhost_build)
1611 return m_hostBuild;
1612
1613 if (regex && (config.contains(c: QLatin1Char('*')) || config.contains(c: QLatin1Char('?')))) {
1614 auto re = QRegularExpression::fromWildcard(pattern: config.toString());
1615
1616 // mkspecs
1617 if (re.match(subject: m_qmakespecName).hasMatch())
1618 return true;
1619
1620 // CONFIG variable
1621 const auto configValues = values(variableName: statics.strCONFIG);
1622 for (const ProString &configValue : configValues) {
1623 ProStringRoUser u1(configValue, m_tmp[m_toggle ^= 1]);
1624 if (re.match(subject: u1.str()).hasMatch())
1625 return true;
1626 }
1627 } else {
1628 // mkspecs
1629 if (m_qmakespecName == config)
1630 return true;
1631
1632 // CONFIG variable
1633 if (values(variableName: statics.strCONFIG).contains(str: config))
1634 return true;
1635 }
1636
1637 return false;
1638}
1639
1640QMakeEvaluator::VisitReturn QMakeEvaluator::expandVariableReferences(
1641 const ushort *&tokPtr, int sizeHint, ProStringList *ret, bool joined)
1642{
1643 ret->reserve(asize: sizeHint);
1644 forever {
1645 if (evaluateExpression(tokPtr, ret, joined) == ReturnError)
1646 return ReturnError;
1647 switch (*tokPtr) {
1648 case TokValueTerminator:
1649 case TokFuncTerminator:
1650 tokPtr++;
1651 return ReturnTrue;
1652 case TokArgSeparator:
1653 if (joined) {
1654 tokPtr++;
1655 continue;
1656 }
1657 Q_FALLTHROUGH();
1658 default:
1659 Q_ASSERT_X(false, "expandVariableReferences", "Unrecognized token");
1660 break;
1661 }
1662 }
1663}
1664
1665QMakeEvaluator::VisitReturn QMakeEvaluator::prepareFunctionArgs(
1666 const ushort *&tokPtr, QList<ProStringList> *ret)
1667{
1668 if (*tokPtr != TokFuncTerminator) {
1669 for (;; tokPtr++) {
1670 ProStringList arg;
1671 if (evaluateExpression(tokPtr, ret: &arg, joined: false) == ReturnError)
1672 return ReturnError;
1673 *ret << arg;
1674 if (*tokPtr == TokFuncTerminator)
1675 break;
1676 Q_ASSERT(*tokPtr == TokArgSeparator);
1677 }
1678 }
1679 tokPtr++;
1680 return ReturnTrue;
1681}
1682
1683QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFunction(
1684 const ProFunctionDef &func, const QList<ProStringList> &argumentsList, ProStringList *ret)
1685{
1686 VisitReturn vr;
1687
1688 if (m_valuemapStack.size() >= 100) {
1689 evalError(fL1S("Ran into infinite recursion (depth > 100)."));
1690 vr = ReturnError;
1691 } else {
1692 m_valuemapStack.push(t: ProValueMap());
1693 m_locationStack.push(t: m_current);
1694
1695 ProStringList args;
1696 for (int i = 0; i < argumentsList.size(); ++i) {
1697 args += argumentsList[i];
1698 m_valuemapStack.top()[ProKey(QString::number(i+1))] = argumentsList[i];
1699 }
1700 m_valuemapStack.top()[statics.strARGS] = args;
1701 m_valuemapStack.top()[statics.strARGC] = ProStringList(ProString(QString::number(argumentsList.size())));
1702 vr = visitProBlock(pro: func.pro(), tokPtr: func.tokPtr());
1703 if (vr == ReturnReturn)
1704 vr = ReturnTrue;
1705 if (vr == ReturnTrue)
1706 *ret = m_returnValue;
1707 m_returnValue.clear();
1708
1709 m_current = m_locationStack.pop();
1710 m_valuemapStack.pop();
1711 }
1712 return vr;
1713}
1714
1715QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBoolFunction(
1716 const ProFunctionDef &func, const QList<ProStringList> &argumentsList,
1717 const ProString &function)
1718{
1719 ProStringList ret;
1720 VisitReturn vr = evaluateFunction(func, argumentsList, ret: &ret);
1721 if (vr == ReturnTrue) {
1722 if (ret.isEmpty())
1723 return ReturnTrue;
1724 if (ret.at(i: 0) != statics.strfalse) {
1725 if (ret.at(i: 0) == statics.strtrue)
1726 return ReturnTrue;
1727 bool ok;
1728 int val = ret.at(i: 0).toInt(ok: &ok);
1729 if (ok) {
1730 if (val)
1731 return ReturnTrue;
1732 } else {
1733 ProStringRoUser u1(function, m_tmp1);
1734 evalError(fL1S("Unexpected return value from test '%1': %2.")
1735 .arg(args&: u1.str(), args: ret.join(sep: QLatin1String(" :: "))));
1736 }
1737 }
1738 return ReturnFalse;
1739 }
1740 return vr;
1741}
1742
1743QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditionalFunction(
1744 const ProKey &func, const ushort *&tokPtr)
1745{
1746 auto adef = statics.functions.constFind(key: func);
1747 if (adef != statics.functions.constEnd()) {
1748 //why don't the builtin functions just use args_list? --Sam
1749 ProStringList args;
1750 if (expandVariableReferences(tokPtr, sizeHint: 5, ret: &args, joined: true) == ReturnError)
1751 return ReturnError;
1752 return evaluateBuiltinConditional(adef: *adef, function: func, args);
1753 }
1754
1755 QHash<ProKey, ProFunctionDef>::ConstIterator it =
1756 m_functionDefs.testFunctions.constFind(key: func);
1757 if (it != m_functionDefs.testFunctions.constEnd()) {
1758 QList<ProStringList> args;
1759 if (prepareFunctionArgs(tokPtr, ret: &args) == ReturnError)
1760 return ReturnError;
1761 traceMsg(fmt: "calling %s(%s)", dbgKey(func), dbgStrListList(args));
1762 return evaluateBoolFunction(func: *it, argumentsList: args, function: func);
1763 }
1764
1765 skipExpression(pTokPtr&: tokPtr);
1766 evalError(fL1S("'%1' is not a recognized test function.").arg(a: func.toQStringView()));
1767 return ReturnFalse;
1768}
1769
1770QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateExpandFunction(
1771 const ProKey &func, const ushort *&tokPtr, ProStringList *ret)
1772{
1773 auto adef = statics.expands.constFind(key: func);
1774 if (adef != statics.expands.constEnd()) {
1775 //why don't the builtin functions just use args_list? --Sam
1776 ProStringList args;
1777 if (expandVariableReferences(tokPtr, sizeHint: 5, ret: &args, joined: true) == ReturnError)
1778 return ReturnError;
1779 return evaluateBuiltinExpand(adef: *adef, function: func, args, ret&: *ret);
1780 }
1781
1782 QHash<ProKey, ProFunctionDef>::ConstIterator it =
1783 m_functionDefs.replaceFunctions.constFind(key: func);
1784 if (it != m_functionDefs.replaceFunctions.constEnd()) {
1785 QList<ProStringList> args;
1786 if (prepareFunctionArgs(tokPtr, ret: &args) == ReturnError)
1787 return ReturnError;
1788 traceMsg(fmt: "calling $$%s(%s)", dbgKey(func), dbgStrListList(args));
1789 return evaluateFunction(func: *it, argumentsList: args, ret);
1790 }
1791
1792 skipExpression(pTokPtr&: tokPtr);
1793 evalError(fL1S("'%1' is not a recognized replace function.").arg(a: func.toQStringView()));
1794 return ReturnFalse;
1795}
1796
1797QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditional(
1798 QStringView cond, const QString &where, int line)
1799{
1800 VisitReturn ret = ReturnFalse;
1801 ProFile *pro = m_parser->parsedProBlock(contents: cond, id: 0, name: where, line, grammar: QMakeParser::TestGrammar);
1802 if (pro->isOk()) {
1803 m_locationStack.push(t: m_current);
1804 ret = visitProBlock(pro, tokPtr: pro->tokPtr());
1805 m_current = m_locationStack.pop();
1806 }
1807 pro->deref();
1808 return ret;
1809}
1810
1811#ifdef PROEVALUATOR_FULL
1812QMakeEvaluator::VisitReturn QMakeEvaluator::checkRequirements(const ProStringList &deps)
1813{
1814 ProStringList &failed = valuesRef(variableName: ProKey("QMAKE_FAILED_REQUIREMENTS"));
1815 for (const ProString &dep : deps) {
1816 VisitReturn vr = evaluateConditional(cond: dep.toQStringView(), where: m_current.pro->fileName(), line: m_current.line);
1817 if (vr == ReturnError)
1818 return ReturnError;
1819 if (vr != ReturnTrue)
1820 failed << dep;
1821 }
1822 return ReturnTrue;
1823}
1824#endif
1825
1826static bool isFunctParam(const ProKey &variableName)
1827{
1828 const int len = variableName.size();
1829 const QChar *data = variableName.constData();
1830 for (int i = 0; i < len; i++) {
1831 ushort c = data[i].unicode();
1832 if (c < '0' || c > '9')
1833 return false;
1834 }
1835 return true;
1836}
1837
1838ProValueMap *QMakeEvaluator::findValues(const ProKey &variableName, ProValueMap::Iterator *rit)
1839{
1840 ProValueMapStack::iterator vmi = m_valuemapStack.end();
1841 for (bool first = true; ; first = false) {
1842 --vmi;
1843 ProValueMap::Iterator it = (*vmi).find(key: variableName);
1844 if (it != (*vmi).end()) {
1845 if (it->constBegin() == statics.fakeValue.constBegin())
1846 break;
1847 *rit = it;
1848 return &(*vmi);
1849 }
1850 if (vmi == m_valuemapStack.begin())
1851 break;
1852 if (first && isFunctParam(variableName))
1853 break;
1854 }
1855 return nullptr;
1856}
1857
1858ProStringList &QMakeEvaluator::valuesRef(const ProKey &variableName)
1859{
1860 ProValueMap::Iterator it = m_valuemapStack.top().find(key: variableName);
1861 if (it != m_valuemapStack.top().end()) {
1862 if (it->constBegin() == statics.fakeValue.constBegin())
1863 it->clear();
1864 return *it;
1865 }
1866 if (!isFunctParam(variableName)) {
1867 ProValueMapStack::iterator vmi = m_valuemapStack.end();
1868 if (--vmi != m_valuemapStack.begin()) {
1869 do {
1870 --vmi;
1871 ProValueMap::ConstIterator it = (*vmi).constFind(key: variableName);
1872 if (it != (*vmi).constEnd()) {
1873 ProStringList &ret = m_valuemapStack.top()[variableName];
1874 if (it->constBegin() != statics.fakeValue.constBegin())
1875 ret = *it;
1876 return ret;
1877 }
1878 } while (vmi != m_valuemapStack.begin());
1879 }
1880 }
1881 return m_valuemapStack.top()[variableName];
1882}
1883
1884ProStringList QMakeEvaluator::values(const ProKey &variableName) const
1885{
1886 ProValueMapStack::const_iterator vmi = m_valuemapStack.cend();
1887 for (bool first = true; ; first = false) {
1888 --vmi;
1889 ProValueMap::ConstIterator it = (*vmi).constFind(key: variableName);
1890 if (it != (*vmi).constEnd()) {
1891 if (it->constBegin() == statics.fakeValue.constBegin())
1892 break;
1893 return *it;
1894 }
1895 if (vmi == m_valuemapStack.cbegin())
1896 break;
1897 if (first && isFunctParam(variableName))
1898 break;
1899 }
1900 return ProStringList();
1901}
1902
1903ProString QMakeEvaluator::first(const ProKey &variableName) const
1904{
1905 const ProStringList &vals = values(variableName);
1906 if (!vals.isEmpty())
1907 return vals.first();
1908 return ProString();
1909}
1910
1911QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFile(
1912 const QString &fileName, QMakeHandler::EvalFileType type, LoadFlags flags)
1913{
1914 QMakeParser::ParseFlags pflags = QMakeParser::ParseUseCache;
1915 if (!(flags & LoadSilent))
1916 pflags |= QMakeParser::ParseReportMissing;
1917 if (ProFile *pro = m_parser->parsedProFile(fileName, flags: pflags)) {
1918 m_locationStack.push(t: m_current);
1919 VisitReturn ok = visitProFile(pro, type, flags);
1920 m_current = m_locationStack.pop();
1921 pro->deref();
1922 if (ok == ReturnTrue && !(flags & LoadHidden)) {
1923 ProStringList &iif = m_valuemapStack.front()[ProKey("QMAKE_INTERNAL_INCLUDED_FILES")];
1924 ProString ifn(fileName);
1925 if (!iif.contains(str: ifn))
1926 iif << ifn;
1927 }
1928 return ok;
1929 } else {
1930 return ReturnFalse;
1931 }
1932}
1933
1934QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFileChecked(
1935 const QString &fileName, QMakeHandler::EvalFileType type, LoadFlags flags)
1936{
1937 if (fileName.isEmpty())
1938 return ReturnFalse;
1939 const QMakeEvaluator *ref = this;
1940 do {
1941 for (const ProFile *pf : ref->m_profileStack)
1942 if (pf->fileName() == fileName) {
1943 evalError(fL1S("Circular inclusion of %1.").arg(a: fileName));
1944 return ReturnFalse;
1945 }
1946 } while ((ref = ref->m_caller));
1947 return evaluateFile(fileName, type, flags);
1948}
1949
1950QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFeatureFile(
1951 const QString &fileName, bool silent)
1952{
1953 QString fn = fileName;
1954 if (!fn.endsWith(s: QLatin1String(".prf")))
1955 fn += QLatin1String(".prf");
1956
1957 if (!m_featureRoots)
1958 updateFeaturePaths();
1959#ifdef PROEVALUATOR_THREAD_SAFE
1960 m_featureRoots->mutex.lock();
1961#endif
1962 QString currFn = currentFileName();
1963 if (IoUtils::fileName(fileName: currFn) != IoUtils::fileName(fileName: fn))
1964 currFn.clear();
1965 // Null values cannot regularly exist in the hash, so they indicate that the value still
1966 // needs to be determined. Failed lookups are represented via non-null empty strings.
1967 QString *fnp = &m_featureRoots->cache[qMakePair(value1&: fn, value2&: currFn)];
1968 if (fnp->isNull()) {
1969#ifdef QMAKE_OVERRIDE_PRFS
1970 {
1971 QString ovrfn(QLatin1String(":/qmake/override_features/") + fn);
1972 if (QFileInfo::exists(ovrfn)) {
1973 fn = ovrfn;
1974 goto cool;
1975 }
1976 }
1977#endif
1978 {
1979 int start_root = 0;
1980 const QStringList &paths = m_featureRoots->paths;
1981 if (!currFn.isEmpty()) {
1982 QStringView currPath = IoUtils::pathName(fileName: currFn);
1983 for (int root = 0; root < paths.size(); ++root)
1984 if (currPath == paths.at(i: root)) {
1985 start_root = root + 1;
1986 break;
1987 }
1988 }
1989 for (int root = start_root; root < paths.size(); ++root) {
1990 QString fname = paths.at(i: root) + fn;
1991 if (IoUtils::exists(fileName: fname)) {
1992 fn = fname;
1993 goto cool;
1994 }
1995 }
1996 }
1997#ifdef QMAKE_BUILTIN_PRFS
1998 fn.prepend(QLatin1String(":/qmake/features/"));
1999 if (QFileInfo::exists(fn))
2000 goto cool;
2001#endif
2002 fn = QLatin1String(""); // Indicate failed lookup. See comment above.
2003
2004 cool:
2005 *fnp = fn;
2006 } else {
2007 fn = *fnp;
2008 }
2009#ifdef PROEVALUATOR_THREAD_SAFE
2010 m_featureRoots->mutex.unlock();
2011#endif
2012 if (fn.isEmpty()) {
2013 if (!silent)
2014 evalError(fL1S("Cannot find feature %1").arg(a: fileName));
2015 return ReturnFalse;
2016 }
2017 ProStringList &already = valuesRef(variableName: ProKey("QMAKE_INTERNAL_INCLUDED_FEATURES"));
2018 ProString afn(fn);
2019 if (already.contains(str: afn)) {
2020 if (!silent)
2021 languageWarning(fL1S("Feature %1 already included").arg(a: fileName));
2022 return ReturnTrue;
2023 }
2024 already.append(t: afn);
2025
2026#ifdef PROEVALUATOR_CUMULATIVE
2027 bool cumulative = m_cumulative;
2028 // Even when evaluating the project in cumulative mode to maximize the
2029 // chance of collecting all source declarations, prfs are evaluated in
2030 // exact mode to maximize the chance of them successfully executing
2031 // their programmatic function.
2032 m_cumulative = false;
2033#endif
2034
2035 // The path is fully normalized already.
2036 VisitReturn ok = evaluateFile(fileName: fn, type: QMakeHandler::EvalFeatureFile, flags: LoadProOnly);
2037
2038#ifdef PROEVALUATOR_CUMULATIVE
2039 m_cumulative = cumulative;
2040 if (cumulative) {
2041 // As the data collected in cumulative mode is potentially total
2042 // garbage, yet the prfs fed with it are executed in exact mode,
2043 // we must ignore their results to avoid that evaluation is unduly
2044 // aborted.
2045 ok = ReturnTrue;
2046 }
2047#endif
2048 return ok;
2049}
2050
2051QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFileInto(
2052 const QString &fileName, ProValueMap *values, LoadFlags flags)
2053{
2054 QMakeEvaluator visitor(m_option, m_parser, m_vfs, m_handler);
2055 visitor.m_caller = this;
2056 visitor.m_outputDir = m_outputDir;
2057 visitor.m_featureRoots = m_featureRoots;
2058 VisitReturn ret = visitor.evaluateFileChecked(fileName, type: QMakeHandler::EvalAuxFile, flags);
2059 if (ret != ReturnTrue)
2060 return ret;
2061 *values = visitor.m_valuemapStack.top();
2062 ProKey qiif("QMAKE_INTERNAL_INCLUDED_FILES");
2063 ProStringList &iif = m_valuemapStack.front()[qiif];
2064 const auto ifns = values->value(key: qiif);
2065 for (const ProString &ifn : ifns)
2066 if (!iif.contains(str: ifn))
2067 iif << ifn;
2068 return ReturnTrue;
2069}
2070
2071void QMakeEvaluator::message(int type, const QString &msg) const
2072{
2073 if (!m_skipLevel)
2074 m_handler->message(type: type | (m_cumulative ? QMakeHandler::CumulativeEvalMessage : 0), msg,
2075 fileName: m_current.line ? m_current.pro->fileName() : QString(),
2076 lineNo: m_current.line != 0xffff ? m_current.line : -1);
2077}
2078
2079#ifdef PROEVALUATOR_DEBUG
2080void QMakeEvaluator::debugMsgInternal(int level, const char *fmt, ...) const
2081{
2082 va_list ap;
2083
2084 if (level <= m_debugLevel) {
2085 fprintf(stderr, format: "DEBUG %d: ", level);
2086 va_start(ap, fmt);
2087 vfprintf(stderr, format: fmt, arg: ap);
2088 va_end(ap);
2089 fputc(c: '\n', stderr);
2090 }
2091}
2092
2093void QMakeEvaluator::traceMsgInternal(const char *fmt, ...) const
2094{
2095 va_list ap;
2096
2097 if (!m_current.pro)
2098 fprintf(stderr, format: "DEBUG 1: ");
2099 else if (m_current.line <= 0)
2100 fprintf(stderr, format: "DEBUG 1: %s: ", qPrintable(m_current.pro->fileName()));
2101 else
2102 fprintf(stderr, format: "DEBUG 1: %s:%d: ", qPrintable(m_current.pro->fileName()), m_current.line);
2103 va_start(ap, fmt);
2104 vfprintf(stderr, format: fmt, arg: ap);
2105 va_end(ap);
2106 fputc(c: '\n', stderr);
2107}
2108
2109QString QMakeEvaluator::formatValue(const ProString &val, bool forceQuote)
2110{
2111 QString ret;
2112 ret.reserve(asize: val.size() + 2);
2113 const QChar *chars = val.constData();
2114 bool quote = forceQuote || val.isEmpty();
2115 for (int i = 0, l = val.size(); i < l; i++) {
2116 QChar c = chars[i];
2117 ushort uc = c.unicode();
2118 if (uc < 32) {
2119 switch (uc) {
2120 case '\r':
2121 ret += QLatin1String("\\r");
2122 break;
2123 case '\n':
2124 ret += QLatin1String("\\n");
2125 break;
2126 case '\t':
2127 ret += QLatin1String("\\t");
2128 break;
2129 default:
2130 ret += QString::fromLatin1(ba: "\\x%1").arg(a: uc, fieldWidth: 2, base: 16, fillChar: QLatin1Char('0'));
2131 break;
2132 }
2133 } else {
2134 switch (uc) {
2135 case '\\':
2136 ret += QLatin1String("\\\\");
2137 break;
2138 case '"':
2139 ret += QLatin1String("\\\"");
2140 break;
2141 case '\'':
2142 ret += QLatin1String("\\'");
2143 break;
2144 case 32:
2145 quote = true;
2146 Q_FALLTHROUGH();
2147 default:
2148 ret += c;
2149 break;
2150 }
2151 }
2152 }
2153 if (quote) {
2154 ret.prepend(c: QLatin1Char('"'));
2155 ret.append(c: QLatin1Char('"'));
2156 }
2157 return ret;
2158}
2159
2160QString QMakeEvaluator::formatValueList(const ProStringList &vals, bool commas)
2161{
2162 QString ret;
2163
2164 for (const ProString &str : vals) {
2165 if (!ret.isEmpty()) {
2166 if (commas)
2167 ret += QLatin1Char(',');
2168 ret += QLatin1Char(' ');
2169 }
2170 ret += formatValue(val: str);
2171 }
2172 return ret;
2173}
2174
2175QString QMakeEvaluator::formatValueListList(const QList<ProStringList> &lists)
2176{
2177 QString ret;
2178
2179 for (const ProStringList &list : lists) {
2180 if (!ret.isEmpty())
2181 ret += QLatin1String(", ");
2182 ret += formatValueList(vals: list);
2183 }
2184 return ret;
2185}
2186#endif
2187
2188QT_END_NAMESPACE
2189

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