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