1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 BogDan Vatra <bog_dan_ro@yahoo.com>
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "androidconfigurations.h"
27 #include "androidconstants.h"
28 #include "androidtoolchain.h"
29 #include "androiddevice.h"
30 #include "androidmanager.h"
31 #include "androidqtversion.h"
32 #include "androiddevicedialog.h"
33 #include "avddialog.h"
34
35 #include <coreplugin/icore.h>
36 #include <coreplugin/messagemanager.h>
37
38 #include <projectexplorer/devicesupport/devicemanager.h>
39 #include <projectexplorer/kitinformation.h>
40 #include <projectexplorer/kitmanager.h>
41 #include <projectexplorer/project.h>
42 #include <projectexplorer/projectexplorerconstants.h>
43 #include <projectexplorer/session.h>
44 #include <projectexplorer/toolchainmanager.h>
45
46 #include <debugger/debuggeritemmanager.h>
47 #include <debugger/debuggeritem.h>
48 #include <debugger/debuggerkitinformation.h>
49
50 #include <qtsupport/baseqtversion.h>
51 #include <qtsupport/qtkitinformation.h>
52 #include <qtsupport/qtversionmanager.h>
53
54 #include <utils/algorithm.h>
55 #include <utils/environment.h>
56 #include <utils/environment.h>
57 #include <utils/hostosinfo.h>
58 #include <utils/persistentsettings.h>
59 #include <utils/qtcassert.h>
60 #include <utils/qtcprocess.h>
61 #include <utils/runextensions.h>
62 #include <utils/stringutils.h>
63
64 #include <QApplication>
65 #include <QDirIterator>
66 #include <QFileInfo>
67 #include <QHostAddress>
68 #include <QJsonArray>
69 #include <QJsonDocument>
70 #include <QJsonObject>
71 #include <QLoggingCategory>
72 #include <QProcess>
73 #include <QRegularExpression>
74 #include <QSettings>
75 #include <QStandardPaths>
76 #include <QStringList>
77 #include <QTcpSocket>
78 #include <QThread>
79
80 #include <functional>
81 #include <memory>
82
83 using namespace QtSupport;
84 using namespace ProjectExplorer;
85 using namespace Utils;
86
87 namespace {
88 static Q_LOGGING_CATEGORY(avdConfigLog, "qtc.android.androidconfig", QtWarningMsg)
89 }
90
91 namespace Android {
92 using namespace Internal;
93
94 const char JsonFilePath[] = "android/sdk_definitions.json";
95 const char SdkToolsUrlKey[] = "sdk_tools_url";
96 const char CommonKey[] = "common";
97 const char SdkEssentialPkgsKey[] = "sdk_essential_packages";
98 const char VersionsKey[] = "versions";
99 const char NdkPathKey[] = "ndk_path";
100 const char SpecificQtVersionsKey[] = "specific_qt_versions";
101 const char DefaultVersionKey[] = "default";
102 const char LinuxOsKey[] = "linux";
103 const char WindowsOsKey[] = "windows";
104 const char macOsKey[] = "mac";
105
106
107 namespace {
108 const char jdk8SettingsPath[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit";
109 const char jdkLatestSettingsPath[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\";
110
111 const QLatin1String SettingsGroup("AndroidConfigurations");
112 const QLatin1String SDKLocationKey("SDKLocation");
113 const QLatin1String CustomNdkLocationsKey("CustomNdkLocations");
114 const QLatin1String SdkFullyConfiguredKey("AllEssentialsInstalled");
115 const QLatin1String SDKManagerToolArgsKey("SDKManagerToolArgs");
116 const QLatin1String OpenJDKLocationKey("OpenJDKLocation");
117 const QLatin1String OpenSslPriLocationKey("OpenSSLPriLocation");
118 const QLatin1String AutomaticKitCreationKey("AutomatiKitCreation");
119 const QLatin1String EmulatorArgsKey("EmulatorArgs");
120
121 const QLatin1String ArmToolchainPrefix("arm-linux-androideabi");
122 const QLatin1String X86ToolchainPrefix("x86");
123 const QLatin1String AArch64ToolchainPrefix("aarch64-linux-android");
124 const QLatin1String X86_64ToolchainPrefix("x86_64");
125
126 const QLatin1String ArmToolsPrefix("arm-linux-androideabi");
127 const QLatin1String X86ToolsPrefix("i686-linux-android");
128 const QLatin1String AArch64ToolsPrefix("aarch64-linux-android");
129 const QLatin1String X86_64ToolsPrefix("x86_64-linux-android");
130
131 const QLatin1String ArmToolsDisplayName("arm");
132 const QLatin1String X86ToolsDisplayName("i686");
133 const QLatin1String AArch64ToolsDisplayName("aarch64");
134 const QLatin1String X86_64ToolsDisplayName("x86_64");
135
136 const QLatin1String Unknown("unknown");
137 const QLatin1String keytoolName("keytool");
138 const QLatin1String changeTimeStamp("ChangeTimeStamp");
139
140 const QLatin1String sdkToolsVersionKey("Pkg.Revision");
141 const QLatin1String ndkRevisionKey("Pkg.Revision");
142
sdkSettingsFileName()143 static QString sdkSettingsFileName()
144 {
145 return Core::ICore::installerResourcePath("android.xml").toString();
146 }
147
is32BitUserSpace()148 static bool is32BitUserSpace()
149 {
150 // Do the exact same check as android's emulator is doing:
151 if (HostOsInfo::isLinuxHost()) {
152 if (QSysInfo::WordSize == 32 ) {
153 Environment env = Environment::systemEnvironment();
154 QString executable = env.searchInPath(QLatin1String("file")).toString();
155 QString shell = env.value(QLatin1String("SHELL"));
156 if (executable.isEmpty() || shell.isEmpty())
157 return true; // we can't detect, but creator is 32bit so assume 32bit
158
159 QtcProcess proc;
160 proc.setProcessChannelMode(QProcess::MergedChannels);
161 proc.setTimeoutS(30);
162 proc.setCommand({executable, {shell}});
163 proc.runBlocking();
164 if (proc.result() != QtcProcess::FinishedWithSuccess)
165 return true;
166 return !proc.allOutput().contains("x86-64");
167 }
168 }
169 return false;
170 }
171 }
172
173 //////////////////////////////////
174 // AndroidConfig
175 //////////////////////////////////
176
toolchainPrefix(const Abi & abi)177 QLatin1String AndroidConfig::toolchainPrefix(const Abi &abi)
178 {
179 switch (abi.architecture()) {
180 case Abi::ArmArchitecture:
181 if (abi.wordWidth() == 64)
182 return AArch64ToolchainPrefix;
183 return ArmToolchainPrefix;
184 case Abi::X86Architecture:
185 if (abi.wordWidth() == 64)
186 return X86_64ToolchainPrefix;
187 return X86ToolchainPrefix;
188 default:
189 return Unknown;
190 }
191 }
192
toolsPrefix(const Abi & abi)193 QLatin1String AndroidConfig::toolsPrefix(const Abi &abi)
194 {
195 switch (abi.architecture()) {
196 case Abi::ArmArchitecture:
197 if (abi.wordWidth() == 64)
198 return AArch64ToolsPrefix;
199 return ArmToolsPrefix;
200 case Abi::X86Architecture:
201 if (abi.wordWidth() == 64)
202 return X86_64ToolsPrefix;
203 return X86ToolsPrefix;
204 default:
205 return Unknown;
206 }
207 }
208
displayName(const Abi & abi)209 QLatin1String AndroidConfig::displayName(const Abi &abi)
210 {
211 switch (abi.architecture()) {
212 case Abi::ArmArchitecture:
213 if (abi.wordWidth() == 64)
214 return AArch64ToolsDisplayName;
215 return ArmToolsDisplayName;
216 case Abi::X86Architecture:
217 if (abi.wordWidth() == 64)
218 return X86_64ToolsDisplayName;
219 return X86ToolsDisplayName;
220 default:
221 return Unknown;
222 }
223 }
224
load(const QSettings & settings)225 void AndroidConfig::load(const QSettings &settings)
226 {
227 // user settings
228 m_emulatorArgs = settings.value(EmulatorArgsKey,
229 QStringList({"-netdelay", "none", "-netspeed", "full"})).toStringList();
230 m_sdkLocation = FilePath::fromUserInput(settings.value(SDKLocationKey).toString()).cleanPath();
231 m_customNdkList = settings.value(CustomNdkLocationsKey).toStringList();
232 m_sdkManagerToolArgs = settings.value(SDKManagerToolArgsKey).toStringList();
233 m_openJDKLocation = FilePath::fromString(settings.value(OpenJDKLocationKey).toString());
234 m_openSslLocation = FilePath::fromString(settings.value(OpenSslPriLocationKey).toString());
235 m_automaticKitCreation = settings.value(AutomaticKitCreationKey, true).toBool();
236 m_sdkFullyConfigured = settings.value(SdkFullyConfiguredKey, false).toBool();
237
238 PersistentSettingsReader reader;
239 if (reader.load(FilePath::fromString(sdkSettingsFileName()))
240 && settings.value(changeTimeStamp).toInt() != QFileInfo(sdkSettingsFileName()).lastModified().toMSecsSinceEpoch() / 1000) {
241 // persisten settings
242 m_sdkLocation = FilePath::fromUserInput(reader.restoreValue(SDKLocationKey, m_sdkLocation.toString()).toString()).cleanPath();
243 m_customNdkList = reader.restoreValue(CustomNdkLocationsKey).toStringList();
244 m_sdkManagerToolArgs = reader.restoreValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs).toStringList();
245 m_openJDKLocation = FilePath::fromString(reader.restoreValue(OpenJDKLocationKey, m_openJDKLocation.toString()).toString());
246 m_openSslLocation = FilePath::fromString(reader.restoreValue(OpenSslPriLocationKey, m_openSslLocation.toString()).toString());
247 m_automaticKitCreation = reader.restoreValue(AutomaticKitCreationKey, m_automaticKitCreation).toBool();
248 m_sdkFullyConfigured = reader.restoreValue(SdkFullyConfiguredKey, m_sdkFullyConfigured).toBool();
249 // persistent settings
250 }
251 m_customNdkList.removeAll("");
252 parseDependenciesJson();
253 }
254
save(QSettings & settings) const255 void AndroidConfig::save(QSettings &settings) const
256 {
257 QFileInfo fileInfo(sdkSettingsFileName());
258 if (fileInfo.exists())
259 settings.setValue(changeTimeStamp, fileInfo.lastModified().toMSecsSinceEpoch() / 1000);
260
261 // user settings
262 settings.setValue(SDKLocationKey, m_sdkLocation.toString());
263 settings.setValue(CustomNdkLocationsKey, m_customNdkList);
264 settings.setValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs);
265 settings.setValue(OpenJDKLocationKey, m_openJDKLocation.toString());
266 settings.setValue(OpenSslPriLocationKey, m_openSslLocation.toString());
267 settings.setValue(EmulatorArgsKey, m_emulatorArgs);
268 settings.setValue(AutomaticKitCreationKey, m_automaticKitCreation);
269 settings.setValue(SdkFullyConfiguredKey, m_sdkFullyConfigured);
270 }
271
parseDependenciesJson()272 void AndroidConfig::parseDependenciesJson()
273 {
274 const FilePath sdkConfigUserFile = Core::ICore::userResourcePath(JsonFilePath);
275 const FilePath sdkConfigFile = Core::ICore::resourcePath(JsonFilePath);
276
277 if (!sdkConfigUserFile.exists()) {
278 QDir(sdkConfigUserFile.toFileInfo().absolutePath()).mkpath(".");
279 QFile::copy(sdkConfigFile.toString(), sdkConfigUserFile.toString());
280 }
281
282 if (sdkConfigFile.lastModified() > sdkConfigUserFile.lastModified()) {
283 const QString oldUserFile = (sdkConfigUserFile + ".old").toString();
284 QFile::remove(oldUserFile);
285 QFile::rename(sdkConfigUserFile.toString(), oldUserFile);
286 QFile::copy(sdkConfigFile.toString(), sdkConfigUserFile.toString());
287 }
288
289 QFile jsonFile(sdkConfigUserFile.toString());
290 if (!jsonFile.open(QIODevice::ReadOnly)) {
291 qCDebug(avdConfigLog, "Couldn't open JSON config file %s.", qPrintable(jsonFile.fileName()));
292 return;
293 }
294
295 QJsonObject jsonObject = QJsonDocument::fromJson(jsonFile.readAll()).object();
296
297 if (jsonObject.contains(CommonKey) && jsonObject[CommonKey].isObject()) {
298 QJsonObject commonObject = jsonObject[CommonKey].toObject();
299 // Parse SDK Tools URL
300 if (commonObject.contains(SdkToolsUrlKey) && commonObject[SdkToolsUrlKey].isObject()) {
301 QJsonObject sdkToolsObj(commonObject[SdkToolsUrlKey].toObject());
302 if (Utils::HostOsInfo::isMacHost()) {
303 m_sdkToolsUrl = sdkToolsObj[macOsKey].toString();
304 m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["mac_sha256"].toString().toUtf8());
305 } else if (Utils::HostOsInfo::isWindowsHost()) {
306 m_sdkToolsUrl = sdkToolsObj[WindowsOsKey].toString();
307 m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["windows_sha256"].toString().toUtf8());
308 } else {
309 m_sdkToolsUrl = sdkToolsObj[LinuxOsKey].toString();
310 m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["linux_sha256"].toString().toUtf8());
311 }
312 }
313
314 // Parse common essential packages
315 auto appendEssentialsFromArray = [this](QJsonArray array) {
316 for (const QJsonValueRef &pkg : array)
317 m_commonEssentialPkgs.append(pkg.toString());
318 };
319
320 QJsonObject commonEssentials = commonObject[SdkEssentialPkgsKey].toObject();
321 appendEssentialsFromArray(commonEssentials[DefaultVersionKey].toArray());
322
323 if (Utils::HostOsInfo::isWindowsHost())
324 appendEssentialsFromArray(commonEssentials[WindowsOsKey].toArray());
325 if (Utils::HostOsInfo::isMacHost())
326 appendEssentialsFromArray(commonEssentials[macOsKey].toArray());
327 else
328 appendEssentialsFromArray(commonEssentials[LinuxOsKey].toArray());
329 }
330
331 auto fillQtVersionsRange = [](const QString &shortVersion) {
332 QList<QtVersionNumber> versions;
333 QRegularExpression re("([0-9]\\.[0-9]*\\.)\\[([0-9])\\-([0-9])\\]");
334 QRegularExpressionMatch match = re.match(shortVersion);
335 if (match.hasMatch() && match.lastCapturedIndex() == 3)
336 for (int i = match.captured(2).toInt(); i <= match.captured(3).toInt(); ++i)
337 versions.append(QtVersionNumber(match.captured(1) + QString::number(i)));
338 else
339 versions.append(QtVersionNumber(shortVersion));
340
341 return versions;
342 };
343
344 if (jsonObject.contains(SpecificQtVersionsKey) && jsonObject[SpecificQtVersionsKey].isArray()) {
345 const QJsonArray versionsArray = jsonObject[SpecificQtVersionsKey].toArray();
346 for (const QJsonValue &item : versionsArray) {
347 QJsonObject itemObj = item.toObject();
348 SdkForQtVersions specificVersion;
349 specificVersion.ndkPath = itemObj[NdkPathKey].toString();
350 const auto pkgs = itemObj[SdkEssentialPkgsKey].toArray();
351 for (const QJsonValue &pkg : pkgs)
352 specificVersion.essentialPackages.append(pkg.toString());
353 const auto versions = itemObj[VersionsKey].toArray();
354 for (const QJsonValue &pkg : versions)
355 specificVersion.versions.append(fillQtVersionsRange(pkg.toString()));
356
357 if (itemObj[VersionsKey].toArray().first().toString() == DefaultVersionKey)
358 m_defaultSdkDepends = specificVersion;
359 else
360 m_specificQtVersions.append(specificVersion);
361 }
362 }
363 }
364
availableNdkPlatforms(const BaseQtVersion * qtVersion) const365 QVector<int> AndroidConfig::availableNdkPlatforms(const BaseQtVersion *qtVersion) const
366 {
367 QVector<int> availableNdkPlatforms;
368 QDirIterator it(ndkLocation(qtVersion).pathAppended("platforms").toString(),
369 QStringList("android-*"),
370 QDir::Dirs);
371 while (it.hasNext()) {
372 const QString &fileName = it.next();
373 availableNdkPlatforms.push_back(
374 fileName.mid(fileName.lastIndexOf(QLatin1Char('-')) + 1).toInt());
375 }
376 Utils::sort(availableNdkPlatforms, std::greater<>());
377
378 return availableNdkPlatforms;
379 }
380
getCustomNdkList() const381 QStringList AndroidConfig::getCustomNdkList() const
382 {
383 return m_customNdkList;
384 }
385
addCustomNdk(const QString & customNdk)386 void AndroidConfig::addCustomNdk(const QString &customNdk)
387 {
388 if (!m_customNdkList.contains(customNdk))
389 m_customNdkList.append(customNdk);
390 }
391
removeCustomNdk(const QString & customNdk)392 void AndroidConfig::removeCustomNdk(const QString &customNdk)
393 {
394 m_customNdkList.removeAll(customNdk);
395 }
396
openSslLocation() const397 Utils::FilePath AndroidConfig::openSslLocation() const
398 {
399 return m_openSslLocation;
400 }
401
setOpenSslLocation(const Utils::FilePath & openSslLocation)402 void AndroidConfig::setOpenSslLocation(const Utils::FilePath &openSslLocation)
403 {
404 m_openSslLocation = openSslLocation;
405 }
406
apiLevelNamesFor(const SdkPlatformList & platforms)407 QStringList AndroidConfig::apiLevelNamesFor(const SdkPlatformList &platforms)
408 {
409 return Utils::transform(platforms, AndroidConfig::apiLevelNameFor);
410 }
411
apiLevelNameFor(const SdkPlatform * platform)412 QString AndroidConfig::apiLevelNameFor(const SdkPlatform *platform)
413 {
414 return platform && platform->apiLevel() > 0 ?
415 QString("android-%1").arg(platform->apiLevel()) : "";
416 }
417
isCmdlineSdkToolsInstalled() const418 bool AndroidConfig::isCmdlineSdkToolsInstalled() const
419 {
420 QString toolPath("cmdline-tools/latest/bin/sdkmanager");
421 if (HostOsInfo::isWindowsHost())
422 toolPath += ANDROID_BAT_SUFFIX;
423
424 return m_sdkLocation.pathAppended(toolPath).exists();
425 }
426
adbToolPath() const427 FilePath AndroidConfig::adbToolPath() const
428 {
429 return m_sdkLocation / "platform-tools/adb" QTC_HOST_EXE_SUFFIX;
430 }
431
emulatorToolPath() const432 FilePath AndroidConfig::emulatorToolPath() const
433 {
434 QString relativePath = "emulator/emulator";
435 if (sdkToolsVersion() < QVersionNumber(25, 3, 0) && !isCmdlineSdkToolsInstalled())
436 relativePath = "tools/emulator";
437 return m_sdkLocation / (relativePath + QTC_HOST_EXE_SUFFIX);
438 }
439
sdkManagerToolPath() const440 FilePath AndroidConfig::sdkManagerToolPath() const
441 {
442 QStringList sdkmanagerPaths = {"cmdline-tools/latest/bin/sdkmanager",
443 "tools/bin/sdkmanager"};
444
445 for (QString &toolPath : sdkmanagerPaths) {
446 if (HostOsInfo::isWindowsHost())
447 toolPath += ANDROID_BAT_SUFFIX;
448
449 const FilePath sdkmanagerPath = m_sdkLocation / toolPath;
450 if (sdkmanagerPath.exists())
451 return sdkmanagerPath;
452 }
453
454 return FilePath();
455 }
456
avdManagerToolPath() const457 FilePath AndroidConfig::avdManagerToolPath() const
458 {
459 QStringList sdkmanagerPaths = {"cmdline-tools/latest/bin/avdmanager",
460 "tools/bin/avdmanager"};
461
462 for (QString &toolPath : sdkmanagerPaths) {
463 if (HostOsInfo::isWindowsHost())
464 toolPath += ANDROID_BAT_SUFFIX;
465
466 const FilePath sdkmanagerPath = m_sdkLocation / toolPath;
467 if (sdkmanagerPath.exists())
468 return sdkmanagerPath;
469 }
470
471 return FilePath();
472 }
473
toolchainPathFromNdk(const Utils::FilePath & ndkLocation) const474 FilePath AndroidConfig::toolchainPathFromNdk(const Utils::FilePath &ndkLocation) const
475 {
476 const FilePath tcPath = ndkLocation / "toolchains/";
477 FilePath toolchainPath;
478 QDirIterator llvmIter(tcPath.toString(), {"llvm*"}, QDir::Dirs);
479 if (llvmIter.hasNext()) {
480 llvmIter.next();
481 toolchainPath = tcPath / llvmIter.fileName() / "prebuilt/";
482 }
483
484 // detect toolchain host
485 QStringList hostPatterns;
486 switch (HostOsInfo::hostOs()) {
487 case OsTypeLinux:
488 hostPatterns << QLatin1String("linux*");
489 break;
490 case OsTypeWindows:
491 hostPatterns << QLatin1String("windows*");
492 break;
493 case OsTypeMac:
494 hostPatterns << QLatin1String("darwin*");
495 break;
496 default: /* unknown host */ return FilePath();
497 }
498
499 QDirIterator iter(toolchainPath.toString(), hostPatterns, QDir::Dirs);
500 if (iter.hasNext()) {
501 iter.next();
502 return toolchainPath / iter.fileName();
503 }
504
505 return {};
506 }
507
toolchainPath(const BaseQtVersion * qtVersion) const508 FilePath AndroidConfig::toolchainPath(const BaseQtVersion *qtVersion) const
509 {
510 return toolchainPathFromNdk(ndkLocation(qtVersion));
511 }
512
clangPathFromNdk(const Utils::FilePath & ndkLocation) const513 FilePath AndroidConfig::clangPathFromNdk(const Utils::FilePath &ndkLocation) const
514 {
515 const FilePath path = toolchainPathFromNdk(ndkLocation);
516 if (path.isEmpty())
517 return {};
518 return path / HostOsInfo::withExecutableSuffix("bin/clang");
519 }
520
gdbPath(const ProjectExplorer::Abi & abi,const BaseQtVersion * qtVersion) const521 FilePath AndroidConfig::gdbPath(const ProjectExplorer::Abi &abi, const BaseQtVersion *qtVersion) const
522 {
523 return gdbPathFromNdk(abi, ndkLocation(qtVersion));
524 }
525
gdbPathFromNdk(const Abi & abi,const FilePath & ndkLocation) const526 FilePath AndroidConfig::gdbPathFromNdk(const Abi &abi, const FilePath &ndkLocation) const
527 {
528 const FilePath path = ndkLocation.pathAppended(
529 QString("prebuilt/%1/bin/gdb%2").arg(toolchainHostFromNdk(ndkLocation),
530 QString(QTC_HOST_EXE_SUFFIX)));
531 if (path.exists())
532 return path;
533 // fallback for old NDKs (e.g. 10e)
534 return ndkLocation.pathAppended(QString("toolchains/%1-4.9/prebuilt/%2/bin/%3-gdb%4")
535 .arg(toolchainPrefix(abi),
536 toolchainHostFromNdk(ndkLocation),
537 toolsPrefix(abi),
538 QString(QTC_HOST_EXE_SUFFIX)));
539 }
540
makePathFromNdk(const FilePath & ndkLocation) const541 FilePath AndroidConfig::makePathFromNdk(const FilePath &ndkLocation) const
542 {
543 return ndkLocation.pathAppended(
544 QString("prebuilt/%1/bin/make%2").arg(toolchainHostFromNdk(ndkLocation),
545 QString(QTC_HOST_EXE_SUFFIX)));
546 }
547
openJDKBinPath() const548 FilePath AndroidConfig::openJDKBinPath() const
549 {
550 const FilePath path = m_openJDKLocation;
551 if (!path.isEmpty())
552 return path.pathAppended("bin");
553 return path;
554 }
555
keytoolPath() const556 FilePath AndroidConfig::keytoolPath() const
557 {
558 return openJDKBinPath().pathAppended(keytoolName);
559 }
560
connectedDevices(QString * error) const561 QVector<AndroidDeviceInfo> AndroidConfig::connectedDevices(QString *error) const
562 {
563 return connectedDevices(adbToolPath(), error);
564 }
565
connectedDevices(const FilePath & adbToolPath,QString * error)566 QVector<AndroidDeviceInfo> AndroidConfig::connectedDevices(const FilePath &adbToolPath, QString *error)
567 {
568 QVector<AndroidDeviceInfo> devices;
569 QtcProcess adbProc;
570 adbProc.setTimeoutS(30);
571 CommandLine cmd{adbToolPath, {"devices"}};
572 adbProc.setCommand(cmd);
573 adbProc.runBlocking();
574 if (adbProc.result() != QtcProcess::FinishedWithSuccess) {
575 if (error)
576 *error = QApplication::translate("AndroidConfiguration", "Could not run: %1")
577 .arg(cmd.toUserOutput());
578 return devices;
579 }
580 QStringList adbDevs = adbProc.allOutput().split('\n', Qt::SkipEmptyParts);
581 if (adbDevs.empty())
582 return devices;
583
584 for (const QString &line : adbDevs) // remove the daemon logs
585 if (line.startsWith("* daemon"))
586 adbDevs.removeOne(line);
587 adbDevs.removeFirst(); // remove "List of devices attached" header line
588
589 // workaround for '????????????' serial numbers:
590 // can use "adb -d" when only one usb device attached
591 foreach (const QString &device, adbDevs) {
592 const QString serialNo = device.left(device.indexOf('\t')).trimmed();
593 const QString deviceType = device.mid(device.indexOf('\t')).trimmed();
594 AndroidDeviceInfo dev;
595 dev.serialNumber = serialNo;
596 dev.type = serialNo.startsWith(QLatin1String("emulator")) ? AndroidDeviceInfo::Emulator : AndroidDeviceInfo::Hardware;
597 dev.sdk = getSDKVersion(adbToolPath, dev.serialNumber);
598 dev.cpuAbi = getAbis(adbToolPath, dev.serialNumber);
599 if (deviceType == QLatin1String("unauthorized"))
600 dev.state = AndroidDeviceInfo::UnAuthorizedState;
601 else if (deviceType == QLatin1String("offline"))
602 dev.state = AndroidDeviceInfo::OfflineState;
603 else
604 dev.state = AndroidDeviceInfo::OkState;
605
606 if (dev.type == AndroidDeviceInfo::Emulator) {
607 dev.avdname = getAvdName(dev.serialNumber);
608 if (dev.avdname.isEmpty())
609 dev.avdname = serialNo;
610 }
611
612 devices.push_back(dev);
613 }
614
615 Utils::sort(devices);
616 if (devices.isEmpty() && error)
617 *error = QApplication::translate("AndroidConfiguration",
618 "No devices found in output of: %1")
619 .arg(cmd.toUserOutput());
620 return devices;
621 }
622
isConnected(const QString & serialNumber) const623 bool AndroidConfig::isConnected(const QString &serialNumber) const
624 {
625 QVector<AndroidDeviceInfo> devices = connectedDevices();
626 foreach (AndroidDeviceInfo device, devices) {
627 if (device.serialNumber == serialNumber)
628 return true;
629 }
630 return false;
631 }
632
getDeviceProperty(const FilePath & adbToolPath,const QString & device,const QString & property)633 QString AndroidConfig::getDeviceProperty(const FilePath &adbToolPath, const QString &device, const QString &property)
634 {
635 // workaround for '????????????' serial numbers
636 CommandLine cmd(adbToolPath, AndroidDeviceInfo::adbSelector(device));
637 cmd.addArgs({"shell", "getprop", property});
638
639 QtcProcess adbProc;
640 adbProc.setTimeoutS(10);
641 adbProc.setCommand(cmd);
642 adbProc.runBlocking();
643 if (adbProc.result() != QtcProcess::FinishedWithSuccess)
644 return QString();
645
646 return adbProc.allOutput();
647 }
648
getSDKVersion(const FilePath & adbToolPath,const QString & device)649 int AndroidConfig::getSDKVersion(const FilePath &adbToolPath, const QString &device)
650 {
651 QString tmp = getDeviceProperty(adbToolPath, device, "ro.build.version.sdk");
652 if (tmp.isEmpty())
653 return -1;
654 return tmp.trimmed().toInt();
655 }
656
getAvdName(const QString & serialnumber)657 QString AndroidConfig::getAvdName(const QString &serialnumber)
658 {
659 int index = serialnumber.indexOf(QLatin1String("-"));
660 if (index == -1)
661 return QString();
662 bool ok;
663 int port = serialnumber.mid(index + 1).toInt(&ok);
664 if (!ok)
665 return QString();
666
667 const QByteArray avdName = "avd name\n";
668
669 QTcpSocket tcpSocket;
670 tcpSocket.connectToHost(QHostAddress(QHostAddress::LocalHost), port);
671 if (!tcpSocket.waitForConnected(100)) // Don't wait more than 100ms for a local connection
672 return QString{};
673
674 tcpSocket.write(avdName + "exit\n");
675 tcpSocket.waitForDisconnected(500);
676
677 QByteArray name;
678 const QByteArrayList response = tcpSocket.readAll().split('\n');
679 // The input "avd name" might not be echoed as-is, but contain ASCII
680 // control sequences.
681 for (int i = response.size() - 1; i > 1; --i) {
682 if (response.at(i).startsWith("OK")) {
683 name = response.at(i - 1);
684 break;
685 }
686 }
687 return QString::fromLatin1(name).trimmed();
688 }
689
getOpenGLEnabled(const QString & emulator) const690 AndroidConfig::OpenGl AndroidConfig::getOpenGLEnabled(const QString &emulator) const
691 {
692 QDir dir = QDir::home();
693 if (!dir.cd(QLatin1String(".android")))
694 return OpenGl::Unknown;
695 if (!dir.cd(QLatin1String("avd")))
696 return OpenGl::Unknown;
697 if (!dir.cd(emulator + QLatin1String(".avd")))
698 return OpenGl::Unknown;
699 QFile file(dir.filePath(QLatin1String("config.ini")));
700 if (!file.exists())
701 return OpenGl::Unknown;
702 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
703 return OpenGl::Unknown;
704 while (!file.atEnd()) {
705 QByteArray line = file.readLine();
706 if (line.contains("hw.gpu.enabled") && line.contains("yes"))
707 return OpenGl::Enabled;
708 }
709 return OpenGl::Disabled;
710 }
711
712 //!
713 //! \brief AndroidConfigurations::getProductModel
714 //! \param device serial number
715 //! \return the produce model of the device or if that cannot be read the serial number
716 //!
getProductModel(const QString & device) const717 QString AndroidConfig::getProductModel(const QString &device) const
718 {
719 if (m_serialNumberToDeviceName.contains(device))
720 return m_serialNumberToDeviceName.value(device);
721
722 QString model = getDeviceProperty(adbToolPath(), device, "ro.product.model").trimmed();
723 if (model.isEmpty())
724 return device;
725
726 if (!device.startsWith(QLatin1String("????")))
727 m_serialNumberToDeviceName.insert(device, model);
728 return model;
729 }
730
getAbis(const FilePath & adbToolPath,const QString & device)731 QStringList AndroidConfig::getAbis(const FilePath &adbToolPath, const QString &device)
732 {
733 QStringList result;
734 // First try via ro.product.cpu.abilist
735 QStringList arguments = AndroidDeviceInfo::adbSelector(device);
736 arguments << "shell" << "getprop" << "ro.product.cpu.abilist";
737 QtcProcess adbProc;
738 adbProc.setTimeoutS(10);
739 adbProc.setCommand({adbToolPath, arguments});
740 adbProc.runBlocking();
741 if (adbProc.result() != QtcProcess::FinishedWithSuccess)
742 return result;
743
744 QString output = adbProc.allOutput().trimmed();
745 if (!output.isEmpty()) {
746 QStringList result = output.split(QLatin1Char(','));
747 if (!result.isEmpty())
748 return result;
749 }
750
751 // Fall back to ro.product.cpu.abi, ro.product.cpu.abi2 ...
752 for (int i = 1; i < 6; ++i) {
753 QStringList arguments = AndroidDeviceInfo::adbSelector(device);
754 arguments << QLatin1String("shell") << QLatin1String("getprop");
755 if (i == 1)
756 arguments << QLatin1String("ro.product.cpu.abi");
757 else
758 arguments << QString::fromLatin1("ro.product.cpu.abi%1").arg(i);
759
760 QtcProcess abiProc;
761 abiProc.setTimeoutS(10);
762 abiProc.setCommand({adbToolPath, arguments});
763 abiProc.runBlocking();
764 if (abiProc.result() != QtcProcess::FinishedWithSuccess)
765 return result;
766
767 QString abi = abiProc.allOutput().trimmed();
768 if (abi.isEmpty())
769 break;
770 result << abi;
771 }
772 return result;
773 }
774
isValidNdk(const QString & ndkLocation) const775 bool AndroidConfig::isValidNdk(const QString &ndkLocation) const
776 {
777 auto ndkPath = Utils::FilePath::fromUserInput(ndkLocation);
778 const Utils::FilePath ndkPlatformsDir = ndkPath.pathAppended("platforms");
779
780 return ndkPath.exists() && ndkPath.pathAppended("toolchains").exists()
781 && ndkPlatformsDir.exists() && !ndkPlatformsDir.toString().contains(' ')
782 && !ndkVersion(ndkPath).isNull();
783 }
784
bestNdkPlatformMatch(int target,const BaseQtVersion * qtVersion) const785 QString AndroidConfig::bestNdkPlatformMatch(int target, const BaseQtVersion *qtVersion) const
786 {
787 target = std::max(AndroidManager::defaultMinimumSDK(qtVersion), target);
788 foreach (int apiLevel, availableNdkPlatforms(qtVersion)) {
789 if (apiLevel <= target)
790 return QString::fromLatin1("android-%1").arg(apiLevel);
791 }
792 return QString("android-%1").arg(AndroidManager::defaultMinimumSDK(qtVersion));
793 }
794
sdkLocation() const795 FilePath AndroidConfig::sdkLocation() const
796 {
797 return m_sdkLocation;
798 }
799
setSdkLocation(const FilePath & sdkLocation)800 void AndroidConfig::setSdkLocation(const FilePath &sdkLocation)
801 {
802 m_sdkLocation = sdkLocation;
803 }
804
sdkToolsVersion() const805 QVersionNumber AndroidConfig::sdkToolsVersion() const
806 {
807 QVersionNumber version;
808 if (m_sdkLocation.exists()) {
809 FilePath sdkToolsPropertiesPath;
810 if (isCmdlineSdkToolsInstalled())
811 sdkToolsPropertiesPath = m_sdkLocation / "cmdline-tools/latest/source.properties";
812 else
813 sdkToolsPropertiesPath = m_sdkLocation / "tools/source.properties";
814 QSettings settings(sdkToolsPropertiesPath.toString(), QSettings::IniFormat);
815 auto versionStr = settings.value(sdkToolsVersionKey).toString();
816 version = QVersionNumber::fromString(versionStr);
817 }
818 return version;
819 }
820
buildToolsVersion() const821 QVersionNumber AndroidConfig::buildToolsVersion() const
822 {
823 //TODO: return version according to qt version
824 QVersionNumber maxVersion;
825 QDir buildToolsDir(m_sdkLocation.pathAppended("build-tools").toString());
826 const auto files = buildToolsDir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
827 for (const QFileInfo &file: files)
828 maxVersion = qMax(maxVersion, QVersionNumber::fromString(file.fileName()));
829 return maxVersion;
830 }
831
sdkManagerToolArgs() const832 QStringList AndroidConfig::sdkManagerToolArgs() const
833 {
834 return m_sdkManagerToolArgs;
835 }
836
setSdkManagerToolArgs(const QStringList & args)837 void AndroidConfig::setSdkManagerToolArgs(const QStringList &args)
838 {
839 m_sdkManagerToolArgs = args;
840 }
841
ndkLocation(const BaseQtVersion * qtVersion) const842 FilePath AndroidConfig::ndkLocation(const BaseQtVersion *qtVersion) const
843 {
844 return sdkLocation().pathAppended(ndkPathFromQtVersion(*qtVersion));
845 }
846
ndkVersion(const BaseQtVersion * qtVersion) const847 QVersionNumber AndroidConfig::ndkVersion(const BaseQtVersion *qtVersion) const
848 {
849 return ndkVersion(ndkLocation(qtVersion));
850 }
851
ndkVersion(const FilePath & ndkPath) const852 QVersionNumber AndroidConfig::ndkVersion(const FilePath &ndkPath) const
853 {
854 QVersionNumber version;
855 if (!ndkPath.exists()) {
856 qCDebug(avdConfigLog) << "Cannot find ndk version. Check NDK path."
857 << ndkPath.toString();
858 return version;
859 }
860
861 const FilePath ndkPropertiesPath = ndkPath.pathAppended("source.properties");
862 if (ndkPropertiesPath.exists()) {
863 // source.properties files exists in NDK version > 11
864 QSettings settings(ndkPropertiesPath.toString(), QSettings::IniFormat);
865 auto versionStr = settings.value(ndkRevisionKey).toString();
866 version = QVersionNumber::fromString(versionStr);
867 } else {
868 // No source.properties. There should be a file named RELEASE.TXT
869 const FilePath ndkReleaseTxtPath = ndkPath.pathAppended("RELEASE.TXT");
870 Utils::FileReader reader;
871 QString errorString;
872 if (reader.fetch(ndkReleaseTxtPath, &errorString)) {
873 // RELEASE.TXT contains the ndk version in either of the following formats:
874 // r6a
875 // r10e (64 bit)
876 QString content = QString::fromUtf8(reader.data());
877 QRegularExpression re("(r)(?<major>[0-9]{1,2})(?<minor>[a-z]{1,1})");
878 QRegularExpressionMatch match = re.match(content);
879 if (match.hasMatch()) {
880 QString major = match.captured("major");
881 QString minor = match.captured("minor");
882 // Minor version: a = 0, b = 1, c = 2 and so on.
883 // Int equivalent = minorVersionChar - 'a'. i.e. minorVersionChar - 97.
884 version = QVersionNumber::fromString(QString("%1.%2.0").arg(major)
885 .arg((int)minor[0].toLatin1() - 97));
886 } else {
887 qCDebug(avdConfigLog) << "Cannot find ndk version. Cannot parse RELEASE.TXT."
888 << content;
889 }
890 } else {
891 qCDebug(avdConfigLog) << "Cannot find ndk version." << errorString;
892 }
893 }
894 return version;
895 }
896
allEssentials() const897 QStringList AndroidConfig::allEssentials() const
898 {
899 QList<BaseQtVersion *> installedVersions = QtVersionManager::versions(
900 [](const BaseQtVersion *v) {
901 return v->targetDeviceTypes().contains(Android::Constants::ANDROID_DEVICE_TYPE);
902 });
903
904 QStringList allPackages(defaultEssentials());
905 for (const BaseQtVersion *version : installedVersions)
906 allPackages.append(essentialsFromQtVersion(*version));
907 allPackages.removeDuplicates();
908
909 return allPackages;
910 }
911
allEssentialsInstalled(AndroidSdkManager * sdkManager)912 bool AndroidConfig::allEssentialsInstalled(AndroidSdkManager *sdkManager)
913 {
914 QStringList essentialPkgs(allEssentials());
915 const auto installedPkgs = sdkManager->installedSdkPackages();
916 for (const AndroidSdkPackage *pkg : installedPkgs) {
917 if (essentialPkgs.contains(pkg->sdkStylePath()))
918 essentialPkgs.removeOne(pkg->sdkStylePath());
919 if (essentialPkgs.isEmpty())
920 break;
921 }
922 return essentialPkgs.isEmpty() ? true : false;
923 }
924
sdkToolsOk() const925 bool AndroidConfig::sdkToolsOk() const
926 {
927 bool exists = sdkLocation().exists();
928 bool writable = sdkLocation().isWritablePath();
929 bool sdkToolsExist = !sdkToolsVersion().isNull();
930 return exists && writable && sdkToolsExist;
931 }
932
essentialsFromQtVersion(const BaseQtVersion & version) const933 QStringList AndroidConfig::essentialsFromQtVersion(const BaseQtVersion &version) const
934 {
935 QtVersionNumber qtVersion = version.qtVersion();
936 for (const SdkForQtVersions &item : m_specificQtVersions)
937 if (item.containsVersion(qtVersion))
938 return item.essentialPackages;
939
940 return m_defaultSdkDepends.essentialPackages;
941 }
942
ndkPathFromQtVersion(const BaseQtVersion & version) const943 QString AndroidConfig::ndkPathFromQtVersion(const BaseQtVersion &version) const
944 {
945 QtVersionNumber qtVersion(version.qtVersionString());
946 for (const SdkForQtVersions &item : m_specificQtVersions)
947 if (item.containsVersion(qtVersion))
948 return item.ndkPath;
949
950 return m_defaultSdkDepends.ndkPath;
951 }
952
defaultEssentials() const953 QStringList AndroidConfig::defaultEssentials() const
954 {
955 return m_defaultSdkDepends.essentialPackages + m_commonEssentialPkgs;
956 }
957
containsVersion(const QtVersionNumber & qtVersion) const958 bool SdkForQtVersions::containsVersion(const QtVersionNumber &qtVersion) const
959 {
960 return versions.contains(qtVersion)
961 || versions.contains(QtVersionNumber(qtVersion.majorVersion, qtVersion.minorVersion));
962 }
963
openJDKLocation() const964 FilePath AndroidConfig::openJDKLocation() const
965 {
966 return m_openJDKLocation;
967 }
968
setOpenJDKLocation(const FilePath & openJDKLocation)969 void AndroidConfig::setOpenJDKLocation(const FilePath &openJDKLocation)
970 {
971 m_openJDKLocation = openJDKLocation;
972 }
973
toolchainHost(const BaseQtVersion * qtVersion) const974 QString AndroidConfig::toolchainHost(const BaseQtVersion *qtVersion) const
975 {
976 return toolchainHostFromNdk(ndkLocation(qtVersion));
977 }
978
toolchainHostFromNdk(const FilePath & ndkPath) const979 QString AndroidConfig::toolchainHostFromNdk(const FilePath &ndkPath) const
980 {
981 // detect toolchain host
982 QString toolchainHost;
983 QStringList hostPatterns;
984 switch (HostOsInfo::hostOs()) {
985 case OsTypeLinux:
986 hostPatterns << QLatin1String("linux*");
987 break;
988 case OsTypeWindows:
989 hostPatterns << QLatin1String("windows*");
990 break;
991 case OsTypeMac:
992 hostPatterns << QLatin1String("darwin*");
993 break;
994 default: /* unknown host */
995 return toolchainHost;
996 }
997
998 QDirIterator jt(ndkPath.pathAppended("prebuilt").toString(),
999 hostPatterns,
1000 QDir::Dirs);
1001 if (jt.hasNext()) {
1002 jt.next();
1003 toolchainHost = jt.fileName();
1004 }
1005
1006 return toolchainHost;
1007 }
1008
emulatorArgs() const1009 QStringList AndroidConfig::emulatorArgs() const
1010 {
1011 return m_emulatorArgs;
1012 }
1013
setEmulatorArgs(const QStringList & args)1014 void AndroidConfig::setEmulatorArgs(const QStringList &args)
1015 {
1016 m_emulatorArgs = args;
1017 }
1018
automaticKitCreation() const1019 bool AndroidConfig::automaticKitCreation() const
1020 {
1021 return m_automaticKitCreation;
1022 }
1023
setAutomaticKitCreation(bool b)1024 void AndroidConfig::setAutomaticKitCreation(bool b)
1025 {
1026 m_automaticKitCreation = b;
1027 }
1028
defaultSdkPath()1029 FilePath AndroidConfig::defaultSdkPath()
1030 {
1031 QString sdkFromEnvVar = QString::fromLocal8Bit(getenv("ANDROID_SDK_ROOT"));
1032 if (!sdkFromEnvVar.isEmpty())
1033 return FilePath::fromUserInput(sdkFromEnvVar).cleanPath();
1034
1035 // Set default path of SDK as used by Android Studio
1036 if (Utils::HostOsInfo::isMacHost()) {
1037 return Utils::FilePath::fromString(
1038 QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/Library/Android/sdk");
1039 }
1040
1041 if (Utils::HostOsInfo::isWindowsHost()) {
1042 return Utils::FilePath::fromString(
1043 QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/Android/Sdk");
1044 }
1045
1046 return Utils::FilePath::fromString(
1047 QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/Android/Sdk");
1048 }
1049
1050 ///////////////////////////////////
1051 // AndroidConfigurations
1052 ///////////////////////////////////
setConfig(const AndroidConfig & devConfigs)1053 void AndroidConfigurations::setConfig(const AndroidConfig &devConfigs)
1054 {
1055 emit m_instance->aboutToUpdate();
1056 m_instance->m_config = devConfigs;
1057
1058 m_instance->save();
1059 updateAndroidDevice();
1060 registerNewToolChains();
1061 updateAutomaticKitList();
1062 removeOldToolChains();
1063 emit m_instance->updated();
1064 }
1065
showDeviceDialog(Project * project,int apiLevel,const QStringList & abis)1066 AndroidDeviceInfo AndroidConfigurations::showDeviceDialog(Project *project,
1067 int apiLevel, const QStringList &abis)
1068 {
1069 QString serialNumber;
1070 for (const QString &abi : abis) {
1071 serialNumber = defaultDevice(project, abi);
1072 if (!serialNumber.isEmpty())
1073 break;
1074 }
1075
1076 const AndroidDeviceInfo defaultDevice = AndroidDeviceDialog::defaultDeviceInfo(serialNumber);
1077 if (defaultDevice.isValid())
1078 return defaultDevice;
1079
1080 AndroidDeviceDialog dialog(apiLevel, abis, serialNumber, Core::ICore::dialogParent());
1081 AndroidDeviceInfo info = dialog.showAndGetSelectedDevice();
1082 if (dialog.saveDeviceSelection() && info.isValid()) {
1083 const QString newSerialNumber = info.type == AndroidDeviceInfo::Hardware ?
1084 info.serialNumber : info.avdname;
1085 if (!newSerialNumber.isEmpty()) {
1086 const QString preferredAbi = AndroidManager::devicePreferredAbi(info.cpuAbi, abis);
1087 AndroidConfigurations::setDefaultDevice(project, preferredAbi, newSerialNumber);
1088 }
1089 }
1090 return info;
1091 }
1092
clearDefaultDevices(Project * project)1093 void AndroidConfigurations::clearDefaultDevices(Project *project)
1094 {
1095 if (m_instance->m_defaultDeviceForAbi.contains(project))
1096 m_instance->m_defaultDeviceForAbi.remove(project);
1097 }
1098
setDefaultDevice(Project * project,const QString & abi,const QString & serialNumber)1099 void AndroidConfigurations::setDefaultDevice(Project *project, const QString &abi, const QString &serialNumber)
1100 {
1101 m_instance->m_defaultDeviceForAbi[project][abi] = serialNumber;
1102 }
1103
defaultDevice(Project * project,const QString & abi)1104 QString AndroidConfigurations::defaultDevice(Project *project, const QString &abi)
1105 {
1106 if (!m_instance->m_defaultDeviceForAbi.contains(project))
1107 return QString();
1108 const QMap<QString, QString> &map = m_instance->m_defaultDeviceForAbi.value(project);
1109 if (!map.contains(abi))
1110 return QString();
1111 return map.value(abi);
1112 }
1113
matchToolChain(const ToolChain * atc,const ToolChain * btc)1114 static bool matchToolChain(const ToolChain *atc, const ToolChain *btc)
1115 {
1116 if (atc == btc)
1117 return true;
1118
1119 if (!atc || !btc)
1120 return false;
1121
1122 if (atc->typeId() != Constants::ANDROID_TOOLCHAIN_TYPEID || btc->typeId() != Constants::ANDROID_TOOLCHAIN_TYPEID)
1123 return false;
1124
1125 auto aatc = static_cast<const AndroidToolChain *>(atc);
1126 auto abtc = static_cast<const AndroidToolChain *>(btc);
1127 return aatc->targetAbi() == abtc->targetAbi();
1128 }
1129
registerNewToolChains()1130 void AndroidConfigurations::registerNewToolChains()
1131 {
1132 const QList<ToolChain *> existingAndroidToolChains
1133 = ToolChainManager::toolChains(Utils::equal(&ToolChain::typeId,
1134 Utils::Id(Constants::ANDROID_TOOLCHAIN_TYPEID)));
1135 QList<ToolChain *> newToolchains = AndroidToolChainFactory::autodetectToolChains(
1136 existingAndroidToolChains);
1137
1138 foreach (ToolChain *tc, newToolchains)
1139 ToolChainManager::registerToolChain(tc);
1140
1141 registerCustomToolChainsAndDebuggers();
1142 }
1143
removeOldToolChains()1144 void AndroidConfigurations::removeOldToolChains()
1145 {
1146 foreach (ToolChain *tc, ToolChainManager::toolChains(Utils::equal(&ToolChain::typeId, Utils::Id(Constants::ANDROID_TOOLCHAIN_TYPEID)))) {
1147 if (!tc->isValid())
1148 ToolChainManager::deregisterToolChain(tc);
1149 }
1150 }
1151
removeUnusedDebuggers()1152 void AndroidConfigurations::removeUnusedDebuggers()
1153 {
1154 const QList<QtSupport::BaseQtVersion *> qtVersions = QtSupport::QtVersionManager::versions(
1155 [](const QtSupport::BaseQtVersion *v) {
1156 return v->type() == Constants::ANDROIDQT;
1157 });
1158
1159 QVector<FilePath> uniqueNdks;
1160 for (const QtSupport::BaseQtVersion *qt : qtVersions) {
1161 FilePath ndkLocation = currentConfig().ndkLocation(qt);
1162 if (!uniqueNdks.contains(ndkLocation))
1163 uniqueNdks.append(ndkLocation);
1164 }
1165
1166 uniqueNdks.append(Utils::transform(currentConfig().getCustomNdkList(),
1167 FilePath::fromString).toVector());
1168
1169 const QList<Debugger::DebuggerItem> allDebuggers = Debugger::DebuggerItemManager::debuggers();
1170 for (const Debugger::DebuggerItem &debugger : allDebuggers) {
1171 if (!debugger.displayName().contains("Android"))
1172 continue;
1173
1174 bool isChildOfNdk = false;
1175 for (const FilePath &path : uniqueNdks) {
1176 if (debugger.command().isChildOf(path)) {
1177 isChildOfNdk = true;
1178 break;
1179 }
1180 }
1181
1182 const bool isMultiAbiNdkGdb = debugger.command().fileName().startsWith("gdb");
1183 const bool hasMultiAbiName = debugger.displayName().contains("Multi-Abi");
1184
1185 if (debugger.isAutoDetected() && (!isChildOfNdk || (isMultiAbiNdkGdb && !hasMultiAbiName)))
1186 Debugger::DebuggerItemManager::deregisterDebugger(debugger.id());
1187 }
1188 }
1189
allSupportedAbis()1190 static QStringList allSupportedAbis()
1191 {
1192 return QStringList{
1193 ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A,
1194 ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A,
1195 ProjectExplorer::Constants::ANDROID_ABI_X86,
1196 ProjectExplorer::Constants::ANDROID_ABI_X86_64,
1197 };
1198 }
1199
containsAllAbis(const QStringList & abis)1200 static bool containsAllAbis(const QStringList &abis)
1201 {
1202 QStringList supportedAbis{allSupportedAbis()};
1203 for (const QString &abi : abis)
1204 if (supportedAbis.contains(abi))
1205 supportedAbis.removeOne(abi);
1206
1207 return supportedAbis.isEmpty();
1208 }
1209
getMultiOrSingleAbiString(const QStringList & abis)1210 static QString getMultiOrSingleAbiString(const QStringList &abis)
1211 {
1212 return containsAllAbis(abis) ? "Multi-Abi" : abis.join(",");
1213 }
1214
findOrRegisterDebugger(ToolChain * tc,const QStringList & abisList,bool customDebugger=false)1215 static QVariant findOrRegisterDebugger(ToolChain *tc,
1216 const QStringList &abisList,
1217 bool customDebugger = false)
1218 {
1219 const auto ¤tConfig = AndroidConfigurations::currentConfig();
1220 const FilePath ndk = static_cast<AndroidToolChain *>(tc)->ndkLocation();
1221 const FilePath command = currentConfig.gdbPathFromNdk(tc->targetAbi(), ndk);
1222
1223 // check if the debugger is already registered, but ignoring the display name
1224 const Debugger::DebuggerItem *existing = Debugger::DebuggerItemManager::findByCommand(command);
1225
1226 // Return existing debugger with same command
1227 if (existing && existing->engineType() == Debugger::GdbEngineType
1228 && existing->isAutoDetected()) {
1229 return existing->id();
1230 }
1231
1232 // debugger not found, register a new one
1233 Debugger::DebuggerItem debugger;
1234 debugger.setCommand(command);
1235 debugger.setEngineType(Debugger::GdbEngineType);
1236
1237 // NDK 10 and older have multiple gdb versions per ABI, so check for that.
1238 const bool oldNdkVersion = currentConfig.ndkVersion(ndk) <= QVersionNumber{11};
1239 QString mainName = AndroidConfigurations::tr("Android Debugger (%1, NDK %2)");
1240 if (customDebugger)
1241 mainName.prepend("Custom ");
1242 debugger.setUnexpandedDisplayName(mainName
1243 .arg(getMultiOrSingleAbiString(oldNdkVersion ? abisList : allSupportedAbis()))
1244 .arg(AndroidConfigurations::currentConfig().ndkVersion(ndk).toString()));
1245 debugger.setAutoDetected(true);
1246 debugger.reinitializeFromFile();
1247 return Debugger::DebuggerItemManager::registerDebugger(debugger);
1248 }
1249
registerCustomToolChainsAndDebuggers()1250 void AndroidConfigurations::registerCustomToolChainsAndDebuggers()
1251 {
1252 const QList<ToolChain *> existingAndroidToolChains = ToolChainManager::toolChains(
1253 Utils::equal(&ToolChain::typeId, Utils::Id(Constants::ANDROID_TOOLCHAIN_TYPEID)));
1254 QList<FilePath> customNdks = Utils::transform(currentConfig().getCustomNdkList(),
1255 FilePath::fromString);
1256 QList<ToolChain *> customToolchains
1257 = AndroidToolChainFactory::autodetectToolChainsFromNdks(existingAndroidToolChains,
1258 customNdks,
1259 true);
1260 for (ToolChain *tc : customToolchains) {
1261 ToolChainManager::registerToolChain(tc);
1262 const auto androidToolChain = static_cast<AndroidToolChain *>(tc);
1263 QString abiStr;
1264 if (androidToolChain)
1265 abiStr = androidToolChain->platformLinkerFlags().at(1).split('-').first();
1266 findOrRegisterDebugger(tc, {abiStr}, true);
1267 }
1268 }
updateAutomaticKitList()1269 void AndroidConfigurations::updateAutomaticKitList()
1270 {
1271 for (Kit *k : KitManager::kits()) {
1272 if (DeviceTypeKitAspect::deviceTypeId(k) == Constants::ANDROID_DEVICE_TYPE) {
1273 if (k->value(Constants::ANDROID_KIT_NDK).isNull() || k->value(Constants::ANDROID_KIT_SDK).isNull()) {
1274 if (BaseQtVersion *qt = QtKitAspect::qtVersion(k)) {
1275 k->setValueSilently(Constants::ANDROID_KIT_NDK, currentConfig().ndkLocation(qt).toString());
1276 k->setValue(Constants::ANDROID_KIT_SDK, currentConfig().sdkLocation().toString());
1277 }
1278 }
1279 }
1280 }
1281
1282 const QList<Kit *> existingKits = Utils::filtered(KitManager::kits(), [](Kit *k) {
1283 Utils::Id deviceTypeId = DeviceTypeKitAspect::deviceTypeId(k);
1284 if (k->isAutoDetected() && !k->isSdkProvided()
1285 && deviceTypeId == Utils::Id(Constants::ANDROID_DEVICE_TYPE)) {
1286 return true;
1287 }
1288 return false;
1289 });
1290
1291 removeUnusedDebuggers();
1292
1293 QHash<Abi, QList<const QtSupport::BaseQtVersion *> > qtVersionsForArch;
1294 const QList<QtSupport::BaseQtVersion *> qtVersions
1295 = QtSupport::QtVersionManager::versions([](const QtSupport::BaseQtVersion *v) {
1296 return v->type() == Constants::ANDROIDQT;
1297 });
1298 for (const QtSupport::BaseQtVersion *qtVersion : qtVersions) {
1299 const Abis qtAbis = qtVersion->qtAbis();
1300 if (qtAbis.empty())
1301 continue;
1302 qtVersionsForArch[qtAbis.first()].append(qtVersion);
1303 }
1304
1305 DeviceManager *dm = DeviceManager::instance();
1306 IDevice::ConstPtr device = dm->find(Utils::Id(Constants::ANDROID_DEVICE_ID));
1307 if (device.isNull()) {
1308 // no device, means no sdk path
1309 for (Kit *k : existingKits)
1310 KitManager::deregisterKit(k);
1311 return;
1312 }
1313
1314 // register new kits
1315 const QList<ToolChain *> toolchains = ToolChainManager::toolChains([](const ToolChain *tc) {
1316 return tc->isAutoDetected()
1317 && tc->isValid()
1318 && tc->typeId() == Constants::ANDROID_TOOLCHAIN_TYPEID;
1319 });
1320 QList<Kit *> unhandledKits = existingKits;
1321 for (ToolChain *tc : toolchains) {
1322 if (tc->language() != Utils::Id(ProjectExplorer::Constants::CXX_LANGUAGE_ID))
1323 continue;
1324
1325 for (const QtSupport::BaseQtVersion *qt : qtVersionsForArch.value(tc->targetAbi())) {
1326 FilePath tcNdk = static_cast<const AndroidToolChain *>(tc)->ndkLocation();
1327 if (tcNdk != currentConfig().ndkLocation(qt))
1328 continue;
1329
1330 const QList<ToolChain *> allLanguages
1331 = Utils::filtered(toolchains, [tc, tcNdk](ToolChain *otherTc) {
1332 FilePath otherNdk = static_cast<const AndroidToolChain *>(otherTc)->ndkLocation();
1333 return tc->targetAbi() == otherTc->targetAbi() && tcNdk == otherNdk;
1334 });
1335
1336 QHash<Utils::Id, ToolChain *> toolChainForLanguage;
1337 for (ToolChain *tc : allLanguages)
1338 toolChainForLanguage[tc->language()] = tc;
1339
1340 Kit *existingKit = Utils::findOrDefault(existingKits, [&](const Kit *b) {
1341 if (qt != QtSupport::QtKitAspect::qtVersion(b))
1342 return false;
1343 return matchToolChain(toolChainForLanguage[ProjectExplorer::Constants::CXX_LANGUAGE_ID],
1344 ToolChainKitAspect::cxxToolChain(b))
1345 && matchToolChain(toolChainForLanguage[ProjectExplorer::Constants::C_LANGUAGE_ID],
1346 ToolChainKitAspect::cToolChain(b));
1347 });
1348
1349 const auto initializeKit = [allLanguages, device, tc, qt](Kit *k) {
1350 k->setAutoDetected(true);
1351 k->setAutoDetectionSource("AndroidConfiguration");
1352 DeviceTypeKitAspect::setDeviceTypeId(k, Utils::Id(Constants::ANDROID_DEVICE_TYPE));
1353 for (ToolChain *tc : allLanguages)
1354 ToolChainKitAspect::setToolChain(k, tc);
1355 QtSupport::QtKitAspect::setQtVersion(k, qt);
1356 DeviceKitAspect::setDevice(k, device);
1357 QStringList abis = static_cast<const AndroidQtVersion *>(qt)->androidAbis();
1358 Debugger::DebuggerKitAspect::setDebugger(k, findOrRegisterDebugger(tc, abis));
1359
1360 k->setSticky(ToolChainKitAspect::id(), true);
1361 k->setSticky(QtSupport::QtKitAspect::id(), true);
1362 k->setSticky(DeviceKitAspect::id(), true);
1363 k->setSticky(DeviceTypeKitAspect::id(), true);
1364
1365 QString versionStr = QLatin1String("Qt %{Qt:Version}");
1366 if (!qt->isAutodetected())
1367 versionStr = QString("%1").arg(qt->displayName());
1368 k->setUnexpandedDisplayName(tr("Android %1 Clang %2")
1369 .arg(versionStr)
1370 .arg(getMultiOrSingleAbiString(abis)));
1371 k->setValueSilently(Constants::ANDROID_KIT_NDK, currentConfig().ndkLocation(qt).toString());
1372 k->setValueSilently(Constants::ANDROID_KIT_SDK, currentConfig().sdkLocation().toString());
1373 };
1374
1375 if (existingKit) {
1376 initializeKit(existingKit); // Update the existing kit with new data.
1377 unhandledKits.removeOne(existingKit);
1378 } else {
1379 KitManager::registerKit(initializeKit);
1380 }
1381 }
1382 }
1383 // cleanup any mess that might have existed before, by removing all Android kits that
1384 // existed before, but weren't re-used
1385 for (Kit *k : unhandledKits)
1386 KitManager::deregisterKit(k);
1387 }
1388
force32bitEmulator()1389 bool AndroidConfigurations::force32bitEmulator()
1390 {
1391 return m_instance->m_force32bit;
1392 }
1393
toolsEnvironment(const AndroidConfig & config)1394 Environment AndroidConfigurations::toolsEnvironment(const AndroidConfig &config)
1395 {
1396 Environment env = Environment::systemEnvironment();
1397 FilePath jdkLocation = config.openJDKLocation();
1398 if (!jdkLocation.isEmpty()) {
1399 env.set("JAVA_HOME", jdkLocation.toUserOutput());
1400 env.prependOrSetPath(jdkLocation.pathAppended("bin").toUserOutput());
1401 }
1402 return env;
1403 }
1404
currentConfig()1405 const AndroidConfig &AndroidConfigurations::currentConfig()
1406 {
1407 return m_instance->m_config; // ensure that m_instance is initialized
1408 }
1409
sdkManager()1410 AndroidSdkManager *AndroidConfigurations::sdkManager()
1411 {
1412 return m_instance->m_sdkManager.get();
1413 }
1414
instance()1415 AndroidConfigurations *AndroidConfigurations::instance()
1416 {
1417 return m_instance;
1418 }
1419
save()1420 void AndroidConfigurations::save()
1421 {
1422 QSettings *settings = Core::ICore::settings();
1423 settings->beginGroup(SettingsGroup);
1424 m_config.save(*settings);
1425 settings->endGroup();
1426 }
1427
AndroidConfigurations()1428 AndroidConfigurations::AndroidConfigurations()
1429 : m_sdkManager(new AndroidSdkManager(m_config))
1430 {
1431 load();
1432
1433 connect(SessionManager::instance(), &SessionManager::projectRemoved,
1434 this, &AndroidConfigurations::clearDefaultDevices);
1435 connect(DeviceManager::instance(), &DeviceManager::devicesLoaded,
1436 this, &AndroidConfigurations::updateAndroidDevice);
1437
1438 m_force32bit = is32BitUserSpace();
1439
1440 m_instance = this;
1441 }
1442
1443 AndroidConfigurations::~AndroidConfigurations() = default;
1444
androidStudioPath()1445 static Utils::FilePath androidStudioPath()
1446 {
1447 #if defined(Q_OS_WIN)
1448 const QLatin1String registryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\Android Studio");
1449 const QLatin1String valueName("Path");
1450 const QSettings settings64(registryKey, QSettings::Registry64Format);
1451 const QSettings settings32(registryKey, QSettings::Registry32Format);
1452 return Utils::FilePath::fromUserInput(
1453 settings64.value(valueName, settings32.value(valueName).toString()).toString());
1454 #endif
1455 return {}; // TODO non-Windows
1456 }
1457
getJdkPath()1458 FilePath AndroidConfig::getJdkPath()
1459 {
1460 FilePath jdkHome;
1461
1462 if (HostOsInfo::isWindowsHost()) {
1463 QStringList allVersions;
1464 std::unique_ptr<QSettings> settings(
1465 new QSettings(jdk8SettingsPath, QSettings::NativeFormat));
1466 allVersions = settings->childGroups();
1467 #ifdef Q_OS_WIN
1468 if (allVersions.isEmpty()) {
1469 settings.reset(new QSettings(jdk8SettingsPath, QSettings::Registry64Format));
1470 allVersions = settings->childGroups();
1471 }
1472 #endif // Q_OS_WIN
1473
1474 // If no jdk 1.8 can be found, look for jdk versions above 1.8
1475 // Android section would warn if sdkmanager cannot run with newer jdk versions
1476 if (allVersions.isEmpty()) {
1477 settings.reset(new QSettings(jdkLatestSettingsPath, QSettings::NativeFormat));
1478 allVersions = settings->childGroups();
1479 #ifdef Q_OS_WIN
1480 if (allVersions.isEmpty()) {
1481 settings.reset(new QSettings(jdkLatestSettingsPath, QSettings::Registry64Format));
1482 allVersions = settings->childGroups();
1483 }
1484 #endif // Q_OS_WIN
1485 }
1486
1487 for (const QString &version : qAsConst(allVersions)) {
1488 settings->beginGroup(version);
1489 jdkHome = FilePath::fromUserInput(settings->value("JavaHome").toString());
1490 settings->endGroup();
1491 if (version.startsWith("1.8")) {
1492 if (!jdkHome.exists())
1493 continue;
1494 break;
1495 }
1496 }
1497
1498 // Nothing found yet? Let's try finding Android Studio's jdk
1499 if (jdkHome.isEmpty()) {
1500 const Utils::FilePath androidStudioSdkPath = androidStudioPath();
1501 if (!androidStudioSdkPath.isEmpty()) {
1502 const Utils::FilePath androidStudioSdkJrePath = androidStudioSdkPath / "jre";
1503 if (androidStudioSdkJrePath.exists())
1504 jdkHome = androidStudioSdkJrePath;
1505 }
1506 }
1507 } else {
1508 QStringList args;
1509 if (HostOsInfo::isMacHost())
1510 args << "-c"
1511 << "/usr/libexec/java_home";
1512 else
1513 args << "-c"
1514 << "readlink -f $(which java)";
1515
1516 QProcess findJdkPathProc;
1517 findJdkPathProc.start("sh", args);
1518 findJdkPathProc.waitForFinished();
1519 QByteArray jdkPath = findJdkPathProc.readAllStandardOutput().trimmed();
1520
1521 if (HostOsInfo::isMacHost()) {
1522 jdkHome = FilePath::fromUtf8(jdkPath);
1523 } else {
1524 jdkPath.replace("bin/java", ""); // For OpenJDK 11
1525 jdkPath.replace("jre", "");
1526 jdkPath.replace("//", "/");
1527 jdkHome = FilePath::fromUtf8(jdkPath);
1528 }
1529 }
1530
1531 return jdkHome;
1532 }
1533
load()1534 void AndroidConfigurations::load()
1535 {
1536 QSettings *settings = Core::ICore::settings();
1537 settings->beginGroup(SettingsGroup);
1538 m_config.load(*settings);
1539 settings->endGroup();
1540 }
1541
updateAndroidDevice()1542 void AndroidConfigurations::updateAndroidDevice()
1543 {
1544 DeviceManager * const devMgr = DeviceManager::instance();
1545 if (m_instance->m_config.adbToolPath().exists())
1546 devMgr->addDevice(AndroidDevice::create());
1547 else if (devMgr->find(Constants::ANDROID_DEVICE_ID))
1548 devMgr->removeDevice(Utils::Id(Constants::ANDROID_DEVICE_ID));
1549 }
1550
1551 AndroidConfigurations *AndroidConfigurations::m_instance = nullptr;
1552
1553 } // namespace Android
1554