Files
qt-creator/src/plugins/android/androidtoolchain.cpp
Christian Kandeler 5bf9ac7d56 Android: Prevent invalid assertions in toolchain detection code
NDK r17 removed the MIPS toolchains, but kept the respective
directories, which was enough for us to assume the compiler binaries
were also present.
Instead, we now explicitly check for the presence of the compiler
binaries.

Change-Id: Ice68cf497a66f5e8b900e29634a988547fdee0d8
Reviewed-by: Vikas Pachdha <vikas.pachdha@qt.io>
2018-10-17 12:46:46 +00:00

519 lines
20 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 BogDan Vatra <bog_dan_ro@yahoo.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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.
**
****************************************************************************/
#include "androidtoolchain.h"
#include "androidconstants.h"
#include "androidconfigurations.h"
#include "androidqtversion.h"
#include <extensionsystem/pluginmanager.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtversionmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/toolchainmanager.h>
#include <projectexplorer/projectexplorer.h>
#include <utils/algorithm.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/synchronousprocess.h>
#include <QDir>
#include <QDirIterator>
#include <QFormLayout>
#include <QLabel>
#include <QRegExp>
#include <QVBoxLayout>
namespace {
const QLatin1String NDKGccVersionRegExp("-\\d[\\.\\d]+");
}
namespace Android {
namespace Internal {
using namespace ProjectExplorer;
using namespace Utils;
static const char ANDROID_QT_VERSION_KEY[] = "Qt4ProjectManager.Android.QtVersion";
static const char ANDROID_NDK_TC_VERION[] = "Qt4ProjectManager.Android.NDK_TC_VERION";
QHash<Abi, QList<int> > AndroidToolChainFactory::m_newestVersionForAbi;
FileName AndroidToolChainFactory::m_ndkLocation;
AndroidToolChain::AndroidToolChain(const Abi &abi, const QString &ndkToolChainVersion, Core::Id l, Detection d)
: GccToolChain(Constants::ANDROID_TOOLCHAIN_ID, d),
m_ndkToolChainVersion(ndkToolChainVersion), m_secondaryToolChain(false)
{
setLanguage(l);
setTargetAbi(abi);
setDisplayName(QString::fromLatin1("Android GCC (%1, %2-%3)")
.arg(ToolChainManager::displayNameOfLanguageId(l),
AndroidConfig::displayName(targetAbi()),
ndkToolChainVersion));
}
// for fromMap
AndroidToolChain::AndroidToolChain()
: GccToolChain(Constants::ANDROID_TOOLCHAIN_ID, ToolChain::ManualDetection),
m_secondaryToolChain(false)
{
}
AndroidToolChain::AndroidToolChain(const AndroidToolChain &tc) = default;
AndroidToolChain::~AndroidToolChain() = default;
static QString getArch(const QString &triple)
{
if (triple.indexOf("x86_64") == 0)
return QString::fromUtf8("x86_64");
if (triple.indexOf("i686") == 0)
return QString::fromUtf8("x86");
if (triple.indexOf("mips64") == 0)
return QString::fromUtf8("mips64");
if (triple.indexOf("mips") == 0)
return QString::fromUtf8("mips");
if (triple.indexOf("aarch64") == 0)
return QString::fromUtf8("arm64-v8a");
return QString::fromUtf8("armeabi-v7a");
}
// Paths added here are those that were used by qmake. They were taken from
// *qtsource*/qtbase/mkspecs/common/android-base-head.conf
// Adding them here allows us to use them for all build systems.
static void addBuiltInHeaderPaths(ProjectExplorer::HeaderPaths &paths,
const QString &triple, const QString &version)
{
const Utils::FileName ndkPath = AndroidConfigurations::currentConfig().ndkLocation();
// Get short version (for example 4.9)
auto versionNumber = QVersionNumber::fromString(version);
const QString clangVersion = QString("%1.%2")
.arg(versionNumber.majorVersion()).arg(versionNumber.minorVersion());
Utils::FileName stdcppPath = ndkPath;
stdcppPath.appendPath("sources/cxx-stl/gnu-libstdc++/" + clangVersion);
Utils::FileName includePath = stdcppPath;
Utils::FileName cppLibsPath = stdcppPath;
cppLibsPath.appendPath("libs/" + getArch(triple) + "/include/");
paths.prepend({cppLibsPath.toString(), ProjectExplorer::HeaderPathType::BuiltIn});
includePath.appendPath("include/");
paths.prepend({includePath.toString(), ProjectExplorer::HeaderPathType::BuiltIn});
paths.prepend({ndkPath.toString() + "/sysroot/usr/include/" + triple,
ProjectExplorer::HeaderPathType::BuiltIn});
paths.prepend({ndkPath.toString() + "/sysroot/usr/include",
ProjectExplorer::HeaderPathType::BuiltIn});
}
AndroidToolChain::BuiltInHeaderPathsRunner AndroidToolChain::createBuiltInHeaderPathsRunner() const
{
const QString triple = originalTargetTriple();
const QString version = this->version();
initExtraHeaderPathsFunction([triple, version] (HeaderPaths &paths) {
addBuiltInHeaderPaths(paths, triple, version);
});
return GccToolChain::createBuiltInHeaderPathsRunner();
}
QString AndroidToolChain::typeDisplayName() const
{
return AndroidToolChainFactory::tr("Android GCC");
}
bool AndroidToolChain::isValid() const
{
return GccToolChain::isValid() && targetAbi().isValid() && !m_ndkToolChainVersion.isEmpty()
&& compilerCommand().isChildOf(AndroidConfigurations::currentConfig().ndkLocation())
&& !originalTargetTriple().isEmpty();
}
void AndroidToolChain::addToEnvironment(Environment &env) const
{
// TODO this vars should be configurable in projects -> build tab
// TODO invalidate all .pro files !!!
env.set(QLatin1String("ANDROID_NDK_HOST"), AndroidConfigurations::currentConfig().toolchainHost());
env.set(QLatin1String("ANDROID_NDK_TOOLCHAIN_PREFIX"), AndroidConfig::toolchainPrefix(targetAbi()));
env.set(QLatin1String("ANDROID_NDK_TOOLS_PREFIX"), AndroidConfig::toolsPrefix(targetAbi()));
env.set(QLatin1String("ANDROID_NDK_TOOLCHAIN_VERSION"), m_ndkToolChainVersion);
const Utils::FileName javaHome = AndroidConfigurations::currentConfig().openJDKLocation();
if (!javaHome.isEmpty() && javaHome.toFileInfo().exists()) {
env.set(QLatin1String("JAVA_HOME"), javaHome.toString());
Utils::FileName javaBin = javaHome;
javaBin.appendPath(QLatin1String("bin"));
if (!Utils::contains(env.path(), [&javaBin](const Utils::FileName &p) { return p == javaBin; }))
env.prependOrSetPath(javaBin.toUserOutput());
}
env.set(QLatin1String("ANDROID_HOME"), AndroidConfigurations::currentConfig().sdkLocation().toString());
env.set(QLatin1String("ANDROID_SDK_ROOT"), AndroidConfigurations::currentConfig().sdkLocation().toString());
}
bool AndroidToolChain::operator ==(const ToolChain &tc) const
{
if (!GccToolChain::operator ==(tc))
return false;
return m_ndkToolChainVersion == static_cast<const AndroidToolChain &>(tc).m_ndkToolChainVersion;
}
std::unique_ptr<ToolChainConfigWidget> AndroidToolChain::createConfigurationWidget()
{
return std::make_unique<AndroidToolChainConfigWidget>(this);
}
FileName AndroidToolChain::suggestedDebugger() const
{
return AndroidConfigurations::currentConfig().gdbPath(targetAbi(), m_ndkToolChainVersion);
}
FileName AndroidToolChain::suggestedGdbServer() const
{
return AndroidConfigurations::currentConfig().gdbServer(targetAbi());
}
QVariantMap AndroidToolChain::toMap() const
{
QVariantMap result = GccToolChain::toMap();
result.insert(QLatin1String(ANDROID_NDK_TC_VERION), m_ndkToolChainVersion);
return result;
}
bool AndroidToolChain::fromMap(const QVariantMap &data)
{
if (!GccToolChain::fromMap(data))
return false;
if (data.contains(QLatin1String(ANDROID_QT_VERSION_KEY))) {
QString command = compilerCommand().toString();
QString ndkPath = AndroidConfigurations::currentConfig().ndkLocation().toString();
if (!command.startsWith(ndkPath))
return false;
command = command.mid(ndkPath.length());
if (!command.startsWith(QLatin1String("/toolchains/")))
return false;
command = command.mid(12);
int index = command.indexOf(QLatin1Char('/'));
if (index == -1)
return false;
command = command.left(index);
QRegExp versionRegExp(NDKGccVersionRegExp);
index = versionRegExp.indexIn(command);
if (index == -1)
return false;
m_ndkToolChainVersion = command.mid(index + 1);
QString platform = command.left(index);
setTargetAbi(AndroidConfig::abiForToolChainPrefix(platform));
} else {
m_ndkToolChainVersion = data.value(QLatin1String(ANDROID_NDK_TC_VERION)).toString();
}
Abi abi = targetAbi();
m_secondaryToolChain = AndroidToolChainFactory::versionCompareLess(AndroidToolChainFactory::versionNumberFromString(m_ndkToolChainVersion),
AndroidToolChainFactory::newestToolChainVersionForArch(abi));
return isValid();
}
FileNameList AndroidToolChain::suggestedMkspecList() const
{
return FileNameList() << FileName::fromLatin1("android-g++")
<< FileName::fromLatin1("android-clang");
}
QString AndroidToolChain::makeCommand(const Environment &env) const
{
FileName makePath = AndroidConfigurations::currentConfig().makePath();
if (makePath.exists())
return makePath.toString();
const Utils::FileNameList extraDirectories
= Utils::transform(AndroidConfigurations::currentConfig().makeExtraSearchDirectories(),
[](const QString &s) { return Utils::FileName::fromString(s); });
if (HostOsInfo::isWindowsHost()) {
makePath = env.searchInPath("ma-make.exe", extraDirectories);
if (!makePath.isEmpty())
return makePath.toString();
makePath = env.searchInPath("mingw32-make", extraDirectories);
return makePath.isEmpty() ? QLatin1String("mingw32-make") : makePath.toString();
}
makePath = env.searchInPath("make", extraDirectories);
return makePath.isEmpty() ? "make" : makePath.toString();
}
QString AndroidToolChain::ndkToolChainVersion() const
{
return m_ndkToolChainVersion;
}
bool AndroidToolChain::isSecondaryToolChain() const
{
return m_secondaryToolChain;
}
void AndroidToolChain::setSecondaryToolChain(bool b)
{
if (m_secondaryToolChain == b)
return;
m_secondaryToolChain = b;
toolChainUpdated();
}
GccToolChain::DetectedAbisResult AndroidToolChain::detectSupportedAbis() const
{
GccToolChain::DetectedAbisResult supportedAbis = GccToolChain::detectSupportedAbis();
supportedAbis.supportedAbis = {targetAbi()};
return supportedAbis;
}
// --------------------------------------------------------------------------
// ToolChainConfigWidget
// --------------------------------------------------------------------------
AndroidToolChainConfigWidget::AndroidToolChainConfigWidget(AndroidToolChain *tc) :
ToolChainConfigWidget(tc)
{
QLabel *label = new QLabel(AndroidConfigurations::currentConfig().ndkLocation().toUserOutput());
m_mainLayout->addRow(tr("NDK Root:"), label);
}
// --------------------------------------------------------------------------
// ToolChainFactory
// --------------------------------------------------------------------------
AndroidToolChainFactory::AndroidToolChainFactory()
{
setDisplayName(tr("Android GCC"));
}
QSet<Core::Id> Android::Internal::AndroidToolChainFactory::supportedLanguages() const
{
return {ProjectExplorer::Constants::CXX_LANGUAGE_ID};
}
QList<ToolChain *> AndroidToolChainFactory::autoDetect(const QList<ToolChain *> &alreadyKnown)
{
return autodetectToolChainsForNdk(AndroidConfigurations::currentConfig().ndkLocation(), alreadyKnown);
}
bool AndroidToolChainFactory::canRestore(const QVariantMap &data)
{
return typeIdFromMap(data) == Constants::ANDROID_TOOLCHAIN_ID;
}
ToolChain *AndroidToolChainFactory::restore(const QVariantMap &data)
{
auto tc = new AndroidToolChain();
if (tc->fromMap(data))
return tc;
delete tc;
return nullptr;
}
QList<AndroidToolChainFactory::AndroidToolChainInformation> AndroidToolChainFactory::toolchainPathsForNdk(const FileName &ndkPath)
{
QList<AndroidToolChainInformation> result;
if (ndkPath.isEmpty())
return result;
QRegExp versionRegExp(NDKGccVersionRegExp);
FileName path = ndkPath;
QDirIterator it(path.appendPath(QLatin1String("toolchains")).toString(),
QStringList("*"), QDir::Dirs);
while (it.hasNext()) {
const QString &fileName = FileName::fromString(it.next()).fileName();
int idx = versionRegExp.indexIn(fileName);
if (idx == -1)
continue;
for (const Core::Id lang : {ProjectExplorer::Constants::CXX_LANGUAGE_ID,
ProjectExplorer::Constants::C_LANGUAGE_ID}) {
AndroidToolChainInformation ati;
ati.language = lang;
ati.version = fileName.mid(idx + 1);
QString platform = fileName.left(idx);
ati.abi = AndroidConfig::abiForToolChainPrefix(platform);
if (ati.abi.architecture() == Abi::UnknownArchitecture)
continue;
ati.compilerCommand = AndroidConfigurations::currentConfig().gccPath(ati.abi, lang, ati.version);
result.append(ati);
}
}
return result;
}
QList<int> AndroidToolChainFactory::versionNumberFromString(const QString &version)
{
QList<int> result;
int start = 0;
int end = version.length();
while (start <= end) {
int index = version.indexOf(QLatin1Char('.'), start);
if (index == -1)
index = end;
bool ok;
int v = version.midRef(start, index - start).toInt(&ok);
if (!ok) // unparseable, return what we have
return result;
result << v;
start = index + 1;
}
return result;
}
bool AndroidToolChainFactory::versionCompareLess(const QList<int> &a, const QList<int> &b)
{
int aend = a.length();
int bend = b.length();
int end = qMax(aend, bend);
for (int i = 0; i < end; ++i) {
int an = i < aend ? a.at(i) : 0;
int bn = i < bend ? b.at(i) : 0;
if (an < bn)
return true;
if (bn < an)
return false;
}
return false;
}
bool AndroidToolChainFactory::versionCompareLess(QList<AndroidToolChain *> atc,
QList<AndroidToolChain *> btc)
{
if (atc.isEmpty() || btc.isEmpty())
return false;
const QList<int> a = versionNumberFromString(atc.at(0)->ndkToolChainVersion());
const QList<int> b = versionNumberFromString(btc.at(0)->ndkToolChainVersion());
return versionCompareLess(a, b);
}
static AndroidToolChain *findToolChain(Utils::FileName &compilerPath, Core::Id lang,
const QList<ToolChain *> &alreadyKnown)
{
return static_cast<AndroidToolChain *>(
Utils::findOrDefault(alreadyKnown, [compilerPath, lang](ToolChain *tc) {
return tc->typeId() == Constants::ANDROID_TOOLCHAIN_ID
&& tc->language() == lang
&& tc->compilerCommand() == compilerPath;
}));
}
QList<ToolChain *>
AndroidToolChainFactory::autodetectToolChainsForNdk(const FileName &ndkPath,
const QList<ToolChain *> &alreadyKnown)
{
QList<ToolChain *> result;
if (ndkPath.isEmpty())
return result;
QRegExp versionRegExp(NDKGccVersionRegExp);
FileName path = ndkPath;
QDirIterator it(path.appendPath(QLatin1String("toolchains")).toString(),
QStringList("*"), QDir::Dirs);
QHash<Abi, QList<AndroidToolChain *>> newestToolChainForArch;
while (it.hasNext()) {
const QString &fileName = FileName::fromString(it.next()).fileName();
int idx = versionRegExp.indexIn(fileName);
if (idx == -1)
continue;
QString version = fileName.mid(idx + 1);
QString platform = fileName.left(idx);
Abi abi = AndroidConfig::abiForToolChainPrefix(platform);
if (abi.architecture() == Abi::UnknownArchitecture)
continue;
QList<AndroidToolChain *> toolChainBundle;
for (Core::Id lang : {ProjectExplorer::Constants::CXX_LANGUAGE_ID, ProjectExplorer::Constants::C_LANGUAGE_ID}) {
FileName compilerPath = AndroidConfigurations::currentConfig().gccPath(abi, lang, version);
if (!compilerPath.exists())
continue;
AndroidToolChain *tc = findToolChain(compilerPath, lang, alreadyKnown);
if (!tc || tc->originalTargetTriple().isEmpty()) {
tc = new AndroidToolChain(abi, version, lang,
ToolChain::AutoDetection);
tc->resetToolChain(compilerPath);
QTC_ASSERT(!tc->originalTargetTriple().isEmpty(),
delete tc; continue);
}
result.append(tc);
toolChainBundle.append(tc);
}
if (toolChainBundle.isEmpty())
continue;
auto it = newestToolChainForArch.constFind(abi);
if (it == newestToolChainForArch.constEnd())
newestToolChainForArch.insert(abi, toolChainBundle);
else if (versionCompareLess(it.value(), toolChainBundle))
newestToolChainForArch[abi] = toolChainBundle;
}
foreach (ToolChain *tc, result) {
auto atc = static_cast<AndroidToolChain *>(tc);
atc->setSecondaryToolChain(!newestToolChainForArch.value(atc->targetAbi()).contains(atc));
}
return result;
}
QList<int> AndroidToolChainFactory::newestToolChainVersionForArch(const Abi &abi)
{
if (m_newestVersionForAbi.isEmpty()
|| m_ndkLocation != AndroidConfigurations::currentConfig().ndkLocation()) {
QRegExp versionRegExp(NDKGccVersionRegExp);
m_ndkLocation = AndroidConfigurations::currentConfig().ndkLocation();
FileName path = m_ndkLocation;
QDirIterator it(path.appendPath(QLatin1String("toolchains")).toString(),
QStringList("*"), QDir::Dirs);
while (it.hasNext()) {
const QString &fileName = FileName::fromString(it.next()).fileName();
int idx = versionRegExp.indexIn(fileName);
if (idx == -1)
continue;
QList<int> version = versionNumberFromString(fileName.mid(idx + 1));
QString platform = fileName.left(idx);
Abi abi = AndroidConfig::abiForToolChainPrefix(platform);
if (abi.architecture() == Abi::UnknownArchitecture)
continue;
QHash<Abi, QList<int> >::const_iterator it
= m_newestVersionForAbi.constFind(abi);
if (it == m_newestVersionForAbi.constEnd())
m_newestVersionForAbi.insert(abi, version);
else if (versionCompareLess(it.value(), version))
m_newestVersionForAbi[abi] = version;
}
}
return m_newestVersionForAbi.value(abi);
}
} // namespace Internal
} // namespace Android