// Copyright (C) 2022 The Qt Company Ltd. // Copyright (C) 2016 BogDan Vatra // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "androidconfigurations.h" #include "androidconstants.h" #include "androiddevice.h" #include "androidmanager.h" #include "androidqtversion.h" #include "androidtoolchain.h" #include "androidtr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_TESTS # include #endif // WITH_TESTS using namespace ProjectExplorer; using namespace QtSupport; using namespace Utils; namespace { static Q_LOGGING_CATEGORY(avdConfigLog, "qtc.android.androidconfig", QtWarningMsg) } namespace Android { using namespace Internal; #ifdef Q_OS_WIN32 #define ANDROID_BAT_SUFFIX ".bat" #else #define ANDROID_BAT_SUFFIX "" #endif const char JsonFilePath[] = "android/sdk_definitions.json"; const char SdkToolsUrlKey[] = "sdk_tools_url"; const char CommonKey[] = "common"; const char SdkEssentialPkgsKey[] = "sdk_essential_packages"; const char VersionsKey[] = "versions"; const char NdksSubDir[] = "ndk/"; const char SpecificQtVersionsKey[] = "specific_qt_versions"; const char DefaultVersionKey[] = "default"; const char LinuxOsKey[] = "linux"; const char WindowsOsKey[] = "windows"; const char macOsKey[] = "mac"; const char SettingsGroup[] = "AndroidConfigurations"; const char SDKLocationKey[] = "SDKLocation"; const char CustomNdkLocationsKey[] = "CustomNdkLocations"; const char DefaultNdkLocationKey[] = "DefaultNdkLocation"; const char SdkFullyConfiguredKey[] = "AllEssentialsInstalled"; const char SDKManagerToolArgsKey[] = "SDKManagerToolArgs"; const char OpenJDKLocationKey[] = "OpenJDKLocation"; const char OpenSslPriLocationKey[] = "OpenSSLPriLocation"; const char AutomaticKitCreationKey[] = "AutomatiKitCreation"; const char EmulatorArgsKey[] = "EmulatorArgs"; const QLatin1String ArmToolchainPrefix("arm-linux-androideabi"); const QLatin1String X86ToolchainPrefix("x86"); const QLatin1String AArch64ToolchainPrefix("aarch64-linux-android"); const QLatin1String X86_64ToolchainPrefix("x86_64"); const QLatin1String ArmToolsPrefix ("arm-linux-androideabi"); const QLatin1String X86ToolsPrefix("i686-linux-android"); const QLatin1String AArch64ToolsPrefix("aarch64-linux-android"); const QLatin1String X86_64ToolsPrefix("x86_64-linux-android"); const QLatin1String ArmToolsDisplayName("arm"); const QLatin1String X86ToolsDisplayName("i686"); const QLatin1String AArch64ToolsDisplayName("aarch64"); const QLatin1String X86_64ToolsDisplayName("x86_64"); const QLatin1String Unknown("unknown"); const QLatin1String keytoolName("keytool"); const Key changeTimeStamp("ChangeTimeStamp"); const char sdkToolsVersionKey[] = "Pkg.Revision"; const char ndkRevisionKey[] = "Pkg.Revision"; static QString sdkSettingsFileName() { return Core::ICore::installerResourcePath("android.xml").toString(); } static QString ndkPackageMarker() { return QLatin1String(Constants::ndkPackageName) + ";"; } static QString platformsPackageMarker() { return QLatin1String(Constants::platformsPackageName) + ";"; } static QString buildToolsPackageMarker() { return QLatin1String(Constants::buildToolsPackageName) + ";"; } static QString getDeviceProperty(const QString &device, const QString &property) { // workaround for '????????????' serial numbers Process adbProc; adbProc.setCommand({AndroidConfig::adbToolPath(), {AndroidDeviceInfo::adbSelector(device), "shell", "getprop", property}}); adbProc.runBlocking(); if (adbProc.result() == ProcessResult::FinishedWithSuccess) return adbProc.allOutput(); return {}; } static QLatin1String toolsPrefix(const Abi &abi) { switch (abi.architecture()) { case Abi::ArmArchitecture: if (abi.wordWidth() == 64) return AArch64ToolsPrefix; return ArmToolsPrefix; case Abi::X86Architecture: if (abi.wordWidth() == 64) return X86_64ToolsPrefix; return X86ToolsPrefix; default: return Unknown; } } static QLatin1String toolchainPrefix(const Abi &abi) { switch (abi.architecture()) { case Abi::ArmArchitecture: if (abi.wordWidth() == 64) return AArch64ToolchainPrefix; return ArmToolchainPrefix; case Abi::X86Architecture: if (abi.wordWidth() == 64) return X86_64ToolchainPrefix; return X86ToolchainPrefix; default: return Unknown; } } static FilePath gdbPathFromNdk(const Abi &abi, const FilePath &ndkLocation) { const FilePath path = ndkLocation.pathAppended( QString("prebuilt/%1/bin/gdb%2").arg(AndroidConfig::toolchainHostFromNdk(ndkLocation), QString(QTC_HOST_EXE_SUFFIX))); if (path.exists()) return path; // fallback for old NDKs (e.g. 10e) return ndkLocation.pathAppended(QString("toolchains/%1-4.9/prebuilt/%2/bin/%3-gdb%4") .arg(toolchainPrefix(abi), AndroidConfig::toolchainHostFromNdk(ndkLocation), toolsPrefix(abi), QString(QTC_HOST_EXE_SUFFIX))); } static FilePath lldbPathFromNdk(const FilePath &ndkLocation) { const FilePath path = ndkLocation.pathAppended( QString("toolchains/llvm/prebuilt/%1/bin/lldb%2") .arg(AndroidConfig::toolchainHostFromNdk(ndkLocation), QString(QTC_HOST_EXE_SUFFIX))); return path.exists() ? path : FilePath(); } namespace AndroidConfig { struct SdkForQtVersions { QList versions; QStringList essentialPackages; bool containsVersion(const QVersionNumber &qtVersion) const { return versions.contains(qtVersion) || versions.contains(QVersionNumber(qtVersion.majorVersion(), qtVersion.minorVersion())); } }; struct AndroidConfigData { void load(const QtcSettings &settings); void save(QtcSettings &settings) const; void parseDependenciesJson(); FilePath m_sdkLocation; FilePath m_temporarySdkToolsPath; QStringList m_sdkManagerToolArgs; FilePath m_openJDKLocation; FilePath m_keystoreLocation; FilePath m_openSslLocation; QString m_emulatorArgs; bool m_automaticKitCreation = true; QUrl m_sdkToolsUrl; QByteArray m_sdkToolsSha256; QStringList m_commonEssentialPkgs; SdkForQtVersions m_defaultSdkDepends; QList m_specificQtVersions; QStringList m_customNdkList; FilePath m_defaultNdk; bool m_sdkFullyConfigured = false; QHash m_serialNumberToDeviceName; // cache }; static AndroidConfigData &config() { static AndroidConfigData theAndroidConfig; return theAndroidConfig; } static FilePath ndkSubPath(const SdkForQtVersions &packages) { const QString ndkPrefix = ndkPackageMarker(); for (const QString &package : packages.essentialPackages) if (package.startsWith(ndkPrefix)) return FilePath::fromString(NdksSubDir) / package.sliced(ndkPrefix.length()); return {}; } static FilePath ndkSubPathFromQtVersion(const QtVersion &version) { if (auto androidQtVersion = dynamic_cast(&version)) { bool ok; const AndroidQtVersion::BuiltWith bw = androidQtVersion->builtWith(&ok); if (ok) return FilePath::fromString(NdksSubDir) / bw.ndkVersion.toString(); } for (const SdkForQtVersions &item : config().m_specificQtVersions) { if (item.containsVersion(version.qtVersion())) return ndkSubPath(item); } return ndkSubPath(config().m_defaultSdkDepends); } ////////////////////////////////// // AndroidConfig ////////////////////////////////// QString getAvdName(const QString &serialnumber) { const int index = serialnumber.indexOf(QLatin1String("-")); if (index == -1) return {}; bool ok; const int port = serialnumber.mid(index + 1).toInt(&ok); if (!ok) return {}; QTcpSocket tcpSocket; tcpSocket.connectToHost(QHostAddress(QHostAddress::LocalHost), port); if (!tcpSocket.waitForConnected(100)) // Don't wait more than 100ms for a local connection return {}; tcpSocket.write("avd name\nexit\n"); tcpSocket.waitForDisconnected(500); const QByteArrayList response = tcpSocket.readAll().split('\n'); // The input "avd name" might not be echoed as-is, but contain ASCII control sequences. for (int i = response.size() - 1; i > 1; --i) { if (response.at(i).startsWith("OK")) return QString::fromLatin1(response.at(i - 1)).trimmed(); } return {}; } QLatin1String displayName(const Abi &abi) { switch (abi.architecture()) { case Abi::ArmArchitecture: if (abi.wordWidth() == 64) return AArch64ToolsDisplayName; return ArmToolsDisplayName; case Abi::X86Architecture: if (abi.wordWidth() == 64) return X86_64ToolsDisplayName; return X86ToolsDisplayName; default: return Unknown; } } void AndroidConfigData::load(const QtcSettings &settings) { // user settings QVariant emulatorArgs = settings.value(EmulatorArgsKey, QString("-netdelay none -netspeed full")); if (emulatorArgs.typeId() == QMetaType::QStringList) // Changed in 8.0 from QStringList to QString. emulatorArgs = ProcessArgs::joinArgs(emulatorArgs.toStringList()); m_emulatorArgs = emulatorArgs.toString(); m_sdkLocation = FilePath::fromUserInput(settings.value(SDKLocationKey).toString()).cleanPath(); m_customNdkList = settings.value(CustomNdkLocationsKey).toStringList(); m_defaultNdk = FilePath::fromUserInput(settings.value(DefaultNdkLocationKey).toString()).cleanPath(); m_sdkManagerToolArgs = settings.value(SDKManagerToolArgsKey).toStringList(); m_openJDKLocation = FilePath::fromString(settings.value(OpenJDKLocationKey).toString()); m_openSslLocation = FilePath::fromString(settings.value(OpenSslPriLocationKey).toString()); m_automaticKitCreation = settings.value(AutomaticKitCreationKey, true).toBool(); m_sdkFullyConfigured = settings.value(SdkFullyConfiguredKey, false).toBool(); PersistentSettingsReader reader; if (reader.load(FilePath::fromString(sdkSettingsFileName())) && settings.value(changeTimeStamp).toInt() != QFileInfo(sdkSettingsFileName()).lastModified().toMSecsSinceEpoch() / 1000) { // persisten settings m_sdkLocation = FilePath::fromUserInput(reader.restoreValue(SDKLocationKey, m_sdkLocation.toString()).toString()).cleanPath(); 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_openSslLocation = FilePath::fromString(reader.restoreValue(OpenSslPriLocationKey, m_openSslLocation.toString()).toString()); m_automaticKitCreation = reader.restoreValue(AutomaticKitCreationKey, m_automaticKitCreation).toBool(); m_sdkFullyConfigured = reader.restoreValue(SdkFullyConfiguredKey, m_sdkFullyConfigured).toBool(); // persistent settings } m_customNdkList.removeAll(""); if (!m_defaultNdk.isEmpty() && AndroidConfig::ndkVersion(m_defaultNdk).isNull()) { if (avdConfigLog().isDebugEnabled()) qCDebug(avdConfigLog).noquote() << "Clearing invalid default NDK setting:" << m_defaultNdk.toUserOutput(); m_defaultNdk.clear(); } parseDependenciesJson(); } void AndroidConfigData::save(QtcSettings &settings) const { QFileInfo fileInfo(sdkSettingsFileName()); if (fileInfo.exists()) settings.setValue(changeTimeStamp, fileInfo.lastModified().toMSecsSinceEpoch() / 1000); // user settings settings.setValue(SDKLocationKey, m_sdkLocation.toString()); settings.setValue(CustomNdkLocationsKey, m_customNdkList); settings.setValue(DefaultNdkLocationKey, m_defaultNdk.toString()); settings.setValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs); settings.setValue(OpenJDKLocationKey, m_openJDKLocation.toString()); settings.setValue(OpenSslPriLocationKey, m_openSslLocation.toString()); settings.setValue(EmulatorArgsKey, m_emulatorArgs); settings.setValue(AutomaticKitCreationKey, m_automaticKitCreation); settings.setValue(SdkFullyConfiguredKey, m_sdkFullyConfigured); } void AndroidConfigData::parseDependenciesJson() { const FilePath sdkConfigUserFile = Core::ICore::userResourcePath(JsonFilePath); const FilePath sdkConfigFile = Core::ICore::resourcePath(JsonFilePath); if (!sdkConfigUserFile.exists()) { sdkConfigUserFile.absolutePath().ensureWritableDir(); sdkConfigFile.copyFile(sdkConfigUserFile); } if (sdkConfigFile.lastModified() > sdkConfigUserFile.lastModified()) { const FilePath oldUserFile = sdkConfigUserFile.stringAppended(".old"); oldUserFile.removeFile(); sdkConfigUserFile.renameFile(oldUserFile); sdkConfigFile.copyFile(sdkConfigUserFile); } QFile jsonFile(sdkConfigUserFile.toString()); if (!jsonFile.open(QIODevice::ReadOnly)) { qCDebug(avdConfigLog, "Couldn't open JSON config file %s.", qPrintable(jsonFile.fileName())); return; } QJsonObject jsonObject = QJsonDocument::fromJson(jsonFile.readAll()).object(); if (jsonObject.contains(CommonKey) && jsonObject[CommonKey].isObject()) { QJsonObject commonObject = jsonObject[CommonKey].toObject(); // Parse SDK Tools URL if (commonObject.contains(SdkToolsUrlKey) && commonObject[SdkToolsUrlKey].isObject()) { QJsonObject sdkToolsObj(commonObject[SdkToolsUrlKey].toObject()); if (HostOsInfo::isMacHost()) { m_sdkToolsUrl = sdkToolsObj[macOsKey].toString(); m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["mac_sha256"].toString().toUtf8()); } else if (HostOsInfo::isWindowsHost()) { m_sdkToolsUrl = sdkToolsObj[WindowsOsKey].toString(); m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["windows_sha256"].toString().toUtf8()); } else { m_sdkToolsUrl = sdkToolsObj[LinuxOsKey].toString(); m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["linux_sha256"].toString().toUtf8()); } } // Parse common essential packages auto appendEssentialsFromArray = [this](QJsonArray array) { for (const QJsonValueRef &pkg : array) m_commonEssentialPkgs.append(pkg.toString()); }; QJsonObject commonEssentials = commonObject[SdkEssentialPkgsKey].toObject(); appendEssentialsFromArray(commonEssentials[DefaultVersionKey].toArray()); if (HostOsInfo::isWindowsHost()) appendEssentialsFromArray(commonEssentials[WindowsOsKey].toArray()); else if (HostOsInfo::isMacHost()) appendEssentialsFromArray(commonEssentials[macOsKey].toArray()); else appendEssentialsFromArray(commonEssentials[LinuxOsKey].toArray()); } auto fillQtVersionsRange = [](const QString &shortVersion) { QList versions; static const QRegularExpression re(R"(([0-9]\.[0-9]+\.)\[([0-9]+)\-([0-9]+)\])"); QRegularExpressionMatch match = re.match(shortVersion); if (match.hasMatch() && match.lastCapturedIndex() == 3) for (int i = match.captured(2).toInt(); i <= match.captured(3).toInt(); ++i) versions.append(QVersionNumber::fromString(match.captured(1) + QString::number(i))); else versions.append(QVersionNumber::fromString(shortVersion + ".-1")); return versions; }; if (jsonObject.contains(SpecificQtVersionsKey) && jsonObject[SpecificQtVersionsKey].isArray()) { const QJsonArray versionsArray = jsonObject[SpecificQtVersionsKey].toArray(); for (const QJsonValue &item : versionsArray) { QJsonObject itemObj = item.toObject(); SdkForQtVersions specificVersion; const auto pkgs = itemObj[SdkEssentialPkgsKey].toArray(); for (const QJsonValue &pkg : pkgs) specificVersion.essentialPackages.append(pkg.toString()); const auto versions = itemObj[VersionsKey].toArray(); for (const QJsonValue &pkg : versions) specificVersion.versions.append(fillQtVersionsRange(pkg.toString())); if (itemObj[VersionsKey].toArray().first().toString() == DefaultVersionKey) m_defaultSdkDepends = specificVersion; else m_specificQtVersions.append(specificVersion); } } } static QList availableNdkPlatformsLegacy(const FilePath &ndkLocation) { QList availableNdkPlatforms; ndkLocation .pathAppended("platforms") .iterateDirectory( [&availableNdkPlatforms](const FilePath &filePath) { availableNdkPlatforms.push_back( filePath.toString() .mid(filePath.path().lastIndexOf('-') + 1) .toInt()); return IterationPolicy::Continue; }, {{"android-*"}, QDir::Dirs}); return availableNdkPlatforms; } static QList availableNdkPlatformsV21Plus(const FilePath &ndkLocation, const Abis &abis, OsType hostOs) { if (abis.isEmpty()) return {}; const QString abi = toolsPrefix(abis.first()); const FilePath libPath = AndroidConfig::toolchainPathFromNdk(ndkLocation, hostOs) / "sysroot/usr/lib" / abi; const FilePaths dirEntries = libPath.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot); const QList availableNdkPlatforms = Utils::transform(dirEntries, [](const FilePath &path) { return path.fileName().toInt(); }); return availableNdkPlatforms; } static QList availableNdkPlatformsImpl(const FilePath &ndkLocation, const Abis &abis, OsType hostOs) { QList result = availableNdkPlatformsLegacy(ndkLocation); if (result.isEmpty()) result = availableNdkPlatformsV21Plus(ndkLocation, abis, hostOs); return Utils::sorted(std::move(result), std::greater<>()); } static QList availableNdkPlatforms(const QtVersion *qtVersion) { return availableNdkPlatformsImpl(AndroidConfig::ndkLocation(qtVersion), qtVersion->qtAbis(), HostOsInfo::hostOs()); } QStringList getCustomNdkList() { return config().m_customNdkList; } void addCustomNdk(const QString &customNdk) { if (!config().m_customNdkList.contains(customNdk)) config().m_customNdkList.append(customNdk); } void removeCustomNdk(const QString &customNdk) { config().m_customNdkList.removeAll(customNdk); } void setDefaultNdk(const FilePath &defaultNdk) { config().m_defaultNdk = defaultNdk; } FilePath defaultNdk() { return config().m_defaultNdk; } FilePath openSslLocation() { return config().m_openSslLocation; } void setOpenSslLocation(const FilePath &openSslLocation) { config().m_openSslLocation = openSslLocation; } QStringList apiLevelNamesFor(const SdkPlatformList &platforms) { return Utils::transform(platforms, AndroidConfig::apiLevelNameFor); } QString apiLevelNameFor(const SdkPlatform *platform) { if (platform && platform->apiLevel() > 0) { QString sdkStylePath = platform->sdkStylePath(); return sdkStylePath.remove("platforms;"); } return {}; } FilePath adbToolPath() { return config().m_sdkLocation.pathAppended("platform-tools/adb").withExecutableSuffix(); } FilePath emulatorToolPath() { const FilePath emulatorFile = config().m_sdkLocation.pathAppended("emulator/emulator").withExecutableSuffix(); return emulatorFile.exists() ? emulatorFile : FilePath(); } FilePath sdkManagerToolPath() { const FilePath sdkmanagerPath = config() .m_sdkLocation.pathAppended(Constants::cmdlineToolsName) .pathAppended("latest/bin/sdkmanager" ANDROID_BAT_SUFFIX); if (sdkmanagerPath.exists()) return sdkmanagerPath; // If it's a first time install use the path of Constants::cmdlineToolsName temporary download const FilePath sdkmanagerTmpPath = config().m_temporarySdkToolsPath.pathAppended( "/bin/sdkmanager" ANDROID_BAT_SUFFIX); return sdkmanagerTmpPath.exists() ? sdkmanagerTmpPath : FilePath(); } FilePath avdManagerToolPath() { const FilePath sdkmanagerPath = config() .m_sdkLocation.pathAppended(Constants::cmdlineToolsName) .pathAppended("/latest/bin/avdmanager" ANDROID_BAT_SUFFIX); return sdkmanagerPath.exists() ? sdkmanagerPath : FilePath(); } void setTemporarySdkToolsPath(const FilePath &path) { config().m_temporarySdkToolsPath = path; } FilePath toolchainPathFromNdk(const FilePath &ndkLocation, OsType hostOs) { const FilePath tcPath = ndkLocation / "toolchains/"; FilePath toolchainPath; QDirIterator llvmIter(tcPath.toString(), {"llvm*"}, QDir::Dirs); if (llvmIter.hasNext()) { llvmIter.next(); toolchainPath = tcPath / llvmIter.fileName() / "prebuilt/"; } if (toolchainPath.isEmpty()) return {}; // detect toolchain host QStringList hostPatterns; switch (hostOs) { case OsTypeLinux: hostPatterns << QLatin1String("linux*"); break; case OsTypeWindows: hostPatterns << QLatin1String("windows*"); break; case OsTypeMac: hostPatterns << QLatin1String("darwin*"); break; default: /* unknown host */ return {}; } QDirIterator iter(toolchainPath.toString(), hostPatterns, QDir::Dirs); if (iter.hasNext()) { iter.next(); return toolchainPath / iter.fileName(); } return {}; } FilePath toolchainPath(const QtVersion *qtVersion) { return toolchainPathFromNdk(ndkLocation(qtVersion)); } FilePath clangPathFromNdk(const FilePath &ndkLocation) { const FilePath path = toolchainPathFromNdk(ndkLocation); if (path.isEmpty()) return {}; return path.pathAppended("bin/clang").withExecutableSuffix(); } FilePath makePathFromNdk(const FilePath &ndkLocation) { return ndkLocation.pathAppended( QString("prebuilt/%1/bin/make%2").arg(toolchainHostFromNdk(ndkLocation), QString(QTC_HOST_EXE_SUFFIX))); } static FilePath openJDKBinPath() { const FilePath path = config().m_openJDKLocation; return path.isEmpty() ? path : path.pathAppended("bin"); } FilePath keytoolPath() { return openJDKBinPath().pathAppended(keytoolName).withExecutableSuffix(); } bool isConnected(const QString &serialNumber) { Process adbProcess; adbProcess.setCommand({adbToolPath(), {"devices"}}); adbProcess.runBlocking(); if (adbProcess.result() != ProcessResult::FinishedWithSuccess) return false; // mid(1) - remove "List of devices attached" header line. // Example output: "List of devices attached\nemulator-5554\tdevice\n\n". const QStringList lines = adbProcess.allOutput().split('\n', Qt::SkipEmptyParts).mid(1); for (const QString &line : lines) { // skip the daemon logs if (!line.startsWith("* daemon") && line.left(line.indexOf('\t')).trimmed() == serialNumber) return true; } return false; } bool sdkFullyConfigured() { return config().m_sdkFullyConfigured; } void setSdkFullyConfigured(bool allEssentialsInstalled) { config().m_sdkFullyConfigured = allEssentialsInstalled; } int getSDKVersion(const QString &device) { const QString tmp = getDeviceProperty(device, "ro.build.version.sdk"); return tmp.isEmpty() ? -1 : tmp.trimmed().toInt(); } //! //! \brief AndroidConfig::getProductModel //! \param device serial number //! \return the produce model of the device or if that cannot be read the serial number //! QString getProductModel(const QString &device) { if (config().m_serialNumberToDeviceName.contains(device)) return config().m_serialNumberToDeviceName.value(device); const QString model = getDeviceProperty(device, "ro.product.model").trimmed(); if (model.isEmpty()) return device; if (!device.startsWith("????")) config().m_serialNumberToDeviceName.insert(device, model); return model; } QStringList getAbis(const QString &device) { const FilePath adbTool = AndroidConfig::adbToolPath(); QStringList result; // First try via ro.product.cpu.abilist Process adbProc; adbProc.setCommand({adbTool, {AndroidDeviceInfo::adbSelector(device), "shell", "getprop", "ro.product.cpu.abilist"}}); adbProc.runBlocking(); if (adbProc.result() != ProcessResult::FinishedWithSuccess) return result; QString output = adbProc.allOutput().trimmed(); if (!output.isEmpty()) { QStringList result = output.split(QLatin1Char(',')); if (!result.isEmpty()) return result; } // Fall back to ro.product.cpu.abi, ro.product.cpu.abi2 ... for (int i = 1; i < 6; ++i) { CommandLine cmd{adbTool, {AndroidDeviceInfo::adbSelector(device), "shell", "getprop"}}; if (i == 1) cmd.addArg("ro.product.cpu.abi"); else cmd.addArg(QString::fromLatin1("ro.product.cpu.abi%1").arg(i)); Process abiProc; abiProc.setCommand(cmd); abiProc.runBlocking(); if (abiProc.result() != ProcessResult::FinishedWithSuccess) return result; QString abi = abiProc.allOutput().trimmed(); if (abi.isEmpty()) break; result << abi; } return result; } bool isValidNdk(const QString &ndkLocation) { const FilePath ndkPath = FilePath::fromUserInput(ndkLocation); if (!ndkPath.exists() || !ndkPath.pathAppended("toolchains").exists()) return false; const QVersionNumber version = AndroidConfig::ndkVersion(ndkPath); if (version.isNull()) return false; const FilePath ndkPlatformsDir = ndkPath.pathAppended("platforms"); if (version.majorVersion() <= 22 && (!ndkPlatformsDir.exists() || ndkPlatformsDir.toString().contains(' '))) return false; return true; } QString bestNdkPlatformMatch(int target, const QtVersion *qtVersion) { target = std::max(AndroidManager::defaultMinimumSDK(qtVersion), target); const QList platforms = availableNdkPlatforms(qtVersion); for (const int apiLevel : platforms) { if (apiLevel <= target) return QString::fromLatin1("android-%1").arg(apiLevel); } return QString("android-%1").arg(AndroidManager::defaultMinimumSDK(qtVersion)); } FilePath sdkLocation() { return config().m_sdkLocation; } void setSdkLocation(const FilePath &sdkLocation) { config().m_sdkLocation = sdkLocation; } static FilePath sdkToolsVersionPath() { const FilePath sdkVersionPaths = config() .m_sdkLocation.pathAppended(Constants::cmdlineToolsName) .pathAppended("/latest/source.properties"); if (sdkVersionPaths.exists()) return sdkVersionPaths; // If it's a first time install use the path of Constants::cmdlineToolsName temporary download const FilePath tmpSdkPath = config().m_temporarySdkToolsPath.pathAppended("source.properties"); if (tmpSdkPath.exists()) return tmpSdkPath; return {}; } QVersionNumber sdkToolsVersion() { if (!config().m_sdkLocation.exists()) return {}; const FilePath sdkToolsPropertiesPath = sdkToolsVersionPath(); const QSettings settings(sdkToolsPropertiesPath.toString(), QSettings::IniFormat); return QVersionNumber::fromString(settings.value(sdkToolsVersionKey).toString()); } QVersionNumber buildToolsVersion() { //TODO: return version according to qt version QVersionNumber maxVersion; QDir buildToolsDir(config().m_sdkLocation.pathAppended("build-tools").toString()); const auto files = buildToolsDir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot); for (const QFileInfo &file: files) maxVersion = qMax(maxVersion, QVersionNumber::fromString(file.fileName())); return maxVersion; } QStringList sdkManagerToolArgs() { return config().m_sdkManagerToolArgs; } void setSdkManagerToolArgs(const QStringList &args) { config().m_sdkManagerToolArgs = args; } FilePath ndkLocation(const QtVersion *qtVersion) { if (!config().m_defaultNdk.isEmpty()) return config().m_defaultNdk; // A selected default NDK is good for any Qt version return sdkLocation().resolvePath(ndkSubPathFromQtVersion(*qtVersion)); } QVersionNumber ndkVersion(const QtVersion *qtVersion) { return ndkVersion(ndkLocation(qtVersion)); } QVersionNumber ndkVersion(const FilePath &ndkPath) { QVersionNumber version; if (!ndkPath.exists()) { qCDebug(avdConfigLog).noquote() << "Cannot find ndk version. Check NDK path." << ndkPath.toUserOutput(); return version; } const FilePath ndkPropertiesPath = ndkPath.pathAppended("source.properties"); if (ndkPropertiesPath.exists()) { // source.properties files exists in NDK version > 11 QSettings settings(ndkPropertiesPath.toString(), QSettings::IniFormat); auto versionStr = settings.value(ndkRevisionKey).toString(); version = QVersionNumber::fromString(versionStr); } else { // No source.properties. There should be a file named RELEASE.TXT const FilePath ndkReleaseTxtPath = ndkPath.pathAppended("RELEASE.TXT"); FileReader reader; QString errorString; if (reader.fetch(ndkReleaseTxtPath, &errorString)) { // RELEASE.TXT contains the ndk version in either of the following formats: // r6a // r10e (64 bit) QString content = QString::fromUtf8(reader.data()); static const QRegularExpression re("(r)(?[0-9]{1,2})(?[a-z]{1,1})"); QRegularExpressionMatch match = re.match(content); if (match.hasMatch()) { QString major = match.captured("major"); QString minor = match.captured("minor"); // Minor version: a = 0, b = 1, c = 2 and so on. // Int equivalent = minorVersionChar - 'a'. i.e. minorVersionChar - 97. version = QVersionNumber::fromString(QString("%1.%2.0").arg(major) .arg((int)minor[0].toLatin1() - 97)); } else { qCDebug(avdConfigLog) << "Cannot find ndk version. Cannot parse RELEASE.TXT." << content; } } else { qCDebug(avdConfigLog) << "Cannot find ndk version." << errorString; } } return version; } QUrl sdkToolsUrl() { return config().m_sdkToolsUrl; } QByteArray getSdkToolsSha256() { return config().m_sdkToolsSha256; } static QStringList defaultEssentials() { return config().m_defaultSdkDepends.essentialPackages + config().m_commonEssentialPkgs; } static QStringList packagesExcludingBuiltWithDefaults(const QStringList &packages) { return Utils::filtered(packages, [] (const QString &p) { return !p.startsWith(ndkPackageMarker()) && !p.startsWith(platformsPackageMarker()) && !p.startsWith(buildToolsPackageMarker()); }); } static QString essentialBuiltWithBuildToolsPackage(int builtWithApiVersion) { // For build-tools, to avoid the situation of potentially having the essential packages // invalidated whenever a new minor version is released, check if any version with major // version matching builtWith apiVersion and use it as essential, otherwise use the any // other one that has an minimum major version of builtWith apiVersion. const BuildToolsList buildTools = AndroidConfigurations::sdkManager()->filteredBuildTools(builtWithApiVersion); const BuildToolsList apiBuildTools = Utils::filtered(buildTools, [builtWithApiVersion] (const BuildTools *pkg) { return pkg->revision().majorVersion() == builtWithApiVersion; }); const QString installedBuildTool = [apiBuildTools] () -> QString { for (const BuildTools *pkg : apiBuildTools) { if (pkg->state() == AndroidSdkPackage::Installed) return pkg->sdkStylePath(); } return {}; }(); if (installedBuildTool.isEmpty()) { if (!apiBuildTools.isEmpty()) return apiBuildTools.first()->sdkStylePath(); else if (!buildTools.isEmpty()) return buildTools.first()->sdkStylePath(); // This means there's something wrong with sdkmanager, return a default version anyway else return buildToolsPackageMarker() + QString::number(builtWithApiVersion) + ".0.0"; } return installedBuildTool; } static QStringList essentialsFromQtVersion(const QtVersion &version) { if (auto androidQtVersion = dynamic_cast(&version)) { bool ok; const AndroidQtVersion::BuiltWith bw = androidQtVersion->builtWith(&ok); if (ok) { QStringList builtWithPackages; builtWithPackages.append(ndkPackageMarker() + bw.ndkVersion.toString()); const QString apiVersion = QString::number(bw.apiVersion); builtWithPackages.append(platformsPackageMarker() + "android-" + apiVersion); builtWithPackages.append(essentialBuiltWithBuildToolsPackage(bw.apiVersion)); return builtWithPackages + packagesExcludingBuiltWithDefaults( config().m_defaultSdkDepends.essentialPackages); } } const QVersionNumber qtVersion = version.qtVersion(); for (const SdkForQtVersions &item : config().m_specificQtVersions) { if (item.containsVersion(qtVersion)) return item.essentialPackages; } return config().m_defaultSdkDepends.essentialPackages; } QStringList allEssentials() { QtVersions installedVersions = QtVersionManager::versions( [](const QtVersion *v) { return v->targetDeviceTypes().contains(Android::Constants::ANDROID_DEVICE_TYPE); }); QStringList allPackages(defaultEssentials()); for (const QtVersion *version : installedVersions) allPackages.append(essentialsFromQtVersion(*version)); allPackages.removeDuplicates(); return allPackages; } static QStringList packagesWithoutNdks(const QStringList &packages) { return Utils::filtered(packages, [] (const QString &p) { return !p.startsWith(ndkPackageMarker()); }); } bool allEssentialsInstalled(AndroidSdkManager *sdkManager) { QStringList essentialPkgs(allEssentials()); const auto installedPkgs = sdkManager->installedSdkPackages(); for (const AndroidSdkPackage *pkg : installedPkgs) { if (essentialPkgs.contains(pkg->sdkStylePath())) essentialPkgs.removeOne(pkg->sdkStylePath()); if (essentialPkgs.isEmpty()) break; } if (!config().m_defaultNdk.isEmpty()) essentialPkgs = packagesWithoutNdks(essentialPkgs); return essentialPkgs.isEmpty() ? true : false; } bool sdkToolsOk() { const bool exists = sdkLocation().exists(); const bool writable = sdkLocation().isWritableDir(); const bool sdkToolsExist = !sdkToolsVersion().isNull(); return exists && writable && sdkToolsExist; } FilePath openJDKLocation() { return config().m_openJDKLocation; } void setOpenJDKLocation(const FilePath &openJDKLocation) { config().m_openJDKLocation = openJDKLocation; } QString toolchainHost(const QtVersion *qtVersion) { return toolchainHostFromNdk(ndkLocation(qtVersion)); } QString toolchainHostFromNdk(const FilePath &ndkPath) { // detect toolchain host QString toolchainHost; QStringList hostPatterns; switch (HostOsInfo::hostOs()) { case OsTypeLinux: hostPatterns << QLatin1String("linux*"); break; case OsTypeWindows: hostPatterns << QLatin1String("windows*"); break; case OsTypeMac: hostPatterns << QLatin1String("darwin*"); break; default: /* unknown host */ return toolchainHost; } QDirIterator jt(ndkPath.pathAppended("prebuilt").toString(), hostPatterns, QDir::Dirs); if (jt.hasNext()) { jt.next(); toolchainHost = jt.fileName(); } return toolchainHost; } QString emulatorArgs() { return config().m_emulatorArgs; } void setEmulatorArgs(const QString &args) { config().m_emulatorArgs = args; } bool automaticKitCreation() { return config().m_automaticKitCreation; } void setAutomaticKitCreation(bool b) { config().m_automaticKitCreation = b; } FilePath defaultSdkPath() { QString sdkFromEnvVar = QString::fromLocal8Bit(getenv("ANDROID_SDK_ROOT")); if (!sdkFromEnvVar.isEmpty()) return FilePath::fromUserInput(sdkFromEnvVar).cleanPath(); // Set default path of SDK as used by Android Studio if (HostOsInfo::isMacHost()) { return FilePath::fromString( QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/Library/Android/sdk"); } if (HostOsInfo::isWindowsHost()) { return FilePath::fromString( QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/Android/Sdk"); } return FilePath::fromString( QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/Android/Sdk"); } Environment toolsEnvironment() { Environment env = Environment::systemEnvironment(); FilePath jdkLocation = openJDKLocation(); if (!jdkLocation.isEmpty()) { env.set(Constants::JAVA_HOME_ENV_VAR, jdkLocation.toUserOutput()); env.prependOrSetPath(jdkLocation.pathAppended("bin")); } return env; } static FilePath androidStudioPath() { #if defined(Q_OS_WIN) const QLatin1String registryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\Android Studio"); const QLatin1String valueName("Path"); const QSettings settings64(registryKey, QSettings::Registry64Format); const QSettings settings32(registryKey, QSettings::Registry32Format); return FilePath::fromUserInput( settings64.value(valueName, settings32.value(valueName).toString()).toString()); #endif return {}; // TODO non-Windows } FilePath getJdkPath() { FilePath jdkHome = FilePath::fromString(qtcEnvironmentVariable(Constants::JAVA_HOME_ENV_VAR)); if (jdkHome.exists()) return jdkHome; if (HostOsInfo::isWindowsHost()) { // Look for Android Studio's jdk first const FilePath androidStudioSdkPath = androidStudioPath(); if (!androidStudioSdkPath.isEmpty()) { const QStringList allVersions{"jbr", "jre"}; for (const QString &version : allVersions) { const FilePath androidStudioSdkJbrPath = androidStudioSdkPath / version; if (androidStudioSdkJbrPath.exists()) return androidStudioSdkJbrPath; } } if (jdkHome.isEmpty()) { QStringList allVersions; QSettings settings("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\", QSettings::NativeFormat); allVersions = settings.childGroups(); #ifdef Q_OS_WIN if (allVersions.isEmpty()) { settings.setDefaultFormat(QSettings::Registry64Format); allVersions = settings.childGroups(); } #endif // Q_OS_WIN // Look for the highest existing JDK allVersions.sort(); std::reverse(allVersions.begin(), allVersions.end()); // Order descending for (const QString &version : std::as_const(allVersions)) { settings.beginGroup(version); jdkHome = FilePath::fromUserInput(settings.value("JavaHome").toString()); settings.endGroup(); if (jdkHome.exists()) break; } } } else { QStringList args; if (HostOsInfo::isMacHost()) args << "-c" << "/usr/libexec/java_home"; else args << "-c" << "readlink -f $(which java)"; Process findJdkPathProc; findJdkPathProc.setCommand({"sh", args}); findJdkPathProc.start(); findJdkPathProc.waitForFinished(); QByteArray jdkPath = findJdkPathProc.rawStdOut().trimmed(); if (HostOsInfo::isMacHost()) { jdkHome = FilePath::fromUtf8(jdkPath); } else { jdkPath.replace("bin/java", ""); // For OpenJDK 11 jdkPath.replace("jre", ""); jdkPath.replace("//", "/"); jdkHome = FilePath::fromUtf8(jdkPath); } } return jdkHome; } } // namespace AndroidConfig /////////////////////////////////// // AndroidConfigurations /////////////////////////////////// AndroidConfigurations *m_instance = nullptr; AndroidConfigurations::AndroidConfigurations() : m_sdkManager(new AndroidSdkManager) { load(); connect(DeviceManager::instance(), &DeviceManager::devicesLoaded, this, &AndroidConfigurations::updateAndroidDevice); m_instance = this; } void AndroidConfigurations::applyConfig() { emit m_instance->aboutToUpdate(); m_instance->save(); updateAndroidDevice(); registerNewToolchains(); updateAutomaticKitList(); removeOldToolchains(); emit m_instance->updated(); } static bool matchToolchain(const Toolchain *atc, const Toolchain *btc) { if (atc == btc) return true; if (!atc || !btc) return false; if (atc->typeId() != Constants::ANDROID_TOOLCHAIN_TYPEID || btc->typeId() != Constants::ANDROID_TOOLCHAIN_TYPEID) return false; return atc->targetAbi() == btc->targetAbi(); } void AndroidConfigurations::registerNewToolchains() { const Toolchains existingAndroidToolchains = ToolchainManager::toolchains(Utils::equal(&Toolchain::typeId, Id(Constants::ANDROID_TOOLCHAIN_TYPEID))); const Toolchains newToolchains = autodetectToolchains(existingAndroidToolchains); for (Toolchain *tc : newToolchains) ToolchainManager::registerToolchain(tc); registerCustomToolchainsAndDebuggers(); } void AndroidConfigurations::removeOldToolchains() { const auto tcs = ToolchainManager::toolchains(Utils::equal(&Toolchain::typeId, Id(Constants::ANDROID_TOOLCHAIN_TYPEID))); for (Toolchain *tc : tcs) { if (!tc->isValid()) ToolchainManager::deregisterToolchain(tc); } } void AndroidConfigurations::removeUnusedDebuggers() { const QList qtVersions = QtVersionManager::versions([](const QtVersion *v) { return v->type() == Constants::ANDROID_QT_TYPE; }); QList uniqueNdks; for (const QtVersion *qt : qtVersions) { FilePath ndkLocation = AndroidConfig::ndkLocation(qt); if (!uniqueNdks.contains(ndkLocation)) uniqueNdks.append(ndkLocation); } uniqueNdks.append(FileUtils::toFilePathList(AndroidConfig::getCustomNdkList())); const QList allDebuggers = Debugger::DebuggerItemManager::debuggers(); for (const Debugger::DebuggerItem &debugger : allDebuggers) { if (!debugger.displayName().contains("Android")) continue; bool isChildOfNdk = false; for (const FilePath &path : uniqueNdks) { if (debugger.command().isChildOf(path)) { isChildOfNdk = true; break; } } const bool isMultiAbiNdkGdb = debugger.command().fileName().startsWith("gdb"); const bool hasMultiAbiName = debugger.displayName().contains("Multi-Abi"); if (debugger.isAutoDetected() && (!isChildOfNdk || (isMultiAbiNdkGdb && !hasMultiAbiName))) Debugger::DebuggerItemManager::deregisterDebugger(debugger.id()); } } static QStringList allSupportedAbis() { return QStringList{ ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A, ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A, ProjectExplorer::Constants::ANDROID_ABI_X86, ProjectExplorer::Constants::ANDROID_ABI_X86_64, }; } static bool containsAllAbis(const QStringList &abis) { QStringList supportedAbis{allSupportedAbis()}; for (const QString &abi : abis) if (supportedAbis.contains(abi)) supportedAbis.removeOne(abi); return supportedAbis.isEmpty(); } static QString getMultiOrSingleAbiString(const QStringList &abis) { return containsAllAbis(abis) ? "Multi-Abi" : abis.join(","); } static const Debugger::DebuggerItem *existingDebugger(const FilePath &command, Debugger::DebuggerEngineType type) { // check if the debugger is already registered, but ignoring the display name const Debugger::DebuggerItem *existing = Debugger::DebuggerItemManager::findByCommand(command); // Return existing debugger with same command if (existing && existing->engineType() == type && existing->isAutoDetected()) return existing; return nullptr; } static QVariant findOrRegisterDebugger(Toolchain *tc, const QStringList &abisList, bool customDebugger = false) { const FilePath ndk = static_cast(tc)->ndkLocation(); const FilePath lldbCommand = lldbPathFromNdk(ndk); const Debugger::DebuggerItem *existingLldb = existingDebugger(lldbCommand, Debugger::LldbEngineType); // Return existing debugger with same command - prefer lldb (limit to sdk/ndk min version?) if (existingLldb) return existingLldb->id(); const FilePath gdbCommand = gdbPathFromNdk(tc->targetAbi(), ndk); // check if the debugger is already registered, but ignoring the display name const Debugger::DebuggerItem *existingGdb = existingDebugger(gdbCommand, Debugger::GdbEngineType); // Return existing debugger with same command if (existingGdb) return existingGdb->id(); const QString mainName = Tr::tr("Android Debugger (%1, NDK %2)"); const QString custom = customDebugger ? QString{"Custom "} : QString{}; // debugger not found, register a new one // check lldb QVariant registeredLldb; if (!lldbCommand.isEmpty()) { Debugger::DebuggerItem debugger; debugger.setCommand(lldbCommand); debugger.setEngineType(Debugger::LldbEngineType); debugger.setUnexpandedDisplayName(custom + mainName .arg(getMultiOrSingleAbiString(allSupportedAbis())) .arg(AndroidConfig::ndkVersion(ndk).toString()) + ' ' + debugger.engineTypeName()); debugger.setAutoDetected(true); debugger.reinitializeFromFile(); registeredLldb = Debugger::DebuggerItemManager::registerDebugger(debugger); } // we always have a value for gdb (but we shouldn't - we currently use a fallback) if (!gdbCommand.exists()) { if (!registeredLldb.isNull()) return registeredLldb; return {}; } Debugger::DebuggerItem debugger; debugger.setCommand(gdbCommand); debugger.setEngineType(Debugger::GdbEngineType); // NDK 10 and older have multiple gdb versions per ABI, so check for that. const bool oldNdkVersion = AndroidConfig::ndkVersion(ndk) <= QVersionNumber{11}; debugger.setUnexpandedDisplayName(custom + mainName .arg(getMultiOrSingleAbiString(oldNdkVersion ? abisList : allSupportedAbis())) .arg(AndroidConfig::ndkVersion(ndk).toString()) + ' ' + debugger.engineTypeName()); debugger.setAutoDetected(true); debugger.reinitializeFromFile(); QVariant registeredGdb = Debugger::DebuggerItemManager::registerDebugger(debugger); return registeredLldb.isNull() ? registeredGdb : registeredLldb; } void AndroidConfigurations::registerCustomToolchainsAndDebuggers() { const Toolchains existingAndroidToolchains = ToolchainManager::toolchains( Utils::equal(&Toolchain::typeId, Id(Constants::ANDROID_TOOLCHAIN_TYPEID))); const FilePaths customNdks = FileUtils::toFilePathList(AndroidConfig::getCustomNdkList()); const Toolchains customToolchains = autodetectToolchainsFromNdks(existingAndroidToolchains, customNdks, true); for (Toolchain *tc : customToolchains) { ToolchainManager::registerToolchain(tc); const auto androidToolchain = static_cast(tc); QString abiStr; if (androidToolchain) abiStr = androidToolchain->platformLinkerFlags().at(1).split('-').first(); findOrRegisterDebugger(tc, {abiStr}, true); } } void AndroidConfigurations::updateAutomaticKitList() { for (Kit *k : KitManager::kits()) { if (DeviceTypeKitAspect::deviceTypeId(k) == Constants::ANDROID_DEVICE_TYPE) { if (k->value(Constants::ANDROID_KIT_NDK).isNull() || k->value(Constants::ANDROID_KIT_SDK).isNull()) { if (QtVersion *qt = QtKitAspect::qtVersion(k)) { k->setValueSilently(Constants::ANDROID_KIT_NDK, AndroidConfig::ndkLocation(qt).toString()); k->setValue(Constants::ANDROID_KIT_SDK, AndroidConfig::sdkLocation().toString()); } } } } const QList existingKits = Utils::filtered(KitManager::kits(), [](Kit *k) { Id deviceTypeId = DeviceTypeKitAspect::deviceTypeId(k); if (k->isAutoDetected() && !k->isSdkProvided() && deviceTypeId == Constants::ANDROID_DEVICE_TYPE) { return true; } return false; }); removeUnusedDebuggers(); QHash > qtVersionsForArch; const QList qtVersions = QtVersionManager::versions([](const QtVersion *v) { return v->type() == Constants::ANDROID_QT_TYPE; }); for (const QtVersion *qtVersion : qtVersions) { const Abis qtAbis = qtVersion->qtAbis(); if (qtAbis.empty()) continue; qtVersionsForArch[qtAbis.first()].append(qtVersion); } // register new kits const Toolchains toolchains = ToolchainManager::toolchains([](const Toolchain *tc) { return tc->isAutoDetected() && tc->typeId() == Constants::ANDROID_TOOLCHAIN_TYPEID && tc->isValid(); }); QList unhandledKits = existingKits; for (Toolchain *tc : toolchains) { if (tc->language() != ProjectExplorer::Constants::CXX_LANGUAGE_ID) continue; for (const QtVersion *qt : qtVersionsForArch.value(tc->targetAbi())) { FilePath tcNdk = static_cast(tc)->ndkLocation(); if (tcNdk != AndroidConfig::ndkLocation(qt)) continue; const Toolchains allLanguages = Utils::filtered(toolchains, [tc, tcNdk](Toolchain *otherTc) { FilePath otherNdk = static_cast(otherTc)->ndkLocation(); return tc->targetAbi() == otherTc->targetAbi() && tcNdk == otherNdk; }); QHash toolchainForLanguage; for (Toolchain *tc : allLanguages) toolchainForLanguage[tc->language()] = tc; Kit *existingKit = Utils::findOrDefault(existingKits, [&](const Kit *b) { if (qt != QtKitAspect::qtVersion(b)) return false; return matchToolchain(toolchainForLanguage[ProjectExplorer::Constants::CXX_LANGUAGE_ID], ToolchainKitAspect::cxxToolchain(b)) && matchToolchain(toolchainForLanguage[ProjectExplorer::Constants::C_LANGUAGE_ID], ToolchainKitAspect::cToolchain(b)); }); const auto initializeKit = [allLanguages, tc, qt](Kit *k) { k->setAutoDetected(true); k->setAutoDetectionSource("AndroidConfiguration"); DeviceTypeKitAspect::setDeviceTypeId(k, Constants::ANDROID_DEVICE_TYPE); for (Toolchain *tc : allLanguages) ToolchainKitAspect::setToolchain(k, tc); QtKitAspect::setQtVersion(k, qt); QStringList abis = static_cast(qt)->androidAbis(); Debugger::DebuggerKitAspect::setDebugger(k, findOrRegisterDebugger(tc, abis)); BuildDeviceKitAspect::setDeviceId(k, DeviceManager::defaultDesktopDevice()->id()); k->setSticky(QtKitAspect::id(), true); k->setSticky(DeviceTypeKitAspect::id(), true); QString versionStr = QLatin1String("Qt %{Qt:Version}"); if (!qt->isAutodetected()) versionStr = QString("%1").arg(qt->displayName()); k->setUnexpandedDisplayName(Tr::tr("Android %1 Clang %2") .arg(versionStr) .arg(getMultiOrSingleAbiString(abis))); k->setValueSilently(Constants::ANDROID_KIT_NDK, AndroidConfig::ndkLocation(qt).toString()); k->setValueSilently(Constants::ANDROID_KIT_SDK, AndroidConfig::sdkLocation().toString()); }; if (existingKit) { initializeKit(existingKit); // Update the existing kit with new data. unhandledKits.removeOne(existingKit); } else { KitManager::registerKit(initializeKit); } } } // cleanup any mess that might have existed before, by removing all Android kits that // existed before, but weren't re-used KitManager::deregisterKits(unhandledKits); } AndroidSdkManager *AndroidConfigurations::sdkManager() { return m_instance->m_sdkManager.get(); } AndroidConfigurations *AndroidConfigurations::instance() { return m_instance; } void AndroidConfigurations::save() { QtcSettings *settings = Core::ICore::settings(); settings->beginGroup(SettingsGroup); AndroidConfig::config().save(*settings); settings->endGroup(); } void AndroidConfigurations::load() { QtcSettings *settings = Core::ICore::settings(); settings->beginGroup(SettingsGroup); AndroidConfig::config().load(*settings); settings->endGroup(); } void AndroidConfigurations::updateAndroidDevice() { // Remove any dummy Android device, because it won't be usable. DeviceManager *const devMgr = DeviceManager::instance(); IDevice::ConstPtr dev = devMgr->find(Constants::ANDROID_DEVICE_ID); if (dev) devMgr->removeDevice(dev->id()); AndroidDeviceManager::setupDevicesWatcher(); } #ifdef WITH_TESTS class AndroidConfigurationsTest final : public QObject { Q_OBJECT private slots: void testAndroidConfigAvailableNdkPlatforms_data(); void testAndroidConfigAvailableNdkPlatforms(); }; void AndroidConfigurationsTest::testAndroidConfigAvailableNdkPlatforms_data() { QTest::addColumn("ndkPath"); QTest::addColumn("abis"); QTest::addColumn("hostOs"); QTest::addColumn >("expectedPlatforms"); QTest::newRow("ndkLegacy") << FilePath::fromUserInput(":/android/tst/ndk/19.2.5345600") << Abis() << OsTypeOther << QList{28, 27, 26, 24, 23, 22, 21, 19, 18, 17, 16}; const FilePath ndkV21Plus = FilePath::fromUserInput(":/android/tst/ndk/23.1.7779620"); const QList abis32Bit = {31, 30, 29, 28, 27, 26, 24, 23, 22, 21, 19, 18, 17, 16}; const QList abis64Bit = {31, 30, 29, 28, 27, 26, 24, 23, 22, 21}; QTest::newRow("ndkV21Plus armeabi-v7a OsTypeWindows") << ndkV21Plus << Abis{AndroidManager::androidAbi2Abi( ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A)} << OsTypeWindows << abis32Bit; QTest::newRow("ndkV21Plus arm64-v8a OsTypeLinux") << ndkV21Plus << Abis{AndroidManager::androidAbi2Abi( ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A)} << OsTypeLinux << abis64Bit; QTest::newRow("ndkV21Plus x86 OsTypeMac") << ndkV21Plus << Abis{AndroidManager::androidAbi2Abi( ProjectExplorer::Constants::ANDROID_ABI_X86)} << OsTypeMac << abis32Bit; QTest::newRow("ndkV21Plus x86_64 OsTypeWindows") << ndkV21Plus << Abis{AndroidManager::androidAbi2Abi( ProjectExplorer::Constants::ANDROID_ABI_X86_64)} << OsTypeWindows << abis64Bit; } void AndroidConfigurationsTest::testAndroidConfigAvailableNdkPlatforms() { QFETCH(FilePath, ndkPath); QFETCH(Abis, abis); QFETCH(OsType, hostOs); QFETCH(QList, expectedPlatforms); const QList foundPlatforms = AndroidConfig::availableNdkPlatformsImpl(ndkPath, abis, hostOs); QCOMPARE(foundPlatforms, expectedPlatforms); } QObject *createAndroidConfigurationsTest() { return new AndroidConfigurationsTest; } #endif // WITH_TESTS void setupAndroidConfigurations() { static AndroidConfigurations theAndroidConfigurations; } } // namespace Android #include "androidconfigurations.moc"