summaryrefslogtreecommitdiffstats
path: root/qmake/library/qmakebuiltins.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'qmake/library/qmakebuiltins.cpp')
-rw-r--r--qmake/library/qmakebuiltins.cpp1684
1 files changed, 1684 insertions, 0 deletions
diff --git a/qmake/library/qmakebuiltins.cpp b/qmake/library/qmakebuiltins.cpp
new file mode 100644
index 0000000000..bf1c00f262
--- /dev/null
+++ b/qmake/library/qmakebuiltins.cpp
@@ -0,0 +1,1684 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the qmake application of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmakeevaluator.h"
+
+#include "qmakeevaluator_p.h"
+#include "qmakeglobals.h"
+#include "qmakeparser.h"
+#include "ioutils.h"
+
+#include <qbytearray.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qlist.h>
+#include <qregexp.h>
+#include <qset.h>
+#include <qstringlist.h>
+#include <qtextstream.h>
+
+#ifdef Q_OS_UNIX
+#include <time.h>
+#include <utime.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#else
+#include <windows.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef Q_OS_WIN32
+#define QT_POPEN _popen
+#define QT_PCLOSE _pclose
+#else
+#define QT_POPEN popen
+#define QT_PCLOSE pclose
+#endif
+
+using namespace QMakeInternal;
+
+QT_BEGIN_NAMESPACE
+
+#define fL1S(s) QString::fromLatin1(s)
+
+enum ExpandFunc {
+ E_INVALID = 0, E_MEMBER, E_FIRST, E_LAST, E_SIZE, E_CAT, E_FROMFILE, E_EVAL, E_LIST,
+ E_SPRINTF, E_FORMAT_NUMBER, E_JOIN, E_SPLIT, E_BASENAME, E_DIRNAME, E_SECTION,
+ E_FIND, E_SYSTEM, E_UNIQUE, E_REVERSE, E_QUOTE, E_ESCAPE_EXPAND,
+ E_UPPER, E_LOWER, E_FILES, E_PROMPT, E_RE_ESCAPE, E_VAL_ESCAPE,
+ E_REPLACE, E_SORT_DEPENDS, E_RESOLVE_DEPENDS, E_ENUMERATE_VARS,
+ E_SHADOWED, E_ABSOLUTE_PATH, E_RELATIVE_PATH, E_CLEAN_PATH,
+ E_SYSTEM_PATH, E_SHELL_PATH, E_SYSTEM_QUOTE, E_SHELL_QUOTE
+};
+
+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_LOG, T_MESSAGE, T_WARNING, T_ERROR, T_IF,
+ T_MKPATH, T_WRITE_FILE, T_TOUCH, T_CACHE
+};
+
+void QMakeEvaluator::initFunctionStatics()
+{
+ 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 },
+ { "format_number", E_FORMAT_NUMBER },
+ { "join", E_JOIN },
+ { "split", E_SPLIT },
+ { "basename", E_BASENAME },
+ { "dirname", E_DIRNAME },
+ { "section", E_SECTION },
+ { "find", E_FIND },
+ { "system", E_SYSTEM },
+ { "unique", E_UNIQUE },
+ { "reverse", E_REVERSE },
+ { "quote", E_QUOTE },
+ { "escape_expand", E_ESCAPE_EXPAND },
+ { "upper", E_UPPER },
+ { "lower", E_LOWER },
+ { "re_escape", E_RE_ESCAPE },
+ { "val_escape", E_VAL_ESCAPE },
+ { "files", E_FILES },
+ { "prompt", E_PROMPT },
+ { "replace", E_REPLACE },
+ { "sort_depends", E_SORT_DEPENDS },
+ { "resolve_depends", E_RESOLVE_DEPENDS },
+ { "enumerate_vars", E_ENUMERATE_VARS },
+ { "shadowed", E_SHADOWED },
+ { "absolute_path", E_ABSOLUTE_PATH },
+ { "relative_path", E_RELATIVE_PATH },
+ { "clean_path", E_CLEAN_PATH },
+ { "system_path", E_SYSTEM_PATH },
+ { "shell_path", E_SHELL_PATH },
+ { "system_quote", E_SYSTEM_QUOTE },
+ { "shell_quote", E_SHELL_QUOTE },
+ };
+ for (unsigned i = 0; i < sizeof(expandInits)/sizeof(expandInits[0]); ++i)
+ statics.expands.insert(ProKey(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 },
+ { "log", T_LOG },
+ { "message", T_MESSAGE },
+ { "warning", T_WARNING },
+ { "error", T_ERROR },
+ { "mkpath", T_MKPATH },
+ { "write_file", T_WRITE_FILE },
+ { "touch", T_TOUCH },
+ { "cache", T_CACHE },
+ };
+ for (unsigned i = 0; i < sizeof(testInits)/sizeof(testInits[0]); ++i)
+ statics.functions.insert(ProKey(testInits[i].name), testInits[i].func);
+}
+
+static bool isTrue(const ProString &_str, QString &tmp)
+{
+ const QString &str = _str.toQString(tmp);
+ return !str.compare(statics.strtrue, Qt::CaseInsensitive) || str.toInt();
+}
+
+#ifdef Q_OS_WIN
+static QString windowsErrorCode()
+{
+ wchar_t *string = 0;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPWSTR)&string,
+ 0,
+ NULL);
+ QString ret = QString::fromWCharArray(string);
+ LocalFree((HLOCAL)string);
+ return ret;
+}
+#endif
+
+static QString
+quoteValue(const ProString &val)
+{
+ QString ret;
+ ret.reserve(val.size());
+ const QChar *chars = val.constData();
+ bool quote = val.isEmpty();
+ bool escaping = false;
+ for (int i = 0, l = val.size(); i < l; i++) {
+ QChar c = chars[i];
+ ushort uc = c.unicode();
+ if (uc < 32) {
+ if (!escaping) {
+ escaping = true;
+ ret += QLatin1String("$$escape_expand(");
+ }
+ switch (uc) {
+ case '\r':
+ ret += QLatin1String("\\\\r");
+ break;
+ case '\n':
+ ret += QLatin1String("\\\\n");
+ break;
+ case '\t':
+ ret += QLatin1String("\\\\t");
+ break;
+ default:
+ ret += QString::fromLatin1("\\\\x%1").arg(uc, 2, 16, QLatin1Char('0'));
+ break;
+ }
+ } else {
+ if (escaping) {
+ escaping = false;
+ ret += QLatin1Char(')');
+ }
+ switch (uc) {
+ case '\\':
+ ret += QLatin1String("\\\\");
+ break;
+ case '"':
+ ret += QLatin1String("\\\"");
+ break;
+ case '\'':
+ ret += QLatin1String("\\'");
+ break;
+ case '$':
+ ret += QLatin1String("\\$");
+ break;
+ case '#':
+ ret += QLatin1String("$${LITERAL_HASH}");
+ break;
+ case 32:
+ quote = true;
+ // fallthrough
+ default:
+ ret += c;
+ break;
+ }
+ }
+ }
+ if (escaping)
+ ret += QLatin1Char(')');
+ if (quote) {
+ ret.prepend(QLatin1Char('"'));
+ ret.append(QLatin1Char('"'));
+ }
+ return ret;
+}
+
+static bool
+doWriteFile(const QString &name, QIODevice::OpenMode mode, const QString &contents, QString *errStr)
+{
+ QByteArray bytes = contents.toLocal8Bit();
+ QFile cfile(name);
+ if (!(mode & QIODevice::Append) && cfile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ if (cfile.readAll() == bytes)
+ return true;
+ cfile.close();
+ }
+ if (!cfile.open(mode | QIODevice::WriteOnly | QIODevice::Text)) {
+ *errStr = cfile.errorString();
+ return false;
+ }
+ cfile.write(bytes);
+ cfile.close();
+ if (cfile.error() != QFile::NoError) {
+ *errStr = cfile.errorString();
+ return false;
+ }
+ return true;
+}
+
+QMakeEvaluator::VisitReturn
+QMakeEvaluator::writeFile(const QString &ctx, const QString &fn, QIODevice::OpenMode mode,
+ const QString &contents)
+{
+ QFileInfo qfi(fn);
+ if (!QDir::current().mkpath(qfi.path())) {
+ evalError(fL1S("Cannot create %1directory %2.")
+ .arg(ctx, QDir::toNativeSeparators(qfi.path())));
+ return ReturnFalse;
+ }
+ QString errStr;
+ if (!doWriteFile(qfi.filePath(), mode, contents, &errStr)) {
+ evalError(fL1S("Cannot write %1file %2: %3.")
+ .arg(ctx, QDir::toNativeSeparators(qfi.filePath()), errStr));
+ return ReturnFalse;
+ }
+ return ReturnTrue;
+}
+
+#ifndef QT_BOOTSTRAPPED
+void QMakeEvaluator::runProcess(QProcess *proc, const QString &command) const
+{
+ proc->setWorkingDirectory(currentDirectory());
+# ifdef PROEVALUATOR_SETENV
+ if (!m_option->environment.isEmpty())
+ proc->setProcessEnvironment(m_option->environment);
+# endif
+# 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);
+}
+#endif
+
+QByteArray QMakeEvaluator::getCommandOutput(const QString &args) const
+{
+#ifndef QT_BOOTSTRAPPED
+ QProcess proc;
+ runProcess(&proc, args);
+ QByteArray errout = proc.readAllStandardError();
+# ifdef PROEVALUATOR_FULL
+ // FIXME: Qt really should have the option to set forwarding per channel
+ fputs(errout.constData(), stderr);
+# else
+ if (!errout.isEmpty()) {
+ if (errout.endsWith('\n'))
+ errout.chop(1);
+ m_handler->message(QMakeHandler::EvalError, QString::fromLocal8Bit(errout));
+ }
+# endif
+ return proc.readAllStandardOutput();
+#else
+ QByteArray out;
+ if (FILE *proc = QT_POPEN(QString(QLatin1String("cd ")
+ + IoUtils::shellQuote(QDir::toNativeSeparators(currentDirectory()))
+ + QLatin1String(" && ") + args).toLocal8Bit().constData(), "r")) {
+ while (!feof(proc)) {
+ char buff[10 * 1024];
+ int read_in = int(fread(buff, 1, sizeof(buff), proc));
+ if (!read_in)
+ break;
+ out += QByteArray(buff, read_in);
+ }
+ QT_PCLOSE(proc);
+ }
+ return out;
+#endif
+}
+
+void QMakeEvaluator::populateDeps(
+ const ProStringList &deps, const ProString &prefix,
+ QHash<ProKey, QSet<ProKey> > &dependencies, ProValueMap &dependees,
+ ProStringList &rootSet) const
+{
+ foreach (const ProString &item, deps)
+ if (!dependencies.contains(item.toKey())) {
+ QSet<ProKey> &dset = dependencies[item.toKey()]; // Always create entry
+ ProStringList depends = values(ProKey(prefix + item + QString::fromLatin1(".depends")));
+ if (depends.isEmpty()) {
+ rootSet << item;
+ } else {
+ foreach (const ProString &dep, depends) {
+ dset.insert(dep.toKey());
+ dependees[dep.toKey()] << item;
+ }
+ populateDeps(depends, prefix, dependencies, dependees, rootSet);
+ }
+ }
+}
+
+ProStringList QMakeEvaluator::evaluateBuiltinExpand(
+ const ProKey &func, const ProStringList &args)
+{
+ ProStringList ret;
+
+ traceMsg("calling built-in $$%s(%s)", dbgKey(func), dbgSepStrList(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(ProKey(lfn)));
+ if (func_t)
+ deprecationWarning(fL1S("Using uppercased builtin functions is deprecated."));
+ }
+ }
+ switch (func_t) {
+ case E_BASENAME:
+ case E_DIRNAME:
+ case E_SECTION: {
+ bool regexp = false;
+ QString sep;
+ ProString var;
+ int beg = 0;
+ int end = -1;
+ if (func_t == E_SECTION) {
+ if (args.count() != 3 && args.count() != 4) {
+ 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.at(1).toQString();
+ beg = args.at(2).toQString(m_tmp2).toInt();
+ if (args.count() == 4)
+ end = args.at(3).toQString(m_tmp2).toInt();
+ }
+ } else {
+ if (args.count() != 1) {
+ evalError(fL1S("%1(var) requires one argument.").arg(func.toQString(m_tmp1)));
+ } else {
+ var = args[0];
+ regexp = true;
+ sep = QLatin1String("[\\\\/]");
+ if (func_t == E_DIRNAME)
+ end = -2;
+ else
+ beg = -1;
+ }
+ }
+ 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).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).setSource(str));
+ }
+ }
+ }
+ break;
+ }
+ case E_SPRINTF:
+ if (args.count() < 1) {
+ evalError(fL1S("sprintf(format, ...) requires at least one argument."));
+ } else {
+ QString tmp = args.at(0).toQString(m_tmp1);
+ for (int i = 1; i < args.count(); ++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_FORMAT_NUMBER:
+ if (args.count() > 2) {
+ evalError(fL1S("format_number(number[, options...]) requires one or two arguments."));
+ } else {
+ int ibase = 10;
+ int obase = 10;
+ int width = 0;
+ bool zeropad = false;
+ bool leftalign = false;
+ enum { DefaultSign, PadSign, AlwaysSign } sign = DefaultSign;
+ if (args.count() >= 2) {
+ foreach (const ProString &opt, split_value_list(args.at(1).toQString(m_tmp2))) {
+ opt.toQString(m_tmp3);
+ if (m_tmp3.startsWith(QLatin1String("ibase="))) {
+ ibase = m_tmp3.mid(6).toInt();
+ } else if (m_tmp3.startsWith(QLatin1String("obase="))) {
+ obase = m_tmp3.mid(6).toInt();
+ } else if (m_tmp3.startsWith(QLatin1String("width="))) {
+ width = m_tmp3.mid(6).toInt();
+ } else if (m_tmp3 == QLatin1String("zeropad")) {
+ zeropad = true;
+ } else if (m_tmp3 == QLatin1String("padsign")) {
+ sign = PadSign;
+ } else if (m_tmp3 == QLatin1String("alwayssign")) {
+ sign = AlwaysSign;
+ } else if (m_tmp3 == QLatin1String("leftalign")) {
+ leftalign = true;
+ } else {
+ evalError(fL1S("format_number(): invalid format option %1.").arg(m_tmp3));
+ goto formfail;
+ }
+ }
+ }
+ args.at(0).toQString(m_tmp3);
+ if (m_tmp3.contains(QLatin1Char('.'))) {
+ evalError(fL1S("format_number(): floats are currently not supported."));
+ break;
+ }
+ bool ok;
+ qlonglong num = m_tmp3.toLongLong(&ok, ibase);
+ if (!ok) {
+ evalError(fL1S("format_number(): malformed number %2 for base %1.")
+ .arg(ibase).arg(m_tmp3));
+ break;
+ }
+ QString outstr;
+ if (num < 0) {
+ num = -num;
+ outstr = QLatin1Char('-');
+ } else if (sign == AlwaysSign) {
+ outstr = QLatin1Char('+');
+ } else if (sign == PadSign) {
+ outstr = QLatin1Char(' ');
+ }
+ QString numstr = QString::number(num, obase);
+ int space = width - outstr.length() - numstr.length();
+ if (space <= 0) {
+ outstr += numstr;
+ } else if (leftalign) {
+ outstr += numstr + QString(space, QLatin1Char(' '));
+ } else if (zeropad) {
+ outstr += QString(space, QLatin1Char('0')) + numstr;
+ } else {
+ outstr.prepend(QString(space, QLatin1Char(' ')));
+ outstr += numstr;
+ }
+ ret += ProString(outstr);
+ }
+ formfail:
+ break;
+ case E_JOIN: {
+ if (args.count() < 1 || args.count() > 4) {
+ evalError(fL1S("join(var, glue, before, after) requires one to four arguments."));
+ } else {
+ QString glue;
+ ProString before, after;
+ if (args.count() >= 2)
+ glue = args.at(1).toQString(m_tmp1);
+ if (args.count() >= 3)
+ before = args[2];
+ if (args.count() == 4)
+ after = args[3];
+ 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 = split_value_list(before + var.join(glue) + after, src);
+ }
+ }
+ break;
+ }
+ case E_SPLIT:
+ if (args.count() < 1 || args.count() > 2) {
+ evalError(fL1S("split(var, sep) requires one or two arguments."));
+ } else {
+ 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).setSource(var));
+ }
+ break;
+ case E_MEMBER:
+ if (args.count() < 1 || args.count() > 3) {
+ evalError(fL1S("member(var, start, end) requires one to three arguments."));
+ } else {
+ bool ok = true;
+ const ProStringList &var = values(map(args.at(0)));
+ int start = 0, end = 0;
+ if (args.count() >= 2) {
+ 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(statics.strDotDot);
+ if (dotdot != -1) {
+ start = start_str.left(dotdot).toInt(&ok);
+ if (ok)
+ end = start_str.mid(dotdot+2).toInt(&ok);
+ }
+ }
+ if (!ok)
+ evalError(fL1S("member() argument 2 (start) '%2' invalid.")
+ .arg(start_str));
+ } else {
+ end = start;
+ if (args.count() == 3)
+ end = args.at(2).toQString(m_tmp1).toInt(&ok);
+ if (!ok)
+ evalError(fL1S("member() argument 3 (end) '%2' invalid.")
+ .arg(args.at(2).toQString(m_tmp1)));
+ }
+ }
+ if (ok) {
+ if (start < 0)
+ start += var.count();
+ if (end < 0)
+ end += var.count();
+ if (start < 0 || start >= var.count() || end < 0 || end >= var.count()) {
+ //nothing
+ } else if (start < end) {
+ for (int i = start; i <= end && var.count() >= i; i++)
+ ret.append(var[i]);
+ } else {
+ for (int i = start; i >= end && var.count() >= i && i >= 0; i--)
+ ret += var[i];
+ }
+ }
+ }
+ break;
+ case E_FIRST:
+ case E_LAST:
+ if (args.count() != 1) {
+ evalError(fL1S("%1(var) requires one argument.").arg(func.toQString(m_tmp1)));
+ } else {
+ const ProStringList &var = values(map(args.at(0)));
+ if (!var.isEmpty()) {
+ if (func_t == E_FIRST)
+ ret.append(var[0]);
+ else
+ ret.append(var.last());
+ }
+ }
+ 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())));
+ break;
+ case E_CAT:
+ if (args.count() < 1 || args.count() > 2) {
+ evalError(fL1S("cat(file, singleline=true) requires one or two arguments."));
+ } else {
+ const QString &file = args.at(0).toQString(m_tmp1);
+
+ bool blob = false;
+ bool lines = false;
+ bool singleLine = true;
+ if (args.count() > 1) {
+ args.at(1).toQString(m_tmp2);
+ if (!m_tmp2.compare(QLatin1String("false"), Qt::CaseInsensitive))
+ singleLine = false;
+ else if (!m_tmp2.compare(QLatin1String("blob"), Qt::CaseInsensitive))
+ blob = true;
+ else if (!m_tmp2.compare(QLatin1String("lines"), Qt::CaseInsensitive))
+ lines = true;
+ }
+
+ QFile qfile(resolvePath(m_option->expandEnvVars(file)));
+ if (qfile.open(QIODevice::ReadOnly)) {
+ QTextStream stream(&qfile);
+ if (blob) {
+ ret += ProString(stream.readAll());
+ } else {
+ while (!stream.atEnd()) {
+ if (lines) {
+ ret += ProString(stream.readLine());
+ } else {
+ ret += split_value_list(stream.readLine().trimmed());
+ if (!singleLine)
+ ret += ProString("\n");
+ }
+ }
+ }
+ }
+ }
+ break;
+ case E_FROMFILE:
+ if (args.count() != 2) {
+ evalError(fL1S("fromfile(file, variable) requires two arguments."));
+ } else {
+ ProValueMap vars;
+ QString fn = resolvePath(m_option->expandEnvVars(args.at(0).toQString(m_tmp1)));
+ fn.detach();
+ if (evaluateFileInto(fn, &vars, LoadProOnly))
+ ret = vars.value(map(args.at(1)));
+ }
+ break;
+ case E_EVAL:
+ if (args.count() != 1) {
+ evalError(fL1S("eval(variable) requires one argument."));
+ } else {
+ ret += values(map(args.at(0)));
+ }
+ break;
+ case E_LIST: {
+ QString tmp;
+ tmp.sprintf(".QMAKE_INTERNAL_TMP_variableName_%d", m_listCount++);
+ ret = ProStringList(ProString(tmp));
+ 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).toKey()] = lst;
+ break; }
+ case E_FIND:
+ if (args.count() != 2) {
+ evalError(fL1S("find(var, str) requires two arguments."));
+ } else {
+ 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) {
+ evalError(fL1S("system(execute) requires one or two arguments."));
+ } else {
+ bool blob = false;
+ bool lines = false;
+ bool singleLine = true;
+ if (args.count() > 1) {
+ args.at(1).toQString(m_tmp2);
+ if (!m_tmp2.compare(QLatin1String("false"), Qt::CaseInsensitive))
+ singleLine = false;
+ else if (!m_tmp2.compare(QLatin1String("blob"), Qt::CaseInsensitive))
+ blob = true;
+ else if (!m_tmp2.compare(QLatin1String("lines"), Qt::CaseInsensitive))
+ lines = true;
+ }
+ QByteArray bytes = getCommandOutput(args.at(0).toQString(m_tmp2));
+ if (lines) {
+ QTextStream stream(bytes);
+ while (!stream.atEnd())
+ ret += ProString(stream.readLine());
+ } else {
+ QString output = QString::fromLocal8Bit(bytes);
+ if (blob) {
+ ret += ProString(output);
+ } else {
+ output.replace(QLatin1Char('\t'), QLatin1Char(' '));
+ if (singleLine)
+ output.replace(QLatin1Char('\n'), QLatin1Char(' '));
+ ret += split_value_list(output);
+ }
+ }
+ }
+ }
+ break;
+ case E_UNIQUE:
+ if (args.count() != 1) {
+ evalError(fL1S("unique(var) requires one argument."));
+ } else {
+ ret = values(map(args.at(0)));
+ ret.removeDuplicates();
+ }
+ break;
+ case E_REVERSE:
+ if (args.count() != 1) {
+ evalError(fL1S("reverse(var) requires one argument."));
+ } else {
+ ProStringList var = values(args.at(0).toKey());
+ for (int i = 0; i < var.size() / 2; i++)
+ qSwap(var[i], var[var.size() - i - 1]);
+ ret += var;
+ }
+ break;
+ case E_QUOTE:
+ ret += args;
+ break;
+ case E_ESCAPE_EXPAND:
+ for (int i = 0; i < args.size(); ++i) {
+ 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('\\')) {
+ ++x;
+ } else {
+ struct {
+ char in, out;
+ } mapped_quotes[] = {
+ { 'n', '\n' },
+ { 't', '\t' },
+ { 'r', '\r' },
+ { 0, 0 }
+ };
+ for (int i = 0; mapped_quotes[i].in; ++i) {
+ if (*(i_data+x+1) == QLatin1Char(mapped_quotes[i].in)) {
+ *(i_data+x) = QLatin1Char(mapped_quotes[i].out);
+ if (x < i_len-2)
+ memmove(i_data+x+1, i_data+x+2, (i_len-x-2)*sizeof(QChar));
+ --i_len;
+ break;
+ }
+ }
+ }
+ }
+ }
+ ret.append(ProString(QString(i_data, i_len)).setSource(args.at(i)));
+ }
+ break;
+ case E_RE_ESCAPE:
+ 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).setSource(args.at(i)));
+ }
+ break;
+ case E_VAL_ESCAPE:
+ if (args.count() != 1) {
+ evalError(fL1S("val_escape(var) requires one argument."));
+ } else {
+ const ProStringList &vals = values(args.at(0).toKey());
+ ret.reserve(vals.size());
+ foreach (const ProString &str, vals)
+ ret += ProString(quoteValue(str));
+ }
+ break;
+ case E_UPPER:
+ case E_LOWER:
+ 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).setSource(args.at(i)));
+ }
+ break;
+ case E_FILES:
+ if (args.count() != 1 && args.count() != 2) {
+ evalError(fL1S("files(pattern, recursive=false) requires one or two arguments."));
+ } else {
+ bool recursive = false;
+ if (args.count() == 2)
+ recursive = isTrue(args.at(1), m_tmp2);
+ QStringList dirs;
+ QString r = m_option->expandEnvVars(args.at(0).toQString(m_tmp1))
+ .replace(QLatin1Char('\\'), QLatin1Char('/'));
+ QString pfx;
+ if (IoUtils::isRelativePath(r)) {
+ pfx = currentDirectory();
+ if (!pfx.endsWith(QLatin1Char('/')))
+ pfx += QLatin1Char('/');
+ }
+ int slash = r.lastIndexOf(QLatin1Char('/'));
+ if (slash != -1) {
+ 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
+ QRegExp regex(r, Qt::CaseSensitive, QRegExp::Wildcard);
+ for (int d = 0; d < dirs.count(); d++) {
+ QString dir = dirs[d];
+ QDir qdir(pfx + dir);
+ for (int i = 0; i < (int)qdir.count(); ++i) {
+ if (qdir[i] == statics.strDot || qdir[i] == statics.strDotDot)
+ continue;
+ QString fname = dir + qdir[i];
+ if (IoUtils::fileType(pfx + fname) == IoUtils::FileIsDir) {
+ if (recursive)
+ dirs.append(fname + QLatin1Char('/'));
+ }
+ if (regex.exactMatch(qdir[i]))
+ ret += ProString(fname).setSource(currentProFile());
+ }
+ }
+ }
+ break;
+#ifdef PROEVALUATOR_FULL
+ case E_PROMPT: {
+ if (args.count() != 1) {
+ evalError(fL1S("prompt(question) requires one argument."));
+// } else if (currentFileName() == QLatin1String("-")) {
+// evalError(fL1S("prompt(question) cannot be used when '-o -' is used"));
+ } else {
+ QString msg = m_option->expandEnvVars(args.at(0).toQString(m_tmp1));
+ if (!msg.endsWith(QLatin1Char('?')))
+ msg += QLatin1Char('?');
+ fprintf(stderr, "Project PROMPT: %s ", qPrintable(msg));
+
+ QFile qfile;
+ if (qfile.open(stdin, QIODevice::ReadOnly)) {
+ QTextStream t(&qfile);
+ ret = split_value_list(t.readLine());
+ }
+ }
+ break; }
+#endif
+ case E_REPLACE:
+ if (args.count() != 3 ) {
+ evalError(fL1S("replace(var, before, after) requires three arguments."));
+ } else {
+ 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).setSource(val));
+ }
+ }
+ break;
+ case E_SORT_DEPENDS:
+ case E_RESOLVE_DEPENDS:
+ if (args.count() < 1 || args.count() > 2) {
+ evalError(fL1S("%1(var, prefix) requires one or two arguments.").arg(func.toQString(m_tmp1)));
+ } else {
+ QHash<ProKey, QSet<ProKey> > dependencies;
+ ProValueMap dependees;
+ ProStringList rootSet;
+ ProStringList orgList = values(args.at(0).toKey());
+ populateDeps(orgList, (args.count() < 2 ? ProString() : args.at(1)),
+ dependencies, dependees, rootSet);
+ for (int i = 0; i < rootSet.size(); ++i) {
+ const ProString &item = rootSet.at(i);
+ if ((func_t == E_RESOLVE_DEPENDS) || orgList.contains(item))
+ ret.prepend(item);
+ foreach (const ProString &dep, dependees[item.toKey()]) {
+ QSet<ProKey> &dset = dependencies[dep.toKey()];
+ dset.remove(rootSet.at(i).toKey()); // *Don't* use 'item' - rootSet may have changed!
+ if (dset.isEmpty())
+ rootSet << dep;
+ }
+ }
+ }
+ break;
+ case E_ENUMERATE_VARS: {
+ QSet<ProString> keys;
+ foreach (const ProValueMap &vmap, m_valuemapStack)
+ for (ProValueMap::ConstIterator it = vmap.constBegin(); it != vmap.constEnd(); ++it)
+ keys.insert(it.key());
+ ret.reserve(keys.size());
+ foreach (const ProString &key, keys)
+ ret << key;
+ break; }
+ case E_SHADOWED:
+ if (args.count() != 1) {
+ evalError(fL1S("shadowed(path) requires one argument."));
+ } else {
+ QString rstr = m_option->shadowedPath(resolvePath(args.at(0).toQString(m_tmp1)));
+ if (rstr.isEmpty())
+ break;
+ ret << (rstr.isSharedWith(m_tmp1) ? args.at(0) : ProString(rstr).setSource(args.at(0)));
+ }
+ break;
+ case E_ABSOLUTE_PATH:
+ if (args.count() > 2) {
+ evalError(fL1S("absolute_path(path[, base]) requires one or two arguments."));
+ } else {
+ QString rstr = QDir::cleanPath(
+ QDir(args.count() > 1 ? args.at(1).toQString(m_tmp2) : currentDirectory())
+ .absoluteFilePath(args.at(0).toQString(m_tmp1)));
+ ret << (rstr.isSharedWith(m_tmp1) ? args.at(0) : ProString(rstr).setSource(args.at(0)));
+ }
+ break;
+ case E_RELATIVE_PATH:
+ if (args.count() > 2) {
+ evalError(fL1S("relative_path(path[, base]) requires one or two arguments."));
+ } else {
+ QDir baseDir(args.count() > 1 ? args.at(1).toQString(m_tmp2) : currentDirectory());
+ QString rstr = baseDir.relativeFilePath(baseDir.absoluteFilePath(
+ args.at(0).toQString(m_tmp1)));
+ ret << (rstr.isSharedWith(m_tmp1) ? args.at(0) : ProString(rstr).setSource(args.at(0)));
+ }
+ break;
+ case E_CLEAN_PATH:
+ if (args.count() != 1) {
+ evalError(fL1S("clean_path(path) requires one argument."));
+ } else {
+ QString rstr = QDir::cleanPath(args.at(0).toQString(m_tmp1));
+ ret << (rstr.isSharedWith(m_tmp1) ? args.at(0) : ProString(rstr).setSource(args.at(0)));
+ }
+ break;
+ case E_SYSTEM_PATH:
+ if (args.count() != 1) {
+ evalError(fL1S("system_path(path) requires one argument."));
+ } else {
+ QString rstr = args.at(0).toQString(m_tmp1);
+#ifdef Q_OS_WIN
+ rstr.replace(QLatin1Char('/'), QLatin1Char('\\'));
+#else
+ rstr.replace(QLatin1Char('\\'), QLatin1Char('/'));
+#endif
+ ret << (rstr.isSharedWith(m_tmp1) ? args.at(0) : ProString(rstr).setSource(args.at(0)));
+ }
+ break;
+ case E_SHELL_PATH:
+ if (args.count() != 1) {
+ evalError(fL1S("shell_path(path) requires one argument."));
+ } else {
+ QString rstr = args.at(0).toQString(m_tmp1);
+ if (m_dirSep.startsWith(QLatin1Char('\\')))
+ rstr.replace(QLatin1Char('/'), QLatin1Char('\\'));
+ else
+ rstr.replace(QLatin1Char('\\'), QLatin1Char('/'));
+ ret << (rstr.isSharedWith(m_tmp1) ? args.at(0) : ProString(rstr).setSource(args.at(0)));
+ }
+ break;
+ case E_SYSTEM_QUOTE:
+ if (args.count() != 1) {
+ evalError(fL1S("system_quote(arg) requires one argument."));
+ } else {
+ QString rstr = IoUtils::shellQuote(args.at(0).toQString(m_tmp1));
+ ret << (rstr.isSharedWith(m_tmp1) ? args.at(0) : ProString(rstr).setSource(args.at(0)));
+ }
+ break;
+ case E_SHELL_QUOTE:
+ if (args.count() != 1) {
+ evalError(fL1S("shell_quote(arg) requires one argument."));
+ } else {
+ QString rstr = args.at(0).toQString(m_tmp1);
+ if (m_dirSep.startsWith(QLatin1Char('\\')))
+ rstr = IoUtils::shellQuoteWin(rstr);
+ else
+ rstr = IoUtils::shellQuoteUnix(rstr);
+ ret << (rstr.isSharedWith(m_tmp1) ? args.at(0) : ProString(rstr).setSource(args.at(0)));
+ }
+ break;
+ case E_INVALID:
+ evalError(fL1S("'%1' is not a recognized replace function.")
+ .arg(func.toQString(m_tmp1)));
+ break;
+ default:
+ evalError(fL1S("Function '%1' is not implemented.").arg(func.toQString(m_tmp1)));
+ break;
+ }
+
+ return ret;
+}
+
+QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional(
+ const ProKey &function, const ProStringList &args)
+{
+ traceMsg("calling built-in %s(%s)", dbgKey(function), dbgSepStrList(args));
+
+ TestFunc func_t = (TestFunc)statics.functions.value(function);
+ switch (func_t) {
+ case T_DEFINED: {
+ if (args.count() < 1 || args.count() > 2) {
+ evalError(fL1S("defined(function, [\"test\"|\"replace\"])"
+ " requires one or two arguments."));
+ return ReturnFalse;
+ }
+ const ProKey &var = args.at(0).toKey();
+ if (args.count() > 1) {
+ if (args[1] == QLatin1String("test")) {
+ return returnBool(m_functionDefs.testFunctions.contains(var));
+ } else if (args[1] == QLatin1String("replace")) {
+ return returnBool(m_functionDefs.replaceFunctions.contains(var));
+ } else if (args[1] == QLatin1String("var")) {
+ ProValueMap::Iterator it;
+ return returnBool(findValues(var, &it));
+ }
+ evalError(fL1S("defined(function, type): unexpected type [%1].")
+ .arg(args.at(1).toQString(m_tmp1)));
+ return ReturnFalse;
+ }
+ return returnBool(m_functionDefs.replaceFunctions.contains(var)
+ || m_functionDefs.testFunctions.contains(var));
+ }
+ 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_cumulative)
+ return ReturnTrue;
+ if (m_valuemapStack.size() == 1) {
+ evalError(fL1S("unexpected return()."));
+ return ReturnFalse;
+ }
+ return ReturnReturn;
+ case T_EXPORT: {
+ if (args.count() != 1) {
+ evalError(fL1S("export(variable) requires one argument."));
+ return ReturnFalse;
+ }
+ const ProKey &var = map(args.at(0));
+ for (ProValueMapStack::Iterator vmi = m_valuemapStack.end();
+ --vmi != m_valuemapStack.begin(); ) {
+ ProValueMap::Iterator it = (*vmi).find(var);
+ if (it != (*vmi).end()) {
+ if (it->constBegin() == statics.fakeValue.constBegin()) {
+ // This is stupid, but qmake doesn't propagate deletions
+ m_valuemapStack.first()[var] = ProStringList();
+ } else {
+ m_valuemapStack.first()[var] = *it;
+ }
+ (*vmi).erase(it);
+ while (--vmi != m_valuemapStack.begin())
+ (*vmi).remove(var);
+ break;
+ }
+ }
+ return ReturnTrue;
+ }
+ case T_INFILE:
+ if (args.count() < 2 || args.count() > 3) {
+ evalError(fL1S("infile(file, var, [values]) requires two or three arguments."));
+ } else {
+ ProValueMap vars;
+ QString fn = resolvePath(m_option->expandEnvVars(args.at(0).toQString(m_tmp1)));
+ fn.detach();
+ if (!evaluateFileInto(fn, &vars, LoadProOnly))
+ return ReturnFalse;
+ if (args.count() == 2)
+ return returnBool(vars.contains(map(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 ReturnFalse;
+#ifdef PROEVALUATOR_FULL
+ case T_REQUIRES:
+ checkRequirements(args);
+ return ReturnFalse; // Another qmake breakage
+#endif
+ case T_EVAL: {
+ VisitReturn ret = ReturnFalse;
+ ProFile *pro = m_parser->parsedProBlock(args.join(statics.field_sep),
+ m_current.pro->fileName(), m_current.line);
+ if (pro) {
+ if (m_cumulative || pro->isOk()) {
+ m_locationStack.push(m_current);
+ visitProBlock(pro, pro->tokPtr());
+ ret = ReturnTrue; // This return value is not too useful, but that's qmake
+ m_current = m_locationStack.pop();
+ }
+ pro->deref();
+ }
+ return ret;
+ }
+ case T_BREAK:
+ if (m_skipLevel)
+ return ReturnFalse;
+ if (m_loopLevel)
+ return ReturnBreak;
+ evalError(fL1S("Unexpected break()."));
+ return ReturnFalse;
+ case T_NEXT:
+ if (m_skipLevel)
+ return ReturnFalse;
+ if (m_loopLevel)
+ return ReturnNext;
+ evalError(fL1S("Unexpected next()."));
+ return ReturnFalse;
+ case T_IF: {
+ if (args.count() != 1) {
+ evalError(fL1S("if(condition) requires one argument."));
+ return ReturnFalse;
+ }
+ return returnBool(evaluateConditional(args.at(0).toQString(),
+ m_current.pro->fileName(), m_current.line));
+ }
+ case T_CONFIG: {
+ if (args.count() < 1 || args.count() > 2) {
+ evalError(fL1S("CONFIG(config) requires one or two arguments."));
+ return ReturnFalse;
+ }
+ 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 = values(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()) {
+ return returnBool(configs[i] == args[0]);
+ }
+ }
+ }
+ return ReturnFalse;
+ }
+ case T_CONTAINS: {
+ if (args.count() < 2 || args.count() > 3) {
+ evalError(fL1S("contains(var, val) requires two or three arguments."));
+ return ReturnFalse;
+ }
+
+ 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 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.at(2).toQString(m_tmp3).split(QLatin1Char('|'));
+ for (int i = l.size() - 1; i >= 0; i--) {
+ const ProString val = l[i];
+ for (int mut = 0; mut < mutuals.count(); mut++) {
+ if (val == mutuals[mut].trimmed()) {
+ return returnBool((!regx.isEmpty()
+ && regx.exactMatch(val.toQString(m_tmp2)))
+ || val == qry);
+ }
+ }
+ }
+ }
+ return ReturnFalse;
+ }
+ case T_COUNT: {
+ if (args.count() != 2 && args.count() != 3) {
+ 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) {
+ const ProString &comp = args.at(2);
+ const int val = args.at(1).toQString(m_tmp1).toInt();
+ if (comp == QLatin1String(">") || comp == QLatin1String("greaterThan")) {
+ return returnBool(cnt > val);
+ } else if (comp == QLatin1String(">=")) {
+ return returnBool(cnt >= val);
+ } else if (comp == QLatin1String("<") || comp == QLatin1String("lessThan")) {
+ return returnBool(cnt < val);
+ } else if (comp == QLatin1String("<=")) {
+ return returnBool(cnt <= val);
+ } else if (comp == QLatin1String("equals") || comp == QLatin1String("isEqual")
+ || comp == QLatin1String("=") || comp == QLatin1String("==")) {
+ return returnBool(cnt == val);
+ } else {
+ evalError(fL1S("Unexpected modifier to count(%2).").arg(comp.toQString(m_tmp1)));
+ return ReturnFalse;
+ }
+ }
+ return returnBool(cnt == args.at(1).toQString(m_tmp1).toInt());
+ }
+ case T_GREATERTHAN:
+ case T_LESSTHAN: {
+ if (args.count() != 2) {
+ evalError(fL1S("%1(variable, value) requires two arguments.")
+ .arg(function.toQString(m_tmp1)));
+ return ReturnFalse;
+ }
+ 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
+ int lhs_int = lhs.toInt(&ok);
+ if (ok) {
+ if (func_t == T_GREATERTHAN)
+ return returnBool(lhs_int > rhs_int);
+ return returnBool(lhs_int < rhs_int);
+ }
+ }
+ if (func_t == T_GREATERTHAN)
+ return returnBool(lhs > rhs);
+ return returnBool(lhs < rhs);
+ }
+ case T_EQUALS:
+ if (args.count() != 2) {
+ evalError(fL1S("%1(variable, value) requires two arguments.")
+ .arg(function.toQString(m_tmp1)));
+ return ReturnFalse;
+ }
+ return returnBool(values(map(args.at(0))).join(statics.field_sep)
+ == args.at(1).toQString(m_tmp1));
+ case T_CLEAR: {
+ if (args.count() != 1) {
+ evalError(fL1S("%1(variable) requires one argument.")
+ .arg(function.toQString(m_tmp1)));
+ return ReturnFalse;
+ }
+ ProValueMap *hsh;
+ ProValueMap::Iterator it;
+ const ProKey &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 (args.count() != 1) {
+ evalError(fL1S("%1(variable) requires one argument.")
+ .arg(function.toQString(m_tmp1)));
+ return ReturnFalse;
+ }
+ ProValueMap *hsh;
+ ProValueMap::Iterator it;
+ const ProKey &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 (args.count() < 1 || args.count() > 3) {
+ evalError(fL1S("include(file, [into, [silent]]) requires one, two or three arguments."));
+ return ReturnFalse;
+ }
+ QString parseInto;
+ LoadFlags flags = 0;
+ if (args.count() >= 2) {
+ parseInto = args.at(1).toQString(m_tmp2);
+ if (args.count() >= 3 && isTrue(args.at(2), m_tmp3))
+ flags = LoadSilent;
+ }
+ QString fn = resolvePath(m_option->expandEnvVars(args.at(0).toQString(m_tmp1)));
+ fn.detach();
+ bool ok;
+ if (parseInto.isEmpty()) {
+ ok = evaluateFileChecked(fn, QMakeHandler::EvalIncludeFile, LoadProOnly | flags);
+ } else {
+ ProValueMap symbols;
+ if ((ok = evaluateFileInto(fn, &symbols, LoadAll | flags))) {
+ ProValueMap newMap;
+ for (ProValueMap::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 (ProValueMap::ConstIterator it = symbols.constBegin();
+ it != symbols.constEnd(); ++it) {
+ const QString &ky = it.key().toQString(m_tmp1);
+ if (!ky.startsWith(QLatin1Char('.')))
+ newMap.insert(ProKey(parseInto + QLatin1Char('.') + ky), it.value());
+ }
+ m_valuemapStack.top() = newMap;
+ }
+ }
+ return returnBool(ok || (flags & LoadSilent));
+ }
+ case T_LOAD: {
+ bool ignore_error = false;
+ if (args.count() == 2) {
+ ignore_error = isTrue(args.at(1), m_tmp2);
+ } else if (args.count() != 1) {
+ evalError(fL1S("load(feature) requires one or two arguments."));
+ return ReturnFalse;
+ }
+ return returnBool(evaluateFeatureFile(m_option->expandEnvVars(args.at(0).toQString()),
+ ignore_error) || ignore_error);
+ }
+ case T_DEBUG: {
+#ifdef PROEVALUATOR_DEBUG
+ if (args.count() != 2) {
+ evalError(fL1S("debug(level, message) requires two arguments."));
+ return ReturnFalse;
+ }
+ int level = args.at(0).toInt();
+ if (level <= m_debugLevel) {
+ const QString &msg = m_option->expandEnvVars(args.at(1).toQString(m_tmp2));
+ debugMsg(level, "Project DEBUG: %s", qPrintable(msg));
+ }
+#endif
+ return ReturnTrue;
+ }
+ case T_LOG:
+ case T_ERROR:
+ case T_WARNING:
+ case T_MESSAGE: {
+ if (args.count() != 1) {
+ evalError(fL1S("%1(message) requires one argument.")
+ .arg(function.toQString(m_tmp1)));
+ return ReturnFalse;
+ }
+ const QString &msg = m_option->expandEnvVars(args.at(0).toQString(m_tmp2));
+ if (!m_skipLevel) {
+ if (func_t == T_LOG) {
+#ifdef PROEVALUATOR_FULL
+ fputs(msg.toLatin1().constData(), stderr);
+#endif
+ } else {
+ m_handler->fileMessage(fL1S("Project %1: %2")
+ .arg(function.toQString(m_tmp1).toUpper(), msg));
+ }
+ }
+ return (func_t == T_ERROR && !m_cumulative) ? ReturnError : ReturnTrue;
+ }
+#ifdef PROEVALUATOR_FULL
+ case T_SYSTEM: {
+ if (m_cumulative) // Anything else would be insanity
+ return ReturnFalse;
+ if (args.count() != 1) {
+ evalError(fL1S("system(exec) requires one argument."));
+ return ReturnFalse;
+ }
+#ifndef QT_BOOTSTRAPPED
+ QProcess proc;
+ proc.setProcessChannelMode(QProcess::ForwardedChannels);
+ runProcess(&proc, args.at(0).toQString(m_tmp2));
+ return returnBool(proc.exitStatus() == QProcess::NormalExit && proc.exitCode() == 0);
+#else
+ return returnBool(system((QLatin1String("cd ")
+ + IoUtils::shellQuote(QDir::toNativeSeparators(currentDirectory()))
+ + QLatin1String(" && ") + args.at(0)).toLocal8Bit().constData()) == 0);
+#endif
+ }
+#endif
+ case T_ISEMPTY: {
+ if (args.count() != 1) {
+ evalError(fL1S("isEmpty(var) requires one argument."));
+ return ReturnFalse;
+ }
+ return returnBool(values(map(args.at(0))).isEmpty());
+ }
+ case T_EXISTS: {
+ if (args.count() != 1) {
+ evalError(fL1S("exists(file) requires one argument."));
+ return ReturnFalse;
+ }
+ const QString &file = resolvePath(m_option->expandEnvVars(args.at(0).toQString(m_tmp1)));
+
+ if (IoUtils::exists(file)) {
+ return ReturnTrue;
+ }
+ 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;
+ }
+
+ return ReturnFalse;
+ }
+#ifdef PROEVALUATOR_FULL
+ case T_MKPATH: {
+ if (args.count() != 1) {
+ evalError(fL1S("mkpath(file) requires one argument."));
+ return ReturnFalse;
+ }
+ const QString &fn = resolvePath(args.at(0).toQString(m_tmp1));
+ if (!QDir::current().mkpath(fn)) {
+ evalError(fL1S("Cannot create directory %1.").arg(QDir::toNativeSeparators(fn)));
+ return ReturnFalse;
+ }
+ return ReturnTrue;
+ }
+ case T_WRITE_FILE: {
+ if (args.count() > 3) {
+ evalError(fL1S("write_file(name, [content var, [append]]) requires one to three arguments."));
+ return ReturnFalse;
+ }
+ QIODevice::OpenMode mode = QIODevice::Truncate;
+ QString contents;
+ if (args.count() >= 2) {
+ const ProStringList &vals = values(args.at(1).toKey());
+ if (!vals.isEmpty())
+ contents = vals.join(fL1S("\n")) + QLatin1Char('\n');
+ if (args.count() >= 3)
+ if (!args.at(2).toQString(m_tmp1).compare(fL1S("append"), Qt::CaseInsensitive))
+ mode = QIODevice::Append;
+ }
+ return writeFile(QString(), resolvePath(args.at(0).toQString(m_tmp1)), mode, contents);
+ }
+ case T_TOUCH: {
+ if (args.count() != 2) {
+ evalError(fL1S("touch(file, reffile) requires two arguments."));
+ return ReturnFalse;
+ }
+ const QString &tfn = resolvePath(args.at(0).toQString(m_tmp1));
+ const QString &rfn = resolvePath(args.at(1).toQString(m_tmp2));
+#ifdef Q_OS_UNIX
+ struct stat st;
+ if (stat(rfn.toLocal8Bit().constData(), &st)) {
+ evalError(fL1S("Cannot stat() reference file %1: %2.").arg(rfn, fL1S(strerror(errno))));
+ return ReturnFalse;
+ }
+ struct utimbuf utb;
+ utb.actime = time(0);
+ utb.modtime = st.st_mtime;
+ if (utime(tfn.toLocal8Bit().constData(), &utb)) {
+ evalError(fL1S("Cannot touch %1: %2.").arg(tfn, fL1S(strerror(errno))));
+ return ReturnFalse;
+ }
+#else
+ HANDLE rHand = CreateFile((wchar_t*)rfn.utf16(),
+ GENERIC_READ, FILE_SHARE_READ,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (rHand == INVALID_HANDLE_VALUE) {
+ evalError(fL1S("Cannot open() reference file %1: %2.").arg(rfn, windowsErrorCode()));
+ return ReturnFalse;
+ }
+ FILETIME ft;
+ GetFileTime(rHand, 0, 0, &ft);
+ CloseHandle(rHand);
+ HANDLE wHand = CreateFile((wchar_t*)tfn.utf16(),
+ GENERIC_WRITE, FILE_SHARE_READ,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (wHand == INVALID_HANDLE_VALUE) {
+ evalError(fL1S("Cannot open() %1: %2.").arg(tfn, windowsErrorCode()));
+ return ReturnFalse;
+ }
+ SetFileTime(wHand, 0, 0, &ft);
+ CloseHandle(wHand);
+#endif
+ return ReturnTrue;
+ }
+ case T_CACHE: {
+ if (args.count() > 3) {
+ evalError(fL1S("cache(var, [set|add|sub] [transient] [super], [srcvar]) requires one to three arguments."));
+ return ReturnFalse;
+ }
+ bool persist = true;
+ bool super = false;
+ enum { CacheSet, CacheAdd, CacheSub } mode = CacheSet;
+ ProKey srcvar;
+ if (args.count() >= 2) {
+ foreach (const ProString &opt, split_value_list(args.at(1).toQString(m_tmp2))) {
+ opt.toQString(m_tmp3);
+ if (m_tmp3 == QLatin1String("transient")) {
+ persist = false;
+ } else if (m_tmp3 == QLatin1String("super")) {
+ super = true;
+ } else if (m_tmp3 == QLatin1String("set")) {
+ mode = CacheSet;
+ } else if (m_tmp3 == QLatin1String("add")) {
+ mode = CacheAdd;
+ } else if (m_tmp3 == QLatin1String("sub")) {
+ mode = CacheSub;
+ } else {
+ evalError(fL1S("cache(): invalid flag %1.").arg(m_tmp3));
+ return ReturnFalse;
+ }
+ }
+ if (args.count() >= 3) {
+ srcvar = args.at(2).toKey();
+ } else if (mode != CacheSet) {
+ evalError(fL1S("cache(): modes other than 'set' require a source variable."));
+ return ReturnFalse;
+ }
+ }
+ QString varstr;
+ ProKey dstvar = args.at(0).toKey();
+ if (!dstvar.isEmpty()) {
+ if (srcvar.isEmpty())
+ srcvar = dstvar;
+ ProValueMap::Iterator srcvarIt;
+ if (!findValues(srcvar, &srcvarIt)) {
+ evalError(fL1S("Variable %1 is not defined.").arg(srcvar.toQString(m_tmp1)));
+ return ReturnFalse;
+ }
+ // The caches for the host and target may differ (e.g., when we are manipulating
+ // CONFIG), so we cannot compute a common new value for both.
+ const ProStringList &diffval = *srcvarIt;
+ ProStringList newval;
+ bool changed = false;
+ for (bool hostBuild = false; ; hostBuild = true) {
+ if (QMakeBaseEnv *baseEnv = m_option->baseEnvs.value(
+ QMakeBaseKey(m_buildRoot, hostBuild))) {
+ QMakeEvaluator *baseEval = baseEnv->evaluator;
+ const ProStringList &oldval = baseEval->values(dstvar);
+ if (mode == CacheSet) {
+ newval = diffval;
+ } else {
+ newval = oldval;
+ if (mode == CacheAdd)
+ newval += diffval;
+ else
+ removeEach(&newval, diffval);
+ }
+ if (oldval != newval) {
+ baseEval->valuesRef(dstvar) = newval;
+ if (super) {
+ do {
+ if (dstvar == QLatin1String("QMAKEPATH")) {
+ baseEval->m_qmakepath = newval.toQStringList();
+ baseEval->updateMkspecPaths();
+ } else if (dstvar == QLatin1String("QMAKEFEATURES")) {
+ baseEval->m_qmakefeatures = newval.toQStringList();
+ } else {
+ break;
+ }
+ baseEval->updateFeaturePaths();
+ if (hostBuild == m_hostBuild)
+ m_featureRoots = baseEval->m_featureRoots;
+ } while (false);
+ }
+ changed = true;
+ }
+ }
+ if (hostBuild)
+ break;
+ }
+ // We assume that whatever got the cached value to be what it is now will do so
+ // the next time as well, so we just skip the persisting if nothing changed.
+ if (!persist || !changed)
+ return ReturnTrue;
+ varstr = dstvar.toQString();
+ if (mode == CacheAdd)
+ varstr += QLatin1String(" +=");
+ else if (mode == CacheSub)
+ varstr += QLatin1String(" -=");
+ else
+ varstr += QLatin1String(" =");
+ if (diffval.count() == 1) {
+ varstr += QLatin1Char(' ');
+ varstr += quoteValue(diffval.at(0));
+ } else if (!diffval.isEmpty()) {
+ foreach (const ProString &vval, diffval) {
+ varstr += QLatin1String(" \\\n ");
+ varstr += quoteValue(vval);
+ }
+ }
+ varstr += QLatin1Char('\n');
+ }
+ QString fn;
+ if (super) {
+ if (m_superfile.isEmpty()) {
+ m_superfile = m_outputDir + QLatin1String("/.qmake.super");
+ printf("Info: creating super cache file %s\n", qPrintable(m_superfile));
+ valuesRef(ProKey("_QMAKE_SUPER_CACHE_")) << ProString(m_superfile);
+ }
+ fn = m_superfile;
+ } else {
+ if (m_cachefile.isEmpty()) {
+ m_cachefile = m_outputDir + QLatin1String("/.qmake.cache");
+ printf("Info: creating cache file %s\n", qPrintable(m_cachefile));
+ valuesRef(ProKey("_QMAKE_CACHE_")) << ProString(m_cachefile);
+ // We could update m_{source,build}Root and m_featureRoots here, or even
+ // "re-home" our rootEnv, but this doesn't sound too useful - if somebody
+ // wanted qmake to find something in the build directory, he could have
+ // done so "from the outside".
+ // The sub-projects will find the new cache all by themselves.
+ }
+ fn = m_cachefile;
+ }
+ return writeFile(fL1S("cache "), fn, QIODevice::Append, varstr);
+ }
+#endif
+ case T_INVALID:
+ evalError(fL1S("'%1' is not a recognized test function.")
+ .arg(function.toQString(m_tmp1)));
+ return ReturnFalse;
+ default:
+ evalError(fL1S("Function '%1' is not implemented.").arg(function.toQString(m_tmp1)));
+ return ReturnFalse;
+ }
+}
+
+QT_END_NAMESPACE