qt5base-lts/qmake/project.cpp
Oswald Buddenhagen e07372ff50 fix host vs. makefile directory separator mess
the system path separator and shell are bound to the host system
(system() will use cmd even on mingw with sh.exe in path).
the makefiles otoh may depend on what the qmakespec defines.

consequently, add $$system_path() and $$system_quote() (for use with
system() & $$system()). $$native_path() is renamed to $$shell_path() and
should be used with $$shell_quote() to produce command lines in
makefiles.
$$QMAKE_DIR_SEP needs to be applied to Option::dir_sep right after
parsing the spec, so it is available to $$shell_{path,quote}().

Change-Id: If3db4849e7f96068cf03a32348a24f3a72d6292c
Reviewed-by: Rafael Roquetto <rafael.roquetto@kdab.com>
Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
2012-07-28 00:09:45 +02:00

3811 lines
145 KiB
C++

/****************************************************************************
**
** 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 "project.h"
#include "property.h"
#include "option.h"
#include "cachekeys.h"
#include "generators/metamakefile.h"
#include <qdatetime.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qdir.h>
#include <qregexp.h>
#include <qtextstream.h>
#include <qstack.h>
#include <qdebug.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>
#elif defined(Q_OS_WIN32)
#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
QT_BEGIN_NAMESPACE
//expand functions
enum ExpandFunc { E_MEMBER=1, E_FIRST, E_LAST, 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_SIZE, 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 };
QHash<QString, ExpandFunc> qmake_expandFunctions()
{
static QHash<QString, ExpandFunc> *qmake_expand_functions = 0;
if(!qmake_expand_functions) {
qmake_expand_functions = new QHash<QString, ExpandFunc>;
qmakeAddCacheClear(qmakeDeleteCacheClear<QHash<QString, ExpandFunc> >, (void**)&qmake_expand_functions);
qmake_expand_functions->insert("member", E_MEMBER);
qmake_expand_functions->insert("first", E_FIRST);
qmake_expand_functions->insert("last", E_LAST);
qmake_expand_functions->insert("cat", E_CAT);
qmake_expand_functions->insert("fromfile", E_FROMFILE);
qmake_expand_functions->insert("eval", E_EVAL);
qmake_expand_functions->insert("list", E_LIST);
qmake_expand_functions->insert("sprintf", E_SPRINTF);
qmake_expand_functions->insert("format_number", E_FORMAT_NUMBER);
qmake_expand_functions->insert("join", E_JOIN);
qmake_expand_functions->insert("split", E_SPLIT);
qmake_expand_functions->insert("basename", E_BASENAME);
qmake_expand_functions->insert("dirname", E_DIRNAME);
qmake_expand_functions->insert("section", E_SECTION);
qmake_expand_functions->insert("find", E_FIND);
qmake_expand_functions->insert("system", E_SYSTEM);
qmake_expand_functions->insert("unique", E_UNIQUE);
qmake_expand_functions->insert("reverse", E_REVERSE);
qmake_expand_functions->insert("quote", E_QUOTE);
qmake_expand_functions->insert("escape_expand", E_ESCAPE_EXPAND);
qmake_expand_functions->insert("upper", E_UPPER);
qmake_expand_functions->insert("lower", E_LOWER);
qmake_expand_functions->insert("re_escape", E_RE_ESCAPE);
qmake_expand_functions->insert("val_escape", E_VAL_ESCAPE);
qmake_expand_functions->insert("files", E_FILES);
qmake_expand_functions->insert("prompt", E_PROMPT);
qmake_expand_functions->insert("replace", E_REPLACE);
qmake_expand_functions->insert("size", E_SIZE);
qmake_expand_functions->insert("sort_depends", E_SORT_DEPENDS);
qmake_expand_functions->insert("resolve_depends", E_RESOLVE_DEPENDS);
qmake_expand_functions->insert("enumerate_vars", E_ENUMERATE_VARS);
qmake_expand_functions->insert("shadowed", E_SHADOWED);
qmake_expand_functions->insert("absolute_path", E_ABSOLUTE_PATH);
qmake_expand_functions->insert("relative_path", E_RELATIVE_PATH);
qmake_expand_functions->insert("clean_path", E_CLEAN_PATH);
qmake_expand_functions->insert("system_path", E_SYSTEM_PATH);
qmake_expand_functions->insert("shell_path", E_SHELL_PATH);
qmake_expand_functions->insert("system_quote", E_SYSTEM_QUOTE);
qmake_expand_functions->insert("shell_quote", E_SHELL_QUOTE);
}
return *qmake_expand_functions;
}
//replace functions
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_ERROR, T_MESSAGE, T_WARNING, T_LOG,
T_IF, T_OPTION, T_CACHE, T_MKPATH, T_WRITE_FILE, T_TOUCH };
QHash<QString, TestFunc> qmake_testFunctions()
{
static QHash<QString, TestFunc> *qmake_test_functions = 0;
if(!qmake_test_functions) {
qmake_test_functions = new QHash<QString, TestFunc>;
qmake_test_functions->insert("requires", T_REQUIRES);
qmake_test_functions->insert("greaterThan", T_GREATERTHAN);
qmake_test_functions->insert("lessThan", T_LESSTHAN);
qmake_test_functions->insert("equals", T_EQUALS);
qmake_test_functions->insert("isEqual", T_EQUALS);
qmake_test_functions->insert("exists", T_EXISTS);
qmake_test_functions->insert("export", T_EXPORT);
qmake_test_functions->insert("clear", T_CLEAR);
qmake_test_functions->insert("unset", T_UNSET);
qmake_test_functions->insert("eval", T_EVAL);
qmake_test_functions->insert("CONFIG", T_CONFIG);
qmake_test_functions->insert("if", T_IF);
qmake_test_functions->insert("isActiveConfig", T_CONFIG);
qmake_test_functions->insert("system", T_SYSTEM);
qmake_test_functions->insert("return", T_RETURN);
qmake_test_functions->insert("break", T_BREAK);
qmake_test_functions->insert("next", T_NEXT);
qmake_test_functions->insert("defined", T_DEFINED);
qmake_test_functions->insert("contains", T_CONTAINS);
qmake_test_functions->insert("infile", T_INFILE);
qmake_test_functions->insert("count", T_COUNT);
qmake_test_functions->insert("isEmpty", T_ISEMPTY);
qmake_test_functions->insert("include", T_INCLUDE);
qmake_test_functions->insert("load", T_LOAD);
qmake_test_functions->insert("debug", T_DEBUG);
qmake_test_functions->insert("error", T_ERROR);
qmake_test_functions->insert("message", T_MESSAGE);
qmake_test_functions->insert("warning", T_WARNING);
qmake_test_functions->insert("log", T_LOG);
qmake_test_functions->insert("option", T_OPTION);
qmake_test_functions->insert("cache", T_CACHE);
qmake_test_functions->insert("mkpath", T_MKPATH);
qmake_test_functions->insert("write_file", T_WRITE_FILE);
qmake_test_functions->insert("touch", T_TOUCH);
}
return *qmake_test_functions;
}
struct parser_info {
QString file;
int line_no;
bool from_file;
} parser;
static QString cached_source_root;
static QString cached_build_root;
static QStringList cached_qmakepath;
static QStringList cached_qmakefeatures;
static QStringList *all_feature_roots[2] = { 0, 0 };
static void
invalidateFeatureRoots()
{
for (int i = 0; i < 2; i++)
if (all_feature_roots[i])
all_feature_roots[i]->clear();
}
static QString remove_quotes(const QString &arg)
{
const ushort SINGLEQUOTE = '\'';
const ushort DOUBLEQUOTE = '"';
const QChar *arg_data = arg.data();
const ushort first = arg_data->unicode();
const int arg_len = arg.length();
if(first == SINGLEQUOTE || first == DOUBLEQUOTE) {
const ushort last = (arg_data+arg_len-1)->unicode();
if(last == first)
return arg.mid(1, arg_len-2);
}
return arg;
}
static QString varMap(const QString &x)
{
QString ret(x);
if(ret == "INTERFACES")
ret = "FORMS";
else if(ret == "QMAKE_POST_BUILD")
ret = "QMAKE_POST_LINK";
else if(ret == "TARGETDEPS")
ret = "POST_TARGETDEPS";
else if(ret == "LIBPATH")
ret = "QMAKE_LIBDIR";
else if(ret == "QMAKE_EXT_MOC")
ret = "QMAKE_EXT_CPP_MOC";
else if(ret == "QMAKE_MOD_MOC")
ret = "QMAKE_H_MOD_MOC";
else if(ret == "QMAKE_LFLAGS_SHAPP")
ret = "QMAKE_LFLAGS_APP";
else if(ret == "PRECOMPH")
ret = "PRECOMPILED_HEADER";
else if(ret == "PRECOMPCPP")
ret = "PRECOMPILED_SOURCE";
else if(ret == "INCPATH")
ret = "INCLUDEPATH";
else if(ret == "QMAKE_EXTRA_WIN_COMPILERS" || ret == "QMAKE_EXTRA_UNIX_COMPILERS")
ret = "QMAKE_EXTRA_COMPILERS";
else if(ret == "QMAKE_EXTRA_WIN_TARGETS" || ret == "QMAKE_EXTRA_UNIX_TARGETS")
ret = "QMAKE_EXTRA_TARGETS";
else if(ret == "QMAKE_EXTRA_UNIX_INCLUDES")
ret = "QMAKE_EXTRA_INCLUDES";
else if(ret == "QMAKE_EXTRA_UNIX_VARIABLES")
ret = "QMAKE_EXTRA_VARIABLES";
else if(ret == "QMAKE_RPATH")
ret = "QMAKE_LFLAGS_RPATH";
else if(ret == "QMAKE_FRAMEWORKDIR")
ret = "QMAKE_FRAMEWORKPATH";
else if(ret == "QMAKE_FRAMEWORKDIR_FLAGS")
ret = "QMAKE_FRAMEWORKPATH_FLAGS";
else if(ret == "IN_PWD")
ret = "PWD";
else
return ret;
warn_msg(WarnDeprecated, "%s:%d: Variable %s is deprecated; use %s instead.",
parser.file.toLatin1().constData(), parser.line_no,
x.toLatin1().constData(), ret.toLatin1().constData());
return ret;
}
static QStringList split_arg_list(const QString &params)
{
int quote = 0;
QStringList args;
const ushort LPAREN = '(';
const ushort RPAREN = ')';
const ushort SINGLEQUOTE = '\'';
const ushort DOUBLEQUOTE = '"';
const ushort BACKSLASH = '\\';
const ushort COMMA = ',';
const ushort SPACE = ' ';
//const ushort TAB = '\t';
const QChar *params_data = params.data();
const int params_len = params.length();
for(int last = 0; ;) {
while(last < params_len && (params_data[last].unicode() == SPACE
/*|| params_data[last].unicode() == TAB*/))
++last;
for(int x = last, parens = 0; ; x++) {
if(x == params_len) {
while(x > last && params_data[x-1].unicode() == SPACE)
--x;
args << params.mid(last, x - last);
// Could do a check for unmatched parens here, but split_value_list()
// is called on all our output, so mistakes will be caught anyway.
return args;
}
ushort unicode = params_data[x].unicode();
if(x != (int)params_len-1 && unicode == BACKSLASH &&
(params_data[x+1].unicode() == SINGLEQUOTE || params_data[x+1].unicode() == DOUBLEQUOTE)) {
x++; //get that 'escape'
} else if(quote && unicode == quote) {
quote = 0;
} else if(!quote && (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE)) {
quote = unicode;
} else if(unicode == RPAREN) {
--parens;
} else if(unicode == LPAREN) {
++parens;
}
if(!parens && !quote && unicode == COMMA) {
int prev = last;
last = x+1;
while(x > prev && params_data[x-1].unicode() == SPACE)
--x;
args << params.mid(prev, x - prev);
break;
}
}
}
}
static QStringList split_value_list(const QString &vals)
{
QString build;
QStringList ret;
ushort quote = 0;
int parens = 0;
const ushort LPAREN = '(';
const ushort RPAREN = ')';
const ushort SINGLEQUOTE = '\'';
const ushort DOUBLEQUOTE = '"';
const ushort BACKSLASH = '\\';
ushort unicode;
const QChar *vals_data = vals.data();
const int vals_len = vals.length();
for(int x = 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 && unicode == quote) {
quote = 0;
} else if(!quote && (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE)) {
quote = unicode;
} else if(unicode == RPAREN) {
--parens;
} else if(unicode == LPAREN) {
++parens;
}
if(!parens && !quote && (vals_data[x] == Option::field_sep)) {
ret << build;
build.clear();
} else {
build += vals_data[x];
}
}
if(!build.isEmpty())
ret << build;
if (parens)
warn_msg(WarnDeprecated, "%s:%d: Unmatched parentheses are deprecated.",
parser.file.toLatin1().constData(), parser.line_no);
// Could do a check for unmatched quotes here, but doVariableReplaceExpand()
// is called on all our output, so mistakes will be caught anyway.
return ret;
}
//just a parsable entity
struct ParsableBlock
{
ParsableBlock() : ref_cnt(1) { }
virtual ~ParsableBlock() { }
struct Parse {
QString text;
parser_info pi;
Parse(const QString &t) : text(t){ pi = parser; }
};
QList<Parse> parselist;
inline int ref() { return ++ref_cnt; }
inline int deref() { return --ref_cnt; }
protected:
int ref_cnt;
virtual bool continueBlock() = 0;
bool eval(QMakeProject *p, QHash<QString, QStringList> &place);
};
bool ParsableBlock::eval(QMakeProject *p, QHash<QString, QStringList> &place)
{
//save state
parser_info pi = parser;
const int block_count = p->scope_blocks.count();
//execute
bool ret = true;
for(int i = 0; i < parselist.count(); i++) {
parser = parselist.at(i).pi;
if(!(ret = p->parse(parselist.at(i).text, place)) || !continueBlock())
break;
}
//restore state
parser = pi;
while(p->scope_blocks.count() > block_count)
p->scope_blocks.pop();
return ret;
}
//defined functions
struct FunctionBlock : public ParsableBlock
{
FunctionBlock() : calling_place(0), scope_level(1), cause_return(false) { }
QHash<QString, QStringList> vars;
QHash<QString, QStringList> *calling_place;
QStringList return_value;
int scope_level;
bool cause_return;
bool exec(const QList<QStringList> &args,
QMakeProject *p, QHash<QString, QStringList> &place, QStringList &functionReturn);
virtual bool continueBlock() { return !cause_return; }
};
bool FunctionBlock::exec(const QList<QStringList> &args,
QMakeProject *proj, QHash<QString, QStringList> &place,
QStringList &functionReturn)
{
//save state
#if 1
calling_place = &place;
#else
calling_place = &proj->variables();
#endif
return_value.clear();
cause_return = false;
//execute
#if 0
vars = proj->variables(); // should be place so that local variables can be inherited
#else
vars = place;
#endif
vars["ARGS"].clear();
for(int i = 0; i < args.count(); i++) {
vars["ARGS"] += args[i];
vars[QString::number(i+1)] = args[i];
}
bool ret = ParsableBlock::eval(proj, vars);
functionReturn = return_value;
//restore state
calling_place = 0;
return_value.clear();
vars.clear();
return ret;
}
//loops
struct IteratorBlock : public ParsableBlock
{
IteratorBlock() : scope_level(1), loop_forever(false), cause_break(false), cause_next(false) { }
int scope_level;
struct Test {
QString func;
QStringList args;
bool invert;
parser_info pi;
Test(const QString &f, QStringList &a, bool i) : func(f), args(a), invert(i) { pi = parser; }
};
QList<Test> test;
QString variable;
bool loop_forever, cause_break, cause_next;
QStringList list;
bool exec(QMakeProject *p, QHash<QString, QStringList> &place);
virtual bool continueBlock() { return !cause_next && !cause_break; }
};
bool IteratorBlock::exec(QMakeProject *p, QHash<QString, QStringList> &place)
{
bool ret = true;
QStringList::Iterator it;
if(!loop_forever)
it = list.begin();
int iterate_count = 0;
//save state
IteratorBlock *saved_iterator = p->iterator;
p->iterator = this;
//do the loop
while(loop_forever || it != list.end()) {
cause_next = cause_break = false;
if(!loop_forever && (*it).isEmpty()) { //ignore empty items
++it;
continue;
}
//set up the loop variable
QStringList va;
if(!variable.isEmpty()) {
va = place[variable];
if(loop_forever)
place[variable] = QStringList(QString::number(iterate_count));
else
place[variable] = QStringList(*it);
}
//do the iterations
bool succeed = true;
for(QList<Test>::Iterator test_it = test.begin(); test_it != test.end(); ++test_it) {
parser = (*test_it).pi;
succeed = p->doProjectTest((*test_it).func, (*test_it).args, place);
if((*test_it).invert)
succeed = !succeed;
if(!succeed)
break;
}
if(succeed)
ret = ParsableBlock::eval(p, place);
//restore the variable in the map
if(!variable.isEmpty())
place[variable] = va;
//loop counters
if(!loop_forever)
++it;
iterate_count++;
if(!ret || cause_break)
break;
}
//restore state
p->iterator = saved_iterator;
return ret;
}
QMakeProject::ScopeBlock::~ScopeBlock()
{
#if 0
if(iterate)
delete iterate;
#endif
}
static void qmake_error_msg(const QString &msg)
{
fprintf(stderr, "%s:%d: %s\n", parser.file.toLatin1().constData(), parser.line_no,
msg.toLatin1().constData());
}
/*
1) environment variable QMAKEFEATURES (as separated by colons)
2) property variable QMAKEFEATURES (as separated by colons)
3) <project_root> (where .qmake.cache lives) + FEATURES_DIR
4) environment variable QMAKEPATH (as separated by colons) + /mkspecs/FEATURES_DIR
5) your QMAKESPEC/features dir
6) your data_install/mkspecs/FEATURES_DIR
7) your QMAKESPEC/../FEATURES_DIR dir
FEATURES_DIR is defined as:
1) features/(unix|win32|macx)/
2) features/
*/
QStringList QMakeProject::qmakeFeaturePaths()
{
const QString mkspecs_concat = QLatin1String("/mkspecs");
const QString base_concat = QLatin1String("/features");
QStringList concat;
foreach (const QString &sfx, values("QMAKE_PLATFORM"))
concat << base_concat + QLatin1Char('/') + sfx;
concat << base_concat;
QStringList feature_roots = splitPathList(QString::fromLocal8Bit(qgetenv("QMAKEFEATURES")));
feature_roots += cached_qmakefeatures;
if(prop)
feature_roots += splitPathList(prop->value("QMAKEFEATURES"));
QStringList feature_bases;
if (!cached_build_root.isEmpty())
feature_bases << cached_build_root;
if (!cached_source_root.isEmpty())
feature_bases << cached_source_root;
QStringList qmakepath = splitPathList(QString::fromLocal8Bit(qgetenv("QMAKEPATH")));
qmakepath += cached_qmakepath;
foreach (const QString &path, qmakepath)
feature_bases << (path + mkspecs_concat);
if (!real_spec.isEmpty()) {
// The spec is already platform-dependent, so no subdirs here.
feature_roots << real_spec + base_concat;
// Also check directly under the root directory of the mkspecs collection
QFileInfo specfi(real_spec);
QDir specrootdir(specfi.absolutePath());
while (!specrootdir.isRoot()) {
const QString specrootpath = specrootdir.path();
if (specrootpath.endsWith(mkspecs_concat)) {
if (QFile::exists(specrootpath + base_concat))
feature_bases << specrootpath;
break;
}
specrootdir.cdUp();
}
}
feature_bases << (QLibraryInfo::rawLocation(QLibraryInfo::HostDataPath,
QLibraryInfo::EffectivePaths) + mkspecs_concat);
foreach (const QString &fb, feature_bases)
foreach (const QString &cc, concat)
feature_roots << (fb + cc);
feature_roots.removeDuplicates();
QStringList ret;
foreach (const QString &root, feature_roots)
if (QFileInfo(root).exists())
ret << root;
return ret;
}
QStringList qmake_mkspec_paths()
{
QStringList ret;
const QString concat = QLatin1String("/mkspecs");
QStringList qmakepath = splitPathList(QString::fromLocal8Bit(qgetenv("QMAKEPATH")));
qmakepath += cached_qmakepath;
foreach (const QString &path, qmakepath)
ret << (path + concat);
if (!cached_build_root.isEmpty())
ret << cached_build_root + concat;
if (!cached_source_root.isEmpty())
ret << cached_source_root + concat;
ret << QLibraryInfo::rawLocation(QLibraryInfo::HostDataPath, QLibraryInfo::EffectivePaths) + concat;
ret.removeDuplicates();
return ret;
}
static void
setTemplate(QStringList &varlist)
{
if (!Option::user_template.isEmpty()) { // Don't permit override
varlist = QStringList(Option::user_template);
} else {
if (varlist.isEmpty())
varlist << "app";
else
varlist.erase(varlist.begin() + 1, varlist.end());
}
if (!Option::user_template_prefix.isEmpty()
&& !varlist.first().startsWith(Option::user_template_prefix)) {
varlist.first().prepend(Option::user_template_prefix);
}
}
QMakeProject::~QMakeProject()
{
if(own_prop)
delete prop;
cleanup();
}
void
QMakeProject::init(QMakeProperty *p)
{
if(!p) {
prop = new QMakeProperty;
own_prop = true;
} else {
prop = p;
own_prop = false;
}
host_build = false;
reset();
}
void
QMakeProject::cleanup()
{
for (QHash<QString, FunctionBlock*>::iterator it = replaceFunctions.begin(); it != replaceFunctions.end(); ++it)
if (!it.value()->deref())
delete it.value();
replaceFunctions.clear();
for (QHash<QString, FunctionBlock*>::iterator it = testFunctions.begin(); it != testFunctions.end(); ++it)
if (!it.value()->deref())
delete it.value();
testFunctions.clear();
}
// Duplicate project. It is *not* allowed to call the complex read() functions on the copy.
QMakeProject::QMakeProject(QMakeProject *p, const QHash<QString, QStringList> *_vars)
{
init(p->properties());
vars = _vars ? *_vars : p->variables();
host_build = p->host_build;
for(QHash<QString, FunctionBlock*>::iterator it = p->replaceFunctions.begin(); it != p->replaceFunctions.end(); ++it) {
it.value()->ref();
replaceFunctions.insert(it.key(), it.value());
}
for(QHash<QString, FunctionBlock*>::iterator it = p->testFunctions.begin(); it != p->testFunctions.end(); ++it) {
it.value()->ref();
testFunctions.insert(it.key(), it.value());
}
}
void
QMakeProject::reset()
{
// scope_blocks starts with one non-ignoring entity
scope_blocks.clear();
scope_blocks.push(ScopeBlock());
iterator = 0;
function = 0;
backslashWarned = false;
need_restart = false;
}
bool
QMakeProject::parse(const QString &t, QHash<QString, QStringList> &place, int numLines)
{
// To preserve the integrity of any UTF-8 characters in .pro file, temporarily replace the
// non-breaking space (0xA0) characters with another non-space character, so that
// QString::simplified() call will not replace it with space.
// Note: There won't be any two byte characters in .pro files, so 0x10A0 should be a safe
// replacement character.
static QChar nbsp(0xA0);
static QChar nbspFix(0x01A0);
QString s;
if (t.indexOf(nbsp) != -1) {
s = t;
s.replace(nbsp, nbspFix);
s = s.simplified();
s.replace(nbspFix, nbsp);
} else {
s = t.simplified();
}
int hash_mark = s.indexOf("#");
if(hash_mark != -1) //good bye comments
s = s.left(hash_mark);
if(s.isEmpty()) // blank_line
return true;
if(scope_blocks.top().ignore) {
bool continue_parsing = false;
// adjust scope for each block which appears on a single line
for(int i = 0; i < s.length(); i++) {
if(s[i] == '{') {
scope_blocks.push(ScopeBlock(true));
} else if(s[i] == '}') {
if(scope_blocks.count() == 1) {
fprintf(stderr, "Braces mismatch %s:%d\n", parser.file.toLatin1().constData(), parser.line_no);
return false;
}
ScopeBlock sb = scope_blocks.pop();
if(sb.iterate) {
sb.iterate->exec(this, place);
delete sb.iterate;
sb.iterate = 0;
}
if(!scope_blocks.top().ignore) {
debug_msg(1, "Project Parser: %s:%d : Leaving block %d", parser.file.toLatin1().constData(),
parser.line_no, scope_blocks.count()+1);
s = s.mid(i+1).trimmed();
continue_parsing = !s.isEmpty();
break;
}
}
}
if(!continue_parsing) {
debug_msg(1, "Project Parser: %s:%d : Ignored due to block being false.",
parser.file.toLatin1().constData(), parser.line_no);
return true;
}
}
if(function) {
QString append;
int d_off = 0;
const QChar *d = s.unicode();
bool function_finished = false;
while(d_off < s.length()) {
if(*(d+d_off) == QLatin1Char('}')) {
function->scope_level--;
if(!function->scope_level) {
function_finished = true;
break;
}
} else if(*(d+d_off) == QLatin1Char('{')) {
function->scope_level++;
}
append += *(d+d_off);
++d_off;
}
if(!append.isEmpty())
function->parselist.append(IteratorBlock::Parse(append));
if(function_finished) {
function = 0;
s = QString(d+d_off, s.length()-d_off);
} else {
return true;
}
} else if(IteratorBlock *it = scope_blocks.top().iterate) {
QString append;
int d_off = 0;
const QChar *d = s.unicode();
bool iterate_finished = false;
while(d_off < s.length()) {
if(*(d+d_off) == QLatin1Char('}')) {
it->scope_level--;
if(!it->scope_level) {
iterate_finished = true;
break;
}
} else if(*(d+d_off) == QLatin1Char('{')) {
it->scope_level++;
}
append += *(d+d_off);
++d_off;
}
if(!append.isEmpty())
scope_blocks.top().iterate->parselist.append(IteratorBlock::Parse(append));
if(iterate_finished) {
scope_blocks.top().iterate = 0;
bool ret = it->exec(this, place);
delete it;
if(!ret)
return false;
s = s.mid(d_off);
} else {
return true;
}
}
QString scope, var, op;
QStringList val;
#define SKIP_WS(d, o, l) while(o < l && (*(d+o) == QLatin1Char(' ') || *(d+o) == QLatin1Char('\t'))) ++o
const QChar *d = s.unicode();
int d_off = 0;
SKIP_WS(d, d_off, s.length());
IteratorBlock *iterator = 0;
bool scope_failed = false, else_line = false, or_op=false;
QChar quote = 0;
int parens = 0, scope_count=0, start_block = 0;
while(d_off < s.length()) {
if(!parens) {
if(*(d+d_off) == QLatin1Char('='))
break;
if(*(d+d_off) == QLatin1Char('+') || *(d+d_off) == QLatin1Char('-') ||
*(d+d_off) == QLatin1Char('*') || *(d+d_off) == QLatin1Char('~')) {
if(*(d+d_off+1) == QLatin1Char('=')) {
break;
} else if(*(d+d_off+1) == QLatin1Char(' ')) {
const QChar *k = d+d_off+1;
int k_off = 0;
SKIP_WS(k, k_off, s.length()-d_off);
if(*(k+k_off) == QLatin1Char('=')) {
QString msg;
qmake_error_msg(QString(d+d_off, 1) + "must be followed immediately by =");
return false;
}
}
}
}
if(!quote.isNull()) {
if(*(d+d_off) == quote)
quote = QChar();
} else if(*(d+d_off) == '(') {
++parens;
} else if(*(d+d_off) == ')') {
--parens;
} else if(*(d+d_off) == '"' /*|| *(d+d_off) == '\''*/) {
quote = *(d+d_off);
}
if(!parens && quote.isNull() &&
(*(d+d_off) == QLatin1Char(':') || *(d+d_off) == QLatin1Char('{') ||
*(d+d_off) == QLatin1Char(')') || *(d+d_off) == QLatin1Char('|'))) {
scope_count++;
scope = var.trimmed();
if(*(d+d_off) == QLatin1Char(')'))
scope += *(d+d_off); // need this
var = "";
bool test = scope_failed;
if(scope.isEmpty()) {
test = true;
} else if(scope.toLower() == "else") { //else is a builtin scope here as it modifies state
if(scope_count != 1 || scope_blocks.top().else_status == ScopeBlock::TestNone) {
qmake_error_msg(("Unexpected " + scope + " ('" + s + "')").toLatin1());
return false;
}
else_line = true;
test = (scope_blocks.top().else_status == ScopeBlock::TestSeek);
debug_msg(1, "Project Parser: %s:%d : Else%s %s.", parser.file.toLatin1().constData(), parser.line_no,
scope == "else" ? "" : QString(" (" + scope + ")").toLatin1().constData(),
test ? "considered" : "excluded");
} else {
QString comp_scope = scope;
bool invert_test = (comp_scope.at(0) == QLatin1Char('!'));
if(invert_test)
comp_scope = comp_scope.mid(1);
int lparen = comp_scope.indexOf('(');
if(or_op == scope_failed) {
if(lparen != -1) { // if there is an lparen in the scope, it IS a function
int rparen = comp_scope.lastIndexOf(')');
if(rparen == -1) {
qmake_error_msg("Function missing right paren: " + comp_scope);
return false;
}
QString func = comp_scope.left(lparen);
QStringList args = split_arg_list(comp_scope.mid(lparen+1, rparen - lparen - 1));
if(function) {
fprintf(stderr, "%s:%d: No tests can come after a function definition!\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
} else if(func == "for") { //for is a builtin function here, as it modifies state
if(args.count() > 2 || args.count() < 1) {
fprintf(stderr, "%s:%d: for(iterate, list) requires two arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
} else if(iterator) {
fprintf(stderr, "%s:%d unexpected nested for()\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
iterator = new IteratorBlock;
QString it_list;
if(args.count() == 1) {
doVariableReplace(args[0], place);
it_list = args[0];
if(args[0] != "ever") {
delete iterator;
iterator = 0;
fprintf(stderr, "%s:%d: for(iterate, list) requires two arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
it_list = "forever";
} else if(args.count() == 2) {
iterator->variable = args[0];
doVariableReplace(args[1], place);
it_list = args[1];
}
QStringList list = place[it_list];
if(list.isEmpty()) {
if(it_list == "forever") {
iterator->loop_forever = true;
} else {
int dotdot = it_list.indexOf("..");
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++)
list << QString::number(i);
} else {
for(int i = start; i >= end; i--)
list << QString::number(i);
}
}
}
}
}
}
iterator->list = list;
test = !invert_test;
} else if(iterator) {
iterator->test.append(IteratorBlock::Test(func, args, invert_test));
test = !invert_test;
} else if(func == "defineTest" || func == "defineReplace") {
if(!function_blocks.isEmpty()) {
fprintf(stderr,
"%s:%d: cannot define a function within another definition.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
if(args.count() != 1) {
fprintf(stderr, "%s:%d: %s(function_name) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no, func.toLatin1().constData());
return false;
}
QHash<QString, FunctionBlock*> *map = 0;
if(func == "defineTest")
map = &testFunctions;
else
map = &replaceFunctions;
#if 0
if(!map || map->contains(args[0])) {
fprintf(stderr, "%s:%d: Function[%s] multiply defined.\n",
parser.file.toLatin1().constData(), parser.line_no, args[0].toLatin1().constData());
return false;
}
#endif
function = new FunctionBlock;
map->insert(args[0], function);
test = true;
} else {
test = doProjectTest(func, args, place);
if(*(d+d_off) == QLatin1Char(')') && d_off == s.length()-1) {
if(invert_test)
test = !test;
scope_blocks.top().else_status =
(test ? ScopeBlock::TestFound : ScopeBlock::TestSeek);
return true; // assume we are done
}
}
} else {
QString cscope = comp_scope.trimmed();
doVariableReplace(cscope, place);
test = isActiveConfig(cscope.trimmed(), true, &place);
}
if(invert_test)
test = !test;
}
}
if(!test && !scope_failed)
debug_msg(1, "Project Parser: %s:%d : Test (%s) failed.", parser.file.toLatin1().constData(),
parser.line_no, scope.toLatin1().constData());
if(test == or_op)
scope_failed = !test;
or_op = (*(d+d_off) == QLatin1Char('|'));
if(*(d+d_off) == QLatin1Char('{')) { // scoping block
start_block++;
if(iterator) {
for(int off = 0, braces = 0; true; ++off) {
if(*(d+d_off+off) == QLatin1Char('{'))
++braces;
else if(*(d+d_off+off) == QLatin1Char('}') && braces)
--braces;
if(!braces || d_off+off == s.length()) {
iterator->parselist.append(s.mid(d_off, off-1));
if(braces > 1)
iterator->scope_level += braces-1;
d_off += off-1;
break;
}
}
}
}
} else if(!parens && *(d+d_off) == QLatin1Char('}')) {
if(start_block) {
--start_block;
} else if(!scope_blocks.count()) {
warn_msg(WarnParser, "Possible braces mismatch %s:%d", parser.file.toLatin1().constData(), parser.line_no);
} else {
if(scope_blocks.count() == 1) {
fprintf(stderr, "Braces mismatch %s:%d\n", parser.file.toLatin1().constData(), parser.line_no);
return false;
}
debug_msg(1, "Project Parser: %s:%d : Leaving block %d", parser.file.toLatin1().constData(),
parser.line_no, scope_blocks.count());
ScopeBlock sb = scope_blocks.pop();
if(sb.iterate)
sb.iterate->exec(this, place);
}
} else {
var += *(d+d_off);
}
++d_off;
}
var = var.trimmed();
if(!else_line || (else_line && !scope_failed))
scope_blocks.top().else_status = (!scope_failed ? ScopeBlock::TestFound : ScopeBlock::TestSeek);
if(start_block) {
ScopeBlock next_block(scope_failed);
next_block.iterate = iterator;
if(iterator)
next_block.else_status = ScopeBlock::TestNone;
else if(scope_failed)
next_block.else_status = ScopeBlock::TestSeek;
else
next_block.else_status = ScopeBlock::TestFound;
scope_blocks.push(next_block);
debug_msg(1, "Project Parser: %s:%d : Entering block %d (%d). [%s]", parser.file.toLatin1().constData(),
parser.line_no, scope_blocks.count(), scope_failed, s.toLatin1().constData());
} else if(iterator) {
iterator->parselist.append(QString(var+s.mid(d_off)));
bool ret = iterator->exec(this, place);
delete iterator;
return ret;
}
if((!scope_count && !var.isEmpty()) || (scope_count == 1 && else_line))
scope_blocks.top().else_status = ScopeBlock::TestNone;
if(d_off == s.length()) {
if(!var.trimmed().isEmpty())
qmake_error_msg(("Parse Error ('" + s + "')").toLatin1());
return var.isEmpty(); // allow just a scope
}
SKIP_WS(d, d_off, s.length());
for(; d_off < s.length() && op.indexOf('=') == -1; op += *(d+(d_off++)))
;
op.replace(QRegExp("\\s"), "");
SKIP_WS(d, d_off, s.length());
QString vals = s.mid(d_off); // vals now contains the space separated list of values
int rbraces = vals.count('}'), lbraces = vals.count('{');
if(scope_blocks.count() > 1 && rbraces - lbraces == 1 && vals.endsWith('}')) {
debug_msg(1, "Project Parser: %s:%d : Leaving block %d", parser.file.toLatin1().constData(),
parser.line_no, scope_blocks.count());
ScopeBlock sb = scope_blocks.pop();
if(sb.iterate)
sb.iterate->exec(this, place);
vals.truncate(vals.length()-1);
} else if(rbraces != lbraces) {
warn_msg(WarnParser, "Possible braces mismatch {%s} %s:%d",
vals.toLatin1().constData(), parser.file.toLatin1().constData(), parser.line_no);
}
if(scope_failed)
return true; // oh well
#undef SKIP_WS
doVariableReplace(var, place);
var = varMap(var); //backwards compatibility
if(vals.contains('=') && numLines > 1)
warn_msg(WarnParser, "Possible accidental line continuation: {%s} at %s:%d",
var.toLatin1().constData(), parser.file.toLatin1().constData(), parser.line_no);
QStringList &varlist = place[var]; // varlist is the list in the symbol table
if(Option::debug_level >= 1) {
QString tmp_vals = vals;
doVariableReplace(tmp_vals, place);
debug_msg(1, "Project Parser: %s:%d :%s: :%s: (%s)", parser.file.toLatin1().constData(), parser.line_no,
var.toLatin1().constData(), op.toLatin1().constData(), tmp_vals.toLatin1().constData());
}
// now do the operation
if(op == "~=") {
doVariableReplace(vals, place);
if(vals.length() < 4 || vals.at(0) != 's') {
qmake_error_msg(("~= operator only can handle s/// function ('" +
s + "')").toLatin1());
return false;
}
QChar sep = vals.at(1);
QStringList func = vals.split(sep);
if(func.count() < 3 || func.count() > 4) {
qmake_error_msg(("~= operator only can handle s/// function ('" +
s + "')").toLatin1());
return false;
}
bool global = false, case_sense = true, quote = false;
if(func.count() == 4) {
global = func[3].indexOf('g') != -1;
case_sense = func[3].indexOf('i') == -1;
quote = func[3].indexOf('q') != -1;
}
QString from = func[1], to = func[2];
if(quote)
from = QRegExp::escape(from);
QRegExp regexp(from, case_sense ? Qt::CaseSensitive : Qt::CaseInsensitive);
for(QStringList::Iterator varit = varlist.begin(); varit != varlist.end();) {
if((*varit).contains(regexp)) {
(*varit) = (*varit).replace(regexp, to);
if ((*varit).isEmpty())
varit = varlist.erase(varit);
else
++varit;
if(!global)
break;
} else
++varit;
}
} else {
QStringList vallist;
{
//doVariableReplace(vals, place);
QStringList tmp = split_value_list(vals);
for(int i = 0; i < tmp.size(); ++i)
vallist += doVariableReplaceExpand(tmp[i], place);
}
if(op == "=") {
#if 0 // This is way too noisy
if(!varlist.isEmpty()) {
bool send_warning = false;
if(var != "TEMPLATE" && var != "TARGET") {
QSet<QString> incoming_vals = vallist.toSet();
for(int i = 0; i < varlist.size(); ++i) {
const QString var = varlist.at(i).trimmed();
if(!var.isEmpty() && !incoming_vals.contains(var)) {
send_warning = true;
break;
}
}
}
if(send_warning)
warn_msg(WarnParser, "Operator=(%s) clears variables previously set: %s:%d",
var.toLatin1().constData(), parser.file.toLatin1().constData(), parser.line_no);
}
#endif
varlist.clear();
}
for(QStringList::ConstIterator valit = vallist.begin();
valit != vallist.end(); ++valit) {
if((*valit).isEmpty())
continue;
if((op == "*=" && !varlist.contains((*valit))) ||
op == "=" || op == "+=")
varlist.append((*valit));
else if(op == "-=")
varlist.removeAll((*valit));
}
if(var == "REQUIRES") // special case to get communicated to backends!
doProjectCheckReqs(vallist, place);
else if (var == QLatin1String("TEMPLATE"))
setTemplate(varlist);
}
return true;
}
bool
QMakeProject::read(QTextStream &file, QHash<QString, QStringList> &place)
{
int numLines = 0;
bool ret = true;
QString s;
while(!file.atEnd()) {
parser.line_no++;
QString line = file.readLine().trimmed();
int prelen = line.length();
int hash_mark = line.indexOf("#");
if(hash_mark != -1) //good bye comments
line = line.left(hash_mark).trimmed();
if(!line.isEmpty() && line.right(1) == "\\") {
if(!line.startsWith("#")) {
line.truncate(line.length() - 1);
s += line + Option::field_sep;
++numLines;
}
} else if(!line.isEmpty() || (line.isEmpty() && !prelen)) {
if(s.isEmpty() && line.isEmpty())
continue;
if(!line.isEmpty()) {
s += line;
++numLines;
}
if(!s.isEmpty()) {
if(!(ret = parse(s, place, numLines))) {
s = "";
numLines = 0;
break;
}
s = "";
numLines = 0;
if (need_restart)
break;
}
}
}
if (!s.isEmpty())
ret = parse(s, place, numLines);
return ret;
}
bool
QMakeProject::read(const QString &file, QHash<QString, QStringList> &place)
{
parser_info pi = parser;
reset();
const QString oldpwd = qmake_getpwd();
QString filename = Option::normalizePath(file, false);
bool ret = false, using_stdin = false;
QFile qfile;
if(filename == QLatin1String("-")) {
qfile.setFileName("");
ret = qfile.open(stdin, QIODevice::ReadOnly);
using_stdin = true;
} else if(QFileInfo(file).isDir()) {
return false;
} else {
qfile.setFileName(filename);
ret = qfile.open(QIODevice::ReadOnly);
qmake_setpwd(QFileInfo(filename).absolutePath());
}
if(ret) {
place["PWD"] = QStringList(qmake_getpwd());
parser_info pi = parser;
parser.from_file = true;
parser.file = filename;
parser.line_no = 0;
if (qfile.peek(3) == QByteArray("\xef\xbb\xbf")) {
//UTF-8 BOM will cause subtle errors
qmake_error_msg("Unexpected UTF-8 BOM found");
ret = false;
} else {
QTextStream t(&qfile);
ret = read(t, place);
}
if(!using_stdin)
qfile.close();
}
if (!need_restart && scope_blocks.count() != 1) {
qmake_error_msg("Unterminated conditional block at end of file");
ret = false;
}
parser = pi;
qmake_setpwd(oldpwd);
return ret;
}
bool
QMakeProject::read(const QString &project, uchar cmd)
{
pfile = QFileInfo(project).absoluteFilePath();
return read(cmd);
}
bool
QMakeProject::read(uchar cmd)
{
again:
if (init_vars.isEmpty()) {
loadDefaults();
init_vars = vars;
} else {
vars = init_vars;
}
if (cmd & ReadSetup) {
if (base_vars.isEmpty()) {
QString superdir;
QString project_root;
QStringList qmakepath;
QStringList qmakefeatures;
project_build_root.clear();
if (Option::mkfile::do_cache) { // parse the cache
QHash<QString, QStringList> cache;
QString rdir = Option::output_dir;
forever {
QFileInfo qfi(rdir, QLatin1String(".qmake.super"));
if (qfi.exists()) {
superfile = qfi.filePath();
if (!read(superfile, cache))
return false;
superdir = rdir;
break;
}
QFileInfo qdfi(rdir);
if (qdfi.isRoot())
break;
rdir = qdfi.path();
}
if (Option::mkfile::cachefile.isEmpty()) { //find it as it has not been specified
QString sdir = qmake_getpwd();
QString dir = Option::output_dir;
forever {
QFileInfo qsfi(sdir, QLatin1String(".qmake.conf"));
if (qsfi.exists()) {
conffile = qsfi.filePath();
if (!read(conffile, cache))
return false;
}
QFileInfo qfi(dir, QLatin1String(".qmake.cache"));
if (qfi.exists()) {
cachefile = qfi.filePath();
if (!read(cachefile, cache))
return false;
}
if (!conffile.isEmpty() || !cachefile.isEmpty()) {
project_root = sdir;
project_build_root = dir;
break;
}
if (dir == superdir)
break;
QFileInfo qsdfi(sdir);
QFileInfo qdfi(dir);
if (qsdfi.isRoot() || qdfi.isRoot())
break;
sdir = qsdfi.path();
dir = qdfi.path();
}
} else {
QFileInfo fi(Option::mkfile::cachefile);
cachefile = QDir::cleanPath(fi.absoluteFilePath());
if (!read(cachefile, cache))
return false;
project_build_root = QDir::cleanPath(fi.absolutePath());
// This intentionally bypasses finding a source root,
// as the result would be more or less arbitrary.
}
if (Option::mkfile::xqmakespec.isEmpty() && !cache["XQMAKESPEC"].isEmpty())
Option::mkfile::xqmakespec = cache["XQMAKESPEC"].first();
if (Option::mkfile::qmakespec.isEmpty() && !cache["QMAKESPEC"].isEmpty()) {
Option::mkfile::qmakespec = cache["QMAKESPEC"].first();
if (Option::mkfile::xqmakespec.isEmpty())
Option::mkfile::xqmakespec = Option::mkfile::qmakespec;
}
qmakepath = cache.value(QLatin1String("QMAKEPATH"));
qmakefeatures = cache.value(QLatin1String("QMAKEFEATURES"));
if (!superfile.isEmpty())
vars["_QMAKE_SUPER_CACHE_"] << superfile;
if (!cachefile.isEmpty())
vars["_QMAKE_CACHE_"] << cachefile;
}
// Look for mkspecs/ in source and build. First to win determines the root.
QString sdir = qmake_getpwd();
QString dir = Option::output_dir;
while (dir != project_build_root) {
if ((dir != sdir && QFileInfo(sdir, QLatin1String("mkspecs")).isDir())
|| QFileInfo(dir, QLatin1String("mkspecs")).isDir()) {
if (dir != sdir)
project_root = sdir;
project_build_root = dir;
break;
}
if (dir == superdir)
break;
QFileInfo qsdfi(sdir);
QFileInfo qdfi(dir);
if (qsdfi.isRoot() || qdfi.isRoot())
break;
sdir = qsdfi.path();
dir = qdfi.path();
}
if (qmakepath != cached_qmakepath || qmakefeatures != cached_qmakefeatures
|| project_build_root != cached_build_root) { // No need to check source dir, as it goes in sync
cached_source_root = project_root;
cached_build_root = project_build_root;
cached_qmakepath = qmakepath;
cached_qmakefeatures = qmakefeatures;
invalidateFeatureRoots();
}
{ // parse mkspec
QString *specp = host_build ? &Option::mkfile::qmakespec : &Option::mkfile::xqmakespec;
QString qmakespec = *specp;
if (qmakespec.isEmpty())
qmakespec = host_build ? "default-host" : "default";
if (QDir::isRelativePath(qmakespec)) {
QStringList mkspec_roots = qmake_mkspec_paths();
debug_msg(2, "Looking for mkspec %s in (%s)", qmakespec.toLatin1().constData(),
mkspec_roots.join("::").toLatin1().constData());
bool found_mkspec = false;
for (QStringList::ConstIterator it = mkspec_roots.begin(); it != mkspec_roots.end(); ++it) {
QString mkspec = (*it) + QLatin1Char('/') + qmakespec;
if (QFile::exists(mkspec)) {
found_mkspec = true;
*specp = qmakespec = mkspec;
break;
}
}
if (!found_mkspec) {
fprintf(stderr, "Could not find mkspecs for your QMAKESPEC(%s) after trying:\n\t%s\n",
qmakespec.toLatin1().constData(), mkspec_roots.join("\n\t").toLatin1().constData());
return false;
}
}
// We do this before reading the spec, so it can use module and feature paths from
// here without resorting to tricks. This is the only planned use case anyway.
if (!superfile.isEmpty()) {
debug_msg(1, "Project super cache file: reading %s", superfile.toLatin1().constData());
read(superfile, vars);
}
// parse qmake configuration
doProjectInclude("spec_pre", IncludeFlagFeature, vars);
while(qmakespec.endsWith(QLatin1Char('/')))
qmakespec.truncate(qmakespec.length()-1);
QString spec = qmakespec + QLatin1String("/qmake.conf");
debug_msg(1, "QMAKESPEC conf: reading %s", spec.toLatin1().constData());
if (!read(spec, vars)) {
fprintf(stderr, "Failure to read QMAKESPEC conf file %s.\n", spec.toLatin1().constData());
return false;
}
#ifdef Q_OS_UNIX
real_spec = QFileInfo(qmakespec).canonicalFilePath();
#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.
QString orig_spec = first(QLatin1String("QMAKESPEC_ORIGINAL"));
real_spec = orig_spec.isEmpty() ? qmakespec : orig_spec;
#endif
vars["QMAKESPEC"] << real_spec;
short_spec = QFileInfo(real_spec).fileName();
doProjectInclude("spec_post", IncludeFlagFeature, vars);
// The spec extends the feature search path, so invalidate the cache.
invalidateFeatureRoots();
// The MinGW and x-build specs may change the separator; $$shell_{path,quote}() need it
Option::dir_sep = first(QLatin1String("QMAKE_DIR_SEP"));
if (!conffile.isEmpty()) {
debug_msg(1, "Project config file: reading %s", conffile.toLatin1().constData());
read(conffile, vars);
}
if (!cachefile.isEmpty()) {
debug_msg(1, "QMAKECACHE file: reading %s", cachefile.toLatin1().constData());
read(cachefile, vars);
}
}
base_vars = vars;
} else {
vars = base_vars; // start with the base
}
setupProject();
}
for (QHash<QString, QStringList>::ConstIterator it = extra_vars.constBegin();
it != extra_vars.constEnd(); ++it)
vars.insert(it.key(), it.value());
if(cmd & ReadFeatures) {
debug_msg(1, "Processing default_pre: %s", vars["CONFIG"].join("::").toLatin1().constData());
doProjectInclude("default_pre", IncludeFlagFeature, vars);
}
//before commandline
if (cmd & ReadSetup) {
parser.file = "(internal)";
parser.from_file = false;
parser.line_no = 1; //really arg count now.. duh
reset();
for(QStringList::ConstIterator it = Option::before_user_vars.begin();
it != Option::before_user_vars.end(); ++it) {
if(!parse((*it), vars)) {
fprintf(stderr, "Argument failed to parse: %s\n", (*it).toLatin1().constData());
return false;
}
parser.line_no++;
}
}
// After user configs, to override them
if (!extra_configs.isEmpty()) {
parser.file = "(extra configs)";
parser.from_file = false;
parser.line_no = 1; //really arg count now.. duh
parse("CONFIG += " + extra_configs.join(" "), vars);
}
if(cmd & ReadProFile) { // parse project file
debug_msg(1, "Project file: reading %s", pfile.toLatin1().constData());
if(pfile != "-" && !QFile::exists(pfile) && !pfile.endsWith(Option::pro_ext))
pfile += Option::pro_ext;
if(!read(pfile, vars))
return false;
if (need_restart) {
base_vars.clear();
cleanup();
goto again;
}
}
if (cmd & ReadSetup) {
parser.file = "(internal)";
parser.from_file = false;
parser.line_no = 1; //really arg count now.. duh
reset();
for(QStringList::ConstIterator it = Option::after_user_vars.begin();
it != Option::after_user_vars.end(); ++it) {
if(!parse((*it), vars)) {
fprintf(stderr, "Argument failed to parse: %s\n", (*it).toLatin1().constData());
return false;
}
parser.line_no++;
}
}
// Again, to ensure the project does not mess with us.
// Specifically, do not allow a project to override debug/release within a
// debug_and_release build pass - it's too late for that at this point anyway.
if (!extra_configs.isEmpty()) {
parser.file = "(extra configs)";
parser.from_file = false;
parser.line_no = 1; //really arg count now.. duh
parse("CONFIG += " + extra_configs.join(" "), vars);
}
if(cmd & ReadFeatures) {
debug_msg(1, "Processing default_post: %s", vars["CONFIG"].join("::").toLatin1().constData());
doProjectInclude("default_post", IncludeFlagFeature, vars);
QHash<QString, bool> processed;
const QStringList &configs = vars["CONFIG"];
debug_msg(1, "Processing CONFIG features: %s", configs.join("::").toLatin1().constData());
while(1) {
bool finished = true;
for(int i = configs.size()-1; i >= 0; --i) {
const QString config = configs[i].toLower();
if(!processed.contains(config)) {
processed.insert(config, true);
if(doProjectInclude(config, IncludeFlagFeature, vars) == IncludeSuccess) {
finished = false;
break;
}
}
}
if(finished)
break;
}
}
return true;
}
void
QMakeProject::setupProject()
{
setTemplate(vars["TEMPLATE"]);
if (pfile != "-")
vars["TARGET"] << QFileInfo(pfile).baseName();
vars["_PRO_FILE_"] << pfile;
vars["_PRO_FILE_PWD_"] << (pfile.isEmpty() ? qmake_getpwd() : QFileInfo(pfile).absolutePath());
vars["OUT_PWD"] << Option::output_dir;
}
void
QMakeProject::loadDefaults()
{
vars["LITERAL_WHITESPACE"] << QLatin1String("\t");
vars["LITERAL_DOLLAR"] << QLatin1String("$");
vars["LITERAL_HASH"] << QLatin1String("#");
vars["DIR_SEPARATOR"] << Option::dir_sep;
vars["DIRLIST_SEPARATOR"] << Option::dirlist_sep;
vars["QMAKE_QMAKE"] << Option::qmake_abslocation;
vars["_DATE_"] << QDateTime::currentDateTime().toString();
#if defined(Q_OS_WIN32)
vars["QMAKE_HOST.os"] << QString::fromLatin1("Windows");
DWORD name_length = 1024;
wchar_t name[1024];
if (GetComputerName(name, &name_length))
vars["QMAKE_HOST.name"] << QString::fromWCharArray(name);
QSysInfo::WinVersion ver = QSysInfo::WindowsVersion;
vars["QMAKE_HOST.version"] << QString::number(ver);
QString verStr;
switch (ver) {
case QSysInfo::WV_Me: verStr = QLatin1String("WinMe"); break;
case QSysInfo::WV_95: verStr = QLatin1String("Win95"); break;
case QSysInfo::WV_98: verStr = QLatin1String("Win98"); break;
case QSysInfo::WV_NT: verStr = QLatin1String("WinNT"); break;
case QSysInfo::WV_2000: verStr = QLatin1String("Win2000"); break;
case QSysInfo::WV_2003: verStr = QLatin1String("Win2003"); break;
case QSysInfo::WV_XP: verStr = QLatin1String("WinXP"); break;
case QSysInfo::WV_VISTA: verStr = QLatin1String("WinVista"); break;
default: verStr = QLatin1String("Unknown"); break;
}
vars["QMAKE_HOST.version_string"] << verStr;
SYSTEM_INFO info;
GetSystemInfo(&info);
QString archStr;
switch (info.wProcessorArchitecture) {
# ifdef PROCESSOR_ARCHITECTURE_AMD64
case PROCESSOR_ARCHITECTURE_AMD64:
archStr = QLatin1String("x86_64");
break;
# endif
case PROCESSOR_ARCHITECTURE_INTEL:
archStr = QLatin1String("x86");
break;
case PROCESSOR_ARCHITECTURE_IA64:
# ifdef PROCESSOR_ARCHITECTURE_IA32_ON_WIN64
case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64:
# endif
archStr = QLatin1String("IA64");
break;
default:
archStr = QLatin1String("Unknown");
break;
}
vars["QMAKE_HOST.arch"] << archStr;
# if defined(Q_CC_MSVC)
QString paths = QString::fromLocal8Bit(qgetenv("PATH"));
QString vcBin64 = QString::fromLocal8Bit(qgetenv("VCINSTALLDIR"));
if (!vcBin64.endsWith('\\'))
vcBin64.append('\\');
vcBin64.append("bin\\amd64");
QString vcBinX86_64 = QString::fromLocal8Bit(qgetenv("VCINSTALLDIR"));
if (!vcBinX86_64.endsWith('\\'))
vcBinX86_64.append('\\');
vcBinX86_64.append("bin\\x86_amd64");
if (paths.contains(vcBin64,Qt::CaseInsensitive) || paths.contains(vcBinX86_64,Qt::CaseInsensitive))
vars["QMAKE_TARGET.arch"] << QString::fromLatin1("x86_64");
else
vars["QMAKE_TARGET.arch"] << QString::fromLatin1("x86");
# endif
#elif defined(Q_OS_UNIX)
struct utsname name;
if (!uname(&name)) {
vars["QMAKE_HOST.os"] << QString::fromLocal8Bit(name.sysname);
vars["QMAKE_HOST.name"] << QString::fromLocal8Bit(name.nodename);
vars["QMAKE_HOST.version"] << QString::fromLocal8Bit(name.release);
vars["QMAKE_HOST.version_string"] << QString::fromLocal8Bit(name.version);
vars["QMAKE_HOST.arch"] << QString::fromLocal8Bit(name.machine);
}
#endif
}
bool
QMakeProject::isActiveConfig(const QString &x, bool regex, QHash<QString, QStringList> *place)
{
if(x.isEmpty())
return true;
//magic types for easy flipping
if(x == "true")
return true;
else if(x == "false")
return false;
if (x == "host_build")
return host_build;
//mkspecs
QRegExp re(x, Qt::CaseSensitive, QRegExp::Wildcard);
if ((regex && re.exactMatch(short_spec)) || (!regex && short_spec == x))
return true;
//simple matching
const QStringList &configs = (place ? (*place)["CONFIG"] : vars["CONFIG"]);
for(QStringList::ConstIterator it = configs.begin(); it != configs.end(); ++it) {
if(((regex && re.exactMatch((*it))) || (!regex && (*it) == x)) && re.exactMatch((*it)))
return true;
}
return false;
}
bool
QMakeProject::doProjectTest(QString str, QHash<QString, QStringList> &place)
{
QString chk = remove_quotes(str);
if(chk.isEmpty())
return true;
bool invert_test = (chk.left(1) == "!");
if(invert_test)
chk = chk.mid(1);
bool test=false;
int lparen = chk.indexOf('(');
if(lparen != -1) { // if there is an lparen in the chk, it IS a function
int rparen = chk.indexOf(')', lparen);
if(rparen == -1) {
qmake_error_msg("Function missing right paren: " + chk);
} else {
QString func = chk.left(lparen);
test = doProjectTest(func, chk.mid(lparen+1, rparen - lparen - 1), place);
}
} else {
test = isActiveConfig(chk, true, &place);
}
if(invert_test)
return !test;
return test;
}
bool
QMakeProject::doProjectTest(QString func, const QString &params,
QHash<QString, QStringList> &place)
{
return doProjectTest(func, split_arg_list(params), place);
}
QMakeProject::IncludeStatus
QMakeProject::doProjectInclude(QString file, uchar flags, QHash<QString, QStringList> &place)
{
if(flags & IncludeFlagFeature) {
if(!file.endsWith(Option::prf_ext))
file += Option::prf_ext;
{
QStringList *&feature_roots = all_feature_roots[host_build];
if(!feature_roots) {
feature_roots = new QStringList;
qmakeAddCacheClear(qmakeDeleteCacheClear<QStringList>, (void**)&feature_roots);
}
if (feature_roots->isEmpty())
*feature_roots = qmakeFeaturePaths();
debug_msg(2, "Looking for feature '%s' in (%s)", file.toLatin1().constData(),
feature_roots->join("::").toLatin1().constData());
int start_root = 0;
if(parser.from_file) {
QFileInfo currFile(parser.file), prfFile(file);
if(currFile.fileName() == prfFile.fileName()) {
currFile = QFileInfo(currFile.canonicalFilePath());
for(int root = 0; root < feature_roots->size(); ++root) {
prfFile = QFileInfo(feature_roots->at(root) +
QLatin1Char('/') + file).canonicalFilePath();
if(prfFile == currFile) {
start_root = root+1;
break;
}
}
}
}
for(int root = start_root; root < feature_roots->size(); ++root) {
QString prf(feature_roots->at(root) + QLatin1Char('/') + file);
if (QFile::exists(prf)) {
file = prf;
goto foundf;
}
}
return IncludeNoExist;
foundf: ;
}
if(place["QMAKE_INTERNAL_INCLUDED_FEATURES"].indexOf(file) != -1)
return IncludeFeatureAlreadyLoaded;
place["QMAKE_INTERNAL_INCLUDED_FEATURES"].append(file);
} else if (QDir::isRelativePath(file)) {
QStringList include_roots;
if(Option::output_dir != qmake_getpwd())
include_roots << qmake_getpwd();
include_roots << Option::output_dir;
for(int root = 0; root < include_roots.size(); ++root) {
QString testName = QDir::fromNativeSeparators(include_roots[root]);
if (!testName.endsWith(QLatin1Char('/')))
testName += QLatin1Char('/');
testName += file;
if(QFile::exists(testName)) {
file = testName;
goto foundi;
}
}
return IncludeNoExist;
foundi: ;
} else if (!QFile::exists(file)) {
return IncludeNoExist;
}
debug_msg(1, "Project Parser: %s'ing file %s.", (flags & IncludeFlagFeature) ? "load" : "include",
file.toLatin1().constData());
QString orig_file = file;
int di = file.lastIndexOf(QLatin1Char('/'));
QString oldpwd = qmake_getpwd();
if(di != -1) {
if(!qmake_setpwd(file.left(file.lastIndexOf(QLatin1Char('/'))))) {
fprintf(stderr, "Cannot find directory: %s\n", file.left(di).toLatin1().constData());
return IncludeFailure;
}
}
bool parsed = false;
parser_info pi = parser;
{
if(flags & (IncludeFlagNewProject|IncludeFlagNewParser)) {
// The "project's variables" are used in other places (eg. export()) so it's not
// possible to use "place" everywhere. Instead just set variables and grab them later
QMakeProject proj(prop);
if(flags & IncludeFlagNewParser) {
parsed = proj.read(file, proj.variables()); // parse just that file (fromfile, infile)
} else {
parsed = proj.read(file); // parse all aux files (load/include into)
}
place = proj.variables();
} else {
QStack<ScopeBlock> sc = scope_blocks;
IteratorBlock *it = iterator;
FunctionBlock *fu = function;
parsed = read(file, place);
iterator = it;
function = fu;
scope_blocks = sc;
}
}
if(parsed) {
if(place["QMAKE_INTERNAL_INCLUDED_FILES"].indexOf(orig_file) == -1)
place["QMAKE_INTERNAL_INCLUDED_FILES"].append(orig_file);
} else {
warn_msg(WarnParser, "%s:%d: Failure to include file %s.",
pi.file.toLatin1().constData(), pi.line_no, orig_file.toLatin1().constData());
}
parser = pi;
qmake_setpwd(oldpwd);
place["PWD"] = QStringList(qmake_getpwd());
if(!parsed)
return IncludeParseFailure;
return IncludeSuccess;
}
static void
subAll(QStringList *val, const QStringList &diffval)
{
foreach (const QString &dv, diffval)
val->removeAll(dv);
}
inline static
bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16])
{
for (int x = arg.length() - 1; x >= 0; --x) {
ushort c = arg.unicode()[x].unicode();
if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))))
return true;
}
return false;
}
static QString
shellQuoteUnix(const QString &arg)
{
// Chars that should be quoted (TM). This includes:
static const uchar iqm[] = {
0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
}; // 0-32 \'"$`<>|;&(){}*?#!~[]
if (!arg.length())
return QString::fromLatin1("\"\"");
QString ret(arg);
if (hasSpecialChars(ret, iqm)) {
ret.replace(QLatin1Char('\''), QLatin1String("'\\''"));
ret.prepend(QLatin1Char('\''));
ret.append(QLatin1Char('\''));
}
return ret;
}
static QString
shellQuoteWin(const QString &arg)
{
// Chars that should be quoted (TM). This includes:
// - control chars & space
// - the shell meta chars "&()<>^|
// - the potential separators ,;=
static const uchar iqm[] = {
0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78,
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
};
if (!arg.length())
return QString::fromLatin1("\"\"");
QString ret(arg);
if (hasSpecialChars(ret, iqm)) {
// Quotes are escaped and their preceding backslashes are doubled.
// It's impossible to escape anything inside a quoted string on cmd
// level, so the outer quoting must be "suspended".
ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\""));
// The argument must not end with a \ since this would be interpreted
// as escaping the quote -- rather put the \ behind the quote: e.g.
// rather use "foo"\ than "foo\"
int i = ret.length();
while (i > 0 && ret.at(i - 1) == QLatin1Char('\\'))
--i;
ret.insert(i, QLatin1Char('"'));
ret.prepend(QLatin1Char('"'));
}
return ret;
}
static QString
quoteValue(const QString &val)
{
QString ret;
ret.reserve(val.length());
bool quote = val.isEmpty();
bool escaping = false;
for (int i = 0, l = val.length(); i < l; i++) {
QChar c = val.unicode()[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
writeFile(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;
}
static QByteArray
getCommandOutput(const QString &args)
{
QByteArray out;
if (FILE *proc = QT_POPEN(args.toLatin1().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;
}
#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
QStringList
QMakeProject::doProjectExpand(QString func, const QString &params,
QHash<QString, QStringList> &place)
{
return doProjectExpand(func, split_arg_list(params), place);
}
QStringList
QMakeProject::doProjectExpand(QString func, QStringList args,
QHash<QString, QStringList> &place)
{
QList<QStringList> args_list;
for(int i = 0; i < args.size(); ++i) {
QStringList arg = split_value_list(args[i]), tmp;
for(int i = 0; i < arg.size(); ++i)
tmp += doVariableReplaceExpand(arg[i], place);;
args_list += tmp;
}
return doProjectExpand(func, args_list, place);
}
static void
populateDeps(const QStringList &deps, const QString &prefix,
QHash<QString, QSet<QString> > &dependencies, QHash<QString, QStringList> &dependees,
QStringList &rootSet, QHash<QString, QStringList> &place)
{
foreach (const QString &item, deps)
if (!dependencies.contains(item)) {
QSet<QString> &dset = dependencies[item]; // Always create entry
QStringList depends = place.value(prefix + item + ".depends");
if (depends.isEmpty()) {
rootSet << item;
} else {
foreach (const QString &dep, depends) {
dset.insert(dep);
dependees[dep] << item;
}
populateDeps(depends, prefix, dependencies, dependees, rootSet, place);
}
}
}
QStringList
QMakeProject::doProjectExpand(QString func, QList<QStringList> args_list,
QHash<QString, QStringList> &place)
{
func = func.trimmed();
if(replaceFunctions.contains(func)) {
FunctionBlock *defined = replaceFunctions[func];
function_blocks.push(defined);
QStringList ret;
defined->exec(args_list, this, place, ret);
bool correct = function_blocks.pop() == defined;
Q_ASSERT(correct); Q_UNUSED(correct);
return ret;
}
QStringList args; //why don't the builtin functions just use args_list? --Sam
for(int i = 0; i < args_list.size(); ++i)
args += args_list[i].join(QString(Option::field_sep));
ExpandFunc func_t = qmake_expandFunctions().value(func);
if (!func_t && (func_t = qmake_expandFunctions().value(func.toLower())))
warn_msg(WarnDeprecated, "%s:%d: Using uppercased builtin functions is deprecated.",
parser.file.toLatin1().constData(), parser.line_no);
debug_msg(1, "Running project expand: %s(%s) [%d]",
func.toLatin1().constData(), args.join("::").toLatin1().constData(), func_t);
QStringList ret;
switch(func_t) {
case E_MEMBER: {
if(args.count() < 1 || args.count() > 3) {
fprintf(stderr, "%s:%d: member(var, start, end) requires three arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
bool ok = true;
const QStringList &var = values(args.first(), place);
int start = 0, end = 0;
if(args.count() >= 2) {
QString start_str = args[1];
start = start_str.toInt(&ok);
if(!ok) {
if(args.count() == 2) {
int dotdot = start_str.indexOf("..");
if(dotdot != -1) {
start = start_str.left(dotdot).toInt(&ok);
if(ok)
end = start_str.mid(dotdot+2).toInt(&ok);
}
}
if(!ok)
fprintf(stderr, "%s:%d: member() argument 2 (start) '%s' invalid.\n",
parser.file.toLatin1().constData(), parser.line_no,
start_str.toLatin1().constData());
} else {
end = start;
if(args.count() == 3)
end = args[2].toInt(&ok);
if(!ok)
fprintf(stderr, "%s:%d: member() argument 3 (end) '%s' invalid.\n",
parser.file.toLatin1().constData(), parser.line_no,
args[2].toLatin1().constData());
}
}
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 && (int)var.count() >= i; i++)
ret += var[i];
} else {
for(int i = start; i >= end && (int)var.count() >= i && i >= 0; i--)
ret += var[i];
}
}
}
break; }
case E_FIRST:
case E_LAST: {
if(args.count() != 1) {
fprintf(stderr, "%s:%d: %s(var) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no, func.toLatin1().constData());
} else {
const QStringList &var = values(args.first(), place);
if(!var.isEmpty()) {
if(func_t == E_FIRST)
ret = QStringList(var[0]);
else
ret = QStringList(var[var.size()-1]);
}
}
break; }
case E_CAT: {
if(args.count() < 1 || args.count() > 2) {
fprintf(stderr, "%s:%d: cat(file) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
QString file = Option::normalizePath(args[0]);
bool blob = false;
bool lines = false;
bool singleLine = true;
if (args.count() > 1) {
if (!args.at(1).compare(QLatin1String("false"), Qt::CaseInsensitive))
singleLine = false;
else if (!args.at(1).compare(QLatin1String("blob"), Qt::CaseInsensitive))
blob = true;
else if (!args.at(1).compare(QLatin1String("lines"), Qt::CaseInsensitive))
lines = true;
}
QFile qfile(file);
if(qfile.open(QIODevice::ReadOnly)) {
QTextStream stream(&qfile);
if (blob) {
ret += stream.readAll();
} else {
while (!stream.atEnd()) {
if (lines) {
ret += stream.readLine();
} else {
ret += split_value_list(stream.readLine().trimmed());
if (!singleLine)
ret += "\n";
}
}
}
}
}
break; }
case E_FROMFILE: {
if(args.count() != 2) {
fprintf(stderr, "%s:%d: fromfile(file, variable) requires two arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
QString seek_var = args[1], file = Option::normalizePath(args[0]);
QHash<QString, QStringList> tmp;
if(doProjectInclude(file, IncludeFlagNewParser, tmp) == IncludeSuccess) {
if(tmp.contains("QMAKE_INTERNAL_INCLUDED_FILES")) {
QStringList &out = place["QMAKE_INTERNAL_INCLUDED_FILES"];
const QStringList &in = tmp["QMAKE_INTERNAL_INCLUDED_FILES"];
for(int i = 0; i < in.size(); ++i) {
if(out.indexOf(in[i]) == -1)
out += in[i];
}
}
ret = tmp[seek_var];
}
}
break; }
case E_EVAL: {
if(args.count() < 1 || args.count() > 2) {
fprintf(stderr, "%s:%d: eval(variable) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
const QHash<QString, QStringList> *source = &place;
if(args.count() == 2) {
if(args.at(1) == "Global") {
source = &vars;
} else if(args.at(1) == "Local") {
source = &place;
} else {
fprintf(stderr, "%s:%d: unexpected source to eval.\n", parser.file.toLatin1().constData(),
parser.line_no);
}
}
ret += source->value(args.at(0));
}
break; }
case E_LIST: {
static int x = 0;
QString tmp;
tmp.sprintf(".QMAKE_INTERNAL_TMP_VAR_%d", x++);
ret = QStringList(tmp);
QStringList &lst = (*((QHash<QString, QStringList>*)&place))[tmp];
lst.clear();
for(QStringList::ConstIterator arg_it = args.begin();
arg_it != args.end(); ++arg_it)
lst += split_value_list((*arg_it));
break; }
case E_SPRINTF: {
if(args.count() < 1) {
fprintf(stderr, "%s:%d: sprintf(format, ...) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
QString tmp = args.at(0);
for(int i = 1; i < args.count(); ++i)
tmp = tmp.arg(args.at(i));
ret = split_value_list(tmp);
}
break; }
case E_FORMAT_NUMBER:
if (args.count() > 2) {
fprintf(stderr, "%s:%d: format_number(number[, options...]) requires one or two arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
} 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 QString &opt, split_value_list(args.at(1))) {
if (opt.startsWith(QLatin1String("ibase="))) {
ibase = opt.mid(6).toInt();
} else if (opt.startsWith(QLatin1String("obase="))) {
obase = opt.mid(6).toInt();
} else if (opt.startsWith(QLatin1String("width="))) {
width = opt.mid(6).toInt();
} else if (opt == QLatin1String("zeropad")) {
zeropad = true;
} else if (opt == QLatin1String("padsign")) {
sign = PadSign;
} else if (opt == QLatin1String("alwayssign")) {
sign = AlwaysSign;
} else if (opt == QLatin1String("leftalign")) {
leftalign = true;
} else {
fprintf(stderr, "%s:%d: format_number(): invalid format option %s.\n",
parser.file.toLatin1().constData(), parser.line_no,
opt.toLatin1().constData());
goto formfail;
}
}
}
if (args.at(0).contains(QLatin1Char('.'))) {
fprintf(stderr, "%s:%d: format_number(): floats are currently not supported.\n",
parser.file.toLatin1().constData(), parser.line_no);
break;
}
bool ok;
qlonglong num = args.at(0).toLongLong(&ok, ibase);
if (!ok) {
fprintf(stderr, "%s:%d: format_number(): malformed number %s for base %d.\n",
parser.file.toLatin1().constData(), parser.line_no,
args.at(0).toLatin1().constData(), ibase);
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 += outstr;
}
formfail:
break;
case E_JOIN: {
if(args.count() < 1 || args.count() > 4) {
fprintf(stderr, "%s:%d: join(var, glue, before, after) requires four"
"arguments.\n", parser.file.toLatin1().constData(), parser.line_no);
} else {
QString glue, before, after;
if(args.count() >= 2)
glue = args[1];
if(args.count() >= 3)
before = args[2];
if(args.count() == 4)
after = args[3];
const QStringList &var = values(args.first(), place);
if(!var.isEmpty())
ret = split_value_list(before + var.join(glue) + after);
}
break; }
case E_SPLIT: {
if(args.count() < 1 || args.count() > 2) {
fprintf(stderr, "%s:%d split(var, sep) requires one or two arguments\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
QString sep = QString(Option::field_sep);
if(args.count() >= 2)
sep = args[1];
QStringList var = values(args.first(), place);
for(QStringList::ConstIterator vit = var.begin(); vit != var.end(); ++vit) {
QStringList lst = (*vit).split(sep);
for(QStringList::ConstIterator spltit = lst.begin(); spltit != lst.end(); ++spltit)
ret += (*spltit);
}
}
break; }
case E_BASENAME:
case E_DIRNAME:
case E_SECTION: {
bool regexp = false;
QString sep, var;
int beg=0, end=-1;
if(func_t == E_SECTION) {
if(args.count() != 3 && args.count() != 4) {
fprintf(stderr, "%s:%d section(var, sep, begin, end) requires three argument\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
var = args[0];
sep = args[1];
beg = args[2].toInt();
if(args.count() == 4)
end = args[3].toInt();
}
} else {
if(args.count() != 1) {
fprintf(stderr, "%s:%d %s(var) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no, func.toLatin1().constData());
} else {
var = args[0];
regexp = true;
sep = "[" + QRegExp::escape(Option::dir_sep) + "/]";
if(func_t == E_DIRNAME)
end = -2;
else
beg = -1;
}
}
if(!var.isNull()) {
const QStringList &l = values(var, place);
for(QStringList::ConstIterator it = l.begin(); it != l.end(); ++it) {
QString separator = sep;
if(regexp)
ret += (*it).section(QRegExp(separator), beg, end);
else
ret += (*it).section(separator, beg, end);
}
}
break; }
case E_FIND: {
if(args.count() != 2) {
fprintf(stderr, "%s:%d find(var, str) requires two arguments\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
QRegExp regx(args[1]);
const QStringList &var = values(args.first(), place);
for(QStringList::ConstIterator vit = var.begin();
vit != var.end(); ++vit) {
if(regx.indexIn(*vit) != -1)
ret += (*vit);
}
}
break; }
case E_SYSTEM: {
if(args.count() < 1 || args.count() > 2) {
fprintf(stderr, "%s:%d system(execut) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
bool blob = false;
bool lines = false;
bool singleLine = true;
if (args.count() > 1) {
if (!args.at(1).compare(QLatin1String("false"), Qt::CaseInsensitive))
singleLine = false;
else if (!args.at(1).compare(QLatin1String("blob"), Qt::CaseInsensitive))
blob = true;
else if (!args.at(1).compare(QLatin1String("lines"), Qt::CaseInsensitive))
lines = true;
}
QByteArray bytes = getCommandOutput(args.at(0));
if (lines) {
QTextStream stream(bytes);
while (!stream.atEnd())
ret += stream.readLine();
} else {
QString output = QString::fromLocal8Bit(bytes);
if (blob) {
ret += 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) {
fprintf(stderr, "%s:%d unique(var) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
const QStringList &var = values(args.first(), place);
for(int i = 0; i < var.count(); i++) {
if(!ret.contains(var[i]))
ret.append(var[i]);
}
}
break; }
case E_REVERSE:
if (args.count() != 1) {
fprintf(stderr, "%s:%d reverse(var) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
QStringList var = values(args.first(), place);
for (int i = 0; i < var.size() / 2; i++)
var.swap(i, 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) {
QChar *i_data = args[i].data();
int i_len = args[i].length();
for(int x = 0; x < i_len; ++x) {
if(*(i_data+x) == '\\' && x < i_len-1) {
if(*(i_data+x+1) == '\\') {
++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) == mapped_quotes[i].in) {
*(i_data+x) = 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(QString(i_data, i_len));
}
break; }
case E_RE_ESCAPE: {
for(int i = 0; i < args.size(); ++i)
ret += QRegExp::escape(args[i]);
break; }
case E_VAL_ESCAPE:
if (args.count() != 1) {
fprintf(stderr, "%s:%d val_escape(var) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
QStringList vals = values(args.at(0), place);
ret.reserve(vals.length());
foreach (const QString &str, vals)
ret += quoteValue(str);
}
break;
case E_UPPER:
case E_LOWER: {
for(int i = 0; i < args.size(); ++i) {
if(func_t == E_UPPER)
ret += args[i].toUpper();
else
ret += args[i].toLower();
}
break; }
case E_FILES: {
if(args.count() != 1 && args.count() != 2) {
fprintf(stderr, "%s:%d files(pattern) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
bool recursive = false;
if(args.count() == 2)
recursive = (args[1].toLower() == "true" || args[1].toInt());
QStringList dirs;
QString r = Option::normalizePath(args[0]);
int slash = r.lastIndexOf(QLatin1Char('/'));
if(slash != -1) {
dirs.append(r.left(slash));
r = r.mid(slash+1);
} else {
dirs.append("");
}
QRegExp regex(r, Qt::CaseSensitive, QRegExp::Wildcard);
for(int d = 0; d < dirs.count(); d++) {
QString dir = dirs[d];
if (!dir.isEmpty() && !dir.endsWith(QLatin1Char('/')))
dir += "/";
QDir qdir(dir);
for(int i = 0; i < (int)qdir.count(); ++i) {
if(qdir[i] == "." || qdir[i] == "..")
continue;
QString fname = dir + qdir[i];
if(QFileInfo(fname).isDir()) {
if(recursive)
dirs.append(fname);
}
if(regex.exactMatch(qdir[i]))
ret += fname;
}
}
}
break; }
case E_PROMPT: {
if(args.count() != 1) {
fprintf(stderr, "%s:%d prompt(question) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else if(pfile == "-") {
fprintf(stderr, "%s:%d prompt(question) cannot be used when '-o -' is used.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
QString msg = fixEnvVariables(args.first());
if(!msg.endsWith("?"))
msg += "?";
fprintf(stderr, "Project %s: %s ", func.toUpper().toLatin1().constData(),
msg.toLatin1().constData());
QFile qfile;
if(qfile.open(stdin, QIODevice::ReadOnly)) {
QTextStream t(&qfile);
ret = split_value_list(t.readLine());
}
}
break; }
case E_REPLACE: {
if(args.count() != 3 ) {
fprintf(stderr, "%s:%d replace(var, before, after) requires three arguments\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
const QRegExp before( args[1] );
const QString after( args[2] );
QStringList var = values(args.first(), place);
for(QStringList::Iterator it = var.begin(); it != var.end(); ++it)
ret += it->replace(before, after);
}
break; }
case E_SIZE: {
if(args.count() != 1) {
fprintf(stderr, "%s:%d: size(var) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
int size = values(args[0], place).size();
ret += QString::number(size);
}
break; }
case E_SORT_DEPENDS:
case E_RESOLVE_DEPENDS: {
if(args.count() < 1 || args.count() > 2) {
fprintf(stderr, "%s:%d: %s(var, prefix) requires one or two arguments.\n",
parser.file.toLatin1().constData(), parser.line_no, func.toLatin1().constData());
} else {
QHash<QString, QSet<QString> > dependencies;
QHash<QString, QStringList> dependees;
QStringList rootSet;
QStringList orgList = values(args[0], place);
populateDeps(orgList, (args.count() != 2 ? QString() : args[1]),
dependencies, dependees, rootSet, place);
for (int i = 0; i < rootSet.size(); ++i) {
const QString &item = rootSet.at(i);
if ((func_t == E_RESOLVE_DEPENDS) || orgList.contains(item))
ret.prepend(item);
foreach (const QString &dep, dependees[item]) {
QSet<QString> &dset = dependencies[dep];
dset.remove(rootSet.at(i)); // *Don't* use 'item' - rootSet may have changed!
if (dset.isEmpty())
rootSet << dep;
}
}
}
break; }
case E_ENUMERATE_VARS:
ret += place.keys();
break;
case E_SHADOWED: {
QString val = QDir::cleanPath(QFileInfo(args.at(0)).absoluteFilePath());
if (Option::mkfile::source_root.isEmpty()) {
ret += val;
} else if (val.startsWith(Option::mkfile::source_root)
&& (val.length() == Option::mkfile::source_root.length()
|| val.at(Option::mkfile::source_root.length()) == QLatin1Char('/'))) {
ret += Option::mkfile::build_root + val.mid(Option::mkfile::source_root.length());
}
break; }
case E_ABSOLUTE_PATH:
if (args.count() > 2)
fprintf(stderr, "%s:%d absolute_path(path[, base]) requires one or two arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
else
ret += QDir::cleanPath(QDir(args.count() > 1 ? args.at(1) : QString())
.absoluteFilePath(args.at(0)));
break;
case E_RELATIVE_PATH:
if (args.count() > 2)
fprintf(stderr, "%s:%d relative_path(path[, base]) requires one or two arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
else
ret += QDir::cleanPath(QDir(args.count() > 1 ? args.at(1) : QString())
.relativeFilePath(args.at(0)));
break;
case E_CLEAN_PATH:
if (args.count() != 1)
fprintf(stderr, "%s:%d clean_path(path) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
else
ret += QDir::cleanPath(args.at(0));
break;
case E_SYSTEM_PATH:
if (args.count() != 1)
fprintf(stderr, "%s:%d system_path(path) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
else
ret += Option::fixPathToLocalOS(args.at(0), false);
break;
case E_SHELL_PATH:
if (args.count() != 1)
fprintf(stderr, "%s:%d shell_path(path) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
else
ret += Option::fixPathToTargetOS(args.at(0), false);
break;
case E_SYSTEM_QUOTE:
if (args.count() != 1)
fprintf(stderr, "%s:%d system_quote(args) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
else
#ifdef Q_OS_WIN
ret += shellQuoteWin(args.at(0));
#else
ret += shellQuoteUnix(args.at(0));
#endif
break;
case E_SHELL_QUOTE:
if (args.count() != 1)
fprintf(stderr, "%s:%d shell_quote(args) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
else if (Option::dir_sep.at(0) != QLatin1Char('/'))
ret += shellQuoteWin(args.at(0));
else
ret += shellQuoteUnix(args.at(0));
break;
default: {
fprintf(stderr, "%s:%d: Unknown replace function: %s\n",
parser.file.toLatin1().constData(), parser.line_no,
func.toLatin1().constData());
break; }
}
return ret;
}
bool
QMakeProject::doProjectTest(QString func, QStringList args, QHash<QString, QStringList> &place)
{
QList<QStringList> args_list;
for(int i = 0; i < args.size(); ++i) {
QStringList arg = split_value_list(args[i]), tmp;
for(int i = 0; i < arg.size(); ++i)
tmp += doVariableReplaceExpand(arg[i], place);
args_list += tmp;
}
return doProjectTest(func, args_list, place);
}
bool
QMakeProject::doProjectTest(QString func, QList<QStringList> args_list, QHash<QString, QStringList> &place)
{
func = func.trimmed();
if(testFunctions.contains(func)) {
FunctionBlock *defined = testFunctions[func];
QStringList ret;
function_blocks.push(defined);
defined->exec(args_list, this, place, ret);
bool correct = function_blocks.pop() == defined;
Q_ASSERT(correct); Q_UNUSED(correct);
if(ret.isEmpty()) {
return true;
} else {
if(ret.first() == "true") {
return true;
} else if(ret.first() == "false") {
return false;
} else {
bool ok;
int val = ret.first().toInt(&ok);
if(ok)
return val;
fprintf(stderr, "%s:%d Unexpected return value from test %s [%s].\n",
parser.file.toLatin1().constData(),
parser.line_no, func.toLatin1().constData(),
ret.join("::").toLatin1().constData());
}
return false;
}
return false;
}
QStringList args; //why don't the builtin functions just use args_list? --Sam
for(int i = 0; i < args_list.size(); ++i)
args += args_list[i].join(QString(Option::field_sep));
TestFunc func_t = qmake_testFunctions().value(func);
debug_msg(1, "Running project test: %s(%s) [%d]",
func.toLatin1().constData(), args.join("::").toLatin1().constData(), func_t);
switch(func_t) {
case T_REQUIRES:
return doProjectCheckReqs(args, place);
case T_LESSTHAN:
case T_GREATERTHAN: {
if(args.count() != 2) {
fprintf(stderr, "%s:%d: %s(variable, value) requires two arguments.\n", parser.file.toLatin1().constData(),
parser.line_no, func.toLatin1().constData());
return false;
}
QString rhs(args[1]), lhs(values(args[0], place).join(QString(Option::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 lhs_int > rhs_int;
return lhs_int < rhs_int;
}
}
if(func_t == T_GREATERTHAN)
return lhs > rhs;
return lhs < rhs; }
case T_IF: {
if(args.count() != 1) {
fprintf(stderr, "%s:%d: if(condition) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
const QString cond = args.first();
const QChar *d = cond.unicode();
QChar quote = 0;
bool ret = true, or_op = false;
QString test;
for(int d_off = 0, parens = 0, d_len = cond.size(); d_off < d_len; ++d_off) {
if(!quote.isNull()) {
if(*(d+d_off) == quote)
quote = QChar();
} else if(*(d+d_off) == '(') {
++parens;
} else if(*(d+d_off) == ')') {
--parens;
} else if(*(d+d_off) == '"' /*|| *(d+d_off) == '\''*/) {
quote = *(d+d_off);
}
if(!parens && quote.isNull() && (*(d+d_off) == QLatin1Char(':') || *(d+d_off) == QLatin1Char('|') || d_off == d_len-1)) {
if(d_off == d_len-1)
test += *(d+d_off);
if(!test.isEmpty()) {
if (or_op != ret)
ret = doProjectTest(test, place);
test.clear();
}
if(*(d+d_off) == QLatin1Char(':')) {
or_op = false;
} else if(*(d+d_off) == QLatin1Char('|')) {
or_op = true;
}
} else {
test += *(d+d_off);
}
}
return ret; }
case T_EQUALS:
if(args.count() != 2) {
fprintf(stderr, "%s:%d: %s(variable, value) requires two arguments.\n", parser.file.toLatin1().constData(),
parser.line_no, func.toLatin1().constData());
return false;
}
return values(args[0], place).join(QString(Option::field_sep)) == args[1];
case T_EXISTS: {
if(args.count() != 1) {
fprintf(stderr, "%s:%d: exists(file) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
QString file = Option::normalizePath(args.first());
if(QFile::exists(file))
return true;
//regular expression I guess
QString dirstr = qmake_getpwd();
int slsh = file.lastIndexOf(QLatin1Char('/'));
if(slsh != -1) {
dirstr = file.left(slsh+1);
file = file.right(file.length() - slsh - 1);
}
return QDir(dirstr).entryList(QStringList(file)).count(); }
case T_EXPORT:
if(args.count() != 1) {
fprintf(stderr, "%s:%d: export(variable) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
for(int i = 0; i < function_blocks.size(); ++i) {
FunctionBlock *f = function_blocks.at(i);
f->vars[args[0]] = values(args[0], place);
if(!i && f->calling_place)
(*f->calling_place)[args[0]] = values(args[0], place);
}
return true;
case T_CLEAR:
if(args.count() != 1) {
fprintf(stderr, "%s:%d: clear(variable) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
if(!place.contains(args[0]))
return false;
place[args[0]].clear();
return true;
case T_UNSET:
if(args.count() != 1) {
fprintf(stderr, "%s:%d: unset(variable) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
if(!place.contains(args[0]))
return false;
place.remove(args[0]);
return true;
case T_EVAL: {
if(args.count() < 1 && 0) {
fprintf(stderr, "%s:%d: eval(project) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
QString project = args.join(" ");
parser_info pi = parser;
parser.from_file = false;
parser.file = "(eval)";
parser.line_no = 0;
QTextStream t(&project, QIODevice::ReadOnly);
bool ret = read(t, place);
parser = pi;
return ret; }
case T_CONFIG: {
if(args.count() < 1 || args.count() > 2) {
fprintf(stderr, "%s:%d: CONFIG(config) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
if(args.count() == 1)
return isActiveConfig(args[0]);
const QStringList mutuals = args[1].split('|');
const QStringList &configs = values("CONFIG", place);
for(int i = configs.size()-1; i >= 0; i--) {
for(int mut = 0; mut < mutuals.count(); mut++) {
if(configs[i] == mutuals[mut].trimmed())
return (configs[i] == args[0]);
}
}
return false; }
case T_SYSTEM:
if(args.count() < 1 || args.count() > 2) {
fprintf(stderr, "%s:%d: system(exec) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
if(args.count() == 2) {
const QString sarg = args[1];
if (sarg.toLower() == "true" || sarg.toInt())
warn_msg(WarnParser, "%s:%d: system()'s second argument is now hard-wired to false.\n",
parser.file.toLatin1().constData(), parser.line_no);
}
return system(args[0].toLatin1().constData()) == 0;
case T_RETURN:
if(function_blocks.isEmpty()) {
fprintf(stderr, "%s:%d unexpected return()\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
FunctionBlock *f = function_blocks.top();
f->cause_return = true;
if(args_list.count() >= 1)
f->return_value += args_list[0];
}
return true;
case T_BREAK:
if(iterator)
iterator->cause_break = true;
else
fprintf(stderr, "%s:%d unexpected break()\n",
parser.file.toLatin1().constData(), parser.line_no);
return true;
case T_NEXT:
if(iterator)
iterator->cause_next = true;
else
fprintf(stderr, "%s:%d unexpected next()\n",
parser.file.toLatin1().constData(), parser.line_no);
return true;
case T_DEFINED:
if(args.count() < 1 || args.count() > 2) {
fprintf(stderr, "%s:%d: defined(function) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
} else {
if(args.count() > 1) {
if(args[1] == "test")
return testFunctions.contains(args[0]);
else if(args[1] == "replace")
return replaceFunctions.contains(args[0]);
else if(args[1] == "var")
return place.contains(args[0]);
fprintf(stderr, "%s:%d: defined(function, type): unexpected type [%s].\n",
parser.file.toLatin1().constData(), parser.line_no,
args[1].toLatin1().constData());
} else {
if(replaceFunctions.contains(args[0]) || testFunctions.contains(args[0]))
return true;
}
}
return false;
case T_CONTAINS: {
if(args.count() < 2 || args.count() > 3) {
fprintf(stderr, "%s:%d: contains(var, val) requires at lesat 2 arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
QRegExp regx(args[1]);
const QStringList &l = values(args[0], place);
if(args.count() == 2) {
for(int i = 0; i < l.size(); ++i) {
const QString val = l[i];
if(regx.exactMatch(val) || val == args[1])
return true;
}
} else {
const QStringList mutuals = args[2].split('|');
for(int i = l.size()-1; i >= 0; i--) {
const QString val = l[i];
for(int mut = 0; mut < mutuals.count(); mut++) {
if(val == mutuals[mut].trimmed())
return (regx.exactMatch(val) || val == args[1]);
}
}
}
return false; }
case T_INFILE: {
if(args.count() < 2 || args.count() > 3) {
fprintf(stderr, "%s:%d: infile(file, var, val) requires at least 2 arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
bool ret = false;
QHash<QString, QStringList> tmp;
if(doProjectInclude(Option::normalizePath(args[0]), IncludeFlagNewParser, tmp) == IncludeSuccess) {
if(tmp.contains("QMAKE_INTERNAL_INCLUDED_FILES")) {
QStringList &out = place["QMAKE_INTERNAL_INCLUDED_FILES"];
const QStringList &in = tmp["QMAKE_INTERNAL_INCLUDED_FILES"];
for(int i = 0; i < in.size(); ++i) {
if(out.indexOf(in[i]) == -1)
out += in[i];
}
}
if(args.count() == 2) {
ret = tmp.contains(args[1]);
} else {
QRegExp regx(args[2]);
const QStringList &l = tmp[args[1]];
for(QStringList::ConstIterator it = l.begin(); it != l.end(); ++it) {
if(regx.exactMatch((*it)) || (*it) == args[2]) {
ret = true;
break;
}
}
}
}
return ret; }
case T_COUNT:
if(args.count() != 2 && args.count() != 3) {
fprintf(stderr, "%s:%d: count(var, count) requires two arguments.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
if(args.count() == 3) {
QString comp = args[2];
if(comp == ">" || comp == "greaterThan")
return values(args[0], place).count() > args[1].toInt();
if(comp == ">=")
return values(args[0], place).count() >= args[1].toInt();
if(comp == "<" || comp == "lessThan")
return values(args[0], place).count() < args[1].toInt();
if(comp == "<=")
return values(args[0], place).count() <= args[1].toInt();
if(comp == "equals" || comp == "isEqual" || comp == "=" || comp == "==")
return values(args[0], place).count() == args[1].toInt();
fprintf(stderr, "%s:%d: unexpected modifier to count(%s)\n", parser.file.toLatin1().constData(),
parser.line_no, comp.toLatin1().constData());
return false;
}
return values(args[0], place).count() == args[1].toInt();
case T_ISEMPTY:
if(args.count() != 1) {
fprintf(stderr, "%s:%d: isEmpty(var) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
return values(args[0], place).isEmpty();
case T_INCLUDE:
case T_LOAD: {
QString parseInto;
const bool include_statement = (func_t == T_INCLUDE);
bool ignore_error = false;
if(args.count() >= 2) {
if(func_t == T_INCLUDE) {
parseInto = args[1];
if (args.count() == 3){
QString sarg = args[2];
if (sarg.toLower() == "true" || sarg.toInt())
ignore_error = true;
}
} else {
QString sarg = args[1];
ignore_error = (sarg.toLower() == "true" || sarg.toInt());
}
} else if(args.count() != 1) {
QString func_desc = "load(feature)";
if(include_statement)
func_desc = "include(file)";
fprintf(stderr, "%s:%d: %s requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no, func_desc.toLatin1().constData());
return false;
}
QString file = Option::normalizePath(args.first());
uchar flags = IncludeFlagNone;
if(!include_statement)
flags |= IncludeFlagFeature;
IncludeStatus stat = IncludeFailure;
if(!parseInto.isEmpty()) {
QHash<QString, QStringList> symbols;
stat = doProjectInclude(file, flags|IncludeFlagNewProject, symbols);
if(stat == IncludeSuccess) {
QHash<QString, QStringList> out_place;
for(QHash<QString, QStringList>::ConstIterator it = place.begin(); it != place.end(); ++it) {
const QString var = it.key();
if(var != parseInto && !var.startsWith(parseInto + "."))
out_place.insert(var, it.value());
}
for(QHash<QString, QStringList>::ConstIterator it = symbols.begin(); it != symbols.end(); ++it) {
const QString var = it.key();
if(!var.startsWith("."))
out_place.insert(parseInto + "." + it.key(), it.value());
}
place = out_place;
}
} else {
stat = doProjectInclude(file, flags, place);
}
if(stat == IncludeFeatureAlreadyLoaded) {
warn_msg(WarnParser, "%s:%d: Duplicate of loaded feature %s",
parser.file.toLatin1().constData(), parser.line_no, file.toLatin1().constData());
} else if(stat == IncludeNoExist && !ignore_error) {
warn_msg(WarnAll, "%s:%d: Unable to find file for inclusion %s",
parser.file.toLatin1().constData(), parser.line_no, file.toLatin1().constData());
return false;
} else if(stat >= IncludeFailure) {
if(!ignore_error) {
printf("Project LOAD(): Feature %s cannot be found.\n", file.toLatin1().constData());
if (!ignore_error)
#if defined(QT_BUILD_QMAKE_LIBRARY)
return false;
#else
exit(3);
#endif
}
return false;
}
return true; }
case T_DEBUG: {
if(args.count() != 2) {
fprintf(stderr, "%s:%d: debug(level, message) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no);
return false;
}
QString msg = fixEnvVariables(args[1]);
debug_msg(args[0].toInt(), "Project DEBUG: %s", msg.toLatin1().constData());
return true; }
case T_LOG:
case T_ERROR:
case T_MESSAGE:
case T_WARNING: {
if(args.count() != 1) {
fprintf(stderr, "%s:%d: %s(message) requires one argument.\n", parser.file.toLatin1().constData(),
parser.line_no, func.toLatin1().constData());
return false;
}
QString msg = fixEnvVariables(args.first());
if (func_t == T_LOG) {
fputs(msg.toLatin1().constData(), stderr);
} else {
fprintf(stderr, "Project %s: %s\n", func.toUpper().toLatin1().constData(), msg.toLatin1().constData());
if (func == "error")
#if defined(QT_BUILD_QMAKE_LIBRARY)
return false;
#else
exit(2);
#endif
}
return true; }
case T_OPTION:
if (args.count() != 1) {
fprintf(stderr, "%s:%d: option() requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
if (args.first() == "host_build") {
if (!host_build && isActiveConfig("cross_compile")) {
host_build = true;
need_restart = true;
}
} else {
fprintf(stderr, "%s:%d: unrecognized option() argument '%s'.\n",
parser.file.toLatin1().constData(), parser.line_no,
args.first().toLatin1().constData());
return false;
}
return true;
case T_CACHE: {
if (args.count() > 3) {
fprintf(stderr, "%s:%d: cache(var, [set|add|sub] [transient] [super], [srcvar]) requires one to three arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
bool persist = true;
bool super = false;
enum { CacheSet, CacheAdd, CacheSub } mode = CacheSet;
QString srcvar;
if (args.count() >= 2) {
foreach (const QString &opt, split_value_list(args.at(1))) {
if (opt == QLatin1String("transient")) {
persist = false;
} else if (opt == QLatin1String("super")) {
super = true;
} else if (opt == QLatin1String("set")) {
mode = CacheSet;
} else if (opt == QLatin1String("add")) {
mode = CacheAdd;
} else if (opt == QLatin1String("sub")) {
mode = CacheSub;
} else {
fprintf(stderr, "%s:%d: cache(): invalid flag %s.\n",
parser.file.toLatin1().constData(), parser.line_no,
opt.toLatin1().constData());
return false;
}
}
if (args.count() >= 3) {
srcvar = args.at(2);
} else if (mode != CacheSet) {
fprintf(stderr, "%s:%d: cache(): modes other than 'set' require a source variable.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
}
QString varstr;
QString dstvar = args.at(0);
if (!dstvar.isEmpty()) {
if (srcvar.isEmpty())
srcvar = dstvar;
if (!place.contains(srcvar)) {
fprintf(stderr, "%s:%d: variable %s is not defined.\n",
parser.file.toLatin1().constData(), parser.line_no,
srcvar.toLatin1().constData());
return false;
}
// The current ("native") value can differ from the cached value, e.g., the current
// CONFIG will typically have more values than the cached one. Therefore we deal with
// them separately.
const QStringList diffval = values(srcvar, place);
const QStringList oldval = base_vars.value(dstvar);
QStringList newval;
if (mode == CacheSet) {
newval = diffval;
} else {
newval = oldval;
if (mode == CacheAdd)
newval += diffval;
else
subAll(&newval, diffval);
}
// We assume that whatever got the cached value to be what it is now will do so
// the next time as well, so it is OK that the early exit here will skip the
// persisting as well.
if (oldval == newval)
return true;
base_vars[dstvar] = newval;
do {
if (dstvar == "QMAKEPATH")
cached_qmakepath = newval;
else if (dstvar == "QMAKEFEATURES")
cached_qmakefeatures = newval;
else
break;
invalidateFeatureRoots();
} while (false);
if (!persist)
return true;
varstr = dstvar;
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 QString &vval, diffval) {
varstr += QLatin1String(" \\\n ");
varstr += quoteValue(vval);
}
}
varstr += QLatin1Char('\n');
}
QString fn;
if (super) {
if (superfile.isEmpty()) {
superfile = Option::output_dir + QLatin1String("/.qmake.super");
printf("Info: creating super cache file %s\n", superfile.toLatin1().constData());
vars["_QMAKE_SUPER_CACHE_"] << superfile;
}
fn = superfile;
} else {
if (cachefile.isEmpty()) {
cachefile = Option::output_dir + QLatin1String("/.qmake.cache");
printf("Info: creating cache file %s\n", cachefile.toLatin1().constData());
vars["_QMAKE_CACHE_"] << cachefile;
if (cached_build_root.isEmpty()) {
cached_build_root = Option::output_dir;
cached_source_root = values("_PRO_FILE_PWD_", place).first();
if (cached_source_root == cached_build_root)
cached_source_root.clear();
invalidateFeatureRoots();
}
}
fn = cachefile;
}
QFileInfo qfi(fn);
if (!QDir::current().mkpath(qfi.path())) {
fprintf(stderr, "%s:%d: ERROR creating cache directory %s\n",
parser.file.toLatin1().constData(), parser.line_no,
qfi.path().toLatin1().constData());
return false;
}
QString errStr;
if (!writeFile(fn, QIODevice::Append, varstr, &errStr)) {
fprintf(stderr, "ERROR writing cache file %s: %s\n",
fn.toLatin1().constData(), errStr.toLatin1().constData());
#if defined(QT_BUILD_QMAKE_LIBRARY)
return false;
#else
exit(2);
#endif
}
return true; }
case T_MKPATH:
if (args.count() != 1) {
fprintf(stderr, "%s:%d: mkpath(name) requires one argument.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
if (!QDir::current().mkpath(args.at(0))) {
fprintf(stderr, "%s:%d: ERROR creating directory %s\n",
parser.file.toLatin1().constData(), parser.line_no,
QDir::toNativeSeparators(args.at(0)).toLatin1().constData());
return false;
}
return true;
case T_WRITE_FILE: {
if (args.count() > 3) {
fprintf(stderr, "%s:%d: write_file(name, [content var, [append]]) requires one to three arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
QIODevice::OpenMode mode = QIODevice::Truncate;
QString contents;
if (args.count() >= 2) {
QStringList vals = values(args.at(1), place);
if (!vals.isEmpty())
contents = vals.join(QLatin1String("\n")) + QLatin1Char('\n');
if (args.count() >= 3)
if (!args.at(2).compare(QLatin1String("append"), Qt::CaseInsensitive))
mode = QIODevice::Append;
}
QFileInfo qfi(args.at(0));
if (!QDir::current().mkpath(qfi.path())) {
fprintf(stderr, "%s:%d: ERROR creating directory %s\n",
parser.file.toLatin1().constData(), parser.line_no,
qfi.path().toLatin1().constData());
return false;
}
QString errStr;
if (!writeFile(args.at(0), mode, contents, &errStr)) {
fprintf(stderr, "%s:%d ERROR writing %s: %s\n",
parser.file.toLatin1().constData(), parser.line_no,
args.at(0).toLatin1().constData(), errStr.toLatin1().constData());
return false;
}
return true; }
case T_TOUCH: {
if (args.count() != 2) {
fprintf(stderr, "%s:%d: touch(file, reffile) requires two arguments.\n",
parser.file.toLatin1().constData(), parser.line_no);
return false;
}
#ifdef Q_OS_UNIX
struct stat st;
if (stat(args.at(1).toLocal8Bit().constData(), &st)) {
fprintf(stderr, "%s:%d: ERROR: cannot stat() reference file %s: %s.\n",
parser.file.toLatin1().constData(), parser.line_no,
args.at(1).toLatin1().constData(), strerror(errno));
return false;
}
struct utimbuf utb;
utb.actime = time(0);
utb.modtime = st.st_mtime;
if (utime(args.at(0).toLocal8Bit().constData(), &utb)) {
fprintf(stderr, "%s:%d: ERROR: cannot touch %s: %s.\n",
parser.file.toLatin1().constData(), parser.line_no,
args.at(0).toLatin1().constData(), strerror(errno));
return false;
}
#else
HANDLE rHand = CreateFile((wchar_t*)args.at(1).utf16(),
GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (rHand == INVALID_HANDLE_VALUE) {
fprintf(stderr, "%s:%d: ERROR: cannot open() reference file %s: %s.\n",
parser.file.toLatin1().constData(), parser.line_no,
args.at(1).toLatin1().constData(),
windowsErrorCode().toLatin1().constData());
return false;
}
FILETIME ft;
GetFileTime(rHand, 0, 0, &ft);
CloseHandle(rHand);
HANDLE wHand = CreateFile((wchar_t*)args.at(0).utf16(),
GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (wHand == INVALID_HANDLE_VALUE) {
fprintf(stderr, "%s:%d: ERROR: cannot open %s: %s.\n",
parser.file.toLatin1().constData(), parser.line_no,
args.at(0).toLatin1().constData(),
windowsErrorCode().toLatin1().constData());
return false;
}
SetFileTime(wHand, 0, 0, &ft);
CloseHandle(wHand);
#endif
break; }
default:
fprintf(stderr, "%s:%d: Unknown test function: %s\n", parser.file.toLatin1().constData(), parser.line_no,
func.toLatin1().constData());
}
return false;
}
bool
QMakeProject::doProjectCheckReqs(const QStringList &deps, QHash<QString, QStringList> &place)
{
bool ret = false;
for(QStringList::ConstIterator it = deps.begin(); it != deps.end(); ++it) {
bool test = doProjectTest((*it), place);
if(!test) {
debug_msg(1, "Project Parser: %s:%d Failed test: REQUIRES = %s",
parser.file.toLatin1().constData(), parser.line_no,
(*it).toLatin1().constData());
place["QMAKE_FAILED_REQUIREMENTS"].append((*it));
ret = false;
}
}
return ret;
}
bool
QMakeProject::test(const QString &v)
{
QHash<QString, QStringList> tmp = vars;
return doProjectTest(v, tmp);
}
bool
QMakeProject::test(const QString &func, const QList<QStringList> &args)
{
QHash<QString, QStringList> tmp = vars;
return doProjectTest(func, args, tmp);
}
QStringList
QMakeProject::expand(const QString &str)
{
bool ok;
QHash<QString, QStringList> tmp = vars;
const QStringList ret = doVariableReplaceExpand(str, tmp, &ok);
if(ok)
return ret;
return QStringList();
}
QString
QMakeProject::expand(const QString &str, const QString &file, int line)
{
bool ok;
parser_info pi = parser;
parser.file = file;
parser.line_no = line;
parser.from_file = false;
QHash<QString, QStringList> tmp = vars;
const QStringList ret = doVariableReplaceExpand(str, tmp, &ok);
parser = pi;
return ok ? ret.join(QString(Option::field_sep)) : QString();
}
QStringList
QMakeProject::expand(const QString &func, const QList<QStringList> &args)
{
QHash<QString, QStringList> tmp = vars;
return doProjectExpand(func, args, tmp);
}
bool
QMakeProject::doVariableReplace(QString &str, QHash<QString, QStringList> &place)
{
bool ret;
str = doVariableReplaceExpand(str, place, &ret).join(QString(Option::field_sep));
return ret;
}
QStringList
QMakeProject::doVariableReplaceExpand(const QString &str, QHash<QString, QStringList> &place, bool *ok)
{
QStringList ret;
if(ok)
*ok = true;
if(str.isEmpty())
return ret;
const ushort LSQUARE = '[';
const ushort RSQUARE = ']';
const ushort LCURLY = '{';
const ushort RCURLY = '}';
const ushort LPAREN = '(';
const ushort RPAREN = ')';
const ushort DOLLAR = '$';
const ushort SLASH = '\\';
const ushort UNDERSCORE = '_';
const ushort DOT = '.';
const ushort SPACE = ' ';
const ushort TAB = '\t';
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();
enum { VAR, ENVIRON, FUNCTION, PROPERTY } var_type = VAR;
unicode = str_data[++i].unicode();
if(unicode == LSQUARE) {
unicode = str_data[++i].unicode();
term = RSQUARE;
var_type = PROPERTY;
} else if(unicode == LCURLY) {
unicode = str_data[++i].unicode();
var_type = VAR;
term = RCURLY;
} else if(unicode == LPAREN) {
unicode = str_data[++i].unicode();
var_type = ENVIRON;
term = RPAREN;
}
while(1) {
if(!(unicode & (0xFF<<8)) &&
unicode != DOT && unicode != UNDERSCORE &&
//unicode != SINGLEQUOTE && unicode != DOUBLEQUOTE &&
(unicode < 'a' || unicode > 'z') && (unicode < 'A' || unicode > 'Z') &&
(unicode < '0' || unicode > '9') && (!term || unicode != '/'))
break;
var.append(QChar(unicode));
if(++i == str_len)
break;
unicode = str_data[i].unicode();
// at this point, i points to either the 'term' or 'next' character (which is in unicode)
}
if(var_type == VAR && unicode == LPAREN) {
var_type = FUNCTION;
int depth = 0;
while(1) {
if(++i == str_len)
break;
unicode = str_data[i].unicode();
if(unicode == LPAREN) {
depth++;
} else if(unicode == RPAREN) {
if(!depth)
break;
--depth;
}
args.append(QChar(unicode));
}
if(++i < str_len)
unicode = str_data[i].unicode();
else
unicode = 0;
// at this point i is pointing to the 'next' character (which is in unicode)
// this might actually be a term character since you can do $${func()}
}
if(term) {
if(unicode != term) {
qmake_error_msg("Missing " + QString(term) + " terminator [found " + (unicode?QString(unicode):QString("end-of-line")) + "]");
if(ok)
*ok = false;
return QStringList();
}
} 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;
if(var_type == ENVIRON) {
replacement = split_value_list(QString::fromLocal8Bit(qgetenv(var.toLatin1().constData())));
} else if(var_type == PROPERTY) {
if(prop)
replacement = split_value_list(prop->value(var));
} else if(var_type == FUNCTION) {
replacement = doProjectExpand(var, args, place);
} else if(var_type == VAR) {
replacement = magicValues(var, place);
}
if(!(replaced++) && start_var)
current = str.left(start_var);
if(!replacement.isEmpty()) {
if(quote) {
current += replacement.join(QString(Option::field_sep));
} else {
current += replacement.takeFirst();
if(!replacement.isEmpty()) {
if(!current.isEmpty())
ret.append(current);
current = replacement.takeLast();
if(!replacement.isEmpty())
ret += replacement;
}
}
}
debug_msg(2, "Project Parser [var replace]: %s -> %s",
str.toLatin1().constData(), var.toLatin1().constData(),
replacement.join("::").toLatin1().constData());
} else {
if(replaced)
current.append("$");
}
}
if(quote && unicode == quote) {
unicode = 0;
quote = 0;
} else if(unicode == SLASH) {
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;
}
}
if(!escape && !backslashWarned) {
backslashWarned = true;
warn_msg(WarnDeprecated, "%s:%d: Unescaped backslashes are deprecated.",
parser.file.toLatin1().constData(), parser.line_no);
}
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();
}
}
if(replaced && unicode)
current.append(QChar(unicode));
}
if(!replaced)
ret = QStringList(str);
else if(!current.isEmpty())
ret.append(current);
//qDebug() << "REPLACE" << str << ret;
if (quote)
warn_msg(WarnDeprecated, "%s:%d: Unmatched quotes are deprecated.",
parser.file.toLatin1().constData(), parser.line_no);
return ret;
}
QStringList QMakeProject::magicValues(const QString &_var, const QHash<QString, QStringList> &place) const
{
QString var = varMap(_var);
if (var == QLatin1String("_LINE_")) { //parser line number
return QStringList(QString::number(parser.line_no));
} else if(var == QLatin1String("_FILE_")) { //parser file
return QStringList(parser.file);
}
return place[var];
}
QStringList &QMakeProject::values(const QString &_var, QHash<QString, QStringList> &place)
{
QString var = varMap(_var);
return place[var];
}
bool QMakeProject::isEmpty(const QString &v) const
{
QHash<QString, QStringList>::ConstIterator it = vars.constFind(v);
return it == vars.constEnd() || it->isEmpty();
}
void
QMakeProject::dump() const
{
QStringList out;
for (QHash<QString, QStringList>::ConstIterator it = vars.begin(); it != vars.end(); ++it) {
if (!it.key().startsWith('.')) {
QString str = it.key() + " =";
foreach (const QString &v, it.value())
str += ' ' + quoteValue(v);
out << str;
}
}
out.sort();
foreach (const QString &v, out)
puts(qPrintable(v));
}
QT_END_NAMESPACE