| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Linguist of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #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 <qregexp.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 <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) |
| { |
| } |
| |
| uint qHash(const QMakeBaseKey &key) |
| { |
| return qHash(key.root) ^ qHash(key.stash) ^ (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(const QStringRef &vals, int source) |
| { |
| QString build; |
| ProStringList ret; |
| |
| if (!source) |
| source = currentFileId(); |
| |
| const QChar *vals_data = vals.data(); |
| const int vals_len = vals.length(); |
| ushort quote = 0; |
| bool hadWord = false; |
| for (int x = 0; x < vals_len; x++) { |
| ushort 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) { |
| ushort 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 QRegExp ®exp, 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).toQStringRef(), 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 { |
| const QStringRef &itl = it_list.toQStringRef(); |
| 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.count()) |
| 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; |
| const QStringRef &val = varVal.at(0).toQStringRef(); |
| if (val.length() < 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, QString::KeepEmptyParts); |
| if (func.count() < 3 || func.count() > 4) { |
| evalError(fL1S("The s/// function expects 3 or 4 arguments.")); |
| return ReturnTrue; |
| } |
| |
| bool global = false, quote = false, case_sense = false; |
| if (func.count() == 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 = QRegExp::escape(pattern); |
| |
| QRegExp regexp(pattern, case_sense ? Qt::CaseSensitive : Qt::CaseInsensitive); |
| |
| // 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(';'), QString::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 : qAsConst(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(QStringRef(&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 : qAsConst(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 : qAsConst(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 : qAsConst(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.count(); ++i) |
| if (!feature_roots.at(i).endsWith((ushort)'/')) |
| feature_roots[i].append((ushort)'/'); |
| |
| feature_roots.removeDuplicates(); |
| |
| QStringList ret; |
| for (const QString &root : qAsConst(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.count() > 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(const QStringRef &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('?')))) { |
| QRegExp re(config.toString(), Qt::CaseSensitive, QRegExp::Wildcard); |
| |
| // mkspecs |
| if (re.exactMatch(m_qmakespecName)) |
| 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.exactMatch(u1.str())) |
| 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 = ReturnFalse; |
| } else { |
| m_valuemapStack.push(ProValueMap()); |
| m_locationStack.push(m_current); |
| |
| ProStringList args; |
| for (int i = 0; i < argumentsList.count(); ++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.count()))); |
| 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( |
| const QStringRef &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.toQStringRef(), 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()) { |
| QStringRef 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 |