Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #include "qmakeevaluator.h"
++ #include "qmakeevaluator_p.h"
++
++ #include "qmakeglobals.h"
++ #include "qmakeparser.h"
++ #include "qmakevfs.h"
++ #include "ioutils.h"
++
++ #include <qbytearray.h>
++ #include <qdatetime.h>
++ #include <qdebug.h>
++ #include <qdir.h>
++ #include <qfile.h>
++ #include <qfileinfo.h>
++ #include <qlist.h>
++ #include <qregularexpression.h>
++ #include <qset.h>
++ #include <qstack.h>
++ #include <qstring.h>
++ #include <qstringlist.h>
++ #ifdef PROEVALUATOR_THREAD_SAFE
++ # include <qthreadpool.h>
++ #endif
++
++ #ifdef Q_OS_UNIX
++ #include <unistd.h>
++ #include <sys/utsname.h>
++ # ifdef Q_OS_BSD4
++ # include <sys/sysctl.h>
++ # endif
++ #else
++ #include <qt_windows.h>
++ #endif
++ #include <stdio.h>
++ #include <stdlib.h>
++
++ using namespace QMakeInternal;
++
++ QT_BEGIN_NAMESPACE
++
++ #define fL1S(s) QString::fromLatin1(s)
++
++ // we can't use QThread in qmake
++ // this function is a merger of QThread::idealThreadCount from qthread_win.cpp and qthread_unix.cpp
++ static int idealThreadCount()
++ {
++ #ifdef PROEVALUATOR_THREAD_SAFE
++ return QThread::idealThreadCount();
++ #elif defined(Q_OS_WIN)
++ SYSTEM_INFO sysinfo;
++ GetSystemInfo(&sysinfo);
++ return sysinfo.dwNumberOfProcessors;
++ #else
++ // there are a couple more definitions in the Unix QThread::idealThreadCount, but
++ // we don't need them all here
++ int cores = 1;
++ # if defined(Q_OS_BSD4)
++ // FreeBSD, OpenBSD, NetBSD, BSD/OS, OS X
++ size_t len = sizeof(cores);
++ int mib[2];
++ mib[0] = CTL_HW;
++ mib[1] = HW_NCPU;
++ if (sysctl(mib, 2, &cores, &len, NULL, 0) != 0) {
++ perror("sysctl");
++ }
++ # elif defined(_SC_NPROCESSORS_ONLN)
++ // the rest: Linux, Solaris, AIX, Tru64
++ cores = (int)sysconf(_SC_NPROCESSORS_ONLN);
++ if (cores == -1)
++ return 1;
++ # endif
++ return cores;
++ #endif
++ }
++
++
++ QMakeBaseKey::QMakeBaseKey(const QString &_root, const QString &_stash, bool _hostBuild)
++ : root(_root), stash(_stash), hostBuild(_hostBuild)
++ {
++ }
++
++ size_t qHash(const QMakeBaseKey &key, size_t seed)
++ {
++ return qHash(key.root, seed) ^ qHash(key.stash, seed) ^ (uint)key.hostBuild;
++ }
++
++ bool operator==(const QMakeBaseKey &one, const QMakeBaseKey &two)
++ {
++ return one.root == two.root && one.stash == two.stash && one.hostBuild == two.hostBuild;
++ }
++
++ QMakeBaseEnv::QMakeBaseEnv()
++ : evaluator(nullptr)
++ {
++ #ifdef PROEVALUATOR_THREAD_SAFE
++ inProgress = false;
++ #endif
++ }
++
++ QMakeBaseEnv::~QMakeBaseEnv()
++ {
++ delete evaluator;
++ }
++
++ namespace QMakeInternal {
++ QMakeStatics statics;
++ }
++
++ void QMakeEvaluator::initStatics()
++ {
++ if (!statics.field_sep.isNull())
++ return;
++
++ statics.field_sep = QLatin1String(" ");
++ statics.strtrue = QLatin1String("true");
++ statics.strfalse = QLatin1String("false");
++ statics.strCONFIG = ProKey("CONFIG");
++ statics.strARGS = ProKey("ARGS");
++ statics.strARGC = ProKey("ARGC");
++ statics.strDot = QLatin1String(".");
++ statics.strDotDot = QLatin1String("..");
++ statics.strever = QLatin1String("ever");
++ statics.strforever = QLatin1String("forever");
++ statics.strhost_build = QLatin1String("host_build");
++ statics.strTEMPLATE = ProKey("TEMPLATE");
++ statics.strQMAKE_PLATFORM = ProKey("QMAKE_PLATFORM");
++ statics.strQMAKE_DIR_SEP = ProKey("QMAKE_DIR_SEP");
++ statics.strQMAKESPEC = ProKey("QMAKESPEC");
++ #ifdef PROEVALUATOR_FULL
++ statics.strREQUIRES = ProKey("REQUIRES");
++ #endif
++
++ statics.fakeValue = ProStringList(ProString("_FAKE_")); // It has to have a unique begin() value
++
++ initFunctionStatics();
++
++ static const struct {
++ const char * const oldname, * const newname;
++ } mapInits[] = {
++ { "INTERFACES", "FORMS" },
++ { "QMAKE_POST_BUILD", "QMAKE_POST_LINK" },
++ { "TARGETDEPS", "POST_TARGETDEPS" },
++ { "LIBPATH", "QMAKE_LIBDIR" },
++ { "QMAKE_EXT_MOC", "QMAKE_EXT_CPP_MOC" },
++ { "QMAKE_MOD_MOC", "QMAKE_H_MOD_MOC" },
++ { "QMAKE_LFLAGS_SHAPP", "QMAKE_LFLAGS_APP" },
++ { "PRECOMPH", "PRECOMPILED_HEADER" },
++ { "PRECOMPCPP", "PRECOMPILED_SOURCE" },
++ { "INCPATH", "INCLUDEPATH" },
++ { "QMAKE_EXTRA_WIN_COMPILERS", "QMAKE_EXTRA_COMPILERS" },
++ { "QMAKE_EXTRA_UNIX_COMPILERS", "QMAKE_EXTRA_COMPILERS" },
++ { "QMAKE_EXTRA_WIN_TARGETS", "QMAKE_EXTRA_TARGETS" },
++ { "QMAKE_EXTRA_UNIX_TARGETS", "QMAKE_EXTRA_TARGETS" },
++ { "QMAKE_EXTRA_UNIX_INCLUDES", "QMAKE_EXTRA_INCLUDES" },
++ { "QMAKE_EXTRA_UNIX_VARIABLES", "QMAKE_EXTRA_VARIABLES" },
++ { "QMAKE_RPATH", "QMAKE_LFLAGS_RPATH" },
++ { "QMAKE_FRAMEWORKDIR", "QMAKE_FRAMEWORKPATH" },
++ { "QMAKE_FRAMEWORKDIR_FLAGS", "QMAKE_FRAMEWORKPATH_FLAGS" },
++ { "IN_PWD", "PWD" },
++ { "DEPLOYMENT", "INSTALLS" }
++ };
++ statics.varMap.reserve((int)(sizeof(mapInits)/sizeof(mapInits[0])));
++ for (unsigned i = 0; i < sizeof(mapInits)/sizeof(mapInits[0]); ++i)
++ statics.varMap.insert(ProKey(mapInits[i].oldname), ProKey(mapInits[i].newname));
++ }
++
++ const ProKey &QMakeEvaluator::map(const ProKey &var)
++ {
++ QHash<ProKey, ProKey>::ConstIterator it = statics.varMap.constFind(var);
++ if (it == statics.varMap.constEnd())
++ return var;
++ deprecationWarning(fL1S("Variable %1 is deprecated; use %2 instead.")
++ .arg(var.toQString(), it.value().toQString()));
++ return it.value();
++ }
++
++
++ QMakeEvaluator::QMakeEvaluator(QMakeGlobals *option, QMakeParser *parser, QMakeVfs *vfs,
++ QMakeHandler *handler)
++ :
++ #ifdef PROEVALUATOR_DEBUG
++ m_debugLevel(option->debugLevel),
++ #endif
++ m_option(option), m_parser(parser), m_handler(handler), m_vfs(vfs)
++ {
++ // So that single-threaded apps don't have to call initialize() for now.
++ initStatics();
++
++ // Configuration, more or less
++ m_caller = nullptr;
++ #ifdef PROEVALUATOR_CUMULATIVE
++ m_cumulative = false;
++ #endif
++ m_hostBuild = false;
++
++ // Evaluator state
++ #ifdef PROEVALUATOR_CUMULATIVE
++ m_skipLevel = 0;
++ #endif
++ m_listCount = 0;
++ m_toggle = 0;
++ m_valuemapStack.push(ProValueMap());
++ m_valuemapInited = false;
++ }
++
++ QMakeEvaluator::~QMakeEvaluator()
++ {
++ }
++
++ void QMakeEvaluator::initFrom(const QMakeEvaluator *other)
++ {
++ Q_ASSERT_X(other, "QMakeEvaluator::visitProFile", "Project not prepared");
++ m_functionDefs = other->m_functionDefs;
++ m_valuemapStack = other->m_valuemapStack;
++ m_valuemapInited = true;
++ m_qmakespec = other->m_qmakespec;
++ m_qmakespecName = other->m_qmakespecName;
++ m_mkspecPaths = other->m_mkspecPaths;
++ m_featureRoots = other->m_featureRoots;
++ m_dirSep = other->m_dirSep;
++ }
++
++ //////// Evaluator tools /////////
++
++ uint QMakeEvaluator::getBlockLen(const ushort *&tokPtr)
++ {
++ uint len = *tokPtr++;
++ len |= (uint)*tokPtr++ << 16;
++ return len;
++ }
++
++ void QMakeEvaluator::skipStr(const ushort *&tokPtr)
++ {
++ uint len = *tokPtr++;
++ tokPtr += len;
++ }
++
++ void QMakeEvaluator::skipHashStr(const ushort *&tokPtr)
++ {
++ tokPtr += 2;
++ uint len = *tokPtr++;
++ tokPtr += len;
++ }
++
++ // FIXME: this should not build new strings for direct sections.
++ // Note that the E_SPRINTF and E_LIST implementations rely on the deep copy.
++ ProStringList QMakeEvaluator::split_value_list(QStringView vals, int source)
++ {
++ QString build;
++ ProStringList ret;
++
++ if (!source)
++ source = currentFileId();
++
++ const QChar *vals_data = vals.data();
++ const int vals_len = vals.size();
++ char16_t quote = 0;
++ bool hadWord = false;
++ for (int x = 0; x < vals_len; x++) {
++ char16_t unicode = vals_data[x].unicode();
++ if (unicode == quote) {
++ quote = 0;
++ hadWord = true;
++ build += QChar(unicode);
++ continue;
++ }
++ switch (unicode) {
++ case '"':
++ case '\'':
++ if (!quote)
++ quote = unicode;
++ // FIXME: this is inconsistent with the "there are no empty strings" dogma.
++ hadWord = true;
++ break;
++ case ' ':
++ case '\t':
++ if (!quote) {
++ if (hadWord) {
++ ret << ProString(build).setSource(source);
++ build.clear();
++ hadWord = false;
++ }
++ continue;
++ }
++ break;
++ case '\\':
++ if (x + 1 != vals_len) {
++ char16_t next = vals_data[++x].unicode();
++ if (next == '\'' || next == '"' || next == '\\') {
++ build += QChar(unicode);
++ unicode = next;
++ } else {
++ --x;
++ }
++ }
++ Q_FALLTHROUGH();
++ default:
++ hadWord = true;
++ break;
++ }
++ build += QChar(unicode);
++ }
++ if (hadWord)
++ ret << ProString(build).setSource(source);
++ return ret;
++ }
++
++ static void replaceInList(ProStringList *varlist,
++ const QRegularExpression &regexp, const QString &replace, bool global, QString &tmp)
++ {
++ for (ProStringList::Iterator varit = varlist->begin(); varit != varlist->end(); ) {
++ ProStringRoUser u1(*varit, tmp);
++ QString val = u1.str();
++ QString copy = val; // Force detach and have a reference value
++ val.replace(regexp, replace);
++ if (!val.isSharedWith(copy) && val != copy) {
++ if (val.isEmpty()) {
++ varit = varlist->erase(varit);
++ } else {
++ (*varit).setValue(val);
++ ++varit;
++ }
++ if (!global)
++ break;
++ } else {
++ ++varit;
++ }
++ }
++ }
++
++ //////// Evaluator /////////
++
++ static ALWAYS_INLINE void addStr(
++ const ProString &str, ProStringList *ret, bool &pending, bool joined)
++ {
++ if (joined) {
++ ret->last().append(str, &pending);
++ } else {
++ if (!pending) {
++ pending = true;
++ *ret << str;
++ } else {
++ ret->last().append(str);
++ }
++ }
++ }
++
++ static ALWAYS_INLINE void addStrList(
++ const ProStringList &list, ushort tok, ProStringList *ret, bool &pending, bool joined)
++ {
++ if (!list.isEmpty()) {
++ if (joined) {
++ ret->last().append(list, &pending, !(tok & TokQuoted));
++ } else {
++ if (tok & TokQuoted) {
++ if (!pending) {
++ pending = true;
++ *ret << ProString();
++ }
++ ret->last().append(list);
++ } else {
++ if (!pending) {
++ // Another qmake bizzarity: if nothing is pending and the
++ // first element is empty, it will be eaten
++ if (!list.at(0).isEmpty()) {
++ // The common case
++ pending = true;
++ *ret += list;
++ return;
++ }
++ } else {
++ ret->last().append(list.at(0));
++ }
++ // This is somewhat slow, but a corner case
++ for (int j = 1; j < list.size(); ++j) {
++ pending = true;
++ *ret << list.at(j);
++ }
++ }
++ }
++ }
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateExpression(
++ const ushort *&tokPtr, ProStringList *ret, bool joined)
++ {
++ debugMsg(2, joined ? "evaluating joined expression" : "evaluating expression");
++ ProFile *pro = m_current.pro;
++ if (joined)
++ *ret << ProString();
++ bool pending = false;
++ forever {
++ ushort tok = *tokPtr++;
++ if (tok & TokNewStr) {
++ debugMsg(2, "new string");
++ pending = false;
++ }
++ ushort maskedTok = tok & TokMask;
++ switch (maskedTok) {
++ case TokLine:
++ m_current.line = *tokPtr++;
++ break;
++ case TokLiteral: {
++ const ProString &val = pro->getStr(tokPtr);
++ debugMsg(2, "literal %s", dbgStr(val));
++ addStr(val, ret, pending, joined);
++ break; }
++ case TokHashLiteral: {
++ const ProKey &val = pro->getHashStr(tokPtr);
++ debugMsg(2, "hashed literal %s", dbgStr(val.toString()));
++ addStr(val, ret, pending, joined);
++ break; }
++ case TokVariable: {
++ const ProKey &var = pro->getHashStr(tokPtr);
++ const ProStringList &val = values(map(var));
++ debugMsg(2, "variable %s => %s", dbgKey(var), dbgStrList(val));
++ addStrList(val, tok, ret, pending, joined);
++ break; }
++ case TokProperty: {
++ const ProKey &var = pro->getHashStr(tokPtr);
++ const ProString &val = propertyValue(var);
++ debugMsg(2, "property %s => %s", dbgKey(var), dbgStr(val));
++ addStr(val, ret, pending, joined);
++ break; }
++ case TokEnvVar: {
++ const ProString &var = pro->getStr(tokPtr);
++ const ProString &val = ProString(m_option->getEnv(var.toQString()));
++ debugMsg(2, "env var %s => %s", dbgStr(var), dbgStr(val));
++ addStr(val, ret, pending, joined);
++ break; }
++ case TokFuncName: {
++ const ProKey &func = pro->getHashStr(tokPtr);
++ debugMsg(2, "function %s", dbgKey(func));
++ ProStringList val;
++ if (evaluateExpandFunction(func, tokPtr, &val) == ReturnError)
++ return ReturnError;
++ addStrList(val, tok, ret, pending, joined);
++ break; }
++ default:
++ debugMsg(2, "evaluated expression => %s", dbgStrList(*ret));
++ tokPtr--;
++ return ReturnTrue;
++ }
++ }
++ }
++
++ void QMakeEvaluator::skipExpression(const ushort *&pTokPtr)
++ {
++ const ushort *tokPtr = pTokPtr;
++ forever {
++ ushort tok = *tokPtr++;
++ switch (tok) {
++ case TokLine:
++ m_current.line = *tokPtr++;
++ break;
++ case TokValueTerminator:
++ case TokFuncTerminator:
++ pTokPtr = tokPtr;
++ return;
++ case TokArgSeparator:
++ break;
++ default:
++ switch (tok & TokMask) {
++ case TokLiteral:
++ case TokEnvVar:
++ skipStr(tokPtr);
++ break;
++ case TokHashLiteral:
++ case TokVariable:
++ case TokProperty:
++ skipHashStr(tokPtr);
++ break;
++ case TokFuncName:
++ skipHashStr(tokPtr);
++ pTokPtr = tokPtr;
++ skipExpression(pTokPtr);
++ tokPtr = pTokPtr;
++ break;
++ default:
++ Q_ASSERT_X(false, "skipExpression", "Unrecognized token");
++ break;
++ }
++ }
++ }
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(
++ ProFile *pro, const ushort *tokPtr)
++ {
++ m_current.pro = pro;
++ m_current.line = 0;
++ return visitProBlock(tokPtr);
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(
++ const ushort *tokPtr)
++ {
++ traceMsg("entering block");
++ ProStringList curr;
++ ProFile *pro = m_current.pro;
++ bool okey = true, or_op = false, invert = false;
++ uint blockLen;
++ while (ushort tok = *tokPtr++) {
++ VisitReturn ret;
++ switch (tok) {
++ case TokLine:
++ m_current.line = *tokPtr++;
++ continue;
++ case TokAssign:
++ case TokAppend:
++ case TokAppendUnique:
++ case TokRemove:
++ case TokReplace:
++ ret = visitProVariable(tok, curr, tokPtr);
++ if (ret == ReturnError)
++ break;
++ curr.clear();
++ continue;
++ case TokBranch:
++ blockLen = getBlockLen(tokPtr);
++ if (m_cumulative) {
++ #ifdef PROEVALUATOR_CUMULATIVE
++ if (!okey)
++ m_skipLevel++;
++ ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue;
++ tokPtr += blockLen;
++ blockLen = getBlockLen(tokPtr);
++ if (!okey)
++ m_skipLevel--;
++ else
++ m_skipLevel++;
++ if ((ret == ReturnTrue || ret == ReturnFalse) && blockLen)
++ ret = visitProBlock(tokPtr);
++ if (okey)
++ m_skipLevel--;
++ #endif
++ } else {
++ if (okey) {
++ traceMsg("taking 'then' branch");
++ ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue;
++ traceMsg("finished 'then' branch");
++ }
++ tokPtr += blockLen;
++ blockLen = getBlockLen(tokPtr);
++ if (!okey) {
++ traceMsg("taking 'else' branch");
++ ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue;
++ traceMsg("finished 'else' branch");
++ }
++ }
++ tokPtr += blockLen;
++ okey = true, or_op = false; // force next evaluation
++ break;
++ case TokForLoop:
++ if (m_cumulative || okey != or_op) {
++ const ProKey &variable = pro->getHashStr(tokPtr);
++ uint exprLen = getBlockLen(tokPtr);
++ const ushort *exprPtr = tokPtr;
++ tokPtr += exprLen;
++ blockLen = getBlockLen(tokPtr);
++ ret = visitProLoop(variable, exprPtr, tokPtr);
++ } else {
++ skipHashStr(tokPtr);
++ uint exprLen = getBlockLen(tokPtr);
++ tokPtr += exprLen;
++ blockLen = getBlockLen(tokPtr);
++ traceMsg("skipped loop");
++ ret = ReturnTrue;
++ }
++ tokPtr += blockLen;
++ okey = true, or_op = false; // force next evaluation
++ break;
++ case TokBypassNesting:
++ blockLen = getBlockLen(tokPtr);
++ if ((m_cumulative || okey != or_op) && blockLen) {
++ ProValueMapStack savedValuemapStack = std::move(m_valuemapStack);
++ m_valuemapStack.clear();
++ m_valuemapStack.splice(m_valuemapStack.end(),
++ savedValuemapStack, savedValuemapStack.begin());
++ traceMsg("visiting nesting-bypassing block");
++ ret = visitProBlock(tokPtr);
++ traceMsg("visited nesting-bypassing block");
++ savedValuemapStack.splice(savedValuemapStack.begin(),
++ m_valuemapStack, m_valuemapStack.begin());
++ m_valuemapStack = std::move(savedValuemapStack);
++ } else {
++ traceMsg("skipped nesting-bypassing block");
++ ret = ReturnTrue;
++ }
++ tokPtr += blockLen;
++ okey = true, or_op = false; // force next evaluation
++ break;
++ case TokTestDef:
++ case TokReplaceDef:
++ if (m_cumulative || okey != or_op) {
++ const ProKey &name = pro->getHashStr(tokPtr);
++ blockLen = getBlockLen(tokPtr);
++ visitProFunctionDef(tok, name, tokPtr);
++ traceMsg("defined %s function %s",
++ tok == TokTestDef ? "test" : "replace", dbgKey(name));
++ } else {
++ traceMsg("skipped function definition");
++ skipHashStr(tokPtr);
++ blockLen = getBlockLen(tokPtr);
++ }
++ tokPtr += blockLen;
++ okey = true, or_op = false; // force next evaluation
++ continue;
++ case TokNot:
++ traceMsg("NOT");
++ invert ^= true;
++ continue;
++ case TokAnd:
++ traceMsg("AND");
++ or_op = false;
++ continue;
++ case TokOr:
++ traceMsg("OR");
++ or_op = true;
++ continue;
++ case TokCondition:
++ if (!m_skipLevel && okey != or_op) {
++ if (curr.size() != 1) {
++ if (!m_cumulative || !curr.isEmpty())
++ evalError(fL1S("Conditional must expand to exactly one word."));
++ okey = false;
++ } else {
++ okey = isActiveConfig(curr.at(0).toQStringView(), true);
++ traceMsg("condition %s is %s", dbgStr(curr.at(0)), dbgBool(okey));
++ okey ^= invert;
++ }
++ } else {
++ traceMsg("skipped condition %s", curr.size() == 1 ? dbgStr(curr.at(0)) : "<invalid>");
++ }
++ or_op = !okey; // tentatively force next evaluation
++ invert = false;
++ curr.clear();
++ continue;
++ case TokTestCall:
++ if (!m_skipLevel && okey != or_op) {
++ if (curr.size() != 1) {
++ if (!m_cumulative || !curr.isEmpty())
++ evalError(fL1S("Test name must expand to exactly one word."));
++ skipExpression(tokPtr);
++ okey = false;
++ } else {
++ traceMsg("evaluating test function %s", dbgStr(curr.at(0)));
++ ret = evaluateConditionalFunction(curr.at(0).toKey(), tokPtr);
++ switch (ret) {
++ case ReturnTrue: okey = true; break;
++ case ReturnFalse: okey = false; break;
++ default:
++ traceMsg("aborting block, function status: %s", dbgReturn(ret));
++ return ret;
++ }
++ traceMsg("test function returned %s", dbgBool(okey));
++ okey ^= invert;
++ }
++ } else if (m_cumulative) {
++ #ifdef PROEVALUATOR_CUMULATIVE
++ m_skipLevel++;
++ if (curr.size() != 1)
++ skipExpression(tokPtr);
++ else
++ evaluateConditionalFunction(curr.at(0).toKey(), tokPtr);
++ m_skipLevel--;
++ #endif
++ } else {
++ skipExpression(tokPtr);
++ traceMsg("skipped test function %s", curr.size() == 1 ? dbgStr(curr.at(0)) : "<invalid>");
++ }
++ or_op = !okey; // tentatively force next evaluation
++ invert = false;
++ curr.clear();
++ continue;
++ case TokReturn:
++ m_returnValue = curr;
++ curr.clear();
++ ret = ReturnReturn;
++ goto ctrlstm;
++ case TokBreak:
++ ret = ReturnBreak;
++ goto ctrlstm;
++ case TokNext:
++ ret = ReturnNext;
++ ctrlstm:
++ if (!m_skipLevel && okey != or_op) {
++ traceMsg("flow control statement '%s', aborting block", dbgReturn(ret));
++ return ret;
++ }
++ traceMsg("skipped flow control statement '%s'", dbgReturn(ret));
++ okey = false, or_op = true; // force next evaluation
++ continue;
++ default: {
++ const ushort *oTokPtr = --tokPtr;
++ ret = evaluateExpression(tokPtr, &curr, false);
++ if (ret == ReturnError || tokPtr != oTokPtr)
++ break;
++ }
++ Q_ASSERT_X(false, "visitProBlock", "unexpected item type");
++ continue;
++ }
++ if (ret != ReturnTrue && ret != ReturnFalse) {
++ traceMsg("aborting block, status: %s", dbgReturn(ret));
++ return ret;
++ }
++ }
++ traceMsg("leaving block, okey=%s", dbgBool(okey));
++ return returnBool(okey);
++ }
++
++
++ void QMakeEvaluator::visitProFunctionDef(
++ ushort tok, const ProKey &name, const ushort *tokPtr)
++ {
++ QHash<ProKey, ProFunctionDef> *hash =
++ (tok == TokTestDef
++ ? &m_functionDefs.testFunctions
++ : &m_functionDefs.replaceFunctions);
++ hash->insert(name, ProFunctionDef(m_current.pro, tokPtr - m_current.pro->tokPtr()));
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProLoop(
++ const ProKey &_variable, const ushort *exprPtr, const ushort *tokPtr)
++ {
++ VisitReturn ret = ReturnTrue;
++ bool infinite = false;
++ int index = 0;
++ ProKey variable;
++ ProStringList oldVarVal;
++ ProStringList it_list_out;
++ if (expandVariableReferences(exprPtr, 0, &it_list_out, true) == ReturnError)
++ return ReturnError;
++ ProString it_list = it_list_out.at(0);
++ if (_variable.isEmpty()) {
++ if (it_list != statics.strever) {
++ evalError(fL1S("Invalid loop expression."));
++ return ReturnFalse;
++ }
++ it_list = ProString(statics.strforever);
++ } else {
++ variable = map(_variable);
++ oldVarVal = values(variable);
++ }
++ ProStringList list = values(it_list.toKey());
++ if (list.isEmpty()) {
++ if (it_list == statics.strforever) {
++ if (m_cumulative) {
++ // The termination conditions wouldn't be evaluated, so we must skip it.
++ traceMsg("skipping forever loop in cumulative mode");
++ return ReturnFalse;
++ }
++ infinite = true;
++ } else {
++ QStringView itl = it_list.toQStringView();
++ int dotdot = itl.indexOf(statics.strDotDot);
++ if (dotdot != -1) {
++ bool ok;
++ int start = itl.left(dotdot).toInt(&ok);
++ if (ok) {
++ int end = itl.mid(dotdot+2).toInt(&ok);
++ if (ok) {
++ const int absDiff = qAbs(end - start);
++ if (m_cumulative && absDiff > 100) {
++ // Such a loop is unlikely to contribute something useful to the
++ // file collection, and may cause considerable delay.
++ traceMsg("skipping excessive loop in cumulative mode");
++ return ReturnFalse;
++ }
++ list.reserve(absDiff + 1);
++ if (start < end) {
++ for (int i = start; i <= end; i++)
++ list << ProString(QString::number(i));
++ } else {
++ for (int i = start; i >= end; i--)
++ list << ProString(QString::number(i));
++ }
++ }
++ }
++ }
++ }
++ }
++
++ if (infinite)
++ traceMsg("entering infinite loop for %s", dbgKey(variable));
++ else
++ traceMsg("entering loop for %s over %s", dbgKey(variable), dbgStrList(list));
++
++ forever {
++ if (infinite) {
++ if (!variable.isEmpty())
++ m_valuemapStack.top()[variable] = ProStringList(ProString(QString::number(index)));
++ if (++index > 1000) {
++ evalError(fL1S("Ran into infinite loop (> 1000 iterations)."));
++ break;
++ }
++ traceMsg("loop iteration %d", index);
++ } else {
++ ProString val;
++ do {
++ if (index >= list.size())
++ goto do_break;
++ val = list.at(index++);
++ } while (val.isEmpty()); // stupid, but qmake is like that
++ traceMsg("loop iteration %s", dbgStr(val));
++ m_valuemapStack.top()[variable] = ProStringList(val);
++ }
++
++ ret = visitProBlock(tokPtr);
++ switch (ret) {
++ case ReturnTrue:
++ case ReturnFalse:
++ break;
++ case ReturnNext:
++ ret = ReturnTrue;
++ break;
++ case ReturnBreak:
++ ret = ReturnTrue;
++ goto do_break;
++ default:
++ goto do_break;
++ }
++ }
++ do_break:
++
++ traceMsg("done looping");
++
++ if (!variable.isEmpty())
++ m_valuemapStack.top()[variable] = oldVarVal;
++ return ret;
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProVariable(
++ ushort tok, const ProStringList &curr, const ushort *&tokPtr)
++ {
++ int sizeHint = *tokPtr++;
++
++ if (curr.size() != 1) {
++ skipExpression(tokPtr);
++ if (!m_cumulative || !curr.isEmpty())
++ evalError(fL1S("Left hand side of assignment must expand to exactly one word."));
++ return ReturnTrue;
++ }
++ const ProKey &varName = map(curr.first());
++
++ if (tok == TokReplace) { // ~=
++ // DEFINES ~= s/a/b/?[gqi]
++
++ ProStringList varVal;
++ if (expandVariableReferences(tokPtr, sizeHint, &varVal, true) == ReturnError)
++ return ReturnError;
++ QStringView val = varVal.at(0).toQStringView();
++ if (val.size() < 4 || val.at(0) != QLatin1Char('s')) {
++ evalError(fL1S("The ~= operator can handle only the s/// function."));
++ return ReturnTrue;
++ }
++ QChar sep = val.at(1);
++ auto func = val.split(sep, Qt::KeepEmptyParts);
++ if (func.size() < 3 || func.size() > 4) {
++ evalError(fL1S("The s/// function expects 3 or 4 arguments."));
++ return ReturnTrue;
++ }
++
++ bool global = false, quote = false, case_sense = false;
++ if (func.size() == 4) {
++ global = func[3].indexOf(QLatin1Char('g')) != -1;
++ case_sense = func[3].indexOf(QLatin1Char('i')) == -1;
++ quote = func[3].indexOf(QLatin1Char('q')) != -1;
++ }
++ QString pattern = func[1].toString();
++ QString replace = func[2].toString();
++ if (quote)
++ pattern = QRegularExpression::escape(pattern);
++
++ QRegularExpression regexp(pattern, case_sense ? QRegularExpression::NoPatternOption :
++ QRegularExpression::CaseInsensitiveOption);
++
++ // We could make a union of modified and unmodified values,
++ // but this will break just as much as it fixes, so leave it as is.
++ replaceInList(&valuesRef(varName), regexp, replace, global, m_tmp2);
++ debugMsg(2, "replaced %s with %s", dbgQStr(pattern), dbgQStr(replace));
++ } else {
++ ProStringList varVal;
++ if (expandVariableReferences(tokPtr, sizeHint, &varVal, false) == ReturnError)
++ return ReturnError;
++ switch (tok) {
++ default: // whatever - cannot happen
++ case TokAssign: // =
++ varVal.removeEmpty();
++ // FIXME: add check+warning about accidental value removal.
++ // This may be a bit too noisy, though.
++ m_valuemapStack.top()[varName] = varVal;
++ debugMsg(2, "assigning");
++ break;
++ case TokAppendUnique: // *=
++ valuesRef(varName).insertUnique(varVal);
++ debugMsg(2, "appending unique");
++ break;
++ case TokAppend: // +=
++ varVal.removeEmpty();
++ valuesRef(varName) += varVal;
++ debugMsg(2, "appending");
++ break;
++ case TokRemove: // -=
++ if (!m_cumulative) {
++ valuesRef(varName).removeEach(varVal);
++ } else {
++ // We are stingy with our values.
++ }
++ debugMsg(2, "removing");
++ break;
++ }
++ }
++ traceMsg("%s := %s", dbgKey(varName), dbgStrList(values(varName)));
++
++ if (varName == statics.strTEMPLATE)
++ setTemplate();
++ else if (varName == statics.strQMAKE_PLATFORM)
++ m_featureRoots = nullptr;
++ else if (varName == statics.strQMAKE_DIR_SEP)
++ m_dirSep = first(varName);
++ else if (varName == statics.strQMAKESPEC) {
++ if (!values(varName).isEmpty()) {
++ QString spec = values(varName).first().toQString();
++ if (IoUtils::isAbsolutePath(spec)) {
++ m_qmakespec = spec;
++ m_qmakespecName = IoUtils::fileName(m_qmakespec).toString();
++ m_featureRoots = nullptr;
++ }
++ }
++ }
++ #ifdef PROEVALUATOR_FULL
++ else if (varName == statics.strREQUIRES)
++ return checkRequirements(values(varName));
++ #endif
++
++ return ReturnTrue;
++ }
++
++ void QMakeEvaluator::setTemplate()
++ {
++ ProStringList &values = valuesRef(statics.strTEMPLATE);
++ if (!m_option->user_template.isEmpty()) {
++ // Don't allow override
++ values = ProStringList(ProString(m_option->user_template));
++ } else {
++ if (values.isEmpty())
++ values.append(ProString("app"));
++ else
++ values.erase(values.begin() + 1, values.end());
++ }
++ if (!m_option->user_template_prefix.isEmpty()) {
++ ProString val = values.first();
++ if (!val.startsWith(m_option->user_template_prefix))
++ values = ProStringList(ProString(m_option->user_template_prefix + val));
++ }
++ }
++
++ #if defined(Q_CC_MSVC)
++ static ProString msvcBinDirToQMakeArch(QString subdir)
++ {
++ int idx = subdir.indexOf(QLatin1Char('\\'));
++ if (idx == -1)
++ return ProString("x86");
++ subdir.remove(0, idx + 1);
++ idx = subdir.indexOf(QLatin1Char('_'));
++ if (idx >= 0)
++ subdir.remove(0, idx + 1);
++ subdir = subdir.toLower();
++ if (subdir == QLatin1String("amd64"))
++ return ProString("x86_64");
++ // Since 2017 the folder structure from here is HostX64|X86/x64|x86
++ idx = subdir.indexOf(QLatin1Char('\\'));
++ if (idx == -1)
++ return ProString("x86");
++ subdir.remove(0, idx + 1);
++ if (subdir == QLatin1String("x64"))
++ return ProString("x86_64");
++ return ProString(subdir);
++ }
++
++ static ProString defaultMsvcArchitecture()
++ {
++ #if defined(Q_OS_WIN64)
++ return ProString("x86_64");
++ #else
++ return ProString("x86");
++ #endif
++ }
++
++ static ProString msvcArchitecture(const QString &vcInstallDir, const QString &pathVar)
++ {
++ if (vcInstallDir.isEmpty())
++ return defaultMsvcArchitecture();
++ QString vcBinDir = vcInstallDir;
++ if (vcBinDir.endsWith(QLatin1Char('\\')))
++ vcBinDir.chop(1);
++ const auto dirs = pathVar.split(QLatin1Char(';'), Qt::SkipEmptyParts);
++ for (const QString &dir : dirs) {
++ if (!dir.startsWith(vcBinDir, Qt::CaseInsensitive))
++ continue;
++ const ProString arch = msvcBinDirToQMakeArch(dir.mid(vcBinDir.length() + 1));
++ if (!arch.isEmpty())
++ return arch;
++ }
++ return defaultMsvcArchitecture();
++ }
++ #endif // defined(Q_CC_MSVC)
++
++ void QMakeEvaluator::loadDefaults()
++ {
++ ProValueMap &vars = m_valuemapStack.top();
++
++ vars[ProKey("DIR_SEPARATOR")] << ProString(m_option->dir_sep);
++ vars[ProKey("DIRLIST_SEPARATOR")] << ProString(m_option->dirlist_sep);
++ vars[ProKey("_DATE_")] << ProString(QDateTime::currentDateTime().toString());
++ if (!m_option->qmake_abslocation.isEmpty())
++ vars[ProKey("QMAKE_QMAKE")] << ProString(m_option->qmake_abslocation);
++ if (!m_option->qmake_args.isEmpty())
++ vars[ProKey("QMAKE_ARGS")] = ProStringList(m_option->qmake_args);
++ if (!m_option->qtconf.isEmpty())
++ vars[ProKey("QMAKE_QTCONF")] = ProString(m_option->qtconf);
++ vars[ProKey("QMAKE_HOST.cpu_count")] = ProString(QString::number(idealThreadCount()));
++ #if defined(Q_OS_WIN32)
++ vars[ProKey("QMAKE_HOST.os")] << ProString("Windows");
++
++ DWORD name_length = 1024;
++ wchar_t name[1024];
++ if (GetComputerName(name, &name_length))
++ vars[ProKey("QMAKE_HOST.name")] << ProString(QString::fromWCharArray(name));
++
++ vars[ProKey("QMAKE_HOST.version")] << ProString(QSysInfo::kernelVersion());
++ vars[ProKey("QMAKE_HOST.version_string")] << ProString(QSysInfo::productVersion());
++
++ SYSTEM_INFO info;
++ GetSystemInfo(&info);
++ ProString archStr;
++ switch (info.wProcessorArchitecture) {
++ # ifdef PROCESSOR_ARCHITECTURE_AMD64
++ case PROCESSOR_ARCHITECTURE_AMD64:
++ archStr = ProString("x86_64");
++ break;
++ # endif
++ case PROCESSOR_ARCHITECTURE_INTEL:
++ archStr = ProString("x86");
++ break;
++ case PROCESSOR_ARCHITECTURE_IA64:
++ # ifdef PROCESSOR_ARCHITECTURE_IA32_ON_WIN64
++ case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64:
++ # endif
++ archStr = ProString("IA64");
++ break;
++ default:
++ archStr = ProString("Unknown");
++ break;
++ }
++ vars[ProKey("QMAKE_HOST.arch")] << archStr;
++
++ # if defined(Q_CC_MSVC) // ### bogus condition, but nobody x-builds for msvc with a different qmake
++ // Since VS 2017 we need VCToolsInstallDir instead of VCINSTALLDIR
++ QString vcInstallDir = m_option->getEnv(QLatin1String("VCToolsInstallDir"));
++ if (vcInstallDir.isEmpty())
++ vcInstallDir = m_option->getEnv(QLatin1String("VCINSTALLDIR"));
++ vars[ProKey("QMAKE_TARGET.arch")] = msvcArchitecture(
++ vcInstallDir,
++ m_option->getEnv(QLatin1String("PATH")));
++ # endif
++ #elif defined(Q_OS_UNIX)
++ struct utsname name;
++ if (uname(&name) != -1) {
++ vars[ProKey("QMAKE_HOST.os")] << ProString(name.sysname);
++ vars[ProKey("QMAKE_HOST.name")] << ProString(QString::fromLocal8Bit(name.nodename));
++ vars[ProKey("QMAKE_HOST.version")] << ProString(name.release);
++ vars[ProKey("QMAKE_HOST.version_string")] << ProString(name.version);
++ vars[ProKey("QMAKE_HOST.arch")] << ProString(name.machine);
++ }
++ #endif
++
++ m_valuemapInited = true;
++ }
++
++ bool QMakeEvaluator::prepareProject(const QString &inDir)
++ {
++ QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
++ QString superdir;
++ if (m_option->do_cache) {
++ QString conffile;
++ QString cachefile = m_option->cachefile;
++ if (cachefile.isEmpty()) { //find it as it has not been specified
++ if (m_outputDir.isEmpty())
++ goto no_cache;
++ superdir = m_outputDir;
++ forever {
++ QString superfile = superdir + QLatin1String("/.qmake.super");
++ if (m_vfs->exists(superfile, flags)) {
++ m_superfile = QDir::cleanPath(superfile);
++ break;
++ }
++ QFileInfo qdfi(superdir);
++ if (qdfi.isRoot()) {
++ superdir.clear();
++ break;
++ }
++ superdir = qdfi.path();
++ }
++ QString sdir = inDir;
++ QString dir = m_outputDir;
++ forever {
++ conffile = sdir + QLatin1String("/.qmake.conf");
++ if (!m_vfs->exists(conffile, flags))
++ conffile.clear();
++ cachefile = dir + QLatin1String("/.qmake.cache");
++ if (!m_vfs->exists(cachefile, flags))
++ cachefile.clear();
++ if (!conffile.isEmpty() || !cachefile.isEmpty()) {
++ if (dir != sdir)
++ m_sourceRoot = sdir;
++ m_buildRoot = dir;
++ break;
++ }
++ if (dir == superdir)
++ goto no_cache;
++ QFileInfo qsdfi(sdir);
++ QFileInfo qdfi(dir);
++ if (qsdfi.isRoot() || qdfi.isRoot())
++ goto no_cache;
++ sdir = qsdfi.path();
++ dir = qdfi.path();
++ }
++ } else {
++ m_buildRoot = QFileInfo(cachefile).path();
++ }
++ m_conffile = QDir::cleanPath(conffile);
++ m_cachefile = QDir::cleanPath(cachefile);
++ }
++ no_cache:
++
++ QString dir = m_outputDir;
++ forever {
++ QString stashfile = dir + QLatin1String("/.qmake.stash");
++ if (dir == (!superdir.isEmpty() ? superdir : m_buildRoot) || m_vfs->exists(stashfile, flags)) {
++ m_stashfile = QDir::cleanPath(stashfile);
++ break;
++ }
++ QFileInfo qdfi(dir);
++ if (qdfi.isRoot())
++ break;
++ dir = qdfi.path();
++ }
++
++ return true;
++ }
++
++ bool QMakeEvaluator::loadSpecInternal()
++ {
++ if (evaluateFeatureFile(QLatin1String("spec_pre.prf")) != ReturnTrue)
++ return false;
++ QString spec = m_qmakespec + QLatin1String("/qmake.conf");
++ if (evaluateFile(spec, QMakeHandler::EvalConfigFile, LoadProOnly) != ReturnTrue) {
++ evalError(fL1S("Could not read qmake configuration file %1.").arg(spec));
++ return false;
++ }
++ #ifndef QT_BUILD_QMAKE
++ // Legacy support for Qt4 default specs
++ # ifdef Q_OS_UNIX
++ if (m_qmakespec.endsWith(QLatin1String("/default-host"))
++ || m_qmakespec.endsWith(QLatin1String("/default"))) {
++ QString rspec = QFileInfo(m_qmakespec).symLinkTarget();
++ if (!rspec.isEmpty())
++ m_qmakespec = QDir::cleanPath(QDir(m_qmakespec).absoluteFilePath(rspec));
++ }
++ # else
++ // We can't resolve symlinks as they do on Unix, so configure.exe puts
++ // the source of the qmake.conf at the end of the default/qmake.conf in
++ // the QMAKESPEC_ORIGINAL variable.
++ const ProString &orig_spec = first(ProKey("QMAKESPEC_ORIGINAL"));
++ if (!orig_spec.isEmpty()) {
++ QString spec = orig_spec.toQString();
++ if (IoUtils::isAbsolutePath(spec))
++ m_qmakespec = spec;
++ }
++ # endif
++ #endif
++ valuesRef(ProKey("QMAKESPEC")) = ProString(m_qmakespec);
++ m_qmakespecName = IoUtils::fileName(m_qmakespec).toString();
++ // This also ensures that m_featureRoots is valid.
++ if (evaluateFeatureFile(QLatin1String("spec_post.prf")) != ReturnTrue)
++ return false;
++ return true;
++ }
++
++ bool QMakeEvaluator::loadSpec()
++ {
++ QString qmakespec = m_option->expandEnvVars(
++ m_hostBuild ? m_option->qmakespec : m_option->xqmakespec);
++
++ {
++ QMakeEvaluator evaluator(m_option, m_parser, m_vfs, m_handler);
++ evaluator.m_sourceRoot = m_sourceRoot;
++ evaluator.m_buildRoot = m_buildRoot;
++
++ if (!m_superfile.isEmpty() && evaluator.evaluateFile(
++ m_superfile, QMakeHandler::EvalConfigFile, LoadProOnly|LoadHidden) != ReturnTrue) {
++ return false;
++ }
++ if (!m_conffile.isEmpty() && evaluator.evaluateFile(
++ m_conffile, QMakeHandler::EvalConfigFile, LoadProOnly|LoadHidden) != ReturnTrue) {
++ return false;
++ }
++ if (!m_cachefile.isEmpty() && evaluator.evaluateFile(
++ m_cachefile, QMakeHandler::EvalConfigFile, LoadProOnly|LoadHidden) != ReturnTrue) {
++ return false;
++ }
++ if (qmakespec.isEmpty()) {
++ if (!m_hostBuild)
++ qmakespec = evaluator.first(ProKey("XQMAKESPEC")).toQString();
++ if (qmakespec.isEmpty())
++ qmakespec = evaluator.first(ProKey("QMAKESPEC")).toQString();
++ }
++ m_qmakepath = evaluator.values(ProKey("QMAKEPATH")).toQStringList();
++ m_qmakefeatures = evaluator.values(ProKey("QMAKEFEATURES")).toQStringList();
++ }
++
++ updateMkspecPaths();
++ if (qmakespec.isEmpty())
++ qmakespec = propertyValue(ProKey(m_hostBuild ? "QMAKE_SPEC" : "QMAKE_XSPEC")).toQString();
++ #ifndef QT_BUILD_QMAKE
++ // Legacy support for Qt4 qmake in Qt Creator, etc.
++ if (qmakespec.isEmpty())
++ qmakespec = m_hostBuild ? QLatin1String("default-host") : QLatin1String("default");
++ #endif
++ if (IoUtils::isRelativePath(qmakespec)) {
++ for (const QString &root : std::as_const(m_mkspecPaths)) {
++ QString mkspec = root + QLatin1Char('/') + qmakespec;
++ if (IoUtils::exists(mkspec)) {
++ qmakespec = mkspec;
++ goto cool;
++ }
++ }
++ evalError(fL1S("Could not find qmake spec '%1'.").arg(qmakespec));
++ return false;
++ }
++ cool:
++ m_qmakespec = QDir::cleanPath(qmakespec);
++
++ if (!m_superfile.isEmpty()) {
++ valuesRef(ProKey("_QMAKE_SUPER_CACHE_")) << ProString(m_superfile);
++ if (evaluateFile(
++ m_superfile, QMakeHandler::EvalConfigFile, LoadProOnly|LoadHidden) != ReturnTrue)
++ return false;
++ }
++ if (!loadSpecInternal())
++ return false;
++ if (!m_conffile.isEmpty()) {
++ valuesRef(ProKey("_QMAKE_CONF_")) << ProString(m_conffile);
++ if (evaluateFile(
++ m_conffile, QMakeHandler::EvalConfigFile, LoadProOnly) != ReturnTrue)
++ return false;
++ }
++ if (!m_cachefile.isEmpty()) {
++ valuesRef(ProKey("_QMAKE_CACHE_")) << ProString(m_cachefile);
++ if (evaluateFile(
++ m_cachefile, QMakeHandler::EvalConfigFile, LoadProOnly) != ReturnTrue)
++ return false;
++ }
++ QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
++ if (!m_stashfile.isEmpty() && m_vfs->exists(m_stashfile, flags)) {
++ valuesRef(ProKey("_QMAKE_STASH_")) << ProString(m_stashfile);
++ if (evaluateFile(
++ m_stashfile, QMakeHandler::EvalConfigFile, LoadProOnly) != ReturnTrue)
++ return false;
++ }
++ return true;
++ }
++
++ void QMakeEvaluator::setupProject()
++ {
++ setTemplate();
++ ProValueMap &vars = m_valuemapStack.top();
++ int proFile = currentFileId();
++ vars[ProKey("TARGET")] << ProString(QFileInfo(currentFileName()).baseName()).setSource(proFile);
++ vars[ProKey("_PRO_FILE_")] << ProString(currentFileName()).setSource(proFile);
++ vars[ProKey("_PRO_FILE_PWD_")] << ProString(currentDirectory()).setSource(proFile);
++ vars[ProKey("OUT_PWD")] << ProString(m_outputDir).setSource(proFile);
++ }
++
++ void QMakeEvaluator::evaluateCommand(const QString &cmds, const QString &where)
++ {
++ if (!cmds.isEmpty()) {
++ ProFile *pro = m_parser->parsedProBlock(QStringView(cmds), 0, where, -1);
++ if (pro->isOk()) {
++ m_locationStack.push(m_current);
++ visitProBlock(pro, pro->tokPtr());
++ m_current = m_locationStack.pop();
++ }
++ pro->deref();
++ }
++ }
++
++ void QMakeEvaluator::applyExtraConfigs()
++ {
++ if (m_extraConfigs.isEmpty())
++ return;
++
++ evaluateCommand(fL1S("CONFIG += ") + m_extraConfigs.join(QLatin1Char(' ')), fL1S("(extra configs)"));
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConfigFeatures()
++ {
++ QSet<QString> processed;
++ forever {
++ bool finished = true;
++ ProStringList configs = values(statics.strCONFIG);
++ for (int i = configs.size() - 1; i >= 0; --i) {
++ ProStringRoUser u1(configs.at(i), m_tmp1);
++ QString config = u1.str().toLower();
++ if (!processed.contains(config)) {
++ config.detach();
++ processed.insert(config);
++ VisitReturn vr = evaluateFeatureFile(config, true);
++ if (vr == ReturnError && !m_cumulative)
++ return vr;
++ if (vr == ReturnTrue) {
++ finished = false;
++ break;
++ }
++ }
++ }
++ if (finished)
++ break;
++ }
++ return ReturnTrue;
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProFile(
++ ProFile *pro, QMakeHandler::EvalFileType type, LoadFlags flags)
++ {
++ if (!m_cumulative && !pro->isOk())
++ return ReturnFalse;
++
++ if (flags & LoadPreFiles) {
++ if (!prepareProject(pro->directoryName()))
++ return ReturnFalse;
++
++ m_hostBuild = pro->isHostBuild();
++
++ #ifdef PROEVALUATOR_THREAD_SAFE
++ m_option->mutex.lock();
++ #endif
++ QMakeBaseEnv **baseEnvPtr = &m_option->baseEnvs[QMakeBaseKey(m_buildRoot, m_stashfile, m_hostBuild)];
++ if (!*baseEnvPtr)
++ *baseEnvPtr = new QMakeBaseEnv;
++ QMakeBaseEnv *baseEnv = *baseEnvPtr;
++
++ #ifdef PROEVALUATOR_THREAD_SAFE
++ QMutexLocker locker(&baseEnv->mutex);
++ m_option->mutex.unlock();
++ if (baseEnv->inProgress) {
++ QThreadPool::globalInstance()->releaseThread();
++ baseEnv->cond.wait(&baseEnv->mutex);
++ QThreadPool::globalInstance()->reserveThread();
++ if (!baseEnv->isOk)
++ return ReturnFalse;
++ } else
++ #endif
++ if (!baseEnv->evaluator) {
++ #ifdef PROEVALUATOR_THREAD_SAFE
++ baseEnv->inProgress = true;
++ locker.unlock();
++ #endif
++
++ QMakeEvaluator *baseEval = new QMakeEvaluator(m_option, m_parser, m_vfs, m_handler);
++ baseEnv->evaluator = baseEval;
++ baseEval->m_superfile = m_superfile;
++ baseEval->m_conffile = m_conffile;
++ baseEval->m_cachefile = m_cachefile;
++ baseEval->m_stashfile = m_stashfile;
++ baseEval->m_sourceRoot = m_sourceRoot;
++ baseEval->m_buildRoot = m_buildRoot;
++ baseEval->m_hostBuild = m_hostBuild;
++ bool ok = baseEval->loadSpec();
++
++ #ifdef PROEVALUATOR_THREAD_SAFE
++ locker.relock();
++ baseEnv->isOk = ok;
++ baseEnv->inProgress = false;
++ baseEnv->cond.wakeAll();
++ #endif
++
++ if (!ok)
++ return ReturnFalse;
++ }
++ #ifdef PROEVALUATOR_THREAD_SAFE
++ else if (!baseEnv->isOk)
++ return ReturnFalse;
++ #endif
++
++ initFrom(baseEnv->evaluator);
++ } else {
++ if (!m_valuemapInited)
++ loadDefaults();
++ }
++
++ VisitReturn vr;
++
++ m_handler->aboutToEval(currentProFile(), pro, type);
++ m_profileStack.push(pro);
++ valuesRef(ProKey("PWD")) = ProStringList(ProString(currentDirectory()));
++ if (flags & LoadPreFiles) {
++ setupProject();
++
++ if (!m_option->extra_cmds[QMakeEvalEarly].isEmpty())
++ evaluateCommand(m_option->extra_cmds[QMakeEvalEarly], fL1S("(command line -early)"));
++
++ for (ProValueMap::ConstIterator it = m_extraVars.constBegin();
++ it != m_extraVars.constEnd(); ++it)
++ m_valuemapStack.front().insert(it.key(), it.value());
++
++ // In case default_pre needs to make decisions based on the current
++ // build pass configuration.
++ applyExtraConfigs();
++
++ if ((vr = evaluateFeatureFile(QLatin1String("default_pre.prf"))) == ReturnError)
++ goto failed;
++
++ if (!m_option->extra_cmds[QMakeEvalBefore].isEmpty()) {
++ evaluateCommand(m_option->extra_cmds[QMakeEvalBefore], fL1S("(command line)"));
++
++ // Again, after user configs, to override them
++ applyExtraConfigs();
++ }
++ }
++
++ debugMsg(1, "visiting file %s", qPrintable(pro->fileName()));
++ if ((vr = visitProBlock(pro, pro->tokPtr())) == ReturnError)
++ goto failed;
++ debugMsg(1, "done visiting file %s", qPrintable(pro->fileName()));
++
++ if (flags & LoadPostFiles) {
++ evaluateCommand(m_option->extra_cmds[QMakeEvalAfter], fL1S("(command line -after)"));
++
++ // Again, to ensure the project does not mess with us.
++ // Specifically, do not allow a project to override debug/release within a
++ // debug_and_release build pass - it's too late for that at this point anyway.
++ applyExtraConfigs();
++
++ if ((vr = evaluateFeatureFile(QLatin1String("default_post.prf"))) == ReturnError)
++ goto failed;
++
++ if (!m_option->extra_cmds[QMakeEvalLate].isEmpty())
++ evaluateCommand(m_option->extra_cmds[QMakeEvalLate], fL1S("(command line -late)"));
++
++ if ((vr = evaluateConfigFeatures()) == ReturnError)
++ goto failed;
++ }
++ vr = ReturnTrue;
++ failed:
++ m_profileStack.pop();
++ valuesRef(ProKey("PWD")) = ProStringList(ProString(currentDirectory()));
++ m_handler->doneWithEval(currentProFile());
++
++ return vr;
++ }
++
++
++ void QMakeEvaluator::updateMkspecPaths()
++ {
++ QStringList ret;
++ const QString concat = QLatin1String("/mkspecs");
++
++ const auto paths = m_option->getPathListEnv(QLatin1String("QMAKEPATH"));
++ for (const QString &it : paths)
++ ret << it + concat;
++
++ for (const QString &it : std::as_const(m_qmakepath))
++ ret << it + concat;
++
++ if (!m_buildRoot.isEmpty())
++ ret << m_buildRoot + concat;
++ if (!m_sourceRoot.isEmpty())
++ ret << m_sourceRoot + concat;
++
++ ret << m_option->propertyValue(ProKey("QT_HOST_DATA/get")) + concat;
++ ret << m_option->propertyValue(ProKey("QT_HOST_DATA/src")) + concat;
++
++ ret.removeDuplicates();
++ m_mkspecPaths = ret;
++ }
++
++ void QMakeEvaluator::updateFeaturePaths()
++ {
++ QString mkspecs_concat = QLatin1String("/mkspecs");
++ QString features_concat = QLatin1String("/features/");
++
++ QStringList feature_roots;
++
++ feature_roots += m_option->getPathListEnv(QLatin1String("QMAKEFEATURES"));
++ feature_roots += m_qmakefeatures;
++ feature_roots += m_option->splitPathList(
++ m_option->propertyValue(ProKey("QMAKEFEATURES")).toQString());
++
++ QStringList feature_bases;
++ if (!m_buildRoot.isEmpty()) {
++ feature_bases << m_buildRoot + mkspecs_concat;
++ feature_bases << m_buildRoot;
++ }
++ if (!m_sourceRoot.isEmpty()) {
++ feature_bases << m_sourceRoot + mkspecs_concat;
++ feature_bases << m_sourceRoot;
++ }
++
++ const auto items = m_option->getPathListEnv(QLatin1String("QMAKEPATH"));
++ for (const QString &item : items)
++ feature_bases << (item + mkspecs_concat);
++
++ for (const QString &item : std::as_const(m_qmakepath))
++ feature_bases << (item + mkspecs_concat);
++
++ if (!m_qmakespec.isEmpty()) {
++ // The spec is already platform-dependent, so no subdirs here.
++ feature_roots << (m_qmakespec + features_concat);
++
++ // Also check directly under the root directory of the mkspecs collection
++ QDir specdir(m_qmakespec);
++ while (!specdir.isRoot() && specdir.cdUp()) {
++ const QString specpath = specdir.path();
++ if (specpath.endsWith(mkspecs_concat)) {
++ if (IoUtils::exists(specpath + features_concat))
++ feature_bases << specpath;
++ break;
++ }
++ }
++ }
++
++ feature_bases << (m_option->propertyValue(ProKey("QT_HOST_DATA/get")) + mkspecs_concat);
++ feature_bases << (m_option->propertyValue(ProKey("QT_HOST_DATA/src")) + mkspecs_concat);
++
++ for (const QString &fb : std::as_const(feature_bases)) {
++ const auto sfxs = values(ProKey("QMAKE_PLATFORM"));
++ for (const ProString &sfx : sfxs)
++ feature_roots << (fb + features_concat + sfx + QLatin1Char('/'));
++ feature_roots << (fb + features_concat);
++ }
++
++ for (int i = 0; i < feature_roots.size(); ++i)
++ if (!feature_roots.at(i).endsWith(QLatin1Char('/')))
++ feature_roots[i].append(QLatin1Char('/'));
++
++ feature_roots.removeDuplicates();
++
++ QStringList ret;
++ for (const QString &root : std::as_const(feature_roots))
++ if (IoUtils::exists(root))
++ ret << root;
++ m_featureRoots = new QMakeFeatureRoots(ret);
++ }
++
++ ProString QMakeEvaluator::propertyValue(const ProKey &name) const
++ {
++ if (name == QLatin1String("QMAKE_MKSPECS"))
++ return ProString(m_mkspecPaths.join(m_option->dirlist_sep));
++ ProString ret = m_option->propertyValue(name);
++ // if (ret.isNull())
++ // evalError(fL1S("Querying unknown property %1").arg(name.toQStringView()));
++ return ret;
++ }
++
++ ProFile *QMakeEvaluator::currentProFile() const
++ {
++ if (m_profileStack.size() > 0)
++ return m_profileStack.top();
++ return nullptr;
++ }
++
++ int QMakeEvaluator::currentFileId() const
++ {
++ ProFile *pro = currentProFile();
++ if (pro)
++ return pro->id();
++ return 0;
++ }
++
++ QString QMakeEvaluator::currentFileName() const
++ {
++ ProFile *pro = currentProFile();
++ if (pro)
++ return pro->fileName();
++ return QString();
++ }
++
++ QString QMakeEvaluator::currentDirectory() const
++ {
++ ProFile *pro = currentProFile();
++ if (pro)
++ return pro->directoryName();
++ return QString();
++ }
++
++ bool QMakeEvaluator::isActiveConfig(QStringView config, bool regex)
++ {
++ // magic types for easy flipping
++ if (config == statics.strtrue)
++ return true;
++ if (config == statics.strfalse)
++ return false;
++
++ if (config == statics.strhost_build)
++ return m_hostBuild;
++
++ if (regex && (config.contains(QLatin1Char('*')) || config.contains(QLatin1Char('?')))) {
++ auto re = QRegularExpression::fromWildcard(config.toString());
++
++ // mkspecs
++ if (re.match(m_qmakespecName).hasMatch())
++ return true;
++
++ // CONFIG variable
++ const auto configValues = values(statics.strCONFIG);
++ for (const ProString &configValue : configValues) {
++ ProStringRoUser u1(configValue, m_tmp[m_toggle ^= 1]);
++ if (re.match(u1.str()).hasMatch())
++ return true;
++ }
++ } else {
++ // mkspecs
++ if (m_qmakespecName == config)
++ return true;
++
++ // CONFIG variable
++ if (values(statics.strCONFIG).contains(config))
++ return true;
++ }
++
++ return false;
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::expandVariableReferences(
++ const ushort *&tokPtr, int sizeHint, ProStringList *ret, bool joined)
++ {
++ ret->reserve(sizeHint);
++ forever {
++ if (evaluateExpression(tokPtr, ret, joined) == ReturnError)
++ return ReturnError;
++ switch (*tokPtr) {
++ case TokValueTerminator:
++ case TokFuncTerminator:
++ tokPtr++;
++ return ReturnTrue;
++ case TokArgSeparator:
++ if (joined) {
++ tokPtr++;
++ continue;
++ }
++ Q_FALLTHROUGH();
++ default:
++ Q_ASSERT_X(false, "expandVariableReferences", "Unrecognized token");
++ break;
++ }
++ }
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::prepareFunctionArgs(
++ const ushort *&tokPtr, QList<ProStringList> *ret)
++ {
++ if (*tokPtr != TokFuncTerminator) {
++ for (;; tokPtr++) {
++ ProStringList arg;
++ if (evaluateExpression(tokPtr, &arg, false) == ReturnError)
++ return ReturnError;
++ *ret << arg;
++ if (*tokPtr == TokFuncTerminator)
++ break;
++ Q_ASSERT(*tokPtr == TokArgSeparator);
++ }
++ }
++ tokPtr++;
++ return ReturnTrue;
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFunction(
++ const ProFunctionDef &func, const QList<ProStringList> &argumentsList, ProStringList *ret)
++ {
++ VisitReturn vr;
++
++ if (m_valuemapStack.size() >= 100) {
++ evalError(fL1S("Ran into infinite recursion (depth > 100)."));
++ vr = ReturnError;
++ } else {
++ m_valuemapStack.push(ProValueMap());
++ m_locationStack.push(m_current);
++
++ ProStringList args;
++ for (int i = 0; i < argumentsList.size(); ++i) {
++ args += argumentsList[i];
++ m_valuemapStack.top()[ProKey(QString::number(i+1))] = argumentsList[i];
++ }
++ m_valuemapStack.top()[statics.strARGS] = args;
++ m_valuemapStack.top()[statics.strARGC] = ProStringList(ProString(QString::number(argumentsList.size())));
++ vr = visitProBlock(func.pro(), func.tokPtr());
++ if (vr == ReturnReturn)
++ vr = ReturnTrue;
++ if (vr == ReturnTrue)
++ *ret = m_returnValue;
++ m_returnValue.clear();
++
++ m_current = m_locationStack.pop();
++ m_valuemapStack.pop();
++ }
++ return vr;
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBoolFunction(
++ const ProFunctionDef &func, const QList<ProStringList> &argumentsList,
++ const ProString &function)
++ {
++ ProStringList ret;
++ VisitReturn vr = evaluateFunction(func, argumentsList, &ret);
++ if (vr == ReturnTrue) {
++ if (ret.isEmpty())
++ return ReturnTrue;
++ if (ret.at(0) != statics.strfalse) {
++ if (ret.at(0) == statics.strtrue)
++ return ReturnTrue;
++ bool ok;
++ int val = ret.at(0).toInt(&ok);
++ if (ok) {
++ if (val)
++ return ReturnTrue;
++ } else {
++ ProStringRoUser u1(function, m_tmp1);
++ evalError(fL1S("Unexpected return value from test '%1': %2.")
++ .arg(u1.str(), ret.join(QLatin1String(" :: "))));
++ }
++ }
++ return ReturnFalse;
++ }
++ return vr;
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditionalFunction(
++ const ProKey &func, const ushort *&tokPtr)
++ {
++ auto adef = statics.functions.constFind(func);
++ if (adef != statics.functions.constEnd()) {
++ //why don't the builtin functions just use args_list? --Sam
++ ProStringList args;
++ if (expandVariableReferences(tokPtr, 5, &args, true) == ReturnError)
++ return ReturnError;
++ return evaluateBuiltinConditional(*adef, func, args);
++ }
++
++ QHash<ProKey, ProFunctionDef>::ConstIterator it =
++ m_functionDefs.testFunctions.constFind(func);
++ if (it != m_functionDefs.testFunctions.constEnd()) {
++ QList<ProStringList> args;
++ if (prepareFunctionArgs(tokPtr, &args) == ReturnError)
++ return ReturnError;
++ traceMsg("calling %s(%s)", dbgKey(func), dbgStrListList(args));
++ return evaluateBoolFunction(*it, args, func);
++ }
++
++ skipExpression(tokPtr);
++ evalError(fL1S("'%1' is not a recognized test function.").arg(func.toQStringView()));
++ return ReturnFalse;
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateExpandFunction(
++ const ProKey &func, const ushort *&tokPtr, ProStringList *ret)
++ {
++ auto adef = statics.expands.constFind(func);
++ if (adef != statics.expands.constEnd()) {
++ //why don't the builtin functions just use args_list? --Sam
++ ProStringList args;
++ if (expandVariableReferences(tokPtr, 5, &args, true) == ReturnError)
++ return ReturnError;
++ return evaluateBuiltinExpand(*adef, func, args, *ret);
++ }
++
++ QHash<ProKey, ProFunctionDef>::ConstIterator it =
++ m_functionDefs.replaceFunctions.constFind(func);
++ if (it != m_functionDefs.replaceFunctions.constEnd()) {
++ QList<ProStringList> args;
++ if (prepareFunctionArgs(tokPtr, &args) == ReturnError)
++ return ReturnError;
++ traceMsg("calling $$%s(%s)", dbgKey(func), dbgStrListList(args));
++ return evaluateFunction(*it, args, ret);
++ }
++
++ skipExpression(tokPtr);
++ evalError(fL1S("'%1' is not a recognized replace function.").arg(func.toQStringView()));
++ return ReturnFalse;
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditional(
++ QStringView cond, const QString &where, int line)
++ {
++ VisitReturn ret = ReturnFalse;
++ ProFile *pro = m_parser->parsedProBlock(cond, 0, where, line, QMakeParser::TestGrammar);
++ if (pro->isOk()) {
++ m_locationStack.push(m_current);
++ ret = visitProBlock(pro, pro->tokPtr());
++ m_current = m_locationStack.pop();
++ }
++ pro->deref();
++ return ret;
++ }
++
++ #ifdef PROEVALUATOR_FULL
++ QMakeEvaluator::VisitReturn QMakeEvaluator::checkRequirements(const ProStringList &deps)
++ {
++ ProStringList &failed = valuesRef(ProKey("QMAKE_FAILED_REQUIREMENTS"));
++ for (const ProString &dep : deps) {
++ VisitReturn vr = evaluateConditional(dep.toQStringView(), m_current.pro->fileName(), m_current.line);
++ if (vr == ReturnError)
++ return ReturnError;
++ if (vr != ReturnTrue)
++ failed << dep;
++ }
++ return ReturnTrue;
++ }
++ #endif
++
++ static bool isFunctParam(const ProKey &variableName)
++ {
++ const int len = variableName.size();
++ const QChar *data = variableName.constData();
++ for (int i = 0; i < len; i++) {
++ ushort c = data[i].unicode();
++ if (c < '0' || c > '9')
++ return false;
++ }
++ return true;
++ }
++
++ ProValueMap *QMakeEvaluator::findValues(const ProKey &variableName, ProValueMap::Iterator *rit)
++ {
++ ProValueMapStack::iterator vmi = m_valuemapStack.end();
++ for (bool first = true; ; first = false) {
++ --vmi;
++ ProValueMap::Iterator it = (*vmi).find(variableName);
++ if (it != (*vmi).end()) {
++ if (it->constBegin() == statics.fakeValue.constBegin())
++ break;
++ *rit = it;
++ return &(*vmi);
++ }
++ if (vmi == m_valuemapStack.begin())
++ break;
++ if (first && isFunctParam(variableName))
++ break;
++ }
++ return nullptr;
++ }
++
++ ProStringList &QMakeEvaluator::valuesRef(const ProKey &variableName)
++ {
++ ProValueMap::Iterator it = m_valuemapStack.top().find(variableName);
++ if (it != m_valuemapStack.top().end()) {
++ if (it->constBegin() == statics.fakeValue.constBegin())
++ it->clear();
++ return *it;
++ }
++ if (!isFunctParam(variableName)) {
++ ProValueMapStack::iterator vmi = m_valuemapStack.end();
++ if (--vmi != m_valuemapStack.begin()) {
++ do {
++ --vmi;
++ ProValueMap::ConstIterator it = (*vmi).constFind(variableName);
++ if (it != (*vmi).constEnd()) {
++ ProStringList &ret = m_valuemapStack.top()[variableName];
++ if (it->constBegin() != statics.fakeValue.constBegin())
++ ret = *it;
++ return ret;
++ }
++ } while (vmi != m_valuemapStack.begin());
++ }
++ }
++ return m_valuemapStack.top()[variableName];
++ }
++
++ ProStringList QMakeEvaluator::values(const ProKey &variableName) const
++ {
++ ProValueMapStack::const_iterator vmi = m_valuemapStack.cend();
++ for (bool first = true; ; first = false) {
++ --vmi;
++ ProValueMap::ConstIterator it = (*vmi).constFind(variableName);
++ if (it != (*vmi).constEnd()) {
++ if (it->constBegin() == statics.fakeValue.constBegin())
++ break;
++ return *it;
++ }
++ if (vmi == m_valuemapStack.cbegin())
++ break;
++ if (first && isFunctParam(variableName))
++ break;
++ }
++ return ProStringList();
++ }
++
++ ProString QMakeEvaluator::first(const ProKey &variableName) const
++ {
++ const ProStringList &vals = values(variableName);
++ if (!vals.isEmpty())
++ return vals.first();
++ return ProString();
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFile(
++ const QString &fileName, QMakeHandler::EvalFileType type, LoadFlags flags)
++ {
++ QMakeParser::ParseFlags pflags = QMakeParser::ParseUseCache;
++ if (!(flags & LoadSilent))
++ pflags |= QMakeParser::ParseReportMissing;
++ if (ProFile *pro = m_parser->parsedProFile(fileName, pflags)) {
++ m_locationStack.push(m_current);
++ VisitReturn ok = visitProFile(pro, type, flags);
++ m_current = m_locationStack.pop();
++ pro->deref();
++ if (ok == ReturnTrue && !(flags & LoadHidden)) {
++ ProStringList &iif = m_valuemapStack.front()[ProKey("QMAKE_INTERNAL_INCLUDED_FILES")];
++ ProString ifn(fileName);
++ if (!iif.contains(ifn))
++ iif << ifn;
++ }
++ return ok;
++ } else {
++ return ReturnFalse;
++ }
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFileChecked(
++ const QString &fileName, QMakeHandler::EvalFileType type, LoadFlags flags)
++ {
++ if (fileName.isEmpty())
++ return ReturnFalse;
++ const QMakeEvaluator *ref = this;
++ do {
++ for (const ProFile *pf : ref->m_profileStack)
++ if (pf->fileName() == fileName) {
++ evalError(fL1S("Circular inclusion of %1.").arg(fileName));
++ return ReturnFalse;
++ }
++ } while ((ref = ref->m_caller));
++ return evaluateFile(fileName, type, flags);
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFeatureFile(
++ const QString &fileName, bool silent)
++ {
++ QString fn = fileName;
++ if (!fn.endsWith(QLatin1String(".prf")))
++ fn += QLatin1String(".prf");
++
++ if (!m_featureRoots)
++ updateFeaturePaths();
++ #ifdef PROEVALUATOR_THREAD_SAFE
++ m_featureRoots->mutex.lock();
++ #endif
++ QString currFn = currentFileName();
++ if (IoUtils::fileName(currFn) != IoUtils::fileName(fn))
++ currFn.clear();
++ // Null values cannot regularly exist in the hash, so they indicate that the value still
++ // needs to be determined. Failed lookups are represented via non-null empty strings.
++ QString *fnp = &m_featureRoots->cache[qMakePair(fn, currFn)];
++ if (fnp->isNull()) {
++ #ifdef QMAKE_OVERRIDE_PRFS
++ {
++ QString ovrfn(QLatin1String(":/qmake/override_features/") + fn);
++ if (QFileInfo::exists(ovrfn)) {
++ fn = ovrfn;
++ goto cool;
++ }
++ }
++ #endif
++ {
++ int start_root = 0;
++ const QStringList &paths = m_featureRoots->paths;
++ if (!currFn.isEmpty()) {
++ QStringView currPath = IoUtils::pathName(currFn);
++ for (int root = 0; root < paths.size(); ++root)
++ if (currPath == paths.at(root)) {
++ start_root = root + 1;
++ break;
++ }
++ }
++ for (int root = start_root; root < paths.size(); ++root) {
++ QString fname = paths.at(root) + fn;
++ if (IoUtils::exists(fname)) {
++ fn = fname;
++ goto cool;
++ }
++ }
++ }
++ #ifdef QMAKE_BUILTIN_PRFS
++ fn.prepend(QLatin1String(":/qmake/features/"));
++ if (QFileInfo::exists(fn))
++ goto cool;
++ #endif
++ fn = QLatin1String(""); // Indicate failed lookup. See comment above.
++
++ cool:
++ *fnp = fn;
++ } else {
++ fn = *fnp;
++ }
++ #ifdef PROEVALUATOR_THREAD_SAFE
++ m_featureRoots->mutex.unlock();
++ #endif
++ if (fn.isEmpty()) {
++ if (!silent)
++ evalError(fL1S("Cannot find feature %1").arg(fileName));
++ return ReturnFalse;
++ }
++ ProStringList &already = valuesRef(ProKey("QMAKE_INTERNAL_INCLUDED_FEATURES"));
++ ProString afn(fn);
++ if (already.contains(afn)) {
++ if (!silent)
++ languageWarning(fL1S("Feature %1 already included").arg(fileName));
++ return ReturnTrue;
++ }
++ already.append(afn);
++
++ #ifdef PROEVALUATOR_CUMULATIVE
++ bool cumulative = m_cumulative;
++ // Even when evaluating the project in cumulative mode to maximize the
++ // chance of collecting all source declarations, prfs are evaluated in
++ // exact mode to maximize the chance of them successfully executing
++ // their programmatic function.
++ m_cumulative = false;
++ #endif
++
++ // The path is fully normalized already.
++ VisitReturn ok = evaluateFile(fn, QMakeHandler::EvalFeatureFile, LoadProOnly);
++
++ #ifdef PROEVALUATOR_CUMULATIVE
++ m_cumulative = cumulative;
++ if (cumulative) {
++ // As the data collected in cumulative mode is potentially total
++ // garbage, yet the prfs fed with it are executed in exact mode,
++ // we must ignore their results to avoid that evaluation is unduly
++ // aborted.
++ ok = ReturnTrue;
++ }
++ #endif
++ return ok;
++ }
++
++ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFileInto(
++ const QString &fileName, ProValueMap *values, LoadFlags flags)
++ {
++ QMakeEvaluator visitor(m_option, m_parser, m_vfs, m_handler);
++ visitor.m_caller = this;
++ visitor.m_outputDir = m_outputDir;
++ visitor.m_featureRoots = m_featureRoots;
++ VisitReturn ret = visitor.evaluateFileChecked(fileName, QMakeHandler::EvalAuxFile, flags);
++ if (ret != ReturnTrue)
++ return ret;
++ *values = visitor.m_valuemapStack.top();
++ ProKey qiif("QMAKE_INTERNAL_INCLUDED_FILES");
++ ProStringList &iif = m_valuemapStack.front()[qiif];
++ const auto ifns = values->value(qiif);
++ for (const ProString &ifn : ifns)
++ if (!iif.contains(ifn))
++ iif << ifn;
++ return ReturnTrue;
++ }
++
++ void QMakeEvaluator::message(int type, const QString &msg) const
++ {
++ if (!m_skipLevel)
++ m_handler->message(type | (m_cumulative ? QMakeHandler::CumulativeEvalMessage : 0), msg,
++ m_current.line ? m_current.pro->fileName() : QString(),
++ m_current.line != 0xffff ? m_current.line : -1);
++ }
++
++ #ifdef PROEVALUATOR_DEBUG
++ void QMakeEvaluator::debugMsgInternal(int level, const char *fmt, ...) const
++ {
++ va_list ap;
++
++ if (level <= m_debugLevel) {
++ fprintf(stderr, "DEBUG %d: ", level);
++ va_start(ap, fmt);
++ vfprintf(stderr, fmt, ap);
++ va_end(ap);
++ fputc('\n', stderr);
++ }
++ }
++
++ void QMakeEvaluator::traceMsgInternal(const char *fmt, ...) const
++ {
++ va_list ap;
++
++ if (!m_current.pro)
++ fprintf(stderr, "DEBUG 1: ");
++ else if (m_current.line <= 0)
++ fprintf(stderr, "DEBUG 1: %s: ", qPrintable(m_current.pro->fileName()));
++ else
++ fprintf(stderr, "DEBUG 1: %s:%d: ", qPrintable(m_current.pro->fileName()), m_current.line);
++ va_start(ap, fmt);
++ vfprintf(stderr, fmt, ap);
++ va_end(ap);
++ fputc('\n', stderr);
++ }
++
++ QString QMakeEvaluator::formatValue(const ProString &val, bool forceQuote)
++ {
++ QString ret;
++ ret.reserve(val.size() + 2);
++ const QChar *chars = val.constData();
++ bool quote = forceQuote || val.isEmpty();
++ for (int i = 0, l = val.size(); i < l; i++) {
++ QChar c = chars[i];
++ ushort uc = c.unicode();
++ if (uc < 32) {
++ switch (uc) {
++ case '\r':
++ ret += QLatin1String("\\r");
++ break;
++ case '\n':
++ ret += QLatin1String("\\n");
++ break;
++ case '\t':
++ ret += QLatin1String("\\t");
++ break;
++ default:
++ ret += QString::fromLatin1("\\x%1").arg(uc, 2, 16, QLatin1Char('0'));
++ break;
++ }
++ } else {
++ switch (uc) {
++ case '\\':
++ ret += QLatin1String("\\\\");
++ break;
++ case '"':
++ ret += QLatin1String("\\\"");
++ break;
++ case '\'':
++ ret += QLatin1String("\\'");
++ break;
++ case 32:
++ quote = true;
++ Q_FALLTHROUGH();
++ default:
++ ret += c;
++ break;
++ }
++ }
++ }
++ if (quote) {
++ ret.prepend(QLatin1Char('"'));
++ ret.append(QLatin1Char('"'));
++ }
++ return ret;
++ }
++
++ QString QMakeEvaluator::formatValueList(const ProStringList &vals, bool commas)
++ {
++ QString ret;
++
++ for (const ProString &str : vals) {
++ if (!ret.isEmpty()) {
++ if (commas)
++ ret += QLatin1Char(',');
++ ret += QLatin1Char(' ');
++ }
++ ret += formatValue(str);
++ }
++ return ret;
++ }
++
++ QString QMakeEvaluator::formatValueListList(const QList<ProStringList> &lists)
++ {
++ QString ret;
++
++ for (const ProStringList &list : lists) {
++ if (!ret.isEmpty())
++ ret += QLatin1String(", ");
++ ret += formatValueList(list);
++ }
++ return ret;
++ }
++ #endif
++
++ QT_END_NAMESPACE