diff options
author | Qt Continuous Integration System <qt-info@nokia.com> | 2011-06-10 12:12:36 (GMT) |
---|---|---|
committer | Qt Continuous Integration System <qt-info@nokia.com> | 2011-06-10 12:12:36 (GMT) |
commit | af66d24cd6ba0ec5cbe2d6ecd899a0f1977637eb (patch) | |
tree | 0b7a83889728bdc2ab8414a51c51193a175edb0b /tools/linguist/shared/profileevaluator.cpp | |
parent | c6e155472da8b7192952420aa628677680c3c34a (diff) | |
parent | b9993f684c7ef8c04aa6a0e87af596609957f906 (diff) | |
download | Qt-af66d24cd6ba0ec5cbe2d6ecd899a0f1977637eb.zip Qt-af66d24cd6ba0ec5cbe2d6ecd899a0f1977637eb.tar.gz Qt-af66d24cd6ba0ec5cbe2d6ecd899a0f1977637eb.tar.bz2 |
Merge branch 'master' of git://scm.dev.nokia.troll.no/qt/qt-fire-team
* 'master' of git://scm.dev.nokia.troll.no/qt/qt-fire-team: (118 commits)
Fix compilation of lrelease on Windows
Allow selecting fonts with irregular style names
Fix missing empty lines in Qt HTML when displayed in compliant browsers
Fix autotest on Windows
Update internal state before emitting configurationChanged() signals.
Fix compilation of qtconfig without X11
Compile on Mac OS X
Fixed qmlshadersplugin manual test shaders on SGX family GPU:s.
Fixed qmlshadersplugin on windows VC2008 toolchain.
Reset input context in Symbian when another window is opened.
KERN-EXEC 3 panic in QCoeFepInputContext::translateInputWidget()
Fix for QTBUG-18947. Changed the macosx deployment target to 10.5
Ammend last commit
Implemented QAccessibleTextEdit::attributes()
Changing cursor position in all boundaries
code clean-up for QSystemSemaphore
simplify QSharedMemoryPrivate::cleanHandle()
minor improvements for QSharedMemory
fix potential keyfile leaking
refactoring of the QWSSharedMemory class
...
Diffstat (limited to 'tools/linguist/shared/profileevaluator.cpp')
-rw-r--r-- | tools/linguist/shared/profileevaluator.cpp | 4170 |
1 files changed, 2520 insertions, 1650 deletions
diff --git a/tools/linguist/shared/profileevaluator.cpp b/tools/linguist/shared/profileevaluator.cpp index 0393768..0539efa 100644 --- a/tools/linguist/shared/profileevaluator.cpp +++ b/tools/linguist/shared/profileevaluator.cpp @@ -40,8 +40,9 @@ ****************************************************************************/ #include "profileevaluator.h" -#include "proparserutils.h" -#include "proitems.h" + +#include "profileparser.h" +#include "ioutils.h" #include <QtCore/QByteArray> #include <QtCore/QDateTime> @@ -56,6 +57,9 @@ #include <QtCore/QString> #include <QtCore/QStringList> #include <QtCore/QTextStream> +#ifdef PROEVALUATOR_THREAD_SAFE +# include <QtCore/QThreadPool> +#endif #ifdef Q_OS_UNIX #include <unistd.h> @@ -74,966 +78,1482 @@ #define QT_PCLOSE pclose #endif +using namespace ProFileEvaluatorInternal; + QT_BEGIN_NAMESPACE +using namespace ProStringConstants; + + +#define fL1S(s) QString::fromLatin1(s) + /////////////////////////////////////////////////////////////////////// // -// Option +// ProFileOption // /////////////////////////////////////////////////////////////////////// -QString -Option::fixString(QString string, uchar flags) +ProFileOption::ProFileOption() { - // XXX Ripped out caching, so this will be slow. Should not matter for current uses. +#ifdef Q_OS_WIN + dirlist_sep = QLatin1Char(';'); + dir_sep = QLatin1Char('\\'); +#else + dirlist_sep = QLatin1Char(':'); + dir_sep = QLatin1Char('/'); +#endif + qmakespec = getEnv(QLatin1String("QMAKESPEC")); - //fix the environment variables - if (flags & Option::FixEnvVars) { - int rep; - QRegExp reg_variableName(QLatin1String("\\$\\(.*\\)")); - reg_variableName.setMinimal(true); - while ((rep = reg_variableName.indexIn(string)) != -1) - string.replace(rep, reg_variableName.matchedLength(), - QString::fromLocal8Bit(qgetenv(string.mid(rep + 2, reg_variableName.matchedLength() - 3).toLatin1().constData()).constData())); - } + host_mode = HOST_UNKNOWN_MODE; + target_mode = TARG_UNKNOWN_MODE; - //canonicalize it (and treat as a path) - if (flags & Option::FixPathCanonicalize) { -#if 0 - string = QFileInfo(string).canonicalFilePath(); +#ifdef PROEVALUATOR_THREAD_SAFE + base_inProgress = false; #endif - string = QDir::cleanPath(string); - } +} - if (string.length() > 2 && string[0].isLetter() && string[1] == QLatin1Char(':')) - string[0] = string[0].toLower(); +ProFileOption::~ProFileOption() +{ +} - //fix separators - Q_ASSERT(!((flags & Option::FixPathToLocalSeparators) && (flags & Option::FixPathToTargetSeparators))); - if (flags & Option::FixPathToLocalSeparators) { -#if defined(Q_OS_WIN32) - string = string.replace(QLatin1Char('/'), QLatin1Char('\\')); -#else - string = string.replace(QLatin1Char('\\'), QLatin1Char('/')); -#endif - } else if (flags & Option::FixPathToTargetSeparators) { - string = string.replace(QLatin1Char('/'), Option::dir_sep) - .replace(QLatin1Char('\\'), Option::dir_sep); +void ProFileOption::setCommandLineArguments(const QStringList &args) +{ + QStringList _precmds, _preconfigs, _postcmds, _postconfigs; + bool after = false; + + bool isConf = false; + foreach (const QString &arg, args) { + if (isConf) { + isConf = false; + if (after) + _postconfigs << arg; + else + _preconfigs << arg; + } else if (arg.startsWith(QLatin1Char('-'))) { + if (arg == QLatin1String("-after")) { + after = true; + } else if (arg == QLatin1String("-config")) { + isConf = true; + } else if (arg == QLatin1String("-win32")) { + host_mode = HOST_WIN_MODE; + target_mode = TARG_WIN_MODE; + } else if (arg == QLatin1String("-unix")) { + host_mode = HOST_UNIX_MODE; + target_mode = TARG_UNIX_MODE; + } else if (arg == QLatin1String("-macx")) { + host_mode = HOST_MACX_MODE; + target_mode = TARG_MACX_MODE; + } + } else if (arg.contains(QLatin1Char('='))) { + if (after) + _postcmds << arg; + else + _precmds << arg; + } } - if ((string.startsWith(QLatin1Char('"')) && string.endsWith(QLatin1Char('"'))) || - (string.startsWith(QLatin1Char('\'')) && string.endsWith(QLatin1Char('\'')))) - string = string.mid(1, string.length() - 2); + if (!_preconfigs.isEmpty()) + _precmds << (fL1S("CONFIG += ") + _preconfigs.join(fL1S(" "))); + precmds = _precmds.join(fL1S("\n")); + if (!_postconfigs.isEmpty()) + _postcmds << (fL1S("CONFIG += ") + _postconfigs.join(fL1S(" "))); + postcmds = _postcmds.join(fL1S("\n")); - return string; + if (host_mode != HOST_UNKNOWN_MODE) + applyHostMode(); } +void ProFileOption::applyHostMode() +{ + if (host_mode == HOST_WIN_MODE) { + dir_sep = fL1S("\\"); + } else { + dir_sep = fL1S("/"); + } +} + +QString ProFileOption::getEnv(const QString &var) const +{ +#ifndef QT_BOOTSTRAPPED + if (!environment.isEmpty()) + return environment.value(var); +#endif + return QString::fromLocal8Bit(qgetenv(var.toLocal8Bit().constData())); +} + +#ifdef PROEVALUATOR_INIT_PROPS +bool ProFileOption::initProperties(const QString &qmake) +{ + QByteArray data; +#ifndef QT_BOOTSTRAPPED + QProcess proc; + proc.start(qmake, QStringList() << QLatin1String("-query")); + if (!proc.waitForFinished()) + return false; + data = proc.readAll(); +#else + if (FILE *proc = QT_POPEN(QString(IoUtils::shellQuote(qmake) + QLatin1String(" -query")) + .toLocal8Bit(), "r")) { + char buff[1024]; + while (!feof(proc)) + data.append(buff, int(fread(buff, 1, 1023, proc))); + QT_PCLOSE(proc); + } +#endif + foreach (QByteArray line, data.split('\n')) + if (!line.startsWith("QMAKE_")) { + int off = line.indexOf(':'); + if (off < 0) // huh? + continue; + if (line.endsWith('\r')) + line.chop(1); + properties.insert(QString::fromLatin1(line.left(off)), + QString::fromLocal8Bit(line.mid(off + 1))); + } + return true; +} +#endif + /////////////////////////////////////////////////////////////////////// // // ProFileEvaluator::Private // /////////////////////////////////////////////////////////////////////// -class ProFileEvaluator::Private : public AbstractProItemVisitor +class ProFileEvaluator::Private { public: - Private(ProFileEvaluator *q_); + static void initStatics(); + Private(ProFileEvaluator *q_, ProFileOption *option, ProFileParser *parser, + ProFileEvaluatorHandler *handler); + ~Private(); ProFileEvaluator *q; - int m_lineNo; // Error reporting - bool m_verbose; - - /////////////// Reading pro file - - bool read(ProFile *pro); - - ProBlock *currentBlock(); - void updateItem(); - bool parseLine(const QString &line); - void insertVariable(const QString &line, int *i); - void insertOperator(const char op); - void insertComment(const QString &comment); - void enterScope(bool multiLine); - void leaveScope(); - void finalizeBlock(); - - QStack<ProBlock *> m_blockstack; - ProBlock *m_block; - - ProItem *m_commentItem; - QString m_proitem; - QString m_pendingComment; - bool m_syntaxError; - bool m_contNextLine; - bool m_inQuote; - int m_parens; - - /////////////// Evaluating pro file contents - - // implementation of AbstractProItemVisitor - ProItem::ProItemReturn visitBeginProBlock(ProBlock *block); - void visitEndProBlock(ProBlock *block); - ProItem::ProItemReturn visitProLoopIteration(); - void visitProLoopCleanup(); - void visitBeginProVariable(ProVariable *variable); - void visitEndProVariable(ProVariable *variable); - ProItem::ProItemReturn visitBeginProFile(ProFile *value); - ProItem::ProItemReturn visitEndProFile(ProFile *value); - void visitProValue(ProValue *value); - ProItem::ProItemReturn visitProFunction(ProFunction *function); - void visitProOperator(ProOperator *oper); - void visitProCondition(ProCondition *condition); - - QStringList valuesDirect(const QString &variableName) const { return m_valuemap[variableName]; } - QStringList values(const QString &variableName) const; - QStringList values(const QString &variableName, const ProFile *pro) const; - QStringList values(const QString &variableName, const QHash<QString, QStringList> &place, - const ProFile *pro) const; - QString propertyValue(const QString &val) const; + enum VisitReturn { + ReturnFalse, + ReturnTrue, + ReturnBreak, + ReturnNext, + ReturnReturn + }; + + static ALWAYS_INLINE uint getBlockLen(const ushort *&tokPtr); + ProString getStr(const ushort *&tokPtr); + ProString getHashStr(const ushort *&tokPtr); + void evaluateExpression(const ushort *&tokPtr, ProStringList *ret, bool joined); + static ALWAYS_INLINE void skipStr(const ushort *&tokPtr); + static ALWAYS_INLINE void skipHashStr(const ushort *&tokPtr); + void skipExpression(const ushort *&tokPtr); + + void visitCmdLine(const QString &cmds); + VisitReturn visitProFile(ProFile *pro, ProFileEvaluatorHandler::EvalFileType type, + ProFileEvaluator::LoadFlags flags); + VisitReturn visitProBlock(ProFile *pro, const ushort *tokPtr); + VisitReturn visitProBlock(const ushort *tokPtr); + VisitReturn visitProLoop(const ProString &variable, const ushort *exprPtr, + const ushort *tokPtr); + void visitProFunctionDef(ushort tok, const ProString &name, const ushort *tokPtr); + void visitProVariable(ushort tok, const ProStringList &curr, const ushort *&tokPtr); + + static inline const ProString &map(const ProString &var); + QHash<ProString, ProStringList> *findValues(const ProString &variableName, + QHash<ProString, ProStringList>::Iterator *it); + ProStringList &valuesRef(const ProString &variableName); + ProStringList valuesDirect(const ProString &variableName) const; + ProStringList values(const ProString &variableName) const; + QString propertyValue(const QString &val, bool complain) const; + + ProStringList split_value_list(const QString &vals, const ProFile *source = 0); bool isActiveConfig(const QString &config, bool regex = false); - QStringList expandVariableReferences(const QString &value); - void doVariableReplace(QString *str); - QStringList evaluateExpandFunction(const QString &function, const QString &arguments); - QString format(const char *format) const; + ProStringList expandVariableReferences(const ProString &value, int *pos = 0, bool joined = false); + ProStringList expandVariableReferences(const ushort *&tokPtr, int sizeHint = 0, bool joined = false); + ProStringList evaluateExpandFunction(const ProString &function, const ProString &arguments); + ProStringList evaluateExpandFunction(const ProString &function, const ushort *&tokPtr); + ProStringList evaluateExpandFunction(const ProString &function, const ProStringList &args); + void evalError(const QString &msg) const; QString currentFileName() const; QString currentDirectory() const; ProFile *currentProFile() const; - - ProItem::ProItemReturn evaluateConditionalFunction(const QString &function, const QString &arguments); - bool evaluateFile(const QString &fileName); + QString resolvePath(const QString &fileName) const + { return IoUtils::resolvePath(currentDirectory(), fileName); } + + VisitReturn evaluateConditionalFunction(const ProString &function, const ProString &arguments); + VisitReturn evaluateConditionalFunction(const ProString &function, const ushort *&tokPtr); + VisitReturn evaluateConditionalFunction(const ProString &function, const ProStringList &args); + bool evaluateFileDirect(const QString &fileName, ProFileEvaluatorHandler::EvalFileType type, + ProFileEvaluator::LoadFlags flags); + bool evaluateFile(const QString &fileName, ProFileEvaluatorHandler::EvalFileType type, + ProFileEvaluator::LoadFlags flags); bool evaluateFeatureFile(const QString &fileName); + enum EvalIntoMode { EvalProOnly, EvalWithDefaults, EvalWithSetup }; + bool evaluateFileInto(const QString &fileName, ProFileEvaluatorHandler::EvalFileType type, + QHash<ProString, ProStringList> *values, FunctionDefs *defs, + EvalIntoMode mode); // values are output-only, defs are input-only + + static ALWAYS_INLINE VisitReturn returnBool(bool b) + { return b ? ReturnTrue : ReturnFalse; } + + QList<ProStringList> prepareFunctionArgs(const ushort *&tokPtr); + QList<ProStringList> prepareFunctionArgs(const ProString &arguments); + ProStringList evaluateFunction(const FunctionDef &func, const QList<ProStringList> &argumentsList, bool *ok); + VisitReturn evaluateBoolFunction(const FunctionDef &func, const QList<ProStringList> &argumentsList, + const ProString &function); + + bool modesForGenerator(const QString &gen, + ProFileOption::HOST_MODE *host_mode, ProFileOption::TARG_MODE *target_mode) const; + void validateModes() const; + QStringList qmakeMkspecPaths() const; + QStringList qmakeFeaturePaths() const; + + QString expandEnvVars(const QString &str) const; + QString fixPathToLocalOS(const QString &str) const; + QString sysrootify(const QString &path, const QString &baseDir) const; + +#ifndef QT_BOOTSTRAPPED + void runProcess(QProcess *proc, const QString &command, QProcess::ProcessChannel chan) const; +#endif - static inline ProItem::ProItemReturn returnBool(bool b) - { return b ? ProItem::ReturnTrue : ProItem::ReturnFalse; } - - QStringList evaluateFunction(ProBlock *funcPtr, const QStringList &argumentsList, bool *ok); - - QStringList qmakeFeaturePaths(); - - struct State { - bool condition; - bool prevCondition; - } m_sts; - bool m_invertNext; // Short-lived, so not in State int m_skipLevel; + int m_loopLevel; // To report unexpected break() and next()s +#ifdef PROEVALUATOR_CUMULATIVE bool m_cumulative; - bool m_isFirstVariableValue; - QString m_lastVarName; - ProVariable::VariableOperator m_variableOperator; - QString m_origfile; - QString m_oldPath; // To restore the current path to the path - QStack<ProFile*> m_profileStack; // To handle 'include(a.pri), so we can track back to 'a.pro' when finished with 'a.pri' - struct ProLoop { - QString variable; - QStringList oldVarVal; - QStringList list; - int index; - bool infinite; +#else + enum { m_cumulative = 0 }; +#endif + + struct Location { + Location() : pro(0), line(0) {} + Location(ProFile *_pro, int _line) : pro(_pro), line(_line) {} + ProFile *pro; + int line; }; - QStack<ProLoop> m_loopStack; - // we need the following two variables for handling - // CONFIG = foo bar $$CONFIG - QHash<QString, QStringList> m_tempValuemap; // used while evaluating (variable operator value1 value2 ...) - QHash<const ProFile*, QHash<QString, QStringList> > m_tempFilevaluemap; // used while evaluating (variable operator value1 value2 ...) + Location m_current; // Currently evaluated location + QStack<Location> m_locationStack; // All execution location changes + QStack<ProFile *> m_profileStack; // Includes only - QHash<QString, QStringList> m_valuemap; // VariableName must be us-ascii, the content however can be non-us-ascii. - QHash<const ProFile*, QHash<QString, QStringList> > m_filevaluemap; // Variables per include file - QHash<QString, QString> m_properties; QString m_outputDir; - bool m_definingTest; - QString m_definingFunc; - QHash<QString, ProBlock *> m_testFunctions; - QHash<QString, ProBlock *> m_replaceFunctions; - QStringList m_returnValue; - QStack<QHash<QString, QStringList> > m_valuemapStack; - QStack<QHash<const ProFile*, QHash<QString, QStringList> > > m_filevaluemapStack; + int m_listCount; + FunctionDefs m_functionDefs; + ProStringList m_returnValue; + QStack<QHash<ProString, ProStringList> > m_valuemapStack; // VariableName must be us-ascii, the content however can be non-us-ascii. + QString m_tmp1, m_tmp2, m_tmp3, m_tmp[2]; // Temporaries for efficient toQString + + ProFileOption *m_option; + ProFileParser *m_parser; + ProFileEvaluatorHandler *m_handler; + + enum ExpandFunc { + E_INVALID = 0, E_MEMBER, E_FIRST, E_LAST, E_SIZE, E_CAT, E_FROMFILE, E_EVAL, E_LIST, + E_SPRINTF, E_JOIN, E_SPLIT, E_BASENAME, E_DIRNAME, E_SECTION, + E_FIND, E_SYSTEM, E_UNIQUE, E_QUOTE, E_ESCAPE_EXPAND, + E_UPPER, E_LOWER, E_FILES, E_PROMPT, E_RE_ESCAPE, + E_REPLACE + }; + + enum TestFunc { + T_INVALID = 0, T_REQUIRES, T_GREATERTHAN, T_LESSTHAN, T_EQUALS, + T_EXISTS, T_EXPORT, T_CLEAR, T_UNSET, T_EVAL, T_CONFIG, T_SYSTEM, + T_RETURN, T_BREAK, T_NEXT, T_DEFINED, T_CONTAINS, T_INFILE, + T_COUNT, T_ISEMPTY, T_INCLUDE, T_LOAD, T_DEBUG, T_MESSAGE, T_IF + }; - int m_prevLineNo; // Checking whether we're assigning the same TARGET - ProFile *m_prevProFile; // See m_prevLineNo + enum VarName { + V_LITERAL_DOLLAR, V_LITERAL_HASH, V_LITERAL_WHITESPACE, + V_DIRLIST_SEPARATOR, V_DIR_SEPARATOR, + V_OUT_PWD, V_PWD, V_IN_PWD, + V__FILE_, V__LINE_, V__PRO_FILE_, V__PRO_FILE_PWD_, + V_QMAKE_HOST_arch, V_QMAKE_HOST_name, V_QMAKE_HOST_os, + V_QMAKE_HOST_version, V_QMAKE_HOST_version_string, + V__DATE_, V__QMAKE_CACHE_ + }; }; -Q_DECLARE_TYPEINFO(ProFileEvaluator::Private::State, Q_PRIMITIVE_TYPE); -Q_DECLARE_TYPEINFO(ProFileEvaluator::Private::ProLoop, Q_MOVABLE_TYPE); +static struct { + QString field_sep; + QString strtrue; + QString strfalse; + QString strunix; + QString strmacx; + QString strmac; + QString strwin32; + QString strsymbian; + ProString strCONFIG; + ProString strARGS; + QString strDot; + QString strDotDot; + QString strever; + QString strforever; + ProString strTEMPLATE; + ProString strQMAKE_DIR_SEP; + QHash<ProString, int> expands; + QHash<ProString, int> functions; + QHash<ProString, int> varList; + QHash<ProString, ProString> varMap; + QRegExp reg_variableName; + ProStringList fakeValue; +} statics; + +void ProFileEvaluator::Private::initStatics() +{ + if (!statics.field_sep.isNull()) + return; + + statics.field_sep = QLatin1String(" "); + statics.strtrue = QLatin1String("true"); + statics.strfalse = QLatin1String("false"); + statics.strunix = QLatin1String("unix"); + statics.strmacx = QLatin1String("macx"); + statics.strmac = QLatin1String("mac"); + statics.strwin32 = QLatin1String("win32"); + statics.strsymbian = QLatin1String("symbian"); + statics.strCONFIG = ProString("CONFIG"); + statics.strARGS = ProString("ARGS"); + statics.strDot = QLatin1String("."); + statics.strDotDot = QLatin1String(".."); + statics.strever = QLatin1String("ever"); + statics.strforever = QLatin1String("forever"); + statics.strTEMPLATE = ProString("TEMPLATE"); + statics.strQMAKE_DIR_SEP = ProString("QMAKE_DIR_SEP"); + + statics.reg_variableName.setPattern(QLatin1String("\\$\\(.*\\)")); + statics.reg_variableName.setMinimal(true); + + statics.fakeValue.detach(); // It has to have a unique begin() value + + static const struct { + const char * const name; + const ExpandFunc func; + } expandInits[] = { + { "member", E_MEMBER }, + { "first", E_FIRST }, + { "last", E_LAST }, + { "size", E_SIZE }, + { "cat", E_CAT }, + { "fromfile", E_FROMFILE }, + { "eval", E_EVAL }, + { "list", E_LIST }, + { "sprintf", E_SPRINTF }, + { "join", E_JOIN }, + { "split", E_SPLIT }, + { "basename", E_BASENAME }, + { "dirname", E_DIRNAME }, + { "section", E_SECTION }, + { "find", E_FIND }, + { "system", E_SYSTEM }, + { "unique", E_UNIQUE }, + { "quote", E_QUOTE }, + { "escape_expand", E_ESCAPE_EXPAND }, + { "upper", E_UPPER }, + { "lower", E_LOWER }, + { "re_escape", E_RE_ESCAPE }, + { "files", E_FILES }, + { "prompt", E_PROMPT }, // interactive, so cannot be implemented + { "replace", E_REPLACE } + }; + for (unsigned i = 0; i < sizeof(expandInits)/sizeof(expandInits[0]); ++i) + statics.expands.insert(ProString(expandInits[i].name), expandInits[i].func); + + static const struct { + const char * const name; + const TestFunc func; + } testInits[] = { + { "requires", T_REQUIRES }, + { "greaterThan", T_GREATERTHAN }, + { "lessThan", T_LESSTHAN }, + { "equals", T_EQUALS }, + { "isEqual", T_EQUALS }, + { "exists", T_EXISTS }, + { "export", T_EXPORT }, + { "clear", T_CLEAR }, + { "unset", T_UNSET }, + { "eval", T_EVAL }, + { "CONFIG", T_CONFIG }, + { "if", T_IF }, + { "isActiveConfig", T_CONFIG }, + { "system", T_SYSTEM }, + { "return", T_RETURN }, + { "break", T_BREAK }, + { "next", T_NEXT }, + { "defined", T_DEFINED }, + { "contains", T_CONTAINS }, + { "infile", T_INFILE }, + { "count", T_COUNT }, + { "isEmpty", T_ISEMPTY }, + { "load", T_LOAD }, + { "include", T_INCLUDE }, + { "debug", T_DEBUG }, + { "message", T_MESSAGE }, + { "warning", T_MESSAGE }, + { "error", T_MESSAGE }, + }; + for (unsigned i = 0; i < sizeof(testInits)/sizeof(testInits[0]); ++i) + statics.functions.insert(ProString(testInits[i].name), testInits[i].func); + + static const char * const names[] = { + "LITERAL_DOLLAR", "LITERAL_HASH", "LITERAL_WHITESPACE", + "DIRLIST_SEPARATOR", "DIR_SEPARATOR", + "OUT_PWD", "PWD", "IN_PWD", + "_FILE_", "_LINE_", "_PRO_FILE_", "_PRO_FILE_PWD_", + "QMAKE_HOST.arch", "QMAKE_HOST.name", "QMAKE_HOST.os", + "QMAKE_HOST.version", "QMAKE_HOST.version_string", + "_DATE_", "_QMAKE_CACHE_" + }; + for (unsigned i = 0; i < sizeof(names)/sizeof(names[0]); ++i) + statics.varList.insert(ProString(names[i]), i); + + 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" } + }; + for (unsigned i = 0; i < sizeof(mapInits)/sizeof(mapInits[0]); ++i) + statics.varMap.insert(ProString(mapInits[i].oldname), + ProString(mapInits[i].newname)); +} + +const ProString &ProFileEvaluator::Private::map(const ProString &var) +{ + QHash<ProString, ProString>::ConstIterator it = statics.varMap.constFind(var); + return (it != statics.varMap.constEnd()) ? it.value() : var; +} + -ProFileEvaluator::Private::Private(ProFileEvaluator *q_) - : q(q_) +ProFileEvaluator::Private::Private(ProFileEvaluator *q_, ProFileOption *option, + ProFileParser *parser, ProFileEvaluatorHandler *handler) + : q(q_), m_option(option), m_parser(parser), m_handler(handler) { - // Global parser state - m_prevLineNo = 0; - m_prevProFile = 0; + // So that single-threaded apps don't have to call initialize() for now. + initStatics(); // Configuration, more or less - m_verbose = true; +#ifdef PROEVALUATOR_CUMULATIVE m_cumulative = true; +#endif // Evaluator state - m_sts.condition = false; - m_sts.prevCondition = false; - m_invertNext = false; m_skipLevel = 0; - m_isFirstVariableValue = true; - m_definingFunc.clear(); + m_loopLevel = 0; + m_listCount = 0; + m_valuemapStack.push(QHash<ProString, ProStringList>()); } -bool ProFileEvaluator::Private::read(ProFile *pro) +ProFileEvaluator::Private::~Private() { - QFile file(pro->fileName()); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - q->errorMessage(format("%1 not readable.").arg(pro->fileName())); - return false; - } - - // Parser state - m_block = 0; - m_commentItem = 0; - m_inQuote = false; - m_parens = 0; - m_contNextLine = false; - m_syntaxError = false; - m_lineNo = 1; - m_blockstack.clear(); - m_blockstack.push(pro); - - QTextStream ts(&file); - while (!ts.atEnd()) { - QString line = ts.readLine(); - if (!parseLine(line)) { - q->errorMessage(format(".pro parse failure.")); - return false; - } - ++m_lineNo; - } - return true; } -bool ProFileEvaluator::Private::parseLine(const QString &line0) -{ - if (m_blockstack.isEmpty()) - return false; - - int parens = m_parens; - bool inQuote = m_inQuote; - bool escaped = false; - QString line = line0.simplified(); +//////// Evaluator tools ///////// - for (int i = 0; !m_syntaxError && i < line.length(); ++i) { - ushort c = line.at(i).unicode(); - if (c == '#') { // Yep - no escaping possible - insertComment(line.mid(i + 1)); - escaped = m_contNextLine; - break; - } - if (!escaped) { - if (c == '\\') { - escaped = true; - m_proitem += c; - continue; - } else if (c == '"') { - inQuote = !inQuote; - m_proitem += c; - continue; - } - } else { - escaped = false; - } - if (!inQuote) { - if (c == '(') { - ++parens; - } else if (c == ')') { - --parens; - } else if (!parens) { - if (m_block && (m_block->blockKind() & ProBlock::VariableKind)) { - if (c == ' ') - updateItem(); - else - m_proitem += c; - continue; - } - if (c == ':') { - enterScope(false); - continue; - } - if (c == '{') { - enterScope(true); - continue; - } - if (c == '}') { - leaveScope(); - continue; - } - if (c == '=') { - insertVariable(line, &i); - continue; - } - if (c == '|' || c == '!') { - insertOperator(c); - continue; - } - } - } - - m_proitem += c; - } - m_inQuote = inQuote; - m_parens = parens; - m_contNextLine = escaped; - if (escaped) { - m_proitem.chop(1); - updateItem(); - return true; - } else { - if (!m_syntaxError) { - updateItem(); - finalizeBlock(); - return true; - } - return false; - } +uint ProFileEvaluator::Private::getBlockLen(const ushort *&tokPtr) +{ + uint len = *tokPtr++; + len |= (uint)*tokPtr++ << 16; + return len; } -void ProFileEvaluator::Private::finalizeBlock() +ProString ProFileEvaluator::Private::getStr(const ushort *&tokPtr) { - if (m_blockstack.isEmpty()) { - m_syntaxError = true; - } else { - if (m_blockstack.top()->blockKind() & ProBlock::SingleLine) - leaveScope(); - m_block = 0; - m_commentItem = 0; - } + uint len = *tokPtr++; + ProString ret(m_current.pro->items(), tokPtr - m_current.pro->tokPtr(), len, NoHash); + ret.setSource(m_current.pro); + tokPtr += len; + return ret; } -void ProFileEvaluator::Private::insertVariable(const QString &line, int *i) +ProString ProFileEvaluator::Private::getHashStr(const ushort *&tokPtr) { - ProVariable::VariableOperator opkind; - - if (m_proitem.isEmpty()) // Line starting with '=', like a conflict marker - return; - - switch (m_proitem.at(m_proitem.length() - 1).unicode()) { - case '+': - m_proitem.chop(1); - opkind = ProVariable::AddOperator; - break; - case '-': - m_proitem.chop(1); - opkind = ProVariable::RemoveOperator; - break; - case '*': - m_proitem.chop(1); - opkind = ProVariable::UniqueAddOperator; - break; - case '~': - m_proitem.chop(1); - opkind = ProVariable::ReplaceOperator; - break; - default: - opkind = ProVariable::SetOperator; - } + uint hash = getBlockLen(tokPtr); + uint len = *tokPtr++; + ProString ret(m_current.pro->items(), tokPtr - m_current.pro->tokPtr(), len, hash); + tokPtr += len; + return ret; +} - ProBlock *block = m_blockstack.top(); - m_proitem = m_proitem.trimmed(); - ProVariable *variable = new ProVariable(m_proitem, block); - variable->setLineNumber(m_lineNo); - variable->setVariableOperator(opkind); - block->appendItem(variable); - m_block = variable; - - if (!m_pendingComment.isEmpty()) { - m_block->setComment(m_pendingComment); - m_pendingComment.clear(); - } - m_commentItem = variable; +void ProFileEvaluator::Private::skipStr(const ushort *&tokPtr) +{ + uint len = *tokPtr++; + tokPtr += len; +} - m_proitem.clear(); +void ProFileEvaluator::Private::skipHashStr(const ushort *&tokPtr) +{ + tokPtr += 2; + uint len = *tokPtr++; + tokPtr += len; +} - if (opkind == ProVariable::ReplaceOperator) { - // skip util end of line or comment - while (1) { - ++(*i); +// 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 ProFileEvaluator::Private::split_value_list(const QString &vals, const ProFile *source) +{ + QString build; + ProStringList ret; + QStack<char> quote; - // end of line? - if (*i >= line.count()) - break; + const ushort SPACE = ' '; + const ushort LPAREN = '('; + const ushort RPAREN = ')'; + const ushort SINGLEQUOTE = '\''; + const ushort DOUBLEQUOTE = '"'; + const ushort BACKSLASH = '\\'; - // comment? - if (line.at(*i).unicode() == '#') { - --(*i); - break; - } + if (!source) + source = currentProFile(); + + ushort unicode; + const QChar *vals_data = vals.data(); + const int vals_len = vals.length(); + for (int x = 0, parens = 0; x < vals_len; x++) { + unicode = vals_data[x].unicode(); + if (x != (int)vals_len-1 && unicode == BACKSLASH && + (vals_data[x+1].unicode() == SINGLEQUOTE || vals_data[x+1].unicode() == DOUBLEQUOTE)) { + build += vals_data[x++]; //get that 'escape' + } else if (!quote.isEmpty() && unicode == quote.top()) { + quote.pop(); + } else if (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE) { + quote.push(unicode); + } else if (unicode == RPAREN) { + --parens; + } else if (unicode == LPAREN) { + ++parens; + } - m_proitem += line.at(*i); + if (!parens && quote.isEmpty() && vals_data[x] == SPACE) { + ret << ProString(build, NoHash).setSource(source); + build.clear(); + } else { + build += vals_data[x]; } - m_proitem = m_proitem.trimmed(); } + if (!build.isEmpty()) + ret << ProString(build, NoHash).setSource(source); + return ret; } -void ProFileEvaluator::Private::insertOperator(const char op) +static void zipEmpty(ProStringList *value) { - updateItem(); - - ProOperator::OperatorKind opkind; - switch(op) { - case '!': - opkind = ProOperator::NotOperator; - break; - case '|': - opkind = ProOperator::OrOperator; - break; - default: - opkind = ProOperator::OrOperator; - } - - ProBlock * const block = currentBlock(); - ProOperator * const proOp = new ProOperator(opkind); - proOp->setLineNumber(m_lineNo); - block->appendItem(proOp); - m_commentItem = proOp; + for (int i = value->size(); --i >= 0;) + if (value->at(i).isEmpty()) + value->remove(i); } -void ProFileEvaluator::Private::insertComment(const QString &comment) +static void insertUnique(ProStringList *varlist, const ProStringList &value) { - updateItem(); - - QString strComment; - if (!m_commentItem) - strComment = m_pendingComment; - else - strComment = m_commentItem->comment(); - - if (strComment.isEmpty()) - strComment = comment; - else { - strComment += QLatin1Char('\n'); - strComment += comment.trimmed(); - } - - strComment = strComment.trimmed(); - - if (!m_commentItem) - m_pendingComment = strComment; - else - m_commentItem->setComment(strComment); + foreach (const ProString &str, value) + if (!str.isEmpty() && !varlist->contains(str)) + varlist->append(str); } -void ProFileEvaluator::Private::enterScope(bool multiLine) +static void removeAll(ProStringList *varlist, const ProString &value) { - updateItem(); - - ProBlock *parent = currentBlock(); - ProBlock *block = new ProBlock(parent); - block->setLineNumber(m_lineNo); - parent->setBlockKind(ProBlock::ScopeKind); - - parent->appendItem(block); - - if (multiLine) - block->setBlockKind(ProBlock::ScopeContentsKind); - else - block->setBlockKind(ProBlock::ScopeContentsKind|ProBlock::SingleLine); - - m_blockstack.push(block); - m_block = 0; + for (int i = varlist->size(); --i >= 0; ) + if (varlist->at(i) == value) + varlist->remove(i); } -void ProFileEvaluator::Private::leaveScope() +static void removeEach(ProStringList *varlist, const ProStringList &value) { - updateItem(); - m_blockstack.pop(); - finalizeBlock(); + foreach (const ProString &str, value) + if (!str.isEmpty()) + removeAll(varlist, str); } -ProBlock *ProFileEvaluator::Private::currentBlock() +static void replaceInList(ProStringList *varlist, + const QRegExp ®exp, const QString &replace, bool global, QString &tmp) { - if (m_block) - return m_block; - - ProBlock *parent = m_blockstack.top(); - m_block = new ProBlock(parent); - m_block->setLineNumber(m_lineNo); - parent->appendItem(m_block); - - if (!m_pendingComment.isEmpty()) { - m_block->setComment(m_pendingComment); - m_pendingComment.clear(); + for (ProStringList::Iterator varit = varlist->begin(); varit != varlist->end(); ) { + QString val = varit->toQString(tmp); + QString copy = val; // Force detach and have a reference value + val.replace(regexp, replace); + if (!val.isSharedWith(copy)) { + if (val.isEmpty()) { + varit = varlist->erase(varit); + } else { + (*varit).setValue(val, NoHash); + ++varit; + } + if (!global) + break; + } else { + ++varit; + } } +} - m_commentItem = m_block; - - return m_block; +QString ProFileEvaluator::Private::expandEnvVars(const QString &str) const +{ + QString string = str; + int rep; + QRegExp reg_variableName = statics.reg_variableName; // Copy for thread safety + while ((rep = reg_variableName.indexIn(string)) != -1) + string.replace(rep, reg_variableName.matchedLength(), + m_option->getEnv(string.mid(rep + 2, reg_variableName.matchedLength() - 3))); + return string; } -void ProFileEvaluator::Private::updateItem() +// This is braindead, but we want qmake compat +QString ProFileEvaluator::Private::fixPathToLocalOS(const QString &str) const { - m_proitem = m_proitem.trimmed(); - if (m_proitem.isEmpty()) - return; + QString string = expandEnvVars(str); - ProBlock *block = currentBlock(); - if (block->blockKind() & ProBlock::VariableKind) { - m_commentItem = new ProValue(m_proitem, static_cast<ProVariable*>(block)); - } else if (m_proitem.endsWith(QLatin1Char(')'))) { - m_commentItem = new ProFunction(m_proitem); - } else { - m_commentItem = new ProCondition(m_proitem); - } - m_commentItem->setLineNumber(m_lineNo); - block->appendItem(m_commentItem); + if (string.length() > 2 && string.at(0).isLetter() && string.at(1) == QLatin1Char(':')) + string[0] = string[0].toLower(); - m_proitem.clear(); +#if defined(Q_OS_WIN32) + string.replace(QLatin1Char('/'), QLatin1Char('\\')); +#else + string.replace(QLatin1Char('\\'), QLatin1Char('/')); +#endif + return string; +} + +static bool isTrue(const ProString &_str, QString &tmp) +{ + const QString &str = _str.toQString(tmp); + return !str.compare(statics.strtrue, Qt::CaseInsensitive) || str.toInt(); } +//////// Evaluator ///////// -ProItem::ProItemReturn ProFileEvaluator::Private::visitBeginProBlock(ProBlock *block) +static ALWAYS_INLINE void addStr( + const ProString &str, ProStringList *ret, bool &pending, bool joined) { - if (block->blockKind() & ProBlock::ScopeContentsKind) { - if (!m_definingFunc.isEmpty()) { - if (!m_skipLevel || m_cumulative) { - QHash<QString, ProBlock *> *hash = - (m_definingTest ? &m_testFunctions : &m_replaceFunctions); - if (ProBlock *def = hash->value(m_definingFunc)) - def->deref(); - hash->insert(m_definingFunc, block); - block->ref(); - block->setBlockKind(block->blockKind() | ProBlock::FunctionBodyKind); - } - m_definingFunc.clear(); - return ProItem::ReturnSkip; - } else if (!(block->blockKind() & ProBlock::FunctionBodyKind)) { - if (!m_sts.condition) - ++m_skipLevel; - else - Q_ASSERT(!m_skipLevel); - } + if (joined) { + ret->last().append(str, &pending); } else { - if (!m_skipLevel) { - if (m_sts.condition) { - m_sts.prevCondition = true; - m_sts.condition = false; - } + if (!pending) { + pending = true; + *ret << str; } else { - Q_ASSERT(!m_sts.condition); + ret->last().append(str); } } - return ProItem::ReturnTrue; } -void ProFileEvaluator::Private::visitEndProBlock(ProBlock *block) +static ALWAYS_INLINE void addStrList( + const ProStringList &list, ushort tok, ProStringList *ret, bool &pending, bool joined) { - if ((block->blockKind() & ProBlock::ScopeContentsKind) - && !(block->blockKind() & ProBlock::FunctionBodyKind)) { - if (m_skipLevel) { - Q_ASSERT(!m_sts.condition); - --m_skipLevel; - } else if (!(block->blockKind() & ProBlock::SingleLine)) { - // Conditionals contained inside this block may have changed the state. - // So we reset it here to make an else following us do the right thing. - m_sts.condition = true; + 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); + } + } } } } -ProItem::ProItemReturn ProFileEvaluator::Private::visitProLoopIteration() -{ - ProLoop &loop = m_loopStack.top(); - - if (loop.infinite) { - if (!loop.variable.isEmpty()) - m_valuemap[loop.variable] = QStringList(QString::number(loop.index++)); - if (loop.index > 1000) { - q->errorMessage(format("ran into infinite loop (> 1000 iterations).")); - return ProItem::ReturnFalse; +void ProFileEvaluator::Private::evaluateExpression( + const ushort *&tokPtr, ProStringList *ret, bool joined) +{ + if (joined) + *ret << ProString(); + bool pending = false; + forever { + ushort tok = *tokPtr++; + if (tok & TokNewStr) + pending = false; + ushort maskedTok = tok & TokMask; + switch (maskedTok) { + case TokLine: + m_current.line = *tokPtr++; + break; + case TokLiteral: + addStr(getStr(tokPtr), ret, pending, joined); + break; + case TokHashLiteral: + addStr(getHashStr(tokPtr), ret, pending, joined); + break; + case TokVariable: + addStrList(values(map(getHashStr(tokPtr))), tok, ret, pending, joined); + break; + case TokProperty: + addStr(ProString(propertyValue( + getStr(tokPtr).toQString(m_tmp1), true), NoHash).setSource(currentProFile()), + ret, pending, joined); + break; + case TokEnvVar: + addStrList(split_value_list(m_option->getEnv(getStr(tokPtr).toQString(m_tmp1))), + tok, ret, pending, joined); + break; + case TokFuncName: { + ProString func = getHashStr(tokPtr); + addStrList(evaluateExpandFunction(func, tokPtr), tok, ret, pending, joined); + break; } + default: + tokPtr--; + return; } - } else { - QString val; - do { - if (loop.index >= loop.list.count()) - return ProItem::ReturnFalse; - val = loop.list.at(loop.index++); - } while (val.isEmpty()); // stupid, but qmake is like that - m_valuemap[loop.variable] = QStringList(val); } - return ProItem::ReturnTrue; -} - -void ProFileEvaluator::Private::visitProLoopCleanup() -{ - ProLoop &loop = m_loopStack.top(); - m_valuemap[loop.variable] = loop.oldVarVal; - m_loopStack.pop_back(); } -void ProFileEvaluator::Private::visitBeginProVariable(ProVariable *variable) +void ProFileEvaluator::Private::skipExpression(const ushort *&pTokPtr) { - m_lastVarName = variable->variable(); - m_variableOperator = variable->variableOperator(); - m_isFirstVariableValue = true; - m_tempValuemap = m_valuemap; - m_tempFilevaluemap = m_filevaluemap; + 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 TokProperty: + case TokEnvVar: + skipStr(tokPtr); + break; + case TokHashLiteral: + case TokVariable: + skipHashStr(tokPtr); + break; + case TokFuncName: + skipHashStr(tokPtr); + pTokPtr = tokPtr; + skipExpression(pTokPtr); + tokPtr = pTokPtr; + break; + default: + Q_ASSERT_X(false, "skipExpression", "Unrecognized token"); + break; + } + } + } } -void ProFileEvaluator::Private::visitEndProVariable(ProVariable *variable) -{ - Q_UNUSED(variable); - m_valuemap = m_tempValuemap; - m_filevaluemap = m_tempFilevaluemap; - m_lastVarName.clear(); +ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProBlock( + ProFile *pro, const ushort *tokPtr) +{ + m_current.pro = pro; + m_current.line = 0; + return visitProBlock(tokPtr); +} + +ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProBlock( + const ushort *tokPtr) +{ + ProStringList curr; + bool okey = true, or_op = false, invert = false; + uint blockLen; + VisitReturn ret = ReturnTrue; + while (ushort tok = *tokPtr++) { + switch (tok) { + case TokLine: + m_current.line = *tokPtr++; + continue; + case TokAssign: + case TokAppend: + case TokAppendUnique: + case TokRemove: + case TokReplace: + visitProVariable(tok, curr, tokPtr); + curr.clear(); + continue; + case TokBranch: + blockLen = getBlockLen(tokPtr); + if (m_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--; + } else { + if (okey) + ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue; + tokPtr += blockLen; + blockLen = getBlockLen(tokPtr); + if (!okey) + ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue; + } + tokPtr += blockLen; + okey = true, or_op = false; // force next evaluation + break; + case TokForLoop: + if (m_cumulative) { // This is a no-win situation, so just pretend it's no loop + skipHashStr(tokPtr); + uint exprLen = getBlockLen(tokPtr); + tokPtr += exprLen; + blockLen = getBlockLen(tokPtr); + ret = visitProBlock(tokPtr); + } else if (okey != or_op) { + const ProString &variable = 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); + ret = ReturnTrue; + } + tokPtr += blockLen; + okey = true, or_op = false; // force next evaluation + break; + case TokTestDef: + case TokReplaceDef: + if (m_cumulative || okey != or_op) { + const ProString &name = getHashStr(tokPtr); + blockLen = getBlockLen(tokPtr); + visitProFunctionDef(tok, name, tokPtr); + } else { + skipHashStr(tokPtr); + blockLen = getBlockLen(tokPtr); + } + tokPtr += blockLen; + okey = true, or_op = false; // force next evaluation + continue; + case TokNot: + invert ^= true; + continue; + case TokAnd: + or_op = false; + continue; + case TokOr: + 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).toQString(m_tmp2), true) ^ invert; + } + } + 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 { + ret = evaluateConditionalFunction(curr.at(0), tokPtr); + switch (ret) { + case ReturnTrue: okey = true; break; + case ReturnFalse: okey = false; break; + default: return ret; + } + okey ^= invert; + } + } else if (m_cumulative) { + m_skipLevel++; + if (curr.size() != 1) + skipExpression(tokPtr); + else + evaluateConditionalFunction(curr.at(0), tokPtr); + m_skipLevel--; + } else { + skipExpression(tokPtr); + } + or_op = !okey; // tentatively force next evaluation + invert = false; + curr.clear(); + continue; + default: { + const ushort *oTokPtr = --tokPtr; + evaluateExpression(tokPtr, &curr, false); + if (tokPtr != oTokPtr) + continue; + } + Q_ASSERT_X(false, "visitProBlock", "unexpected item type"); + } + if (ret != ReturnTrue && ret != ReturnFalse) + break; + } + return ret; } -void ProFileEvaluator::Private::visitProOperator(ProOperator *oper) -{ - m_invertNext = (oper->operatorKind() == ProOperator::NotOperator); -} -void ProFileEvaluator::Private::visitProCondition(ProCondition *cond) +void ProFileEvaluator::Private::visitProFunctionDef( + ushort tok, const ProString &name, const ushort *tokPtr) { - if (!m_skipLevel) { - if (!cond->text().compare(QLatin1String("else"), Qt::CaseInsensitive)) { - m_sts.condition = !m_sts.prevCondition; - } else { - m_sts.prevCondition = false; - if (!m_sts.condition && isActiveConfig(cond->text(), true) ^ m_invertNext) - m_sts.condition = true; - } - } - m_invertNext = false; + QHash<ProString, FunctionDef> *hash = + (tok == TokTestDef + ? &m_functionDefs.testFunctions + : &m_functionDefs.replaceFunctions); + hash->insert(name, FunctionDef(m_current.pro, tokPtr - m_current.pro->tokPtr())); } -ProItem::ProItemReturn ProFileEvaluator::Private::visitBeginProFile(ProFile * pro) +ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProLoop( + const ProString &_variable, const ushort *exprPtr, const ushort *tokPtr) { - PRE(pro); - m_lineNo = pro->lineNumber(); - if (m_origfile.isEmpty()) - m_origfile = pro->fileName(); - if (m_oldPath.isEmpty()) { - // change the working directory for the initial profile we visit, since - // that is *the* profile. All the other times we reach this function will be due to - // include(file) or load(file) - - m_oldPath = QDir::currentPath(); - - m_profileStack.push(pro); - - const QString mkspecDirectory = propertyValue(QLatin1String("QMAKE_MKSPECS")); - if (!mkspecDirectory.isEmpty()) { - bool cumulative = m_cumulative; - m_cumulative = false; - // This is what qmake does, everything set in the mkspec is also set - // But this also creates a lot of problems - evaluateFile(mkspecDirectory + QLatin1String("/default/qmake.conf")); - evaluateFile(mkspecDirectory + QLatin1String("/features/default_pre.prf")); - m_cumulative = cumulative; + VisitReturn ret = ReturnTrue; + bool infinite = false; + int index = 0; + ProString variable; + ProStringList oldVarVal; + ProString it_list = expandVariableReferences(exprPtr, 0, true).at(0); + if (_variable.isEmpty()) { + if (it_list != statics.strever) { + evalError(fL1S("Invalid loop expression.")); + return ReturnFalse; } - - return returnBool(QDir::setCurrent(pro->directoryName())); + it_list = ProString(statics.strforever); + } else { + variable = map(_variable); + oldVarVal = valuesDirect(variable); } - - return ProItem::ReturnTrue; -} - -ProItem::ProItemReturn ProFileEvaluator::Private::visitEndProFile(ProFile * pro) -{ - PRE(pro); - m_lineNo = pro->lineNumber(); - if (m_profileStack.count() == 1 && !m_oldPath.isEmpty()) { - const QString &mkspecDirectory = propertyValue(QLatin1String("QMAKE_MKSPECS")); - if (!mkspecDirectory.isEmpty()) { - bool cumulative = m_cumulative; - m_cumulative = false; - - evaluateFile(mkspecDirectory + QLatin1String("/features/default_post.prf")); - - QSet<QString> processed; - forever { - bool finished = true; - QStringList configs = valuesDirect(QLatin1String("CONFIG")); - for (int i = configs.size() - 1; i >= 0; --i) { - const QString config = configs[i].toLower(); - if (!processed.contains(config)) { - processed.insert(config); - if (evaluateFile(mkspecDirectory + QLatin1String("/features/") - + config + QLatin1String(".prf"))) { - finished = false; - break; + ProStringList list = valuesDirect(it_list); + if (list.isEmpty()) { + if (it_list == statics.strforever) { + infinite = true; + } else { + const QString &itl = it_list.toQString(m_tmp1); + 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) { + if (start < end) { + for (int i = start; i <= end; i++) + list << ProString(QString::number(i), NoHash); + } else { + for (int i = start; i >= end; i--) + list << ProString(QString::number(i), NoHash); } } } - if (finished) - break; } - - foreach (ProBlock *itm, m_replaceFunctions) - itm->deref(); - m_replaceFunctions.clear(); - foreach (ProBlock *itm, m_testFunctions) - itm->deref(); - m_testFunctions.clear(); - - m_cumulative = cumulative; } - - m_profileStack.pop(); - return returnBool(QDir::setCurrent(m_oldPath)); } - return ProItem::ReturnTrue; -} - -static void replaceInList(QStringList *varlist, - const QRegExp ®exp, const QString &replace, bool global) -{ - for (QStringList::Iterator varit = varlist->begin(); varit != varlist->end(); ) { - if ((*varit).contains(regexp)) { - (*varit).replace(regexp, replace); - if ((*varit).isEmpty()) - varit = varlist->erase(varit); - else - ++varit; - if(!global) + m_loopLevel++; + forever { + if (infinite) { + if (!variable.isEmpty()) + m_valuemapStack.top()[variable] = ProStringList(ProString(QString::number(index++), NoHash)); + if (index > 1000) { + evalError(fL1S("ran into infinite loop (> 1000 iterations).")); break; + } } else { - ++varit; + ProString val; + do { + if (index >= list.count()) + goto do_break; + val = list.at(index++); + } while (val.isEmpty()); // stupid, but qmake is like that + 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: + m_loopLevel--; + + if (!variable.isEmpty()) + m_valuemapStack.top()[variable] = oldVarVal; + return ret; } -void ProFileEvaluator::Private::visitProValue(ProValue *value) +void ProFileEvaluator::Private::visitProVariable( + ushort tok, const ProStringList &curr, const ushort *&tokPtr) { - PRE(value); - m_lineNo = value->lineNumber(); - QString val = value->value(); + int sizeHint = *tokPtr++; - QString varName = m_lastVarName; + 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; + } + const ProString &varName = map(curr.first()); - QStringList v = expandVariableReferences(val); + if (tok == TokReplace) { // ~= + // DEFINES ~= s/a/b/?[gqi] - // Since qmake combines different values for the TARGET variable, we join - // TARGET values that are on the same line. We can't do this later with all - // values because this parser isn't scope-aware, so we'd risk joining - // scope-specific targets together. - if (varName == QLatin1String("TARGET") - && m_lineNo == m_prevLineNo - && currentProFile() == m_prevProFile) { - QStringList targets = m_tempValuemap.value(QLatin1String("TARGET")); - m_tempValuemap.remove(QLatin1String("TARGET")); - QStringList lastTarget(targets.takeLast()); - lastTarget << v.join(QLatin1String(" ")); - targets.push_back(lastTarget.join(QLatin1String(" "))); - v = targets; - } - m_prevLineNo = m_lineNo; - m_prevProFile = currentProFile(); + const ProStringList &varVal = expandVariableReferences(tokPtr, sizeHint, true); + const QString &val = varVal.at(0).toQString(m_tmp1); + if (val.length() < 4 || val.at(0) != QLatin1Char('s')) { + evalError(fL1S("the ~= operator can handle only the s/// function.")); + return; + } + QChar sep = val.at(1); + QStringList func = val.split(sep); + if (func.count() < 3 || func.count() > 4) { + evalError(fL1S("the s/// function expects 3 or 4 arguments.")); + return; + } + + 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]; + QString replace = func[2]; + if (quote) + pattern = QRegExp::escape(pattern); + + QRegExp regexp(pattern, case_sense ? Qt::CaseSensitive : Qt::CaseInsensitive); - switch (m_variableOperator) { - case ProVariable::SetOperator: // = + if (!m_skipLevel || m_cumulative) { + // 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); + } + } else { + ProStringList varVal = expandVariableReferences(tokPtr, sizeHint); + switch (tok) { + default: // whatever - cannot happen + case TokAssign: // = if (!m_cumulative) { if (!m_skipLevel) { - if (m_isFirstVariableValue) { - m_tempValuemap[varName] = v; - m_tempFilevaluemap[currentProFile()][varName] = v; - } else { // handle lines "CONFIG = foo bar" - m_tempValuemap[varName] += v; - m_tempFilevaluemap[currentProFile()][varName] += v; - } + zipEmpty(&varVal); + m_valuemapStack.top()[varName] = varVal; } } else { - // We are greedy for values. - m_tempValuemap[varName] += v; - m_tempFilevaluemap[currentProFile()][varName] += v; + zipEmpty(&varVal); + if (!varVal.isEmpty()) { + // We are greedy for values. But avoid exponential growth. + ProStringList &v = valuesRef(varName); + if (v.isEmpty()) { + v = varVal; + } else { + ProStringList old = v; + v = varVal; + QSet<ProString> has; + has.reserve(v.size()); + foreach (const ProString &s, v) + has.insert(s); + v.reserve(v.size() + old.size()); + foreach (const ProString &s, old) + if (!has.contains(s)) + v << s; + } + } } break; - case ProVariable::UniqueAddOperator: // *= - if (!m_skipLevel || m_cumulative) { - insertUnique(&m_tempValuemap, varName, v); - insertUnique(&m_tempFilevaluemap[currentProFile()], varName, v); - } + case TokAppendUnique: // *= + if (!m_skipLevel || m_cumulative) + insertUnique(&valuesRef(varName), varVal); break; - case ProVariable::AddOperator: // += + case TokAppend: // += if (!m_skipLevel || m_cumulative) { - m_tempValuemap[varName] += v; - m_tempFilevaluemap[currentProFile()][varName] += v; + zipEmpty(&varVal); + valuesRef(varName) += varVal; } break; - case ProVariable::RemoveOperator: // -= + case TokRemove: // -= if (!m_cumulative) { - if (!m_skipLevel) { - removeEach(&m_tempValuemap, varName, v); - removeEach(&m_tempFilevaluemap[currentProFile()], varName, v); - } + if (!m_skipLevel) + removeEach(&valuesRef(varName), varVal); } else { // We are stingy with our values, too. } break; - case ProVariable::ReplaceOperator: // ~= - { - // DEFINES ~= s/a/b/?[gqi] + } + } +} - doVariableReplace(&val); - if (val.length() < 4 || val[0] != QLatin1Char('s')) { - q->logMessage(format("the ~= operator can handle only the s/// function.")); - break; +void ProFileEvaluator::Private::visitCmdLine(const QString &cmds) +{ + if (!cmds.isEmpty()) { + if (ProFile *pro = m_parser->parsedProBlock(fL1S("(command line)"), cmds)) { + m_locationStack.push(m_current); + visitProBlock(pro, pro->tokPtr()); + m_current = m_locationStack.pop(); + pro->deref(); + } + } +} + +ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProFile( + ProFile *pro, ProFileEvaluatorHandler::EvalFileType type, + ProFileEvaluator::LoadFlags flags) +{ + if (!m_cumulative && !pro->isOk()) + return ReturnFalse; + + m_handler->aboutToEval(currentProFile(), pro, type); + m_profileStack.push(pro); + if (flags & LoadPreFiles) { +#ifdef PROEVALUATOR_THREAD_SAFE + { + QMutexLocker locker(&m_option->mutex); + if (m_option->base_inProgress) { + QThreadPool::globalInstance()->releaseThread(); + m_option->cond.wait(&m_option->mutex); + QThreadPool::globalInstance()->reserveThread(); + } else +#endif + if (m_option->base_valuemap.isEmpty()) { +#ifdef PROEVALUATOR_THREAD_SAFE + m_option->base_inProgress = true; + locker.unlock(); +#endif + +#ifdef PROEVALUATOR_CUMULATIVE + bool cumulative = m_cumulative; + m_cumulative = false; +#endif + + // ### init QMAKE_QMAKE, QMAKE_SH + // ### init QMAKE_EXT_{C,H,CPP,OBJ} + // ### init TEMPLATE_PREFIX + + QString qmake_cache = m_option->cachefile; + if (qmake_cache.isEmpty() && !m_outputDir.isEmpty()) { //find it as it has not been specified + QDir dir(m_outputDir); + forever { + qmake_cache = dir.path() + QLatin1String("/.qmake.cache"); + if (IoUtils::exists(qmake_cache)) + break; + if (!dir.cdUp() || dir.isRoot()) { + qmake_cache.clear(); + break; + } + } } - QChar sep = val.at(1); - QStringList func = val.split(sep); - if (func.count() < 3 || func.count() > 4) { - q->logMessage(format("the s/// function expects 3 or 4 arguments.")); - break; + if (!qmake_cache.isEmpty()) { + qmake_cache = resolvePath(qmake_cache); + QHash<ProString, ProStringList> cache_valuemap; + if (evaluateFileInto(qmake_cache, ProFileEvaluatorHandler::EvalConfigFile, + &cache_valuemap, 0, EvalProOnly)) { + if (m_option->qmakespec.isEmpty()) { + const ProStringList &vals = cache_valuemap.value(ProString("QMAKESPEC")); + if (!vals.isEmpty()) + m_option->qmakespec = vals.first().toQString(); + } + } else { + qmake_cache.clear(); + } } + m_option->cachefile = qmake_cache; + + QStringList mkspec_roots = qmakeMkspecPaths(); - 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 qmakespec = expandEnvVars(m_option->qmakespec); + if (qmakespec.isEmpty()) { + foreach (const QString &root, mkspec_roots) { + QString mkspec = root + QLatin1String("/default"); + if (IoUtils::fileType(mkspec) == IoUtils::FileIsDir) { + qmakespec = mkspec; + break; + } + } + if (qmakespec.isEmpty()) { + m_handler->configError(fL1S("Could not find qmake configuration directory")); + // Unlike in qmake, not finding the spec is not critical ... + } } - QString pattern = func[1]; - QString replace = func[2]; - if (quote) - pattern = QRegExp::escape(pattern); - - QRegExp regexp(pattern, case_sense ? Qt::CaseSensitive : Qt::CaseInsensitive); - - if (!m_skipLevel || m_cumulative) { - // 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(&m_tempValuemap[varName], regexp, replace, global); - replaceInList(&m_tempFilevaluemap[currentProFile()][varName], regexp, replace, global); + + if (IoUtils::isRelativePath(qmakespec)) { + if (IoUtils::exists(currentDirectory() + QLatin1Char('/') + qmakespec + + QLatin1String("/qmake.conf"))) { + qmakespec = currentDirectory() + QLatin1Char('/') + qmakespec; + } else if (!m_outputDir.isEmpty() + && IoUtils::exists(m_outputDir + QLatin1Char('/') + qmakespec + + QLatin1String("/qmake.conf"))) { + qmakespec = m_outputDir + QLatin1Char('/') + qmakespec; + } else { + foreach (const QString &root, mkspec_roots) { + QString mkspec = root + QLatin1Char('/') + qmakespec; + if (IoUtils::exists(mkspec)) { + qmakespec = mkspec; + goto cool; + } + } + m_handler->configError(fL1S("Could not find qmake configuration file")); + // Unlike in qmake, a missing config is not critical ... + qmakespec.clear(); + cool: ; + } } + + if (!qmakespec.isEmpty()) { + m_option->qmakespec = QDir::cleanPath(qmakespec); + + QString spec = m_option->qmakespec + QLatin1String("/qmake.conf"); + if (!evaluateFileDirect(spec, ProFileEvaluatorHandler::EvalConfigFile, + ProFileEvaluator::LoadProOnly)) { + m_handler->configError( + fL1S("Could not read qmake configuration file %1").arg(spec)); + } else if (!m_option->cachefile.isEmpty()) { + evaluateFileDirect(m_option->cachefile, + ProFileEvaluatorHandler::EvalConfigFile, + ProFileEvaluator::LoadProOnly); + } + m_option->qmakespec_name = IoUtils::fileName(m_option->qmakespec).toString(); + if (m_option->qmakespec_name == QLatin1String("default")) { +#ifdef Q_OS_UNIX + char buffer[1024]; + int l = ::readlink(m_option->qmakespec.toLocal8Bit().constData(), buffer, 1024); + if (l != -1) + m_option->qmakespec_name = + IoUtils::fileName(QString::fromLocal8Bit(buffer, l)).toString(); +#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_ORG variable. + const ProStringList &spec_org = + m_option->base_valuemap.value(ProString("QMAKESPEC_ORIGINAL")); + if (!spec_org.isEmpty()) + m_option->qmakespec_name = + IoUtils::fileName(spec_org.first().toQString()).toString(); +#endif + } + } + + evaluateFeatureFile(QLatin1String("default_pre.prf")); + + m_option->base_valuemap = m_valuemapStack.top(); + m_option->base_functions = m_functionDefs; + +#ifdef PROEVALUATOR_CUMULATIVE + m_cumulative = cumulative; +#endif + +#ifdef PROEVALUATOR_THREAD_SAFE + locker.relock(); + m_option->base_inProgress = false; + m_option->cond.wakeAll(); +#endif + goto fresh; } - break; +#ifdef PROEVALUATOR_THREAD_SAFE + } +#endif + + m_valuemapStack.top() = m_option->base_valuemap; + m_functionDefs = m_option->base_functions; + fresh: + ProStringList &tgt = m_valuemapStack.top()[ProString("TARGET")]; + if (tgt.isEmpty()) + tgt.append(ProString(QFileInfo(pro->fileName()).baseName(), NoHash)); + + visitCmdLine(m_option->precmds); } - m_isFirstVariableValue = false; -} -ProItem::ProItemReturn ProFileEvaluator::Private::visitProFunction(ProFunction *func) -{ - // Make sure that called subblocks don't inherit & destroy the state - bool invertThis = m_invertNext; - m_invertNext = false; - if (!m_skipLevel) - m_sts.prevCondition = false; - if (m_cumulative || !m_sts.condition) { - QString text = func->text(); - int lparen = text.indexOf(QLatin1Char('(')); - int rparen = text.lastIndexOf(QLatin1Char(')')); - Q_ASSERT(lparen < rparen); - QString arguments = text.mid(lparen + 1, rparen - lparen - 1); - QString funcName = text.left(lparen); - m_lineNo = func->lineNumber(); - ProItem::ProItemReturn result = evaluateConditionalFunction(funcName.trimmed(), arguments); - if (result != ProItem::ReturnFalse && result != ProItem::ReturnTrue) - return result; - if (!m_skipLevel && ((result == ProItem::ReturnTrue) ^ invertThis)) - m_sts.condition = true; + visitProBlock(pro, pro->tokPtr()); + + if (flags & LoadPostFiles) { + visitCmdLine(m_option->postcmds); + + evaluateFeatureFile(QLatin1String("default_post.prf")); + + QSet<QString> processed; + forever { + bool finished = true; + ProStringList configs = valuesDirect(statics.strCONFIG); + for (int i = configs.size() - 1; i >= 0; --i) { + QString config = configs.at(i).toQString(m_tmp1).toLower(); + if (!processed.contains(config)) { + config.detach(); + processed.insert(config); + if (evaluateFeatureFile(config)) { + finished = false; + break; + } + } + } + if (finished) + break; + } } - return ProItem::ReturnTrue; + m_profileStack.pop(); + m_handler->doneWithEval(currentProFile()); + + return ReturnTrue; } -QStringList ProFileEvaluator::Private::qmakeFeaturePaths() +QStringList ProFileEvaluator::Private::qmakeMkspecPaths() const +{ + QStringList ret; + const QString concat = QLatin1String("/mkspecs"); + + QString qmakepath = m_option->getEnv(QLatin1String("QMAKEPATH")); + if (!qmakepath.isEmpty()) + foreach (const QString &it, qmakepath.split(m_option->dirlist_sep)) + ret << QDir::cleanPath(it) + concat; + + QString builtIn = propertyValue(QLatin1String("QT_INSTALL_DATA"), false) + concat; + if (!ret.contains(builtIn)) + ret << builtIn; + + return ret; +} + +QStringList ProFileEvaluator::Private::qmakeFeaturePaths() const { + QString mkspecs_concat = QLatin1String("/mkspecs"); + QString features_concat = QLatin1String("/features"); QStringList concat; - { - const QString base_concat = QDir::separator() + QLatin1String("features"); - concat << base_concat + QDir::separator() + QLatin1String("mac"); - concat << base_concat + QDir::separator() + QLatin1String("macx"); - concat << base_concat + QDir::separator() + QLatin1String("unix"); - concat << base_concat + QDir::separator() + QLatin1String("win32"); - concat << base_concat + QDir::separator() + QLatin1String("mac9"); - concat << base_concat + QDir::separator() + QLatin1String("qnx6"); - concat << base_concat; + + validateModes(); + switch (m_option->target_mode) { + case ProFileOption::TARG_MACX_MODE: + concat << QLatin1String("/features/mac"); + concat << QLatin1String("/features/macx"); + concat << QLatin1String("/features/unix"); + break; + default: // Can't happen, just make the compiler shut up + case ProFileOption::TARG_UNIX_MODE: + concat << QLatin1String("/features/unix"); + break; + case ProFileOption::TARG_WIN_MODE: + concat << QLatin1String("/features/win32"); + break; + case ProFileOption::TARG_SYMBIAN_MODE: + concat << QLatin1String("/features/symbian"); + break; } - const QString mkspecs_concat = QDir::separator() + QLatin1String("mkspecs"); + concat << features_concat; + QStringList feature_roots; - QByteArray mkspec_path = qgetenv("QMAKEFEATURES"); - if (!mkspec_path.isNull()) - feature_roots += splitPathList(QString::fromLocal8Bit(mkspec_path)); - /* - if (prop) - feature_roots += splitPathList(prop->value("QMAKEFEATURES")); - if (!Option::mkfile::cachefile.isEmpty()) { - QString path; - int last_slash = Option::mkfile::cachefile.lastIndexOf(Option::dir_sep); - if (last_slash != -1) - path = Option::fixPathToLocalOS(Option::mkfile::cachefile.left(last_slash)); + + QString mkspec_path = m_option->getEnv(QLatin1String("QMAKEFEATURES")); + if (!mkspec_path.isEmpty()) + foreach (const QString &f, mkspec_path.split(m_option->dirlist_sep)) + feature_roots += resolvePath(f); + + feature_roots += propertyValue(QLatin1String("QMAKEFEATURES"), false).split( + m_option->dirlist_sep, QString::SkipEmptyParts); + + if (!m_option->cachefile.isEmpty()) { + QString path = m_option->cachefile.left(m_option->cachefile.lastIndexOf((ushort)'/')); foreach (const QString &concat_it, concat) feature_roots << (path + concat_it); } - */ - QByteArray qmakepath = qgetenv("QMAKEPATH"); + QString qmakepath = m_option->getEnv(QLatin1String("QMAKEPATH")); if (!qmakepath.isNull()) { - const QStringList lst = splitPathList(QString::fromLocal8Bit(qmakepath)); + const QStringList lst = qmakepath.split(m_option->dirlist_sep); foreach (const QString &item, lst) { + QString citem = resolvePath(item); foreach (const QString &concat_it, concat) - feature_roots << (item + mkspecs_concat + concat_it); + feature_roots << (citem + mkspecs_concat + concat_it); } } - //if (!Option::mkfile::qmakespec.isEmpty()) - // feature_roots << Option::mkfile::qmakespec + QDir::separator() + "features"; - //if (!Option::mkfile::qmakespec.isEmpty()) { - // QFileInfo specfi(Option::mkfile::qmakespec); - // QDir specdir(specfi.absoluteFilePath()); - // while (!specdir.isRoot()) { - // if (!specdir.cdUp() || specdir.isRoot()) - // break; - // if (QFile::exists(specdir.path() + QDir::separator() + "features")) { - // foreach (const QString &concat_it, concat) - // feature_roots << (specdir.path() + concat_it); - // break; - // } - // } - //} + + if (!m_option->qmakespec.isEmpty()) { + QString qmakespec = resolvePath(m_option->qmakespec); + feature_roots << (qmakespec + features_concat); + + QDir specdir(qmakespec); + while (!specdir.isRoot()) { + if (!specdir.cdUp() || specdir.isRoot()) + break; + if (IoUtils::exists(specdir.path() + features_concat)) { + foreach (const QString &concat_it, concat) + feature_roots << (specdir.path() + concat_it); + break; + } + } + } + foreach (const QString &concat_it, concat) - feature_roots << (propertyValue(QLatin1String("QT_INSTALL_PREFIX")) + + feature_roots << (propertyValue(QLatin1String("QT_INSTALL_PREFIX"), false) + mkspecs_concat + concat_it); foreach (const QString &concat_it, concat) - feature_roots << (propertyValue(QLatin1String("QT_INSTALL_DATA")) + + feature_roots << (propertyValue(QLatin1String("QT_INSTALL_DATA"), false) + mkspecs_concat + concat_it); + + for (int i = 0; i < feature_roots.count(); ++i) + if (!feature_roots.at(i).endsWith((ushort)'/')) + feature_roots[i].append((ushort)'/'); + + feature_roots.removeDuplicates(); + return feature_roots; } -QString ProFileEvaluator::Private::propertyValue(const QString &name) const -{ - if (m_properties.contains(name)) - return m_properties.value(name); - if (name == QLatin1String("QT_INSTALL_PREFIX")) - return QLibraryInfo::location(QLibraryInfo::PrefixPath); - if (name == QLatin1String("QT_INSTALL_DATA")) - return QLibraryInfo::location(QLibraryInfo::DataPath); - if (name == QLatin1String("QT_INSTALL_DOCS")) - return QLibraryInfo::location(QLibraryInfo::DocumentationPath); - if (name == QLatin1String("QT_INSTALL_HEADERS")) - return QLibraryInfo::location(QLibraryInfo::HeadersPath); - if (name == QLatin1String("QT_INSTALL_LIBS")) - return QLibraryInfo::location(QLibraryInfo::LibrariesPath); - if (name == QLatin1String("QT_INSTALL_BINS")) - return QLibraryInfo::location(QLibraryInfo::BinariesPath); - if (name == QLatin1String("QT_INSTALL_PLUGINS")) - return QLibraryInfo::location(QLibraryInfo::PluginsPath); - if (name == QLatin1String("QT_INSTALL_TRANSLATIONS")) - return QLibraryInfo::location(QLibraryInfo::TranslationsPath); - if (name == QLatin1String("QT_INSTALL_CONFIGURATION")) - return QLibraryInfo::location(QLibraryInfo::SettingsPath); - if (name == QLatin1String("QT_INSTALL_EXAMPLES")) - return QLibraryInfo::location(QLibraryInfo::ExamplesPath); - if (name == QLatin1String("QT_INSTALL_DEMOS")) - return QLibraryInfo::location(QLibraryInfo::DemosPath); +QString ProFileEvaluator::Private::propertyValue(const QString &name, bool complain) const +{ + if (m_option->properties.contains(name)) + return m_option->properties.value(name); if (name == QLatin1String("QMAKE_MKSPECS")) - return qmake_mkspec_paths().join(Option::dirlist_sep); + return qmakeMkspecPaths().join(m_option->dirlist_sep); if (name == QLatin1String("QMAKE_VERSION")) return QLatin1String("1.0"); //### FIXME - //return qmake_version(); -#ifdef QT_VERSION_STR - if (name == QLatin1String("QT_VERSION")) - return QLatin1String(QT_VERSION_STR); -#endif - return QLatin1String("UNKNOWN"); //### + if (complain) + evalError(fL1S("Querying unknown property %1").arg(name)); + return QString(); } ProFile *ProFileEvaluator::Private::currentProFile() const @@ -1057,17 +1577,114 @@ QString ProFileEvaluator::Private::currentDirectory() const return cur->directoryName(); } -void ProFileEvaluator::Private::doVariableReplace(QString *str) +QString ProFileEvaluator::Private::sysrootify(const QString &path, const QString &baseDir) const { - *str = expandVariableReferences(*str).join(QString(Option::field_sep)); + const bool isHostSystemPath = m_option->sysroot.isEmpty() || path.startsWith(m_option->sysroot) + || path.startsWith(baseDir) || path.startsWith(m_outputDir); + return isHostSystemPath ? path : m_option->sysroot + path; } -QStringList ProFileEvaluator::Private::expandVariableReferences(const QString &str) +#ifndef QT_BOOTSTRAPPED +void ProFileEvaluator::Private::runProcess(QProcess *proc, const QString &command, + QProcess::ProcessChannel chan) const { - QStringList ret; + proc->setWorkingDirectory(currentDirectory()); + if (!m_option->environment.isEmpty()) + proc->setProcessEnvironment(m_option->environment); +# ifdef Q_OS_WIN + proc->setNativeArguments(QLatin1String("/v:off /s /c \"") + command + QLatin1Char('"')); + proc->start(m_option->getEnv(QLatin1String("COMSPEC")), QStringList()); +# else + proc->start(QLatin1String("/bin/sh"), QStringList() << QLatin1String("-c") << command); +# endif + proc->waitForFinished(-1); + proc->setReadChannel(chan); + QByteArray errout = proc->readAll(); + if (errout.endsWith('\n')) + errout.chop(1); + m_handler->evalError(QString(), 0, QString::fromLocal8Bit(errout)); +} +#endif + +// The (QChar*)current->constData() constructs below avoid pointless detach() calls +// FIXME: This is inefficient. Should not make new string if it is a straight subsegment +static ALWAYS_INLINE void appendChar(ushort unicode, + QString *current, QChar **ptr, ProString *pending) +{ + if (!pending->isEmpty()) { + int len = pending->size(); + current->resize(current->size() + len); + ::memcpy((QChar*)current->constData(), pending->constData(), len * 2); + pending->clear(); + *ptr = (QChar*)current->constData() + len; + } + *(*ptr)++ = QChar(unicode); +} + +static void appendString(const ProString &string, + QString *current, QChar **ptr, ProString *pending) +{ + if (string.isEmpty()) + return; + QChar *uc = (QChar*)current->constData(); + int len; + if (*ptr != uc) { + len = *ptr - uc; + current->resize(current->size() + string.size()); + } else if (!pending->isEmpty()) { + len = pending->size(); + current->resize(current->size() + len + string.size()); + ::memcpy((QChar*)current->constData(), pending->constData(), len * 2); + pending->clear(); + } else { + *pending = string; + return; + } + *ptr = (QChar*)current->constData() + len; + ::memcpy(*ptr, string.constData(), string.size() * 2); + *ptr += string.size(); +} + +static void flushCurrent(ProStringList *ret, + QString *current, QChar **ptr, ProString *pending, bool joined) +{ + QChar *uc = (QChar*)current->constData(); + int len = *ptr - uc; + if (len) { + ret->append(ProString(QString(uc, len), NoHash)); + *ptr = uc; + } else if (!pending->isEmpty()) { + ret->append(*pending); + pending->clear(); + } else if (joined) { + ret->append(ProString()); + } +} + +static inline void flushFinal(ProStringList *ret, + const QString ¤t, const QChar *ptr, const ProString &pending, + const ProString &str, bool replaced, bool joined) +{ + int len = ptr - current.data(); + if (len) { + if (!replaced && len == str.size()) + ret->append(str); + else + ret->append(ProString(QString(current.data(), len), NoHash)); + } else if (!pending.isEmpty()) { + ret->append(pending); + } else if (joined) { + ret->append(ProString()); + } +} + +ProStringList ProFileEvaluator::Private::expandVariableReferences( + const ProString &str, int *pos, bool joined) +{ + ProStringList ret; // if (ok) // *ok = true; - if (str.isEmpty()) + if (str.isEmpty() && !pos) return ret; const ushort LSQUARE = '['; @@ -1082,42 +1699,45 @@ QStringList ProFileEvaluator::Private::expandVariableReferences(const QString &s const ushort DOT = '.'; const ushort SPACE = ' '; const ushort TAB = '\t'; + const ushort COMMA = ','; const ushort SINGLEQUOTE = '\''; const ushort DOUBLEQUOTE = '"'; - ushort unicode, quote = 0; - const QChar *str_data = str.data(); - const int str_len = str.length(); - - ushort term; - QString var, args; - - int replaced = 0; - QString current; - for (int i = 0; i < str_len; ++i) { - unicode = str_data[i].unicode(); - const int start_var = i; - if (unicode == DOLLAR && str_len > i+2) { - unicode = str_data[++i].unicode(); - if (unicode == DOLLAR) { - term = 0; - var.clear(); - args.clear(); + ushort unicode, quote = 0, parens = 0; + const ushort *str_data = (const ushort *)str.constData(); + const int str_len = str.size(); + + ProString var, args; + + bool replaced = false; + bool putSpace = false; + QString current; // Buffer for successively assembled string segments + current.resize(str.size()); + QChar *ptr = current.data(); + ProString pending; // Buffer for string segments from variables + // Only one of the above buffers can be filled at a given time. + for (int i = pos ? *pos : 0; i < str_len; ++i) { + unicode = str_data[i]; + if (unicode == DOLLAR) { + if (str_len > i+2 && str_data[i+1] == DOLLAR) { + ++i; + ushort term = 0; enum { VAR, ENVIRON, FUNCTION, PROPERTY } var_type = VAR; - unicode = str_data[++i].unicode(); + unicode = str_data[++i]; if (unicode == LSQUARE) { - unicode = str_data[++i].unicode(); + unicode = str_data[++i]; term = RSQUARE; var_type = PROPERTY; } else if (unicode == LCURLY) { - unicode = str_data[++i].unicode(); + unicode = str_data[++i]; var_type = VAR; term = RCURLY; } else if (unicode == LPAREN) { - unicode = str_data[++i].unicode(); + unicode = str_data[++i]; var_type = ENVIRON; term = RPAREN; } + int name_start = i; forever { if (!(unicode & (0xFF<<8)) && unicode != DOT && unicode != UNDERSCORE && @@ -1125,19 +1745,20 @@ QStringList ProFileEvaluator::Private::expandVariableReferences(const QString &s (unicode < 'a' || unicode > 'z') && (unicode < 'A' || unicode > 'Z') && (unicode < '0' || unicode > '9')) break; - var.append(QChar(unicode)); if (++i == str_len) break; - unicode = str_data[i].unicode(); + unicode = str_data[i]; // at this point, i points to either the 'term' or 'next' character (which is in unicode) } + var = str.mid(name_start, i - name_start); if (var_type == VAR && unicode == LPAREN) { var_type = FUNCTION; + name_start = i + 1; int depth = 0; forever { if (++i == str_len) break; - unicode = str_data[i].unicode(); + unicode = str_data[i]; if (unicode == LPAREN) { depth++; } else if (unicode == RPAREN) { @@ -1145,10 +1766,10 @@ QStringList ProFileEvaluator::Private::expandVariableReferences(const QString &s break; --depth; } - args.append(QChar(unicode)); } + args = str.mid(name_start, i - name_start); if (++i < str_len) - unicode = str_data[i].unicode(); + unicode = str_data[i]; else unicode = 0; // at this point i is pointing to the 'next' character (which is in unicode) @@ -1156,232 +1777,415 @@ QStringList ProFileEvaluator::Private::expandVariableReferences(const QString &s } if (term) { if (unicode != term) { - q->logMessage(format("Missing %1 terminator [found %2]") - .arg(QChar(term)) - .arg(unicode ? QString(unicode) : QString::fromLatin1(("end-of-line")))); + evalError(fL1S("Missing %1 terminator [found %2]") + .arg(QChar(term)) + .arg(unicode ? QString(unicode) : fL1S("end-of-line"))); // if (ok) // *ok = false; - return QStringList(); + if (pos) + *pos = str_len; + return ProStringList(); } } else { // move the 'cursor' back to the last char of the thing we were looking at --i; } - // since i never points to the 'next' character, there is no reason for this to be set - unicode = 0; - QStringList replacement; + ProStringList replacement; if (var_type == ENVIRON) { - replacement = split_value_list(QString::fromLocal8Bit(qgetenv(var.toLatin1().constData()))); + replacement = split_value_list(m_option->getEnv(var.toQString(m_tmp1))); } else if (var_type == PROPERTY) { - replacement << propertyValue(var); + replacement << ProString(propertyValue(var.toQString(m_tmp1), true), NoHash); } else if (var_type == FUNCTION) { - replacement << evaluateExpandFunction(var, args); + replacement += evaluateExpandFunction(var, args); } else if (var_type == VAR) { - replacement = values(var); + replacement = values(map(var)); } - if (!(replaced++) && start_var) - current = str.left(start_var); if (!replacement.isEmpty()) { - if (quote) { - current += replacement.join(QString(Option::field_sep)); + if (quote || joined) { + if (putSpace) { + putSpace = false; + if (!replacement.at(0).isEmpty()) // Bizarre, indeed + appendChar(' ', ¤t, &ptr, &pending); + } + appendString(ProString(replacement.join(statics.field_sep), NoHash), + ¤t, &ptr, &pending); } else { - current += replacement.takeFirst(); - if (!replacement.isEmpty()) { - if (!current.isEmpty()) - ret.append(current); - current = replacement.takeLast(); - if (!replacement.isEmpty()) - ret += replacement; + appendString(replacement.at(0), ¤t, &ptr, &pending); + if (replacement.size() > 1) { + flushCurrent(&ret, ¤t, &ptr, &pending, false); + int j = 1; + if (replacement.size() > 2) { + // FIXME: ret.reserve(ret.size() + replacement.size() - 2); + for (; j < replacement.size() - 1; ++j) + ret << replacement.at(j); + } + pending = replacement.at(j); } } + replaced = true; } - } else { - if (replaced) - current.append(QLatin1Char('$')); + continue; } - } - if (quote && unicode == quote) { - unicode = 0; - quote = 0; } else if (unicode == BACKSLASH) { - bool escape = false; - const char *symbols = "[]{}()$\\'\""; - for (const char *s = symbols; *s; ++s) { - if (str_data[i+1].unicode() == (ushort)*s) { - i++; - escape = true; - if (!(replaced++)) - current = str.left(start_var); - current.append(str.at(i)); - break; - } + static const char symbols[] = "[]{}()$\\'\""; + ushort unicode2 = str_data[i+1]; + if (!(unicode2 & 0xff00) && strchr(symbols, unicode2)) { + unicode = unicode2; + ++i; + } + } else if (quote) { + if (unicode == quote) { + quote = 0; + continue; } - if (escape || !replaced) - unicode =0; - } else if (!quote && (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE)) { - quote = unicode; - unicode = 0; - if (!(replaced++) && i) - current = str.left(i); - } else if (!quote && (unicode == SPACE || unicode == TAB)) { - unicode = 0; - if (!current.isEmpty()) { - ret.append(current); - current.clear(); + } else { + if (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE) { + quote = unicode; + continue; + } else if (unicode == SPACE || unicode == TAB) { + if (!joined) + flushCurrent(&ret, ¤t, &ptr, &pending, false); + else if ((ptr - (QChar*)current.constData()) || !pending.isEmpty()) + putSpace = true; + continue; + } else if (pos) { + if (unicode == LPAREN) { + ++parens; + } else if (unicode == RPAREN) { + --parens; + } else if (!parens && unicode == COMMA) { + if (!joined) { + *pos = i + 1; + flushFinal(&ret, current, ptr, pending, str, replaced, false); + return ret; + } + flushCurrent(&ret, ¤t, &ptr, &pending, true); + putSpace = false; + continue; + } } } - if (replaced && unicode) - current.append(QChar(unicode)); + if (putSpace) { + putSpace = false; + appendChar(' ', ¤t, &ptr, &pending); + } + appendChar(unicode, ¤t, &ptr, &pending); } - if (!replaced) - ret = QStringList(str); - else if (!current.isEmpty()) - ret.append(current); + if (pos) + *pos = str_len; + flushFinal(&ret, current, ptr, pending, str, replaced, joined); return ret; } +bool ProFileEvaluator::Private::modesForGenerator(const QString &gen, + ProFileOption::HOST_MODE *host_mode, ProFileOption::TARG_MODE *target_mode) const +{ + if (gen == fL1S("UNIX")) { +#ifdef Q_OS_MAC + *host_mode = ProFileOption::HOST_MACX_MODE; + *target_mode = ProFileOption::TARG_MACX_MODE; +#else + *host_mode = ProFileOption::HOST_UNIX_MODE; + *target_mode = ProFileOption::TARG_UNIX_MODE; +#endif + } else if (gen == fL1S("MSVC.NET") || gen == fL1S("BMAKE") || gen == fL1S("MSBUILD")) { + *host_mode = ProFileOption::HOST_WIN_MODE; + *target_mode = ProFileOption::TARG_WIN_MODE; + } else if (gen == fL1S("MINGW")) { +#if defined(Q_OS_MAC) + *host_mode = ProFileOption::HOST_MACX_MODE; +#elif defined(Q_OS_UNIX) + *host_mode = ProFileOption::HOST_UNIX_MODE; +#else + *host_mode = ProFileOption::HOST_WIN_MODE; +#endif + *target_mode = ProFileOption::TARG_WIN_MODE; + } else if (gen == fL1S("PROJECTBUILDER") || gen == fL1S("XCODE")) { + *host_mode = ProFileOption::HOST_MACX_MODE; + *target_mode = ProFileOption::TARG_MACX_MODE; + } else if (gen == fL1S("SYMBIAN_ABLD") || gen == fL1S("SYMBIAN_SBSV2") + || gen == fL1S("SYMBIAN_UNIX") || gen == fL1S("SYMBIAN_MINGW")) { +#if defined(Q_OS_MAC) + *host_mode = ProFileOption::HOST_MACX_MODE; +#elif defined(Q_OS_UNIX) + *host_mode = ProFileOption::HOST_UNIX_MODE; +#else + *host_mode = ProFileOption::HOST_WIN_MODE; +#endif + *target_mode = ProFileOption::TARG_SYMBIAN_MODE; + } else { + evalError(fL1S("Unknown generator specified: %1").arg(gen)); + return false; + } + return true; +} + +void ProFileEvaluator::Private::validateModes() const +{ + if (m_option->host_mode == ProFileOption::HOST_UNKNOWN_MODE + || m_option->target_mode == ProFileOption::TARG_UNKNOWN_MODE) { + const QHash<ProString, ProStringList> &vals = + m_option->base_valuemap.isEmpty() ? m_valuemapStack[0] : m_option->base_valuemap; + ProFileOption::HOST_MODE host_mode; + ProFileOption::TARG_MODE target_mode; + const ProStringList &gen = vals.value(ProString("MAKEFILE_GENERATOR")); + if (gen.isEmpty()) { + evalError(fL1S("Using OS scope before setting MAKEFILE_GENERATOR")); + } else if (modesForGenerator(gen.at(0).toQString(), &host_mode, &target_mode)) { + if (m_option->host_mode == ProFileOption::HOST_UNKNOWN_MODE) { + m_option->host_mode = host_mode; + m_option->applyHostMode(); + } + + if (m_option->target_mode == ProFileOption::TARG_UNKNOWN_MODE) { + const ProStringList &tgt = vals.value(ProString("TARGET_PLATFORM")); + if (!tgt.isEmpty()) { + const QString &os = tgt.at(0).toQString(); + if (os == statics.strunix) + m_option->target_mode = ProFileOption::TARG_UNIX_MODE; + else if (os == statics.strmacx) + m_option->target_mode = ProFileOption::TARG_MACX_MODE; + else if (os == statics.strsymbian) + m_option->target_mode = ProFileOption::TARG_SYMBIAN_MODE; + else if (os == statics.strwin32) + m_option->target_mode = ProFileOption::TARG_WIN_MODE; + else + evalError(fL1S("Unknown target platform specified: %1").arg(os)); + } else { + m_option->target_mode = target_mode; + } + } + } + } +} + bool ProFileEvaluator::Private::isActiveConfig(const QString &config, bool regex) { // magic types for easy flipping - if (config == QLatin1String("true")) + if (config == statics.strtrue) return true; - if (config == QLatin1String("false")) + if (config == statics.strfalse) return false; - // mkspecs - if ((Option::target_mode == Option::TARG_MACX_MODE - || Option::target_mode == Option::TARG_QNX6_MODE - || Option::target_mode == Option::TARG_UNIX_MODE) - && config == QLatin1String("unix")) - return true; - if (Option::target_mode == Option::TARG_MACX_MODE && config == QLatin1String("macx")) - return true; - if (Option::target_mode == Option::TARG_QNX6_MODE && config == QLatin1String("qnx6")) - return true; - if (Option::target_mode == Option::TARG_MAC9_MODE && config == QLatin1String("mac9")) - return true; - if ((Option::target_mode == Option::TARG_MAC9_MODE - || Option::target_mode == Option::TARG_MACX_MODE) - && config == QLatin1String("mac")) - return true; - if (Option::target_mode == Option::TARG_WIN_MODE && config == QLatin1String("win32")) - return true; + if (config == statics.strunix) { + validateModes(); + return m_option->target_mode == ProFileOption::TARG_UNIX_MODE + || m_option->target_mode == ProFileOption::TARG_MACX_MODE + || m_option->target_mode == ProFileOption::TARG_SYMBIAN_MODE; + } else if (config == statics.strmacx || config == statics.strmac) { + validateModes(); + return m_option->target_mode == ProFileOption::TARG_MACX_MODE; + } else if (config == statics.strsymbian) { + validateModes(); + return m_option->target_mode == ProFileOption::TARG_SYMBIAN_MODE; + } else if (config == statics.strwin32) { + validateModes(); + return m_option->target_mode == ProFileOption::TARG_WIN_MODE; + } - QRegExp re(config, Qt::CaseSensitive, QRegExp::Wildcard); - QString spec = Option::qmakespec; - if ((regex && re.exactMatch(spec)) || (!regex && spec == config)) - return true; + if (regex && (config.contains(QLatin1Char('*')) || config.contains(QLatin1Char('?')))) { + QString cfg = config; + cfg.detach(); // Keep m_tmp out of QRegExp's cache + QRegExp re(cfg, Qt::CaseSensitive, QRegExp::Wildcard); + + // mkspecs + if (re.exactMatch(m_option->qmakespec_name)) + return true; + + // CONFIG variable + int t = 0; + foreach (const ProString &configValue, valuesDirect(statics.strCONFIG)) { + if (re.exactMatch(configValue.toQString(m_tmp[t]))) + return true; + t ^= 1; + } + } else { + // mkspecs + if (m_option->qmakespec_name == config) + return true; + + // CONFIG variable + if (valuesDirect(statics.strCONFIG).contains(ProString(config, NoHash))) + return true; + } return false; } -QStringList ProFileEvaluator::Private::evaluateFunction( - ProBlock *funcPtr, const QStringList &argumentsList, bool *ok) +ProStringList ProFileEvaluator::Private::expandVariableReferences( + const ushort *&tokPtr, int sizeHint, bool joined) +{ + ProStringList ret; + ret.reserve(sizeHint); + forever { + evaluateExpression(tokPtr, &ret, joined); + switch (*tokPtr) { + case TokValueTerminator: + case TokFuncTerminator: + tokPtr++; + return ret; + case TokArgSeparator: + if (joined) { + tokPtr++; + continue; + } + // fallthrough + default: + Q_ASSERT_X(false, "expandVariableReferences", "Unrecognized token"); + break; + } + } +} + +QList<ProStringList> ProFileEvaluator::Private::prepareFunctionArgs(const ushort *&tokPtr) +{ + QList<ProStringList> args_list; + if (*tokPtr != TokFuncTerminator) { + for (;; tokPtr++) { + ProStringList arg; + evaluateExpression(tokPtr, &arg, false); + args_list << arg; + if (*tokPtr == TokFuncTerminator) + break; + Q_ASSERT(*tokPtr == TokArgSeparator); + } + } + tokPtr++; + return args_list; +} + +QList<ProStringList> ProFileEvaluator::Private::prepareFunctionArgs(const ProString &arguments) +{ + QList<ProStringList> args_list; + for (int pos = 0; pos < arguments.size(); ) + args_list << expandVariableReferences(arguments, &pos); + return args_list; +} + +ProStringList ProFileEvaluator::Private::evaluateFunction( + const FunctionDef &func, const QList<ProStringList> &argumentsList, bool *ok) { bool oki; - QStringList ret; + ProStringList ret; if (m_valuemapStack.count() >= 100) { - q->errorMessage(format("ran into infinite recursion (depth > 100).")); + evalError(fL1S("ran into infinite recursion (depth > 100).")); oki = false; } else { - State sts = m_sts; - m_valuemapStack.push(m_valuemap); - m_filevaluemapStack.push(m_filevaluemap); + m_valuemapStack.push(QHash<ProString, ProStringList>()); + m_locationStack.push(m_current); + int loopLevel = m_loopLevel; + m_loopLevel = 0; - QStringList args; + ProStringList args; for (int i = 0; i < argumentsList.count(); ++i) { - QStringList theArgs = expandVariableReferences(argumentsList[i]); - args += theArgs; - m_valuemap[QString::number(i+1)] = theArgs; + args += argumentsList[i]; + m_valuemapStack.top()[ProString(QString::number(i+1))] = argumentsList[i]; } - m_valuemap[QLatin1String("ARGS")] = args; - oki = (funcPtr->Accept(this) != ProItem::ReturnFalse); // True || Return + m_valuemapStack.top()[statics.strARGS] = args; + oki = (visitProBlock(func.pro(), func.tokPtr()) != ReturnFalse); // True || Return ret = m_returnValue; m_returnValue.clear(); - m_valuemap = m_valuemapStack.pop(); - m_filevaluemap = m_filevaluemapStack.pop(); - m_sts = sts; + m_loopLevel = loopLevel; + m_current = m_locationStack.pop(); + m_valuemapStack.pop(); } if (ok) *ok = oki; if (oki) return ret; - return QStringList(); -} - -QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &func, const QString &arguments) -{ - QStringList argumentsList = split_arg_list(arguments); - - if (ProBlock *funcPtr = m_replaceFunctions.value(func, 0)) - return evaluateFunction(funcPtr, argumentsList, 0); - - QStringList args; - for (int i = 0; i < argumentsList.count(); ++i) - args += expandVariableReferences(argumentsList[i]).join(Option::field_sep); - - enum ExpandFunc { E_MEMBER=1, E_FIRST, E_LAST, E_CAT, E_FROMFILE, E_EVAL, E_LIST, - E_SPRINTF, E_JOIN, E_SPLIT, E_BASENAME, E_DIRNAME, E_SECTION, - E_FIND, E_SYSTEM, E_UNIQUE, E_QUOTE, E_ESCAPE_EXPAND, - E_UPPER, E_LOWER, E_FILES, E_PROMPT, E_RE_ESCAPE, - E_REPLACE }; - - static QHash<QString, int> expands; - if (expands.isEmpty()) { - expands.insert(QLatin1String("member"), E_MEMBER); - expands.insert(QLatin1String("first"), E_FIRST); - expands.insert(QLatin1String("last"), E_LAST); - expands.insert(QLatin1String("cat"), E_CAT); - expands.insert(QLatin1String("fromfile"), E_FROMFILE); // implementation disabled (see comment below) - expands.insert(QLatin1String("eval"), E_EVAL); - expands.insert(QLatin1String("list"), E_LIST); - expands.insert(QLatin1String("sprintf"), E_SPRINTF); - expands.insert(QLatin1String("join"), E_JOIN); - expands.insert(QLatin1String("split"), E_SPLIT); - expands.insert(QLatin1String("basename"), E_BASENAME); - expands.insert(QLatin1String("dirname"), E_DIRNAME); - expands.insert(QLatin1String("section"), E_SECTION); - expands.insert(QLatin1String("find"), E_FIND); - expands.insert(QLatin1String("system"), E_SYSTEM); - expands.insert(QLatin1String("unique"), E_UNIQUE); - expands.insert(QLatin1String("quote"), E_QUOTE); - expands.insert(QLatin1String("escape_expand"), E_ESCAPE_EXPAND); - expands.insert(QLatin1String("upper"), E_UPPER); - expands.insert(QLatin1String("lower"), E_LOWER); - expands.insert(QLatin1String("re_escape"), E_RE_ESCAPE); - expands.insert(QLatin1String("files"), E_FILES); - expands.insert(QLatin1String("prompt"), E_PROMPT); // interactive, so cannot be implemented - expands.insert(QLatin1String("replace"), E_REPLACE); + return ProStringList(); +} + +ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::evaluateBoolFunction( + const FunctionDef &func, const QList<ProStringList> &argumentsList, + const ProString &function) +{ + bool ok; + ProStringList ret = evaluateFunction(func, argumentsList, &ok); + if (ok) { + if (ret.isEmpty()) + return ReturnTrue; + if (ret.at(0) != statics.strfalse) { + if (ret.at(0) == statics.strtrue) + return ReturnTrue; + int val = ret.at(0).toQString(m_tmp1).toInt(&ok); + if (ok) { + if (val) + return ReturnTrue; + } else { + evalError(fL1S("Unexpected return value from test '%1': %2") + .arg(function.toQString(m_tmp1)) + .arg(ret.join(QLatin1String(" :: ")))); + } + } } - ExpandFunc func_t = ExpandFunc(expands.value(func.toLower())); + return ReturnFalse; +} - QStringList ret; +ProStringList ProFileEvaluator::Private::evaluateExpandFunction( + const ProString &func, const ushort *&tokPtr) +{ + QHash<ProString, FunctionDef>::ConstIterator it = + m_functionDefs.replaceFunctions.constFind(func); + if (it != m_functionDefs.replaceFunctions.constEnd()) + return evaluateFunction(*it, prepareFunctionArgs(tokPtr), 0); + + //why don't the builtin functions just use args_list? --Sam + return evaluateExpandFunction(func, expandVariableReferences(tokPtr, 5, true)); +} + +ProStringList ProFileEvaluator::Private::evaluateExpandFunction( + const ProString &func, const ProString &arguments) +{ + QHash<ProString, FunctionDef>::ConstIterator it = + m_functionDefs.replaceFunctions.constFind(func); + if (it != m_functionDefs.replaceFunctions.constEnd()) + return evaluateFunction(*it, prepareFunctionArgs(arguments), 0); + + //why don't the builtin functions just use args_list? --Sam + int pos = 0; + return evaluateExpandFunction(func, expandVariableReferences(arguments, &pos, true)); +} + +ProStringList ProFileEvaluator::Private::evaluateExpandFunction( + const ProString &func, const ProStringList &args) +{ + ExpandFunc func_t = ExpandFunc(statics.expands.value(func)); + if (func_t == 0) { + const QString &fn = func.toQString(m_tmp1); + const QString &lfn = fn.toLower(); + if (!fn.isSharedWith(lfn)) + func_t = ExpandFunc(statics.expands.value(ProString(lfn))); + } + + ProStringList ret; switch (func_t) { case E_BASENAME: case E_DIRNAME: case E_SECTION: { bool regexp = false; - QString sep, var; + QString sep; + ProString var; int beg = 0; int end = -1; if (func_t == E_SECTION) { if (args.count() != 3 && args.count() != 4) { - q->logMessage(format("%1(var) section(var, sep, begin, end) " - "requires three or four arguments.").arg(func)); + evalError(fL1S("%1(var) section(var, sep, begin, end) requires" + " three or four arguments.").arg(func.toQString(m_tmp1))); } else { var = args[0]; - sep = args[1]; - beg = args[2].toInt(); + sep = args.at(1).toQString(); + beg = args.at(2).toQString(m_tmp2).toInt(); if (args.count() == 4) - end = args[3].toInt(); + end = args.at(3).toQString(m_tmp2).toInt(); } } else { if (args.count() != 1) { - q->logMessage(format("%1(var) requires one argument.").arg(func)); + evalError(fL1S("%1(var) requires one argument.").arg(func.toQString(m_tmp1))); } else { var = args[0]; regexp = true; @@ -1392,67 +2196,81 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun beg = -1; } } - if (!var.isNull()) { - foreach (const QString str, values(var)) { - if (regexp) - ret += str.section(QRegExp(sep), beg, end); - else - ret += str.section(sep, beg, end); + if (!var.isEmpty()) { + if (regexp) { + QRegExp sepRx(sep); + foreach (const ProString &str, values(map(var))) { + const QString &rstr = str.toQString(m_tmp1).section(sepRx, beg, end); + ret << (rstr.isSharedWith(m_tmp1) ? str : ProString(rstr, NoHash).setSource(str)); + } + } else { + foreach (const ProString &str, values(map(var))) { + const QString &rstr = str.toQString(m_tmp1).section(sep, beg, end); + ret << (rstr.isSharedWith(m_tmp1) ? str : ProString(rstr, NoHash).setSource(str)); + } } } break; } case E_SPRINTF: if(args.count() < 1) { - q->logMessage(format("sprintf(format, ...) requires at least one argument")); + evalError(fL1S("sprintf(format, ...) requires at least one argument")); } else { - QString tmp = args.at(0); + QString tmp = args.at(0).toQString(m_tmp1); for (int i = 1; i < args.count(); ++i) - tmp = tmp.arg(args.at(i)); + tmp = tmp.arg(args.at(i).toQString(m_tmp2)); + // Note: this depends on split_value_list() making a deep copy ret = split_value_list(tmp); } break; case E_JOIN: { if (args.count() < 1 || args.count() > 4) { - q->logMessage(format("join(var, glue, before, after) requires one to four arguments.")); + evalError(fL1S("join(var, glue, before, after) requires one to four arguments.")); } else { - QString glue, before, after; + QString glue; + ProString before, after; if (args.count() >= 2) - glue = args[1]; + glue = args.at(1).toQString(m_tmp1); if (args.count() >= 3) before = args[2]; if (args.count() == 4) after = args[3]; - const QStringList &var = values(args.first()); - if (!var.isEmpty()) - ret.append(before + var.join(glue) + after); + const ProStringList &var = values(map(args.at(0))); + if (!var.isEmpty()) { + const ProFile *src = currentProFile(); + foreach (const ProString &v, var) + if (const ProFile *s = v.sourceFile()) { + src = s; + break; + } + ret.append(ProString(before + var.join(glue) + after, NoHash).setSource(src)); + } } break; } - case E_SPLIT: { + case E_SPLIT: if (args.count() != 2) { - q->logMessage(format("split(var, sep) requires one or two arguments")); + evalError(fL1S("split(var, sep) requires one or two arguments")); } else { - const QString &sep = (args.count() == 2) ? args[1] : QString(Option::field_sep); - foreach (const QString &var, values(args.first())) - foreach (const QString &splt, var.split(sep)) - ret.append(splt); + const QString &sep = (args.count() == 2) ? args.at(1).toQString(m_tmp1) : statics.field_sep; + foreach (const ProString &var, values(map(args.at(0)))) + foreach (const QString &splt, var.toQString(m_tmp2).split(sep)) + ret << (splt.isSharedWith(m_tmp2) ? var : ProString(splt, NoHash).setSource(var)); } break; - } - case E_MEMBER: { + case E_MEMBER: if (args.count() < 1 || args.count() > 3) { - q->logMessage(format("member(var, start, end) requires one to three arguments.")); + evalError(fL1S("member(var, start, end) requires one to three arguments.")); } else { bool ok = true; - const QStringList var = values(args.first()); + const ProStringList &var = values(map(args.at(0))); int start = 0, end = 0; if (args.count() >= 2) { - QString start_str = args[1]; + const QString &start_str = args.at(1).toQString(m_tmp1); start = start_str.toInt(&ok); if (!ok) { if (args.count() == 2) { - int dotdot = start_str.indexOf(QLatin1String("..")); + int dotdot = start_str.indexOf(statics.strDotDot); if (dotdot != -1) { start = start_str.left(dotdot).toInt(&ok); if (ok) @@ -1460,15 +2278,15 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun } } if (!ok) - q->logMessage(format("member() argument 2 (start) '%2' invalid.") - .arg(start_str)); + evalError(fL1S("member() argument 2 (start) '%2' invalid.") + .arg(start_str)); } else { end = start; if (args.count() == 3) - end = args[2].toInt(&ok); + end = args.at(2).toQString(m_tmp1).toInt(&ok); if (!ok) - q->logMessage(format("member() argument 3 (end) '%2' invalid.\n") - .arg(args[2])); + evalError(fL1S("member() argument 3 (end) '%2' invalid.\n") + .arg(args.at(2).toQString(m_tmp1))); } } if (ok) { @@ -1488,13 +2306,12 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun } } break; - } case E_FIRST: - case E_LAST: { + case E_LAST: if (args.count() != 1) { - q->logMessage(format("%1(var) requires one argument.").arg(func)); + evalError(fL1S("%1(var) requires one argument.").arg(func.toQString(m_tmp1))); } else { - const QStringList var = values(args.first()); + const ProStringList &var = values(map(args.at(0))); if (!var.isEmpty()) { if (func_t == E_FIRST) ret.append(var[0]); @@ -1503,92 +2320,96 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun } } break; - } + case E_SIZE: + if (args.count() != 1) + evalError(fL1S("size(var) requires one argument.")); + else + ret.append(ProString(QString::number(values(map(args.at(0))).size()), NoHash)); + break; case E_CAT: if (args.count() < 1 || args.count() > 2) { - q->logMessage(format("cat(file, singleline=true) requires one or two arguments.")); + evalError(fL1S("cat(file, singleline=true) requires one or two arguments.")); } else { - QString file = args[0]; - file = Option::fixPathToLocalOS(file); + const QString &file = args.at(0).toQString(m_tmp1); bool singleLine = true; if (args.count() > 1) - singleLine = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive)); + singleLine = isTrue(args.at(1), m_tmp2); - QFile qfile(file); + QFile qfile(resolvePath(expandEnvVars(file))); if (qfile.open(QIODevice::ReadOnly)) { QTextStream stream(&qfile); while (!stream.atEnd()) { ret += split_value_list(stream.readLine().trimmed()); if (!singleLine) - ret += QLatin1String("\n"); + ret += ProString("\n", NoHash); } qfile.close(); } } break; -#if 0 // Used only by Qt's configure for caching case E_FROMFILE: if (args.count() != 2) { - q->logMessage(format("fromfile(file, variable) requires two arguments.")); + evalError(fL1S("fromfile(file, variable) requires two arguments.")); } else { - QString file = args[0], seek_variableName = args[1]; - - ProFile pro(Option::fixPathToLocalOS(file)); - - ProFileEvaluator visitor; - visitor.setVerbose(m_verbose); - visitor.setCumulative(m_cumulative); - - if (!visitor.queryProFile(&pro)) - break; - - if (!visitor.accept(&pro)) - break; - - ret = visitor.values(seek_variableName); + QHash<ProString, ProStringList> vars; + QString fn = resolvePath(expandEnvVars(args.at(0).toQString(m_tmp1))); + fn.detach(); + if (evaluateFileInto(fn, ProFileEvaluatorHandler::EvalAuxFile, + &vars, &m_functionDefs, EvalWithDefaults)) + ret = vars.value(map(args.at(1))); } break; -#endif - case E_EVAL: { + case E_EVAL: if (args.count() != 1) { - q->logMessage(format("eval(variable) requires one argument")); - + evalError(fL1S("eval(variable) requires one argument")); } else { - ret += values(args.at(0)); + ret += values(map(args.at(0))); } - break; } + break; case E_LIST: { - static int x = 0; QString tmp; - tmp.sprintf(".QMAKE_INTERNAL_TMP_variableName_%d", x++); - ret = QStringList(tmp); - QStringList lst; - foreach (const QString &arg, args) - lst += split_value_list(arg); - m_valuemap[tmp] = lst; + tmp.sprintf(".QMAKE_INTERNAL_TMP_variableName_%d", m_listCount++); + ret = ProStringList(ProString(tmp, NoHash)); + ProStringList lst; + foreach (const ProString &arg, args) + lst += split_value_list(arg.toQString(m_tmp1), arg.sourceFile()); // Relies on deep copy + m_valuemapStack.top()[ret.at(0)] = lst; break; } case E_FIND: if (args.count() != 2) { - q->logMessage(format("find(var, str) requires two arguments.")); + evalError(fL1S("find(var, str) requires two arguments.")); } else { - QRegExp regx(args[1]); - foreach (const QString &val, values(args.first())) - if (regx.indexIn(val) != -1) + QRegExp regx(args.at(1).toQString()); + int t = 0; + foreach (const ProString &val, values(map(args.at(0)))) { + if (regx.indexIn(val.toQString(m_tmp[t])) != -1) ret += val; + t ^= 1; + } } break; case E_SYSTEM: if (!m_skipLevel) { if (args.count() < 1 || args.count() > 2) { - q->logMessage(format("system(execute) requires one or two arguments.")); + evalError(fL1S("system(execute) requires one or two arguments.")); } else { - char buff[256]; - FILE *proc = QT_POPEN(args[0].toLatin1(), "r"); bool singleLine = true; if (args.count() > 1) - singleLine = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive)); - QString output; + singleLine = isTrue(args.at(1), m_tmp2); + QByteArray output; +#ifndef QT_BOOTSTRAPPED + QProcess proc; + runProcess(&proc, args.at(0).toQString(m_tmp2), QProcess::StandardError); + output = proc.readAllStandardOutput(); + output.replace('\t', ' '); + if (singleLine) + output.replace('\n', ' '); +#else + char buff[256]; + FILE *proc = QT_POPEN(QString(QLatin1String("cd ") + + IoUtils::shellQuote(currentDirectory()) + + QLatin1String(" && ") + args[0]).toLocal8Bit(), "r"); while (proc && !feof(proc)) { int read_in = int(fread(buff, 1, 255, proc)); if (!read_in) @@ -1597,32 +2418,31 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun if ((singleLine && buff[i] == '\n') || buff[i] == '\t') buff[i] = ' '; } - buff[read_in] = '\0'; - output += QLatin1String(buff); + output.append(buff, read_in); } - ret += split_value_list(output); if (proc) QT_PCLOSE(proc); +#endif + ret += split_value_list(QString::fromLocal8Bit(output)); } } break; case E_UNIQUE: if(args.count() != 1) { - q->logMessage(format("unique(var) requires one argument.")); + evalError(fL1S("unique(var) requires one argument.")); } else { - foreach (const QString &var, values(args.first())) - if (!ret.contains(var)) - ret.append(var); + ret = values(map(args.at(0))); + ret.removeDuplicates(); } break; case E_QUOTE: - for (int i = 0; i < args.count(); ++i) - ret += QStringList(args.at(i)); + ret += args; break; case E_ESCAPE_EXPAND: for (int i = 0; i < args.size(); ++i) { - QChar *i_data = args[i].data(); - int i_len = args[i].length(); + QString str = args.at(i).toQString(); + QChar *i_data = str.data(); + int i_len = str.length(); for (int x = 0; x < i_len; ++x) { if (*(i_data+x) == QLatin1Char('\\') && x < i_len-1) { if (*(i_data+x+1) == QLatin1Char('\\')) { @@ -1648,299 +2468,238 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun } } } - ret.append(QString(i_data, i_len)); + ret.append(ProString(QString(i_data, i_len), NoHash).setSource(args.at(i))); } break; case E_RE_ESCAPE: - for (int i = 0; i < args.size(); ++i) - ret += QRegExp::escape(args[i]); + for (int i = 0; i < args.size(); ++i) { + const QString &rstr = QRegExp::escape(args.at(i).toQString(m_tmp1)); + ret << (rstr.isSharedWith(m_tmp1) ? args.at(i) : ProString(rstr, NoHash).setSource(args.at(i))); + } break; case E_UPPER: case E_LOWER: - for (int i = 0; i < args.count(); ++i) - if (func_t == E_UPPER) - ret += args[i].toUpper(); - else - ret += args[i].toLower(); + for (int i = 0; i < args.count(); ++i) { + QString rstr = args.at(i).toQString(m_tmp1); + rstr = (func_t == E_UPPER) ? rstr.toUpper() : rstr.toLower(); + ret << (rstr.isSharedWith(m_tmp1) ? args.at(i) : ProString(rstr, NoHash).setSource(args.at(i))); + } break; case E_FILES: if (args.count() != 1 && args.count() != 2) { - q->logMessage(format("files(pattern, recursive=false) requires one or two arguments")); + evalError(fL1S("files(pattern, recursive=false) requires one or two arguments")); } else { bool recursive = false; if (args.count() == 2) - recursive = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive) || args[1].toInt()); + recursive = isTrue(args.at(1), m_tmp2); QStringList dirs; - QString r = Option::fixPathToLocalOS(args[0]); + QString r = fixPathToLocalOS(args.at(0).toQString(m_tmp1)); + QString pfx; + if (IoUtils::isRelativePath(r)) { + pfx = currentDirectory(); + if (!pfx.endsWith(QLatin1Char('/'))) + pfx += QLatin1Char('/'); + } int slash = r.lastIndexOf(QDir::separator()); if (slash != -1) { - dirs.append(r.left(slash)); + dirs.append(r.left(slash+1)); r = r.mid(slash+1); } else { dirs.append(QString()); } + r.detach(); // Keep m_tmp out of QRegExp's cache const QRegExp regex(r, Qt::CaseSensitive, QRegExp::Wildcard); for (int d = 0; d < dirs.count(); d++) { QString dir = dirs[d]; - if (!dir.isEmpty() && !dir.endsWith(Option::dir_sep)) - dir += QLatin1Char('/'); - - QDir qdir(dir); + QDir qdir(pfx + dir); for (int i = 0; i < (int)qdir.count(); ++i) { - if (qdir[i] == QLatin1String(".") || qdir[i] == QLatin1String("..")) + if (qdir[i] == statics.strDot || qdir[i] == statics.strDotDot) continue; QString fname = dir + qdir[i]; - if (QFileInfo(fname).isDir()) { + if (IoUtils::fileType(pfx + fname) == IoUtils::FileIsDir) { if (recursive) - dirs.append(fname); + dirs.append(fname + QDir::separator()); } if (regex.exactMatch(qdir[i])) - ret += fname; + ret += ProString(fname, NoHash).setSource(currentProFile()); } } } break; case E_REPLACE: if(args.count() != 3 ) { - q->logMessage(format("replace(var, before, after) requires three arguments")); + evalError(fL1S("replace(var, before, after) requires three arguments")); } else { - const QRegExp before(args[1]); - const QString after(args[2]); - foreach (QString val, values(args.first())) - ret += val.replace(before, after); + const QRegExp before(args.at(1).toQString()); + const QString &after(args.at(2).toQString(m_tmp2)); + foreach (const ProString &val, values(map(args.at(0)))) { + QString rstr = val.toQString(m_tmp1); + QString copy = rstr; // Force a detach on modify + rstr.replace(before, after); + ret << (rstr.isSharedWith(m_tmp1) ? val : ProString(rstr, NoHash).setSource(val)); + } } break; - case 0: - q->logMessage(format("'%1' is not a recognized replace function").arg(func)); + case E_INVALID: + evalError(fL1S("'%1' is not a recognized replace function") + .arg(func.toQString(m_tmp1))); break; default: - q->logMessage(format("Function '%1' is not implemented").arg(func)); + evalError(fL1S("Function '%1' is not implemented").arg(func.toQString(m_tmp1))); break; } return ret; } -ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction( - const QString &function, const QString &arguments) +ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::evaluateConditionalFunction( + const ProString &function, const ProString &arguments) { - QStringList argumentsList = split_arg_list(arguments); + QHash<ProString, FunctionDef>::ConstIterator it = + m_functionDefs.testFunctions.constFind(function); + if (it != m_functionDefs.testFunctions.constEnd()) + return evaluateBoolFunction(*it, prepareFunctionArgs(arguments), function); - if (ProBlock *funcPtr = m_testFunctions.value(function, 0)) { - bool ok; - QStringList ret = evaluateFunction(funcPtr, argumentsList, &ok); - if (ok) { - if (ret.isEmpty()) { - return ProItem::ReturnTrue; - } else { - if (ret.first() != QLatin1String("false")) { - if (ret.first() == QLatin1String("true")) { - return ProItem::ReturnTrue; - } else { - bool ok; - int val = ret.first().toInt(&ok); - if (ok) { - if (val) - return ProItem::ReturnTrue; - } else { - q->logMessage(format("Unexpected return value from test '%1': %2") - .arg(function).arg(ret.join(QLatin1String(" :: ")))); - } - } - } - } - } - return ProItem::ReturnFalse; - } + //why don't the builtin functions just use args_list? --Sam + int pos = 0; + return evaluateConditionalFunction(function, expandVariableReferences(arguments, &pos, true)); +} - QString sep; - sep.append(Option::field_sep); - QStringList args; - for (int i = 0; i < argumentsList.count(); ++i) - args += expandVariableReferences(argumentsList[i]).join(sep); - - enum TestFunc { T_REQUIRES=1, T_GREATERTHAN, T_LESSTHAN, T_EQUALS, - T_EXISTS, T_EXPORT, T_CLEAR, T_UNSET, T_EVAL, T_CONFIG, T_SYSTEM, - T_RETURN, T_BREAK, T_NEXT, T_DEFINED, T_CONTAINS, T_INFILE, - T_COUNT, T_ISEMPTY, T_INCLUDE, T_LOAD, T_DEBUG, T_MESSAGE, T_IF, - T_FOR, T_DEFINE_TEST, T_DEFINE_REPLACE }; - - static QHash<QString, int> functions; - if (functions.isEmpty()) { - functions.insert(QLatin1String("requires"), T_REQUIRES); - functions.insert(QLatin1String("greaterThan"), T_GREATERTHAN); - functions.insert(QLatin1String("lessThan"), T_LESSTHAN); - functions.insert(QLatin1String("equals"), T_EQUALS); - functions.insert(QLatin1String("isEqual"), T_EQUALS); - functions.insert(QLatin1String("exists"), T_EXISTS); - functions.insert(QLatin1String("export"), T_EXPORT); - functions.insert(QLatin1String("clear"), T_CLEAR); - functions.insert(QLatin1String("unset"), T_UNSET); - functions.insert(QLatin1String("eval"), T_EVAL); - functions.insert(QLatin1String("CONFIG"), T_CONFIG); - functions.insert(QLatin1String("if"), T_IF); - functions.insert(QLatin1String("isActiveConfig"), T_CONFIG); - functions.insert(QLatin1String("system"), T_SYSTEM); - functions.insert(QLatin1String("return"), T_RETURN); - functions.insert(QLatin1String("break"), T_BREAK); - functions.insert(QLatin1String("next"), T_NEXT); - functions.insert(QLatin1String("defined"), T_DEFINED); - functions.insert(QLatin1String("contains"), T_CONTAINS); - functions.insert(QLatin1String("infile"), T_INFILE); - functions.insert(QLatin1String("count"), T_COUNT); - functions.insert(QLatin1String("isEmpty"), T_ISEMPTY); - functions.insert(QLatin1String("load"), T_LOAD); //v - functions.insert(QLatin1String("include"), T_INCLUDE); //v - functions.insert(QLatin1String("debug"), T_DEBUG); - functions.insert(QLatin1String("message"), T_MESSAGE); //v - functions.insert(QLatin1String("warning"), T_MESSAGE); //v - functions.insert(QLatin1String("error"), T_MESSAGE); //v - functions.insert(QLatin1String("for"), T_FOR); //v - functions.insert(QLatin1String("defineTest"), T_DEFINE_TEST); //v - functions.insert(QLatin1String("defineReplace"), T_DEFINE_REPLACE); //v - } +ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::evaluateConditionalFunction( + const ProString &function, const ushort *&tokPtr) +{ + QHash<ProString, FunctionDef>::ConstIterator it = + m_functionDefs.testFunctions.constFind(function); + if (it != m_functionDefs.testFunctions.constEnd()) + return evaluateBoolFunction(*it, prepareFunctionArgs(tokPtr), function); + + //why don't the builtin functions just use args_list? --Sam + return evaluateConditionalFunction(function, expandVariableReferences(tokPtr, 5, true)); +} - TestFunc func_t = (TestFunc)functions.value(function); +ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::evaluateConditionalFunction( + const ProString &function, const ProStringList &args) +{ + TestFunc func_t = (TestFunc)statics.functions.value(function); switch (func_t) { - case T_DEFINE_TEST: - m_definingTest = true; - goto defineFunc; - case T_DEFINE_REPLACE: - m_definingTest = false; - defineFunc: - if (args.count() != 1) { - q->logMessage(format("%s(function) requires one argument.").arg(function)); - return ProItem::ReturnFalse; - } - m_definingFunc = args.first(); - return ProItem::ReturnTrue; case T_DEFINED: if (args.count() < 1 || args.count() > 2) { - q->logMessage(format("defined(function, [\"test\"|\"replace\"])" - " requires one or two arguments.")); - return ProItem::ReturnFalse; + evalError(fL1S("defined(function, [\"test\"|\"replace\"])" + " requires one or two arguments.")); + return ReturnFalse; } if (args.count() > 1) { if (args[1] == QLatin1String("test")) - return returnBool(m_testFunctions.contains(args[0])); + return returnBool(m_functionDefs.testFunctions.contains(args[0])); else if (args[1] == QLatin1String("replace")) - return returnBool(m_replaceFunctions.contains(args[0])); - q->logMessage(format("defined(function, type):" - " unexpected type [%1].\n").arg(args[1])); - return ProItem::ReturnFalse; + return returnBool(m_functionDefs.replaceFunctions.contains(args[0])); + evalError(fL1S("defined(function, type): unexpected type [%1].\n") + .arg(args.at(1).toQString(m_tmp1))); + return ReturnFalse; } - return returnBool(m_replaceFunctions.contains(args[0]) - || m_testFunctions.contains(args[0])); + return returnBool(m_functionDefs.replaceFunctions.contains(args[0]) + || m_functionDefs.testFunctions.contains(args[0])); case T_RETURN: m_returnValue = args; // It is "safe" to ignore returns - due to qmake brokeness // they cannot be used to terminate loops anyway. if (m_skipLevel || m_cumulative) - return ProItem::ReturnTrue; + return ReturnTrue; if (m_valuemapStack.isEmpty()) { - q->logMessage(format("unexpected return().")); - return ProItem::ReturnFalse; + evalError(fL1S("unexpected return().")); + return ReturnFalse; } - return ProItem::ReturnReturn; - case T_EXPORT: + return ReturnReturn; + case T_EXPORT: { if (m_skipLevel && !m_cumulative) - return ProItem::ReturnTrue; + return ReturnTrue; if (args.count() != 1) { - q->logMessage(format("export(variable) requires one argument.")); - return ProItem::ReturnFalse; + evalError(fL1S("export(variable) requires one argument.")); + return ReturnFalse; + } + const ProString &var = map(args.at(0)); + for (int i = m_valuemapStack.size(); --i > 0; ) { + QHash<ProString, ProStringList>::Iterator it = m_valuemapStack[i].find(var); + if (it != m_valuemapStack.at(i).end()) { + if (it->constBegin() == statics.fakeValue.constBegin()) { + // This is stupid, but qmake doesn't propagate deletions + m_valuemapStack[0][var] = ProStringList(); + } else { + m_valuemapStack[0][var] = *it; + } + m_valuemapStack[i].erase(it); + while (--i) + m_valuemapStack[i].remove(var); + break; + } } - for (int i = 0; i < m_valuemapStack.size(); ++i) { - m_valuemapStack[i][args[0]] = m_valuemap[args[0]]; - m_filevaluemapStack[i][currentProFile()][args[0]] = - m_filevaluemap[currentProFile()][args[0]]; + return ReturnTrue; + } + case T_INFILE: + if (args.count() < 2 || args.count() > 3) { + evalError(fL1S("infile(file, var, [values]) requires two or three arguments.")); + } else { + QHash<ProString, ProStringList> vars; + QString fn = resolvePath(expandEnvVars(args.at(0).toQString(m_tmp1))); + fn.detach(); + if (!evaluateFileInto(fn, ProFileEvaluatorHandler::EvalAuxFile, + &vars, &m_functionDefs, EvalWithDefaults)) + return ReturnFalse; + if (args.count() == 2) + return returnBool(vars.contains(args.at(1))); + QRegExp regx; + const QString &qry = args.at(2).toQString(m_tmp1); + if (qry != QRegExp::escape(qry)) { + QString copy = qry; + copy.detach(); + regx.setPattern(copy); + } + int t = 0; + foreach (const ProString &s, vars.value(map(args.at(1)))) { + if ((!regx.isEmpty() && regx.exactMatch(s.toQString(m_tmp[t]))) || s == qry) + return ReturnTrue; + t ^= 1; + } } - return ProItem::ReturnTrue; + return ReturnFalse; #if 0 - case T_INFILE: case T_REQUIRES: - case T_EVAL: #endif - case T_FOR: { - if (m_cumulative) // This is a no-win situation, so just pretend it's no loop - return ProItem::ReturnTrue; - if (m_skipLevel) - return ProItem::ReturnFalse; - if (args.count() > 2 || args.count() < 1) { - q->logMessage(format("for({var, list|var, forever|ever})" - " requires one or two arguments.")); - return ProItem::ReturnFalse; - } - ProLoop loop; - loop.infinite = false; - loop.index = 0; - QString it_list; - if (args.count() == 1) { - doVariableReplace(&args[0]); - it_list = args[0]; - if (args[0] != QLatin1String("ever")) { - q->logMessage(format("for({var, list|var, forever|ever})" - " requires one or two arguments.")); - return ProItem::ReturnFalse; - } - it_list = QLatin1String("forever"); - } else { - loop.variable = args[0]; - loop.oldVarVal = m_valuemap.value(loop.variable); - doVariableReplace(&args[1]); - it_list = args[1]; - } - loop.list = m_valuemap[it_list]; - if (loop.list.isEmpty()) { - if (it_list == QLatin1String("forever")) { - loop.infinite = true; - } else { - int dotdot = it_list.indexOf(QLatin1String("..")); - if (dotdot != -1) { - bool ok; - int start = it_list.left(dotdot).toInt(&ok); - if (ok) { - int end = it_list.mid(dotdot+2).toInt(&ok); - if (ok) { - if (start < end) { - for (int i = start; i <= end; i++) - loop.list << QString::number(i); - } else { - for (int i = start; i >= end; i--) - loop.list << QString::number(i); - } - } - } - } - } + case T_EVAL: { + ProFile *pro = m_parser->parsedProBlock(fL1S("(eval)"), + args.join(statics.field_sep)); + if (!pro) + return ReturnFalse; + m_locationStack.push(m_current); + VisitReturn ret = visitProBlock(pro, pro->tokPtr()); + m_current = m_locationStack.pop(); + pro->deref(); + return ret; } - m_loopStack.push(loop); - m_sts.condition = true; - return ProItem::ReturnLoop; - } case T_BREAK: if (m_skipLevel) - return ProItem::ReturnFalse; - if (!m_loopStack.isEmpty()) - return ProItem::ReturnBreak; - // ### missing: breaking out of multiline blocks - q->logMessage(format("unexpected break().")); - return ProItem::ReturnFalse; + return ReturnFalse; + if (m_loopLevel) + return ReturnBreak; + evalError(fL1S("unexpected break().")); + return ReturnFalse; case T_NEXT: if (m_skipLevel) - return ProItem::ReturnFalse; - if (!m_loopStack.isEmpty()) - return ProItem::ReturnNext; - q->logMessage(format("unexpected next().")); - return ProItem::ReturnFalse; + return ReturnFalse; + if (m_loopLevel) + return ReturnNext; + evalError(fL1S("unexpected next().")); + return ReturnFalse; case T_IF: { + if (m_skipLevel && !m_cumulative) + return ReturnFalse; if (args.count() != 1) { - q->logMessage(format("if(condition) requires one argument.")); - return ProItem::ReturnFalse; + evalError(fL1S("if(condition) requires one argument.")); + return ReturnFalse; } - QString cond = args.first(); - bool escaped = false; // This is more than qmake does + const ProString &cond = args.at(0); bool quoted = false; bool ret = true; bool orOp = false; @@ -1949,83 +2708,69 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction( int parens = 0; QString test; test.reserve(20); - QString args; - args.reserve(50); - const QChar *d = cond.unicode(); - const QChar *ed = d + cond.length(); + QString argsString; + argsString.reserve(50); + const QChar *d = cond.constData(); + const QChar *ed = d + cond.size(); while (d < ed) { ushort c = (d++)->unicode(); - if (!escaped) { - if (c == '\\') { - escaped = true; - args += c; // Assume no-one quotes the test name - continue; - } else if (c == '"') { - quoted = !quoted; - args += c; // Ditto - continue; - } - } else { - escaped = false; - } + bool isOp = false; if (quoted) { - args += c; // Ditto + if (c == '"') + quoted = false; + else if (c == '!' && test.isEmpty()) + invert = true; + else + test += c; + } else if (c == '(') { + isFunc = true; + if (parens) + argsString += c; + ++parens; + } else if (c == ')') { + --parens; + if (parens) + argsString += c; + } else if (!parens) { + if (c == '"') + quoted = true; + else if (c == ':' || c == '|') + isOp = true; + else if (c == '!' && test.isEmpty()) + invert = true; + else + test += c; } else { - bool isOp = false; - if (c == '(') { - isFunc = true; - if (parens) - args += c; - ++parens; - } else if (c == ')') { - --parens; - if (parens) - args += c; - } else if (!parens) { - if (c == ':' || c == '|') - isOp = true; - else if (c == '!') - invert = true; - else - test += c; - } else { - args += c; - } - if (!parens && (isOp || d == ed)) { - // Yes, qmake doesn't shortcut evaluations here. We can't, either, - // as some test functions have side effects. - bool success; - if (isFunc) { - success = evaluateConditionalFunction(test, args); - } else { - success = isActiveConfig(test, true); - } - success ^= invert; - if (orOp) - ret |= success; + argsString += c; + } + if (!quoted && !parens && (isOp || d == ed)) { + if (m_cumulative || (orOp != ret)) { + test = test.trimmed(); + if (isFunc) + ret = evaluateConditionalFunction(ProString(test), ProString(argsString, NoHash)); else - ret &= success; - orOp = (c == '|'); - invert = false; - isFunc = false; - test.clear(); - args.clear(); + ret = isActiveConfig(test, true); + ret ^= invert; } + orOp = (c == '|'); + invert = false; + isFunc = false; + test.clear(); + argsString.clear(); } } return returnBool(ret); } case T_CONFIG: { if (args.count() < 1 || args.count() > 2) { - q->logMessage(format("CONFIG(config) requires one or two arguments.")); - return ProItem::ReturnFalse; - } - if (args.count() == 1) { - //cond = isActiveConfig(args.first()); XXX - return ProItem::ReturnFalse; + evalError(fL1S("CONFIG(config) requires one or two arguments.")); + return ReturnFalse; } - const QStringList mutuals = args[1].split(QLatin1Char('|')); - const QStringList &configs = valuesDirect(QLatin1String("CONFIG")); + if (args.count() == 1) + return returnBool(isActiveConfig(args.at(0).toQString(m_tmp2))); + const QStringList &mutuals = args.at(1).toQString(m_tmp2).split(QLatin1Char('|')); + const ProStringList &configs = valuesDirect(statics.strCONFIG); + for (int i = configs.size() - 1; i >= 0; i--) { for (int mut = 0; mut < mutuals.count(); mut++) { if (configs[i] == mutuals[mut].trimmed()) { @@ -2033,68 +2778,81 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction( } } } - return ProItem::ReturnFalse; + return ReturnFalse; } case T_CONTAINS: { if (args.count() < 2 || args.count() > 3) { - q->logMessage(format("contains(var, val) requires two or three arguments.")); - return ProItem::ReturnFalse; + evalError(fL1S("contains(var, val) requires two or three arguments.")); + return ReturnFalse; } - QRegExp regx(args[1]); - const QStringList &l = values(args.first()); + const QString &qry = args.at(1).toQString(m_tmp1); + QRegExp regx; + if (qry != QRegExp::escape(qry)) { + QString copy = qry; + copy.detach(); + regx.setPattern(copy); + } + const ProStringList &l = values(map(args.at(0))); if (args.count() == 2) { + int t = 0; for (int i = 0; i < l.size(); ++i) { - const QString val = l[i]; - if (regx.exactMatch(val) || val == args[1]) { - return ProItem::ReturnTrue; - } + const ProString &val = l[i]; + if ((!regx.isEmpty() && regx.exactMatch(val.toQString(m_tmp[t]))) || val == qry) + return ReturnTrue; + t ^= 1; } } else { - const QStringList mutuals = args[2].split(QLatin1Char('|')); + const QStringList &mutuals = args.at(2).toQString(m_tmp3).split(QLatin1Char('|')); for (int i = l.size() - 1; i >= 0; i--) { - const QString val = l[i]; + const ProString val = l[i]; for (int mut = 0; mut < mutuals.count(); mut++) { if (val == mutuals[mut].trimmed()) { - return returnBool(regx.exactMatch(val) || val == args[1]); + return returnBool((!regx.isEmpty() + && regx.exactMatch(val.toQString(m_tmp2))) + || val == qry); } } } } - return ProItem::ReturnFalse; + return ReturnFalse; } case T_COUNT: { if (args.count() != 2 && args.count() != 3) { - q->logMessage(format("count(var, count, op=\"equals\") requires two or three arguments.")); - return ProItem::ReturnFalse; + evalError(fL1S("count(var, count, op=\"equals\") requires two or three arguments.")); + return ReturnFalse; } + int cnt = values(map(args.at(0))).count(); if (args.count() == 3) { - QString comp = args[2]; + const ProString &comp = args.at(2); + const int val = args.at(1).toQString(m_tmp1).toInt(); if (comp == QLatin1String(">") || comp == QLatin1String("greaterThan")) { - return returnBool(values(args.first()).count() > args[1].toInt()); + return returnBool(cnt > val); } else if (comp == QLatin1String(">=")) { - return returnBool(values(args.first()).count() >= args[1].toInt()); + return returnBool(cnt >= val); } else if (comp == QLatin1String("<") || comp == QLatin1String("lessThan")) { - return returnBool(values(args.first()).count() < args[1].toInt()); + return returnBool(cnt < val); } else if (comp == QLatin1String("<=")) { - return returnBool(values(args.first()).count() <= args[1].toInt()); + return returnBool(cnt <= val); } else if (comp == QLatin1String("equals") || comp == QLatin1String("isEqual") || comp == QLatin1String("=") || comp == QLatin1String("==")) { - return returnBool(values(args.first()).count() == args[1].toInt()); + return returnBool(cnt == val); } else { - q->logMessage(format("unexpected modifier to count(%2)").arg(comp)); - return ProItem::ReturnFalse; + evalError(fL1S("unexpected modifier to count(%2)").arg(comp.toQString(m_tmp1))); + return ReturnFalse; } } - return returnBool(values(args.first()).count() == args[1].toInt()); + return returnBool(cnt == args.at(1).toQString(m_tmp1).toInt()); } case T_GREATERTHAN: case T_LESSTHAN: { if (args.count() != 2) { - q->logMessage(format("%1(variable, value) requires two arguments.").arg(function)); - return ProItem::ReturnFalse; + evalError(fL1S("%1(variable, value) requires two arguments.") + .arg(function.toQString(m_tmp1))); + return ReturnFalse; } - QString rhs(args[1]), lhs(values(args[0]).join(QString(Option::field_sep))); + const QString &rhs(args.at(1).toQString(m_tmp1)), + &lhs(values(map(args.at(0))).join(statics.field_sep)); bool ok; int rhs_int = rhs.toInt(&ok); if (ok) { // do integer compare @@ -2111,206 +2869,300 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction( } case T_EQUALS: if (args.count() != 2) { - q->logMessage(format("%1(variable, value) requires two arguments.").arg(function)); - return ProItem::ReturnFalse; + evalError(fL1S("%1(variable, value) requires two arguments.") + .arg(function.toQString(m_tmp1))); + return ReturnFalse; } - return returnBool(values(args[0]).join(QString(Option::field_sep)) == args[1]); + return returnBool(values(map(args.at(0))).join(statics.field_sep) + == args.at(1).toQString(m_tmp1)); case T_CLEAR: { if (m_skipLevel && !m_cumulative) - return ProItem::ReturnFalse; + return ReturnFalse; if (args.count() != 1) { - q->logMessage(format("%1(variable) requires one argument.").arg(function)); - return ProItem::ReturnFalse; - } - QHash<QString, QStringList>::Iterator it = m_valuemap.find(args[0]); - if (it == m_valuemap.end()) - return ProItem::ReturnFalse; - it->clear(); - return ProItem::ReturnTrue; + evalError(fL1S("%1(variable) requires one argument.") + .arg(function.toQString(m_tmp1))); + return ReturnFalse; + } + QHash<ProString, ProStringList> *hsh; + QHash<ProString, ProStringList>::Iterator it; + const ProString &var = map(args.at(0)); + if (!(hsh = findValues(var, &it))) + return ReturnFalse; + if (hsh == &m_valuemapStack.top()) + it->clear(); + else + m_valuemapStack.top()[var].clear(); + return ReturnTrue; } case T_UNSET: { if (m_skipLevel && !m_cumulative) - return ProItem::ReturnFalse; + return ReturnFalse; if (args.count() != 1) { - q->logMessage(format("%1(variable) requires one argument.").arg(function)); - return ProItem::ReturnFalse; - } - QHash<QString, QStringList>::Iterator it = m_valuemap.find(args[0]); - if (it == m_valuemap.end()) - return ProItem::ReturnFalse; - m_valuemap.erase(it); - return ProItem::ReturnTrue; + evalError(fL1S("%1(variable) requires one argument.") + .arg(function.toQString(m_tmp1))); + return ReturnFalse; + } + QHash<ProString, ProStringList> *hsh; + QHash<ProString, ProStringList>::Iterator it; + const ProString &var = map(args.at(0)); + if (!(hsh = findValues(var, &it))) + return ReturnFalse; + if (m_valuemapStack.size() == 1) + hsh->erase(it); + else if (hsh == &m_valuemapStack.top()) + *it = statics.fakeValue; + else + m_valuemapStack.top()[var] = statics.fakeValue; + return ReturnTrue; } case T_INCLUDE: { if (m_skipLevel && !m_cumulative) - return ProItem::ReturnFalse; + return ReturnFalse; QString parseInto; // the third optional argument to include() controls warnings // and is not used here - if ((args.count() == 2) || (args.count() == 3)) { - parseInto = args[1]; + if ((args.count() == 2) || (args.count() == 3) ) { + parseInto = args.at(1).toQString(m_tmp2); } else if (args.count() != 1) { - q->logMessage(format("include(file) requires one, two or three arguments.")); - return ProItem::ReturnFalse; - } - QString fileName = args.first(); - // ### this breaks if we have include(c:/reallystupid.pri) but IMHO that's really bad style. - QDir currentProPath(currentDirectory()); - fileName = QDir::cleanPath(currentProPath.absoluteFilePath(fileName)); - State sts = m_sts; - bool ok = evaluateFile(fileName); - m_sts = sts; + evalError(fL1S("include(file, into, silent) requires one, two or three arguments.")); + return ReturnFalse; + } + QString fn = resolvePath(expandEnvVars(args.at(0).toQString(m_tmp1))); + fn.detach(); + bool ok; + if (parseInto.isEmpty()) { + ok = evaluateFile(fn, ProFileEvaluatorHandler::EvalIncludeFile, + ProFileEvaluator::LoadProOnly); + } else { + QHash<ProString, ProStringList> symbols; + if ((ok = evaluateFileInto(fn, ProFileEvaluatorHandler::EvalAuxFile, + &symbols, 0, EvalWithSetup))) { + QHash<ProString, ProStringList> newMap; + for (QHash<ProString, ProStringList>::ConstIterator + it = m_valuemapStack.top().constBegin(), + end = m_valuemapStack.top().constEnd(); + it != end; ++it) { + const QString &ky = it.key().toQString(m_tmp1); + if (!(ky.startsWith(parseInto) && + (ky.length() == parseInto.length() + || ky.at(parseInto.length()) == QLatin1Char('.')))) + newMap[it.key()] = it.value(); + } + for (QHash<ProString, ProStringList>::ConstIterator it = symbols.constBegin(); + it != symbols.constEnd(); ++it) { + const QString &ky = it.key().toQString(m_tmp1); + if (!ky.startsWith(QLatin1Char('.'))) + newMap.insert(ProString(parseInto + QLatin1Char('.') + ky), it.value()); + } + m_valuemapStack.top() = newMap; + } + } return returnBool(ok); } case T_LOAD: { if (m_skipLevel && !m_cumulative) - return ProItem::ReturnFalse; - QString parseInto; + return ReturnFalse; bool ignore_error = false; if (args.count() == 2) { - QString sarg = args[1]; - ignore_error = (!sarg.compare(QLatin1String("true"), Qt::CaseInsensitive) || sarg.toInt()); + ignore_error = isTrue(args.at(1), m_tmp2); } else if (args.count() != 1) { - q->logMessage(format("load(feature) requires one or two arguments.")); - return ProItem::ReturnFalse; + evalError(fL1S("load(feature) requires one or two arguments.")); + return ReturnFalse; } // XXX ignore_error unused - return returnBool(evaluateFeatureFile(args.first())); + return returnBool(evaluateFeatureFile(expandEnvVars(args.at(0).toQString()))); } case T_DEBUG: // Yup - do nothing. Nothing is going to enable debug output anyway. - return ProItem::ReturnFalse; + return ReturnFalse; case T_MESSAGE: { if (args.count() != 1) { - q->logMessage(format("%1(message) requires one argument.").arg(function)); - return ProItem::ReturnFalse; - } - QString msg = fixEnvVariables(args.first()); - q->fileMessage(QString::fromLatin1("Project %1: %2").arg(function.toUpper(), msg)); + evalError(fL1S("%1(message) requires one argument.") + .arg(function.toQString(m_tmp1))); + return ReturnFalse; + } + const QString &msg = expandEnvVars(args.at(0).toQString(m_tmp2)); + if (!m_skipLevel) + m_handler->fileMessage(fL1S("Project %1: %2") + .arg(function.toQString(m_tmp1).toUpper(), msg)); // ### Consider real termination in non-cumulative mode return returnBool(function != QLatin1String("error")); } #if 0 // Way too dangerous to enable. case T_SYSTEM: { if (args.count() != 1) { - q->logMessage(format("system(exec) requires one argument.")); - ProItem::ReturnFalse; - } - return returnBool(system(args.first().toLatin1().constData()) == 0); + evalError(fL1S("system(exec) requires one argument.")); + return ReturnFalse; + } +#ifndef QT_BOOTSTRAPPED + QProcess proc; + proc.setProcessChannelMode(QProcess::MergedChannels); + runProcess(&proc, args.at(0).toQString(m_tmp2), QProcess::StandardOutput); + return returnBool(proc.exitStatus() == QProcess::NormalExit && proc.exitCode() == 0); +#else + return returnBool(system((QLatin1String("cd ") + + IoUtils::shellQuote(currentDirectory()) + + QLatin1String(" && ") + args.at(0)).toLocal8Bit().constData()) == 0); +#endif } #endif case T_ISEMPTY: { if (args.count() != 1) { - q->logMessage(format("isEmpty(var) requires one argument.")); - return ProItem::ReturnFalse; + evalError(fL1S("isEmpty(var) requires one argument.")); + return ReturnFalse; } - QStringList sl = values(args.first()); + const ProStringList &sl = values(map(args.at(0))); if (sl.count() == 0) { - return ProItem::ReturnTrue; + return ReturnTrue; } else if (sl.count() > 0) { - QString var = sl.first(); + const ProString &var = sl.first(); if (var.isEmpty()) - return ProItem::ReturnTrue; + return ReturnTrue; } - return ProItem::ReturnFalse; + return ReturnFalse; } case T_EXISTS: { if (args.count() != 1) { - q->logMessage(format("exists(file) requires one argument.")); - return ProItem::ReturnFalse; + evalError(fL1S("exists(file) requires one argument.")); + return ReturnFalse; } - QString file = args.first(); - file = Option::fixPathToLocalOS(file); + const QString &file = resolvePath(expandEnvVars(args.at(0).toQString(m_tmp1))); - if (QFile::exists(file)) { - return ProItem::ReturnTrue; + if (IoUtils::exists(file)) { + return ReturnTrue; } - //regular expression I guess - QString dirstr = currentDirectory(); - int slsh = file.lastIndexOf(Option::dir_sep); - if (slsh != -1) { - dirstr = file.left(slsh+1); - file = file.right(file.length() - slsh - 1); + int slsh = file.lastIndexOf(QLatin1Char('/')); + QString fn = file.mid(slsh+1); + if (fn.contains(QLatin1Char('*')) || fn.contains(QLatin1Char('?'))) { + QString dirstr = file.left(slsh+1); + if (!QDir(dirstr).entryList(QStringList(fn)).isEmpty()) + return ReturnTrue; } - if (file.contains(QLatin1Char('*')) || file.contains(QLatin1Char('?'))) - if (!QDir(dirstr).entryList(QStringList(file)).isEmpty()) - return ProItem::ReturnTrue; - return ProItem::ReturnFalse; + return ReturnFalse; } - case 0: - q->logMessage(format("'%1' is not a recognized test function").arg(function)); - return ProItem::ReturnFalse; + case T_INVALID: + evalError(fL1S("'%1' is not a recognized test function") + .arg(function.toQString(m_tmp1))); + return ReturnFalse; default: - q->logMessage(format("Function '%1' is not implemented").arg(function)); - return ProItem::ReturnFalse; + evalError(fL1S("Function '%1' is not implemented").arg(function.toQString(m_tmp1))); + return ReturnFalse; + } +} + +QHash<ProString, ProStringList> *ProFileEvaluator::Private::findValues( + const ProString &variableName, QHash<ProString, ProStringList>::Iterator *rit) +{ + for (int i = m_valuemapStack.size(); --i >= 0; ) { + QHash<ProString, ProStringList>::Iterator it = m_valuemapStack[i].find(variableName); + if (it != m_valuemapStack[i].end()) { + if (it->constBegin() == statics.fakeValue.constBegin()) + return 0; + *rit = it; + return &m_valuemapStack[i]; + } + } + return 0; +} + +ProStringList &ProFileEvaluator::Private::valuesRef(const ProString &variableName) +{ + QHash<ProString, ProStringList>::Iterator it = m_valuemapStack.top().find(variableName); + if (it != m_valuemapStack.top().end()) + return *it; + for (int i = m_valuemapStack.size() - 1; --i >= 0; ) { + QHash<ProString, ProStringList>::ConstIterator it = m_valuemapStack.at(i).constFind(variableName); + if (it != m_valuemapStack.at(i).constEnd()) { + ProStringList &ret = m_valuemapStack.top()[variableName]; + ret = *it; + return ret; + } + } + return m_valuemapStack.top()[variableName]; +} + +ProStringList ProFileEvaluator::Private::valuesDirect(const ProString &variableName) const +{ + for (int i = m_valuemapStack.size(); --i >= 0; ) { + QHash<ProString, ProStringList>::ConstIterator it = m_valuemapStack.at(i).constFind(variableName); + if (it != m_valuemapStack.at(i).constEnd()) { + if (it->constBegin() == statics.fakeValue.constBegin()) + break; + return *it; + } } + return ProStringList(); } -QStringList ProFileEvaluator::Private::values(const QString &variableName, - const QHash<QString, QStringList> &place, - const ProFile *pro) const -{ - if (variableName == QLatin1String("LITERAL_WHITESPACE")) //a real space in a token - return QStringList(QLatin1String("\t")); - if (variableName == QLatin1String("LITERAL_DOLLAR")) //a real $ - return QStringList(QLatin1String("$")); - if (variableName == QLatin1String("LITERAL_HASH")) //a real # - return QStringList(QLatin1String("#")); - if (variableName == QLatin1String("OUT_PWD")) //the out going dir - return QStringList(m_outputDir); - if (variableName == QLatin1String("PWD") || //current working dir (of _FILE_) - variableName == QLatin1String("IN_PWD")) - return QStringList(currentDirectory()); - if (variableName == QLatin1String("DIR_SEPARATOR")) - return QStringList(Option::dir_sep); - if (variableName == QLatin1String("DIRLIST_SEPARATOR")) - return QStringList(Option::dirlist_sep); - if (variableName == QLatin1String("_LINE_")) //parser line number - return QStringList(QString::number(m_lineNo)); - if (variableName == QLatin1String("_FILE_")) //parser file; qmake is a bit weird here - return QStringList(m_profileStack.size() == 1 ? pro->fileName() : QFileInfo(pro->fileName()).fileName()); - if (variableName == QLatin1String("_DATE_")) //current date/time - return QStringList(QDateTime::currentDateTime().toString()); - if (variableName == QLatin1String("_PRO_FILE_")) - return QStringList(m_origfile); - if (variableName == QLatin1String("_PRO_FILE_PWD_")) - return QStringList(QFileInfo(m_origfile).absolutePath()); - if (variableName == QLatin1String("_QMAKE_CACHE_")) - return QStringList(); // FIXME? - if (variableName.startsWith(QLatin1String("QMAKE_HOST."))) { - QString ret, type = variableName.mid(11); +ProStringList ProFileEvaluator::Private::values(const ProString &variableName) const +{ + QHash<ProString, int>::ConstIterator vli = statics.varList.find(variableName); + if (vli != statics.varList.constEnd()) { + int vlidx = *vli; + QString ret; + switch ((VarName)vlidx) { + case V_LITERAL_WHITESPACE: ret = QLatin1String("\t"); break; + case V_LITERAL_DOLLAR: ret = QLatin1String("$"); break; + case V_LITERAL_HASH: ret = QLatin1String("#"); break; + case V_OUT_PWD: // the outgoing dir (shadow of _PRO_FILE_PWD_) + ret = m_outputDir; + break; + case V_PWD: // containing directory of most nested project/include file + case V_IN_PWD: + ret = currentDirectory(); + break; + case V_DIR_SEPARATOR: + validateModes(); + ret = m_option->dir_sep; + break; + case V_DIRLIST_SEPARATOR: + ret = m_option->dirlist_sep; + break; + case V__LINE_: // currently executed line number + ret = QString::number(m_current.line); + break; + case V__FILE_: // currently executed file + ret = m_current.pro->fileName(); + break; + case V__DATE_: //current date/time + ret = QDateTime::currentDateTime().toString(); + break; + case V__PRO_FILE_: + ret = m_profileStack.first()->fileName(); + break; + case V__PRO_FILE_PWD_: + ret = m_profileStack.first()->directoryName(); + break; + case V__QMAKE_CACHE_: + ret = m_option->cachefile; + break; #if defined(Q_OS_WIN32) - if (type == QLatin1String("os")) { - ret = QLatin1String("Windows"); - } else if (type == QLatin1String("name")) { + case V_QMAKE_HOST_os: ret = QLatin1String("Windows"); break; + case V_QMAKE_HOST_name: { DWORD name_length = 1024; - wchar_t name[1024]; + TCHAR name[1024]; if (GetComputerName(name, &name_length)) - ret = QString::fromWCharArray(name); - } else if (type == QLatin1String("version") || type == QLatin1String("version_string")) { - QSysInfo::WinVersion ver = QSysInfo::WindowsVersion; - if (type == QLatin1String("version")) - ret = QString::number(ver); - else if (ver == QSysInfo::WV_Me) - ret = QLatin1String("WinMe"); - else if (ver == QSysInfo::WV_95) - ret = QLatin1String("Win95"); - else if (ver == QSysInfo::WV_98) - ret = QLatin1String("Win98"); - else if (ver == QSysInfo::WV_NT) - ret = QLatin1String("WinNT"); - else if (ver == QSysInfo::WV_2000) - ret = QLatin1String("Win2000"); - else if (ver == QSysInfo::WV_2000) - ret = QLatin1String("Win2003"); - else if (ver == QSysInfo::WV_XP) - ret = QLatin1String("WinXP"); - else if (ver == QSysInfo::WV_VISTA) - ret = QLatin1String("WinVista"); - else - ret = QLatin1String("Unknown"); - } else if (type == QLatin1String("arch")) { + ret = QString::fromUtf16((ushort*)name, name_length); + break; + } + case V_QMAKE_HOST_version: + ret = QString::number(QSysInfo::WindowsVersion); + break; + case V_QMAKE_HOST_version_string: + switch (QSysInfo::WindowsVersion) { + case QSysInfo::WV_Me: ret = QLatin1String("WinMe"); break; + case QSysInfo::WV_95: ret = QLatin1String("Win95"); break; + case QSysInfo::WV_98: ret = QLatin1String("Win98"); break; + case QSysInfo::WV_NT: ret = QLatin1String("WinNT"); break; + case QSysInfo::WV_2000: ret = QLatin1String("Win2000"); break; + case QSysInfo::WV_2003: ret = QLatin1String("Win2003"); break; + case QSysInfo::WV_XP: ret = QLatin1String("WinXP"); break; + case QSysInfo::WV_VISTA: ret = QLatin1String("WinVista"); break; + default: ret = QLatin1String("Unknown"); break; + } + break; + case V_QMAKE_HOST_arch: SYSTEM_INFO info; GetSystemInfo(&info); switch(info.wProcessorArchitecture) { @@ -2332,115 +3184,154 @@ QStringList ProFileEvaluator::Private::values(const QString &variableName, ret = QLatin1String("Unknown"); break; } - } + break; #elif defined(Q_OS_UNIX) - struct utsname name; - if (!uname(&name)) { - if (type == QLatin1String("os")) - ret = QString::fromLatin1(name.sysname); - else if (type == QLatin1String("name")) - ret = QString::fromLatin1(name.nodename); - else if (type == QLatin1String("version")) - ret = QString::fromLatin1(name.release); - else if (type == QLatin1String("version_string")) - ret = QString::fromLatin1(name.version); - else if (type == QLatin1String("arch")) - ret = QString::fromLatin1(name.machine); - } + case V_QMAKE_HOST_os: + case V_QMAKE_HOST_name: + case V_QMAKE_HOST_version: + case V_QMAKE_HOST_version_string: + case V_QMAKE_HOST_arch: + { + struct utsname name; + const char *what; + if (!uname(&name)) { + switch (vlidx) { + case V_QMAKE_HOST_os: what = name.sysname; break; + case V_QMAKE_HOST_name: what = name.nodename; break; + case V_QMAKE_HOST_version: what = name.release; break; + case V_QMAKE_HOST_version_string: what = name.version; break; + case V_QMAKE_HOST_arch: what = name.machine; break; + } + ret = QString::fromLocal8Bit(what); + } + } #endif - return QStringList(ret); + } + return ProStringList(ProString(ret, NoHash)); } - QStringList result = place[variableName]; + ProStringList result = valuesDirect(variableName); if (result.isEmpty()) { - if (variableName == QLatin1String("TARGET")) { - result.append(QFileInfo(m_origfile).baseName()); - } else if (variableName == QLatin1String("TEMPLATE")) { - result.append(QLatin1String("app")); - } else if (variableName == QLatin1String("QMAKE_DIR_SEP")) { - result.append(Option::dirlist_sep); + if (variableName == statics.strTEMPLATE) { + result.append(ProString("app", NoHash)); + } else if (variableName == statics.strQMAKE_DIR_SEP) { + result.append(ProString(m_option->dirlist_sep, NoHash)); } } return result; } -QStringList ProFileEvaluator::Private::values(const QString &variableName) const +bool ProFileEvaluator::Private::evaluateFileDirect( + const QString &fileName, ProFileEvaluatorHandler::EvalFileType type, + ProFileEvaluator::LoadFlags flags) { - return values(variableName, m_valuemap, currentProFile()); -} - -QStringList ProFileEvaluator::Private::values(const QString &variableName, const ProFile *pro) const -{ - return values(variableName, m_filevaluemap[pro], pro); -} - -ProFile *ProFileEvaluator::parsedProFile(const QString &fileName) -{ - QFileInfo fi(fileName); - if (fi.exists()) { - QString fn = QDir::cleanPath(fi.absoluteFilePath()); - foreach (const ProFile *pf, d->m_profileStack) - if (pf->fileName() == fn) { - errorMessage(d->format("circular inclusion of %1").arg(fn)); - return 0; - } - ProFile *pro = new ProFile(fn); - if (d->read(pro)) - return pro; - delete pro; + if (ProFile *pro = m_parser->parsedProFile(fileName, true)) { + m_locationStack.push(m_current); + bool ok = (visitProFile(pro, type, flags) == ReturnTrue); + m_current = m_locationStack.pop(); + pro->deref(); + return ok; + } else { + return false; } - return 0; } -void ProFileEvaluator::releaseParsedProFile(ProFile *proFile) +bool ProFileEvaluator::Private::evaluateFile( + const QString &fileName, ProFileEvaluatorHandler::EvalFileType type, + ProFileEvaluator::LoadFlags flags) { - delete proFile; -} - -bool ProFileEvaluator::Private::evaluateFile(const QString &fileName) -{ - ProFile *pro = q->parsedProFile(fileName); - if (pro) { - m_profileStack.push(pro); - bool ok = (pro->Accept(this) == ProItem::ReturnTrue); - m_profileStack.pop(); - q->releaseParsedProFile(pro); - return ok; - } else { + if (fileName.isEmpty()) return false; - } + foreach (const ProFile *pf, m_profileStack) + if (pf->fileName() == fileName) { + evalError(fL1S("circular inclusion of %1").arg(fileName)); + return false; + } + return evaluateFileDirect(fileName, type, flags); } bool ProFileEvaluator::Private::evaluateFeatureFile(const QString &fileName) { - QString fn; - foreach (const QString &path, qmakeFeaturePaths()) { - QString fname = path + QLatin1Char('/') + fileName; - if (QFileInfo(fname).exists()) { - fn = fname; - break; + QString fn = fileName; + if (!fn.endsWith(QLatin1String(".prf"))) + fn += QLatin1String(".prf"); + + if ((!fileName.contains((ushort)'/') && !fileName.contains((ushort)'\\')) + || !IoUtils::exists(resolvePath(fn))) { + if (m_option->feature_roots.isEmpty()) + m_option->feature_roots = qmakeFeaturePaths(); + int start_root = 0; + QString currFn = currentFileName(); + if (IoUtils::fileName(currFn) == IoUtils::fileName(fn)) { + for (int root = 0; root < m_option->feature_roots.size(); ++root) + if (currFn == m_option->feature_roots.at(root) + fn) { + start_root = root + 1; + break; + } } - fname += QLatin1String(".prf"); - if (QFileInfo(fname).exists()) { - fn = fname; - break; + for (int root = start_root; root < m_option->feature_roots.size(); ++root) { + QString fname = m_option->feature_roots.at(root) + fn; + if (IoUtils::exists(fname)) { + fn = fname; + goto cool; + } } - } - if (fn.isEmpty()) return false; + + cool: + // It's beyond me why qmake has this inside this if ... + ProStringList &already = valuesRef(ProString("QMAKE_INTERNAL_INCLUDED_FEATURES")); + ProString afn(fn, NoHash); + if (already.contains(afn)) + return true; + already.append(afn); + } else { + fn = resolvePath(fn); + } + +#ifdef PROEVALUATOR_CUMULATIVE bool cumulative = m_cumulative; m_cumulative = false; - bool ok = evaluateFile(fn); +#endif + + // The path is fully normalized already. + bool ok = evaluateFileDirect(fn, ProFileEvaluatorHandler::EvalFeatureFile, + ProFileEvaluator::LoadProOnly); + +#ifdef PROEVALUATOR_CUMULATIVE m_cumulative = cumulative; +#endif return ok; } -QString ProFileEvaluator::Private::format(const char *fmt) const +bool ProFileEvaluator::Private::evaluateFileInto( + const QString &fileName, ProFileEvaluatorHandler::EvalFileType type, + QHash<ProString, ProStringList> *values, FunctionDefs *funcs, EvalIntoMode mode) { - ProFile *pro = currentProFile(); - QString fileName = pro ? pro->fileName() : QLatin1String("Not a file"); - int lineNumber = pro ? m_lineNo : 0; - return QString::fromLatin1("%1(%2):").arg(fileName).arg(lineNumber) + QString::fromAscii(fmt); + ProFileEvaluator visitor(m_option, m_parser, m_handler); +#ifdef PROEVALUATOR_CUMULATIVE + visitor.d->m_cumulative = false; +#endif + visitor.d->m_outputDir = m_outputDir; +// visitor.d->m_valuemapStack.top() = *values; + if (funcs) + visitor.d->m_functionDefs = *funcs; + if (mode == EvalWithDefaults) + visitor.d->evaluateFeatureFile(QLatin1String("default_pre.prf")); + if (!visitor.d->evaluateFile(fileName, type, + (mode == EvalWithSetup) ? ProFileEvaluator::LoadAll : ProFileEvaluator::LoadProOnly)) + return false; + *values = visitor.d->m_valuemapStack.top(); +// if (funcs) +// *funcs = visitor.d->m_functionDefs; + return true; +} + +void ProFileEvaluator::Private::evalError(const QString &message) const +{ + if (!m_skipLevel) + m_handler->evalError(m_current.line ? m_current.pro->fileName() : QString(), + m_current.line, message); } @@ -2450,10 +3341,15 @@ QString ProFileEvaluator::Private::format(const char *fmt) const // /////////////////////////////////////////////////////////////////////// -ProFileEvaluator::ProFileEvaluator() - : d(new Private(this)) +void ProFileEvaluator::initialize() +{ + Private::initStatics(); +} + +ProFileEvaluator::ProFileEvaluator(ProFileOption *option, ProFileParser *parser, + ProFileEvaluatorHandler *handler) + : d(new Private(this, option, parser, handler)) { - Option::init(); } ProFileEvaluator::~ProFileEvaluator() @@ -2463,26 +3359,38 @@ ProFileEvaluator::~ProFileEvaluator() bool ProFileEvaluator::contains(const QString &variableName) const { - return d->m_valuemap.contains(variableName); + return d->m_valuemapStack.top().contains(ProString(variableName)); } -inline QStringList fixEnvVariables(const QStringList &x) +QString ProFileEvaluator::value(const QString &variable) const { - QStringList ret; - foreach (const QString &str, x) - ret << Option::fixString(str, Option::FixEnvVars); - return ret; -} + const QStringList &vals = values(variable); + if (!vals.isEmpty()) + return vals.first(); + return QString(); +} QStringList ProFileEvaluator::values(const QString &variableName) const { - return fixEnvVariables(d->values(variableName)); + const ProStringList &values = d->values(ProString(variableName)); + QStringList ret; + ret.reserve(values.size()); + foreach (const ProString &str, values) + ret << d->expandEnvVars(str.toQString()); + return ret; } QStringList ProFileEvaluator::values(const QString &variableName, const ProFile *pro) const { - return fixEnvVariables(d->values(variableName, pro)); + // It makes no sense to put any kind of magic into expanding these + const ProStringList &values = d->m_valuemapStack.at(0).value(ProString(variableName)); + QStringList ret; + ret.reserve(values.size()); + foreach (const ProString &str, values) + if (str.sourceFile() == pro) + ret << d->expandEnvVars(str.toQString()); + return ret; } QStringList ProFileEvaluator::absolutePathValues( @@ -2490,9 +3398,10 @@ QStringList ProFileEvaluator::absolutePathValues( { QStringList result; foreach (const QString &el, values(variable)) { - const QFileInfo info = QFileInfo(baseDirectory, el); - if (info.isDir()) - result << QDir::cleanPath(info.absoluteFilePath()); + QString absEl = IoUtils::isAbsolutePath(el) + ? d->sysrootify(el, baseDirectory) : IoUtils::resolvePath(baseDirectory, el); + if (IoUtils::fileType(absEl) == IoUtils::FileIsDir) + result << QDir::cleanPath(absEl); } return result; } @@ -2503,34 +3412,40 @@ QStringList ProFileEvaluator::absoluteFileValues( { QStringList result; foreach (const QString &el, pro ? values(variable, pro) : values(variable)) { - QFileInfo info(el); - if (info.isAbsolute()) { - if (info.exists()) { - result << QDir::cleanPath(el); + QString absEl; + if (IoUtils::isAbsolutePath(el)) { + const QString elWithSysroot = d->sysrootify(el, baseDirectory); + if (IoUtils::exists(elWithSysroot)) { + result << QDir::cleanPath(elWithSysroot); goto next; } + absEl = elWithSysroot; } else { foreach (const QString &dir, searchDirs) { - QFileInfo info(dir, el); - if (info.isFile()) { - result << QDir::cleanPath(info.filePath()); + QString fn = dir + QLatin1Char('/') + el; + if (IoUtils::exists(fn)) { + result << QDir::cleanPath(fn); goto next; } } if (baseDirectory.isEmpty()) goto next; - info = QFileInfo(baseDirectory, el); + absEl = baseDirectory + QLatin1Char('/') + el; } { - QFileInfo baseInfo(info.absolutePath()); - if (baseInfo.exists()) { - QString wildcard = info.fileName(); + absEl = QDir::cleanPath(absEl); + int nameOff = absEl.lastIndexOf(QLatin1Char('/')); + QString absDir = d->m_tmp1.setRawData(absEl.constData(), nameOff); + if (IoUtils::exists(absDir)) { + QString wildcard = d->m_tmp2.setRawData(absEl.constData() + nameOff + 1, + absEl.length() - nameOff - 1); if (wildcard.contains(QLatin1Char('*')) || wildcard.contains(QLatin1Char('?'))) { - QDir theDir(QDir::cleanPath(baseInfo.filePath())); + wildcard.detach(); // Keep m_tmp out of QRegExp's cache + QDir theDir(absDir); foreach (const QString &fn, theDir.entryList(QStringList(wildcard))) - if (fn != QLatin1String(".") && fn != QLatin1String("..")) - result << theDir.absoluteFilePath(fn); - } + if (fn != statics.strDot && fn != statics.strDotDot) + result << absDir + QLatin1Char('/') + fn; + } // else if (acceptMissing) } } next: ; @@ -2538,86 +3453,41 @@ QStringList ProFileEvaluator::absoluteFileValues( return result; } -ProFileEvaluator::TemplateType ProFileEvaluator::templateType() +ProFileEvaluator::TemplateType ProFileEvaluator::templateType() const { - QStringList templ = values(QLatin1String("TEMPLATE")); + const ProStringList &templ = d->values(statics.strTEMPLATE); if (templ.count() >= 1) { - const QString &t = templ.last(); + const QString &t = templ.at(0).toQString(); if (!t.compare(QLatin1String("app"), Qt::CaseInsensitive)) return TT_Application; if (!t.compare(QLatin1String("lib"), Qt::CaseInsensitive)) return TT_Library; if (!t.compare(QLatin1String("script"), Qt::CaseInsensitive)) return TT_Script; + if (!t.compare(QLatin1String("aux"), Qt::CaseInsensitive)) + return TT_Aux; if (!t.compare(QLatin1String("subdirs"), Qt::CaseInsensitive)) return TT_Subdirs; } return TT_Unknown; } -bool ProFileEvaluator::queryProFile(ProFile *pro) -{ - return d->read(pro); -} - -bool ProFileEvaluator::accept(ProFile *pro) +bool ProFileEvaluator::accept(ProFile *pro, LoadFlags flags) { - return pro->Accept(d); + return d->visitProFile(pro, ProFileEvaluatorHandler::EvalProjectFile, flags) == Private::ReturnTrue; } QString ProFileEvaluator::propertyValue(const QString &name) const { - return d->propertyValue(name); -} - -namespace { - template<class K, class T> void insert(QHash<K,T> *out, const QHash<K,T> &in) - { - typename QHash<K,T>::const_iterator i = in.begin(); - while (i != in.end()) { - out->insert(i.key(), i.value()); - ++i; - } - } -} // anon namespace - -void ProFileEvaluator::addVariables(const QHash<QString, QStringList> &variables) -{ - insert(&(d->m_valuemap), variables); -} - -void ProFileEvaluator::addProperties(const QHash<QString, QString> &properties) -{ - insert(&(d->m_properties), properties); -} - -void ProFileEvaluator::logMessage(const QString &message) -{ - if (d->m_verbose && !d->m_skipLevel) - fprintf(stderr, "%s\n", qPrintable(message)); -} - -void ProFileEvaluator::fileMessage(const QString &message) -{ - if (!d->m_skipLevel) - fprintf(stderr, "%s\n", qPrintable(message)); -} - -void ProFileEvaluator::errorMessage(const QString &message) -{ - if (!d->m_skipLevel) - fprintf(stderr, "%s\n", qPrintable(message)); -} - -void ProFileEvaluator::setVerbose(bool on) -{ - d->m_verbose = on; + return d->propertyValue(name, false); } +#ifdef PROEVALUATOR_CUMULATIVE void ProFileEvaluator::setCumulative(bool on) { d->m_cumulative = on; } +#endif void ProFileEvaluator::setOutputDir(const QString &dir) { |