qt5base-lts/qmake/library/qmakeglobals.cpp
Oswald Buddenhagen 7c87ffff9a qmake: skip empty parts when splitting strings in some more places
... and make it explicit where we can't do that for semantical or
backwards compat reasons.

most urgently, this fixes an assertion failure when $QMAKEFEATURES
contains empty paths (e.g., due to a trailing semicolon).

notable observation: QByteArray::split() has no argument for the split
behavior (it always keeps empty parts).

Task-number: QTBUG-47325
Change-Id: I72d4b2e154a2ed1802cfa98fb4a5211a68e43231
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
2018-06-01 09:09:35 +00:00

394 lines
13 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the qmake application of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qmakeglobals.h"
#include "qmakeevaluator.h"
#include "ioutils.h"
#include <qbytearray.h>
#include <qdatetime.h>
#include <qdebug.h>
#include <qdir.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qlist.h>
#include <qregexp.h>
#include <qset.h>
#include <qstack.h>
#include <qstring.h>
#include <qstringlist.h>
#include <qtextstream.h>
#ifdef PROEVALUATOR_THREAD_SAFE
# include <qthreadpool.h>
#endif
#ifdef Q_OS_UNIX
#include <unistd.h>
#include <sys/utsname.h>
#else
#include <windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#ifdef Q_OS_WIN32
#define QT_POPEN _popen
#define QT_POPEN_READ "rb"
#define QT_PCLOSE _pclose
#else
#define QT_POPEN popen
#define QT_POPEN_READ "r"
#define QT_PCLOSE pclose
#endif
QT_BEGIN_NAMESPACE
using namespace QMakeInternal; // for IoUtils
#define fL1S(s) QString::fromLatin1(s)
QMakeGlobals::QMakeGlobals()
{
do_cache = true;
#ifdef PROEVALUATOR_DEBUG
debugLevel = 0;
#endif
#ifdef Q_OS_WIN
dirlist_sep = QLatin1Char(';');
dir_sep = QLatin1Char('\\');
#else
dirlist_sep = QLatin1Char(':');
dir_sep = QLatin1Char('/');
#endif
}
QMakeGlobals::~QMakeGlobals()
{
qDeleteAll(baseEnvs);
}
QString QMakeGlobals::cleanSpec(QMakeCmdLineParserState &state, const QString &spec)
{
QString ret = QDir::cleanPath(spec);
if (ret.contains(QLatin1Char('/'))) {
QString absRet = IoUtils::resolvePath(state.pwd, ret);
if (QFile::exists(absRet))
ret = absRet;
}
return ret;
}
QMakeGlobals::ArgumentReturn QMakeGlobals::addCommandLineArguments(
QMakeCmdLineParserState &state, QStringList &args, int *pos)
{
enum { ArgNone, ArgConfig, ArgSpec, ArgXSpec, ArgTmpl, ArgTmplPfx, ArgCache, ArgQtConf } argState = ArgNone;
for (; *pos < args.count(); (*pos)++) {
QString arg = args.at(*pos);
switch (argState) {
case ArgConfig:
state.configs[state.phase] << arg;
break;
case ArgSpec:
qmakespec = args[*pos] = cleanSpec(state, arg);
break;
case ArgXSpec:
xqmakespec = args[*pos] = cleanSpec(state, arg);
break;
case ArgTmpl:
user_template = arg;
break;
case ArgTmplPfx:
user_template_prefix = arg;
break;
case ArgCache:
cachefile = args[*pos] = IoUtils::resolvePath(state.pwd, arg);
break;
case ArgQtConf:
qtconf = args[*pos] = IoUtils::resolvePath(state.pwd, arg);
break;
default:
if (arg.startsWith(QLatin1Char('-'))) {
if (arg == QLatin1String("--")) {
state.extraargs = args.mid(*pos + 1);
args.erase(args.begin() + *pos, args.end());
return ArgumentsOk;
}
if (arg == QLatin1String("-early"))
state.phase = QMakeEvalEarly;
else if (arg == QLatin1String("-before"))
state.phase = QMakeEvalBefore;
else if (arg == QLatin1String("-after"))
state.phase = QMakeEvalAfter;
else if (arg == QLatin1String("-late"))
state.phase = QMakeEvalLate;
else if (arg == QLatin1String("-config"))
argState = ArgConfig;
else if (arg == QLatin1String("-nocache"))
do_cache = false;
else if (arg == QLatin1String("-cache"))
argState = ArgCache;
else if (arg == QLatin1String("-qtconf"))
argState = ArgQtConf;
else if (arg == QLatin1String("-platform") || arg == QLatin1String("-spec"))
argState = ArgSpec;
else if (arg == QLatin1String("-xplatform") || arg == QLatin1String("-xspec"))
argState = ArgXSpec;
else if (arg == QLatin1String("-template") || arg == QLatin1String("-t"))
argState = ArgTmpl;
else if (arg == QLatin1String("-template_prefix") || arg == QLatin1String("-tp"))
argState = ArgTmplPfx;
else if (arg == QLatin1String("-win32"))
dir_sep = QLatin1Char('\\');
else if (arg == QLatin1String("-unix"))
dir_sep = QLatin1Char('/');
else
return ArgumentUnknown;
} else if (arg.contains(QLatin1Char('='))) {
state.cmds[state.phase] << arg;
} else {
return ArgumentUnknown;
}
continue;
}
argState = ArgNone;
}
if (argState != ArgNone)
return ArgumentMalformed;
return ArgumentsOk;
}
void QMakeGlobals::commitCommandLineArguments(QMakeCmdLineParserState &state)
{
if (!state.extraargs.isEmpty()) {
QString extra = fL1S("QMAKE_EXTRA_ARGS =");
for (const QString &ea : qAsConst(state.extraargs))
extra += QLatin1Char(' ') + QMakeEvaluator::quoteValue(ProString(ea));
state.cmds[QMakeEvalBefore] << extra;
}
for (int p = 0; p < 4; p++) {
if (!state.configs[p].isEmpty())
state.cmds[p] << (fL1S("CONFIG += ") + state.configs[p].join(QLatin1Char(' ')));
extra_cmds[p] = state.cmds[p].join(QLatin1Char('\n'));
}
if (xqmakespec.isEmpty())
xqmakespec = qmakespec;
}
void QMakeGlobals::useEnvironment()
{
if (xqmakespec.isEmpty())
xqmakespec = getEnv(QLatin1String("XQMAKESPEC"));
if (qmakespec.isEmpty()) {
qmakespec = getEnv(QLatin1String("QMAKESPEC"));
if (xqmakespec.isEmpty())
xqmakespec = qmakespec;
}
}
void QMakeGlobals::setCommandLineArguments(const QString &pwd, const QStringList &_args)
{
QStringList args = _args;
QMakeCmdLineParserState state(pwd);
for (int pos = 0; pos < args.size(); pos++)
addCommandLineArguments(state, args, &pos);
commitCommandLineArguments(state);
useEnvironment();
}
void QMakeGlobals::setDirectories(const QString &input_dir, const QString &output_dir)
{
if (input_dir != output_dir && !output_dir.isEmpty()) {
QString srcpath = input_dir;
if (!srcpath.endsWith(QLatin1Char('/')))
srcpath += QLatin1Char('/');
QString dstpath = output_dir;
if (!dstpath.endsWith(QLatin1Char('/')))
dstpath += QLatin1Char('/');
int srcLen = srcpath.length();
int dstLen = dstpath.length();
int lastSl = -1;
while (++lastSl, --srcLen, --dstLen,
srcLen && dstLen && srcpath.at(srcLen) == dstpath.at(dstLen))
if (srcpath.at(srcLen) == QLatin1Char('/'))
lastSl = 0;
source_root = srcpath.left(srcLen + lastSl);
build_root = dstpath.left(dstLen + lastSl);
}
}
QString QMakeGlobals::shadowedPath(const QString &fileName) const
{
if (source_root.isEmpty())
return fileName;
if (fileName.startsWith(source_root)
&& (fileName.length() == source_root.length()
|| fileName.at(source_root.length()) == QLatin1Char('/'))) {
return build_root + fileName.mid(source_root.length());
}
return QString();
}
QStringList QMakeGlobals::splitPathList(const QString &val) const
{
QStringList ret;
if (!val.isEmpty()) {
QString cwd(QDir::currentPath());
const QStringList vals = val.split(dirlist_sep, QString::SkipEmptyParts);
ret.reserve(vals.length());
for (const QString &it : vals)
ret << IoUtils::resolvePath(cwd, it);
}
return ret;
}
QString QMakeGlobals::getEnv(const QString &var) const
{
#ifdef PROEVALUATOR_SETENV
return environment.value(var);
#else
return QString::fromLocal8Bit(qgetenv(var.toLocal8Bit().constData()));
#endif
}
QStringList QMakeGlobals::getPathListEnv(const QString &var) const
{
return splitPathList(getEnv(var));
}
QString QMakeGlobals::expandEnvVars(const QString &str) const
{
QString string = str;
int startIndex = 0;
forever {
startIndex = string.indexOf(QLatin1Char('$'), startIndex);
if (startIndex < 0)
break;
if (string.length() < startIndex + 3)
break;
if (string.at(startIndex + 1) != QLatin1Char('(')) {
startIndex++;
continue;
}
int endIndex = string.indexOf(QLatin1Char(')'), startIndex + 2);
if (endIndex < 0)
break;
QString value = getEnv(string.mid(startIndex + 2, endIndex - startIndex - 2));
string.replace(startIndex, endIndex - startIndex + 1, value);
startIndex += value.length();
}
return string;
}
#ifndef QT_BUILD_QMAKE
#ifdef PROEVALUATOR_INIT_PROPS
bool QMakeGlobals::initProperties()
{
QByteArray data;
#ifndef QT_BOOTSTRAPPED
QProcess proc;
proc.start(qmake_abslocation, QStringList() << QLatin1String("-query"));
if (!proc.waitForFinished())
return false;
data = proc.readAll();
#else
if (FILE *proc = QT_POPEN(QString(IoUtils::shellQuote(qmake_abslocation)
+ QLatin1String(" -query")).toLocal8Bit(), QT_POPEN_READ)) {
char buff[1024];
while (!feof(proc))
data.append(buff, int(fread(buff, 1, 1023, proc)));
QT_PCLOSE(proc);
}
#endif
parseProperties(data, properties);
return true;
}
#endif
void QMakeGlobals::parseProperties(const QByteArray &data, QHash<ProKey, ProString> &properties)
{
const auto lines = data.split('\n');
for (QByteArray line : lines) {
int off = line.indexOf(':');
if (off < 0) // huh?
continue;
if (line.endsWith('\r'))
line.chop(1);
QString name = QString::fromLatin1(line.left(off));
ProString value = ProString(QDir::fromNativeSeparators(
QString::fromLocal8Bit(line.mid(off + 1))));
if (value.isNull())
value = ProString(""); // Make sure it is not null, to discern from missing keys
properties.insert(ProKey(name), value);
if (name.startsWith(QLatin1String("QT_"))) {
enum { PropPut, PropRaw, PropGet } variant;
if (name.contains(QLatin1Char('/'))) {
if (name.endsWith(QLatin1String("/raw")))
variant = PropRaw;
else if (name.endsWith(QLatin1String("/get")))
variant = PropGet;
else // Nothing falls back on /src or /dev.
continue;
name.chop(4);
} else {
variant = PropPut;
}
if (name.startsWith(QLatin1String("QT_INSTALL_"))) {
if (variant < PropRaw) {
if (name == QLatin1String("QT_INSTALL_PREFIX")
|| name == QLatin1String("QT_INSTALL_DATA")
|| name == QLatin1String("QT_INSTALL_LIBS")
|| name == QLatin1String("QT_INSTALL_BINS")) {
// Qt4 fallback
QString hname = name;
hname.replace(3, 7, QLatin1String("HOST"));
properties.insert(ProKey(hname), value);
properties.insert(ProKey(hname + QLatin1String("/get")), value);
properties.insert(ProKey(hname + QLatin1String("/src")), value);
}
properties.insert(ProKey(name + QLatin1String("/raw")), value);
}
if (variant <= PropRaw)
properties.insert(ProKey(name + QLatin1String("/dev")), value);
} else if (!name.startsWith(QLatin1String("QT_HOST_"))) {
continue;
}
if (variant != PropRaw) {
if (variant < PropGet)
properties.insert(ProKey(name + QLatin1String("/get")), value);
properties.insert(ProKey(name + QLatin1String("/src")), value);
}
}
}
}
#endif // QT_BUILD_QMAKE
QT_END_NAMESPACE