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 "qtversionmanager.h"
27 
28 #include "baseqtversion.h"
29 #include "exampleslistmodel.h"
30 #include "qtkitinformation.h"
31 #include "qtsupportconstants.h"
32 #include "qtversionfactory.h"
33 
34 #include <coreplugin/icore.h>
35 #include <coreplugin/helpmanager.h>
36 
37 #include <extensionsystem/pluginmanager.h>
38 
39 #include <projectexplorer/toolchainmanager.h>
40 
41 #include <utils/algorithm.h>
42 #include <utils/buildablehelperlibrary.h>
43 #include <utils/environment.h>
44 #include <utils/filesystemwatcher.h>
45 #include <utils/hostosinfo.h>
46 #include <utils/persistentsettings.h>
47 #include <utils/qtcprocess.h>
48 #include <utils/qtcassert.h>
49 
50 #include <QDir>
51 #include <QFile>
52 #include <QLoggingCategory>
53 #include <QSettings>
54 #include <QStandardPaths>
55 #include <QStringList>
56 #include <QTextStream>
57 #include <QTimer>
58 
59 using namespace Utils;
60 
61 namespace QtSupport {
62 
63 using namespace Internal;
64 
65 const char QTVERSION_DATA_KEY[] = "QtVersion.";
66 const char QTVERSION_TYPE_KEY[] = "QtVersion.Type";
67 const char QTVERSION_FILE_VERSION_KEY[] = "Version";
68 const char QTVERSION_FILENAME[] = "qtversion.xml";
69 
70 using VersionMap = QMap<int, BaseQtVersion *>;
71 static VersionMap m_versions;
72 
73 const char DOCUMENTATION_SETTING_KEY[] = "QtSupport/DocumentationSetting";
74 
75 static int m_idcount = 0;
76 // managed by QtProjectManagerPlugin
77 static QtVersionManager *m_instance = nullptr;
78 static FileSystemWatcher *m_configFileWatcher = nullptr;
79 static QTimer *m_fileWatcherTimer = nullptr;
80 static PersistentSettingsWriter *m_writer = nullptr;
81 static QVector<ExampleSetModel::ExtraExampleSet> m_pluginRegisteredExampleSets;
82 
83 static Q_LOGGING_CATEGORY(log, "qtc.qt.versions", QtWarningMsg);
84 
globalSettingsFileName()85 static FilePath globalSettingsFileName()
86 {
87     return Core::ICore::installerResourcePath(QTVERSION_FILENAME);
88 }
89 
settingsFileName(const QString & path)90 static FilePath settingsFileName(const QString &path)
91 {
92     return Core::ICore::userResourcePath(path);
93 }
94 
95 
96 // prefer newer qts otherwise compare on id
qtVersionNumberCompare(BaseQtVersion * a,BaseQtVersion * b)97 bool qtVersionNumberCompare(BaseQtVersion *a, BaseQtVersion *b)
98 {
99     return a->qtVersion() > b->qtVersion() || (a->qtVersion() == b->qtVersion() && a->uniqueId() < b->uniqueId());
100 }
101 static bool restoreQtVersions();
102 static void findSystemQt();
103 static void saveQtVersions();
104 
pluginRegisteredExampleSets()105 QVector<ExampleSetModel::ExtraExampleSet> ExampleSetModel::pluginRegisteredExampleSets()
106 {
107     return m_pluginRegisteredExampleSets;
108 }
109 
110 // --------------------------------------------------------------------------
111 // QtVersionManager
112 // --------------------------------------------------------------------------
113 
QtVersionManager()114 QtVersionManager::QtVersionManager()
115 {
116     m_instance = this;
117     m_configFileWatcher = nullptr;
118     m_fileWatcherTimer = new QTimer(this);
119     m_writer = nullptr;
120     m_idcount = 1;
121 
122     qRegisterMetaType<FilePath>();
123 
124     // Give the file a bit of time to settle before reading it...
125     m_fileWatcherTimer->setInterval(2000);
126     connect(m_fileWatcherTimer, &QTimer::timeout, this, [this] { updateFromInstaller(); });
127 }
128 
triggerQtVersionRestore()129 void QtVersionManager::triggerQtVersionRestore()
130 {
131     disconnect(ProjectExplorer::ToolChainManager::instance(), &ProjectExplorer::ToolChainManager::toolChainsLoaded,
132                this, &QtVersionManager::triggerQtVersionRestore);
133 
134     bool success = restoreQtVersions();
135     m_instance->updateFromInstaller(false);
136     if (!success) {
137         // We did neither restore our settings or upgraded
138         // in that case figure out if there's a qt in path
139         // and add it to the Qt versions
140         findSystemQt();
141     }
142 
143     emit m_instance->qtVersionsLoaded();
144     emit m_instance->qtVersionsChanged(m_versions.keys(), QList<int>(), QList<int>());
145     saveQtVersions();
146 
147     const FilePath configFileName = globalSettingsFileName();
148     if (configFileName.exists()) {
149         m_configFileWatcher = new FileSystemWatcher(m_instance);
150         connect(m_configFileWatcher, &FileSystemWatcher::fileChanged,
151                 m_fileWatcherTimer, QOverload<>::of(&QTimer::start));
152         m_configFileWatcher->addFile(configFileName.toString(),
153                                      FileSystemWatcher::WatchModifiedDate);
154     } // exists
155 
156     const QList<BaseQtVersion *> vs = versions();
157     updateDocumentation(vs, {}, vs);
158 }
159 
isLoaded()160 bool QtVersionManager::isLoaded()
161 {
162     return m_writer;
163 }
164 
~QtVersionManager()165 QtVersionManager::~QtVersionManager()
166 {
167     delete m_writer;
168     qDeleteAll(m_versions);
169     m_versions.clear();
170 }
171 
initialized()172 void QtVersionManager::initialized()
173 {
174     connect(ProjectExplorer::ToolChainManager::instance(), &ProjectExplorer::ToolChainManager::toolChainsLoaded,
175             QtVersionManager::instance(), &QtVersionManager::triggerQtVersionRestore);
176 }
177 
instance()178 QtVersionManager *QtVersionManager::instance()
179 {
180     return m_instance;
181 }
182 
restoreQtVersions()183 static bool restoreQtVersions()
184 {
185     QTC_ASSERT(!m_writer, return false);
186     m_writer = new PersistentSettingsWriter(settingsFileName(QTVERSION_FILENAME),
187                                             "QtCreatorQtVersions");
188 
189     const QList<QtVersionFactory *> factories = QtVersionFactory::allQtVersionFactories();
190 
191     PersistentSettingsReader reader;
192     const FilePath filename = settingsFileName(QTVERSION_FILENAME);
193 
194     if (!reader.load(filename))
195         return false;
196     QVariantMap data = reader.restoreValues();
197 
198     // Check version:
199     const int version = data.value(QTVERSION_FILE_VERSION_KEY, 0).toInt();
200     if (version < 1)
201         return false;
202 
203     const QString keyPrefix(QTVERSION_DATA_KEY);
204     const QVariantMap::ConstIterator dcend = data.constEnd();
205     for (QVariantMap::ConstIterator it = data.constBegin(); it != dcend; ++it) {
206         const QString &key = it.key();
207         if (!key.startsWith(keyPrefix))
208             continue;
209         bool ok;
210         int count = key.mid(keyPrefix.count()).toInt(&ok);
211         if (!ok || count < 0)
212             continue;
213 
214         const QVariantMap qtversionMap = it.value().toMap();
215         const QString type = qtversionMap.value(QTVERSION_TYPE_KEY).toString();
216 
217         bool restored = false;
218         for (QtVersionFactory *f : factories) {
219             if (f->canRestore(type)) {
220                 if (BaseQtVersion *qtv = f->restore(type, qtversionMap)) {
221                     if (m_versions.contains(qtv->uniqueId())) {
222                         // This shouldn't happen, we are restoring the same id multiple times?
223                         qWarning() << "A Qt version with id"<<qtv->uniqueId()<<"already exists";
224                         delete qtv;
225                     } else {
226                         m_versions.insert(qtv->uniqueId(), qtv);
227                         m_idcount = qtv->uniqueId() > m_idcount ? qtv->uniqueId() : m_idcount;
228                         restored = true;
229                         break;
230                     }
231                 }
232             }
233         }
234         if (!restored)
235             qWarning("Warning: Unable to restore Qt version '%s' stored in %s.",
236                      qPrintable(type),
237                      qPrintable(filename.toUserOutput()));
238     }
239     ++m_idcount;
240 
241     return true;
242 }
243 
updateFromInstaller(bool emitSignal)244 void QtVersionManager::updateFromInstaller(bool emitSignal)
245 {
246     m_fileWatcherTimer->stop();
247 
248     const FilePath path = globalSettingsFileName();
249     // Handle overwritting of data:
250     if (m_configFileWatcher) {
251         m_configFileWatcher->removeFile(path.toString());
252         m_configFileWatcher->addFile(path.toString(), FileSystemWatcher::WatchModifiedDate);
253     }
254 
255     QList<int> added;
256     QList<int> removed;
257     QList<int> changed;
258 
259     const QList<QtVersionFactory *> factories = QtVersionFactory::allQtVersionFactories();
260     PersistentSettingsReader reader;
261     QVariantMap data;
262     if (reader.load(path))
263         data = reader.restoreValues();
264 
265     if (log().isDebugEnabled()) {
266         qCDebug(log) << "======= Existing Qt versions =======";
267         for (BaseQtVersion *version : qAsConst(m_versions)) {
268             qCDebug(log) << version->qmakeFilePath().toUserOutput() << "id:"<<version->uniqueId();
269             qCDebug(log) << "  autodetection source:" << version->detectionSource();
270             qCDebug(log) << "";
271         }
272         qCDebug(log)<< "======= Adding sdk versions =======";
273     }
274 
275     QStringList sdkVersions;
276 
277     const QString keyPrefix(QTVERSION_DATA_KEY);
278     const QVariantMap::ConstIterator dcend = data.constEnd();
279     for (QVariantMap::ConstIterator it = data.constBegin(); it != dcend; ++it) {
280         const QString &key = it.key();
281         if (!key.startsWith(keyPrefix))
282             continue;
283         bool ok;
284         int count = key.mid(keyPrefix.count()).toInt(&ok);
285         if (!ok || count < 0)
286             continue;
287 
288         QVariantMap qtversionMap = it.value().toMap();
289         const QString type = qtversionMap.value(QTVERSION_TYPE_KEY).toString();
290         const QString autoDetectionSource = qtversionMap.value("autodetectionSource").toString();
291         sdkVersions << autoDetectionSource;
292         int id = -1; // see BaseQtVersion::fromMap()
293         QtVersionFactory *factory = nullptr;
294         for (QtVersionFactory *f : factories) {
295             if (f->canRestore(type))
296                 factory = f;
297         }
298         if (!factory) {
299             qCDebug(log, "Warning: Unable to find factory for type '%s'", qPrintable(type));
300             continue;
301         }
302         // First try to find a existing Qt version to update
303         bool restored = false;
304         const VersionMap versionsCopy = m_versions; // m_versions is modified in loop
305         for (BaseQtVersion *v : versionsCopy) {
306             if (v->detectionSource() == autoDetectionSource) {
307                 id = v->uniqueId();
308                 qCDebug(log) << " Qt version found with same autodetection source" << autoDetectionSource << " => Migrating id:" << id;
309                 m_versions.remove(id);
310                 qtversionMap[Constants::QTVERSIONID] = id;
311                 qtversionMap[Constants::QTVERSIONNAME] = v->unexpandedDisplayName();
312                 delete v;
313 
314                 if (BaseQtVersion *qtv = factory->restore(type, qtversionMap)) {
315                     Q_ASSERT(qtv->isAutodetected());
316                     m_versions.insert(id, qtv);
317                     restored = true;
318                 }
319                 if (restored)
320                     changed << id;
321                 else
322                     removed << id;
323             }
324         }
325         // Create a new qtversion
326         if (!restored) { // didn't replace any existing versions
327             qCDebug(log) << " No Qt version found matching" << autoDetectionSource << " => Creating new version";
328             if (BaseQtVersion *qtv = factory->restore(type, qtversionMap)) {
329                 Q_ASSERT(qtv->isAutodetected());
330                 m_versions.insert(qtv->uniqueId(), qtv);
331                 added << qtv->uniqueId();
332                 restored = true;
333             }
334         }
335         if (!restored) {
336             qCDebug(log, "Warning: Unable to update qtversion '%s' from sdk installer.",
337                     qPrintable(autoDetectionSource));
338         }
339     }
340 
341     if (log().isDebugEnabled()) {
342         qCDebug(log) << "======= Before removing outdated sdk versions =======";
343         for (BaseQtVersion *version : qAsConst(m_versions)) {
344             qCDebug(log) << version->qmakeFilePath().toUserOutput() << "id:" << version->uniqueId();
345             qCDebug(log) << "  autodetection source:" << version->detectionSource();
346             qCDebug(log) << "";
347         }
348     }
349     const VersionMap versionsCopy = m_versions; // m_versions is modified in loop
350     for (BaseQtVersion *qtVersion : versionsCopy) {
351         if (qtVersion->detectionSource().startsWith("SDK.")) {
352             if (!sdkVersions.contains(qtVersion->detectionSource())) {
353                 qCDebug(log) << "  removing version" << qtVersion->detectionSource();
354                 m_versions.remove(qtVersion->uniqueId());
355                 removed << qtVersion->uniqueId();
356             }
357         }
358     }
359 
360     if (log().isDebugEnabled()) {
361         qCDebug(log)<< "======= End result =======";
362         for (BaseQtVersion *version : qAsConst(m_versions)) {
363             qCDebug(log) << version->qmakeFilePath().toUserOutput() << "id:" << version->uniqueId();
364             qCDebug(log) << "  autodetection source:" << version->detectionSource();
365             qCDebug(log) << "";
366         }
367     }
368     if (emitSignal)
369         emit qtVersionsChanged(added, removed, changed);
370 }
371 
saveQtVersions()372 static void saveQtVersions()
373 {
374     if (!m_writer)
375         return;
376 
377     QVariantMap data;
378     data.insert(QTVERSION_FILE_VERSION_KEY, 1);
379 
380     int count = 0;
381     for (BaseQtVersion *qtv : qAsConst(m_versions)) {
382         QVariantMap tmp = qtv->toMap();
383         if (tmp.isEmpty())
384             continue;
385         tmp.insert(QTVERSION_TYPE_KEY, qtv->type());
386         data.insert(QString::fromLatin1(QTVERSION_DATA_KEY) + QString::number(count), tmp);
387         ++count;
388     }
389     m_writer->save(data, Core::ICore::dialogParent());
390 }
391 
392 // Executes qtchooser with arguments in a process and returns its output
runQtChooser(const QString & qtchooser,const QStringList & arguments)393 static QList<QByteArray> runQtChooser(const QString &qtchooser, const QStringList &arguments)
394 {
395     QProcess p;
396     p.start(qtchooser, arguments);
397     p.waitForFinished();
398     const bool success = p.exitCode() == 0;
399     return success ? p.readAllStandardOutput().split('\n') : QList<QByteArray>();
400 }
401 
402 // Asks qtchooser for the qmake path of a given version
qmakePath(const QString & qtchooser,const QString & version)403 static QString qmakePath(const QString &qtchooser, const QString &version)
404 {
405     const QList<QByteArray> outputs = runQtChooser(qtchooser,
406                                                    {QStringLiteral("-qt=%1").arg(version),
407                                                     QStringLiteral("-print-env")});
408     for (const QByteArray &output : outputs) {
409         if (output.startsWith("QTTOOLDIR=\"")) {
410             QByteArray withoutVarName = output.mid(11); // remove QTTOOLDIR="
411             withoutVarName.chop(1); // remove trailing quote
412             return QStandardPaths::findExecutable(QStringLiteral("qmake"), QStringList()
413                                                   << QString::fromLocal8Bit(withoutVarName));
414         }
415     }
416     return QString();
417 }
418 
gatherQmakePathsFromQtChooser()419 static FilePaths gatherQmakePathsFromQtChooser()
420 {
421     const QString qtchooser = QStandardPaths::findExecutable(QStringLiteral("qtchooser"));
422     if (qtchooser.isEmpty())
423         return FilePaths();
424 
425     const QList<QByteArray> versions = runQtChooser(qtchooser, QStringList("-l"));
426     QSet<FilePath> foundQMakes;
427     for (const QByteArray &version : versions) {
428         FilePath possibleQMake = FilePath::fromString(
429                     qmakePath(qtchooser, QString::fromLocal8Bit(version)));
430         if (!possibleQMake.isEmpty())
431             foundQMakes << possibleQMake;
432     }
433     return Utils::toList(foundQMakes);
434 }
435 
findSystemQt()436 static void findSystemQt()
437 {
438     FilePaths systemQMakes
439             = BuildableHelperLibrary::findQtsInEnvironment(Environment::systemEnvironment());
440     systemQMakes.append(gatherQmakePathsFromQtChooser());
441     for (const FilePath &qmakePath : qAsConst(systemQMakes)) {
442         if (BuildableHelperLibrary::isQtChooser(qmakePath))
443             continue;
444         const auto isSameQmake = [qmakePath](const BaseQtVersion *version) {
445             return Environment::systemEnvironment().
446                     isSameExecutable(qmakePath.toString(), version->qmakeFilePath().toString());
447         };
448         if (contains(m_versions, isSameQmake))
449             continue;
450         BaseQtVersion *version = QtVersionFactory::createQtVersionFromQMakePath(qmakePath,
451                                                                                 false,
452                                                                                 "PATH");
453         if (version)
454             m_versions.insert(version->uniqueId(), version);
455     }
456 }
457 
addVersion(BaseQtVersion * version)458 void QtVersionManager::addVersion(BaseQtVersion *version)
459 {
460     QTC_ASSERT(m_writer, return);
461     QTC_ASSERT(version, return);
462     if (m_versions.contains(version->uniqueId()))
463         return;
464 
465     int uniqueId = version->uniqueId();
466     m_versions.insert(uniqueId, version);
467 
468     emit m_instance->qtVersionsChanged(QList<int>() << uniqueId, QList<int>(), QList<int>());
469     saveQtVersions();
470 }
471 
removeVersion(BaseQtVersion * version)472 void QtVersionManager::removeVersion(BaseQtVersion *version)
473 {
474     QTC_ASSERT(version, return);
475     m_versions.remove(version->uniqueId());
476     emit m_instance->qtVersionsChanged(QList<int>(), QList<int>() << version->uniqueId(), QList<int>());
477     saveQtVersions();
478     delete version;
479 }
480 
registerExampleSet(const QString & displayName,const QString & manifestPath,const QString & examplesPath)481 void QtVersionManager::registerExampleSet(const QString &displayName,
482                                           const QString &manifestPath,
483                                           const QString &examplesPath)
484 {
485     m_pluginRegisteredExampleSets.append({displayName, manifestPath, examplesPath});
486 }
487 
488 using Path = QString;
489 using FileName = QString;
documentationFiles(BaseQtVersion * v)490 static QList<std::pair<Path, FileName>> documentationFiles(BaseQtVersion *v)
491 {
492     QList<std::pair<Path, FileName>> files;
493     const QStringList docPaths = QStringList(
494         {v->docsPath().toString() + QChar('/'), v->docsPath().toString() + "/qch/"});
495     for (const QString &docPath : docPaths) {
496         const QDir versionHelpDir(docPath);
497         for (const QString &helpFile : versionHelpDir.entryList(QStringList("*.qch"), QDir::Files))
498             files.append({docPath, helpFile});
499     }
500     return files;
501 }
502 
documentationFiles(const QList<BaseQtVersion * > & vs,bool highestOnly=false)503 static QStringList documentationFiles(const QList<BaseQtVersion *> &vs, bool highestOnly = false)
504 {
505     // if highestOnly is true, register each file only once per major Qt version, even if
506     // multiple minor or patch releases of that major version are installed
507     QHash<int, QSet<QString>> includedFileNames; // major Qt version -> names
508     QSet<QString> filePaths;
509     const QList<BaseQtVersion *> versions = highestOnly ? QtVersionManager::sortVersions(vs) : vs;
510     for (BaseQtVersion *v : versions) {
511         const int majorVersion = v->qtVersion().majorVersion;
512         QSet<QString> &majorVersionFileNames = includedFileNames[majorVersion];
513         for (const std::pair<Path, FileName> &file : documentationFiles(v)) {
514             if (!highestOnly || !majorVersionFileNames.contains(file.second)) {
515                 filePaths.insert(file.first + file.second);
516                 majorVersionFileNames.insert(file.second);
517             }
518         }
519     }
520     return filePaths.values();
521 }
522 
updateDocumentation(const QList<BaseQtVersion * > & added,const QList<BaseQtVersion * > & removed,const QList<BaseQtVersion * > & allNew)523 void QtVersionManager::updateDocumentation(const QList<BaseQtVersion *> &added,
524                                            const QList<BaseQtVersion *> &removed,
525                                            const QList<BaseQtVersion *> &allNew)
526 {
527     const DocumentationSetting setting = documentationSetting();
528     const QStringList docsOfAll = setting == DocumentationSetting::None
529                                       ? QStringList()
530                                       : documentationFiles(allNew,
531                                                            setting
532                                                                == DocumentationSetting::HighestOnly);
533     const QStringList docsToRemove = Utils::filtered(documentationFiles(removed),
534                                                      [&docsOfAll](const QString &f) {
535                                                          return !docsOfAll.contains(f);
536                                                      });
537     const QStringList docsToAdd = Utils::filtered(documentationFiles(added),
538                                                   [&docsOfAll](const QString &f) {
539                                                       return docsOfAll.contains(f);
540                                                   });
541     Core::HelpManager::unregisterDocumentation(docsToRemove);
542     Core::HelpManager::registerDocumentation(docsToAdd);
543 }
544 
getUniqueId()545 int QtVersionManager::getUniqueId()
546 {
547     return m_idcount++;
548 }
549 
versions(const BaseQtVersion::Predicate & predicate)550 QList<BaseQtVersion *> QtVersionManager::versions(const BaseQtVersion::Predicate &predicate)
551 {
552     QList<BaseQtVersion *> versions;
553     QTC_ASSERT(isLoaded(), return versions);
554     if (predicate)
555         return Utils::filtered(m_versions.values(), predicate);
556     return m_versions.values();
557 }
558 
sortVersions(const QList<BaseQtVersion * > & input)559 QList<BaseQtVersion *> QtVersionManager::sortVersions(const QList<BaseQtVersion *> &input)
560 {
561     QList<BaseQtVersion *> result = input;
562     Utils::sort(result, qtVersionNumberCompare);
563     return result;
564 }
565 
version(int id)566 BaseQtVersion *QtVersionManager::version(int id)
567 {
568     QTC_ASSERT(isLoaded(), return nullptr);
569     VersionMap::const_iterator it = m_versions.constFind(id);
570     if (it == m_versions.constEnd())
571         return nullptr;
572     return it.value();
573 }
574 
version(const BaseQtVersion::Predicate & predicate)575 BaseQtVersion *QtVersionManager::version(const BaseQtVersion::Predicate &predicate)
576 {
577     return Utils::findOrDefault(m_versions.values(), predicate);
578 }
579 
580 // This function is really simplistic...
equals(BaseQtVersion * a,BaseQtVersion * b)581 static bool equals(BaseQtVersion *a, BaseQtVersion *b)
582 {
583     return a->equals(b);
584 }
585 
setNewQtVersions(const QList<BaseQtVersion * > & newVersions)586 void QtVersionManager::setNewQtVersions(const QList<BaseQtVersion *> &newVersions)
587 {
588     // We want to preserve the same order as in the settings dialog
589     // so we sort a copy
590     QList<BaseQtVersion *> sortedNewVersions = newVersions;
591     Utils::sort(sortedNewVersions, &BaseQtVersion::uniqueId);
592 
593     QList<BaseQtVersion *> addedVersions;
594     QList<BaseQtVersion *> removedVersions;
595     QList<std::pair<BaseQtVersion *, BaseQtVersion *>> changedVersions;
596     // So we trying to find the minimal set of changed versions,
597     // iterate over both sorted list
598 
599     // newVersions and oldVersions iterator
600     QList<BaseQtVersion *>::const_iterator nit, nend;
601     VersionMap::const_iterator oit, oend;
602     nit = sortedNewVersions.constBegin();
603     nend = sortedNewVersions.constEnd();
604     oit = m_versions.constBegin();
605     oend = m_versions.constEnd();
606 
607     while (nit != nend && oit != oend) {
608         int nid = (*nit)->uniqueId();
609         int oid = (*oit)->uniqueId();
610         if (nid < oid) {
611             addedVersions.push_back(*nit);
612             ++nit;
613         } else if (oid < nid) {
614             removedVersions.push_back(*oit);
615             ++oit;
616         } else {
617             if (!equals(*oit, *nit))
618                 changedVersions.push_back({*oit, *nit});
619             ++oit;
620             ++nit;
621         }
622     }
623 
624     while (nit != nend) {
625         addedVersions.push_back(*nit);
626         ++nit;
627     }
628 
629     while (oit != oend) {
630         removedVersions.push_back(*oit);
631         ++oit;
632     }
633 
634     if (!changedVersions.isEmpty() || !addedVersions.isEmpty() || !removedVersions.isEmpty()) {
635         const QList<BaseQtVersion *> changedOldVersions
636             = Utils::transform(changedVersions, &std::pair<BaseQtVersion *, BaseQtVersion *>::first);
637         const QList<BaseQtVersion *> changedNewVersions
638             = Utils::transform(changedVersions,
639                                &std::pair<BaseQtVersion *, BaseQtVersion *>::second);
640         updateDocumentation(addedVersions + changedNewVersions,
641                             removedVersions + changedOldVersions,
642                             sortedNewVersions);
643     }
644     const QList<int> addedIds = Utils::transform(addedVersions, &BaseQtVersion::uniqueId);
645     const QList<int> removedIds = Utils::transform(removedVersions, &BaseQtVersion::uniqueId);
646     const QList<int> changedIds = Utils::transform(changedVersions,
647                                                    [](std::pair<BaseQtVersion *, BaseQtVersion *> v) {
648                                                        return v.first->uniqueId();
649                                                    });
650 
651     qDeleteAll(m_versions);
652     m_versions = Utils::transform<VersionMap>(sortedNewVersions, [](BaseQtVersion *v) {
653         return std::make_pair(v->uniqueId(), v);
654     });
655     saveQtVersions();
656 
657     if (!changedVersions.isEmpty() || !addedVersions.isEmpty() || !removedVersions.isEmpty())
658         emit m_instance->qtVersionsChanged(addedIds, removedIds, changedIds);
659 }
660 
setDocumentationSetting(const QtVersionManager::DocumentationSetting & setting)661 void QtVersionManager::setDocumentationSetting(const QtVersionManager::DocumentationSetting &setting)
662 {
663     if (setting == documentationSetting())
664         return;
665     Core::ICore::settings()->setValueWithDefault(DOCUMENTATION_SETTING_KEY, int(setting), 0);
666     // force re-evaluating which documentation should be registered
667     // by claiming that all are removed and re-added
668     const QList<BaseQtVersion *> vs = versions();
669     updateDocumentation(vs, vs, vs);
670 }
671 
documentationSetting()672 QtVersionManager::DocumentationSetting QtVersionManager::documentationSetting()
673 {
674     return DocumentationSetting(
675         Core::ICore::settings()->value(DOCUMENTATION_SETTING_KEY, 0).toInt());
676 }
677 
678 } // namespace QtVersion
679