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