// 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 #include #include #include #include #include #include #include #include #include #include #include #ifdef PROEVALUATOR_THREAD_SAFE # include #endif #ifdef Q_OS_UNIX #include #include # ifdef Q_OS_BSD4 # include # endif #else #include #endif #include #include 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) { 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::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 ®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).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)) : ""); } 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)) : ""); } 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 *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 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 *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 &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 &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::ConstIterator it = m_functionDefs.testFunctions.constFind(func); if (it != m_functionDefs.testFunctions.constEnd()) { QList 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::ConstIterator it = m_functionDefs.replaceFunctions.constFind(func); if (it != m_functionDefs.replaceFunctions.constEnd()) { QList 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 &lists) { QString ret; for (const ProStringList &list : lists) { if (!ret.isEmpty()) ret += QLatin1String(", "); ret += formatValueList(list); } return ret; } #endif QT_END_NAMESPACE