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