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 "qtkitinformation.h"
27 
28 #include "qtsupportconstants.h"
29 #include "qtversionmanager.h"
30 #include "qtparser.h"
31 #include "qttestparser.h"
32 
33 #include <projectexplorer/projectexplorerconstants.h>
34 #include <projectexplorer/task.h>
35 #include <projectexplorer/toolchain.h>
36 #include <projectexplorer/toolchainmanager.h>
37 
38 #include <utils/algorithm.h>
39 #include <utils/buildablehelperlibrary.h>
40 #include <utils/layoutbuilder.h>
41 #include <utils/macroexpander.h>
42 #include <utils/qtcassert.h>
43 
44 #include <QComboBox>
45 
46 using namespace ProjectExplorer;
47 using namespace Utils;
48 
49 namespace QtSupport {
50 namespace Internal {
51 
52 class QtKitAspectWidget final : public KitAspectWidget
53 {
54     Q_DECLARE_TR_FUNCTIONS(QtSupport::QtKitAspectWidget)
55 public:
QtKitAspectWidget(Kit * k,const KitAspect * ki)56     QtKitAspectWidget(Kit *k, const KitAspect *ki) : KitAspectWidget(k, ki)
57     {
58         m_combo = createSubWidget<QComboBox>();
59         m_combo->setSizePolicy(QSizePolicy::Ignored, m_combo->sizePolicy().verticalPolicy());
60         m_combo->addItem(tr("None"), -1);
61 
62         QList<int> versionIds = Utils::transform(QtVersionManager::versions(), &BaseQtVersion::uniqueId);
63         versionsChanged(versionIds, QList<int>(), QList<int>());
64 
65         m_manageButton = createManageButton(Constants::QTVERSION_SETTINGS_PAGE_ID);
66 
67         refresh();
68         m_combo->setToolTip(ki->description());
69 
70         connect(m_combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
71                 this, &QtKitAspectWidget::currentWasChanged);
72 
73         connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsChanged,
74                 this, &QtKitAspectWidget::versionsChanged);
75     }
76 
~QtKitAspectWidget()77     ~QtKitAspectWidget() final
78     {
79         delete m_combo;
80         delete m_manageButton;
81     }
82 
83 private:
makeReadOnly()84     void makeReadOnly() final { m_combo->setEnabled(false); }
85 
addToLayout(LayoutBuilder & builder)86     void addToLayout(LayoutBuilder &builder)
87     {
88         addMutableAction(m_combo);
89         builder.addItem(m_combo);
90         builder.addItem(m_manageButton);
91     }
92 
refresh()93     void refresh() final
94     {
95         m_combo->setCurrentIndex(findQtVersion(QtKitAspect::qtVersionId(m_kit)));
96     }
97 
98 private:
itemNameFor(const BaseQtVersion * v)99     static QString itemNameFor(const BaseQtVersion *v)
100     {
101         QTC_ASSERT(v, return QString());
102         QString name = v->displayName();
103         if (!v->isValid())
104             name = tr("%1 (invalid)").arg(v->displayName());
105         return name;
106     }
107 
versionsChanged(const QList<int> & added,const QList<int> & removed,const QList<int> & changed)108     void versionsChanged(const QList<int> &added, const QList<int> &removed, const QList<int> &changed)
109     {
110         for (const int id : added) {
111             BaseQtVersion *v = QtVersionManager::version(id);
112             QTC_CHECK(v);
113             QTC_CHECK(findQtVersion(id) < 0);
114             m_combo->addItem(itemNameFor(v), id);
115         }
116         for (const int id : removed) {
117             int pos = findQtVersion(id);
118             if (pos >= 0) // We do not include invalid Qt versions, so do not try to remove those.
119                 m_combo->removeItem(pos);
120         }
121         for (const int id : changed) {
122             BaseQtVersion *v = QtVersionManager::version(id);
123             int pos = findQtVersion(id);
124             QTC_CHECK(pos >= 0);
125             m_combo->setItemText(pos, itemNameFor(v));
126         }
127     }
128 
currentWasChanged(int idx)129     void currentWasChanged(int idx)
130     {
131         QtKitAspect::setQtVersionId(m_kit, m_combo->itemData(idx).toInt());
132     }
133 
findQtVersion(const int id) const134     int findQtVersion(const int id) const
135     {
136         for (int i = 0; i < m_combo->count(); ++i) {
137             if (id == m_combo->itemData(i).toInt())
138                 return i;
139         }
140         return -1;
141     }
142 
143     QComboBox *m_combo;
144     QWidget *m_manageButton;
145 };
146 } // namespace Internal
147 
QtKitAspect()148 QtKitAspect::QtKitAspect()
149 {
150     setObjectName(QLatin1String("QtKitAspect"));
151     setId(QtKitAspect::id());
152     setDisplayName(tr("Qt version"));
153     setDescription(tr("The Qt library to use for all projects using this kit.<br>"
154                       "A Qt version is required for qmake-based projects "
155                       "and optional when using other build systems."));
156     setPriority(26000);
157 
158     connect(KitManager::instance(), &KitManager::kitsLoaded,
159             this, &QtKitAspect::kitsWereLoaded);
160 }
161 
setup(Kit * k)162 void QtKitAspect::setup(Kit *k)
163 {
164     if (!k || k->hasValue(id()))
165         return;
166     const Abi tcAbi = ToolChainKitAspect::targetAbi(k);
167     const Id deviceType = DeviceTypeKitAspect::deviceTypeId(k);
168 
169     const QList<BaseQtVersion *> matches
170             = QtVersionManager::versions([&tcAbi, &deviceType](const BaseQtVersion *qt) {
171         return qt->targetDeviceTypes().contains(deviceType)
172                 && Utils::contains(qt->qtAbis(), [&tcAbi](const Abi &qtAbi) {
173             return qtAbi.isCompatibleWith(tcAbi); });
174     });
175     if (matches.empty())
176         return;
177 
178     // An MSVC 2015 toolchain is compatible with an MSVC 2017 Qt, but we prefer an
179     // MSVC 2015 Qt if we find one.
180     const QList<BaseQtVersion *> exactMatches = Utils::filtered(matches,
181                                                                 [&tcAbi](const BaseQtVersion *qt) {
182         return qt->qtAbis().contains(tcAbi);
183     });
184     const QList<BaseQtVersion *> &candidates = !exactMatches.empty() ? exactMatches : matches;
185 
186     BaseQtVersion * const qtFromPath = QtVersionManager::version(
187                 equal(&BaseQtVersion::detectionSource, QString::fromLatin1("PATH")));
188     if (qtFromPath && candidates.contains(qtFromPath))
189         k->setValue(id(), qtFromPath->uniqueId());
190     else
191         k->setValue(id(), candidates.first()->uniqueId());
192 }
193 
validate(const Kit * k) const194 Tasks QtKitAspect::validate(const Kit *k) const
195 {
196     QTC_ASSERT(QtVersionManager::isLoaded(), return { });
197     BaseQtVersion *version = qtVersion(k);
198     if (!version)
199     return { };
200 
201     return version->validateKit(k);
202 }
203 
fix(Kit * k)204 void QtKitAspect::fix(Kit *k)
205 {
206     QTC_ASSERT(QtVersionManager::isLoaded(), return);
207     BaseQtVersion *version = qtVersion(k);
208     if (!version) {
209         if (qtVersionId(k) >= 0) {
210             qWarning("Qt version is no longer known, removing from kit \"%s\".",
211                      qPrintable(k->displayName()));
212             setQtVersionId(k, -1);
213         }
214         return;
215     }
216 
217     // Set a matching toolchain if we don't have one.
218     if (ToolChainKitAspect::cxxToolChain(k))
219         return;
220 
221     const QString spec = version->mkspec();
222     QList<ToolChain *> possibleTcs = ToolChainManager::toolChains([version](const ToolChain *t) {
223         if (!t->isValid() || t->language() != ProjectExplorer::Constants::CXX_LANGUAGE_ID)
224             return false;
225         return Utils::anyOf(version->qtAbis(), [t](const Abi &qtAbi) {
226             return t->supportedAbis().contains(qtAbi)
227                    && t->targetAbi().wordWidth() == qtAbi.wordWidth()
228                    && t->targetAbi().architecture() == qtAbi.architecture();
229         });
230     });
231     if (!possibleTcs.isEmpty()) {
232         // Prefer exact matches.
233         // TODO: We should probably prefer the compiler with the highest version number instead,
234         //       but this information is currently not exposed by the ToolChain class.
235         sort(possibleTcs, [version](const ToolChain *tc1, const ToolChain *tc2) {
236             const QVector<Abi> &qtAbis = version->qtAbis();
237             const bool tc1ExactMatch = qtAbis.contains(tc1->targetAbi());
238             const bool tc2ExactMatch = qtAbis.contains(tc2->targetAbi());
239             if (tc1ExactMatch && !tc2ExactMatch)
240                 return true;
241             if (!tc1ExactMatch && tc2ExactMatch)
242                 return false;
243             return tc1->hostPrefersToolchain() && !tc2->hostPrefersToolchain();
244         });
245 
246         const QList<ToolChain *> goodTcs = Utils::filtered(possibleTcs,
247                                                            [&spec](const ToolChain *t) {
248             return t->suggestedMkspecList().contains(spec);
249         });
250         // Hack to prefer a tool chain from PATH (e.g. autodetected) over other matches.
251         // This improves the situation a bit if a cross-compilation tool chain has the
252         // same ABI as the host.
253         const Environment systemEnvironment = Environment::systemEnvironment();
254         ToolChain *bestTc = Utils::findOrDefault(goodTcs,
255                                                  [&systemEnvironment](const ToolChain *t) {
256             return systemEnvironment.path().contains(t->compilerCommand().parentDir());
257         });
258         if (!bestTc) {
259             bestTc = goodTcs.isEmpty() ? possibleTcs.first() : goodTcs.first();
260         }
261         if (bestTc)
262             ToolChainKitAspect::setAllToolChainsToMatch(k, bestTc);
263     }
264 }
265 
createConfigWidget(Kit * k) const266 KitAspectWidget *QtKitAspect::createConfigWidget(Kit *k) const
267 {
268     QTC_ASSERT(k, return nullptr);
269     return new Internal::QtKitAspectWidget(k, this);
270 }
271 
displayNamePostfix(const Kit * k) const272 QString QtKitAspect::displayNamePostfix(const Kit *k) const
273 {
274     BaseQtVersion *version = qtVersion(k);
275     return version ? version->displayName() : QString();
276 }
277 
toUserOutput(const Kit * k) const278 KitAspect::ItemList QtKitAspect::toUserOutput(const Kit *k) const
279 {
280     BaseQtVersion *version = qtVersion(k);
281     return {{tr("Qt version"), version ? version->displayName() : tr("None")}};
282 }
283 
addToBuildEnvironment(const Kit * k,Environment & env) const284 void QtKitAspect::addToBuildEnvironment(const Kit *k, Environment &env) const
285 {
286     BaseQtVersion *version = qtVersion(k);
287     if (version)
288         version->addToEnvironment(k, env);
289 }
290 
createOutputParsers(const Kit * k) const291 QList<OutputLineParser *> QtKitAspect::createOutputParsers(const Kit *k) const
292 {
293     if (qtVersion(k))
294         return {new Internal::QtTestParser, new QtParser};
295     return {};
296 }
297 
298 class QtMacroSubProvider
299 {
300 public:
QtMacroSubProvider(Kit * kit)301     QtMacroSubProvider(Kit *kit)
302         : expander(BaseQtVersion::createMacroExpander(
303               [kit] { return QtKitAspect::qtVersion(kit); }))
304     {}
305 
operator ()() const306     MacroExpander *operator()() const
307     {
308         return expander.get();
309     }
310 
311     std::shared_ptr<MacroExpander> expander;
312 };
313 
addToMacroExpander(Kit * kit,MacroExpander * expander) const314 void QtKitAspect::addToMacroExpander(Kit *kit, MacroExpander *expander) const
315 {
316     QTC_ASSERT(kit, return);
317     expander->registerSubProvider(QtMacroSubProvider(kit));
318 
319     expander->registerVariable("Qt:Name", tr("Name of Qt Version"),
320                 [kit]() -> QString {
321                    BaseQtVersion *version = qtVersion(kit);
322                    return version ? version->displayName() : tr("unknown");
323                 });
324     expander->registerVariable("Qt:qmakeExecutable", tr("Path to the qmake executable"),
325                 [kit]() -> QString {
326                     BaseQtVersion *version = qtVersion(kit);
327                     return version ? version->qmakeFilePath().path() : QString();
328                 });
329 }
330 
id()331 Id QtKitAspect::id()
332 {
333     return "QtSupport.QtInformation";
334 }
335 
qtVersionId(const Kit * k)336 int QtKitAspect::qtVersionId(const Kit *k)
337 {
338     if (!k)
339         return -1;
340 
341     int id = -1;
342     QVariant data = k->value(QtKitAspect::id(), -1);
343     if (data.type() == QVariant::Int) {
344         bool ok;
345         id = data.toInt(&ok);
346         if (!ok)
347             id = -1;
348     } else {
349         QString source = data.toString();
350         BaseQtVersion *v = QtVersionManager::version([source](const BaseQtVersion *v) { return v->detectionSource() == source; });
351         if (v)
352             id = v->uniqueId();
353     }
354     return id;
355 }
356 
setQtVersionId(Kit * k,const int id)357 void QtKitAspect::setQtVersionId(Kit *k, const int id)
358 {
359     QTC_ASSERT(k, return);
360     k->setValue(QtKitAspect::id(), id);
361 }
362 
qtVersion(const Kit * k)363 BaseQtVersion *QtKitAspect::qtVersion(const Kit *k)
364 {
365     return QtVersionManager::version(qtVersionId(k));
366 }
367 
setQtVersion(Kit * k,const BaseQtVersion * v)368 void QtKitAspect::setQtVersion(Kit *k, const BaseQtVersion *v)
369 {
370     if (!v)
371         setQtVersionId(k, -1);
372     else
373         setQtVersionId(k, v->uniqueId());
374 }
375 
376 /*!
377  * Helper function that prepends the directory containing the C++ toolchain and Qt
378  * binaries to PATH. This is used to in build configurations targeting broken build
379  * systems to provide hints about which binaries to use.
380  */
381 
addHostBinariesToPath(const Kit * k,Environment & env)382 void QtKitAspect::addHostBinariesToPath(const Kit *k, Environment &env)
383 {
384     if (const ToolChain *tc = ToolChainKitAspect::cxxToolChain(k)) {
385         const FilePath compilerDir = tc->compilerCommand().parentDir();
386         if (!compilerDir.isEmpty())
387             env.prependOrSetPath(compilerDir.toString());
388     }
389 
390     if (const BaseQtVersion *qt = qtVersion(k)) {
391         const FilePath hostBinPath = qt->hostBinPath();
392         if (!hostBinPath.isEmpty())
393             env.prependOrSetPath(hostBinPath.toString());
394     }
395 }
396 
qtVersionsChanged(const QList<int> & addedIds,const QList<int> & removedIds,const QList<int> & changedIds)397 void QtKitAspect::qtVersionsChanged(const QList<int> &addedIds,
398                                     const QList<int> &removedIds,
399                                     const QList<int> &changedIds)
400 {
401     Q_UNUSED(addedIds)
402     Q_UNUSED(removedIds)
403     for (Kit *k : KitManager::kits()) {
404         if (changedIds.contains(qtVersionId(k))) {
405             k->validate(); // Qt version may have become (in)valid
406             notifyAboutUpdate(k);
407         }
408     }
409 }
410 
kitsWereLoaded()411 void QtKitAspect::kitsWereLoaded()
412 {
413     for (Kit *k : KitManager::kits())
414         fix(k);
415 
416     connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsChanged,
417             this, &QtKitAspect::qtVersionsChanged);
418 }
419 
platformPredicate(Id platform)420 Kit::Predicate QtKitAspect::platformPredicate(Id platform)
421 {
422     return [platform](const Kit *kit) -> bool {
423         BaseQtVersion *version = QtKitAspect::qtVersion(kit);
424         return version && version->targetDeviceTypes().contains(platform);
425     };
426 }
427 
qtVersionPredicate(const QSet<Id> & required,const QtVersionNumber & min,const QtVersionNumber & max)428 Kit::Predicate QtKitAspect::qtVersionPredicate(const QSet<Id> &required,
429                                                const QtVersionNumber &min,
430                                                const QtVersionNumber &max)
431 {
432     return [required, min, max](const Kit *kit) -> bool {
433         BaseQtVersion *version = QtKitAspect::qtVersion(kit);
434         if (!version)
435             return false;
436         QtVersionNumber current = version->qtVersion();
437         if (min.majorVersion > -1 && current < min)
438             return false;
439         if (max.majorVersion > -1 && current > max)
440             return false;
441         return version->features().contains(required);
442     };
443 }
444 
supportedPlatforms(const Kit * k) const445 QSet<Id> QtKitAspect::supportedPlatforms(const Kit *k) const
446 {
447     BaseQtVersion *version = QtKitAspect::qtVersion(k);
448     return version ? version->targetDeviceTypes() : QSet<Id>();
449 }
450 
availableFeatures(const Kit * k) const451 QSet<Id> QtKitAspect::availableFeatures(const Kit *k) const
452 {
453     BaseQtVersion *version = QtKitAspect::qtVersion(k);
454     return version ? version->features() : QSet<Id>();
455 }
456 
weight(const Kit * k) const457 int QtKitAspect::weight(const Kit *k) const
458 {
459     const BaseQtVersion * const qt = qtVersion(k);
460     if (!qt)
461         return 0;
462     if (!qt->targetDeviceTypes().contains(DeviceTypeKitAspect::deviceTypeId(k)))
463         return 0;
464     const Abi tcAbi = ToolChainKitAspect::targetAbi(k);
465     if (qt->qtAbis().contains(tcAbi))
466         return 2;
467     return Utils::contains(qt->qtAbis(), [&tcAbi](const Abi &qtAbi) {
468         return qtAbi.isCompatibleWith(tcAbi); }) ? 1 : 0;
469 }
470 
id()471 Id SuppliesQtQuickImportPath::id()
472 {
473     return QtSupport::Constants::FLAGS_SUPPLIES_QTQUICK_IMPORT_PATH;
474 }
475 
id()476 Id KitQmlImportPath::id()
477 {
478     return QtSupport::Constants::KIT_QML_IMPORT_PATH;
479 }
480 
id()481 Id KitHasMergedHeaderPathsWithQmlImportPaths::id()
482 {
483     return QtSupport::Constants::KIT_HAS_MERGED_HEADER_PATHS_WITH_QML_IMPORT_PATHS;
484 }
485 
486 } // namespace QtSupport
487