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 "cmakebuildconfiguration.h"
27 
28 #include "cmakebuildconfiguration.h"
29 #include "cmakebuildstep.h"
30 #include "cmakebuildsystem.h"
31 #include "cmakeconfigitem.h"
32 #include "cmakekitinformation.h"
33 #include "cmakeprojectconstants.h"
34 #include "cmakeprojectplugin.h"
35 #include "cmakespecificsettings.h"
36 #include "configmodel.h"
37 #include "configmodelitemdelegate.h"
38 
39 #include <android/androidconstants.h>
40 #include <ios/iosconstants.h>
41 #include <qnx/qnxconstants.h>
42 #include <webassembly/webassemblyconstants.h>
43 
44 #include <coreplugin/find/itemviewfind.h>
45 #include <coreplugin/icore.h>
46 
47 #include <projectexplorer/buildaspects.h>
48 #include <projectexplorer/buildinfo.h>
49 #include <projectexplorer/buildmanager.h>
50 #include <projectexplorer/buildsteplist.h>
51 #include <projectexplorer/kitinformation.h>
52 #include <projectexplorer/namedwidget.h>
53 #include <projectexplorer/projectexplorer.h>
54 #include <projectexplorer/project.h>
55 #include <projectexplorer/projectmacroexpander.h>
56 #include <projectexplorer/target.h>
57 
58 #include <qtsupport/baseqtversion.h>
59 #include <qtsupport/qtbuildaspects.h>
60 #include <qtsupport/qtkitinformation.h>
61 
62 #include <utils/algorithm.h>
63 #include <utils/categorysortfiltermodel.h>
64 #include <utils/checkablemessagebox.h>
65 #include <utils/detailswidget.h>
66 #include <utils/headerviewstretcher.h>
67 #include <utils/infolabel.h>
68 #include <utils/itemviews.h>
69 #include <utils/layoutbuilder.h>
70 #include <utils/progressindicator.h>
71 #include <utils/qtcassert.h>
72 #include <utils/stringutils.h>
73 #include <utils/variablechooser.h>
74 
75 #include <QApplication>
76 #include <QBoxLayout>
77 #include <QCheckBox>
78 #include <QClipboard>
79 #include <QDialog>
80 #include <QDialogButtonBox>
81 #include <QDir>
82 #include <QGridLayout>
83 #include <QLoggingCategory>
84 #include <QMenu>
85 #include <QMessageBox>
86 #include <QPlainTextEdit>
87 #include <QPushButton>
88 #include <QTimer>
89 
90 using namespace ProjectExplorer;
91 using namespace Utils;
92 using namespace CMakeProjectManager::Internal;
93 
94 namespace CMakeProjectManager {
95 
96 static Q_LOGGING_CATEGORY(cmakeBuildConfigurationLog, "qtc.cmake.bc", QtWarningMsg);
97 
98 const char CONFIGURATION_KEY[] = "CMake.Configuration";
99 const char DEVELOPMENT_TEAM_FLAG[] = "Ios:DevelopmentTeam:Flag";
100 const char PROVISIONING_PROFILE_FLAG[] = "Ios:ProvisioningProfile:Flag";
101 const char CMAKE_OSX_ARCHITECTURES_FLAG[] = "CMAKE_OSX_ARCHITECTURES:DefaultFlag";
102 const char CMAKE_QT6_TOOLCHAIN_FILE_ARG[] =
103         "-DCMAKE_TOOLCHAIN_FILE:PATH=%{Qt:QT_INSTALL_PREFIX}/lib/cmake/Qt6/qt.toolchain.cmake";
104 
105 namespace Internal {
106 
107 class CMakeBuildSettingsWidget : public NamedWidget
108 {
109     Q_DECLARE_TR_FUNCTIONS(CMakeProjectManager::Internal::CMakeBuildSettingsWidget)
110 
111 public:
112     CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc);
113 
114     void setError(const QString &message);
115     void setWarning(const QString &message);
116 
117 private:
118     void updateButtonState();
119     void updateAdvancedCheckBox();
120     void updateFromKit();
121     CMakeProjectManager::CMakeConfig getQmlDebugCxxFlags();
122     CMakeProjectManager::CMakeConfig getSigningFlagsChanges();
123 
124     void updateSelection();
125     void setVariableUnsetFlag(bool unsetFlag);
126     QAction *createForceAction(int type, const QModelIndex &idx);
127 
128     bool eventFilter(QObject *target, QEvent *event) override;
129 
130     void batchEditConfiguration();
131 
132     CMakeBuildConfiguration *m_buildConfiguration;
133     QTreeView *m_configView;
134     ConfigModel *m_configModel;
135     CategorySortFilterModel *m_configFilterModel;
136     CategorySortFilterModel *m_configTextFilterModel;
137     ProgressIndicator *m_progressIndicator;
138     QPushButton *m_addButton;
139     QPushButton *m_editButton;
140     QPushButton *m_setButton;
141     QPushButton *m_unsetButton;
142     QPushButton *m_resetButton;
143     QPushButton *m_clearSelectionButton;
144     QCheckBox *m_showAdvancedCheckBox;
145     QPushButton *m_reconfigureButton;
146     QTimer m_showProgressTimer;
147     FancyLineEdit *m_filterEdit;
148     InfoLabel *m_warningMessageLabel;
149 
150     QPushButton *m_batchEditButton = nullptr;
151 };
152 
mapToSource(const QAbstractItemView * view,const QModelIndex & idx)153 static QModelIndex mapToSource(const QAbstractItemView *view, const QModelIndex &idx)
154 {
155     if (!idx.isValid())
156         return idx;
157 
158     QAbstractItemModel *model = view->model();
159     QModelIndex result = idx;
160     while (auto proxy = qobject_cast<const QSortFilterProxyModel *>(model)) {
161         result = proxy->mapToSource(result);
162         model = proxy->sourceModel();
163     }
164     return result;
165 }
166 
CMakeBuildSettingsWidget(CMakeBuildConfiguration * bc)167 CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc) :
168     NamedWidget(tr("CMake")),
169     m_buildConfiguration(bc),
170     m_configModel(new ConfigModel(this)),
171     m_configFilterModel(new CategorySortFilterModel(this)),
172     m_configTextFilterModel(new CategorySortFilterModel(this))
173 {
174     QTC_CHECK(bc);
175 
176     auto vbox = new QVBoxLayout(this);
177     vbox->setContentsMargins(0, 0, 0, 0);
178     auto container = new DetailsWidget;
179     container->setState(DetailsWidget::NoSummary);
180     vbox->addWidget(container);
181 
182     auto details = new QWidget(container);
183     container->setWidget(details);
184 
185     auto buildDirAspect = bc->buildDirectoryAspect();
186     buildDirAspect->setAutoApplyOnEditingFinished(true);
187     connect(buildDirAspect, &BaseAspect::changed, this, [this]() {
188         m_configModel->flush(); // clear out config cache...;
189     });
190 
191     auto clearCMakeConfiguration = new QPushButton(tr("Re-configure with Initial Parameters"));
192     connect(clearCMakeConfiguration, &QPushButton::clicked, this, [bc]() {
193         auto *settings = CMakeProjectPlugin::projectTypeSpecificSettings();
194         bool doNotAsk = !settings->askBeforeReConfigureInitialParams.value();
195         if (!doNotAsk) {
196             QDialogButtonBox::StandardButton reply = Utils::CheckableMessageBox::question(
197                 Core::ICore::dialogParent(),
198                 tr("Re-configure with Initial Parameters"),
199                 tr("Clear CMake configuration and configure with initial parameters?"),
200                 tr("Do not ask again"),
201                 &doNotAsk,
202                 QDialogButtonBox::Yes | QDialogButtonBox::No,
203                 QDialogButtonBox::Yes);
204 
205             settings->askBeforeReConfigureInitialParams.setValue(!doNotAsk);
206             settings->writeSettings(Core::ICore::settings());
207 
208             if (reply != QDialogButtonBox::Yes) {
209                 return;
210             }
211         }
212 
213         auto cbc = static_cast<CMakeBuildSystem*>(bc->buildSystem());
214         cbc->clearCMakeCache();
215         if (ProjectExplorerPlugin::saveModifiedFiles())
216             cbc->runCMake();
217     });
218 
219     auto buildTypeAspect = bc->aspect<BuildTypeAspect>();
220     connect(buildTypeAspect, &BaseAspect::changed, this, [this, buildTypeAspect]() {
221         if (!m_buildConfiguration->isMultiConfig()) {
222             CMakeConfig config;
223             config << CMakeConfigItem("CMAKE_BUILD_TYPE", buildTypeAspect->value().toUtf8());
224 
225             m_configModel->setBatchEditConfiguration(config);
226         }
227     });
228 
229     auto qmlDebugAspect = bc->aspect<QtSupport::QmlDebuggingAspect>();
230     connect(qmlDebugAspect, &QtSupport::QmlDebuggingAspect::changed, this, [this]() {
231         updateButtonState();
232     });
233 
234     m_warningMessageLabel = new InfoLabel({}, InfoLabel::Warning);
235     m_warningMessageLabel->setVisible(false);
236 
237     m_filterEdit = new FancyLineEdit;
238     m_filterEdit->setPlaceholderText(tr("Filter"));
239     m_filterEdit->setFiltering(true);
240     auto tree = new TreeView;
241     connect(tree, &TreeView::activated,
242             tree, [tree](const QModelIndex &idx) { tree->edit(idx); });
243     m_configView = tree;
244 
245     m_configView->viewport()->installEventFilter(this);
246 
247     m_configFilterModel->setSourceModel(m_configModel);
248     m_configFilterModel->setFilterKeyColumn(0);
249     m_configFilterModel->setFilterRole(ConfigModel::ItemIsAdvancedRole);
250     m_configFilterModel->setFilterFixedString("0");
251 
252     m_configTextFilterModel->setSourceModel(m_configFilterModel);
253     m_configTextFilterModel->setSortRole(Qt::DisplayRole);
254     m_configTextFilterModel->setFilterKeyColumn(-1);
255 
256     connect(m_configTextFilterModel, &QAbstractItemModel::layoutChanged, this, [this]() {
257         QModelIndex selectedIdx = m_configView->currentIndex();
258         if (selectedIdx.isValid())
259             m_configView->scrollTo(selectedIdx);
260     });
261 
262     m_configView->setModel(m_configTextFilterModel);
263     m_configView->setMinimumHeight(300);
264     m_configView->setUniformRowHeights(true);
265     m_configView->setSortingEnabled(true);
266     m_configView->sortByColumn(0, Qt::AscendingOrder);
267     auto stretcher = new HeaderViewStretcher(m_configView->header(), 0);
268     m_configView->setSelectionMode(QAbstractItemView::ExtendedSelection);
269     m_configView->setSelectionBehavior(QAbstractItemView::SelectItems);
270     m_configView->setFrameShape(QFrame::NoFrame);
271     m_configView->setItemDelegate(new ConfigModelItemDelegate(m_buildConfiguration->project()->projectDirectory(),
272                                                               m_configView));
273     QFrame *findWrapper = Core::ItemViewFind::createSearchableWrapper(m_configView, Core::ItemViewFind::LightColored);
274     findWrapper->setFrameStyle(QFrame::StyledPanel);
275 
276     m_progressIndicator = new ProgressIndicator(ProgressIndicatorSize::Large, findWrapper);
277     m_progressIndicator->attachToWidget(findWrapper);
278     m_progressIndicator->raise();
279     m_progressIndicator->hide();
280     m_showProgressTimer.setSingleShot(true);
281     m_showProgressTimer.setInterval(50); // don't show progress for < 50ms tasks
282     connect(&m_showProgressTimer, &QTimer::timeout, [this]() { m_progressIndicator->show(); });
283 
284     m_addButton = new QPushButton(tr("&Add"));
285     m_addButton->setToolTip(tr("Add a new configuration value."));
286     auto addButtonMenu = new QMenu(this);
287     addButtonMenu->addAction(tr("&Boolean"))->setData(
288                 QVariant::fromValue(static_cast<int>(ConfigModel::DataItem::BOOLEAN)));
289     addButtonMenu->addAction(tr("&String"))->setData(
290                 QVariant::fromValue(static_cast<int>(ConfigModel::DataItem::STRING)));
291     addButtonMenu->addAction(tr("&Directory"))->setData(
292                 QVariant::fromValue(static_cast<int>(ConfigModel::DataItem::DIRECTORY)));
293     addButtonMenu->addAction(tr("&File"))->setData(
294                 QVariant::fromValue(static_cast<int>(ConfigModel::DataItem::FILE)));
295     m_addButton->setMenu(addButtonMenu);
296 
297     m_editButton = new QPushButton(tr("&Edit"));
298     m_editButton->setToolTip(tr("Edit the current CMake configuration value."));
299 
300     m_setButton = new QPushButton(tr("&Set"));
301     m_setButton->setToolTip(tr("Set a value in the CMake configuration."));
302 
303     m_unsetButton = new QPushButton(tr("&Unset"));
304     m_unsetButton->setToolTip(tr("Unset a value in the CMake configuration."));
305 
306     m_resetButton = new QPushButton(tr("&Reset"));
307     m_resetButton->setToolTip(tr("Reset all unapplied changes."));
308     m_resetButton->setEnabled(false);
309 
310     m_clearSelectionButton = new QPushButton(tr("Clear Selection"));
311     m_clearSelectionButton->setToolTip(tr("Clear selection."));
312     m_clearSelectionButton->setEnabled(false);
313 
314     m_batchEditButton = new QPushButton(tr("Batch Edit..."));
315     m_batchEditButton->setToolTip(tr("Set or reset multiple values in the CMake Configuration."));
316 
317     m_showAdvancedCheckBox = new QCheckBox(tr("Advanced"));
318 
319     connect(m_configView->selectionModel(), &QItemSelectionModel::selectionChanged,
320             this, [this](const QItemSelection &, const QItemSelection &) {
321                 updateSelection();
322     });
323 
324     m_reconfigureButton = new QPushButton(tr("Apply Configuration Changes"));
325     m_reconfigureButton->setEnabled(false);
326 
327     using namespace Layouting;
328     Grid cmakeConfiguration {
329         m_filterEdit, Break(),
330         findWrapper,
331         Column {
332             m_addButton,
333             m_editButton,
334             m_setButton,
335             m_unsetButton,
336             m_clearSelectionButton,
337             m_resetButton,
338             m_batchEditButton,
339             Space(10),
340             m_showAdvancedCheckBox,
341             Stretch()
342         }
343     };
344 
345     Column {
346         Form {
347             buildDirAspect,
348             bc->aspect<BuildTypeAspect>(),
349             bc->aspect<InitialCMakeArgumentsAspect>(),
350             QString(), clearCMakeConfiguration, Break(),
351             qmlDebugAspect
352         },
353         m_warningMessageLabel,
354         Space(10),
355         cmakeConfiguration,
356         m_reconfigureButton,
357     }.attachTo(details, false);
358 
359     updateAdvancedCheckBox();
360     setError(bc->error());
361     setWarning(bc->warning());
362 
363     connect(bc->buildSystem(), &BuildSystem::parsingStarted, this, [this] {
364         updateButtonState();
365         m_configView->setEnabled(false);
366         m_showProgressTimer.start();
367     });
368 
369     if (bc->buildSystem()->isParsing())
370         m_showProgressTimer.start();
371     else {
372         m_configModel->setConfiguration(m_buildConfiguration->configurationFromCMake());
373         m_configView->expandAll();
374     }
375 
376     connect(bc->buildSystem(), &BuildSystem::parsingFinished, this, [this, stretcher] {
377         m_configModel->setConfiguration(m_buildConfiguration->configurationFromCMake());
378         m_configView->expandAll();
379         m_configView->setEnabled(true);
380         stretcher->stretch();
381         updateButtonState();
382         m_showProgressTimer.stop();
383         m_progressIndicator->hide();
384     });
385     connect(m_buildConfiguration, &CMakeBuildConfiguration::errorOccurred,
386             this, [this]() {
387         m_showProgressTimer.stop();
388         m_progressIndicator->hide();
389     });
390     connect(m_configTextFilterModel, &QAbstractItemModel::modelReset, this, [this, stretcher]() {
391         m_configView->expandAll();
392         stretcher->stretch();
393     });
394 
395     connect(m_configModel, &QAbstractItemModel::dataChanged,
396             this, &CMakeBuildSettingsWidget::updateButtonState);
397     connect(m_configModel, &QAbstractItemModel::modelReset,
398             this, &CMakeBuildSettingsWidget::updateButtonState);
399 
400     connect(m_buildConfiguration,
401             &CMakeBuildConfiguration::signingFlagsChanged,
402             this,
403             &CMakeBuildSettingsWidget::updateButtonState);
404 
405     connect(m_showAdvancedCheckBox, &QCheckBox::stateChanged,
406             this, &CMakeBuildSettingsWidget::updateAdvancedCheckBox);
407 
408     connect(m_filterEdit,
409             &QLineEdit::textChanged,
410             m_configTextFilterModel,
411             [this](const QString &txt) {
412                 m_configTextFilterModel->setFilterRegularExpression(
413                     QRegularExpression(QRegularExpression::escape(txt),
414                                        QRegularExpression::CaseInsensitiveOption));
415             });
416 
417     connect(m_resetButton, &QPushButton::clicked, m_configModel, &ConfigModel::resetAllChanges);
418     connect(m_reconfigureButton,
419             &QPushButton::clicked,
420             m_buildConfiguration,
421             &CMakeBuildConfiguration::runCMakeWithExtraArguments);
422     connect(m_setButton, &QPushButton::clicked, this, [this]() {
423         setVariableUnsetFlag(false);
424     });
425     connect(m_unsetButton, &QPushButton::clicked, this, [this]() {
426         setVariableUnsetFlag(true);
427     });
428     connect(m_editButton, &QPushButton::clicked, this, [this]() {
429         QModelIndex idx = m_configView->currentIndex();
430         if (idx.column() != 1)
431             idx = idx.sibling(idx.row(), 1);
432         m_configView->setCurrentIndex(idx);
433         m_configView->edit(idx);
434     });
435     connect(m_clearSelectionButton, &QPushButton::clicked, this, [this]() {
436         m_configView->selectionModel()->clear();
437     });
438     connect(addButtonMenu, &QMenu::triggered, this, [this](QAction *action) {
439         ConfigModel::DataItem::Type type =
440                 static_cast<ConfigModel::DataItem::Type>(action->data().value<int>());
441         QString value = tr("<UNSET>");
442         if (type == ConfigModel::DataItem::BOOLEAN)
443             value = QString::fromLatin1("OFF");
444 
445         m_configModel->appendConfiguration(tr("<UNSET>"), value, type);
446         const TreeItem *item = m_configModel->findNonRootItem([&value, type](TreeItem *item) {
447                 ConfigModel::DataItem dataItem = ConfigModel::dataItemFromIndex(item->index());
448                 return dataItem.key == tr("<UNSET>") && dataItem.type == type && dataItem.value == value;
449         });
450         QModelIndex idx = m_configModel->indexForItem(item);
451         idx = m_configTextFilterModel->mapFromSource(m_configFilterModel->mapFromSource(idx));
452         m_configView->setFocus();
453         m_configView->scrollTo(idx);
454         m_configView->setCurrentIndex(idx);
455         m_configView->edit(idx);
456     });
457     connect(m_batchEditButton, &QAbstractButton::clicked,
458             this, &CMakeBuildSettingsWidget::batchEditConfiguration);
459 
460     connect(bc, &CMakeBuildConfiguration::errorOccurred, this, &CMakeBuildSettingsWidget::setError);
461     connect(bc, &CMakeBuildConfiguration::warningOccurred, this, &CMakeBuildSettingsWidget::setWarning);
462     connect(bc, &CMakeBuildConfiguration::configurationChanged, this, [this](const CMakeConfig &config) {
463        m_configModel->setBatchEditConfiguration(config);
464     });
465 
466     updateFromKit();
467     connect(m_buildConfiguration->target(), &Target::kitChanged,
468             this, &CMakeBuildSettingsWidget::updateFromKit);
469     connect(m_buildConfiguration, &CMakeBuildConfiguration::enabledChanged,
470             this, [this]() {
471         if (m_buildConfiguration->isEnabled())
472             setError(QString());
473 
474         m_batchEditButton->setEnabled(m_buildConfiguration->isEnabled());
475         m_addButton->setEnabled(m_buildConfiguration->isEnabled());
476     });
477 
478     updateSelection();
479 }
480 
481 
batchEditConfiguration()482 void CMakeBuildSettingsWidget::batchEditConfiguration()
483 {
484     auto dialog = new QDialog(this);
485     dialog->setWindowTitle(tr("Edit CMake Configuration"));
486     dialog->setAttribute(Qt::WA_DeleteOnClose);
487     dialog->setModal(true);
488     auto layout = new QVBoxLayout(dialog);
489     auto editor = new QPlainTextEdit(dialog);
490 
491     auto label = new QLabel(dialog);
492     label->setText(tr("Enter one CMake variable per line.\n"
493        "To set or change a variable, use -D<variable>:<type>=<value>.\n"
494        "<type> can have one of the following values: FILEPATH, PATH, BOOL, INTERNAL, or STRING.\n"
495        "To unset a variable, use -U<variable>.\n"));
496     editor->setMinimumSize(800, 200);
497 
498     auto chooser = new Utils::VariableChooser(dialog);
499     chooser->addSupportedWidget(editor);
500     chooser->addMacroExpanderProvider([this]() { return m_buildConfiguration->macroExpander(); });
501 
502     auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
503 
504     layout->addWidget(editor);
505     layout->addWidget(label);
506     layout->addWidget(buttons);
507 
508     connect(buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
509     connect(buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
510     connect(dialog, &QDialog::accepted, this, [=]{
511         const auto expander = m_buildConfiguration->macroExpander();
512 
513         const QStringList lines = editor->toPlainText().split('\n', Qt::SkipEmptyParts);
514         const QStringList expandedLines = Utils::transform(lines,
515                                            [expander](const QString &s) {
516                                                return expander->expand(s);
517                                            });
518         const CMakeConfig config = CMakeConfig::fromArguments(expandedLines);
519 
520         m_configModel->setBatchEditConfiguration(config);
521     });
522 
523     editor->setPlainText(m_buildConfiguration->configurationChangesArguments().join('\n'));
524 
525     dialog->show();
526 }
527 
setError(const QString & message)528 void CMakeBuildSettingsWidget::setError(const QString &message)
529 {
530     m_buildConfiguration->buildDirectoryAspect()->setProblem(message);
531 }
532 
setWarning(const QString & message)533 void CMakeBuildSettingsWidget::setWarning(const QString &message)
534 {
535     bool showWarning = !message.isEmpty();
536     m_warningMessageLabel->setVisible(showWarning);
537     m_warningMessageLabel->setText(message);
538 }
539 
updateButtonState()540 void CMakeBuildSettingsWidget::updateButtonState()
541 {
542     const bool isParsing = m_buildConfiguration->buildSystem()->isParsing();
543 
544     // Update extra data in buildconfiguration
545     const QList<ConfigModel::DataItem> changes = m_configModel->configurationForCMake();
546 
547     const CMakeConfig configChanges
548         = getQmlDebugCxxFlags() + getSigningFlagsChanges()
549           + Utils::transform(changes, [](const ConfigModel::DataItem &i) {
550                 CMakeConfigItem ni;
551                 ni.key = i.key.toUtf8();
552                 ni.value = i.value.toUtf8();
553                 ni.documentation = i.description.toUtf8();
554                 ni.isAdvanced = i.isAdvanced;
555                 ni.isUnset = i.isUnset;
556                 ni.inCMakeCache = i.inCMakeCache;
557                 ni.values = i.values;
558                 switch (i.type) {
559                 case CMakeProjectManager::ConfigModel::DataItem::BOOLEAN:
560                     ni.type = CMakeConfigItem::BOOL;
561                     break;
562                 case CMakeProjectManager::ConfigModel::DataItem::FILE:
563                     ni.type = CMakeConfigItem::FILEPATH;
564                     break;
565                 case CMakeProjectManager::ConfigModel::DataItem::DIRECTORY:
566                     ni.type = CMakeConfigItem::PATH;
567                     break;
568                 case CMakeProjectManager::ConfigModel::DataItem::STRING:
569                     ni.type = CMakeConfigItem::STRING;
570                     break;
571                 case CMakeProjectManager::ConfigModel::DataItem::UNKNOWN:
572                 default:
573                     ni.type = CMakeConfigItem::UNINITIALIZED;
574                     break;
575                 }
576                 return ni;
577             });
578 
579     m_resetButton->setEnabled(m_configModel->hasChanges() && !isParsing);
580     m_reconfigureButton->setEnabled(!configChanges.isEmpty() && !isParsing);
581     m_buildConfiguration->setConfigurationChanges(configChanges);
582 }
583 
updateAdvancedCheckBox()584 void CMakeBuildSettingsWidget::updateAdvancedCheckBox()
585 {
586     if (m_showAdvancedCheckBox->isChecked()) {
587         m_configFilterModel->setSourceModel(nullptr);
588         m_configTextFilterModel->setSourceModel(m_configModel);
589 
590     } else {
591         m_configTextFilterModel->setSourceModel(nullptr);
592         m_configFilterModel->setSourceModel(m_configModel);
593         m_configTextFilterModel->setSourceModel(m_configFilterModel);
594     }
595 }
596 
updateFromKit()597 void CMakeBuildSettingsWidget::updateFromKit()
598 {
599     const Kit *k = m_buildConfiguration->kit();
600     const CMakeConfig config = CMakeConfigurationKitAspect::configuration(k);
601 
602     QHash<QString, QString> configHash;
603     for (const CMakeConfigItem &i : config)
604         configHash.insert(QString::fromUtf8(i.key), i.expandedValue(k));
605 
606     m_configModel->setConfigurationFromKit(configHash);
607 }
608 
getQmlDebugCxxFlags()609 CMakeConfig CMakeBuildSettingsWidget::getQmlDebugCxxFlags()
610 {
611     const auto aspect = m_buildConfiguration->aspect<QtSupport::QmlDebuggingAspect>();
612     const TriState qmlDebuggingState = aspect->value();
613     if (qmlDebuggingState == TriState::Default) // don't touch anything
614         return {};
615     const bool enable = aspect->value() == TriState::Enabled;
616 
617     const CMakeConfig configList = m_buildConfiguration->configurationFromCMake();
618     const QByteArrayList cxxFlags{"CMAKE_CXX_FLAGS", "CMAKE_CXX_FLAGS_DEBUG",
619                                   "CMAKE_CXX_FLAGS_RELWITHDEBINFO"};
620     const QByteArray qmlDebug("-DQT_QML_DEBUG");
621 
622     CMakeConfig changedConfig;
623 
624     for (const CMakeConfigItem &item : configList) {
625         if (!cxxFlags.contains(item.key))
626             continue;
627 
628         CMakeConfigItem it(item);
629         if (enable) {
630             if (!it.value.contains(qmlDebug)) {
631                 it.value = it.value.append(' ').append(qmlDebug).trimmed();
632                 changedConfig.append(it);
633             }
634         } else {
635             int index = it.value.indexOf(qmlDebug);
636             if (index != -1) {
637                 it.value.remove(index, qmlDebug.length());
638                 it.value = it.value.trimmed();
639                 changedConfig.append(it);
640             }
641         }
642     }
643     return changedConfig;
644 }
645 
getSigningFlagsChanges()646 CMakeConfig CMakeBuildSettingsWidget::getSigningFlagsChanges()
647 {
648     const CMakeConfig flags = m_buildConfiguration->signingFlags();
649     if (flags.isEmpty())
650         return {};
651     const CMakeConfig configList = m_buildConfiguration->configurationFromCMake();
652     if (configList.isEmpty()) {
653         // we don't have any configuration --> initial configuration takes care of this itself
654         return {};
655     }
656     CMakeConfig changedConfig;
657     for (const CMakeConfigItem &signingFlag : flags) {
658         const CMakeConfigItem existingFlag = Utils::findOrDefault(configList,
659                                                                   Utils::equal(&CMakeConfigItem::key,
660                                                                                signingFlag.key));
661         const bool notInConfig = existingFlag.key.isEmpty();
662         if (notInConfig != signingFlag.isUnset || existingFlag.value != signingFlag.value)
663             changedConfig.append(signingFlag);
664     }
665     return changedConfig;
666 }
667 
updateSelection()668 void CMakeBuildSettingsWidget::updateSelection()
669 {
670     const QModelIndexList selectedIndexes = m_configView->selectionModel()->selectedIndexes();
671     unsigned int setableCount = 0;
672     unsigned int unsetableCount = 0;
673     unsigned int editableCount = 0;
674 
675     for (const QModelIndex &index : selectedIndexes) {
676         if (index.isValid() && index.flags().testFlag(Qt::ItemIsSelectable)) {
677             const ConfigModel::DataItem di = ConfigModel::dataItemFromIndex(index);
678             if (di.isUnset)
679                 setableCount++;
680             else
681                 unsetableCount++;
682         }
683         if (index.isValid() && index.flags().testFlag(Qt::ItemIsEditable))
684             editableCount++;
685     }
686 
687     m_clearSelectionButton->setEnabled(!selectedIndexes.isEmpty());
688     m_setButton->setEnabled(setableCount > 0);
689     m_unsetButton->setEnabled(unsetableCount > 0);
690     m_editButton->setEnabled(editableCount == 1);
691 }
692 
setVariableUnsetFlag(bool unsetFlag)693 void CMakeBuildSettingsWidget::setVariableUnsetFlag(bool unsetFlag)
694 {
695     const QModelIndexList selectedIndexes = m_configView->selectionModel()->selectedIndexes();
696     bool unsetFlagToggled = false;
697     for (const QModelIndex &index : selectedIndexes) {
698         if (index.isValid()) {
699             const ConfigModel::DataItem di = ConfigModel::dataItemFromIndex(index);
700             if (di.isUnset != unsetFlag) {
701                 m_configModel->toggleUnsetFlag(mapToSource(m_configView, index));
702                 unsetFlagToggled = true;
703             }
704         }
705     }
706 
707     if (unsetFlagToggled)
708         updateSelection();
709 }
710 
createForceAction(int type,const QModelIndex & idx)711 QAction *CMakeBuildSettingsWidget::createForceAction(int type, const QModelIndex &idx)
712 {
713     auto t = static_cast<ConfigModel::DataItem::Type>(type);
714     QString typeString;
715     switch (type) {
716     case ConfigModel::DataItem::BOOLEAN:
717         typeString = tr("bool", "display string for cmake type BOOLEAN");
718         break;
719     case ConfigModel::DataItem::FILE:
720         typeString = tr("file", "display string for cmake type FILE");
721         break;
722     case ConfigModel::DataItem::DIRECTORY:
723         typeString = tr("directory", "display string for cmake type DIRECTORY");
724         break;
725     case ConfigModel::DataItem::STRING:
726         typeString = tr("string", "display string for cmake type STRING");
727         break;
728     case ConfigModel::DataItem::UNKNOWN:
729         return nullptr;
730     }
731     QAction *forceAction = new QAction(tr("Force to %1").arg(typeString), nullptr);
732     forceAction->setEnabled(m_configModel->canForceTo(idx, t));
733     connect(forceAction, &QAction::triggered,
734             this, [this, idx, t]() { m_configModel->forceTo(idx, t); });
735     return forceAction;
736 }
737 
eventFilter(QObject * target,QEvent * event)738 bool CMakeBuildSettingsWidget::eventFilter(QObject *target, QEvent *event)
739 {
740     // handle context menu events:
741     if (target != m_configView->viewport() || event->type() != QEvent::ContextMenu)
742         return false;
743 
744     auto e = static_cast<QContextMenuEvent *>(event);
745     const QModelIndex idx = mapToSource(m_configView, m_configView->indexAt(e->pos()));
746     if (!idx.isValid())
747         return false;
748 
749     auto menu = new QMenu(this);
750     connect(menu, &QMenu::triggered, menu, &QMenu::deleteLater);
751 
752     QAction *action = nullptr;
753     if ((action = createForceAction(ConfigModel::DataItem::BOOLEAN, idx)))
754         menu->addAction(action);
755     if ((action = createForceAction(ConfigModel::DataItem::FILE, idx)))
756         menu->addAction(action);
757     if ((action = createForceAction(ConfigModel::DataItem::DIRECTORY, idx)))
758         menu->addAction(action);
759     if ((action = createForceAction(ConfigModel::DataItem::STRING, idx)))
760         menu->addAction(action);
761 
762     auto copy = new QAction(tr("Copy"), this);
763     menu->addAction(copy);
764     connect(copy, &QAction::triggered, this, [this] {
765         const QModelIndexList selectedIndexes = m_configView->selectionModel()->selectedIndexes();
766 
767         const QModelIndexList validIndexes = Utils::filtered(selectedIndexes, [](const QModelIndex &index) {
768             return index.isValid() && index.flags().testFlag(Qt::ItemIsSelectable);
769         });
770 
771         const QStringList variableList = Utils::transform(validIndexes, [this](const QModelIndex &index) {
772             return ConfigModel::dataItemFromIndex(index)
773                     .toCMakeConfigItem().toArgument(m_buildConfiguration->macroExpander());
774         });
775 
776         QApplication::clipboard()->setText(variableList.join('\n'), QClipboard::Clipboard);
777     });
778 
779     menu->move(e->globalPos());
780     menu->show();
781 
782     return true;
783 }
784 
isIos(const Kit * k)785 static bool isIos(const Kit *k)
786 {
787     const Id deviceType = DeviceTypeKitAspect::deviceTypeId(k);
788     return deviceType == Ios::Constants::IOS_DEVICE_TYPE
789            || deviceType == Ios::Constants::IOS_SIMULATOR_TYPE;
790 }
791 
isWebAssembly(const Kit * k)792 static bool isWebAssembly(const Kit *k)
793 {
794     return DeviceTypeKitAspect::deviceTypeId(k) == WebAssembly::Constants::WEBASSEMBLY_DEVICE_TYPE;
795 }
796 
isQnx(const Kit * k)797 static bool isQnx(const Kit *k)
798 {
799     return DeviceTypeKitAspect::deviceTypeId(k) == Qnx::Constants::QNX_QNX_OS_TYPE;
800 }
801 
defaultInitialCMakeArguments(const Kit * k,const QString buildType)802 static QStringList defaultInitialCMakeArguments(const Kit *k, const QString buildType)
803 {
804     // Generator:
805     QStringList initialArgs = CMakeGeneratorKitAspect::generatorArguments(k);
806 
807     // CMAKE_BUILD_TYPE:
808     if (!buildType.isEmpty() && !CMakeGeneratorKitAspect::isMultiConfigGenerator(k)) {
809         initialArgs.append(QString::fromLatin1("-DCMAKE_BUILD_TYPE:STRING=%1").arg(buildType));
810     }
811 
812     Internal::CMakeSpecificSettings *settings
813         = Internal::CMakeProjectPlugin::projectTypeSpecificSettings();
814 
815     // Package manager
816     if (settings->packageManagerAutoSetup.value())
817         initialArgs.append(QString::fromLatin1("-DCMAKE_PROJECT_INCLUDE_BEFORE:PATH=%1")
818                            .arg("%{IDE:ResourcePath}/package-manager/auto-setup.cmake"));
819 
820     // Cross-compilation settings:
821     if (!isIos(k)) { // iOS handles this differently
822         const QString sysRoot = SysRootKitAspect::sysRoot(k).path();
823         if (!sysRoot.isEmpty()) {
824             initialArgs.append(QString::fromLatin1("-DCMAKE_SYSROOT:PATH=%1").arg(sysRoot));
825             if (ToolChain *tc = ToolChainKitAspect::cxxToolChain(k)) {
826                 const QString targetTriple = tc->originalTargetTriple();
827                 initialArgs.append(
828                     QString::fromLatin1("-DCMAKE_C_COMPILER_TARGET:STRING=%1").arg(targetTriple));
829                 initialArgs.append(
830                     QString::fromLatin1("-DCMAKE_CXX_COMPILER_TARGET:STRING=%1").arg(targetTriple));
831             }
832         }
833     }
834 
835     initialArgs += CMakeConfigurationKitAspect::toArgumentsList(k);
836 
837     return initialArgs;
838 }
839 
840 } // namespace Internal
841 
842 // -----------------------------------------------------------------------------
843 // CMakeBuildConfiguration:
844 // -----------------------------------------------------------------------------
845 
CMakeBuildConfiguration(Target * target,Id id)846 CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id)
847     : BuildConfiguration(target, id)
848 {
849     m_buildSystem = new CMakeBuildSystem(this);
850 
851     const auto buildDirAspect = aspect<BuildDirectoryAspect>();
852     buildDirAspect->setValueAcceptor(
853         [](const QString &oldDir, const QString &newDir) -> Utils::optional<QString> {
854             if (oldDir.isEmpty())
855                 return newDir;
856 
857             if (QDir(oldDir).exists("CMakeCache.txt") && !QDir(newDir).exists("CMakeCache.txt")) {
858                 if (QMessageBox::information(
859                         Core::ICore::dialogParent(),
860                         tr("Changing Build Directory"),
861                         tr("Change the build directory to \"%1\" and start with a "
862                            "basic CMake configuration?")
863                             .arg(newDir),
864                         QMessageBox::Ok,
865                         QMessageBox::Cancel)
866                     == QMessageBox::Ok) {
867                     return newDir;
868                 }
869                 return Utils::nullopt;
870             }
871             return newDir;
872         });
873 
874     auto initialCMakeArgumentsAspect = addAspect<InitialCMakeArgumentsAspect>();
875     initialCMakeArgumentsAspect->setMacroExpanderProvider([this]{ return macroExpander(); });
876     macroExpander()->registerVariable(DEVELOPMENT_TEAM_FLAG,
877                                       tr("The CMake flag for the development team"),
878                                       [this] {
879                                           const CMakeConfig flags = signingFlags();
880                                           if (!flags.isEmpty())
881                                               return flags.first().toArgument();
882                                           return QString();
883                                       });
884     macroExpander()->registerVariable(PROVISIONING_PROFILE_FLAG,
885                                       tr("The CMake flag for the provisioning profile"),
886                                       [this] {
887                                           const CMakeConfig flags = signingFlags();
888                                           if (flags.size() > 1 && !flags.at(1).isUnset) {
889                                               return flags.at(1).toArgument();
890                                           }
891                                           return QString();
892                                       });
893 
894     macroExpander()->registerVariable(CMAKE_OSX_ARCHITECTURES_FLAG,
895                                       tr("The CMake flag for the architecture on macOS"),
896                                       [target] {
897                                           if (HostOsInfo::isRunningUnderRosetta()) {
898                                               if (auto *qt = QtSupport::QtKitAspect::qtVersion(target->kit())) {
899                                                   const Abis abis = qt->qtAbis();
900                                                   for (const Abi &abi : abis) {
901                                                       if (abi.architecture() == Abi::ArmArchitecture)
902                                                           return QLatin1String("-DCMAKE_OSX_ARCHITECTURES=arm64");
903                                                   }
904                                               }
905                                           }
906                                           return QLatin1String();
907                                       });
908 
909     addAspect<SourceDirectoryAspect>();
910     addAspect<BuildTypeAspect>();
911 
912     appendInitialBuildStep(Constants::CMAKE_BUILD_STEP_ID);
913     appendInitialCleanStep(Constants::CMAKE_BUILD_STEP_ID);
914 
915     setInitializer([this, target](const BuildInfo &info) {
916         const Kit *k = target->kit();
917 
918         QStringList initialArgs = defaultInitialCMakeArguments(k, info.typeName);
919         setIsMultiConfig(CMakeGeneratorKitAspect::isMultiConfigGenerator(k));
920 
921         // Android magic:
922         if (DeviceTypeKitAspect::deviceTypeId(k) == Android::Constants::ANDROID_DEVICE_TYPE) {
923             buildSteps()->appendStep(Android::Constants::ANDROID_BUILD_APK_ID);
924             const auto &bs = buildSteps()->steps().constLast();
925             initialArgs.append("-DANDROID_NATIVE_API_LEVEL:STRING="
926                    + bs->data(Android::Constants::AndroidNdkPlatform).toString());
927             auto ndkLocation = bs->data(Android::Constants::NdkLocation).value<FilePath>();
928             initialArgs.append("-DANDROID_NDK:PATH=" + ndkLocation.path());
929 
930             initialArgs.append("-DCMAKE_TOOLCHAIN_FILE:PATH="
931                    + ndkLocation.pathAppended("build/cmake/android.toolchain.cmake").path());
932 
933             auto androidAbis = bs->data(Android::Constants::AndroidABIs).toStringList();
934             QString preferredAbi;
935             if (androidAbis.contains(ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A)) {
936                 preferredAbi = ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A;
937             } else if (androidAbis.isEmpty()
938                        || androidAbis.contains(ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A)) {
939                 preferredAbi = ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A;
940             } else {
941                 preferredAbi = androidAbis.first();
942             }
943             initialArgs.append("-DANDROID_ABI:STRING=" + preferredAbi);
944             initialArgs.append("-DANDROID_STL:STRING=c++_shared");
945             initialArgs.append("-DCMAKE_FIND_ROOT_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}");
946 
947             QtSupport::BaseQtVersion *qt = QtSupport::QtKitAspect::qtVersion(k);
948             auto sdkLocation = bs->data(Android::Constants::SdkLocation).value<FilePath>();
949 
950             if (qt && qt->qtVersion() >= QtSupport::QtVersionNumber{6, 0, 0}) {
951                 // Don't build apk under ALL target because Qt Creator will handle it
952                 if (qt->qtVersion() >= QtSupport::QtVersionNumber{6, 1, 0})
953                     initialArgs.append("-DQT_NO_GLOBAL_APK_TARGET_PART_OF_ALL:BOOL=ON");
954                 initialArgs.append("-DQT_HOST_PATH:PATH=%{Qt:QT_HOST_PREFIX}");
955                 initialArgs.append("-DANDROID_SDK_ROOT:PATH=" + sdkLocation.path());
956             } else {
957                 initialArgs.append("-DANDROID_SDK:PATH=" + sdkLocation.path());
958             }
959         }
960 
961         const IDevice::ConstPtr device = DeviceKitAspect::device(k);
962         if (isIos(k)) {
963             QtSupport::BaseQtVersion *qt = QtSupport::QtKitAspect::qtVersion(k);
964             if (qt && qt->qtVersion().majorVersion >= 6) {
965                 // TODO it would be better if we could set
966                 // CMAKE_SYSTEM_NAME=iOS and CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=YES
967                 // and build with "cmake --build . -- -arch <arch>" instead of setting the architecture
968                 // and sysroot in the CMake configuration, but that currently doesn't work with Qt/CMake
969                 // https://gitlab.kitware.com/cmake/cmake/-/issues/21276
970                 const Id deviceType = DeviceTypeKitAspect::deviceTypeId(k);
971                 // TODO the architectures are probably not correct with Apple Silicon in the mix...
972                 const QString architecture = deviceType == Ios::Constants::IOS_DEVICE_TYPE
973                                                  ? QLatin1String("arm64")
974                                                  : QLatin1String("x86_64");
975                 const QString sysroot = deviceType == Ios::Constants::IOS_DEVICE_TYPE
976                                             ? QLatin1String("iphoneos")
977                                             : QLatin1String("iphonesimulator");
978                 initialArgs.append(CMAKE_QT6_TOOLCHAIN_FILE_ARG);
979                 initialArgs.append("-DCMAKE_OSX_ARCHITECTURES:STRING=" + architecture);
980                 initialArgs.append("-DCMAKE_OSX_SYSROOT:STRING=" + sysroot);
981                 initialArgs.append("%{" + QLatin1String(DEVELOPMENT_TEAM_FLAG) + "}");
982                 initialArgs.append("%{" + QLatin1String(PROVISIONING_PROFILE_FLAG) + "}");
983             }
984         } else if (device && device->osType() == Utils::OsTypeMac) {
985             initialArgs.append("%{" + QLatin1String(CMAKE_OSX_ARCHITECTURES_FLAG) + "}");
986         }
987 
988         if (isWebAssembly(k) || isQnx(k)) {
989             const QtSupport::BaseQtVersion *qt = QtSupport::QtKitAspect::qtVersion(k);
990             if (qt && qt->qtVersion().majorVersion >= 6)
991                 initialArgs.append(CMAKE_QT6_TOOLCHAIN_FILE_ARG);
992         }
993 
994         if (info.buildDirectory.isEmpty()) {
995             setBuildDirectory(shadowBuildDirectory(target->project()->projectFilePath(),
996                                                    k,
997                                                    info.displayName,
998                                                    info.buildType));
999         }
1000 
1001         if (info.extraInfo.isValid()) {
1002             setSourceDirectory(FilePath::fromVariant(
1003                         info.extraInfo.value<QVariantMap>().value(Constants::CMAKE_HOME_DIR)));
1004         }
1005 
1006         setInitialCMakeArguments(initialArgs);
1007         setCMakeBuildType(info.typeName);
1008     });
1009 
1010     const auto qmlDebuggingAspect = addAspect<QtSupport::QmlDebuggingAspect>();
1011     qmlDebuggingAspect->setKit(target->kit());
1012     setIsMultiConfig(CMakeGeneratorKitAspect::isMultiConfigGenerator(target->kit()));
1013 }
1014 
~CMakeBuildConfiguration()1015 CMakeBuildConfiguration::~CMakeBuildConfiguration()
1016 {
1017     delete m_buildSystem;
1018 }
1019 
toMap() const1020 QVariantMap CMakeBuildConfiguration::toMap() const
1021 {
1022     QVariantMap map(BuildConfiguration::toMap());
1023     return map;
1024 }
1025 
fromMap(const QVariantMap & map)1026 bool CMakeBuildConfiguration::fromMap(const QVariantMap &map)
1027 {
1028     if (!BuildConfiguration::fromMap(map))
1029         return false;
1030 
1031     const CMakeConfig conf
1032             = Utils::filtered(Utils::transform(map.value(QLatin1String(CONFIGURATION_KEY)).toStringList(),
1033                                                [](const QString &v) { return CMakeConfigItem::fromString(v); }),
1034                               [](const CMakeConfigItem &c) { return !c.isNull(); });
1035 
1036     // TODO: Upgrade from Qt Creator < 4.13: Remove when no longer supported!
1037     const QString buildTypeName = [this]() {
1038         switch (buildType()) {
1039         case Debug:
1040             return QString("Debug");
1041         case Profile:
1042             return QString("RelWithDebInfo");
1043         case Release:
1044             return QString("Release");
1045         case Unknown:
1046         default:
1047             return QString("");
1048         }
1049     }();
1050     if (initialCMakeArguments().isEmpty()) {
1051         QStringList initialArgs = defaultInitialCMakeArguments(kit(), buildTypeName)
1052                                   + Utils::transform(conf.toList(), [this](const CMakeConfigItem &i) {
1053                                         return i.toArgument(macroExpander());
1054                                     });
1055 
1056         setInitialCMakeArguments(initialArgs);
1057     }
1058 
1059     return true;
1060 }
1061 
shadowBuildDirectory(const FilePath & projectFilePath,const Kit * k,const QString & bcName,BuildConfiguration::BuildType buildType)1062 FilePath CMakeBuildConfiguration::shadowBuildDirectory(const FilePath &projectFilePath,
1063                                                        const Kit *k,
1064                                                        const QString &bcName,
1065                                                        BuildConfiguration::BuildType buildType)
1066 {
1067     if (projectFilePath.isEmpty())
1068         return FilePath();
1069 
1070     const QString projectName = projectFilePath.parentDir().fileName();
1071     ProjectMacroExpander expander(projectFilePath, projectName, k, bcName, buildType);
1072     const FilePath projectDir = Project::projectDirectory(projectFilePath);
1073     QString buildPath = expander.expand(ProjectExplorerPlugin::buildDirectoryTemplate());
1074     buildPath.replace(" ", "-");
1075 
1076     if (CMakeGeneratorKitAspect::isMultiConfigGenerator(k))
1077         buildPath = buildPath.left(buildPath.lastIndexOf(QString("-%1").arg(bcName)));
1078 
1079     return projectDir.resolvePath(buildPath);
1080 }
1081 
buildTarget(const QString & buildTarget)1082 void CMakeBuildConfiguration::buildTarget(const QString &buildTarget)
1083 {
1084     auto cmBs = qobject_cast<CMakeBuildStep *>(findOrDefault(
1085                                                    buildSteps()->steps(),
1086                                                    [](const BuildStep *bs) {
1087         return bs->id() == Constants::CMAKE_BUILD_STEP_ID;
1088     }));
1089 
1090     QStringList originalBuildTargets;
1091     if (cmBs) {
1092         originalBuildTargets = cmBs->buildTargets();
1093         cmBs->setBuildTargets({buildTarget});
1094     }
1095 
1096     BuildManager::buildList(buildSteps());
1097 
1098     if (cmBs)
1099         cmBs->setBuildTargets(originalBuildTargets);
1100 }
1101 
configurationFromCMake() const1102 CMakeConfig CMakeBuildConfiguration::configurationFromCMake() const
1103 {
1104     return m_configurationFromCMake;
1105 }
1106 
configurationChanges() const1107 CMakeConfig CMakeBuildConfiguration::configurationChanges() const
1108 {
1109     return m_configurationChanges;
1110 }
1111 
configurationChangesArguments() const1112 QStringList CMakeBuildConfiguration::configurationChangesArguments() const
1113 {
1114     return Utils::transform(m_configurationChanges.toList(),
1115                             [](const CMakeConfigItem &i) { return i.toArgument(); });
1116 }
1117 
initialCMakeArguments() const1118 QStringList CMakeBuildConfiguration::initialCMakeArguments() const
1119 {
1120     return aspect<InitialCMakeArgumentsAspect>()->value().split('\n', Qt::SkipEmptyParts);
1121 }
1122 
setConfigurationFromCMake(const CMakeConfig & config)1123 void CMakeBuildConfiguration::setConfigurationFromCMake(const CMakeConfig &config)
1124 {
1125     m_configurationFromCMake = config;
1126 }
1127 
setConfigurationChanges(const CMakeConfig & config)1128 void CMakeBuildConfiguration::setConfigurationChanges(const CMakeConfig &config)
1129 {
1130     qCDebug(cmakeBuildConfigurationLog)
1131         << "Configuration changes before:" << configurationChangesArguments();
1132 
1133     m_configurationChanges = config;
1134 
1135     qCDebug(cmakeBuildConfigurationLog)
1136         << "Configuration changes after:" << configurationChangesArguments();
1137 }
1138 
1139 // FIXME: Run clean steps when a setting starting with "ANDROID_BUILD_ABI_" is changed.
1140 // FIXME: Warn when kit settings are overridden by a project.
1141 
clearError(ForceEnabledChanged fec)1142 void CMakeBuildConfiguration::clearError(ForceEnabledChanged fec)
1143 {
1144     if (!m_error.isEmpty()) {
1145         m_error.clear();
1146         fec = ForceEnabledChanged::True;
1147     }
1148     if (fec == ForceEnabledChanged::True) {
1149         qCDebug(cmakeBuildConfigurationLog) << "Emitting enabledChanged signal";
1150         emit enabledChanged();
1151     }
1152 }
1153 
setInitialCMakeArguments(const QStringList & args)1154 void CMakeBuildConfiguration::setInitialCMakeArguments(const QStringList &args)
1155 {
1156     aspect<InitialCMakeArgumentsAspect>()->setValue(args.join('\n'));
1157 }
1158 
setError(const QString & message)1159 void CMakeBuildConfiguration::setError(const QString &message)
1160 {
1161     qCDebug(cmakeBuildConfigurationLog) << "Setting error to" << message;
1162     QTC_ASSERT(!message.isEmpty(), return );
1163 
1164     const QString oldMessage = m_error;
1165     if (m_error != message)
1166         m_error = message;
1167     if (oldMessage.isEmpty() != !message.isEmpty()) {
1168         qCDebug(cmakeBuildConfigurationLog) << "Emitting enabledChanged signal";
1169         emit enabledChanged();
1170     }
1171     emit errorOccurred(m_error);
1172 }
1173 
setWarning(const QString & message)1174 void CMakeBuildConfiguration::setWarning(const QString &message)
1175 {
1176     if (m_warning == message)
1177         return;
1178     m_warning = message;
1179     emit warningOccurred(m_warning);
1180 }
1181 
error() const1182 QString CMakeBuildConfiguration::error() const
1183 {
1184     return m_error;
1185 }
1186 
warning() const1187 QString CMakeBuildConfiguration::warning() const
1188 {
1189     return m_warning;
1190 }
1191 
createConfigWidget()1192 NamedWidget *CMakeBuildConfiguration::createConfigWidget()
1193 {
1194     return new CMakeBuildSettingsWidget(this);
1195 }
1196 
signingFlags() const1197 CMakeConfig CMakeBuildConfiguration::signingFlags() const
1198 {
1199     return {};
1200 }
1201 
1202 /*!
1203   \class CMakeBuildConfigurationFactory
1204 */
1205 
CMakeBuildConfigurationFactory()1206 CMakeBuildConfigurationFactory::CMakeBuildConfigurationFactory()
1207 {
1208     registerBuildConfiguration<CMakeBuildConfiguration>(Constants::CMAKE_BUILDCONFIGURATION_ID);
1209 
1210     setSupportedProjectType(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
1211     setSupportedProjectMimeTypeName(Constants::CMAKE_PROJECT_MIMETYPE);
1212 
1213     setBuildGenerator([](const Kit *k, const FilePath &projectPath, bool forSetup) {
1214         QList<BuildInfo> result;
1215 
1216         FilePath path = forSetup ? Project::projectDirectory(projectPath) : projectPath;
1217 
1218         for (int type = BuildTypeDebug; type != BuildTypeLast; ++type) {
1219             BuildInfo info = createBuildInfo(BuildType(type));
1220             if (forSetup) {
1221                 info.buildDirectory = CMakeBuildConfiguration::shadowBuildDirectory(projectPath,
1222                                 k,
1223                                 info.typeName,
1224                                 info.buildType);
1225             }
1226             result << info;
1227         }
1228         return result;
1229     });
1230 }
1231 
buildTypeFromByteArray(const QByteArray & in)1232 CMakeBuildConfigurationFactory::BuildType CMakeBuildConfigurationFactory::buildTypeFromByteArray(
1233     const QByteArray &in)
1234 {
1235     const QByteArray bt = in.toLower();
1236     if (bt == "debug")
1237         return BuildTypeDebug;
1238     if (bt == "release")
1239         return BuildTypeRelease;
1240     if (bt == "relwithdebinfo")
1241         return BuildTypeRelWithDebInfo;
1242     if (bt == "minsizerel")
1243         return BuildTypeMinSizeRel;
1244     return BuildTypeNone;
1245 }
1246 
cmakeBuildTypeToBuildType(const CMakeBuildConfigurationFactory::BuildType & in)1247 BuildConfiguration::BuildType CMakeBuildConfigurationFactory::cmakeBuildTypeToBuildType(
1248     const CMakeBuildConfigurationFactory::BuildType &in)
1249 {
1250     // Cover all common CMake build types
1251     if (in == BuildTypeRelease || in == BuildTypeMinSizeRel)
1252         return BuildConfiguration::Release;
1253     else if (in == BuildTypeDebug)
1254         return BuildConfiguration::Debug;
1255     else if (in == BuildTypeRelWithDebInfo)
1256         return BuildConfiguration::Profile;
1257     else
1258         return BuildConfiguration::Unknown;
1259 }
1260 
createBuildInfo(BuildType buildType)1261 BuildInfo CMakeBuildConfigurationFactory::createBuildInfo(BuildType buildType)
1262 {
1263     BuildInfo info;
1264 
1265     switch (buildType) {
1266     case BuildTypeNone:
1267         info.typeName = "Build";
1268         info.displayName = BuildConfiguration::tr("Build");
1269         info.buildType = BuildConfiguration::Unknown;
1270         break;
1271     case BuildTypeDebug:
1272         info.typeName = "Debug";
1273         info.displayName = BuildConfiguration::tr("Debug");
1274         info.buildType = BuildConfiguration::Debug;
1275         break;
1276     case BuildTypeRelease:
1277         info.typeName = "Release";
1278         info.displayName = BuildConfiguration::tr("Release");
1279         info.buildType = BuildConfiguration::Release;
1280         break;
1281     case BuildTypeMinSizeRel:
1282         info.typeName = "MinSizeRel";
1283         info.displayName = CMakeBuildConfiguration::tr("Minimum Size Release");
1284         info.buildType = BuildConfiguration::Release;
1285         break;
1286     case BuildTypeRelWithDebInfo:
1287         info.typeName = "RelWithDebInfo";
1288         info.displayName = CMakeBuildConfiguration::tr("Release with Debug Information");
1289         info.buildType = BuildConfiguration::Profile;
1290         break;
1291     default:
1292         QTC_CHECK(false);
1293         break;
1294     }
1295 
1296     return info;
1297 }
1298 
buildType() const1299 BuildConfiguration::BuildType CMakeBuildConfiguration::buildType() const
1300 {
1301     QByteArray cmakeBuildTypeName = m_configurationFromCMake.valueOf("CMAKE_BUILD_TYPE");
1302     if (cmakeBuildTypeName.isEmpty()) {
1303         QByteArray cmakeCfgTypes = m_configurationFromCMake.valueOf("CMAKE_CONFIGURATION_TYPES");
1304         if (!cmakeCfgTypes.isEmpty())
1305             cmakeBuildTypeName = cmakeBuildType().toUtf8();
1306     }
1307     // Cover all common CMake build types
1308     const CMakeBuildConfigurationFactory::BuildType cmakeBuildType
1309         = CMakeBuildConfigurationFactory::buildTypeFromByteArray(cmakeBuildTypeName);
1310     return CMakeBuildConfigurationFactory::cmakeBuildTypeToBuildType(cmakeBuildType);
1311 }
1312 
buildSystem() const1313 BuildSystem *CMakeBuildConfiguration::buildSystem() const
1314 {
1315     return m_buildSystem;
1316 }
1317 
runCMakeWithExtraArguments()1318 void CMakeBuildConfiguration::runCMakeWithExtraArguments()
1319 {
1320     m_buildSystem->runCMakeWithExtraArguments();
1321 }
1322 
setSourceDirectory(const FilePath & path)1323 void CMakeBuildConfiguration::setSourceDirectory(const FilePath &path)
1324 {
1325     aspect<SourceDirectoryAspect>()->setValue(path.toString());
1326 }
1327 
sourceDirectory() const1328 FilePath CMakeBuildConfiguration::sourceDirectory() const
1329 {
1330     return FilePath::fromString(aspect<SourceDirectoryAspect>()->value());
1331 }
1332 
cmakeBuildType() const1333 QString CMakeBuildConfiguration::cmakeBuildType() const
1334 {
1335     auto setBuildTypeFromConfig = [this](const CMakeConfig &config){
1336         auto it = std::find_if(config.begin(), config.end(),
1337                             [](const CMakeConfigItem &item) { return item.key == "CMAKE_BUILD_TYPE";});
1338         if (it != config.end())
1339             const_cast<CMakeBuildConfiguration*>(this)
1340                 ->setCMakeBuildType(QString::fromUtf8(it->value));
1341     };
1342 
1343     if (!isMultiConfig())
1344         setBuildTypeFromConfig(configurationChanges());
1345 
1346     QString cmakeBuildType = aspect<BuildTypeAspect>()->value();
1347 
1348     const Utils::FilePath cmakeCacheTxt = buildDirectory().pathAppended("CMakeCache.txt");
1349     const bool hasCMakeCache = QFile::exists(cmakeCacheTxt.toString());
1350     CMakeConfig config;
1351 
1352     if (cmakeBuildType == "Unknown") {
1353         // The "Unknown" type is the case of loading of an existing project
1354         // that doesn't have the "CMake.Build.Type" aspect saved
1355         if (hasCMakeCache) {
1356             QString errorMessage;
1357             config = CMakeBuildSystem::parseCMakeCacheDotTxt(cmakeCacheTxt, &errorMessage);
1358         } else {
1359             config = CMakeConfig::fromArguments(initialCMakeArguments());
1360         }
1361     } else if (!hasCMakeCache) {
1362         config = CMakeConfig::fromArguments(initialCMakeArguments());
1363     }
1364 
1365     if (!config.isEmpty() && !isMultiConfig())
1366         setBuildTypeFromConfig(config);
1367 
1368     return cmakeBuildType;
1369 }
1370 
setCMakeBuildType(const QString & cmakeBuildType,bool quiet)1371 void CMakeBuildConfiguration::setCMakeBuildType(const QString &cmakeBuildType, bool quiet)
1372 {
1373     if (quiet) {
1374         aspect<BuildTypeAspect>()->setValueQuietly(cmakeBuildType);
1375         aspect<BuildTypeAspect>()->update();
1376     } else {
1377         aspect<BuildTypeAspect>()->setValue(cmakeBuildType);
1378     }
1379 }
1380 
isMultiConfig() const1381 bool CMakeBuildConfiguration::isMultiConfig() const
1382 {
1383     return m_isMultiConfig;
1384 }
1385 
setIsMultiConfig(bool isMultiConfig)1386 void CMakeBuildConfiguration::setIsMultiConfig(bool isMultiConfig)
1387 {
1388     m_isMultiConfig = isMultiConfig;
1389 }
1390 
1391 namespace Internal {
1392 
1393 // ----------------------------------------------------------------------
1394 // - InitialCMakeParametersAspect:
1395 // ----------------------------------------------------------------------
1396 
InitialCMakeArgumentsAspect()1397 InitialCMakeArgumentsAspect::InitialCMakeArgumentsAspect()
1398 {
1399     setSettingsKey("CMake.Initial.Parameters");
1400     setLabelText(tr("Initial CMake parameters:"));
1401     setDisplayStyle(TextEditDisplay);
1402 }
1403 
1404 // -----------------------------------------------------------------------------
1405 // SourceDirectoryAspect:
1406 // -----------------------------------------------------------------------------
SourceDirectoryAspect()1407 SourceDirectoryAspect::SourceDirectoryAspect()
1408 {
1409     // Will not be displayed, only persisted
1410     setSettingsKey("CMake.Source.Directory");
1411 }
1412 
1413 // -----------------------------------------------------------------------------
1414 // BuildTypeAspect:
1415 // -----------------------------------------------------------------------------
BuildTypeAspect()1416 BuildTypeAspect::BuildTypeAspect()
1417 {
1418     setSettingsKey("CMake.Build.Type");
1419     setLabelText(tr("Build type:"));
1420     setDisplayStyle(LineEditDisplay);
1421     setDefaultValue("Unknown");
1422 }
1423 
1424 } // namespace Internal
1425 } // namespace CMakeProjectManager
1426