1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Canonical 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 "cmakekitinformation.h"
27
28 #include "cmakeprojectconstants.h"
29 #include "cmakeprojectplugin.h"
30 #include "cmakespecificsettings.h"
31 #include "cmaketool.h"
32 #include "cmaketoolmanager.h"
33
34 #include <app/app_version.h>
35
36 #include <coreplugin/icore.h>
37
38 #include <ios/iosconstants.h>
39
40 #include <projectexplorer/kitinformation.h>
41 #include <projectexplorer/projectexplorer.h>
42 #include <projectexplorer/projectexplorerconstants.h>
43 #include <projectexplorer/projectexplorersettings.h>
44 #include <projectexplorer/task.h>
45 #include <projectexplorer/toolchain.h>
46 #include <projectexplorer/devicesupport/idevice.h>
47
48 #include <qtsupport/baseqtversion.h>
49 #include <qtsupport/qtkitinformation.h>
50
51 #include <utils/algorithm.h>
52 #include <utils/elidinglabel.h>
53 #include <utils/environment.h>
54 #include <utils/layoutbuilder.h>
55 #include <utils/macroexpander.h>
56 #include <utils/qtcassert.h>
57 #include <utils/variablechooser.h>
58
59 #include <QComboBox>
60 #include <QDialog>
61 #include <QDialogButtonBox>
62 #include <QGridLayout>
63 #include <QLineEdit>
64 #include <QPlainTextEdit>
65 #include <QPointer>
66 #include <QPushButton>
67
68 using namespace ProjectExplorer;
69 using namespace Utils;
70
71 namespace CMakeProjectManager {
72 // --------------------------------------------------------------------
73 // CMakeKitAspect:
74 // --------------------------------------------------------------------
75
isIos(const Kit * k)76 static bool isIos(const Kit *k)
77 {
78 const Id deviceType = DeviceTypeKitAspect::deviceTypeId(k);
79 return deviceType == Ios::Constants::IOS_DEVICE_TYPE
80 || deviceType == Ios::Constants::IOS_SIMULATOR_TYPE;
81 }
82
defaultCMakeToolId()83 static Id defaultCMakeToolId()
84 {
85 CMakeTool *defaultTool = CMakeToolManager::defaultCMakeTool();
86 return defaultTool ? defaultTool->id() : Id();
87 }
88
89 const char TOOL_ID[] = "CMakeProjectManager.CMakeKitInformation";
90
91 class CMakeKitAspectWidget final : public KitAspectWidget
92 {
93 Q_DECLARE_TR_FUNCTIONS(CMakeProjectManager::Internal::CMakeKitAspect)
94 public:
CMakeKitAspectWidget(Kit * kit,const KitAspect * ki)95 CMakeKitAspectWidget(Kit *kit, const KitAspect *ki) : KitAspectWidget(kit, ki),
96 m_comboBox(createSubWidget<QComboBox>()),
97 m_manageButton(createManageButton(Constants::CMAKE_SETTINGS_PAGE_ID))
98 {
99 m_comboBox->setSizePolicy(QSizePolicy::Ignored, m_comboBox->sizePolicy().verticalPolicy());
100 m_comboBox->setEnabled(false);
101 m_comboBox->setToolTip(ki->description());
102
103 foreach (CMakeTool *tool, CMakeToolManager::cmakeTools())
104 cmakeToolAdded(tool->id());
105
106 updateComboBox();
107 refresh();
108 connect(m_comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
109 this, &CMakeKitAspectWidget::currentCMakeToolChanged);
110
111 CMakeToolManager *cmakeMgr = CMakeToolManager::instance();
112 connect(cmakeMgr, &CMakeToolManager::cmakeAdded,
113 this, &CMakeKitAspectWidget::cmakeToolAdded);
114 connect(cmakeMgr, &CMakeToolManager::cmakeRemoved,
115 this, &CMakeKitAspectWidget::cmakeToolRemoved);
116 connect(cmakeMgr, &CMakeToolManager::cmakeUpdated,
117 this, &CMakeKitAspectWidget::cmakeToolUpdated);
118 }
119
~CMakeKitAspectWidget()120 ~CMakeKitAspectWidget() override
121 {
122 delete m_comboBox;
123 delete m_manageButton;
124 }
125
126 private:
127 // KitAspectWidget interface
makeReadOnly()128 void makeReadOnly() override { m_comboBox->setEnabled(false); }
129
addToLayout(LayoutBuilder & builder)130 void addToLayout(LayoutBuilder &builder) override
131 {
132 addMutableAction(m_comboBox);
133 builder.addItem(m_comboBox);
134 builder.addItem(m_manageButton);
135 }
136
refresh()137 void refresh() override
138 {
139 CMakeTool *tool = CMakeKitAspect::cmakeTool(m_kit);
140 m_comboBox->setCurrentIndex(tool ? indexOf(tool->id()) : -1);
141 }
142
indexOf(Id id)143 int indexOf(Id id)
144 {
145 for (int i = 0; i < m_comboBox->count(); ++i) {
146 if (id == Id::fromSetting(m_comboBox->itemData(i)))
147 return i;
148 }
149 return -1;
150 }
151
updateComboBox()152 void updateComboBox()
153 {
154 // remove unavailable cmake tool:
155 int pos = indexOf(Id());
156 if (pos >= 0)
157 m_comboBox->removeItem(pos);
158
159 if (m_comboBox->count() == 0) {
160 m_comboBox->addItem(tr("<No CMake Tool available>"), Id().toSetting());
161 m_comboBox->setEnabled(false);
162 } else {
163 m_comboBox->setEnabled(true);
164 }
165 }
166
cmakeToolAdded(Id id)167 void cmakeToolAdded(Id id)
168 {
169 const CMakeTool *tool = CMakeToolManager::findById(id);
170 QTC_ASSERT(tool, return);
171
172 m_comboBox->addItem(tool->displayName(), tool->id().toSetting());
173 updateComboBox();
174 refresh();
175 }
176
cmakeToolUpdated(Id id)177 void cmakeToolUpdated(Id id)
178 {
179 const int pos = indexOf(id);
180 QTC_ASSERT(pos >= 0, return);
181
182 const CMakeTool *tool = CMakeToolManager::findById(id);
183 QTC_ASSERT(tool, return);
184
185 m_comboBox->setItemText(pos, tool->displayName());
186 }
187
cmakeToolRemoved(Id id)188 void cmakeToolRemoved(Id id)
189 {
190 const int pos = indexOf(id);
191 QTC_ASSERT(pos >= 0, return);
192
193 // do not handle the current index changed signal
194 m_removingItem = true;
195 m_comboBox->removeItem(pos);
196 m_removingItem = false;
197
198 // update the checkbox and set the current index
199 updateComboBox();
200 refresh();
201 }
202
currentCMakeToolChanged(int index)203 void currentCMakeToolChanged(int index)
204 {
205 if (m_removingItem)
206 return;
207
208 const Id id = Id::fromSetting(m_comboBox->itemData(index));
209 CMakeKitAspect::setCMakeTool(m_kit, id);
210 }
211
212 bool m_removingItem = false;
213 QComboBox *m_comboBox;
214 QWidget *m_manageButton;
215 };
216
CMakeKitAspect()217 CMakeKitAspect::CMakeKitAspect()
218 {
219 setObjectName(QLatin1String("CMakeKitAspect"));
220 setId(TOOL_ID);
221 setDisplayName(tr("CMake Tool"));
222 setDescription(tr("The CMake Tool to use when building a project with CMake.<br>"
223 "This setting is ignored when using other build systems."));
224 setPriority(20000);
225
226 //make sure the default value is set if a selected CMake is removed
227 connect(CMakeToolManager::instance(), &CMakeToolManager::cmakeRemoved,
228 [this] { for (Kit *k : KitManager::kits()) fix(k); });
229
230 //make sure the default value is set if a new default CMake is set
231 connect(CMakeToolManager::instance(), &CMakeToolManager::defaultCMakeChanged,
232 [this] { for (Kit *k : KitManager::kits()) fix(k); });
233 }
234
id()235 Id CMakeKitAspect::id()
236 {
237 return TOOL_ID;
238 }
239
cmakeToolId(const Kit * k)240 Id CMakeKitAspect::cmakeToolId(const Kit *k)
241 {
242 if (!k)
243 return {};
244 return Id::fromSetting(k->value(TOOL_ID));
245 }
246
cmakeTool(const Kit * k)247 CMakeTool *CMakeKitAspect::cmakeTool(const Kit *k)
248 {
249 return CMakeToolManager::findById(cmakeToolId(k));
250 }
251
setCMakeTool(Kit * k,const Id id)252 void CMakeKitAspect::setCMakeTool(Kit *k, const Id id)
253 {
254 const Id toSet = id.isValid() ? id : defaultCMakeToolId();
255 QTC_ASSERT(!id.isValid() || CMakeToolManager::findById(toSet), return);
256 if (k)
257 k->setValue(TOOL_ID, toSet.toSetting());
258 }
259
validate(const Kit * k) const260 Tasks CMakeKitAspect::validate(const Kit *k) const
261 {
262 Tasks result;
263 CMakeTool *tool = CMakeKitAspect::cmakeTool(k);
264 if (tool) {
265 CMakeTool::Version version = tool->version();
266 if (version.major < 3 || (version.major == 3 && version.minor < 14)) {
267 result << BuildSystemTask(Task::Warning, msgUnsupportedVersion(version.fullVersion));
268 }
269 }
270 return result;
271 }
272
setup(Kit * k)273 void CMakeKitAspect::setup(Kit *k)
274 {
275 CMakeTool *tool = CMakeKitAspect::cmakeTool(k);
276 if (tool)
277 return;
278
279 // Look for a suitable auto-detected one:
280 const QString kitSource = k->autoDetectionSource();
281 for (CMakeTool *tool : CMakeToolManager::cmakeTools()) {
282 const QString toolSource = tool->detectionSource();
283 if (!toolSource.isEmpty() && toolSource == kitSource) {
284 setCMakeTool(k, tool->id());
285 return;
286 }
287 }
288
289 setCMakeTool(k, defaultCMakeToolId());
290 }
291
fix(Kit * k)292 void CMakeKitAspect::fix(Kit *k)
293 {
294 setup(k);
295 }
296
toUserOutput(const Kit * k) const297 KitAspect::ItemList CMakeKitAspect::toUserOutput(const Kit *k) const
298 {
299 const CMakeTool *const tool = cmakeTool(k);
300 return {{tr("CMake"), tool ? tool->displayName() : tr("Unconfigured")}};
301 }
302
createConfigWidget(Kit * k) const303 KitAspectWidget *CMakeKitAspect::createConfigWidget(Kit *k) const
304 {
305 QTC_ASSERT(k, return nullptr);
306 return new CMakeKitAspectWidget(k, this);
307 }
308
addToMacroExpander(Kit * k,MacroExpander * expander) const309 void CMakeKitAspect::addToMacroExpander(Kit *k, MacroExpander *expander) const
310 {
311 QTC_ASSERT(k, return);
312 expander->registerFileVariables("CMake:Executable", tr("Path to the cmake executable"),
313 [k] {
314 CMakeTool *tool = CMakeKitAspect::cmakeTool(k);
315 return tool ? tool->cmakeExecutable() : FilePath();
316 });
317 }
318
availableFeatures(const Kit * k) const319 QSet<Id> CMakeKitAspect::availableFeatures(const Kit *k) const
320 {
321 if (cmakeTool(k))
322 return { CMakeProjectManager::Constants::CMAKE_FEATURE_ID };
323 return {};
324 }
325
msgUnsupportedVersion(const QByteArray & versionString)326 QString CMakeKitAspect::msgUnsupportedVersion(const QByteArray &versionString)
327 {
328 return tr("CMake version %1 is unsupported. Please update to "
329 "version 3.14 (with file-api) or later.")
330 .arg(QString::fromUtf8(versionString));
331 }
332
333 // --------------------------------------------------------------------
334 // CMakeGeneratorKitAspect:
335 // --------------------------------------------------------------------
336
337 const char GENERATOR_ID[] = "CMake.GeneratorKitInformation";
338
339 const char GENERATOR_KEY[] = "Generator";
340 const char EXTRA_GENERATOR_KEY[] = "ExtraGenerator";
341 const char PLATFORM_KEY[] = "Platform";
342 const char TOOLSET_KEY[] = "Toolset";
343
344 class CMakeGeneratorKitAspectWidget final : public KitAspectWidget
345 {
346 Q_DECLARE_TR_FUNCTIONS(CMakeProjectManager::Internal::CMakeGeneratorKitAspect)
347
348 public:
CMakeGeneratorKitAspectWidget(Kit * kit,const KitAspect * ki)349 CMakeGeneratorKitAspectWidget(Kit *kit, const KitAspect *ki)
350 : KitAspectWidget(kit, ki),
351 m_label(createSubWidget<ElidingLabel>()),
352 m_changeButton(createSubWidget<QPushButton>())
353 {
354 m_label->setToolTip(ki->description());
355 m_changeButton->setText(tr("Change..."));
356 refresh();
357 connect(m_changeButton, &QPushButton::clicked,
358 this, &CMakeGeneratorKitAspectWidget::changeGenerator);
359 }
360
~CMakeGeneratorKitAspectWidget()361 ~CMakeGeneratorKitAspectWidget() override
362 {
363 delete m_label;
364 delete m_changeButton;
365 }
366
367 private:
368 // KitAspectWidget interface
makeReadOnly()369 void makeReadOnly() override { m_changeButton->setEnabled(false); }
370
addToLayout(LayoutBuilder & builder)371 void addToLayout(LayoutBuilder &builder) override
372 {
373 addMutableAction(m_label);
374 builder.addItem(m_label);
375 builder.addItem(m_changeButton);
376 }
377
refresh()378 void refresh() override
379 {
380 if (m_ignoreChange)
381 return;
382
383 CMakeTool *const tool = CMakeKitAspect::cmakeTool(m_kit);
384 if (tool != m_currentTool)
385 m_currentTool = tool;
386
387 m_changeButton->setEnabled(m_currentTool);
388 const QString generator = CMakeGeneratorKitAspect::generator(kit());
389 const QString extraGenerator = CMakeGeneratorKitAspect::extraGenerator(kit());
390 const QString platform = CMakeGeneratorKitAspect::platform(kit());
391 const QString toolset = CMakeGeneratorKitAspect::toolset(kit());
392
393 const QString message = tr("%1 - %2, Platform: %3, Toolset: %4")
394 .arg(extraGenerator.isEmpty() ? tr("<none>") : extraGenerator)
395 .arg(generator.isEmpty() ? tr("<none>") : generator)
396 .arg(platform.isEmpty() ? tr("<none>") : platform)
397 .arg(toolset.isEmpty() ? tr("<none>") : toolset);
398 m_label->setText(message);
399 }
400
changeGenerator()401 void changeGenerator()
402 {
403 QPointer<QDialog> changeDialog = new QDialog(m_changeButton);
404
405 // Disable help button in titlebar on windows:
406 Qt::WindowFlags flags = changeDialog->windowFlags();
407 flags |= Qt::MSWindowsFixedSizeDialogHint;
408 changeDialog->setWindowFlags(flags);
409
410 changeDialog->setWindowTitle(tr("CMake Generator"));
411
412 auto layout = new QGridLayout(changeDialog);
413 layout->setSizeConstraint(QLayout::SetFixedSize);
414
415 auto cmakeLabel = new QLabel;
416 cmakeLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
417
418 auto generatorCombo = new QComboBox;
419 auto extraGeneratorCombo = new QComboBox;
420 auto platformEdit = new QLineEdit;
421 auto toolsetEdit = new QLineEdit;
422
423 int row = 0;
424 layout->addWidget(new QLabel(QLatin1String("Executable:")));
425 layout->addWidget(cmakeLabel, row, 1);
426
427 ++row;
428 layout->addWidget(new QLabel(tr("Generator:")), row, 0);
429 layout->addWidget(generatorCombo, row, 1);
430
431 ++row;
432 layout->addWidget(new QLabel(tr("Extra generator:")), row, 0);
433 layout->addWidget(extraGeneratorCombo, row, 1);
434
435 ++row;
436 layout->addWidget(new QLabel(tr("Platform:")), row, 0);
437 layout->addWidget(platformEdit, row, 1);
438
439 ++row;
440 layout->addWidget(new QLabel(tr("Toolset:")), row, 0);
441 layout->addWidget(toolsetEdit, row, 1);
442
443 ++row;
444 auto bb = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
445 layout->addWidget(bb, row, 0, 1, 2);
446
447 connect(bb, &QDialogButtonBox::accepted, changeDialog.data(), &QDialog::accept);
448 connect(bb, &QDialogButtonBox::rejected, changeDialog.data(), &QDialog::reject);
449
450 cmakeLabel->setText(m_currentTool->cmakeExecutable().toUserOutput());
451
452 QList<CMakeTool::Generator> generatorList = m_currentTool->supportedGenerators();
453 Utils::sort(generatorList, &CMakeTool::Generator::name);
454
455 for (auto it = generatorList.constBegin(); it != generatorList.constEnd(); ++it)
456 generatorCombo->addItem(it->name);
457
458 auto updateDialog = [&generatorList, generatorCombo, extraGeneratorCombo,
459 platformEdit, toolsetEdit](const QString &name) {
460 const auto it = std::find_if(generatorList.constBegin(), generatorList.constEnd(),
461 [name](const CMakeTool::Generator &g) { return g.name == name; });
462 QTC_ASSERT(it != generatorList.constEnd(), return);
463 generatorCombo->setCurrentText(name);
464
465 extraGeneratorCombo->clear();
466 extraGeneratorCombo->addItem(tr("<none>"), QString());
467 for (const QString &eg : qAsConst(it->extraGenerators))
468 extraGeneratorCombo->addItem(eg, eg);
469 extraGeneratorCombo->setEnabled(extraGeneratorCombo->count() > 1);
470
471 platformEdit->setEnabled(it->supportsPlatform);
472 toolsetEdit->setEnabled(it->supportsToolset);
473 };
474
475 updateDialog(CMakeGeneratorKitAspect::generator(kit()));
476
477 generatorCombo->setCurrentText(CMakeGeneratorKitAspect::generator(kit()));
478 extraGeneratorCombo->setCurrentText(CMakeGeneratorKitAspect::extraGenerator(kit()));
479 platformEdit->setText(platformEdit->isEnabled() ? CMakeGeneratorKitAspect::platform(kit()) : QString());
480 toolsetEdit->setText(toolsetEdit->isEnabled() ? CMakeGeneratorKitAspect::toolset(kit()) : QString());
481
482 connect(generatorCombo, &QComboBox::currentTextChanged, updateDialog);
483
484 if (changeDialog->exec() == QDialog::Accepted) {
485 if (!changeDialog)
486 return;
487
488 CMakeGeneratorKitAspect::set(kit(), generatorCombo->currentText(),
489 extraGeneratorCombo->currentData().toString(),
490 platformEdit->isEnabled() ? platformEdit->text() : QString(),
491 toolsetEdit->isEnabled() ? toolsetEdit->text() : QString());
492 }
493 }
494
495 bool m_ignoreChange = false;
496 ElidingLabel *m_label;
497 QPushButton *m_changeButton;
498 CMakeTool *m_currentTool = nullptr;
499 };
500
501 namespace {
502
503 class GeneratorInfo
504 {
505 public:
506 GeneratorInfo() = default;
GeneratorInfo(const QString & generator_,const QString & extraGenerator_=QString (),const QString & platform_=QString (),const QString & toolset_=QString ())507 GeneratorInfo(const QString &generator_,
508 const QString &extraGenerator_ = QString(),
509 const QString &platform_ = QString(),
510 const QString &toolset_ = QString())
511 : generator(generator_)
512 , extraGenerator(extraGenerator_)
513 , platform(platform_)
514 , toolset(toolset_)
515 {}
516
toVariant() const517 QVariant toVariant() const {
518 QVariantMap result;
519 result.insert(GENERATOR_KEY, generator);
520 result.insert(EXTRA_GENERATOR_KEY, extraGenerator);
521 result.insert(PLATFORM_KEY, platform);
522 result.insert(TOOLSET_KEY, toolset);
523 return result;
524 }
fromVariant(const QVariant & v)525 void fromVariant(const QVariant &v) {
526 const QVariantMap value = v.toMap();
527
528 generator = value.value(GENERATOR_KEY).toString();
529 extraGenerator = value.value(EXTRA_GENERATOR_KEY).toString();
530 platform = value.value(PLATFORM_KEY).toString();
531 toolset = value.value(TOOLSET_KEY).toString();
532 }
533
534 QString generator;
535 QString extraGenerator;
536 QString platform;
537 QString toolset;
538 };
539
540 } // namespace
541
generatorInfo(const Kit * k)542 static GeneratorInfo generatorInfo(const Kit *k)
543 {
544 GeneratorInfo info;
545 if (!k)
546 return info;
547
548 info.fromVariant(k->value(GENERATOR_ID));
549 return info;
550 }
551
setGeneratorInfo(Kit * k,const GeneratorInfo & info)552 static void setGeneratorInfo(Kit *k, const GeneratorInfo &info)
553 {
554 if (!k)
555 return;
556 k->setValue(GENERATOR_ID, info.toVariant());
557 }
558
CMakeGeneratorKitAspect()559 CMakeGeneratorKitAspect::CMakeGeneratorKitAspect()
560 {
561 setObjectName(QLatin1String("CMakeGeneratorKitAspect"));
562 setId(GENERATOR_ID);
563 setDisplayName(tr("CMake generator"));
564 setDescription(tr("CMake generator defines how a project is built when using CMake.<br>"
565 "This setting is ignored when using other build systems."));
566 setPriority(19000);
567 }
568
generator(const Kit * k)569 QString CMakeGeneratorKitAspect::generator(const Kit *k)
570 {
571 return generatorInfo(k).generator;
572 }
573
extraGenerator(const Kit * k)574 QString CMakeGeneratorKitAspect::extraGenerator(const Kit *k)
575 {
576 return generatorInfo(k).extraGenerator;
577 }
578
platform(const Kit * k)579 QString CMakeGeneratorKitAspect::platform(const Kit *k)
580 {
581 return generatorInfo(k).platform;
582 }
583
toolset(const Kit * k)584 QString CMakeGeneratorKitAspect::toolset(const Kit *k)
585 {
586 return generatorInfo(k).toolset;
587 }
588
setGenerator(Kit * k,const QString & generator)589 void CMakeGeneratorKitAspect::setGenerator(Kit *k, const QString &generator)
590 {
591 GeneratorInfo info = generatorInfo(k);
592 info.generator = generator;
593 setGeneratorInfo(k, info);
594 }
595
setExtraGenerator(Kit * k,const QString & extraGenerator)596 void CMakeGeneratorKitAspect::setExtraGenerator(Kit *k, const QString &extraGenerator)
597 {
598 GeneratorInfo info = generatorInfo(k);
599 info.extraGenerator = extraGenerator;
600 setGeneratorInfo(k, info);
601 }
602
setPlatform(Kit * k,const QString & platform)603 void CMakeGeneratorKitAspect::setPlatform(Kit *k, const QString &platform)
604 {
605 GeneratorInfo info = generatorInfo(k);
606 info.platform = platform;
607 setGeneratorInfo(k, info);
608 }
609
setToolset(Kit * k,const QString & toolset)610 void CMakeGeneratorKitAspect::setToolset(Kit *k, const QString &toolset)
611 {
612 GeneratorInfo info = generatorInfo(k);
613 info.toolset = toolset;
614 setGeneratorInfo(k, info);
615 }
616
set(Kit * k,const QString & generator,const QString & extraGenerator,const QString & platform,const QString & toolset)617 void CMakeGeneratorKitAspect::set(Kit *k,
618 const QString &generator,
619 const QString &extraGenerator,
620 const QString &platform,
621 const QString &toolset)
622 {
623 GeneratorInfo info(generator, extraGenerator, platform, toolset);
624 setGeneratorInfo(k, info);
625 }
626
generatorArguments(const Kit * k)627 QStringList CMakeGeneratorKitAspect::generatorArguments(const Kit *k)
628 {
629 QStringList result;
630 GeneratorInfo info = generatorInfo(k);
631 if (info.generator.isEmpty())
632 return result;
633
634 if (info.extraGenerator.isEmpty()) {
635 result.append("-G" + info.generator);
636 } else {
637 result.append("-G" + info.extraGenerator + " - " + info.generator);
638 }
639
640 if (!info.platform.isEmpty())
641 result.append("-A" + info.platform);
642
643 if (!info.toolset.isEmpty())
644 result.append("-T" + info.toolset);
645
646 return result;
647 }
648
isMultiConfigGenerator(const Kit * k)649 bool CMakeGeneratorKitAspect::isMultiConfigGenerator(const Kit *k)
650 {
651 const QString generator = CMakeGeneratorKitAspect::generator(k);
652 return generator.indexOf("Visual Studio") != -1 ||
653 generator == "Xcode" ||
654 generator == "Ninja Multi-Config";
655 }
656
defaultValue(const Kit * k) const657 QVariant CMakeGeneratorKitAspect::defaultValue(const Kit *k) const
658 {
659 QTC_ASSERT(k, return QVariant());
660
661 CMakeTool *tool = CMakeKitAspect::cmakeTool(k);
662 if (!tool)
663 return QVariant();
664
665 if (isIos(k))
666 return GeneratorInfo("Xcode").toVariant();
667
668 const QList<CMakeTool::Generator> known = tool->supportedGenerators();
669 auto it = std::find_if(known.constBegin(), known.constEnd(), [](const CMakeTool::Generator &g) {
670 return g.matches("Ninja");
671 });
672 if (it != known.constEnd()) {
673 const bool hasNinja = [k]() {
674 Internal::CMakeSpecificSettings *settings
675 = Internal::CMakeProjectPlugin::projectTypeSpecificSettings();
676
677 if (settings->ninjaPath.filePath().isEmpty()) {
678 Environment env = k->buildEnvironment();
679 return !env.searchInPath("ninja").isEmpty();
680 }
681 return true;
682 }();
683
684 if (hasNinja)
685 return GeneratorInfo("Ninja").toVariant();
686 }
687
688 if (HostOsInfo::isWindowsHost()) {
689 // *sigh* Windows with its zoo of incompatible stuff again...
690 ToolChain *tc = ToolChainKitAspect::cxxToolChain(k);
691 if (tc && tc->typeId() == ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID) {
692 it = std::find_if(known.constBegin(),
693 known.constEnd(),
694 [](const CMakeTool::Generator &g) {
695 return g.matches("MinGW Makefiles");
696 });
697 } else {
698 it = std::find_if(known.constBegin(),
699 known.constEnd(),
700 [](const CMakeTool::Generator &g) {
701 return g.matches("NMake Makefiles")
702 || g.matches("NMake Makefiles JOM");
703 });
704 if (ProjectExplorerPlugin::projectExplorerSettings().useJom) {
705 it = std::find_if(known.constBegin(),
706 known.constEnd(),
707 [](const CMakeTool::Generator &g) {
708 return g.matches("NMake Makefiles JOM");
709 });
710 }
711
712 if (it == known.constEnd()) {
713 it = std::find_if(known.constBegin(),
714 known.constEnd(),
715 [](const CMakeTool::Generator &g) {
716 return g.matches("NMake Makefiles");
717 });
718 }
719 }
720 } else {
721 // Unix-oid OSes:
722 it = std::find_if(known.constBegin(), known.constEnd(), [](const CMakeTool::Generator &g) {
723 return g.matches("Unix Makefiles");
724 });
725 }
726 if (it == known.constEnd())
727 it = known.constBegin(); // Fallback to the first generator...
728 if (it == known.constEnd())
729 return QVariant();
730
731 return GeneratorInfo(it->name).toVariant();
732 }
733
validate(const Kit * k) const734 Tasks CMakeGeneratorKitAspect::validate(const Kit *k) const
735 {
736 CMakeTool *tool = CMakeKitAspect::cmakeTool(k);
737 if (!tool)
738 return {};
739
740 Tasks result;
741 const auto addWarning = [&result](const QString &desc) {
742 result << BuildSystemTask(Task::Warning, desc);
743 };
744
745 if (!tool->isValid()) {
746 addWarning(tr("CMake Tool is unconfigured, CMake generator will be ignored."));
747 } else {
748 const GeneratorInfo info = generatorInfo(k);
749 QList<CMakeTool::Generator> known = tool->supportedGenerators();
750 auto it = std::find_if(known.constBegin(), known.constEnd(), [info](const CMakeTool::Generator &g) {
751 return g.matches(info.generator, info.extraGenerator);
752 });
753 if (it == known.constEnd()) {
754 addWarning(tr("CMake Tool does not support the configured generator."));
755 } else {
756 if (!it->supportsPlatform && !info.platform.isEmpty())
757 addWarning(tr("Platform is not supported by the selected CMake generator."));
758 if (!it->supportsToolset && !info.toolset.isEmpty())
759 addWarning(tr("Toolset is not supported by the selected CMake generator."));
760 }
761 if (!tool->hasFileApi()) {
762 addWarning(tr("The selected CMake binary does not support file-api. "
763 "%1 will not be able to parse CMake projects.")
764 .arg(Core::Constants::IDE_DISPLAY_NAME));
765 }
766 }
767
768 return result;
769 }
770
setup(Kit * k)771 void CMakeGeneratorKitAspect::setup(Kit *k)
772 {
773 if (!k || k->hasValue(id()))
774 return;
775 GeneratorInfo info;
776 info.fromVariant(defaultValue(k));
777 setGeneratorInfo(k, info);
778 }
779
fix(Kit * k)780 void CMakeGeneratorKitAspect::fix(Kit *k)
781 {
782 const CMakeTool *tool = CMakeKitAspect::cmakeTool(k);
783 const GeneratorInfo info = generatorInfo(k);
784
785 if (!tool)
786 return;
787 QList<CMakeTool::Generator> known = tool->supportedGenerators();
788 auto it = std::find_if(known.constBegin(), known.constEnd(),
789 [info](const CMakeTool::Generator &g) {
790 return g.matches(info.generator, info.extraGenerator);
791 });
792 if (it == known.constEnd()) {
793 GeneratorInfo dv;
794 dv.fromVariant(defaultValue(k));
795 setGeneratorInfo(k, dv);
796 } else {
797 const GeneratorInfo dv(isIos(k) ? QString("Xcode") : info.generator,
798 info.extraGenerator,
799 it->supportsPlatform ? info.platform : QString(),
800 it->supportsToolset ? info.toolset : QString());
801 setGeneratorInfo(k, dv);
802 }
803 }
804
upgrade(Kit * k)805 void CMakeGeneratorKitAspect::upgrade(Kit *k)
806 {
807 QTC_ASSERT(k, return);
808
809 const QVariant value = k->value(GENERATOR_ID);
810 if (value.type() != QVariant::Map) {
811 GeneratorInfo info;
812 const QString fullName = value.toString();
813 const int pos = fullName.indexOf(" - ");
814 if (pos >= 0) {
815 info.generator = fullName.mid(pos + 3);
816 info.extraGenerator = fullName.mid(0, pos);
817 } else {
818 info.generator = fullName;
819 }
820 setGeneratorInfo(k, info);
821 }
822 }
823
toUserOutput(const Kit * k) const824 KitAspect::ItemList CMakeGeneratorKitAspect::toUserOutput(const Kit *k) const
825 {
826 const GeneratorInfo info = generatorInfo(k);
827 QString message;
828 if (info.generator.isEmpty()) {
829 message = tr("<Use Default Generator>");
830 } else {
831 message = tr("Generator: %1<br>Extra generator: %2").arg(info.generator).arg(info.extraGenerator);
832 if (!info.platform.isEmpty())
833 message += "<br/>" + tr("Platform: %1").arg(info.platform);
834 if (!info.toolset.isEmpty())
835 message += "<br/>" + tr("Toolset: %1").arg(info.toolset);
836 }
837 return {{tr("CMake Generator"), message}};
838 }
839
createConfigWidget(Kit * k) const840 KitAspectWidget *CMakeGeneratorKitAspect::createConfigWidget(Kit *k) const
841 {
842 return new CMakeGeneratorKitAspectWidget(k, this);
843 }
844
addToBuildEnvironment(const Kit * k,Environment & env) const845 void CMakeGeneratorKitAspect::addToBuildEnvironment(const Kit *k, Environment &env) const
846 {
847 GeneratorInfo info = generatorInfo(k);
848 if (info.generator == "NMake Makefiles JOM") {
849 if (env.searchInPath("jom.exe").exists())
850 return;
851 env.appendOrSetPath(Core::ICore::libexecPath().toUserOutput());
852 env.appendOrSetPath(Core::ICore::libexecPath("jom").toUserOutput());
853 }
854 }
855
856 // --------------------------------------------------------------------
857 // CMakeConfigurationKitAspect:
858 // --------------------------------------------------------------------
859
860 const char CONFIGURATION_ID[] = "CMake.ConfigurationKitInformation";
861
862 const char CMAKE_C_TOOLCHAIN_KEY[] = "CMAKE_C_COMPILER";
863 const char CMAKE_CXX_TOOLCHAIN_KEY[] = "CMAKE_CXX_COMPILER";
864 const char CMAKE_QMAKE_KEY[] = "QT_QMAKE_EXECUTABLE";
865 const char CMAKE_PREFIX_PATH_KEY[] = "CMAKE_PREFIX_PATH";
866
867 class CMakeConfigurationKitAspectWidget final : public KitAspectWidget
868 {
869 Q_DECLARE_TR_FUNCTIONS(CMakeProjectManager::Internal::CMakeConfigurationKitAspect)
870
871 public:
CMakeConfigurationKitAspectWidget(Kit * kit,const KitAspect * ki)872 CMakeConfigurationKitAspectWidget(Kit *kit, const KitAspect *ki)
873 : KitAspectWidget(kit, ki),
874 m_summaryLabel(createSubWidget<ElidingLabel>()),
875 m_manageButton(createSubWidget<QPushButton>())
876 {
877 refresh();
878 m_manageButton->setText(tr("Change..."));
879 connect(m_manageButton, &QAbstractButton::clicked,
880 this, &CMakeConfigurationKitAspectWidget::editConfigurationChanges);
881 }
882
883 private:
884 // KitAspectWidget interface
addToLayout(LayoutBuilder & builder)885 void addToLayout(LayoutBuilder &builder) override
886 {
887 addMutableAction(m_summaryLabel);
888 builder.addItem(m_summaryLabel);
889 builder.addItem(m_manageButton);
890 }
891
makeReadOnly()892 void makeReadOnly() override
893 {
894 m_manageButton->setEnabled(false);
895 if (m_dialog)
896 m_dialog->reject();
897 }
898
refresh()899 void refresh() override
900 {
901 const QStringList current = CMakeConfigurationKitAspect::toStringList(kit());
902
903 m_summaryLabel->setText(current.join("; "));
904 if (m_editor)
905 m_editor->setPlainText(current.join('\n'));
906 }
907
editConfigurationChanges()908 void editConfigurationChanges()
909 {
910 if (m_dialog) {
911 m_dialog->activateWindow();
912 m_dialog->raise();
913 return;
914 }
915
916 QTC_ASSERT(!m_editor, return);
917
918 m_dialog = new QDialog(m_summaryLabel->window());
919 m_dialog->setWindowTitle(tr("Edit CMake Configuration"));
920 auto layout = new QVBoxLayout(m_dialog);
921 m_editor = new QPlainTextEdit;
922 m_editor->setToolTip(tr("Enter one variable per line with the variable name "
923 "separated from the variable value by \"=\".<br>"
924 "You may provide a type hint by adding \":TYPE\" before the \"=\"."));
925 m_editor->setMinimumSize(800, 200);
926
927 auto chooser = new VariableChooser(m_dialog);
928 chooser->addSupportedWidget(m_editor);
929 chooser->addMacroExpanderProvider([this]() { return kit()->macroExpander(); });
930
931 auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Apply
932 |QDialogButtonBox::Reset|QDialogButtonBox::Cancel);
933
934 layout->addWidget(m_editor);
935 layout->addWidget(buttons);
936
937 connect(buttons, &QDialogButtonBox::accepted, m_dialog, &QDialog::accept);
938 connect(buttons, &QDialogButtonBox::rejected, m_dialog, &QDialog::reject);
939 connect(buttons, &QDialogButtonBox::clicked, m_dialog, [buttons, this](QAbstractButton *button) {
940 if (button != buttons->button(QDialogButtonBox::Reset))
941 return;
942 CMakeConfigurationKitAspect::setConfiguration(kit(),
943 CMakeConfigurationKitAspect::defaultConfiguration(kit()));
944 });
945 connect(m_dialog, &QDialog::accepted, this, &CMakeConfigurationKitAspectWidget::acceptChangesDialog);
946 connect(m_dialog, &QDialog::rejected, this, &CMakeConfigurationKitAspectWidget::closeChangesDialog);
947 connect(buttons->button(QDialogButtonBox::Apply), &QAbstractButton::clicked,
948 this, &CMakeConfigurationKitAspectWidget::applyChanges);
949
950 refresh();
951 m_dialog->show();
952 }
953
applyChanges()954 void applyChanges()
955 {
956 QTC_ASSERT(m_editor, return);
957 CMakeConfigurationKitAspect::fromStringList(kit(), m_editor->toPlainText().split(QLatin1Char('\n')));
958 }
closeChangesDialog()959 void closeChangesDialog()
960 {
961 m_dialog->deleteLater();
962 m_dialog = nullptr;
963 m_editor = nullptr;
964 }
acceptChangesDialog()965 void acceptChangesDialog()
966 {
967 applyChanges();
968 closeChangesDialog();
969 }
970
971 QLabel *m_summaryLabel;
972 QPushButton *m_manageButton;
973 QDialog *m_dialog = nullptr;
974 QPlainTextEdit *m_editor = nullptr;
975 };
976
977
CMakeConfigurationKitAspect()978 CMakeConfigurationKitAspect::CMakeConfigurationKitAspect()
979 {
980 setObjectName(QLatin1String("CMakeConfigurationKitAspect"));
981 setId(CONFIGURATION_ID);
982 setDisplayName(tr("CMake Configuration"));
983 setDescription(tr("Default configuration passed to CMake when setting up a project."));
984 setPriority(18000);
985 }
986
configuration(const Kit * k)987 CMakeConfig CMakeConfigurationKitAspect::configuration(const Kit *k)
988 {
989 if (!k)
990 return CMakeConfig();
991 const QStringList tmp = k->value(CONFIGURATION_ID).toStringList();
992 return Utils::transform(tmp, &CMakeConfigItem::fromString);
993 }
994
setConfiguration(Kit * k,const CMakeConfig & config)995 void CMakeConfigurationKitAspect::setConfiguration(Kit *k, const CMakeConfig &config)
996 {
997 if (!k)
998 return;
999 const QStringList tmp = Utils::transform(config.toList(),
1000 [](const CMakeConfigItem &i) { return i.toString(); });
1001 k->setValue(CONFIGURATION_ID, tmp);
1002 }
1003
toStringList(const Kit * k)1004 QStringList CMakeConfigurationKitAspect::toStringList(const Kit *k)
1005 {
1006 QStringList current = Utils::transform(CMakeConfigurationKitAspect::configuration(k).toList(),
1007 [](const CMakeConfigItem &i) { return i.toString(); });
1008 current = Utils::filtered(current, [](const QString &s) { return !s.isEmpty(); });
1009 Utils::sort(current);
1010 return current;
1011 }
1012
fromStringList(Kit * k,const QStringList & in)1013 void CMakeConfigurationKitAspect::fromStringList(Kit *k, const QStringList &in)
1014 {
1015 CMakeConfig result;
1016 for (const QString &s : in) {
1017 const CMakeConfigItem item = CMakeConfigItem::fromString(s);
1018 if (!item.key.isEmpty())
1019 result << item;
1020 }
1021 setConfiguration(k, result);
1022 }
1023
toArgumentsList(const Kit * k)1024 QStringList CMakeConfigurationKitAspect::toArgumentsList(const Kit *k)
1025 {
1026 return Utils::transform(CMakeConfigurationKitAspect::configuration(k).toList(),
1027 [](const CMakeConfigItem &i) { return i.toArgument(nullptr); });
1028 }
1029
defaultConfiguration(const Kit * k)1030 CMakeConfig CMakeConfigurationKitAspect::defaultConfiguration(const Kit *k)
1031 {
1032 Q_UNUSED(k)
1033 CMakeConfig config;
1034 // Qt4:
1035 config << CMakeConfigItem(CMAKE_QMAKE_KEY, "%{Qt:qmakeExecutable}");
1036 // Qt5:
1037 config << CMakeConfigItem(CMAKE_PREFIX_PATH_KEY, "%{Qt:QT_INSTALL_PREFIX}");
1038
1039 config << CMakeConfigItem(CMAKE_C_TOOLCHAIN_KEY, "%{Compiler:Executable:C}");
1040 config << CMakeConfigItem(CMAKE_CXX_TOOLCHAIN_KEY, "%{Compiler:Executable:Cxx}");
1041
1042 return config;
1043 }
1044
defaultValue(const Kit * k) const1045 QVariant CMakeConfigurationKitAspect::defaultValue(const Kit *k) const
1046 {
1047 // FIXME: Convert preload scripts
1048 CMakeConfig config = defaultConfiguration(k);
1049 const QStringList tmp = Utils::transform(config.toList(),
1050 [](const CMakeConfigItem &i) { return i.toString(); });
1051 return tmp;
1052 }
1053
validate(const Kit * k) const1054 Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const
1055 {
1056 QTC_ASSERT(k, return Tasks());
1057
1058 const QtSupport::BaseQtVersion *const version = QtSupport::QtKitAspect::qtVersion(k);
1059 const ToolChain *const tcC = ToolChainKitAspect::cToolChain(k);
1060 const ToolChain *const tcCxx = ToolChainKitAspect::cxxToolChain(k);
1061 const CMakeConfig config = configuration(k);
1062
1063 const bool isQt4 = version && version->qtVersion() < QtSupport::QtVersionNumber(5, 0, 0);
1064 FilePath qmakePath; // This is relative to the cmake used for building.
1065 QStringList qtInstallDirs; // This is relativ to the cmake used for building.
1066 FilePath tcCPath;
1067 FilePath tcCxxPath;
1068 for (const CMakeConfigItem &i : config) {
1069 // Do not use expand(QByteArray) as we cannot be sure the input is latin1
1070 const FilePath expandedValue
1071 = FilePath::fromString(k->macroExpander()->expand(QString::fromUtf8(i.value)));
1072 if (i.key == CMAKE_QMAKE_KEY)
1073 qmakePath = expandedValue;
1074 else if (i.key == CMAKE_C_TOOLCHAIN_KEY)
1075 tcCPath = expandedValue;
1076 else if (i.key == CMAKE_CXX_TOOLCHAIN_KEY)
1077 tcCxxPath = expandedValue;
1078 else if (i.key == CMAKE_PREFIX_PATH_KEY)
1079 qtInstallDirs = CMakeConfigItem::cmakeSplitValue(expandedValue.path());
1080 }
1081
1082 Tasks result;
1083 const auto addWarning = [&result](const QString &desc) {
1084 result << BuildSystemTask(Task::Warning, desc);
1085 };
1086
1087 // Validate Qt:
1088 if (qmakePath.isEmpty()) {
1089 if (version && version->isValid() && isQt4) {
1090 addWarning(tr("CMake configuration has no path to qmake binary set, "
1091 "even though the kit has a valid Qt version."));
1092 }
1093 } else {
1094 if (!version || !version->isValid()) {
1095 addWarning(tr("CMake configuration has a path to a qmake binary set, "
1096 "even though the kit has no valid Qt version."));
1097 } else if (qmakePath != version->qmakeFilePath() && isQt4) {
1098 addWarning(tr("CMake configuration has a path to a qmake binary set "
1099 "that does not match the qmake binary path "
1100 "configured in the Qt version."));
1101 }
1102 }
1103 if (version && !qtInstallDirs.contains(version->prefix().path()) && !isQt4) {
1104 if (version->isValid()) {
1105 addWarning(tr("CMake configuration has no CMAKE_PREFIX_PATH set "
1106 "that points to the kit Qt version."));
1107 }
1108 }
1109
1110 // Validate Toolchains:
1111 if (tcCPath.isEmpty()) {
1112 if (tcC && tcC->isValid()) {
1113 addWarning(tr("CMake configuration has no path to a C compiler set, "
1114 "even though the kit has a valid tool chain."));
1115 }
1116 } else {
1117 if (!tcC || !tcC->isValid()) {
1118 addWarning(tr("CMake configuration has a path to a C compiler set, "
1119 "even though the kit has no valid tool chain."));
1120 } else if (tcCPath != tcC->compilerCommand()) {
1121 addWarning(tr("CMake configuration has a path to a C compiler set "
1122 "that does not match the compiler path "
1123 "configured in the tool chain of the kit."));
1124 }
1125 }
1126
1127 if (tcCxxPath.isEmpty()) {
1128 if (tcCxx && tcCxx->isValid()) {
1129 addWarning(tr("CMake configuration has no path to a C++ compiler set, "
1130 "even though the kit has a valid tool chain."));
1131 }
1132 } else {
1133 if (!tcCxx || !tcCxx->isValid()) {
1134 addWarning(tr("CMake configuration has a path to a C++ compiler set, "
1135 "even though the kit has no valid tool chain."));
1136 } else if (tcCxxPath != tcCxx->compilerCommand()) {
1137 addWarning(tr("CMake configuration has a path to a C++ compiler set "
1138 "that does not match the compiler path "
1139 "configured in the tool chain of the kit."));
1140 }
1141 }
1142
1143 return result;
1144 }
1145
setup(Kit * k)1146 void CMakeConfigurationKitAspect::setup(Kit *k)
1147 {
1148 if (k && !k->hasValue(CONFIGURATION_ID))
1149 k->setValue(CONFIGURATION_ID, defaultValue(k));
1150 }
1151
fix(Kit * k)1152 void CMakeConfigurationKitAspect::fix(Kit *k)
1153 {
1154 Q_UNUSED(k)
1155 }
1156
toUserOutput(const Kit * k) const1157 KitAspect::ItemList CMakeConfigurationKitAspect::toUserOutput(const Kit *k) const
1158 {
1159 return {{tr("CMake Configuration"), toStringList(k).join("<br>")}};
1160 }
1161
createConfigWidget(Kit * k) const1162 KitAspectWidget *CMakeConfigurationKitAspect::createConfigWidget(Kit *k) const
1163 {
1164 if (!k)
1165 return nullptr;
1166 return new CMakeConfigurationKitAspectWidget(k, this);
1167 }
1168
1169 } // namespace CMakeProjectManager
1170