c49728eb27
Change-Id: Ie07a976cd3c634e04c8b9b1e0a6cacd4c2d94939 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2203 lines
73 KiB
C++
2203 lines
73 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the qmake application of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qmakeevaluator.h"
|
|
#include "qmakeevaluator_p.h"
|
|
|
|
#include "qmakeglobals.h"
|
|
#include "qmakeparser.h"
|
|
#include "qmakevfs.h"
|
|
#include "ioutils.h"
|
|
|
|
#include <qbytearray.h>
|
|
#include <qdatetime.h>
|
|
#include <qdebug.h>
|
|
#include <qdir.h>
|
|
#include <qfile.h>
|
|
#include <qfileinfo.h>
|
|
#include <qlist.h>
|
|
#include <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 <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();
|
|
const int vals_len = vals.length();
|
|
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)) : "<invalid>");
|
|
}
|
|
or_op = !okey; // tentatively force next evaluation
|
|
invert = false;
|
|
curr.clear();
|
|
continue;
|
|
case TokTestCall:
|
|
if (!m_skipLevel && okey != or_op) {
|
|
if (curr.size() != 1) {
|
|
if (!m_cumulative || !curr.isEmpty())
|
|
evalError(fL1S("Test name must expand to exactly one word."));
|
|
skipExpression(tokPtr);
|
|
okey = false;
|
|
} else {
|
|
traceMsg("evaluating test function %s", dbgStr(curr.at(0)));
|
|
ret = evaluateConditionalFunction(curr.at(0).toKey(), tokPtr);
|
|
switch (ret) {
|
|
case ReturnTrue: okey = true; break;
|
|
case ReturnFalse: okey = false; break;
|
|
default:
|
|
traceMsg("aborting block, function status: %s", dbgReturn(ret));
|
|
return ret;
|
|
}
|
|
traceMsg("test function returned %s", dbgBool(okey));
|
|
okey ^= invert;
|
|
}
|
|
} else if (m_cumulative) {
|
|
#ifdef PROEVALUATOR_CUMULATIVE
|
|
m_skipLevel++;
|
|
if (curr.size() != 1)
|
|
skipExpression(tokPtr);
|
|
else
|
|
evaluateConditionalFunction(curr.at(0).toKey(), tokPtr);
|
|
m_skipLevel--;
|
|
#endif
|
|
} else {
|
|
skipExpression(tokPtr);
|
|
traceMsg("skipped test function %s", curr.size() == 1 ? dbgStr(curr.at(0)) : "<invalid>");
|
|
}
|
|
or_op = !okey; // tentatively force next evaluation
|
|
invert = false;
|
|
curr.clear();
|
|
continue;
|
|
case TokReturn:
|
|
m_returnValue = curr;
|
|
curr.clear();
|
|
ret = ReturnReturn;
|
|
goto ctrlstm;
|
|
case TokBreak:
|
|
ret = ReturnBreak;
|
|
goto ctrlstm;
|
|
case TokNext:
|
|
ret = ReturnNext;
|
|
ctrlstm:
|
|
if (!m_skipLevel && okey != or_op) {
|
|
traceMsg("flow control statement '%s', aborting block", dbgReturn(ret));
|
|
return ret;
|
|
}
|
|
traceMsg("skipped flow control statement '%s'", dbgReturn(ret));
|
|
okey = false, or_op = true; // force next evaluation
|
|
continue;
|
|
default: {
|
|
const ushort *oTokPtr = --tokPtr;
|
|
ret = evaluateExpression(tokPtr, &curr, false);
|
|
if (ret == ReturnError || tokPtr != oTokPtr)
|
|
break;
|
|
}
|
|
Q_ASSERT_X(false, "visitProBlock", "unexpected item type");
|
|
continue;
|
|
}
|
|
if (ret != ReturnTrue && ret != ReturnFalse) {
|
|
traceMsg("aborting block, status: %s", dbgReturn(ret));
|
|
return ret;
|
|
}
|
|
}
|
|
traceMsg("leaving block, okey=%s", dbgBool(okey));
|
|
return returnBool(okey);
|
|
}
|
|
|
|
|
|
void QMakeEvaluator::visitProFunctionDef(
|
|
ushort tok, const ProKey &name, const ushort *tokPtr)
|
|
{
|
|
QHash<ProKey, ProFunctionDef> *hash =
|
|
(tok == TokTestDef
|
|
? &m_functionDefs.testFunctions
|
|
: &m_functionDefs.replaceFunctions);
|
|
hash->insert(name, ProFunctionDef(m_current.pro, tokPtr - m_current.pro->tokPtr()));
|
|
}
|
|
|
|
QMakeEvaluator::VisitReturn QMakeEvaluator::visitProLoop(
|
|
const ProKey &_variable, const ushort *exprPtr, const ushort *tokPtr)
|
|
{
|
|
VisitReturn ret = ReturnTrue;
|
|
bool infinite = false;
|
|
int index = 0;
|
|
ProKey variable;
|
|
ProStringList oldVarVal;
|
|
ProStringList it_list_out;
|
|
if (expandVariableReferences(exprPtr, 0, &it_list_out, true) == ReturnError)
|
|
return ReturnError;
|
|
ProString it_list = it_list_out.at(0);
|
|
if (_variable.isEmpty()) {
|
|
if (it_list != statics.strever) {
|
|
evalError(fL1S("Invalid loop expression."));
|
|
return ReturnFalse;
|
|
}
|
|
it_list = ProString(statics.strforever);
|
|
} else {
|
|
variable = map(_variable);
|
|
oldVarVal = values(variable);
|
|
}
|
|
ProStringList list = values(it_list.toKey());
|
|
if (list.isEmpty()) {
|
|
if (it_list == statics.strforever) {
|
|
if (m_cumulative) {
|
|
// The termination conditions wouldn't be evaluated, so we must skip it.
|
|
traceMsg("skipping forever loop in cumulative mode");
|
|
return ReturnFalse;
|
|
}
|
|
infinite = true;
|
|
} else {
|
|
QStringView itl = it_list.toQStringView();
|
|
int dotdot = itl.indexOf(statics.strDotDot);
|
|
if (dotdot != -1) {
|
|
bool ok;
|
|
int start = itl.left(dotdot).toInt(&ok);
|
|
if (ok) {
|
|
int end = itl.mid(dotdot+2).toInt(&ok);
|
|
if (ok) {
|
|
const int absDiff = qAbs(end - start);
|
|
if (m_cumulative && absDiff > 100) {
|
|
// Such a loop is unlikely to contribute something useful to the
|
|
// file collection, and may cause considerable delay.
|
|
traceMsg("skipping excessive loop in cumulative mode");
|
|
return ReturnFalse;
|
|
}
|
|
list.reserve(absDiff + 1);
|
|
if (start < end) {
|
|
for (int i = start; i <= end; i++)
|
|
list << ProString(QString::number(i));
|
|
} else {
|
|
for (int i = start; i >= end; i--)
|
|
list << ProString(QString::number(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (infinite)
|
|
traceMsg("entering infinite loop for %s", dbgKey(variable));
|
|
else
|
|
traceMsg("entering loop for %s over %s", dbgKey(variable), dbgStrList(list));
|
|
|
|
forever {
|
|
if (infinite) {
|
|
if (!variable.isEmpty())
|
|
m_valuemapStack.top()[variable] = ProStringList(ProString(QString::number(index)));
|
|
if (++index > 1000) {
|
|
evalError(fL1S("Ran into infinite loop (> 1000 iterations)."));
|
|
break;
|
|
}
|
|
traceMsg("loop iteration %d", index);
|
|
} else {
|
|
ProString val;
|
|
do {
|
|
if (index >= list.count())
|
|
goto do_break;
|
|
val = list.at(index++);
|
|
} while (val.isEmpty()); // stupid, but qmake is like that
|
|
traceMsg("loop iteration %s", dbgStr(val));
|
|
m_valuemapStack.top()[variable] = ProStringList(val);
|
|
}
|
|
|
|
ret = visitProBlock(tokPtr);
|
|
switch (ret) {
|
|
case ReturnTrue:
|
|
case ReturnFalse:
|
|
break;
|
|
case ReturnNext:
|
|
ret = ReturnTrue;
|
|
break;
|
|
case ReturnBreak:
|
|
ret = ReturnTrue;
|
|
goto do_break;
|
|
default:
|
|
goto do_break;
|
|
}
|
|
}
|
|
do_break:
|
|
|
|
traceMsg("done looping");
|
|
|
|
if (!variable.isEmpty())
|
|
m_valuemapStack.top()[variable] = oldVarVal;
|
|
return ret;
|
|
}
|
|
|
|
QMakeEvaluator::VisitReturn QMakeEvaluator::visitProVariable(
|
|
ushort tok, const ProStringList &curr, const ushort *&tokPtr)
|
|
{
|
|
int sizeHint = *tokPtr++;
|
|
|
|
if (curr.size() != 1) {
|
|
skipExpression(tokPtr);
|
|
if (!m_cumulative || !curr.isEmpty())
|
|
evalError(fL1S("Left hand side of assignment must expand to exactly one word."));
|
|
return ReturnTrue;
|
|
}
|
|
const ProKey &varName = map(curr.first());
|
|
|
|
if (tok == TokReplace) { // ~=
|
|
// DEFINES ~= s/a/b/?[gqi]
|
|
|
|
ProStringList varVal;
|
|
if (expandVariableReferences(tokPtr, sizeHint, &varVal, true) == ReturnError)
|
|
return ReturnError;
|
|
QStringView val = varVal.at(0).toQStringView();
|
|
if (val.length() < 4 || val.at(0) != QLatin1Char('s')) {
|
|
evalError(fL1S("The ~= operator can handle only the s/// function."));
|
|
return ReturnTrue;
|
|
}
|
|
QChar sep = val.at(1);
|
|
auto func = val.split(sep, Qt::KeepEmptyParts);
|
|
if (func.count() < 3 || func.count() > 4) {
|
|
evalError(fL1S("The s/// function expects 3 or 4 arguments."));
|
|
return ReturnTrue;
|
|
}
|
|
|
|
bool global = false, quote = false, case_sense = false;
|
|
if (func.count() == 4) {
|
|
global = func[3].indexOf(QLatin1Char('g')) != -1;
|
|
case_sense = func[3].indexOf(QLatin1Char('i')) == -1;
|
|
quote = func[3].indexOf(QLatin1Char('q')) != -1;
|
|
}
|
|
QString pattern = func[1].toString();
|
|
QString replace = func[2].toString();
|
|
if (quote)
|
|
pattern = 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 : qAsConst(m_mkspecPaths)) {
|
|
QString mkspec = root + QLatin1Char('/') + qmakespec;
|
|
if (IoUtils::exists(mkspec)) {
|
|
qmakespec = mkspec;
|
|
goto cool;
|
|
}
|
|
}
|
|
evalError(fL1S("Could not find qmake spec '%1'.").arg(qmakespec));
|
|
return false;
|
|
}
|
|
cool:
|
|
m_qmakespec = QDir::cleanPath(qmakespec);
|
|
|
|
if (!m_superfile.isEmpty()) {
|
|
valuesRef(ProKey("_QMAKE_SUPER_CACHE_")) << ProString(m_superfile);
|
|
if (evaluateFile(
|
|
m_superfile, QMakeHandler::EvalConfigFile, LoadProOnly|LoadHidden) != ReturnTrue)
|
|
return false;
|
|
}
|
|
if (!loadSpecInternal())
|
|
return false;
|
|
if (!m_conffile.isEmpty()) {
|
|
valuesRef(ProKey("_QMAKE_CONF_")) << ProString(m_conffile);
|
|
if (evaluateFile(
|
|
m_conffile, QMakeHandler::EvalConfigFile, LoadProOnly) != ReturnTrue)
|
|
return false;
|
|
}
|
|
if (!m_cachefile.isEmpty()) {
|
|
valuesRef(ProKey("_QMAKE_CACHE_")) << ProString(m_cachefile);
|
|
if (evaluateFile(
|
|
m_cachefile, QMakeHandler::EvalConfigFile, LoadProOnly) != ReturnTrue)
|
|
return false;
|
|
}
|
|
QMakeVfs::VfsFlags flags = (m_cumulative ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact);
|
|
if (!m_stashfile.isEmpty() && m_vfs->exists(m_stashfile, flags)) {
|
|
valuesRef(ProKey("_QMAKE_STASH_")) << ProString(m_stashfile);
|
|
if (evaluateFile(
|
|
m_stashfile, QMakeHandler::EvalConfigFile, LoadProOnly) != ReturnTrue)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void QMakeEvaluator::setupProject()
|
|
{
|
|
setTemplate();
|
|
ProValueMap &vars = m_valuemapStack.top();
|
|
int proFile = currentFileId();
|
|
vars[ProKey("TARGET")] << ProString(QFileInfo(currentFileName()).baseName()).setSource(proFile);
|
|
vars[ProKey("_PRO_FILE_")] << ProString(currentFileName()).setSource(proFile);
|
|
vars[ProKey("_PRO_FILE_PWD_")] << ProString(currentDirectory()).setSource(proFile);
|
|
vars[ProKey("OUT_PWD")] << ProString(m_outputDir).setSource(proFile);
|
|
}
|
|
|
|
void QMakeEvaluator::evaluateCommand(const QString &cmds, const QString &where)
|
|
{
|
|
if (!cmds.isEmpty()) {
|
|
ProFile *pro = m_parser->parsedProBlock(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 : qAsConst(m_qmakepath))
|
|
ret << it + concat;
|
|
|
|
if (!m_buildRoot.isEmpty())
|
|
ret << m_buildRoot + concat;
|
|
if (!m_sourceRoot.isEmpty())
|
|
ret << m_sourceRoot + concat;
|
|
|
|
ret << m_option->propertyValue(ProKey("QT_HOST_DATA/get")) + concat;
|
|
ret << m_option->propertyValue(ProKey("QT_HOST_DATA/src")) + concat;
|
|
|
|
ret.removeDuplicates();
|
|
m_mkspecPaths = ret;
|
|
}
|
|
|
|
void QMakeEvaluator::updateFeaturePaths()
|
|
{
|
|
QString mkspecs_concat = QLatin1String("/mkspecs");
|
|
QString features_concat = QLatin1String("/features/");
|
|
|
|
QStringList feature_roots;
|
|
|
|
feature_roots += m_option->getPathListEnv(QLatin1String("QMAKEFEATURES"));
|
|
feature_roots += m_qmakefeatures;
|
|
feature_roots += m_option->splitPathList(
|
|
m_option->propertyValue(ProKey("QMAKEFEATURES")).toQString());
|
|
|
|
QStringList feature_bases;
|
|
if (!m_buildRoot.isEmpty()) {
|
|
feature_bases << m_buildRoot + mkspecs_concat;
|
|
feature_bases << m_buildRoot;
|
|
}
|
|
if (!m_sourceRoot.isEmpty()) {
|
|
feature_bases << m_sourceRoot + mkspecs_concat;
|
|
feature_bases << m_sourceRoot;
|
|
}
|
|
|
|
const auto items = m_option->getPathListEnv(QLatin1String("QMAKEPATH"));
|
|
for (const QString &item : items)
|
|
feature_bases << (item + mkspecs_concat);
|
|
|
|
for (const QString &item : qAsConst(m_qmakepath))
|
|
feature_bases << (item + mkspecs_concat);
|
|
|
|
if (!m_qmakespec.isEmpty()) {
|
|
// The spec is already platform-dependent, so no subdirs here.
|
|
feature_roots << (m_qmakespec + features_concat);
|
|
|
|
// Also check directly under the root directory of the mkspecs collection
|
|
QDir specdir(m_qmakespec);
|
|
while (!specdir.isRoot() && specdir.cdUp()) {
|
|
const QString specpath = specdir.path();
|
|
if (specpath.endsWith(mkspecs_concat)) {
|
|
if (IoUtils::exists(specpath + features_concat))
|
|
feature_bases << specpath;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
feature_bases << (m_option->propertyValue(ProKey("QT_HOST_DATA/get")) + mkspecs_concat);
|
|
feature_bases << (m_option->propertyValue(ProKey("QT_HOST_DATA/src")) + mkspecs_concat);
|
|
|
|
for (const QString &fb : qAsConst(feature_bases)) {
|
|
const auto sfxs = values(ProKey("QMAKE_PLATFORM"));
|
|
for (const ProString &sfx : sfxs)
|
|
feature_roots << (fb + features_concat + sfx + QLatin1Char('/'));
|
|
feature_roots << (fb + features_concat);
|
|
}
|
|
|
|
for (int i = 0; i < feature_roots.count(); ++i)
|
|
if (!feature_roots.at(i).endsWith(QLatin1Char('/')))
|
|
feature_roots[i].append(QLatin1Char('/'));
|
|
|
|
feature_roots.removeDuplicates();
|
|
|
|
QStringList ret;
|
|
for (const QString &root : qAsConst(feature_roots))
|
|
if (IoUtils::exists(root))
|
|
ret << root;
|
|
m_featureRoots = new QMakeFeatureRoots(ret);
|
|
}
|
|
|
|
ProString QMakeEvaluator::propertyValue(const ProKey &name) const
|
|
{
|
|
if (name == QLatin1String("QMAKE_MKSPECS"))
|
|
return ProString(m_mkspecPaths.join(m_option->dirlist_sep));
|
|
ProString ret = m_option->propertyValue(name);
|
|
// if (ret.isNull())
|
|
// evalError(fL1S("Querying unknown property %1").arg(name.toQStringView()));
|
|
return ret;
|
|
}
|
|
|
|
ProFile *QMakeEvaluator::currentProFile() const
|
|
{
|
|
if (m_profileStack.count() > 0)
|
|
return m_profileStack.top();
|
|
return nullptr;
|
|
}
|
|
|
|
int QMakeEvaluator::currentFileId() const
|
|
{
|
|
ProFile *pro = currentProFile();
|
|
if (pro)
|
|
return pro->id();
|
|
return 0;
|
|
}
|
|
|
|
QString QMakeEvaluator::currentFileName() const
|
|
{
|
|
ProFile *pro = currentProFile();
|
|
if (pro)
|
|
return pro->fileName();
|
|
return QString();
|
|
}
|
|
|
|
QString QMakeEvaluator::currentDirectory() const
|
|
{
|
|
ProFile *pro = currentProFile();
|
|
if (pro)
|
|
return pro->directoryName();
|
|
return QString();
|
|
}
|
|
|
|
bool QMakeEvaluator::isActiveConfig(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.count(); ++i) {
|
|
args += argumentsList[i];
|
|
m_valuemapStack.top()[ProKey(QString::number(i+1))] = argumentsList[i];
|
|
}
|
|
m_valuemapStack.top()[statics.strARGS] = args;
|
|
m_valuemapStack.top()[statics.strARGC] = ProStringList(ProString(QString::number(argumentsList.count())));
|
|
vr = visitProBlock(func.pro(), func.tokPtr());
|
|
if (vr == ReturnReturn)
|
|
vr = ReturnTrue;
|
|
if (vr == ReturnTrue)
|
|
*ret = m_returnValue;
|
|
m_returnValue.clear();
|
|
|
|
m_current = m_locationStack.pop();
|
|
m_valuemapStack.pop();
|
|
}
|
|
return vr;
|
|
}
|
|
|
|
QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBoolFunction(
|
|
const ProFunctionDef &func, const QList<ProStringList> &argumentsList,
|
|
const ProString &function)
|
|
{
|
|
ProStringList ret;
|
|
VisitReturn vr = evaluateFunction(func, argumentsList, &ret);
|
|
if (vr == ReturnTrue) {
|
|
if (ret.isEmpty())
|
|
return ReturnTrue;
|
|
if (ret.at(0) != statics.strfalse) {
|
|
if (ret.at(0) == statics.strtrue)
|
|
return ReturnTrue;
|
|
bool ok;
|
|
int val = ret.at(0).toInt(&ok);
|
|
if (ok) {
|
|
if (val)
|
|
return ReturnTrue;
|
|
} else {
|
|
ProStringRoUser u1(function, m_tmp1);
|
|
evalError(fL1S("Unexpected return value from test '%1': %2.")
|
|
.arg(u1.str(), ret.join(QLatin1String(" :: "))));
|
|
}
|
|
}
|
|
return ReturnFalse;
|
|
}
|
|
return vr;
|
|
}
|
|
|
|
QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditionalFunction(
|
|
const ProKey &func, const ushort *&tokPtr)
|
|
{
|
|
auto adef = statics.functions.constFind(func);
|
|
if (adef != statics.functions.constEnd()) {
|
|
//why don't the builtin functions just use args_list? --Sam
|
|
ProStringList args;
|
|
if (expandVariableReferences(tokPtr, 5, &args, true) == ReturnError)
|
|
return ReturnError;
|
|
return evaluateBuiltinConditional(*adef, func, args);
|
|
}
|
|
|
|
QHash<ProKey, ProFunctionDef>::ConstIterator it =
|
|
m_functionDefs.testFunctions.constFind(func);
|
|
if (it != m_functionDefs.testFunctions.constEnd()) {
|
|
QList<ProStringList> args;
|
|
if (prepareFunctionArgs(tokPtr, &args) == ReturnError)
|
|
return ReturnError;
|
|
traceMsg("calling %s(%s)", dbgKey(func), dbgStrListList(args));
|
|
return evaluateBoolFunction(*it, args, func);
|
|
}
|
|
|
|
skipExpression(tokPtr);
|
|
evalError(fL1S("'%1' is not a recognized test function.").arg(func.toQStringView()));
|
|
return ReturnFalse;
|
|
}
|
|
|
|
QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateExpandFunction(
|
|
const ProKey &func, const ushort *&tokPtr, ProStringList *ret)
|
|
{
|
|
auto adef = statics.expands.constFind(func);
|
|
if (adef != statics.expands.constEnd()) {
|
|
//why don't the builtin functions just use args_list? --Sam
|
|
ProStringList args;
|
|
if (expandVariableReferences(tokPtr, 5, &args, true) == ReturnError)
|
|
return ReturnError;
|
|
return evaluateBuiltinExpand(*adef, func, args, *ret);
|
|
}
|
|
|
|
QHash<ProKey, ProFunctionDef>::ConstIterator it =
|
|
m_functionDefs.replaceFunctions.constFind(func);
|
|
if (it != m_functionDefs.replaceFunctions.constEnd()) {
|
|
QList<ProStringList> args;
|
|
if (prepareFunctionArgs(tokPtr, &args) == ReturnError)
|
|
return ReturnError;
|
|
traceMsg("calling $$%s(%s)", dbgKey(func), dbgStrListList(args));
|
|
return evaluateFunction(*it, args, ret);
|
|
}
|
|
|
|
skipExpression(tokPtr);
|
|
evalError(fL1S("'%1' is not a recognized replace function.").arg(func.toQStringView()));
|
|
return ReturnFalse;
|
|
}
|
|
|
|
QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditional(
|
|
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;
|
|
m_cumulative = false;
|
|
#endif
|
|
|
|
// The path is fully normalized already.
|
|
VisitReturn ok = evaluateFile(fn, QMakeHandler::EvalFeatureFile, LoadProOnly);
|
|
|
|
#ifdef PROEVALUATOR_CUMULATIVE
|
|
m_cumulative = cumulative;
|
|
#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
|