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