1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
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 "androidsdkmanager.h"
27
28 #include "androidconfigurations.h"
29 #include "androidconstants.h"
30 #include "androidmanager.h"
31
32 #include <utils/algorithm.h>
33 #include <utils/qtcassert.h>
34 #include <utils/qtcprocess.h>
35 #include <utils/runextensions.h>
36 #include <utils/stringutils.h>
37
38 #include <QFutureWatcher>
39 #include <QLoggingCategory>
40 #include <QReadWriteLock>
41 #include <QRegularExpression>
42 #include <QSettings>
43
44 #ifdef WITH_TESTS
45 # include <QTest>
46 # include "androidplugin.h"
47 #endif // WITH_TESTS
48
49 namespace {
50 static Q_LOGGING_CATEGORY(sdkManagerLog, "qtc.android.sdkManager", QtWarningMsg)
51 }
52
53 namespace Android {
54 namespace Internal {
55
56 const char installLocationKey[] = "Installed Location:";
57 const char revisionKey[] = "Version:";
58 const char descriptionKey[] = "Description:";
59 const char commonArgsKey[] = "Common Arguments:";
60
61 const int sdkManagerCmdTimeoutS = 60;
62 const int sdkManagerOperationTimeoutS = 600;
63
64 Q_GLOBAL_STATIC_WITH_ARGS(QRegularExpression, assertionReg,
65 ("(\\(\\s*y\\s*[\\/\\\\]\\s*n\\s*\\)\\s*)(?<mark>[\\:\\?])",
66 QRegularExpression::CaseInsensitiveOption
67 | QRegularExpression::MultilineOption))
68
69 using namespace Utils;
70 using SdkCmdFutureInterface = QFutureInterface<AndroidSdkManager::OperationOutput>;
71
platformNameToApiLevel(const QString & platformName)72 int platformNameToApiLevel(const QString &platformName)
73 {
74 int apiLevel = -1;
75 QRegularExpression re("(android-)(?<apiLevel>[0-9A-Z]{1,})",
76 QRegularExpression::CaseInsensitiveOption);
77 QRegularExpressionMatch match = re.match(platformName);
78 if (match.hasMatch()) {
79 QString apiLevelStr = match.captured("apiLevel");
80 bool isUInt;
81 apiLevel = apiLevelStr.toUInt(&isUInt);
82 if (!isUInt) {
83 if (apiLevelStr == 'Q')
84 apiLevel = 29;
85 else if (apiLevelStr == 'R')
86 apiLevel = 30;
87 else if (apiLevelStr == 'S')
88 apiLevel = 31;
89 }
90 }
91 return apiLevel;
92 }
93
94 /*!
95 Parses the \a line for a [spaces]key[spaces]value[spaces] pattern and returns
96 \c true if \a key is found, false otherwise. Result is copied into \a value.
97 */
valueForKey(QString key,const QString & line,QString * value=nullptr)98 static bool valueForKey(QString key, const QString &line, QString *value = nullptr)
99 {
100 auto trimmedInput = line.trimmed();
101 if (trimmedInput.startsWith(key)) {
102 if (value)
103 *value = trimmedInput.section(key, 1, 1).trimmed();
104 return true;
105 }
106 return false;
107 }
108
parseProgress(const QString & out,bool & foundAssertion)109 int parseProgress(const QString &out, bool &foundAssertion)
110 {
111 int progress = -1;
112 if (out.isEmpty())
113 return progress;
114 QRegularExpression reg("(?<progress>\\d*)%");
115 QStringList lines = out.split(QRegularExpression("[\\n\\r]"), Qt::SkipEmptyParts);
116 for (const QString &line : lines) {
117 QRegularExpressionMatch match = reg.match(line);
118 if (match.hasMatch()) {
119 progress = match.captured("progress").toInt();
120 if (progress < 0 || progress > 100)
121 progress = -1;
122 }
123 if (!foundAssertion)
124 foundAssertion = assertionReg->match(line).hasMatch();
125 }
126 return progress;
127 }
128
watcherDeleter(QFutureWatcher<void> * watcher)129 void watcherDeleter(QFutureWatcher<void> *watcher)
130 {
131 if (!watcher->isFinished() && !watcher->isCanceled())
132 watcher->cancel();
133
134 if (!watcher->isFinished())
135 watcher->waitForFinished();
136
137 delete watcher;
138 }
139
sdkRootArg(const AndroidConfig & config)140 static QString sdkRootArg(const AndroidConfig &config)
141 {
142 return "--sdk_root=" + config.sdkLocation().toString();
143 }
144 /*!
145 Runs the \c sdkmanger tool with arguments \a args. Returns \c true if the command is
146 successfully executed. Output is copied into \a output. The function blocks the calling thread.
147 */
sdkManagerCommand(const AndroidConfig & config,const QStringList & args,QString * output,int timeout=sdkManagerCmdTimeoutS)148 static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &args,
149 QString *output, int timeout = sdkManagerCmdTimeoutS)
150 {
151 QStringList newArgs = args;
152 newArgs.append(sdkRootArg(config));
153 qCDebug(sdkManagerLog) << "Running SDK Manager command (sync):"
154 << CommandLine(config.sdkManagerToolPath(), newArgs)
155 .toUserOutput();
156 QtcProcess proc;
157 proc.setEnvironment(AndroidConfigurations::toolsEnvironment(config));
158 proc.setTimeoutS(timeout);
159 proc.setTimeOutMessageBoxEnabled(true);
160 proc.setCommand({config.sdkManagerToolPath(), newArgs});
161 proc.setProcessUserEventWhileRunning();
162 proc.runBlocking();
163 if (output)
164 *output = proc.allOutput();
165 return proc.result() == QtcProcess::FinishedWithSuccess;
166 }
167
168 /*!
169 Runs the \c sdkmanger tool with arguments \a args. The operation command progress is updated in
170 to the future interface \a fi and \a output is populated with command output. The command listens
171 to cancel signal emmitted by \a sdkManager and kill the commands. The command is also killed
172 after the lapse of \a timeout seconds. The function blocks the calling thread.
173 */
sdkManagerCommand(const AndroidConfig & config,const QStringList & args,AndroidSdkManager & sdkManager,SdkCmdFutureInterface & fi,AndroidSdkManager::OperationOutput & output,double progressQuota,bool interruptible=true,int timeout=sdkManagerOperationTimeoutS)174 static void sdkManagerCommand(const AndroidConfig &config, const QStringList &args,
175 AndroidSdkManager &sdkManager, SdkCmdFutureInterface &fi,
176 AndroidSdkManager::OperationOutput &output, double progressQuota,
177 bool interruptible = true, int timeout = sdkManagerOperationTimeoutS)
178 {
179 QStringList newArgs = args;
180 newArgs.append(sdkRootArg(config));
181 qCDebug(sdkManagerLog) << "Running SDK Manager command (async):"
182 << CommandLine(config.sdkManagerToolPath(), newArgs).toUserOutput();
183 int offset = fi.progressValue();
184 QtcProcess proc;
185 proc.setEnvironment(AndroidConfigurations::toolsEnvironment(config));
186 bool assertionFound = false;
187 proc.setTimeoutS(timeout);
188 proc.setStdOutCallback([offset, progressQuota, &proc, &assertionFound, &fi](const QString &out) {
189 int progressPercent = parseProgress(out, assertionFound);
190 if (assertionFound)
191 proc.stopProcess();
192 if (progressPercent != -1)
193 fi.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota));
194 });
195 proc.setStdErrCallback([&output](const QString &err) {
196 output.stdError = err;
197 });
198 if (interruptible) {
199 QObject::connect(&sdkManager, &AndroidSdkManager::cancelActiveOperations,
200 &proc, &QtcProcess::stopProcess);
201 }
202 proc.setCommand({config.sdkManagerToolPath(), newArgs});
203 proc.setProcessUserEventWhileRunning();
204 proc.runBlocking();
205 if (assertionFound) {
206 output.success = false;
207 output.stdOutput = proc.stdOut();
208 output.stdError = QCoreApplication::translate("Android::Internal::AndroidSdkManager",
209 "The operation requires user interaction. "
210 "Use the \"sdkmanager\" command-line tool.");
211 } else {
212 output.success = proc.result() == QtcProcess::FinishedWithSuccess;
213 }
214 }
215
216
217 class AndroidSdkManagerPrivate
218 {
219 public:
220 AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager, const AndroidConfig &config);
221 ~AndroidSdkManagerPrivate();
222
223 AndroidSdkPackageList filteredPackages(AndroidSdkPackage::PackageState state,
224 AndroidSdkPackage::PackageType type,
225 bool forceUpdate = false);
226 const AndroidSdkPackageList &allPackages(bool forceUpdate = false);
227 void refreshSdkPackages(bool forceReload = false);
228
229 void parseCommonArguments(QFutureInterface<QString> &fi);
230 void updateInstalled(SdkCmdFutureInterface &fi);
231 void update(SdkCmdFutureInterface &fi, const QStringList &install,
232 const QStringList &uninstall);
233 void checkPendingLicense(SdkCmdFutureInterface &fi);
234 void getPendingLicense(SdkCmdFutureInterface &fi);
235
236 void addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future);
237 void setLicenseInput(bool acceptLicense);
238
239 std::unique_ptr<QFutureWatcher<void>, decltype(&watcherDeleter)> m_activeOperation;
240
241 private:
242 QByteArray getUserInput() const;
243 void clearUserInput();
244 void reloadSdkPackages();
245 void clearPackages();
246 bool onLicenseStdOut(const QString &output, bool notify,
247 AndroidSdkManager::OperationOutput &result, SdkCmdFutureInterface &fi);
248
249 AndroidSdkManager &m_sdkManager;
250 const AndroidConfig &m_config;
251 AndroidSdkPackageList m_allPackages;
252 FilePath lastSdkManagerPath;
253 QString m_licenseTextCache;
254 QByteArray m_licenseUserInput;
255 mutable QReadWriteLock m_licenseInputLock;
256
257 public:
258 bool m_packageListingSuccessful = false;
259 };
260
261 /*!
262 \class SdkManagerOutputParser
263 \brief The SdkManagerOutputParser class is a helper class to parse the output of the \c sdkmanager
264 commands.
265 */
266 class SdkManagerOutputParser
267 {
268 class GenericPackageData
269 {
270 public:
isValid() const271 bool isValid() const { return !revision.isNull() && !description.isNull(); }
272 QStringList headerParts;
273 QVersionNumber revision;
274 QString description;
275 Utils::FilePath installedLocation;
276 QMap<QString, QString> extraData;
277 };
278
279 public:
280 enum MarkerTag
281 {
282 None = 0x001,
283 InstalledPackagesMarker = 0x002,
284 AvailablePackagesMarkers = 0x004,
285 AvailableUpdatesMarker = 0x008,
286 EmptyMarker = 0x010,
287 PlatformMarker = 0x020,
288 SystemImageMarker = 0x040,
289 BuildToolsMarker = 0x080,
290 SdkToolsMarker = 0x100,
291 PlatformToolsMarker = 0x200,
292 EmulatorToolsMarker = 0x400,
293 NdkMarker = 0x800,
294 ExtrasMarker = 0x1000,
295 CmdlineSdkToolsMarker = 0x2000,
296 GenericToolMarker = 0x4000,
297 SectionMarkers = InstalledPackagesMarker | AvailablePackagesMarkers | AvailableUpdatesMarker
298 };
299
SdkManagerOutputParser(AndroidSdkPackageList & container)300 SdkManagerOutputParser(AndroidSdkPackageList &container) : m_packages(container) {}
301 void parsePackageListing(const QString &output);
302
303 AndroidSdkPackageList &m_packages;
304
305 private:
306 void compilePackageAssociations();
307 void parsePackageData(MarkerTag packageMarker, const QStringList &data);
308 bool parseAbstractData(GenericPackageData &output, const QStringList &input, int minParts,
309 const QString &logStrTag,
310 const QStringList &extraKeys = QStringList()) const;
311 AndroidSdkPackage *parsePlatform(const QStringList &data) const;
312 QPair<SystemImage *, int> parseSystemImage(const QStringList &data) const;
313 BuildTools *parseBuildToolsPackage(const QStringList &data) const;
314 SdkTools *parseSdkToolsPackage(const QStringList &data) const;
315 PlatformTools *parsePlatformToolsPackage(const QStringList &data) const;
316 EmulatorTools *parseEmulatorToolsPackage(const QStringList &data) const;
317 Ndk *parseNdkPackage(const QStringList &data) const;
318 ExtraTools *parseExtraToolsPackage(const QStringList &data) const;
319 GenericSdkPackage *parseGenericTools(const QStringList &data) const;
320 MarkerTag parseMarkers(const QString &line);
321
322 MarkerTag m_currentSection = MarkerTag::None;
323 QHash<AndroidSdkPackage *, int> m_systemImages;
324 };
325
326 using MarkerTagsType = std::map<SdkManagerOutputParser::MarkerTag, const char *>;
327 Q_GLOBAL_STATIC_WITH_ARGS(MarkerTagsType, markerTags, ({
328 {SdkManagerOutputParser::MarkerTag::InstalledPackagesMarker, "Installed packages:"},
329 {SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Packages:"},
330 {SdkManagerOutputParser::MarkerTag::AvailableUpdatesMarker, "Available Updates:"},
331 {SdkManagerOutputParser::MarkerTag::PlatformMarker, "platforms"},
332 {SdkManagerOutputParser::MarkerTag::SystemImageMarker, "system-images"},
333 {SdkManagerOutputParser::MarkerTag::BuildToolsMarker, "build-tools"},
334 {SdkManagerOutputParser::MarkerTag::SdkToolsMarker, "tools"},
335 {SdkManagerOutputParser::MarkerTag::CmdlineSdkToolsMarker, "cmdline-tools"},
336 {SdkManagerOutputParser::MarkerTag::PlatformToolsMarker, "platform-tools"},
337 {SdkManagerOutputParser::MarkerTag::EmulatorToolsMarker, "emulator"},
338 {SdkManagerOutputParser::MarkerTag::NdkMarker, "ndk"},
339 {SdkManagerOutputParser::MarkerTag::ExtrasMarker, "extras"}
340 }));
341
AndroidSdkManager(const AndroidConfig & config)342 AndroidSdkManager::AndroidSdkManager(const AndroidConfig &config):
343 m_d(new AndroidSdkManagerPrivate(*this, config))
344 {
345 }
346
~AndroidSdkManager()347 AndroidSdkManager::~AndroidSdkManager()
348 {
349 cancelOperatons();
350 }
351
installedSdkPlatforms()352 SdkPlatformList AndroidSdkManager::installedSdkPlatforms()
353 {
354 AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::Installed,
355 AndroidSdkPackage::SdkPlatformPackage);
356 return Utils::static_container_cast<SdkPlatform *>(list);
357 }
358
allSdkPackages()359 const AndroidSdkPackageList &AndroidSdkManager::allSdkPackages()
360 {
361 return m_d->allPackages();
362 }
363
installedSdkPackages()364 AndroidSdkPackageList AndroidSdkManager::installedSdkPackages()
365 {
366 return m_d->filteredPackages(AndroidSdkPackage::Installed, AndroidSdkPackage::AnyValidType);
367 }
368
installedSystemImages()369 SystemImageList AndroidSdkManager::installedSystemImages()
370 {
371 AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::AnyValidState,
372 AndroidSdkPackage::SdkPlatformPackage);
373 QList<SdkPlatform *> platforms = Utils::static_container_cast<SdkPlatform *>(list);
374
375 SystemImageList result;
376 for (SdkPlatform *platform : platforms) {
377 if (platform->systemImages().size() > 0)
378 result.append(platform->systemImages());
379 }
380
381 return result;
382 }
383
installedNdkPackages()384 NdkList AndroidSdkManager::installedNdkPackages()
385 {
386 AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::Installed,
387 AndroidSdkPackage::NDKPackage);
388 return Utils::static_container_cast<Ndk *>(list);
389 }
390
latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state)391 SdkPlatform *AndroidSdkManager::latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state)
392 {
393 SdkPlatform *result = nullptr;
394 const AndroidSdkPackageList list = m_d->filteredPackages(state,
395 AndroidSdkPackage::SdkPlatformPackage);
396 for (AndroidSdkPackage *p : list) {
397 auto platform = static_cast<SdkPlatform *>(p);
398 if (!result || result->apiLevel() < platform->apiLevel())
399 result = platform;
400 }
401 return result;
402 }
403
filteredSdkPlatforms(int minApiLevel,AndroidSdkPackage::PackageState state)404 SdkPlatformList AndroidSdkManager::filteredSdkPlatforms(int minApiLevel,
405 AndroidSdkPackage::PackageState state)
406 {
407 const AndroidSdkPackageList list = m_d->filteredPackages(state,
408 AndroidSdkPackage::SdkPlatformPackage);
409
410 SdkPlatformList result;
411 for (AndroidSdkPackage *p : list) {
412 auto platform = static_cast<SdkPlatform *>(p);
413 if (platform && platform->apiLevel() >= minApiLevel)
414 result << platform;
415 }
416 return result;
417 }
418
reloadPackages(bool forceReload)419 void AndroidSdkManager::reloadPackages(bool forceReload)
420 {
421 m_d->refreshSdkPackages(forceReload);
422 }
423
isBusy() const424 bool AndroidSdkManager::isBusy() const
425 {
426 return m_d->m_activeOperation && !m_d->m_activeOperation->isFinished();
427 }
428
packageListingSuccessful() const429 bool AndroidSdkManager::packageListingSuccessful() const
430 {
431 return m_d->m_packageListingSuccessful;
432 }
433
availableArguments() const434 QFuture<QString> AndroidSdkManager::availableArguments() const
435 {
436 return Utils::runAsync(&AndroidSdkManagerPrivate::parseCommonArguments, m_d.get());
437 }
438
updateAll()439 QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::updateAll()
440 {
441 if (isBusy()) {
442 return QFuture<AndroidSdkManager::OperationOutput>();
443 }
444 auto future = Utils::runAsync(&AndroidSdkManagerPrivate::updateInstalled, m_d.get());
445 m_d->addWatcher(future);
446 return future;
447 }
448
449 QFuture<AndroidSdkManager::OperationOutput>
update(const QStringList & install,const QStringList & uninstall)450 AndroidSdkManager::update(const QStringList &install, const QStringList &uninstall)
451 {
452 if (isBusy())
453 return QFuture<AndroidSdkManager::OperationOutput>();
454 auto future = Utils::runAsync(&AndroidSdkManagerPrivate::update, m_d.get(), install, uninstall);
455 m_d->addWatcher(future);
456 return future;
457 }
458
checkPendingLicenses()459 QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::checkPendingLicenses()
460 {
461 if (isBusy())
462 return QFuture<AndroidSdkManager::OperationOutput>();
463 auto future = Utils::runAsync(&AndroidSdkManagerPrivate::checkPendingLicense, m_d.get());
464 m_d->addWatcher(future);
465 return future;
466 }
467
runLicenseCommand()468 QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::runLicenseCommand()
469 {
470 if (isBusy())
471 return QFuture<AndroidSdkManager::OperationOutput>();
472 auto future = Utils::runAsync(&AndroidSdkManagerPrivate::getPendingLicense, m_d.get());
473 m_d->addWatcher(future);
474 return future;
475 }
476
cancelOperatons()477 void AndroidSdkManager::cancelOperatons()
478 {
479 emit cancelActiveOperations();
480 m_d->m_activeOperation.reset();
481 }
482
acceptSdkLicense(bool accept)483 void AndroidSdkManager::acceptSdkLicense(bool accept)
484 {
485 m_d->setLicenseInput(accept);
486 }
487
parsePackageListing(const QString & output)488 void SdkManagerOutputParser::parsePackageListing(const QString &output)
489 {
490 QStringList packageData;
491 bool collectingPackageData = false;
492 MarkerTag currentPackageMarker = MarkerTag::None;
493
494 auto processCurrentPackage = [&]() {
495 if (collectingPackageData) {
496 collectingPackageData = false;
497 parsePackageData(currentPackageMarker, packageData);
498 packageData.clear();
499 }
500 };
501
502 QRegularExpression delimiters("[\\n\\r]");
503 const auto lines = output.split(delimiters);
504 for (const QString &outputLine : lines) {
505
506 // NOTE: we don't want to parse Dependencies part as it does not add value
507 if (outputLine.startsWith(" "))
508 continue;
509
510 // We don't need to parse this because they would still be listed on available packages
511 if (m_currentSection == AvailableUpdatesMarker)
512 continue;
513
514 MarkerTag marker = parseMarkers(outputLine.trimmed());
515 if (marker & SectionMarkers) {
516 // Section marker found. Update the current section being parsed.
517 m_currentSection = marker;
518 processCurrentPackage();
519 continue;
520 }
521
522 if (m_currentSection == None)
523 continue; // Continue with the verbose output until a valid section starts.
524
525 if (marker == EmptyMarker) {
526 // Empty marker. Occurs at the end of a package details.
527 // Process the collected package data, if any.
528 processCurrentPackage();
529 continue;
530 }
531
532 if (marker == None) {
533 if (collectingPackageData)
534 packageData << outputLine; // Collect data until next marker.
535 else
536 continue;
537 } else {
538 // Package marker found.
539 processCurrentPackage(); // New package starts. Process the collected package data, if any.
540 currentPackageMarker = marker;
541 collectingPackageData = true;
542 packageData << outputLine;
543 }
544 }
545 compilePackageAssociations();
546 }
547
compilePackageAssociations()548 void SdkManagerOutputParser::compilePackageAssociations()
549 {
550 // Return true if package p is already installed i.e. there exists a installed package having
551 // same sdk style path and same revision as of p.
552 auto isInstalled = [](const AndroidSdkPackageList &container, AndroidSdkPackage *p) {
553 return Utils::anyOf(container, [p](AndroidSdkPackage *other) {
554 return other->state() == AndroidSdkPackage::Installed &&
555 other->sdkStylePath() == p->sdkStylePath() &&
556 other->revision() == p->revision();
557 });
558 };
559
560 auto deleteAlreadyInstalled = [isInstalled](AndroidSdkPackageList &packages) {
561 for (auto p = packages.begin(); p != packages.end();) {
562 if ((*p)->state() == AndroidSdkPackage::Available && isInstalled(packages, *p)) {
563 delete *p;
564 p = packages.erase(p);
565 } else {
566 ++p;
567 }
568 }
569 };
570
571 // Remove already installed packages.
572 deleteAlreadyInstalled(m_packages);
573
574 // Filter out available images that are already installed.
575 AndroidSdkPackageList images = m_systemImages.keys();
576 deleteAlreadyInstalled(images);
577
578 // Associate the system images with sdk platforms.
579 for (AndroidSdkPackage *image : qAsConst(images)) {
580 int imageApi = m_systemImages[image];
581 auto itr = std::find_if(m_packages.begin(), m_packages.end(),
582 [imageApi](const AndroidSdkPackage *p) {
583 const SdkPlatform *platform = nullptr;
584 if (p->type() == AndroidSdkPackage::SdkPlatformPackage)
585 platform = static_cast<const SdkPlatform*>(p);
586 return platform && platform->apiLevel() == imageApi;
587 });
588 if (itr != m_packages.end()) {
589 auto platform = static_cast<SdkPlatform*>(*itr);
590 platform->addSystemImage(static_cast<SystemImage *>(image));
591 }
592 }
593 }
594
parsePackageData(MarkerTag packageMarker,const QStringList & data)595 void SdkManagerOutputParser::parsePackageData(MarkerTag packageMarker, const QStringList &data)
596 {
597 QTC_ASSERT(!data.isEmpty() && packageMarker != None, return);
598
599 AndroidSdkPackage *package = nullptr;
600 auto createPackage = [&](std::function<AndroidSdkPackage *(SdkManagerOutputParser *,
601 const QStringList &)> creator) {
602 if ((package = creator(this, data)))
603 m_packages.append(package);
604 };
605
606 switch (packageMarker) {
607 case MarkerTag::BuildToolsMarker:
608 createPackage(&SdkManagerOutputParser::parseBuildToolsPackage);
609 break;
610
611 case MarkerTag::SdkToolsMarker:
612 createPackage(&SdkManagerOutputParser::parseSdkToolsPackage);
613 break;
614
615 case MarkerTag::CmdlineSdkToolsMarker:
616 createPackage(&SdkManagerOutputParser::parseSdkToolsPackage);
617 break;
618
619 case MarkerTag::PlatformToolsMarker:
620 createPackage(&SdkManagerOutputParser::parsePlatformToolsPackage);
621 break;
622
623 case MarkerTag::EmulatorToolsMarker:
624 createPackage(&SdkManagerOutputParser::parseEmulatorToolsPackage);
625 break;
626
627 case MarkerTag::PlatformMarker:
628 createPackage(&SdkManagerOutputParser::parsePlatform);
629 break;
630
631 case MarkerTag::SystemImageMarker:
632 {
633 QPair<SystemImage *, int> result = parseSystemImage(data);
634 if (result.first) {
635 m_systemImages[result.first] = result.second;
636 package = result.first;
637 }
638 }
639 break;
640
641 case MarkerTag::NdkMarker:
642 createPackage(&SdkManagerOutputParser::parseNdkPackage);
643 break;
644
645 case MarkerTag::ExtrasMarker:
646 createPackage(&SdkManagerOutputParser::parseExtraToolsPackage);
647 break;
648
649 case MarkerTag::GenericToolMarker:
650 createPackage(&SdkManagerOutputParser::parseGenericTools);
651 break;
652
653 default:
654 qCDebug(sdkManagerLog) << "Unhandled package: " << markerTags->at(packageMarker);
655 break;
656 }
657
658 if (package) {
659 switch (m_currentSection) {
660 case MarkerTag::InstalledPackagesMarker:
661 package->setState(AndroidSdkPackage::Installed);
662 break;
663 case MarkerTag::AvailablePackagesMarkers:
664 case MarkerTag::AvailableUpdatesMarker:
665 package->setState(AndroidSdkPackage::Available);
666 break;
667 default:
668 qCDebug(sdkManagerLog) << "Invalid section marker: " << markerTags->at(m_currentSection);
669 break;
670 }
671 }
672 }
673
parseAbstractData(SdkManagerOutputParser::GenericPackageData & output,const QStringList & input,int minParts,const QString & logStrTag,const QStringList & extraKeys) const674 bool SdkManagerOutputParser::parseAbstractData(SdkManagerOutputParser::GenericPackageData &output,
675 const QStringList &input, int minParts,
676 const QString &logStrTag,
677 const QStringList &extraKeys) const
678 {
679 if (input.isEmpty()) {
680 qCDebug(sdkManagerLog) << logStrTag + ": Empty input";
681 return false;
682 }
683
684 output.headerParts = input.at(0).split(';');
685 if (output.headerParts.count() < minParts) {
686 qCDebug(sdkManagerLog) << logStrTag + "%1: Unexpected header:" << input;
687 return false;
688 }
689
690 QStringList keys = extraKeys;
691 keys << installLocationKey << revisionKey << descriptionKey;
692 for (const QString &line : input) {
693 QString value;
694 for (const auto &key: qAsConst(keys)) {
695 if (valueForKey(key, line, &value)) {
696 if (key == installLocationKey)
697 output.installedLocation = Utils::FilePath::fromString(value);
698 else if (key == revisionKey)
699 output.revision = QVersionNumber::fromString(value);
700 else if (key == descriptionKey)
701 output.description = value;
702 else
703 output.extraData[key] = value;
704 break;
705 }
706 }
707 }
708
709 return output.isValid();
710 }
711
parsePlatform(const QStringList & data) const712 AndroidSdkPackage *SdkManagerOutputParser::parsePlatform(const QStringList &data) const
713 {
714 SdkPlatform *platform = nullptr;
715 GenericPackageData packageData;
716 if (parseAbstractData(packageData, data, 2, "Platform")) {
717 int apiLevel = platformNameToApiLevel(packageData.headerParts.at(1));
718 if (apiLevel == -1) {
719 qCDebug(sdkManagerLog) << "Platform: Cannot parse api level:"<< data;
720 return nullptr;
721 }
722 platform = new SdkPlatform(packageData.revision, data.at(0), apiLevel);
723 platform->setDescriptionText(packageData.description);
724 platform->setInstalledLocation(packageData.installedLocation);
725 } else {
726 qCDebug(sdkManagerLog) << "Platform: Parsing failed. Minimum required data unavailable:"
727 << data;
728 }
729 return platform;
730 }
731
parseSystemImage(const QStringList & data) const732 QPair<SystemImage *, int> SdkManagerOutputParser::parseSystemImage(const QStringList &data) const
733 {
734 QPair <SystemImage *, int> result(nullptr, -1);
735 GenericPackageData packageData;
736 if (parseAbstractData(packageData, data, 4, "System-image")) {
737 int apiLevel = platformNameToApiLevel(packageData.headerParts.at(1));
738 if (apiLevel == -1) {
739 qCDebug(sdkManagerLog) << "System-image: Cannot parse api level:"<< data;
740 return result;
741 }
742 auto image = new SystemImage(packageData.revision, data.at(0),
743 packageData.headerParts.at(3));
744 image->setInstalledLocation(packageData.installedLocation);
745 image->setDisplayText(packageData.description);
746 image->setDescriptionText(packageData.description);
747 image->setApiLevel(apiLevel);
748 result = qMakePair(image, apiLevel);
749 } else {
750 qCDebug(sdkManagerLog) << "System-image: Minimum required data unavailable: "<< data;
751 }
752 return result;
753 }
754
parseBuildToolsPackage(const QStringList & data) const755 BuildTools *SdkManagerOutputParser::parseBuildToolsPackage(const QStringList &data) const
756 {
757 BuildTools *buildTools = nullptr;
758 GenericPackageData packageData;
759 if (parseAbstractData(packageData, data, 2, "Build-tools")) {
760 buildTools = new BuildTools(packageData.revision, data.at(0));
761 buildTools->setDescriptionText(packageData.description);
762 buildTools->setDisplayText(packageData.description);
763 buildTools->setInstalledLocation(packageData.installedLocation);
764 } else {
765 qCDebug(sdkManagerLog) << "Build-tools: Parsing failed. Minimum required data unavailable:"
766 << data;
767 }
768 return buildTools;
769 }
770
parseSdkToolsPackage(const QStringList & data) const771 SdkTools *SdkManagerOutputParser::parseSdkToolsPackage(const QStringList &data) const
772 {
773 SdkTools *sdkTools = nullptr;
774 GenericPackageData packageData;
775 if (parseAbstractData(packageData, data, 1, "SDK-tools")) {
776 sdkTools = new SdkTools(packageData.revision, data.at(0));
777 sdkTools->setDescriptionText(packageData.description);
778 sdkTools->setDisplayText(packageData.description);
779 sdkTools->setInstalledLocation(packageData.installedLocation);
780 } else {
781 qCDebug(sdkManagerLog) << "SDK-tools: Parsing failed. Minimum required data unavailable:"
782 << data;
783 }
784 return sdkTools;
785 }
786
parsePlatformToolsPackage(const QStringList & data) const787 PlatformTools *SdkManagerOutputParser::parsePlatformToolsPackage(const QStringList &data) const
788 {
789 PlatformTools *platformTools = nullptr;
790 GenericPackageData packageData;
791 if (parseAbstractData(packageData, data, 1, "Platform-tools")) {
792 platformTools = new PlatformTools(packageData.revision, data.at(0));
793 platformTools->setDescriptionText(packageData.description);
794 platformTools->setDisplayText(packageData.description);
795 platformTools->setInstalledLocation(packageData.installedLocation);
796 } else {
797 qCDebug(sdkManagerLog) << "Platform-tools: Parsing failed. Minimum required data "
798 "unavailable:" << data;
799 }
800 return platformTools;
801 }
802
parseEmulatorToolsPackage(const QStringList & data) const803 EmulatorTools *SdkManagerOutputParser::parseEmulatorToolsPackage(const QStringList &data) const
804 {
805 EmulatorTools *emulatorTools = nullptr;
806 GenericPackageData packageData;
807 if (parseAbstractData(packageData, data, 1, "Emulator-tools")) {
808 emulatorTools = new EmulatorTools(packageData.revision, data.at(0));
809 emulatorTools->setDescriptionText(packageData.description);
810 emulatorTools->setDisplayText(packageData.description);
811 emulatorTools->setInstalledLocation(packageData.installedLocation);
812 } else {
813 qCDebug(sdkManagerLog) << "Emulator-tools: Parsing failed. Minimum required data "
814 "unavailable:" << data;
815 }
816 return emulatorTools;
817 }
818
parseNdkPackage(const QStringList & data) const819 Ndk *SdkManagerOutputParser::parseNdkPackage(const QStringList &data) const
820 {
821 Ndk *ndk = nullptr;
822 GenericPackageData packageData;
823 if (parseAbstractData(packageData, data, 1, "NDK")) {
824 ndk = new Ndk(packageData.revision, data.at(0));
825 ndk->setDescriptionText(packageData.description);
826 ndk->setDisplayText(packageData.description);
827 ndk->setInstalledLocation(packageData.installedLocation);
828 } else {
829 qCDebug(sdkManagerLog) << "NDK: Parsing failed. Minimum required data unavailable:"
830 << data;
831 }
832 return ndk;
833 }
834
parseExtraToolsPackage(const QStringList & data) const835 ExtraTools *SdkManagerOutputParser::parseExtraToolsPackage(const QStringList &data) const
836 {
837 ExtraTools *extraTools = nullptr;
838 GenericPackageData packageData;
839 if (parseAbstractData(packageData, data, 1, "Extras")) {
840 extraTools = new ExtraTools(packageData.revision, data.at(0));
841 extraTools->setDescriptionText(packageData.description);
842 extraTools->setDisplayText(packageData.description);
843 extraTools->setInstalledLocation(packageData.installedLocation);
844 } else {
845 qCDebug(sdkManagerLog) << "Extra-tools: Parsing failed. Minimum required data "
846 "unavailable:" << data;
847 }
848 return extraTools;
849 }
850
parseGenericTools(const QStringList & data) const851 GenericSdkPackage *SdkManagerOutputParser::parseGenericTools(const QStringList &data) const
852 {
853 GenericSdkPackage *sdkPackage = nullptr;
854 GenericPackageData packageData;
855 if (parseAbstractData(packageData, data, 1, "Generic")) {
856 sdkPackage = new GenericSdkPackage(packageData.revision, data.at(0));
857 sdkPackage->setDescriptionText(packageData.description);
858 sdkPackage->setDisplayText(packageData.description);
859 sdkPackage->setInstalledLocation(packageData.installedLocation);
860 } else {
861 qCDebug(sdkManagerLog) << "Generic: Parsing failed. Minimum required data "
862 "unavailable:" << data;
863 }
864 return sdkPackage;
865 }
866
parseMarkers(const QString & line)867 SdkManagerOutputParser::MarkerTag SdkManagerOutputParser::parseMarkers(const QString &line)
868 {
869 if (line.isEmpty())
870 return EmptyMarker;
871
872 for (auto pair : *markerTags) {
873 if (line.startsWith(QLatin1String(pair.second)))
874 return pair.first;
875 }
876
877 QRegularExpressionMatch match = QRegularExpression("^[a-zA-Z]+[A-Za-z0-9;._-]+").match(line);
878 if (match.hasMatch() && match.captured(0) == line)
879 return GenericToolMarker;
880
881 return None;
882 }
883
AndroidSdkManagerPrivate(AndroidSdkManager & sdkManager,const AndroidConfig & config)884 AndroidSdkManagerPrivate::AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager,
885 const AndroidConfig &config):
886 m_activeOperation(nullptr, watcherDeleter),
887 m_sdkManager(sdkManager),
888 m_config(config)
889 {
890 }
891
~AndroidSdkManagerPrivate()892 AndroidSdkManagerPrivate::~AndroidSdkManagerPrivate()
893 {
894 clearPackages();
895 }
896
897 AndroidSdkPackageList
filteredPackages(AndroidSdkPackage::PackageState state,AndroidSdkPackage::PackageType type,bool forceUpdate)898 AndroidSdkManagerPrivate::filteredPackages(AndroidSdkPackage::PackageState state,
899 AndroidSdkPackage::PackageType type, bool forceUpdate)
900 {
901 refreshSdkPackages(forceUpdate);
902 return Utils::filtered(m_allPackages, [state, type](const AndroidSdkPackage *p) {
903 return p->state() & state && p->type() & type;
904 });
905 }
906
allPackages(bool forceUpdate)907 const AndroidSdkPackageList &AndroidSdkManagerPrivate::allPackages(bool forceUpdate)
908 {
909 refreshSdkPackages(forceUpdate);
910 return m_allPackages;
911 }
912
reloadSdkPackages()913 void AndroidSdkManagerPrivate::reloadSdkPackages()
914 {
915 emit m_sdkManager.packageReloadBegin();
916 clearPackages();
917
918 lastSdkManagerPath = m_config.sdkManagerToolPath();
919 m_packageListingSuccessful = false;
920
921 if (m_config.sdkToolsVersion().isNull()) {
922 // Configuration has invalid sdk path or corrupt installation.
923 emit m_sdkManager.packageReloadFinished();
924 return;
925 }
926
927 QString packageListing;
928 QStringList args({"--list", "--verbose"});
929 args << m_config.sdkManagerToolArgs();
930 m_packageListingSuccessful = sdkManagerCommand(m_config, args, &packageListing);
931 if (m_packageListingSuccessful) {
932 SdkManagerOutputParser parser(m_allPackages);
933 parser.parsePackageListing(packageListing);
934 }
935 emit m_sdkManager.packageReloadFinished();
936 }
937
refreshSdkPackages(bool forceReload)938 void AndroidSdkManagerPrivate::refreshSdkPackages(bool forceReload)
939 {
940 // Sdk path changed. Updated packages.
941 // QTC updates the package listing only
942 if (m_config.sdkManagerToolPath() != lastSdkManagerPath || forceReload)
943 reloadSdkPackages();
944 }
945
updateInstalled(SdkCmdFutureInterface & fi)946 void AndroidSdkManagerPrivate::updateInstalled(SdkCmdFutureInterface &fi)
947 {
948 fi.setProgressRange(0, 100);
949 fi.setProgressValue(0);
950 AndroidSdkManager::OperationOutput result;
951 result.type = AndroidSdkManager::UpdateAll;
952 result.stdOutput = QCoreApplication::translate("AndroidSdkManager",
953 "Updating installed packages.");
954 fi.reportResult(result);
955 QStringList args("--update");
956 args << m_config.sdkManagerToolArgs();
957 if (!fi.isCanceled())
958 sdkManagerCommand(m_config, args, m_sdkManager, fi, result, 100);
959 else
960 qCDebug(sdkManagerLog) << "Update: Operation cancelled before start";
961
962 if (result.stdError.isEmpty() && !result.success)
963 result.stdError = QCoreApplication::translate("AndroidSdkManager", "Failed.");
964 result.stdOutput = QCoreApplication::translate("AndroidSdkManager", "Done\n\n");
965 fi.reportResult(result);
966 fi.setProgressValue(100);
967 }
968
update(SdkCmdFutureInterface & fi,const QStringList & install,const QStringList & uninstall)969 void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringList &install,
970 const QStringList &uninstall)
971 {
972 fi.setProgressRange(0, 100);
973 fi.setProgressValue(0);
974 double progressQuota = 100.0 / (install.count() + uninstall.count());
975 int currentProgress = 0;
976
977 QString installTag = QCoreApplication::translate("AndroidSdkManager", "Installing");
978 QString uninstallTag = QCoreApplication::translate("AndroidSdkManager", "Uninstalling");
979
980 auto doOperation = [&](const QString& packagePath, const QStringList& args,
981 bool isInstall) {
982 AndroidSdkManager::OperationOutput result;
983 result.type = AndroidSdkManager::UpdatePackage;
984 result.stdOutput = QString("%1 %2").arg(isInstall ? installTag : uninstallTag)
985 .arg(packagePath);
986 fi.reportResult(result);
987 if (fi.isCanceled())
988 qCDebug(sdkManagerLog) << args << "Update: Operation cancelled before start";
989 else
990 sdkManagerCommand(m_config, args, m_sdkManager, fi, result, progressQuota, isInstall);
991 currentProgress += progressQuota;
992 fi.setProgressValue(currentProgress);
993 if (result.stdError.isEmpty() && !result.success)
994 result.stdError = QCoreApplication::translate("AndroidSdkManager", "Failed");
995 result.stdOutput = QCoreApplication::translate("AndroidSdkManager", "Done\n\n");
996 fi.reportResult(result);
997 return fi.isCanceled();
998 };
999
1000
1001 // Uninstall packages
1002 for (const QString &sdkStylePath : uninstall) {
1003 // Uninstall operations are not interptible. We don't want to leave half uninstalled.
1004 QStringList args;
1005 args << "--uninstall" << sdkStylePath << m_config.sdkManagerToolArgs();
1006 if (doOperation(sdkStylePath, args, false))
1007 break;
1008 }
1009
1010 // Install packages
1011 for (const QString &sdkStylePath : install) {
1012 QStringList args(sdkStylePath);
1013 args << m_config.sdkManagerToolArgs();
1014 if (doOperation(sdkStylePath, args, true))
1015 break;
1016 }
1017 fi.setProgressValue(100);
1018 }
1019
checkPendingLicense(SdkCmdFutureInterface & fi)1020 void AndroidSdkManagerPrivate::checkPendingLicense(SdkCmdFutureInterface &fi)
1021 {
1022 fi.setProgressRange(0, 100);
1023 fi.setProgressValue(0);
1024 AndroidSdkManager::OperationOutput result;
1025 result.type = AndroidSdkManager::LicenseCheck;
1026 const QStringList args = {"--licenses", sdkRootArg(m_config)};
1027 if (!fi.isCanceled()) {
1028 const int timeOutS = 4; // Short timeout as workaround for QTCREATORBUG-25667
1029 sdkManagerCommand(m_config, args, m_sdkManager, fi, result, 100.0, true, timeOutS);
1030 } else {
1031 qCDebug(sdkManagerLog) << "Update: Operation cancelled before start";
1032 }
1033
1034 fi.reportResult(result);
1035 fi.setProgressValue(100);
1036 }
1037
getPendingLicense(SdkCmdFutureInterface & fi)1038 void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdFutureInterface &fi)
1039 {
1040 fi.setProgressRange(0, 100);
1041 fi.setProgressValue(0);
1042 AndroidSdkManager::OperationOutput result;
1043 result.type = AndroidSdkManager::LicenseWorkflow;
1044 QtcProcess licenseCommand;
1045 licenseCommand.setEnvironment(AndroidConfigurations::toolsEnvironment(m_config));
1046 bool reviewingLicenses = false;
1047 licenseCommand.setCommand(CommandLine(m_config.sdkManagerToolPath(), {"--licenses", sdkRootArg(m_config)}));
1048 licenseCommand.setUseCtrlCStub(true);
1049 licenseCommand.start();
1050 QTextCodec *codec = QTextCodec::codecForLocale();
1051 int inputCounter = 0, steps = -1;
1052 while (!licenseCommand.waitForFinished(200)) {
1053 QString stdOut = codec->toUnicode(licenseCommand.readAllStandardOutput());
1054 bool assertionFound = false;
1055 if (!stdOut.isEmpty())
1056 assertionFound = onLicenseStdOut(stdOut, reviewingLicenses, result, fi);
1057
1058 if (reviewingLicenses) {
1059 // Check user input
1060 QByteArray userInput = getUserInput();
1061 if (!userInput.isEmpty()) {
1062 clearUserInput();
1063 licenseCommand.write(userInput);
1064 ++inputCounter;
1065 if (steps != -1)
1066 fi.setProgressValue(qRound((inputCounter / (double)steps) * 100));
1067 }
1068 } else if (assertionFound) {
1069 // The first assertion is to start reviewing licenses. Always accept.
1070 reviewingLicenses = true;
1071 QRegularExpression reg("(\\d+\\sof\\s)(?<steps>\\d+)");
1072 QRegularExpressionMatch match = reg.match(stdOut);
1073 if (match.hasMatch())
1074 steps = match.captured("steps").toInt();
1075 licenseCommand.write("Y\n");
1076 }
1077
1078 if (fi.isCanceled()) {
1079 licenseCommand.terminate();
1080 if (!licenseCommand.waitForFinished(300)) {
1081 licenseCommand.kill();
1082 licenseCommand.waitForFinished(200);
1083 }
1084 }
1085 if (licenseCommand.state() == QProcess::NotRunning)
1086 break;
1087 }
1088
1089 m_licenseTextCache.clear();
1090 result.success = licenseCommand.exitStatus() == QProcess::NormalExit;
1091 if (!result.success) {
1092 result.stdError = QCoreApplication::translate("Android::Internal::AndroidSdkManager",
1093 "License command failed.\n\n");
1094 }
1095 fi.reportResult(result);
1096 fi.setProgressValue(100);
1097 }
1098
setLicenseInput(bool acceptLicense)1099 void AndroidSdkManagerPrivate::setLicenseInput(bool acceptLicense)
1100 {
1101 QWriteLocker locker(&m_licenseInputLock);
1102 m_licenseUserInput = acceptLicense ? "Y\n" : "n\n";
1103 }
1104
getUserInput() const1105 QByteArray AndroidSdkManagerPrivate::getUserInput() const
1106 {
1107 QReadLocker locker(&m_licenseInputLock);
1108 return m_licenseUserInput;
1109 }
1110
clearUserInput()1111 void AndroidSdkManagerPrivate::clearUserInput()
1112 {
1113 QWriteLocker locker(&m_licenseInputLock);
1114 m_licenseUserInput.clear();
1115 }
1116
onLicenseStdOut(const QString & output,bool notify,AndroidSdkManager::OperationOutput & result,SdkCmdFutureInterface & fi)1117 bool AndroidSdkManagerPrivate::onLicenseStdOut(const QString &output, bool notify,
1118 AndroidSdkManager::OperationOutput &result,
1119 SdkCmdFutureInterface &fi)
1120 {
1121 m_licenseTextCache.append(output);
1122 QRegularExpressionMatch assertionMatch = assertionReg->match(m_licenseTextCache);
1123 if (assertionMatch.hasMatch()) {
1124 if (notify) {
1125 result.stdOutput = m_licenseTextCache;
1126 fi.reportResult(result);
1127 }
1128 // Clear the current contents. The found license text is dispatched. Continue collecting the
1129 // next license text.
1130 m_licenseTextCache.clear();
1131 return true;
1132 }
1133 return false;
1134 }
1135
addWatcher(const QFuture<AndroidSdkManager::OperationOutput> & future)1136 void AndroidSdkManagerPrivate::addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future)
1137 {
1138 if (future.isFinished())
1139 return;
1140 m_activeOperation.reset(new QFutureWatcher<void>());
1141 m_activeOperation->setFuture(QFuture<void>(future));
1142 }
1143
parseCommonArguments(QFutureInterface<QString> & fi)1144 void AndroidSdkManagerPrivate::parseCommonArguments(QFutureInterface<QString> &fi)
1145 {
1146 QString argumentDetails;
1147 QString output;
1148 sdkManagerCommand(m_config, QStringList("--help"), &output);
1149 bool foundTag = false;
1150 const auto lines = output.split('\n');
1151 for (const QString& line : lines) {
1152 if (fi.isCanceled())
1153 break;
1154 if (foundTag)
1155 argumentDetails.append(line + "\n");
1156 else if (line.startsWith(commonArgsKey))
1157 foundTag = true;
1158 }
1159
1160 if (!fi.isCanceled())
1161 fi.reportResult(argumentDetails);
1162 }
1163
clearPackages()1164 void AndroidSdkManagerPrivate::clearPackages()
1165 {
1166 for (AndroidSdkPackage *p : qAsConst(m_allPackages))
1167 delete p;
1168 m_allPackages.clear();
1169 }
1170
1171 #ifdef WITH_TESTS
testAndroidSdkManagerProgressParser_data()1172 void AndroidPlugin::testAndroidSdkManagerProgressParser_data()
1173 {
1174 QTest::addColumn<QString>("output");
1175 QTest::addColumn<int>("progress");
1176 QTest::addColumn<bool>("foundAssertion");
1177
1178 // Output of "sdkmanager --licenses", Android SDK Tools version 4.0
1179 QTest::newRow("Loading local repository")
1180 << "Loading local repository... \r"
1181 << -1
1182 << false;
1183
1184 QTest::newRow("Fetch progress (single line)")
1185 << "[============= ] 34% Fetch remote repository... \r"
1186 << 34
1187 << false;
1188
1189 QTest::newRow("Fetch progress (multi line)")
1190 << "[============================= ] 73% Fetch remote repository... \r"
1191 "[============================= ] 75% Fetch remote repository... \r"
1192 << 75
1193 << false;
1194
1195 QTest::newRow("Some SDK package licenses not accepted")
1196 << "7 of 7 SDK package licenses not accepted.\n"
1197 << -1
1198 << false;
1199
1200 QTest::newRow("Unaccepted licenses assertion")
1201 << "\nReview licenses that have not been accepted (y/N)? "
1202 << -1
1203 << true;
1204 }
1205
testAndroidSdkManagerProgressParser()1206 void AndroidPlugin::testAndroidSdkManagerProgressParser()
1207 {
1208 QFETCH(QString, output);
1209 QFETCH(int, progress);
1210 QFETCH(bool, foundAssertion);
1211
1212 bool actualFoundAssertion = false;
1213 const int actualProgress = parseProgress(output, actualFoundAssertion);
1214
1215 QCOMPARE(progress, actualProgress);
1216 QCOMPARE(foundAssertion, actualFoundAssertion);
1217 }
1218 #endif // WITH_TESTS
1219
1220 } // namespace Internal
1221 } // namespace Android
1222