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