qt5base-lts/qmake/library/qmakeevaluator.cpp

2189 lines
72 KiB
C++
Raw Normal View History

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