Android: Auto detect clang toolchains

Remove GCC auto detection
Make Clang toochain default for Android
Remove auto detected toolchains from old NDK

Task-number: QTCREATORBUG-11846
Change-Id: I618e6f8eda4f24f498260b8de778ef543311acd1
Reviewed-by: BogDan Vatra <bogdan@kdab.com>
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
This commit is contained in:
Vikas Pachdha
2018-10-25 18:12:43 +02:00
parent dabe72b7bb
commit de97b8cf3e
6 changed files with 153 additions and 500 deletions

View File

@@ -26,31 +26,19 @@
#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 <QFileInfo>
#include <QLoggingCategory>
#include <QRegExp>
#include <QVBoxLayout>
namespace {
const QLatin1String NDKGccVersionRegExp("-\\d[\\.\\d]+");
Q_LOGGING_CATEGORY(androidTCLog, "qtc.android.toolchainmanagement");
}
namespace Android {
@@ -59,108 +47,51 @@ 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";
static const QHash<QString, Abi> ClangTargets = {
{"arm-linux-androideabi",
Abi(Abi::ArmArchitecture, Abi::LinuxOS, Abi::AndroidLinuxFlavor, Abi::ElfFormat, 32)},
{"i686-linux-android",
Abi(Abi::X86Architecture, Abi::LinuxOS, Abi::AndroidLinuxFlavor, Abi::ElfFormat, 32)},
{"x86_64-linux-android",
Abi(Abi::X86Architecture, Abi::LinuxOS, Abi::AndroidLinuxFlavor, Abi::ElfFormat, 64)},
{"aarch64-linux-android",
Abi(Abi::ArmArchitecture, Abi::LinuxOS, Abi::AndroidLinuxFlavor, Abi::ElfFormat, 64)}};
QHash<Abi, QList<int> > AndroidToolChainFactory::m_newestVersionForAbi;
FileName AndroidToolChainFactory::m_ndkLocation;
static const QList<Core::Id> LanguageIds = {ProjectExplorer::Constants::CXX_LANGUAGE_ID,
ProjectExplorer::Constants::C_LANGUAGE_ID};
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)
static ToolChain *findToolChain(Utils::FileName &compilerPath, Core::Id lang, const QString &target,
CToolChainList &alreadyKnown)
{
setLanguage(l);
setTargetAbi(abi);
setDisplayName(QString::fromLatin1("Android GCC (%1, %2-%3)")
.arg(ToolChainManager::displayNameOfLanguageId(l),
AndroidConfig::displayName(targetAbi()),
ndkToolChainVersion));
ToolChain * tc = Utils::findOrDefault(alreadyKnown, [target, compilerPath, lang](ToolChain *tc) {
return tc->typeId() == Constants::ANDROID_TOOLCHAIN_ID
&& tc->language() == lang
&& tc->targetAbi() == ClangTargets[target]
&& tc->compilerCommand() == compilerPath;
});
return tc;
}
// 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");
return AndroidToolChainFactory::tr("Android Clang");
}
bool AndroidToolChain::isValid() const
{
return GccToolChain::isValid() && targetAbi().isValid() && !m_ndkToolChainVersion.isEmpty()
return ClangToolChain::isValid()
&& typeId() == Constants::ANDROID_TOOLCHAIN_ID
&& targetAbi().isValid()
&& 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);
env.set(QLatin1String("ANDROID_NDK_HOST"),
AndroidConfigurations::currentConfig().toolchainHost());
const Utils::FileName javaHome = AndroidConfigurations::currentConfig().openJDKLocation();
if (!javaHome.isEmpty() && javaHome.toFileInfo().exists()) {
env.set(QLatin1String("JAVA_HOME"), javaHome.toString());
@@ -169,26 +100,16 @@ void AndroidToolChain::addToEnvironment(Environment &env) const
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);
env.set(QLatin1String("ANDROID_HOME"),
AndroidConfigurations::currentConfig().sdkLocation().toString());
env.set(QLatin1String("ANDROID_SDK_ROOT"),
AndroidConfigurations::currentConfig().sdkLocation().toString());
}
FileName AndroidToolChain::suggestedDebugger() const
{
return AndroidConfigurations::currentConfig().gdbPath(targetAbi(), m_ndkToolChainVersion);
// TODO: Make use of LLDB if available.
return AndroidConfigurations::currentConfig().gdbPath();
}
FileName AndroidToolChain::suggestedGdbServer() const
@@ -196,45 +117,10 @@ 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))
if (!ClangToolChain::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();
}
@@ -246,59 +132,16 @@ FileNameList AndroidToolChain::suggestedMkspecList() const
QString AndroidToolChain::makeCommand(const Environment &env) const
{
Q_UNUSED(env);
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();
return makePath.exists() ? makePath.toString() : "make";
}
GccToolChain::DetectedAbisResult AndroidToolChain::detectSupportedAbis() const
{
GccToolChain::DetectedAbisResult supportedAbis = GccToolChain::detectSupportedAbis();
supportedAbis.supportedAbis = {targetAbi()};
return supportedAbis;
return GccToolChain::DetectedAbisResult({targetAbi()}, originalTargetTriple());
}
// --------------------------------------------------------------------------
// ToolChainConfigWidget
// --------------------------------------------------------------------------
AndroidToolChainConfigWidget::AndroidToolChainConfigWidget(AndroidToolChain *tc) :
ToolChainConfigWidget(tc)
{
QLabel *label = new QLabel(AndroidConfigurations::currentConfig().ndkLocation().toUserOutput());
m_mainLayout->addRow(tr("NDK Root:"), label);
}
// --------------------------------------------------------------------------
// ToolChainFactory
@@ -306,7 +149,7 @@ AndroidToolChainConfigWidget::AndroidToolChainConfigWidget(AndroidToolChain *tc)
AndroidToolChainFactory::AndroidToolChainFactory()
{
setDisplayName(tr("Android GCC"));
setDisplayName(tr("Android Clang"));
}
QSet<Core::Id> Android::Internal::AndroidToolChainFactory::supportedLanguages() const
@@ -314,9 +157,9 @@ QSet<Core::Id> Android::Internal::AndroidToolChainFactory::supportedLanguages()
return {ProjectExplorer::Constants::CXX_LANGUAGE_ID};
}
QList<ToolChain *> AndroidToolChainFactory::autoDetect(const QList<ToolChain *> &alreadyKnown)
ToolChainList AndroidToolChainFactory::autoDetect(CToolChainList &alreadyKnown)
{
return autodetectToolChainsForNdk(AndroidConfigurations::currentConfig().ndkLocation(), alreadyKnown);
return autodetectToolChainsForNdk(alreadyKnown);
}
bool AndroidToolChainFactory::canRestore(const QVariantMap &data)
@@ -334,184 +177,67 @@ ToolChain *AndroidToolChainFactory::restore(const QVariantMap &data)
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)
ToolChainList AndroidToolChainFactory::autodetectToolChainsForNdk(CToolChainList &alreadyKnown)
{
QList<ToolChain *> result;
if (ndkPath.isEmpty())
FileName clangPath = AndroidConfigurations::currentConfig().clangPath();
if (!clangPath.exists()) {
qCDebug(androidTCLog) << "Clang toolchains detection fails. Can not find Clang"<< clangPath;
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));
qCDebug(androidTCLog) << "Detecting toolchains from Android NDK:"
<< AndroidConfigurations::currentConfig().ndkLocation();
for (const Core::Id &lang : LanguageIds) {
FileName compilerCommand = clangPath;
if (lang == ProjectExplorer::Constants::CXX_LANGUAGE_ID)
compilerCommand.appendString("++");
if (!compilerCommand.exists()) {
qCDebug(androidTCLog) << "Skipping Clang toolchain. Can not find compiler"
<< compilerCommand;
continue;
}
auto targetItr = ClangTargets.constBegin();
while (targetItr != ClangTargets.constEnd()) {
const Abi &abi = targetItr.value();
ToolChain *tc = findToolChain(compilerCommand, lang, targetItr.key(), alreadyKnown);
if (tc) {
qCDebug(androidTCLog) << "Tool chain already known" << abi.toString() << lang;
} else {
qCDebug(androidTCLog) << "New Clang toolchain found" << abi.toString() << lang;
auto atc = new AndroidToolChain(targetItr.key(), lang);
atc->resetToolChain(compilerCommand);
tc = atc;
}
result << tc;
++targetItr;
}
}
return result;
}
QList<int> AndroidToolChainFactory::newestToolChainVersionForArch(const Abi &abi)
// for fromMap
AndroidToolChain::AndroidToolChain()
: ClangToolChain(Constants::ANDROID_TOOLCHAIN_ID, ToolChain::ManualDetection)
{
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);
}
AndroidToolChain::AndroidToolChain(const QString& target, Core::Id languageId)
: ClangToolChain(Constants::ANDROID_TOOLCHAIN_ID, ToolChain::AutoDetection)
{
setOriginalTargetTriple(target);
setLanguage(languageId);
setTargetAbi(ClangTargets[target]);
setPlatformCodeGenFlags({"-target", target});
setPlatformLinkerFlags({"-target", target});
setDisplayName(QString::fromLatin1("Android Clang (%1, %2)")
.arg(ToolChainManager::displayNameOfLanguageId(languageId),
AndroidConfig::displayName(targetAbi())));
}
} // namespace Internal