diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 90d6f875399..5879c0e0073 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -100,6 +100,7 @@ namespace { const QLatin1String SettingsGroup("AndroidConfigurations"); const QLatin1String SDKLocationKey("SDKLocation"); + const QLatin1String CustomNdkLocationsKey("CustomNdkLocations"); const QLatin1String SdkFullyConfiguredKey("AllEssentialsInstalled"); const QLatin1String SDKManagerToolArgsKey("SDKManagerToolArgs"); const QLatin1String OpenJDKLocationKey("OpenJDKLocation"); @@ -235,6 +236,7 @@ void AndroidConfig::load(const QSettings &settings) // user settings m_partitionSize = settings.value(PartitionSizeKey, 1024).toInt(); m_sdkLocation = FilePath::fromString(settings.value(SDKLocationKey).toString()); + m_customNdkList = settings.value(CustomNdkLocationsKey).toStringList(); m_sdkManagerToolArgs = settings.value(SDKManagerToolArgsKey).toStringList(); m_openJDKLocation = FilePath::fromString(settings.value(OpenJDKLocationKey).toString()); m_keystoreLocation = FilePath::fromString(settings.value(KeystoreLocationKey).toString()); @@ -246,12 +248,14 @@ void AndroidConfig::load(const QSettings &settings) && settings.value(changeTimeStamp).toInt() != QFileInfo(sdkSettingsFileName()).lastModified().toMSecsSinceEpoch() / 1000) { // persisten settings m_sdkLocation = FilePath::fromString(reader.restoreValue(SDKLocationKey, m_sdkLocation.toString()).toString()); + m_customNdkList = reader.restoreValue(CustomNdkLocationsKey).toStringList(); m_sdkManagerToolArgs = reader.restoreValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs).toStringList(); m_openJDKLocation = FilePath::fromString(reader.restoreValue(OpenJDKLocationKey, m_openJDKLocation.toString()).toString()); m_automaticKitCreation = reader.restoreValue(AutomaticKitCreationKey, m_automaticKitCreation).toBool(); m_sdkFullyConfigured = reader.restoreValue(SdkFullyConfiguredKey, m_sdkFullyConfigured).toBool(); // persistent settings } + m_customNdkList.removeAll(""); parseDependenciesJson(); } @@ -263,6 +267,7 @@ void AndroidConfig::save(QSettings &settings) const // user settings settings.setValue(SDKLocationKey, m_sdkLocation.toString()); + settings.setValue(CustomNdkLocationsKey, m_customNdkList); settings.setValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs); settings.setValue(OpenJDKLocationKey, m_openJDKLocation.toString()); settings.setValue(KeystoreLocationKey, m_keystoreLocation.toString()); @@ -353,6 +358,22 @@ QVector AndroidConfig::availableNdkPlatforms(const BaseQtVersion *qtVersion return availableNdkPlatforms; } +QStringList AndroidConfig::getCustomNdkList() const +{ + return m_customNdkList; +} + +void AndroidConfig::addCustomNdk(const QString &customNdk) +{ + if (!m_customNdkList.contains(customNdk)) + m_customNdkList.append(customNdk); +} + +void AndroidConfig::removeCustomNdk(const QString &customNdk) +{ + m_customNdkList.removeAll(customNdk); +} + QStringList AndroidConfig::apiLevelNamesFor(const SdkPlatformList &platforms) { return Utils::transform(platforms, AndroidConfig::apiLevelNameFor); @@ -415,9 +436,9 @@ FilePath AndroidConfig::aaptToolPath() const return aaptToolPath.pathAppended(toolPath); } -FilePath AndroidConfig::toolchainPath(const BaseQtVersion *qtVersion) const +FilePath AndroidConfig::toolchainPathFromNdk(const Utils::FilePath &ndkLocation) const { - const FilePath toolchainPath = ndkLocation(qtVersion).pathAppended("toolchains/llvm/prebuilt/"); + const FilePath toolchainPath = ndkLocation.pathAppended("toolchains/llvm/prebuilt/"); // detect toolchain host QStringList hostPatterns; @@ -443,23 +464,41 @@ FilePath AndroidConfig::toolchainPath(const BaseQtVersion *qtVersion) const return {}; } -FilePath AndroidConfig::clangPath(const BaseQtVersion *qtVersion) const +FilePath AndroidConfig::toolchainPath(const BaseQtVersion *qtVersion) const { - const FilePath path = toolchainPath(qtVersion); + return toolchainPathFromNdk(ndkLocation(qtVersion)); +} + +FilePath AndroidConfig::clangPathFromNdk(const Utils::FilePath &ndkLocation) const +{ + const FilePath path = toolchainPathFromNdk(ndkLocation); if (path.isEmpty()) return {}; return path.pathAppended(HostOsInfo::withExecutableSuffix("bin/clang")); } +FilePath AndroidConfig::clangPath(const BaseQtVersion *qtVersion) const +{ + return clangPathFromNdk(ndkLocation(qtVersion)); +} + FilePath AndroidConfig::gdbPath(const ProjectExplorer::Abi &abi, const BaseQtVersion *qtVersion) const { - const FilePath path = ndkLocation(qtVersion).pathAppended( - QString("prebuilt/%1/bin/gdb%2").arg(toolchainHost(qtVersion), QTC_HOST_EXE_SUFFIX)); + return gdbPathFromNdk(abi, ndkLocation(qtVersion)); +} + +FilePath AndroidConfig::gdbPathFromNdk(const Abi &abi, const FilePath &ndkLocation) const +{ + const FilePath path = ndkLocation.pathAppended( + QString("prebuilt/%1/bin/gdb%2").arg(toolchainHostFromNdk(ndkLocation), QTC_HOST_EXE_SUFFIX)); if (path.exists()) return path; // fallback for old NDKs (e.g. 10e) - return ndkLocation(qtVersion).pathAppended(QString("toolchains/%1-4.9/prebuilt/%2/bin/%3-gdb%4") - .arg(toolchainPrefix(abi), toolchainHost(qtVersion), toolsPrefix(abi), QTC_HOST_EXE_SUFFIX)); + return ndkLocation.pathAppended(QString("toolchains/%1-4.9/prebuilt/%2/bin/%3-gdb%4") + .arg(toolchainPrefix(abi), + toolchainHostFromNdk(ndkLocation), + toolsPrefix(abi), + QTC_HOST_EXE_SUFFIX)); } FilePath AndroidConfig::makePath(const BaseQtVersion *qtVersion) const @@ -733,6 +772,16 @@ bool AndroidConfig::useNativeUiTools() const return !version.isNull() && version <= QVersionNumber(25, 3 ,0); } +bool AndroidConfig::isValidNdk(const QString &ndkLocation) const +{ + auto ndkPath = Utils::FilePath::fromUserInput(ndkLocation); + const Utils::FilePath ndkPlatformsDir = ndkPath.pathAppended("platforms"); + + return ndkPath.exists() && ndkPath.pathAppended("toolchains").exists() + && ndkPlatformsDir.exists() && !ndkPlatformsDir.toString().contains(' ') + && !ndkVersion(ndkPath).isNull(); +} + QString AndroidConfig::bestNdkPlatformMatch(int target, const BaseQtVersion *qtVersion) const { target = std::max(AndroidManager::apiLevelRange().first, target); @@ -1076,10 +1125,53 @@ void AndroidConfigurations::registerNewToolChains() const QList existingAndroidToolChains = ToolChainManager::toolChains(Utils::equal(&ToolChain::typeId, Core::Id(Constants::ANDROID_TOOLCHAIN_TYPEID))); - const QList newToolchains - = AndroidToolChainFactory::autodetectToolChainsForNdk(existingAndroidToolChains); + QList newToolchains = AndroidToolChainFactory::autodetectToolChains( + existingAndroidToolChains); + foreach (ToolChain *tc, newToolchains) ToolChainManager::registerToolChain(tc); + + registerCustomToolChainsAndDebuggers(); +} + +void AndroidConfigurations::registerCustomToolChainsAndDebuggers() +{ + const QList existingAndroidToolChains = ToolChainManager::toolChains( + Utils::equal(&ToolChain::typeId, Core::Id(Constants::ANDROID_TOOLCHAIN_TYPEID))); + QList customNdks = Utils::transform(currentConfig().getCustomNdkList(), + FilePath::fromString); + QList customToolchains + = AndroidToolChainFactory::autodetectToolChainsFromNdks(existingAndroidToolChains, + customNdks, + true); + for (ToolChain *tc : customToolchains) { + ToolChainManager::registerToolChain(tc); + + const FilePath ndk = static_cast(tc)->ndkLocation(); + const FilePath command = AndroidConfigurations::currentConfig() + .gdbPathFromNdk(tc->targetAbi(), ndk); + + const Debugger::DebuggerItem *existing = Debugger::DebuggerItemManager::findByCommand( + command); + QString abiStr + = static_cast(tc)->platformLinkerFlags().at(1).split('-').first(); + Abi abi = Abi::abiFromTargetTriplet(abiStr); + if (existing && existing->abis().contains(abi)) + continue; + + Debugger::DebuggerItem debugger; + debugger.setCommand(command); + debugger.setEngineType(Debugger::GdbEngineType); + debugger.setUnexpandedDisplayName( + AndroidConfigurations::tr("Custom Android Debugger (%1, NDK %2)") + .arg(abiStr, + AndroidConfigurations::currentConfig().ndkVersion(ndk).toString())); + debugger.setAutoDetected(true); + debugger.setAbi(abi); + debugger.reinitializeFromFile(); + + Debugger::DebuggerItemManager::registerDebugger(debugger); + } } void AndroidConfigurations::removeOldToolChains() @@ -1104,6 +1196,8 @@ void AndroidConfigurations::removeUnusedDebuggers() uniqueNdks.append(ndkLocation); } + uniqueNdks.append(Utils::transform(currentConfig().getCustomNdkList(), FilePath::fromString)); + const QList allDebuggers = Debugger::DebuggerItemManager::debuggers(); for (const Debugger::DebuggerItem &debugger : allDebuggers) { if (!debugger.displayName().contains("Android")) diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index b2f9ec02015..f793ed78c3f 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -160,9 +160,12 @@ public: Utils::FilePath aaptToolPath() const; Utils::FilePath toolchainPath(const QtSupport::BaseQtVersion *qtVersion) const; + Utils::FilePath toolchainPathFromNdk(const Utils::FilePath &ndkLocation) const; Utils::FilePath clangPath(const QtSupport::BaseQtVersion *qtVersion) const; + Utils::FilePath clangPathFromNdk(const Utils::FilePath &ndkLocation) const; Utils::FilePath gdbPath(const ProjectExplorer::Abi &abi, const QtSupport::BaseQtVersion *qtVersion) const; + Utils::FilePath gdbPathFromNdk(const ProjectExplorer::Abi &abi, const Utils::FilePath &ndkLocation) const; Utils::FilePath makePath(const QtSupport::BaseQtVersion *qtVersion) const; Utils::FilePath makePathFromNdk(const Utils::FilePath &ndkLocation) const; @@ -188,6 +191,11 @@ public: bool sdkFullyConfigured() const { return m_sdkFullyConfigured; }; void setSdkFullyConfigured(bool allEssentialsInstalled) { m_sdkFullyConfigured = allEssentialsInstalled; }; + bool isValidNdk(const QString &ndkLocation) const; + QStringList getCustomNdkList() const; + void addCustomNdk(const QString &customNdk); + void removeCustomNdk(const QString &customNdk); + private: static QString getDeviceProperty(const Utils::FilePath &adbToolPath, const QString &device, const QString &property); @@ -216,6 +224,7 @@ private: QStringList m_commonEssentialPkgs; SdkForQtVersions m_defaultSdkDepends; QList m_specificQtVersions; + QStringList m_customNdkList; bool m_sdkFullyConfigured = false; //caches @@ -237,6 +246,7 @@ public: static QString defaultDevice(ProjectExplorer::Project *project, const QString &abi); // serial number or avd name static void clearDefaultDevices(ProjectExplorer::Project *project); static void registerNewToolChains(); + static void registerCustomToolChainsAndDebuggers(); static void removeUnusedDebuggers(); static void removeOldToolChains(); static void updateAutomaticKitList(); diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index 3eeb0150160..42511b893c3 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -133,6 +133,7 @@ private: bool sdkToolsOk() const; Utils::FilePath getDefaultSdkPath(); void showEvent(QShowEvent *event) override; + void addCustomNdkItem(); Ui_AndroidSettingsWidget *m_ui; AndroidSdkManagerWidget *m_sdkManagerWidget = nullptr; @@ -352,6 +353,22 @@ void AndroidSettingsWidget::updateNdkList() m_ui->ndkListComboBox->clear(); for (const Ndk *ndk : m_sdkManager->installedNdkPackages()) m_ui->ndkListComboBox->addItem(ndk->installedLocation().toString()); + + for (const QString &ndk : m_androidConfig.getCustomNdkList()) { + if (m_androidConfig.isValidNdk(ndk)) + m_ui->ndkListComboBox->addItem(ndk); + else + m_androidConfig.removeCustomNdk(ndk); + } +} + +void AndroidSettingsWidget::addCustomNdkItem() +{ + const QString ndkPath = QDir::toNativeSeparators(m_ui->customNdkPathChooser->rawPath()); + m_androidConfig.addCustomNdk(ndkPath); + if (m_ui->ndkListComboBox->findData(ndkPath) == -1) + m_ui->ndkListComboBox->addItem(ndkPath); + m_ui->ndkListComboBox->setCurrentText(ndkPath); } AndroidSettingsWidget::AndroidSettingsWidget() @@ -435,8 +452,23 @@ AndroidSettingsWidget::AndroidSettingsWidget() connect(m_ui->SDKLocationPathChooser, &Utils::PathChooser::rawPathChanged, this, &AndroidSettingsWidget::onSdkPathChanged); - connect(m_ui->ndkListComboBox, QOverload::of(&QComboBox::currentIndexChanged), - [this](const QString) { validateNdk(); }); + connect(m_ui->ndkListComboBox, + QOverload::of(&QComboBox::currentIndexChanged), + [this](const QString &ndk) { + validateNdk(); + m_ui->removeCustomNdkButton->setEnabled(m_androidConfig.getCustomNdkList().contains(ndk)); + }); + connect(m_ui->customNdkPathChooser, &Utils::PathChooser::rawPathChanged, this, [this]() { + const QString ndkPath = m_ui->customNdkPathChooser->rawPath(); + m_ui->addCustomNdkButton->setEnabled(m_androidConfig.isValidNdk(ndkPath)); + }); + connect(m_ui->addCustomNdkButton, &QPushButton::clicked, this, + &AndroidSettingsWidget::addCustomNdkItem); + connect(m_ui->removeCustomNdkButton, &QPushButton::clicked, this, [this]() { + m_androidConfig.removeCustomNdk(m_ui->ndkListComboBox->currentText()); + m_ui->ndkListComboBox->removeItem(m_ui->ndkListComboBox->currentIndex()); + }); + connect(&m_virtualDevicesWatcher, &QFutureWatcherBase::finished, this, &AndroidSettingsWidget::updateAvds); connect(m_ui->AVDRefreshPushButton, &QAbstractButton::clicked, diff --git a/src/plugins/android/androidsettingswidget.ui b/src/plugins/android/androidsettingswidget.ui index 5d149dddcf5..6d8272b7dfa 100644 --- a/src/plugins/android/androidsettingswidget.ui +++ b/src/plugins/android/androidsettingswidget.ui @@ -93,7 +93,7 @@ - + 0 0 @@ -106,21 +106,24 @@ - - - - Download Android SDK - - - - + - Android NDK list: + Add custom NDK: - + + + + + + + + + + + Automatically download Android SDK Tools to selected location. @@ -130,29 +133,74 @@ - + - - + + - + Android NDK list: - - + + + + Download Android SDK + + + + + + + + 20 + 0 + + + + + + + + false + - + 0 0 + + Add the selected custom NDK. The toolchains and debuggers will be created automatically. + + + Add + - + + + + + false + + + + 0 + 0 + + + + Remove the selected custom NDK. + + + Remove + + + @@ -351,6 +399,10 @@ QWidget
utils/pathchooser.h
1 + + editingFinished() + browsingFinished() + Utils::DetailsWidget diff --git a/src/plugins/android/androidtoolchain.cpp b/src/plugins/android/androidtoolchain.cpp index 4f879d92491..2c45eba1e59 100644 --- a/src/plugins/android/androidtoolchain.cpp +++ b/src/plugins/android/androidtoolchain.cpp @@ -171,7 +171,7 @@ AndroidToolChainFactory::AndroidToolChainFactory() ToolChainList AndroidToolChainFactory::autoDetect(const ToolChainList &alreadyKnown) { - return autodetectToolChainsForNdk(alreadyKnown); + return autodetectToolChains(alreadyKnown); } static FilePath clangPlusPlusPath(const FilePath &clangPath) @@ -181,7 +181,7 @@ static FilePath clangPlusPlusPath(const FilePath &clangPath) QFileInfo(clangPath.toString()).baseName() + "++")); } -static QList androidQtVersionsWithUniqueNdk() +static QList uniqueNdksForCurrentQtVersions() { AndroidConfig config = AndroidConfigurations::currentConfig(); @@ -190,36 +190,40 @@ static QList androidQtVersionsWithUniqueNdk() return v->targetDeviceTypes().contains(Android::Constants::ANDROID_DEVICE_TYPE); }); - auto shouldRemove = [config](const QtSupport::BaseQtVersion *first, - const QtSupport::BaseQtVersion *second) { - return config.ndkLocation(first) == config.ndkLocation(second); - }; + QList uniqueNdks; + for (const QtSupport::BaseQtVersion *version : androidQtVersions) { + FilePath ndk = config.ndkLocation(version); + if (!uniqueNdks.contains(ndk)) + uniqueNdks.append(ndk); + } - QList::iterator it = std::unique(androidQtVersions.begin(), - androidQtVersions.end(), - shouldRemove); - androidQtVersions.erase(it, androidQtVersions.end()); - - return androidQtVersions; + return uniqueNdks; } -ToolChainList AndroidToolChainFactory::autodetectToolChainsForNdk(const ToolChainList &alreadyKnown) +ToolChainList AndroidToolChainFactory::autodetectToolChains(const ToolChainList &alreadyKnown) +{ + const QList uniqueNdks = uniqueNdksForCurrentQtVersions(); + return autodetectToolChainsFromNdks(alreadyKnown, uniqueNdks); +} + +ToolChainList AndroidToolChainFactory::autodetectToolChainsFromNdks( + const ToolChainList &alreadyKnown, + const QList &ndkLocations, + const bool isCustom) { QList result; - const QList androidQtVersions = androidQtVersionsWithUniqueNdk(); const AndroidConfig config = AndroidConfigurations::currentConfig(); - for (const QtSupport::BaseQtVersion *qtVersion : androidQtVersions) { - FilePath clangPath = config.clangPath(qtVersion); + for (const Utils::FilePath &ndkLocation : ndkLocations) { + qCDebug(androidTCLog) << "Detecting toolchains from Android NDK:" << ndkLocation; + + FilePath clangPath = config.clangPathFromNdk(ndkLocation); if (!clangPath.exists()) { qCDebug(androidTCLog) << "Clang toolchains detection fails. Can not find Clang" << clangPath; - return result; + continue; } - qCDebug(androidTCLog) << "Detecting toolchains from Android NDK:" - << config.ndkLocation(qtVersion); - for (const Core::Id &lang : LanguageIds) { FilePath compilerCommand = clangPath; if (lang == ProjectExplorer::Constants::CXX_LANGUAGE_ID) @@ -237,10 +241,11 @@ ToolChainList AndroidToolChainFactory::autodetectToolChainsForNdk(const ToolChai const QString target = targetItr.key(); ToolChain *tc = findToolChain(compilerCommand, lang, target, alreadyKnown); - const QString displayName(QString("Android Clang (%1, %2, NDK %3)") + QLatin1String customStr = isCustom ? QLatin1String("Custom ") : QLatin1String(); + const QString displayName(customStr + QString("Android Clang (%1, %2, NDK %3)") .arg(ToolChainManager::displayNameOfLanguageId(lang), AndroidConfig::displayName(abi), - config.ndkVersion(qtVersion).toString())); + config.ndkVersion(ndkLocation).toString())); if (tc) { qCDebug(androidTCLog) << "Tool chain already known" << abi.toString() << lang; // make sure to update the toolchain with current name format @@ -249,7 +254,7 @@ ToolChainList AndroidToolChainFactory::autodetectToolChainsForNdk(const ToolChai } else { qCDebug(androidTCLog) << "New Clang toolchain found" << abi.toString() << lang; auto atc = new AndroidToolChain(); - atc->setNdkLocation(config.ndkLocation(qtVersion)); + atc->setNdkLocation(ndkLocation); atc->setOriginalTargetTriple(target); atc->setLanguage(lang); atc->setTargetAbi(ClangTargets[target]); diff --git a/src/plugins/android/androidtoolchain.h b/src/plugins/android/androidtoolchain.h index 14ef9231be1..4002cef75fa 100644 --- a/src/plugins/android/androidtoolchain.h +++ b/src/plugins/android/androidtoolchain.h @@ -78,7 +78,10 @@ public: QString version; }; - static ToolChainList autodetectToolChainsForNdk(const ToolChainList &alreadyKnown); + static ToolChainList autodetectToolChains(const ToolChainList &alreadyKnown); + static ToolChainList autodetectToolChainsFromNdks(const ToolChainList &alreadyKnown, + const QList &ndkLocations, + const bool isCustom = false); }; } // namespace Internal