forked from qt-creator/qt-creator
if this is enabled, the evaluator implements all functionality necessary for qmake. without it, functionality which is unnecessary or outright dangerous in qt creator or lupdate context is omitted. Change-Id: I233cfa6daf2e4451f9f1d3d14711be2a7cfe4293 Reviewed-by: Daniel Teske <daniel.teske@nokia.com>
1113 lines
42 KiB
C++
1113 lines
42 KiB
C++
/**************************************************************************
|
|
**
|
|
** This file is part of Qt Creator
|
|
**
|
|
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
|
|
**
|
|
** Contact: Nokia Corporation (qt-info@nokia.com)
|
|
**
|
|
**
|
|
** 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.
|
|
**
|
|
** 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.
|
|
**
|
|
** If you have questions regarding the use of this file, please contact
|
|
** Nokia at qt-info@nokia.com.
|
|
**
|
|
**************************************************************************/
|
|
|
|
#include "qmakeevaluator.h"
|
|
|
|
#include "qmakeevaluator_p.h"
|
|
#include "qmakeglobals.h"
|
|
#include "qmakeparser.h"
|
|
#include "ioutils.h"
|
|
|
|
#include <QByteArray>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QList>
|
|
#include <QRegExp>
|
|
#include <QSet>
|
|
#include <QStringList>
|
|
#include <QTextStream>
|
|
|
|
#ifdef Q_OS_UNIX
|
|
#include <unistd.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 ProFileEvaluatorInternal;
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
using namespace ProStringConstants;
|
|
|
|
|
|
#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_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, E_SORT_DEPENDS, E_RESOLVE_DEPENDS
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
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 },
|
|
{ "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 },
|
|
{ "sort_depends", E_SORT_DEPENDS },
|
|
{ "resolve_depends", E_RESOLVE_DEPENDS }
|
|
};
|
|
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 bool isTrue(const ProString &_str, QString &tmp)
|
|
{
|
|
const QString &str = _str.toQString(tmp);
|
|
return !str.compare(statics.strtrue, Qt::CaseInsensitive) || str.toInt();
|
|
}
|
|
|
|
#ifndef QT_BOOTSTRAPPED
|
|
void QMakeEvaluator::runProcess(QProcess *proc, const QString &command,
|
|
QProcess::ProcessChannel chan) const
|
|
{
|
|
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
|
|
|
|
void QMakeEvaluator::populateDeps(
|
|
const ProStringList &deps, const ProString &prefix,
|
|
QHash<ProString, QSet<ProString> > &dependencies, ProValueMap &dependees,
|
|
ProStringList &rootSet) const
|
|
{
|
|
foreach (const ProString &item, deps)
|
|
if (!dependencies.contains(item)) {
|
|
QSet<ProString> &dset = dependencies[item]; // Always create entry
|
|
ProStringList depends = values(ProString(prefix + item + QString::fromLatin1(".depends")));
|
|
if (depends.isEmpty()) {
|
|
rootSet << item;
|
|
} else {
|
|
foreach (const ProString &dep, depends) {
|
|
dset.insert(dep);
|
|
dependees[dep] << item;
|
|
}
|
|
populateDeps(depends, prefix, dependencies, dependees, rootSet);
|
|
}
|
|
}
|
|
}
|
|
|
|
ProStringList QMakeEvaluator::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;
|
|
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, 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) {
|
|
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_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() != 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, NoHash).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.\n")
|
|
.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()), NoHash));
|
|
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 singleLine = true;
|
|
if (args.count() > 1)
|
|
singleLine = isTrue(args.at(1), m_tmp2);
|
|
|
|
QFile qfile(resolvePath(m_option->expandEnvVars(file)));
|
|
if (qfile.open(QIODevice::ReadOnly)) {
|
|
QTextStream stream(&qfile);
|
|
while (!stream.atEnd()) {
|
|
ret += split_value_list(stream.readLine().trimmed());
|
|
if (!singleLine)
|
|
ret += ProString("\n", NoHash);
|
|
}
|
|
qfile.close();
|
|
}
|
|
}
|
|
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, QMakeHandler::EvalAuxFile, &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, 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) {
|
|
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 singleLine = true;
|
|
if (args.count() > 1)
|
|
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)
|
|
break;
|
|
for (int i = 0; i < read_in; i++) {
|
|
if ((singleLine && buff[i] == '\n') || buff[i] == '\t')
|
|
buff[i] = ' ';
|
|
}
|
|
output.append(buff, read_in);
|
|
}
|
|
if (proc)
|
|
QT_PCLOSE(proc);
|
|
#endif
|
|
ret += split_value_list(QString::fromLocal8Bit(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_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), NoHash).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, NoHash).setSource(args.at(i)));
|
|
}
|
|
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, NoHash).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 = 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+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 + QDir::separator());
|
|
}
|
|
if (regex.exactMatch(qdir[i]))
|
|
ret += ProString(fname, NoHash).setSource(currentProFile());
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
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, NoHash).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<ProString, QSet<ProString> > dependencies;
|
|
ProValueMap dependees;
|
|
ProStringList rootSet;
|
|
ProStringList orgList = values(args.at(0));
|
|
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]) {
|
|
QSet<ProString> &dset = dependencies[dep];
|
|
dset.remove(rootSet.at(i)); // *Don't* use 'item' - rootSet may have changed!
|
|
if (dset.isEmpty())
|
|
rootSet << dep;
|
|
}
|
|
}
|
|
}
|
|
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::evaluateConditionalFunction(
|
|
const ProString &function, const ProStringList &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;
|
|
}
|
|
if (args.count() > 1) {
|
|
if (args[1] == QLatin1String("test"))
|
|
return returnBool(m_functionDefs.testFunctions.contains(args[0]));
|
|
else if (args[1] == QLatin1String("replace"))
|
|
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_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 ReturnTrue;
|
|
if (m_valuemapStack.isEmpty()) {
|
|
evalError(fL1S("unexpected return()."));
|
|
return ReturnFalse;
|
|
}
|
|
return ReturnReturn;
|
|
case T_EXPORT: {
|
|
if (m_skipLevel && !m_cumulative)
|
|
return ReturnTrue;
|
|
if (args.count() != 1) {
|
|
evalError(fL1S("export(variable) requires one argument."));
|
|
return ReturnFalse;
|
|
}
|
|
const ProString &var = map(args.at(0));
|
|
for (int i = m_valuemapStack.size(); --i > 0; ) {
|
|
ProValueMap::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;
|
|
}
|
|
}
|
|
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, QMakeHandler::EvalAuxFile, &vars, LoadProOnly))
|
|
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 ReturnFalse;
|
|
#if 0
|
|
case T_REQUIRES:
|
|
#endif
|
|
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;
|
|
}
|
|
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 (m_skipLevel && !m_cumulative)
|
|
return ReturnFalse;
|
|
if (args.count() != 1) {
|
|
evalError(fL1S("if(condition) requires one argument."));
|
|
return ReturnFalse;
|
|
}
|
|
const ProString &cond = args.at(0);
|
|
bool quoted = false;
|
|
bool ret = true;
|
|
bool orOp = false;
|
|
bool invert = false;
|
|
bool isFunc = false;
|
|
int parens = 0;
|
|
QString test;
|
|
test.reserve(20);
|
|
QString argsString;
|
|
argsString.reserve(50);
|
|
const QChar *d = cond.constData();
|
|
const QChar *ed = d + cond.size();
|
|
while (d < ed) {
|
|
ushort c = (d++)->unicode();
|
|
bool isOp = false;
|
|
if (quoted) {
|
|
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 {
|
|
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 = 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) {
|
|
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 (m_skipLevel && !m_cumulative)
|
|
return ReturnFalse;
|
|
if (args.count() != 1) {
|
|
evalError(fL1S("%1(variable) requires one argument.")
|
|
.arg(function.toQString(m_tmp1)));
|
|
return ReturnFalse;
|
|
}
|
|
ProValueMap *hsh;
|
|
ProValueMap::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 ReturnFalse;
|
|
if (args.count() != 1) {
|
|
evalError(fL1S("%1(variable) requires one argument.")
|
|
.arg(function.toQString(m_tmp1)));
|
|
return ReturnFalse;
|
|
}
|
|
ProValueMap *hsh;
|
|
ProValueMap::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 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.at(1).toQString(m_tmp2);
|
|
} else if (args.count() != 1) {
|
|
evalError(fL1S("include(file, into, silent) requires one, two or three arguments."));
|
|
return ReturnFalse;
|
|
}
|
|
QString fn = resolvePath(m_option->expandEnvVars(args.at(0).toQString(m_tmp1)));
|
|
fn.detach();
|
|
bool ok;
|
|
if (parseInto.isEmpty()) {
|
|
ok = evaluateFile(fn, QMakeHandler::EvalIncludeFile, LoadProOnly);
|
|
} else {
|
|
ProValueMap symbols;
|
|
if ((ok = evaluateFileInto(fn, QMakeHandler::EvalAuxFile, &symbols, LoadAll))) {
|
|
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(ProString(parseInto + QLatin1Char('.') + ky), it.value());
|
|
}
|
|
m_valuemapStack.top() = newMap;
|
|
}
|
|
}
|
|
return returnBool(ok);
|
|
}
|
|
case T_LOAD: {
|
|
if (m_skipLevel && !m_cumulative)
|
|
return ReturnFalse;
|
|
// 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;
|
|
}
|
|
// XXX ignore_error unused
|
|
return returnBool(evaluateFeatureFile(m_option->expandEnvVars(args.at(0).toQString())));
|
|
}
|
|
case T_DEBUG:
|
|
// Yup - do nothing. Nothing is going to enable debug output anyway.
|
|
return ReturnFalse;
|
|
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)
|
|
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"));
|
|
}
|
|
#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::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) {
|
|
evalError(fL1S("isEmpty(var) requires one argument."));
|
|
return ReturnFalse;
|
|
}
|
|
const ProStringList &sl = values(map(args.at(0)));
|
|
if (sl.count() == 0) {
|
|
return ReturnTrue;
|
|
} else if (sl.count() > 0) {
|
|
const ProString &var = sl.first();
|
|
if (var.isEmpty())
|
|
return ReturnTrue;
|
|
}
|
|
return ReturnFalse;
|
|
}
|
|
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;
|
|
}
|
|
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
|