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 qlonglong sde = qgetenv(varName: "SOURCE_DATE_EPOCH").toLongLong();
1017 QDateTime builddate = sde ? QDateTime::fromSecsSinceEpoch(secs: sde)
1018 : QDateTime::currentDateTime();
1019
1020 vars[ProKey("DIR_SEPARATOR")] << ProString(m_option->dir_sep);
1021 vars[ProKey("DIRLIST_SEPARATOR")] << ProString(m_option->dirlist_sep);
1022 vars[ProKey("_DATE_")] << ProString(builddate.toString());
1023 if (!m_option->qmake_abslocation.isEmpty())
1024 vars[ProKey("QMAKE_QMAKE")] << ProString(m_option->qmake_abslocation);
1025 if (!m_option->qmake_args.isEmpty())
1026 vars[ProKey("QMAKE_ARGS")] = ProStringList(m_option->qmake_args);
1027 if (!m_option->qtconf.isEmpty())
1028 vars[ProKey("QMAKE_QTCONF")] = ProString(m_option->qtconf);
1029 vars[ProKey("QMAKE_HOST.cpu_count")] = ProString(QString::number(idealThreadCount()));
1030#if defined(Q_OS_WIN32)
1031 vars[ProKey("QMAKE_HOST.os")] << ProString("Windows");
1032
1033 DWORD name_length = 1024;
1034 wchar_t name[1024];
1035 if (GetComputerName(name, &name_length))
1036 vars[ProKey("QMAKE_HOST.name")] << ProString(QString::fromWCharArray(name));
1037
1038 vars[ProKey("QMAKE_HOST.version")] << ProString(QSysInfo::kernelVersion());
1039 vars[ProKey("QMAKE_HOST.version_string")] << ProString(QSysInfo::productVersion());
1040
1041 SYSTEM_INFO info;
1042 GetSystemInfo(&info);
1043 ProString archStr;
1044 switch (info.wProcessorArchitecture) {
1045# ifdef PROCESSOR_ARCHITECTURE_AMD64
1046 case PROCESSOR_ARCHITECTURE_AMD64:
1047 archStr = ProString("x86_64");
1048 break;
1049# endif
1050# ifdef PROCESSOR_ARCHITECTURE_ARM64
1051 case PROCESSOR_ARCHITECTURE_ARM64:
1052 archStr = ProString("arm64");
1053 break;
1054# endif
1055 case PROCESSOR_ARCHITECTURE_INTEL:
1056 archStr = ProString("x86");
1057 break;
1058 case PROCESSOR_ARCHITECTURE_IA64:
1059# ifdef PROCESSOR_ARCHITECTURE_IA32_ON_WIN64
1060 case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64:
1061# endif
1062 archStr = ProString("IA64");
1063 break;
1064 default:
1065 archStr = ProString("Unknown");
1066 break;
1067 }
1068 vars[ProKey("QMAKE_HOST.arch")] << archStr;
1069
1070# if defined(Q_CC_MSVC) // ### bogus condition, but nobody x-builds for msvc with a different qmake
1071 // Since VS 2017 we need VCToolsInstallDir instead of VCINSTALLDIR
1072 QString vcInstallDir = m_option->getEnv(QLatin1String("VCToolsInstallDir"));
1073 if (vcInstallDir.isEmpty())
1074 vcInstallDir = m_option->getEnv(QLatin1String("VCINSTALLDIR"));
1075 vars[ProKey("QMAKE_TARGET.arch")] = msvcArchitecture(
1076 vcInstallDir,
1077 m_option->getEnv(QLatin1String("PATH")));
1078# endif
1079#elif defined(Q_OS_UNIX)
1080 struct utsname name;
1081 if (uname(name: &name) != -1) {
1082 vars[ProKey("QMAKE_HOST.os")] << ProString(name.sysname);
1083 vars[ProKey("QMAKE_HOST.name")] << ProString(QString::fromLocal8Bit(ba: name.nodename));
1084 vars[ProKey("QMAKE_HOST.version")] << ProString(name.release);
1085 vars[ProKey("QMAKE_HOST.version_string")] << ProString(name.version);
1086 vars[ProKey("QMAKE_HOST.arch")] << ProString(name.machine);
1087 }
1088#endif
1089
1090 m_valuemapInited = true;
1091}
1092
1093bool QMakeEvaluator::prepareProject(const QString &inDir)
1094{
1095 QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1096 QString superdir;
1097 if (m_option->do_cache) {
1098 QString conffile;
1099 QString cachefile = m_option->cachefile;
1100 if (cachefile.isEmpty()) { //find it as it has not been specified
1101 if (m_outputDir.isEmpty())
1102 goto no_cache;
1103 superdir = m_outputDir;
1104 forever {
1105 QString superfile = superdir + QLatin1String("/.qmake.super");
1106 if (m_vfs->exists(fn: superfile, flags)) {
1107 m_superfile = QDir::cleanPath(path: superfile);
1108 break;
1109 }
1110 QFileInfo qdfi(superdir);
1111 if (qdfi.isRoot()) {
1112 superdir.clear();
1113 break;
1114 }
1115 superdir = qdfi.path();
1116 }
1117 QString sdir = inDir;
1118 QString dir = m_outputDir;
1119 forever {
1120 conffile = sdir + QLatin1String("/.qmake.conf");
1121 if (!m_vfs->exists(fn: conffile, flags))
1122 conffile.clear();
1123 cachefile = dir + QLatin1String("/.qmake.cache");
1124 if (!m_vfs->exists(fn: cachefile, flags))
1125 cachefile.clear();
1126 if (!conffile.isEmpty() || !cachefile.isEmpty()) {
1127 if (dir != sdir)
1128 m_sourceRoot = sdir;
1129 m_buildRoot = dir;
1130 break;
1131 }
1132 if (dir == superdir)
1133 goto no_cache;
1134 QFileInfo qsdfi(sdir);
1135 QFileInfo qdfi(dir);
1136 if (qsdfi.isRoot() || qdfi.isRoot())
1137 goto no_cache;
1138 sdir = qsdfi.path();
1139 dir = qdfi.path();
1140 }
1141 } else {
1142 m_buildRoot = QFileInfo(cachefile).path();
1143 }
1144 m_conffile = QDir::cleanPath(path: conffile);
1145 m_cachefile = QDir::cleanPath(path: cachefile);
1146 }
1147 no_cache:
1148
1149 QString dir = m_outputDir;
1150 forever {
1151 QString stashfile = dir + QLatin1String("/.qmake.stash");
1152 if (dir == (!superdir.isEmpty() ? superdir : m_buildRoot) || m_vfs->exists(fn: stashfile, flags)) {
1153 m_stashfile = QDir::cleanPath(path: stashfile);
1154 break;
1155 }
1156 QFileInfo qdfi(dir);
1157 if (qdfi.isRoot())
1158 break;
1159 dir = qdfi.path();
1160 }
1161
1162 return true;
1163}
1164
1165bool QMakeEvaluator::loadSpecInternal()
1166{
1167 if (evaluateFeatureFile(fileName: QLatin1String("spec_pre.prf")) != ReturnTrue)
1168 return false;
1169 QString spec = m_qmakespec + QLatin1String("/qmake.conf");
1170 if (evaluateFile(fileName: spec, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly) != ReturnTrue) {
1171 evalError(fL1S("Could not read qmake configuration file %1.").arg(a: spec));
1172 return false;
1173 }
1174#ifndef QT_BUILD_QMAKE
1175 // Legacy support for Qt4 default specs
1176# ifdef Q_OS_UNIX
1177 if (m_qmakespec.endsWith(QLatin1String("/default-host"))
1178 || m_qmakespec.endsWith(QLatin1String("/default"))) {
1179 QString rspec = QFileInfo(m_qmakespec).symLinkTarget();
1180 if (!rspec.isEmpty())
1181 m_qmakespec = QDir::cleanPath(QDir(m_qmakespec).absoluteFilePath(rspec));
1182 }
1183# else
1184 // We can't resolve symlinks as they do on Unix, so configure.exe puts
1185 // the source of the qmake.conf at the end of the default/qmake.conf in
1186 // the QMAKESPEC_ORIGINAL variable.
1187 const ProString &orig_spec = first(ProKey("QMAKESPEC_ORIGINAL"));
1188 if (!orig_spec.isEmpty()) {
1189 QString spec = orig_spec.toQString();
1190 if (IoUtils::isAbsolutePath(spec))
1191 m_qmakespec = spec;
1192 }
1193# endif
1194#endif
1195 valuesRef(variableName: ProKey("QMAKESPEC")) = ProString(m_qmakespec);
1196 m_qmakespecName = IoUtils::fileName(fileName: m_qmakespec).toString();
1197 // This also ensures that m_featureRoots is valid.
1198 if (evaluateFeatureFile(fileName: QLatin1String("spec_post.prf")) != ReturnTrue)
1199 return false;
1200 return true;
1201}
1202
1203bool QMakeEvaluator::loadSpec()
1204{
1205 QString qmakespec = m_option->expandEnvVars(
1206 str: m_hostBuild ? m_option->qmakespec : m_option->xqmakespec);
1207
1208 {
1209 QMakeEvaluator evaluator(m_option, m_parser, m_vfs, m_handler);
1210 evaluator.m_sourceRoot = m_sourceRoot;
1211 evaluator.m_buildRoot = m_buildRoot;
1212
1213 if (!m_superfile.isEmpty() && evaluator.evaluateFile(
1214 fileName: m_superfile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly|LoadHidden) != ReturnTrue) {
1215 return false;
1216 }
1217 if (!m_conffile.isEmpty() && evaluator.evaluateFile(
1218 fileName: m_conffile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly|LoadHidden) != ReturnTrue) {
1219 return false;
1220 }
1221 if (!m_cachefile.isEmpty() && evaluator.evaluateFile(
1222 fileName: m_cachefile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly|LoadHidden) != ReturnTrue) {
1223 return false;
1224 }
1225 if (qmakespec.isEmpty()) {
1226 if (!m_hostBuild)
1227 qmakespec = evaluator.first(variableName: ProKey("XQMAKESPEC")).toQString();
1228 if (qmakespec.isEmpty())
1229 qmakespec = evaluator.first(variableName: ProKey("QMAKESPEC")).toQString();
1230 }
1231 m_qmakepath = evaluator.values(variableName: ProKey("QMAKEPATH")).toQStringList();
1232 m_qmakefeatures = evaluator.values(variableName: ProKey("QMAKEFEATURES")).toQStringList();
1233 }
1234
1235 updateMkspecPaths();
1236 if (qmakespec.isEmpty())
1237 qmakespec = propertyValue(val: ProKey(m_hostBuild ? "QMAKE_SPEC" : "QMAKE_XSPEC")).toQString();
1238#ifndef QT_BUILD_QMAKE
1239 // Legacy support for Qt4 qmake in Qt Creator, etc.
1240 if (qmakespec.isEmpty())
1241 qmakespec = m_hostBuild ? QLatin1String("default-host") : QLatin1String("default");
1242#endif
1243 if (IoUtils::isRelativePath(fileName: qmakespec)) {
1244 for (const QString &root : std::as_const(t&: m_mkspecPaths)) {
1245 QString mkspec = root + QLatin1Char('/') + qmakespec;
1246 if (IoUtils::exists(fileName: mkspec)) {
1247 qmakespec = mkspec;
1248 goto cool;
1249 }
1250 }
1251 evalError(fL1S("Could not find qmake spec '%1'.").arg(a: qmakespec));
1252 return false;
1253 }
1254 cool:
1255 m_qmakespec = QDir::cleanPath(path: qmakespec);
1256
1257 if (!m_superfile.isEmpty()) {
1258 valuesRef(variableName: ProKey("_QMAKE_SUPER_CACHE_")) << ProString(m_superfile);
1259 if (evaluateFile(
1260 fileName: m_superfile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly|LoadHidden) != ReturnTrue)
1261 return false;
1262 }
1263 if (!loadSpecInternal())
1264 return false;
1265 if (!m_conffile.isEmpty()) {
1266 valuesRef(variableName: ProKey("_QMAKE_CONF_")) << ProString(m_conffile);
1267 if (evaluateFile(
1268 fileName: m_conffile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly) != ReturnTrue)
1269 return false;
1270 }
1271 if (!m_cachefile.isEmpty()) {
1272 valuesRef(variableName: ProKey("_QMAKE_CACHE_")) << ProString(m_cachefile);
1273 if (evaluateFile(
1274 fileName: m_cachefile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly) != ReturnTrue)
1275 return false;
1276 }
1277 QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
1278 if (!m_stashfile.isEmpty() && m_vfs->exists(fn: m_stashfile, flags)) {
1279 valuesRef(variableName: ProKey("_QMAKE_STASH_")) << ProString(m_stashfile);
1280 if (evaluateFile(
1281 fileName: m_stashfile, type: QMakeHandler::EvalConfigFile, flags: LoadProOnly) != ReturnTrue)
1282 return false;
1283 }
1284 return true;
1285}
1286
1287void QMakeEvaluator::setupProject()
1288{
1289 setTemplate();
1290 ProValueMap &vars = m_valuemapStack.top();
1291 int proFile = currentFileId();
1292 vars[ProKey("TARGET")] << ProString(QFileInfo(currentFileName()).baseName()).setSource(proFile);
1293 vars[ProKey("_PRO_FILE_")] << ProString(currentFileName()).setSource(proFile);
1294 vars[ProKey("_PRO_FILE_PWD_")] << ProString(currentDirectory()).setSource(proFile);
1295 vars[ProKey("OUT_PWD")] << ProString(m_outputDir).setSource(proFile);
1296}
1297
1298void QMakeEvaluator::evaluateCommand(const QString &cmds, const QString &where)
1299{
1300 if (!cmds.isEmpty()) {
1301 ProFile *pro = m_parser->parsedProBlock(contents: QStringView(cmds), id: 0, name: where, line: -1);
1302 if (pro->isOk()) {
1303 m_locationStack.push(t: m_current);
1304 visitProBlock(pro, tokPtr: pro->tokPtr());
1305 m_current = m_locationStack.pop();
1306 }
1307 pro->deref();
1308 }
1309}
1310
1311void QMakeEvaluator::applyExtraConfigs()
1312{
1313 if (m_extraConfigs.isEmpty())
1314 return;
1315
1316 evaluateCommand(fL1S("CONFIG += ") + m_extraConfigs.join(sep: QLatin1Char(' ')), fL1S("(extra configs)"));
1317}
1318
1319QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConfigFeatures()
1320{
1321 QSet<QString> processed;
1322 forever {
1323 bool finished = true;
1324 ProStringList configs = values(variableName: statics.strCONFIG);
1325 for (int i = configs.size() - 1; i >= 0; --i) {
1326 ProStringRoUser u1(configs.at(i), m_tmp1);
1327 QString config = u1.str().toLower();
1328 if (!processed.contains(value: config)) {
1329 config.detach();
1330 processed.insert(value: config);
1331 VisitReturn vr = evaluateFeatureFile(fileName: config, silent: true);
1332 if (vr == ReturnError && !m_cumulative)
1333 return vr;
1334 if (vr == ReturnTrue) {
1335 finished = false;
1336 break;
1337 }
1338 }
1339 }
1340 if (finished)
1341 break;
1342 }
1343 return ReturnTrue;
1344}
1345
1346QMakeEvaluator::VisitReturn QMakeEvaluator::visitProFile(
1347 ProFile *pro, QMakeHandler::EvalFileType type, LoadFlags flags)
1348{
1349 if (!m_cumulative && !pro->isOk())
1350 return ReturnFalse;
1351
1352 if (flags & LoadPreFiles) {
1353 if (!prepareProject(inDir: pro->directoryName()))
1354 return ReturnFalse;
1355
1356 m_hostBuild = pro->isHostBuild();
1357
1358#ifdef PROEVALUATOR_THREAD_SAFE
1359 m_option->mutex.lock();
1360#endif
1361 QMakeBaseEnv **baseEnvPtr = &m_option->baseEnvs[QMakeBaseKey(m_buildRoot, m_stashfile, m_hostBuild)];
1362 if (!*baseEnvPtr)
1363 *baseEnvPtr = new QMakeBaseEnv;
1364 QMakeBaseEnv *baseEnv = *baseEnvPtr;
1365
1366#ifdef PROEVALUATOR_THREAD_SAFE
1367 QMutexLocker locker(&baseEnv->mutex);
1368 m_option->mutex.unlock();
1369 if (baseEnv->inProgress) {
1370 QThreadPool::globalInstance()->releaseThread();
1371 baseEnv->cond.wait(&baseEnv->mutex);
1372 QThreadPool::globalInstance()->reserveThread();
1373 if (!baseEnv->isOk)
1374 return ReturnFalse;
1375 } else
1376#endif
1377 if (!baseEnv->evaluator) {
1378#ifdef PROEVALUATOR_THREAD_SAFE
1379 baseEnv->inProgress = true;
1380 locker.unlock();
1381#endif
1382
1383 QMakeEvaluator *baseEval = new QMakeEvaluator(m_option, m_parser, m_vfs, m_handler);
1384 baseEnv->evaluator = baseEval;
1385 baseEval->m_superfile = m_superfile;
1386 baseEval->m_conffile = m_conffile;
1387 baseEval->m_cachefile = m_cachefile;
1388 baseEval->m_stashfile = m_stashfile;
1389 baseEval->m_sourceRoot = m_sourceRoot;
1390 baseEval->m_buildRoot = m_buildRoot;
1391 baseEval->m_hostBuild = m_hostBuild;
1392 bool ok = baseEval->loadSpec();
1393
1394#ifdef PROEVALUATOR_THREAD_SAFE
1395 locker.relock();
1396 baseEnv->isOk = ok;
1397 baseEnv->inProgress = false;
1398 baseEnv->cond.wakeAll();
1399#endif
1400
1401 if (!ok)
1402 return ReturnFalse;
1403 }
1404#ifdef PROEVALUATOR_THREAD_SAFE
1405 else if (!baseEnv->isOk)
1406 return ReturnFalse;
1407#endif
1408
1409 initFrom(other: baseEnv->evaluator);
1410 } else {
1411 if (!m_valuemapInited)
1412 loadDefaults();
1413 }
1414
1415 VisitReturn vr;
1416
1417 m_handler->aboutToEval(parent: currentProFile(), proFile: pro, type);
1418 m_profileStack.push(t: pro);
1419 valuesRef(variableName: ProKey("PWD")) = ProStringList(ProString(currentDirectory()));
1420 if (flags & LoadPreFiles) {
1421 setupProject();
1422
1423 if (!m_option->extra_cmds[QMakeEvalEarly].isEmpty())
1424 evaluateCommand(cmds: m_option->extra_cmds[QMakeEvalEarly], fL1S("(command line -early)"));
1425
1426 for (ProValueMap::ConstIterator it = m_extraVars.constBegin();
1427 it != m_extraVars.constEnd(); ++it)
1428 m_valuemapStack.front().insert(key: it.key(), value: it.value());
1429
1430 // In case default_pre needs to make decisions based on the current
1431 // build pass configuration.
1432 applyExtraConfigs();
1433
1434 if ((vr = evaluateFeatureFile(fileName: QLatin1String("default_pre.prf"))) == ReturnError)
1435 goto failed;
1436
1437 if (!m_option->extra_cmds[QMakeEvalBefore].isEmpty()) {
1438 evaluateCommand(cmds: m_option->extra_cmds[QMakeEvalBefore], fL1S("(command line)"));
1439
1440 // Again, after user configs, to override them
1441 applyExtraConfigs();
1442 }
1443 }
1444
1445 debugMsg(level: 1, fmt: "visiting file %s", qPrintable(pro->fileName()));
1446 if ((vr = visitProBlock(pro, tokPtr: pro->tokPtr())) == ReturnError)
1447 goto failed;
1448 debugMsg(level: 1, fmt: "done visiting file %s", qPrintable(pro->fileName()));
1449
1450 if (flags & LoadPostFiles) {
1451 evaluateCommand(cmds: m_option->extra_cmds[QMakeEvalAfter], fL1S("(command line -after)"));
1452
1453 // Again, to ensure the project does not mess with us.
1454 // Specifically, do not allow a project to override debug/release within a
1455 // debug_and_release build pass - it's too late for that at this point anyway.
1456 applyExtraConfigs();
1457
1458 if ((vr = evaluateFeatureFile(fileName: QLatin1String("default_post.prf"))) == ReturnError)
1459 goto failed;
1460
1461 if (!m_option->extra_cmds[QMakeEvalLate].isEmpty())
1462 evaluateCommand(cmds: m_option->extra_cmds[QMakeEvalLate], fL1S("(command line -late)"));
1463
1464 if ((vr = evaluateConfigFeatures()) == ReturnError)
1465 goto failed;
1466 }
1467 vr = ReturnTrue;
1468 failed:
1469 m_profileStack.pop();
1470 valuesRef(variableName: ProKey("PWD")) = ProStringList(ProString(currentDirectory()));
1471 m_handler->doneWithEval(parent: currentProFile());
1472
1473 return vr;
1474}
1475
1476
1477void QMakeEvaluator::updateMkspecPaths()
1478{
1479 QStringList ret;
1480 const QString concat = QLatin1String("/mkspecs");
1481
1482 const auto paths = m_option->getPathListEnv(var: QLatin1String("QMAKEPATH"));
1483 for (const QString &it : paths)
1484 ret << it + concat;
1485
1486 for (const QString &it : std::as_const(t&: m_qmakepath))
1487 ret << it + concat;
1488
1489 if (!m_buildRoot.isEmpty())
1490 ret << m_buildRoot + concat;
1491 if (!m_sourceRoot.isEmpty())
1492 ret << m_sourceRoot + concat;
1493
1494 ret << m_option->propertyValue(name: ProKey("QT_HOST_DATA/get")) + concat;
1495 ret << m_option->propertyValue(name: ProKey("QT_HOST_DATA/src")) + concat;
1496
1497 ret.removeDuplicates();
1498 m_mkspecPaths = ret;
1499}
1500
1501void QMakeEvaluator::updateFeaturePaths()
1502{
1503 QString mkspecs_concat = QLatin1String("/mkspecs");
1504 QString features_concat = QLatin1String("/features/");
1505
1506 QStringList feature_roots;
1507
1508 feature_roots += m_option->getPathListEnv(var: QLatin1String("QMAKEFEATURES"));
1509 feature_roots += m_qmakefeatures;
1510 feature_roots += m_option->splitPathList(
1511 value: m_option->propertyValue(name: ProKey("QMAKEFEATURES")).toQString());
1512
1513 QStringList feature_bases;
1514 if (!m_buildRoot.isEmpty()) {
1515 feature_bases << m_buildRoot + mkspecs_concat;
1516 feature_bases << m_buildRoot;
1517 }
1518 if (!m_sourceRoot.isEmpty()) {
1519 feature_bases << m_sourceRoot + mkspecs_concat;
1520 feature_bases << m_sourceRoot;
1521 }
1522
1523 const auto items = m_option->getPathListEnv(var: QLatin1String("QMAKEPATH"));
1524 for (const QString &item : items)
1525 feature_bases << (item + mkspecs_concat);
1526
1527 for (const QString &item : std::as_const(t&: m_qmakepath))
1528 feature_bases << (item + mkspecs_concat);
1529
1530 if (!m_qmakespec.isEmpty()) {
1531 // The spec is already platform-dependent, so no subdirs here.
1532 feature_roots << (m_qmakespec + features_concat);
1533
1534 // Also check directly under the root directory of the mkspecs collection
1535 QDir specdir(m_qmakespec);
1536 while (!specdir.isRoot() && specdir.cdUp()) {
1537 const QString specpath = specdir.path();
1538 if (specpath.endsWith(s: mkspecs_concat)) {
1539 if (IoUtils::exists(fileName: specpath + features_concat))
1540 feature_bases << specpath;
1541 break;
1542 }
1543 }
1544 }
1545
1546 feature_bases << (m_option->propertyValue(name: ProKey("QT_HOST_DATA/get")) + mkspecs_concat);
1547 feature_bases << (m_option->propertyValue(name: ProKey("QT_HOST_DATA/src")) + mkspecs_concat);
1548
1549 for (const QString &fb : std::as_const(t&: feature_bases)) {
1550 const auto sfxs = values(variableName: ProKey("QMAKE_PLATFORM"));
1551 for (const ProString &sfx : sfxs)
1552 feature_roots << (fb + features_concat + sfx + QLatin1Char('/'));
1553 feature_roots << (fb + features_concat);
1554 }
1555
1556 for (int i = 0; i < feature_roots.size(); ++i)
1557 if (!feature_roots.at(i).endsWith(c: QLatin1Char('/')))
1558 feature_roots[i].append(c: QLatin1Char('/'));
1559
1560 feature_roots.removeDuplicates();
1561
1562 QStringList ret;
1563 for (const QString &root : std::as_const(t&: feature_roots))
1564 if (IoUtils::exists(fileName: root))
1565 ret << root;
1566 m_featureRoots = new QMakeFeatureRoots(ret);
1567}
1568
1569ProString QMakeEvaluator::propertyValue(const ProKey &name) const
1570{
1571 if (name == QLatin1String("QMAKE_MKSPECS"))
1572 return ProString(m_mkspecPaths.join(sep: m_option->dirlist_sep));
1573 ProString ret = m_option->propertyValue(name);
1574// if (ret.isNull())
1575// evalError(fL1S("Querying unknown property %1").arg(name.toQStringView()));
1576 return ret;
1577}
1578
1579ProFile *QMakeEvaluator::currentProFile() const
1580{
1581 if (m_profileStack.size() > 0)
1582 return m_profileStack.top();
1583 return nullptr;
1584}
1585
1586int QMakeEvaluator::currentFileId() const
1587{
1588 ProFile *pro = currentProFile();
1589 if (pro)
1590 return pro->id();
1591 return 0;
1592}
1593
1594QString QMakeEvaluator::currentFileName() const
1595{
1596 ProFile *pro = currentProFile();
1597 if (pro)
1598 return pro->fileName();
1599 return QString();
1600}
1601
1602QString QMakeEvaluator::currentDirectory() const
1603{
1604 ProFile *pro = currentProFile();
1605 if (pro)
1606 return pro->directoryName();
1607 return QString();
1608}
1609
1610bool QMakeEvaluator::isActiveConfig(QStringView config, bool regex)
1611{
1612 // magic types for easy flipping
1613 if (config == statics.strtrue)
1614 return true;
1615 if (config == statics.strfalse)
1616 return false;
1617
1618 if (config == statics.strhost_build)
1619 return m_hostBuild;
1620
1621 if (regex && (config.contains(c: QLatin1Char('*')) || config.contains(c: QLatin1Char('?')))) {
1622 auto re = QRegularExpression::fromWildcard(pattern: config.toString());
1623
1624 // mkspecs
1625 if (re.match(subject: m_qmakespecName).hasMatch())
1626 return true;
1627
1628 // CONFIG variable
1629 const auto configValues = values(variableName: statics.strCONFIG);
1630 for (const ProString &configValue : configValues) {
1631 ProStringRoUser u1(configValue, m_tmp[m_toggle ^= 1]);
1632 if (re.match(subject: u1.str()).hasMatch())
1633 return true;
1634 }
1635 } else {
1636 // mkspecs
1637 if (m_qmakespecName == config)
1638 return true;
1639
1640 // CONFIG variable
1641 if (values(variableName: statics.strCONFIG).contains(str: config))
1642 return true;
1643 }
1644
1645 return false;
1646}
1647
1648QMakeEvaluator::VisitReturn QMakeEvaluator::expandVariableReferences(
1649 const ushort *&tokPtr, int sizeHint, ProStringList *ret, bool joined)
1650{
1651 ret->reserve(asize: sizeHint);
1652 forever {
1653 if (evaluateExpression(tokPtr, ret, joined) == ReturnError)
1654 return ReturnError;
1655 switch (*tokPtr) {
1656 case TokValueTerminator:
1657 case TokFuncTerminator:
1658 tokPtr++;
1659 return ReturnTrue;
1660 case TokArgSeparator:
1661 if (joined) {
1662 tokPtr++;
1663 continue;
1664 }
1665 Q_FALLTHROUGH();
1666 default:
1667 Q_ASSERT_X(false, "expandVariableReferences", "Unrecognized token");
1668 break;
1669 }
1670 }
1671}
1672
1673QMakeEvaluator::VisitReturn QMakeEvaluator::prepareFunctionArgs(
1674 const ushort *&tokPtr, QList<ProStringList> *ret)
1675{
1676 if (*tokPtr != TokFuncTerminator) {
1677 for (;; tokPtr++) {
1678 ProStringList arg;
1679 if (evaluateExpression(tokPtr, ret: &arg, joined: false) == ReturnError)
1680 return ReturnError;
1681 *ret << arg;
1682 if (*tokPtr == TokFuncTerminator)
1683 break;
1684 Q_ASSERT(*tokPtr == TokArgSeparator);
1685 }
1686 }
1687 tokPtr++;
1688 return ReturnTrue;
1689}
1690
1691QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFunction(
1692 const ProFunctionDef &func, const QList<ProStringList> &argumentsList, ProStringList *ret)
1693{
1694 VisitReturn vr;
1695
1696 if (m_valuemapStack.size() >= 100) {
1697 evalError(fL1S("Ran into infinite recursion (depth > 100)."));
1698 vr = ReturnError;
1699 } else {
1700 m_valuemapStack.push(t: ProValueMap());
1701 m_locationStack.push(t: m_current);
1702
1703 ProStringList args;
1704 for (int i = 0; i < argumentsList.size(); ++i) {
1705 args += argumentsList[i];
1706 m_valuemapStack.top()[ProKey(QString::number(i+1))] = argumentsList[i];
1707 }
1708 m_valuemapStack.top()[statics.strARGS] = args;
1709 m_valuemapStack.top()[statics.strARGC] = ProStringList(ProString(QString::number(argumentsList.size())));
1710 vr = visitProBlock(pro: func.pro(), tokPtr: func.tokPtr());
1711 if (vr == ReturnReturn)
1712 vr = ReturnTrue;
1713 if (vr == ReturnTrue)
1714 *ret = m_returnValue;
1715 m_returnValue.clear();
1716
1717 m_current = m_locationStack.pop();
1718 m_valuemapStack.pop();
1719 }
1720 return vr;
1721}
1722
1723QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBoolFunction(
1724 const ProFunctionDef &func, const QList<ProStringList> &argumentsList,
1725 const ProString &function)
1726{
1727 ProStringList ret;
1728 VisitReturn vr = evaluateFunction(func, argumentsList, ret: &ret);
1729 if (vr == ReturnTrue) {
1730 if (ret.isEmpty())
1731 return ReturnTrue;
1732 if (ret.at(i: 0) != statics.strfalse) {
1733 if (ret.at(i: 0) == statics.strtrue)
1734 return ReturnTrue;
1735 bool ok;
1736 int val = ret.at(i: 0).toInt(ok: &ok);
1737 if (ok) {
1738 if (val)
1739 return ReturnTrue;
1740 } else {
1741 ProStringRoUser u1(function, m_tmp1);
1742 evalError(fL1S("Unexpected return value from test '%1': %2.")
1743 .arg(args&: u1.str(), args: ret.join(sep: QLatin1String(" :: "))));
1744 }
1745 }
1746 return ReturnFalse;
1747 }
1748 return vr;
1749}
1750
1751QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditionalFunction(
1752 const ProKey &func, const ushort *&tokPtr)
1753{
1754 auto adef = statics.functions.constFind(key: func);
1755 if (adef != statics.functions.constEnd()) {
1756 //why don't the builtin functions just use args_list? --Sam
1757 ProStringList args;
1758 if (expandVariableReferences(tokPtr, sizeHint: 5, ret: &args, joined: true) == ReturnError)
1759 return ReturnError;
1760 return evaluateBuiltinConditional(adef: *adef, function: func, args);
1761 }
1762
1763 QHash<ProKey, ProFunctionDef>::ConstIterator it =
1764 m_functionDefs.testFunctions.constFind(key: func);
1765 if (it != m_functionDefs.testFunctions.constEnd()) {
1766 QList<ProStringList> args;
1767 if (prepareFunctionArgs(tokPtr, ret: &args) == ReturnError)
1768 return ReturnError;
1769 traceMsg(fmt: "calling %s(%s)", dbgKey(func), dbgStrListList(args));
1770 return evaluateBoolFunction(func: *it, argumentsList: args, function: func);
1771 }
1772
1773 skipExpression(pTokPtr&: tokPtr);
1774 evalError(fL1S("'%1' is not a recognized test function.").arg(a: func.toQStringView()));
1775 return ReturnFalse;
1776}
1777
1778QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateExpandFunction(
1779 const ProKey &func, const ushort *&tokPtr, ProStringList *ret)
1780{
1781 auto adef = statics.expands.constFind(key: func);
1782 if (adef != statics.expands.constEnd()) {
1783 //why don't the builtin functions just use args_list? --Sam
1784 ProStringList args;
1785 if (expandVariableReferences(tokPtr, sizeHint: 5, ret: &args, joined: true) == ReturnError)
1786 return ReturnError;
1787 return evaluateBuiltinExpand(adef: *adef, function: func, args, ret&: *ret);
1788 }
1789
1790 QHash<ProKey, ProFunctionDef>::ConstIterator it =
1791 m_functionDefs.replaceFunctions.constFind(key: func);
1792 if (it != m_functionDefs.replaceFunctions.constEnd()) {
1793 QList<ProStringList> args;
1794 if (prepareFunctionArgs(tokPtr, ret: &args) == ReturnError)
1795 return ReturnError;
1796 traceMsg(fmt: "calling $$%s(%s)", dbgKey(func), dbgStrListList(args));
1797 return evaluateFunction(func: *it, argumentsList: args, ret);
1798 }
1799
1800 skipExpression(pTokPtr&: tokPtr);
1801 evalError(fL1S("'%1' is not a recognized replace function.").arg(a: func.toQStringView()));
1802 return ReturnFalse;
1803}
1804
1805QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditional(
1806 QStringView cond, const QString &where, int line)
1807{
1808 VisitReturn ret = ReturnFalse;
1809 ProFile *pro = m_parser->parsedProBlock(contents: cond, id: 0, name: where, line, grammar: QMakeParser::TestGrammar);
1810 if (pro->isOk()) {
1811 m_locationStack.push(t: m_current);
1812 ret = visitProBlock(pro, tokPtr: pro->tokPtr());
1813 m_current = m_locationStack.pop();
1814 }
1815 pro->deref();
1816 return ret;
1817}
1818
1819#ifdef PROEVALUATOR_FULL
1820QMakeEvaluator::VisitReturn QMakeEvaluator::checkRequirements(const ProStringList &deps)
1821{
1822 ProStringList &failed = valuesRef(variableName: ProKey("QMAKE_FAILED_REQUIREMENTS"));
1823 for (const ProString &dep : deps) {
1824 VisitReturn vr = evaluateConditional(cond: dep.toQStringView(), where: m_current.pro->fileName(), line: m_current.line);
1825 if (vr == ReturnError)
1826 return ReturnError;
1827 if (vr != ReturnTrue)
1828 failed << dep;
1829 }
1830 return ReturnTrue;
1831}
1832#endif
1833
1834static bool isFunctParam(const ProKey &variableName)
1835{
1836 const int len = variableName.size();
1837 const QChar *data = variableName.constData();
1838 for (int i = 0; i < len; i++) {
1839 ushort c = data[i].unicode();
1840 if (c < '0' || c > '9')
1841 return false;
1842 }
1843 return true;
1844}
1845
1846ProValueMap *QMakeEvaluator::findValues(const ProKey &variableName, ProValueMap::Iterator *rit)
1847{
1848 ProValueMapStack::iterator vmi = m_valuemapStack.end();
1849 for (bool first = true; ; first = false) {
1850 --vmi;
1851 ProValueMap::Iterator it = (*vmi).find(key: variableName);
1852 if (it != (*vmi).end()) {
1853 if (it->constBegin() == statics.fakeValue.constBegin())
1854 break;
1855 *rit = it;
1856 return &(*vmi);
1857 }
1858 if (vmi == m_valuemapStack.begin())
1859 break;
1860 if (first && isFunctParam(variableName))
1861 break;
1862 }
1863 return nullptr;
1864}
1865
1866ProStringList &QMakeEvaluator::valuesRef(const ProKey &variableName)
1867{
1868 ProValueMap::Iterator it = m_valuemapStack.top().find(key: variableName);
1869 if (it != m_valuemapStack.top().end()) {
1870 if (it->constBegin() == statics.fakeValue.constBegin())
1871 it->clear();
1872 return *it;
1873 }
1874 if (!isFunctParam(variableName)) {
1875 ProValueMapStack::iterator vmi = m_valuemapStack.end();
1876 if (--vmi != m_valuemapStack.begin()) {
1877 do {
1878 --vmi;
1879 ProValueMap::ConstIterator it = (*vmi).constFind(key: variableName);
1880 if (it != (*vmi).constEnd()) {
1881 ProStringList &ret = m_valuemapStack.top()[variableName];
1882 if (it->constBegin() != statics.fakeValue.constBegin())
1883 ret = *it;
1884 return ret;
1885 }
1886 } while (vmi != m_valuemapStack.begin());
1887 }
1888 }
1889 return m_valuemapStack.top()[variableName];
1890}
1891
1892ProStringList QMakeEvaluator::values(const ProKey &variableName) const
1893{
1894 ProValueMapStack::const_iterator vmi = m_valuemapStack.cend();
1895 for (bool first = true; ; first = false) {
1896 --vmi;
1897 ProValueMap::ConstIterator it = (*vmi).constFind(key: variableName);
1898 if (it != (*vmi).constEnd()) {
1899 if (it->constBegin() == statics.fakeValue.constBegin())
1900 break;
1901 return *it;
1902 }
1903 if (vmi == m_valuemapStack.cbegin())
1904 break;
1905 if (first && isFunctParam(variableName))
1906 break;
1907 }
1908 return ProStringList();
1909}
1910
1911ProString QMakeEvaluator::first(const ProKey &variableName) const
1912{
1913 const ProStringList &vals = values(variableName);
1914 if (!vals.isEmpty())
1915 return vals.first();
1916 return ProString();
1917}
1918
1919QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFile(
1920 const QString &fileName, QMakeHandler::EvalFileType type, LoadFlags flags)
1921{
1922 QMakeParser::ParseFlags pflags = QMakeParser::ParseUseCache;
1923 if (!(flags & LoadSilent))
1924 pflags |= QMakeParser::ParseReportMissing;
1925 if (ProFile *pro = m_parser->parsedProFile(fileName, flags: pflags)) {
1926 m_locationStack.push(t: m_current);
1927 VisitReturn ok = visitProFile(pro, type, flags);
1928 m_current = m_locationStack.pop();
1929 pro->deref();
1930 if (ok == ReturnTrue && !(flags & LoadHidden)) {
1931 ProStringList &iif = m_valuemapStack.front()[ProKey("QMAKE_INTERNAL_INCLUDED_FILES")];
1932 ProString ifn(fileName);
1933 if (!iif.contains(str: ifn))
1934 iif << ifn;
1935 }
1936 return ok;
1937 } else {
1938 return ReturnFalse;
1939 }
1940}
1941
1942QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFileChecked(
1943 const QString &fileName, QMakeHandler::EvalFileType type, LoadFlags flags)
1944{
1945 if (fileName.isEmpty())
1946 return ReturnFalse;
1947 const QMakeEvaluator *ref = this;
1948 do {
1949 for (const ProFile *pf : ref->m_profileStack)
1950 if (pf->fileName() == fileName) {
1951 evalError(fL1S("Circular inclusion of %1.").arg(a: fileName));
1952 return ReturnFalse;
1953 }
1954 } while ((ref = ref->m_caller));
1955 return evaluateFile(fileName, type, flags);
1956}
1957
1958QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFeatureFile(
1959 const QString &fileName, bool silent)
1960{
1961 QString fn = fileName;
1962 if (!fn.endsWith(s: QLatin1String(".prf")))
1963 fn += QLatin1String(".prf");
1964
1965 if (!m_featureRoots)
1966 updateFeaturePaths();
1967#ifdef PROEVALUATOR_THREAD_SAFE
1968 m_featureRoots->mutex.lock();
1969#endif
1970 QString currFn = currentFileName();
1971 if (IoUtils::fileName(fileName: currFn) != IoUtils::fileName(fileName: fn))
1972 currFn.clear();
1973 // Null values cannot regularly exist in the hash, so they indicate that the value still
1974 // needs to be determined. Failed lookups are represented via non-null empty strings.
1975 QString *fnp = &m_featureRoots->cache[qMakePair(value1&: fn, value2&: currFn)];
1976 if (fnp->isNull()) {
1977#ifdef QMAKE_OVERRIDE_PRFS
1978 {
1979 QString ovrfn(QLatin1String(":/qmake/override_features/") + fn);
1980 if (QFileInfo::exists(ovrfn)) {
1981 fn = ovrfn;
1982 goto cool;
1983 }
1984 }
1985#endif
1986 {
1987 int start_root = 0;
1988 const QStringList &paths = m_featureRoots->paths;
1989 if (!currFn.isEmpty()) {
1990 QStringView currPath = IoUtils::pathName(fileName: currFn);
1991 for (int root = 0; root < paths.size(); ++root)
1992 if (currPath == paths.at(i: root)) {
1993 start_root = root + 1;
1994 break;
1995 }
1996 }
1997 for (int root = start_root; root < paths.size(); ++root) {
1998 QString fname = paths.at(i: root) + fn;
1999 if (IoUtils::exists(fileName: fname)) {
2000 fn = fname;
2001 goto cool;
2002 }
2003 }
2004 }
2005#ifdef QMAKE_BUILTIN_PRFS
2006 fn.prepend(QLatin1String(":/qmake/features/"));
2007 if (QFileInfo::exists(fn))
2008 goto cool;
2009#endif
2010 fn = QLatin1String(""); // Indicate failed lookup. See comment above.
2011
2012 cool:
2013 *fnp = fn;
2014 } else {
2015 fn = *fnp;
2016 }
2017#ifdef PROEVALUATOR_THREAD_SAFE
2018 m_featureRoots->mutex.unlock();
2019#endif
2020 if (fn.isEmpty()) {
2021 if (!silent)
2022 evalError(fL1S("Cannot find feature %1").arg(a: fileName));
2023 return ReturnFalse;
2024 }
2025 ProStringList &already = valuesRef(variableName: ProKey("QMAKE_INTERNAL_INCLUDED_FEATURES"));
2026 ProString afn(fn);
2027 if (already.contains(str: afn)) {
2028 if (!silent)
2029 languageWarning(fL1S("Feature %1 already included").arg(a: fileName));
2030 return ReturnTrue;
2031 }
2032 already.append(t: afn);
2033
2034#ifdef PROEVALUATOR_CUMULATIVE
2035 bool cumulative = m_cumulative;
2036 // Even when evaluating the project in cumulative mode to maximize the
2037 // chance of collecting all source declarations, prfs are evaluated in
2038 // exact mode to maximize the chance of them successfully executing
2039 // their programmatic function.
2040 m_cumulative = false;
2041#endif
2042
2043 // The path is fully normalized already.
2044 VisitReturn ok = evaluateFile(fileName: fn, type: QMakeHandler::EvalFeatureFile, flags: LoadProOnly);
2045
2046#ifdef PROEVALUATOR_CUMULATIVE
2047 m_cumulative = cumulative;
2048 if (cumulative) {
2049 // As the data collected in cumulative mode is potentially total
2050 // garbage, yet the prfs fed with it are executed in exact mode,
2051 // we must ignore their results to avoid that evaluation is unduly
2052 // aborted.
2053 ok = ReturnTrue;
2054 }
2055#endif
2056 return ok;
2057}
2058
2059QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFileInto(
2060 const QString &fileName, ProValueMap *values, LoadFlags flags)
2061{
2062 QMakeEvaluator visitor(m_option, m_parser, m_vfs, m_handler);
2063 visitor.m_caller = this;
2064 visitor.m_outputDir = m_outputDir;
2065 visitor.m_featureRoots = m_featureRoots;
2066 VisitReturn ret = visitor.evaluateFileChecked(fileName, type: QMakeHandler::EvalAuxFile, flags);
2067 if (ret != ReturnTrue)
2068 return ret;
2069 *values = visitor.m_valuemapStack.top();
2070 ProKey qiif("QMAKE_INTERNAL_INCLUDED_FILES");
2071 ProStringList &iif = m_valuemapStack.front()[qiif];
2072 const auto ifns = values->value(key: qiif);
2073 for (const ProString &ifn : ifns)
2074 if (!iif.contains(str: ifn))
2075 iif << ifn;
2076 return ReturnTrue;
2077}
2078
2079void QMakeEvaluator::message(int type, const QString &msg) const
2080{
2081 if (!m_skipLevel)
2082 m_handler->message(type: type | (m_cumulative ? QMakeHandler::CumulativeEvalMessage : 0), msg,
2083 fileName: m_current.line ? m_current.pro->fileName() : QString(),
2084 lineNo: m_current.line != 0xffff ? m_current.line : -1);
2085}
2086
2087#ifdef PROEVALUATOR_DEBUG
2088void QMakeEvaluator::debugMsgInternal(int level, const char *fmt, ...) const
2089{
2090 va_list ap;
2091
2092 if (level <= m_debugLevel) {
2093 fprintf(stderr, format: "DEBUG %d: ", level);
2094 va_start(ap, fmt);
2095 vfprintf(stderr, format: fmt, arg: ap);
2096 va_end(ap);
2097 fputc(c: '\n', stderr);
2098 }
2099}
2100
2101void QMakeEvaluator::traceMsgInternal(const char *fmt, ...) const
2102{
2103 va_list ap;
2104
2105 if (!m_current.pro)
2106 fprintf(stderr, format: "DEBUG 1: ");
2107 else if (m_current.line <= 0)
2108 fprintf(stderr, format: "DEBUG 1: %s: ", qPrintable(m_current.pro->fileName()));
2109 else
2110 fprintf(stderr, format: "DEBUG 1: %s:%d: ", qPrintable(m_current.pro->fileName()), m_current.line);
2111 va_start(ap, fmt);
2112 vfprintf(stderr, format: fmt, arg: ap);
2113 va_end(ap);
2114 fputc(c: '\n', stderr);
2115}
2116
2117QString QMakeEvaluator::formatValue(const ProString &val, bool forceQuote)
2118{
2119 QString ret;
2120 ret.reserve(asize: val.size() + 2);
2121 const QChar *chars = val.constData();
2122 bool quote = forceQuote || val.isEmpty();
2123 for (int i = 0, l = val.size(); i < l; i++) {
2124 QChar c = chars[i];
2125 ushort uc = c.unicode();
2126 if (uc < 32) {
2127 switch (uc) {
2128 case '\r':
2129 ret += QLatin1String("\\r");
2130 break;
2131 case '\n':
2132 ret += QLatin1String("\\n");
2133 break;
2134 case '\t':
2135 ret += QLatin1String("\\t");
2136 break;
2137 default:
2138 ret += QString::fromLatin1(ba: "\\x%1").arg(a: uc, fieldWidth: 2, base: 16, fillChar: QLatin1Char('0'));
2139 break;
2140 }
2141 } else {
2142 switch (uc) {
2143 case '\\':
2144 ret += QLatin1String("\\\\");
2145 break;
2146 case '"':
2147 ret += QLatin1String("\\\"");
2148 break;
2149 case '\'':
2150 ret += QLatin1String("\\'");
2151 break;
2152 case 32:
2153 quote = true;
2154 Q_FALLTHROUGH();
2155 default:
2156 ret += c;
2157 break;
2158 }
2159 }
2160 }
2161 if (quote) {
2162 ret.prepend(c: QLatin1Char('"'));
2163 ret.append(c: QLatin1Char('"'));
2164 }
2165 return ret;
2166}
2167
2168QString QMakeEvaluator::formatValueList(const ProStringList &vals, bool commas)
2169{
2170 QString ret;
2171
2172 for (const ProString &str : vals) {
2173 if (!ret.isEmpty()) {
2174 if (commas)
2175 ret += QLatin1Char(',');
2176 ret += QLatin1Char(' ');
2177 }
2178 ret += formatValue(val: str);
2179 }
2180 return ret;
2181}
2182
2183QString QMakeEvaluator::formatValueListList(const QList<ProStringList> &lists)
2184{
2185 QString ret;
2186
2187 for (const ProStringList &list : lists) {
2188 if (!ret.isEmpty())
2189 ret += QLatin1String(", ");
2190 ret += formatValueList(vals: list);
2191 }
2192 return ret;
2193}
2194#endif
2195
2196QT_END_NAMESPACE
2197

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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