/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "cmakebuildconfiguration.h" #include "cmakebuildconfiguration.h" #include "cmakebuildstep.h" #include "cmakebuildsystem.h" #include "cmakeconfigitem.h" #include "cmakekitinformation.h" #include "cmakeprojectconstants.h" #include "cmakeprojectplugin.h" #include "cmakespecificsettings.h" #include "configmodel.h" #include "configmodelitemdelegate.h" #include "fileapiparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; using namespace CMakeProjectManager::Internal; namespace CMakeProjectManager { static Q_LOGGING_CATEGORY(cmakeBuildConfigurationLog, "qtc.cmake.bc", QtWarningMsg); const char CONFIGURATION_KEY[] = "CMake.Configuration"; const char DEVELOPMENT_TEAM_FLAG[] = "Ios:DevelopmentTeam:Flag"; const char PROVISIONING_PROFILE_FLAG[] = "Ios:ProvisioningProfile:Flag"; const char CMAKE_OSX_ARCHITECTURES_FLAG[] = "CMAKE_OSX_ARCHITECTURES:DefaultFlag"; const char QT_QML_DEBUG_FLAG[] = "Qt:QML_DEBUG_FLAG"; const char CMAKE_QT6_TOOLCHAIN_FILE_ARG[] = "-DCMAKE_TOOLCHAIN_FILE:FILEPATH=%{Qt:QT_INSTALL_PREFIX}/lib/cmake/Qt6/qt.toolchain.cmake"; const char CMAKE_BUILD_TYPE[] = "CMake.Build.Type"; namespace Internal { class CMakeBuildSettingsWidget : public NamedWidget { Q_DECLARE_TR_FUNCTIONS(CMakeProjectManager::Internal::CMakeBuildSettingsWidget) public: CMakeBuildSettingsWidget(CMakeBuildSystem *bc); void setError(const QString &message); void setWarning(const QString &message); private: void updateButtonState(); void updateAdvancedCheckBox(); void updateFromKit(); void updateConfigurationStateIndex(int index); CMakeConfig getQmlDebugCxxFlags(); CMakeConfig getSigningFlagsChanges(); void updateSelection(); void updateConfigurationStateSelection(); bool isInitialConfiguration() const; void setVariableUnsetFlag(bool unsetFlag); QAction *createForceAction(int type, const QModelIndex &idx); bool eventFilter(QObject *target, QEvent *event) override; void batchEditConfiguration(); void reconfigureWithInitialParameters(); void updateInitialCMakeArguments(); void kitCMakeConfiguration(); void updateConfigureDetailsWidgetsSummary( const QStringList &configurationArguments = QStringList()); CMakeBuildSystem *m_buildSystem; QTreeView *m_configView; ConfigModel *m_configModel; CategorySortFilterModel *m_configFilterModel; CategorySortFilterModel *m_configTextFilterModel; ProgressIndicator *m_progressIndicator; QPushButton *m_addButton; QPushButton *m_editButton; QPushButton *m_setButton; QPushButton *m_unsetButton; QPushButton *m_resetButton; QCheckBox *m_showAdvancedCheckBox; QTabBar *m_configurationStates; QPushButton *m_reconfigureButton; QTimer m_showProgressTimer; FancyLineEdit *m_filterEdit; InfoLabel *m_warningMessageLabel; DetailsWidget *m_configureDetailsWidget; QPushButton *m_batchEditButton = nullptr; QPushButton *m_kitConfiguration = nullptr; }; static QModelIndex mapToSource(const QAbstractItemView *view, const QModelIndex &idx) { if (!idx.isValid()) return idx; QAbstractItemModel *model = view->model(); QModelIndex result = idx; while (auto proxy = qobject_cast(model)) { result = proxy->mapToSource(result); model = proxy->sourceModel(); } return result; } CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildSystem *bs) : NamedWidget(tr("CMake")), m_buildSystem(bs), m_configModel(new ConfigModel(this)), m_configFilterModel(new CategorySortFilterModel(this)), m_configTextFilterModel(new CategorySortFilterModel(this)) { QTC_ASSERT(bs, return); BuildConfiguration *bc = bs->buildConfiguration(); auto vbox = new QVBoxLayout(this); vbox->setContentsMargins(0, 0, 0, 0); m_configureDetailsWidget = new DetailsWidget; updateConfigureDetailsWidgetsSummary(); vbox->addWidget(m_configureDetailsWidget); auto details = new QWidget(m_configureDetailsWidget); m_configureDetailsWidget->setWidget(details); auto buildDirAspect = bc->buildDirectoryAspect(); buildDirAspect->setAutoApplyOnEditingFinished(true); connect(buildDirAspect, &BaseAspect::changed, this, [this]() { m_configModel->flush(); // clear out config cache...; }); auto buildTypeAspect = bc->aspect(); connect(buildTypeAspect, &BaseAspect::changed, this, [this, buildTypeAspect] { if (!m_buildSystem->isMultiConfig()) { CMakeConfig config; config << CMakeConfigItem("CMAKE_BUILD_TYPE", buildTypeAspect->value().toUtf8()); m_configModel->setBatchEditConfiguration(config); } }); auto qmlDebugAspect = bc->aspect(); connect(qmlDebugAspect, &QtSupport::QmlDebuggingAspect::changed, this, [this]() { updateButtonState(); }); m_warningMessageLabel = new InfoLabel({}, InfoLabel::Warning); m_warningMessageLabel->setVisible(false); m_configurationStates = new QTabBar(this); m_configurationStates->addTab(tr("Initial Configuration")); m_configurationStates->addTab(tr("Current Configuration")); connect(m_configurationStates, &QTabBar::currentChanged, this, [this](int index) { updateConfigurationStateIndex(index); }); m_kitConfiguration = new QPushButton(tr("Kit Configuration")); m_kitConfiguration->setToolTip(tr("Edit the current kit's CMake configuration.")); m_kitConfiguration->setFixedWidth(m_kitConfiguration->sizeHint().width()); connect(m_kitConfiguration, &QPushButton::clicked, this, [this]() { kitCMakeConfiguration(); }); m_filterEdit = new FancyLineEdit; m_filterEdit->setPlaceholderText(tr("Filter")); m_filterEdit->setFiltering(true); auto tree = new TreeView; connect(tree, &TreeView::activated, tree, [tree](const QModelIndex &idx) { tree->edit(idx); }); m_configView = tree; m_configView->viewport()->installEventFilter(this); m_configFilterModel->setSourceModel(m_configModel); m_configFilterModel->setFilterKeyColumn(0); m_configFilterModel->setFilterRole(ConfigModel::ItemIsAdvancedRole); m_configFilterModel->setFilterFixedString("0"); m_configTextFilterModel->setSourceModel(m_configFilterModel); m_configTextFilterModel->setSortRole(Qt::DisplayRole); m_configTextFilterModel->setFilterKeyColumn(-1); connect(m_configTextFilterModel, &QAbstractItemModel::layoutChanged, this, [this]() { QModelIndex selectedIdx = m_configView->currentIndex(); if (selectedIdx.isValid()) m_configView->scrollTo(selectedIdx); }); m_configView->setModel(m_configTextFilterModel); m_configView->setMinimumHeight(300); m_configView->setUniformRowHeights(true); m_configView->setSortingEnabled(true); m_configView->sortByColumn(0, Qt::AscendingOrder); (void) new HeaderViewStretcher(m_configView->header(), 0); m_configView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_configView->setSelectionBehavior(QAbstractItemView::SelectItems); m_configView->setAlternatingRowColors(true); m_configView->setFrameShape(QFrame::NoFrame); m_configView->setItemDelegate(new ConfigModelItemDelegate(bc->project()->projectDirectory(), m_configView)); m_configView->setRootIsDecorated(false); QFrame *findWrapper = Core::ItemViewFind::createSearchableWrapper(m_configView, Core::ItemViewFind::LightColored); findWrapper->setFrameStyle(QFrame::StyledPanel); m_progressIndicator = new ProgressIndicator(ProgressIndicatorSize::Large, findWrapper); m_progressIndicator->attachToWidget(findWrapper); m_progressIndicator->raise(); m_progressIndicator->hide(); m_showProgressTimer.setSingleShot(true); m_showProgressTimer.setInterval(50); // don't show progress for < 50ms tasks connect(&m_showProgressTimer, &QTimer::timeout, [this]() { m_progressIndicator->show(); }); m_addButton = new QPushButton(tr("&Add")); m_addButton->setToolTip(tr("Add a new configuration value.")); auto addButtonMenu = new QMenu(this); addButtonMenu->addAction(tr("&Boolean"))->setData( QVariant::fromValue(static_cast(ConfigModel::DataItem::BOOLEAN))); addButtonMenu->addAction(tr("&String"))->setData( QVariant::fromValue(static_cast(ConfigModel::DataItem::STRING))); addButtonMenu->addAction(tr("&Directory"))->setData( QVariant::fromValue(static_cast(ConfigModel::DataItem::DIRECTORY))); addButtonMenu->addAction(tr("&File"))->setData( QVariant::fromValue(static_cast(ConfigModel::DataItem::FILE))); m_addButton->setMenu(addButtonMenu); m_editButton = new QPushButton(tr("&Edit")); m_editButton->setToolTip(tr("Edit the current CMake configuration value.")); m_setButton = new QPushButton(tr("&Set")); m_setButton->setToolTip(tr("Set a value in the CMake configuration.")); m_unsetButton = new QPushButton(tr("&Unset")); m_unsetButton->setToolTip(tr("Unset a value in the CMake configuration.")); m_resetButton = new QPushButton(tr("&Reset")); m_resetButton->setToolTip(tr("Reset all unapplied changes.")); m_resetButton->setEnabled(false); m_batchEditButton = new QPushButton(tr("Batch Edit...")); m_batchEditButton->setToolTip(tr("Set or reset multiple values in the CMake configuration.")); m_showAdvancedCheckBox = new QCheckBox(tr("Advanced")); connect(m_configView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &, const QItemSelection &) { updateSelection(); }); m_reconfigureButton = new QPushButton(tr("Run CMake")); m_reconfigureButton->setEnabled(false); using namespace Layouting; Grid cmakeConfiguration { m_filterEdit, br, findWrapper, Column { m_addButton, m_editButton, m_setButton, m_unsetButton, m_resetButton, m_batchEditButton, Space(10), m_showAdvancedCheckBox, st } }; Column { Form { buildDirAspect, bc->aspect(), qmlDebugAspect }, m_warningMessageLabel, m_kitConfiguration, Column { m_configurationStates, Group { Column { cmakeConfiguration, Row { bc->aspect(), bc->aspect() }, m_reconfigureButton, } } }.setSpacing(0) }.attachTo(details, WithoutMargins); updateAdvancedCheckBox(); setError(m_buildSystem->error()); setWarning(m_buildSystem->warning()); connect(m_buildSystem, &BuildSystem::parsingStarted, this, [this] { updateButtonState(); m_configView->setEnabled(false); m_showProgressTimer.start(); }); m_configModel->setMacroExpander(bc->macroExpander()); if (m_buildSystem->isParsing()) m_showProgressTimer.start(); else { m_configModel->setConfiguration(m_buildSystem->configurationFromCMake()); m_configModel->setInitialParametersConfiguration( m_buildSystem->initialCMakeConfiguration()); } connect(m_buildSystem, &BuildSystem::parsingFinished, this, [this] { const CMakeConfig config = m_buildSystem->configurationFromCMake(); auto qmlDebugAspect = m_buildSystem->buildConfiguration() ->aspect(); const TriState qmlDebugSetting = qmlDebugAspect->value(); bool qmlDebugConfig = CMakeBuildConfiguration::hasQmlDebugging(config); if ((qmlDebugSetting == TriState::Enabled && !qmlDebugConfig) || (qmlDebugSetting == TriState::Disabled && qmlDebugConfig)) { qmlDebugAspect->setValue(TriState::Default); } m_configModel->setConfiguration(config); m_configModel->setInitialParametersConfiguration( m_buildSystem->initialCMakeConfiguration()); m_buildSystem->filterConfigArgumentsFromAdditionalCMakeArguments(); updateFromKit(); m_configView->setEnabled(true); updateButtonState(); m_showProgressTimer.stop(); m_progressIndicator->hide(); updateConfigurationStateSelection(); }); connect(m_buildSystem, &CMakeBuildSystem::configurationCleared, this, [this] { updateConfigurationStateSelection(); }); connect(m_buildSystem, &CMakeBuildSystem::errorOccurred, this, [this] { m_showProgressTimer.stop(); m_progressIndicator->hide(); updateConfigurationStateSelection(); }); connect(m_configModel, &QAbstractItemModel::dataChanged, this, &CMakeBuildSettingsWidget::updateButtonState); connect(m_configModel, &QAbstractItemModel::modelReset, this, &CMakeBuildSettingsWidget::updateButtonState); connect(m_buildSystem->cmakeBuildConfiguration(), &CMakeBuildConfiguration::signingFlagsChanged, this, &CMakeBuildSettingsWidget::updateButtonState); connect(m_showAdvancedCheckBox, &QCheckBox::stateChanged, this, &CMakeBuildSettingsWidget::updateAdvancedCheckBox); connect(m_filterEdit, &QLineEdit::textChanged, m_configTextFilterModel, [this](const QString &txt) { m_configTextFilterModel->setFilterRegularExpression( QRegularExpression(QRegularExpression::escape(txt), QRegularExpression::CaseInsensitiveOption)); }); connect(m_resetButton, &QPushButton::clicked, this, [this](){ m_configModel->resetAllChanges(isInitialConfiguration()); }); connect(m_reconfigureButton, &QPushButton::clicked, this, [this] { if (!m_buildSystem->isParsing()) { if (isInitialConfiguration()) { reconfigureWithInitialParameters(); } else { m_buildSystem->runCMakeWithExtraArguments(); } } else { m_buildSystem->stopCMakeRun(); m_reconfigureButton->setEnabled(false); } }); connect(m_setButton, &QPushButton::clicked, this, [this]() { setVariableUnsetFlag(false); }); connect(m_unsetButton, &QPushButton::clicked, this, [this]() { setVariableUnsetFlag(true); }); connect(m_editButton, &QPushButton::clicked, this, [this]() { QModelIndex idx = m_configView->currentIndex(); if (idx.column() != 1) idx = idx.sibling(idx.row(), 1); m_configView->setCurrentIndex(idx); m_configView->edit(idx); }); connect(addButtonMenu, &QMenu::triggered, this, [this](QAction *action) { ConfigModel::DataItem::Type type = static_cast(action->data().value()); QString value = tr(""); if (type == ConfigModel::DataItem::BOOLEAN) value = QString::fromLatin1("OFF"); m_configModel->appendConfiguration(tr(""), value, type, isInitialConfiguration()); const TreeItem *item = m_configModel->findNonRootItem([&value, type](TreeItem *item) { ConfigModel::DataItem dataItem = ConfigModel::dataItemFromIndex(item->index()); return dataItem.key == tr("") && dataItem.type == type && dataItem.value == value; }); QModelIndex idx = m_configModel->indexForItem(item); idx = m_configTextFilterModel->mapFromSource(m_configFilterModel->mapFromSource(idx)); m_configView->setFocus(); m_configView->scrollTo(idx); m_configView->setCurrentIndex(idx); m_configView->edit(idx); }); connect(m_batchEditButton, &QAbstractButton::clicked, this, &CMakeBuildSettingsWidget::batchEditConfiguration); connect(m_buildSystem, &CMakeBuildSystem::errorOccurred, this, &CMakeBuildSettingsWidget::setError); connect(m_buildSystem, &CMakeBuildSystem::warningOccurred, this, &CMakeBuildSettingsWidget::setWarning); connect(m_buildSystem, &CMakeBuildSystem::configurationChanged, m_configModel, &ConfigModel::setBatchEditConfiguration); updateFromKit(); connect(m_buildSystem->target(), &Target::kitChanged, this, &CMakeBuildSettingsWidget::updateFromKit); connect(bc, &CMakeBuildConfiguration::enabledChanged, this, [this, bc] { if (bc->isEnabled()) setError(QString()); }); connect(this, &QObject::destroyed, this, [this] { updateInitialCMakeArguments(); }); connect(bc->aspect(), &Utils::BaseAspect::labelLinkActivated, this, [this](const QString &) { const CMakeTool *tool = CMakeKitAspect::cmakeTool(m_buildSystem->kit()); CMakeTool::openCMakeHelpUrl(tool, "%1/manual/cmake.1.html#options"); }); connect(bc->aspect(), &Utils::BaseAspect::labelLinkActivated, this, [this](const QString &) { const CMakeTool *tool = CMakeKitAspect::cmakeTool(m_buildSystem->kit()); CMakeTool::openCMakeHelpUrl(tool, "%1/manual/cmake.1.html#options"); }); updateSelection(); updateConfigurationStateSelection(); } void CMakeBuildSettingsWidget::batchEditConfiguration() { auto dialog = new QDialog(this); dialog->setWindowTitle(tr("Edit CMake Configuration")); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setModal(true); auto layout = new QVBoxLayout(dialog); auto editor = new QPlainTextEdit(dialog); auto label = new QLabel(dialog); label->setText(tr("Enter one CMake variable per line.
" "To set or change a variable, use -D<variable>:<type>=<value>.
" "<type> can have one of the following values: FILEPATH, PATH, BOOL, INTERNAL, or STRING.
" "To unset a variable, use -U<variable>.
")); connect(label, &QLabel::linkActivated, this, [this](const QString &) { const CMakeTool *tool = CMakeKitAspect::cmakeTool(m_buildSystem->target()->kit()); CMakeTool::openCMakeHelpUrl(tool, "%1/manual/cmake-variables.7.html"); }); editor->setMinimumSize(800, 200); auto chooser = new Utils::VariableChooser(dialog); chooser->addSupportedWidget(editor); chooser->addMacroExpanderProvider([this] { return m_buildSystem->buildConfiguration()->macroExpander(); }); auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); layout->addWidget(editor); layout->addWidget(label); layout->addWidget(buttons); connect(buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject); connect(dialog, &QDialog::accepted, this, [=]{ const auto expander = m_buildSystem->buildConfiguration()->macroExpander(); const QStringList lines = editor->toPlainText().split('\n', Qt::SkipEmptyParts); const QStringList expandedLines = Utils::transform(lines, [expander](const QString &s) { return expander->expand(s); }); const bool isInitial = isInitialConfiguration(); QStringList unknownOptions; CMakeConfig config = CMakeConfig::fromArguments(isInitial ? lines : expandedLines, unknownOptions); for (auto &ci : config) ci.isInitial = isInitial; m_configModel->setBatchEditConfiguration(config); }); editor->setPlainText( m_buildSystem->configurationChangesArguments(isInitialConfiguration()) .join('\n')); dialog->show(); } void CMakeBuildSettingsWidget::reconfigureWithInitialParameters() { CMakeSpecificSettings *settings = CMakeProjectPlugin::projectTypeSpecificSettings(); bool doNotAsk = !settings->askBeforeReConfigureInitialParams.value(); if (!doNotAsk) { QDialogButtonBox::StandardButton reply = Utils::CheckableMessageBox::question( Core::ICore::dialogParent(), tr("Re-configure with Initial Parameters"), tr("Clear CMake configuration and configure with initial parameters?"), tr("Do not ask again"), &doNotAsk, QDialogButtonBox::Yes | QDialogButtonBox::No, QDialogButtonBox::Yes); settings->askBeforeReConfigureInitialParams.setValue(!doNotAsk); settings->writeSettings(Core::ICore::settings()); if (reply != QDialogButtonBox::Yes) { return; } } m_buildSystem->clearCMakeCache(); updateInitialCMakeArguments(); if (ProjectExplorerPlugin::saveModifiedFiles()) m_buildSystem->runCMake(); } void CMakeBuildSettingsWidget::updateInitialCMakeArguments() { CMakeConfig initialList = m_buildSystem->initialCMakeConfiguration(); for (const CMakeConfigItem &ci : m_buildSystem->configurationChanges()) { if (!ci.isInitial) continue; auto it = std::find_if(initialList.begin(), initialList.end(), [ci](const CMakeConfigItem &item) { return item.key == ci.key; }); if (it != initialList.end()) { *it = ci; if (ci.isUnset) initialList.erase(it); } else if (!ci.key.isEmpty()) { initialList.push_back(ci); } } auto bc = m_buildSystem->buildConfiguration(); bc->aspect()->setCMakeConfiguration(initialList); // value() will contain only the unknown arguments (the non -D/-U arguments) // As the user would expect to have e.g. "--preset" from "Initial Configuration" // to "Current Configuration" as additional parameters m_buildSystem->setAdditionalCMakeArguments(ProcessArgs::splitArgs( bc->aspect()->value())); } void CMakeBuildSettingsWidget::kitCMakeConfiguration() { m_buildSystem->kit()->blockNotification(); auto dialog = new QDialog(this); dialog->setWindowTitle(tr("Kit CMake Configuration")); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setModal(true); dialog->setSizeGripEnabled(true); connect(dialog, &QDialog::finished, this, [this] { m_buildSystem->kit()->unblockNotification(); }); CMakeKitAspect kitAspect; CMakeGeneratorKitAspect generatorAspect; CMakeConfigurationKitAspect configurationKitAspect; auto layout = new QGridLayout(dialog); kitAspect.createConfigWidget(m_buildSystem->kit()) ->addToLayoutWithLabel(layout->parentWidget()); generatorAspect.createConfigWidget(m_buildSystem->kit()) ->addToLayoutWithLabel(layout->parentWidget()); configurationKitAspect.createConfigWidget(m_buildSystem->kit()) ->addToLayoutWithLabel(layout->parentWidget()); layout->setColumnStretch(1, 1); auto buttons = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttons, &QDialogButtonBox::clicked, dialog, &QDialog::close); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Maximum, QSizePolicy::MinimumExpanding), 4, 0); layout->addWidget(buttons, 5, 0, 1, -1); dialog->setMinimumWidth(400); dialog->resize(800, 1); dialog->show(); } void CMakeBuildSettingsWidget::updateConfigureDetailsWidgetsSummary( const QStringList &configurationArguments) { ProjectExplorer::ProcessParameters params; CommandLine cmd; const CMakeTool *tool = CMakeKitAspect::cmakeTool(m_buildSystem->kit()); cmd.setExecutable(tool ? tool->cmakeExecutable() : "cmake"); const BuildConfiguration *bc = m_buildSystem->buildConfiguration(); const FilePath buildDirectory = bc ? bc->buildDirectory() : "."; cmd.addArgs({"-S", m_buildSystem->projectDirectory().path()}); cmd.addArgs({"-B", buildDirectory.onDevice(cmd.executable()).path()}); cmd.addArgs(configurationArguments); params.setCommandLine(cmd); m_configureDetailsWidget->setSummaryText(params.summary(tr("Configure"))); m_configureDetailsWidget->setState(DetailsWidget::Expanded); } void CMakeBuildSettingsWidget::setError(const QString &message) { m_buildSystem->buildConfiguration()->buildDirectoryAspect()->setProblem(message); } void CMakeBuildSettingsWidget::setWarning(const QString &message) { bool showWarning = !message.isEmpty(); m_warningMessageLabel->setVisible(showWarning); m_warningMessageLabel->setText(message); } void CMakeBuildSettingsWidget::updateButtonState() { const bool isParsing = m_buildSystem->isParsing(); // Update extra data in buildconfiguration const QList changes = m_configModel->configurationForCMake(); const CMakeConfig configChanges = getQmlDebugCxxFlags() + getSigningFlagsChanges() + Utils::transform(changes, [](const ConfigModel::DataItem &i) { CMakeConfigItem ni; ni.key = i.key.toUtf8(); ni.value = i.value.toUtf8(); ni.documentation = i.description.toUtf8(); ni.isAdvanced = i.isAdvanced; ni.isInitial = i.isInitial; ni.isUnset = i.isUnset; ni.inCMakeCache = i.inCMakeCache; ni.values = i.values; switch (i.type) { case ConfigModel::DataItem::BOOLEAN: ni.type = CMakeConfigItem::BOOL; break; case ConfigModel::DataItem::FILE: ni.type = CMakeConfigItem::FILEPATH; break; case ConfigModel::DataItem::DIRECTORY: ni.type = CMakeConfigItem::PATH; break; case ConfigModel::DataItem::STRING: ni.type = CMakeConfigItem::STRING; break; case ConfigModel::DataItem::UNKNOWN: default: ni.type = CMakeConfigItem::UNINITIALIZED; break; } return ni; }); const bool isInitial = isInitialConfiguration(); m_resetButton->setEnabled(m_configModel->hasChanges(isInitial) && !isParsing); BuildConfiguration *bc = m_buildSystem->buildConfiguration(); bc->aspect()->setVisible(isInitialConfiguration()); bc->aspect()->setVisible(!isInitialConfiguration()); bc->aspect()->setEnabled(!isParsing); bc->aspect()->setEnabled(!isParsing); // Update label and text boldness of the reconfigure button QFont reconfigureButtonFont = m_reconfigureButton->font(); if (isParsing) { m_reconfigureButton->setText(tr("Stop CMake")); reconfigureButtonFont.setBold(false); } else { m_reconfigureButton->setEnabled(true); if (isInitial) { m_reconfigureButton->setText(tr("Re-configure with Initial Parameters")); } else { m_reconfigureButton->setText(tr("Run CMake")); } reconfigureButtonFont.setBold(isInitial ? m_configModel->hasChanges(isInitial) : !configChanges.isEmpty()); } m_reconfigureButton->setFont(reconfigureButtonFont); m_buildSystem->setConfigurationChanges(configChanges); // Update the tooltip with the changes const QStringList configurationArguments = m_buildSystem->configurationChangesArguments( isInitialConfiguration()); m_reconfigureButton->setToolTip(configurationArguments.join('\n')); updateConfigureDetailsWidgetsSummary(configurationArguments); } void CMakeBuildSettingsWidget::updateAdvancedCheckBox() { if (m_showAdvancedCheckBox->isChecked()) { m_configFilterModel->setFilterRole(ConfigModel::ItemIsAdvancedRole); m_configFilterModel->setFilterRegularExpression("[01]"); } else { m_configFilterModel->setFilterRole(ConfigModel::ItemIsAdvancedRole); m_configFilterModel->setFilterFixedString("0"); } updateButtonState(); } void CMakeBuildSettingsWidget::updateFromKit() { const Kit *k = m_buildSystem->kit(); CMakeConfig config = CMakeConfigurationKitAspect::configuration(k); config.append(CMakeGeneratorKitAspect::generatorCMakeConfig(k)); // First the key value parameters ConfigModel::KitConfiguration configHash; for (const CMakeConfigItem &i : config) configHash.insert(QString::fromUtf8(i.key), i); m_configModel->setConfigurationFromKit(configHash); // Then the additional parameters const QStringList additionalKitCMake = ProcessArgs::splitArgs( CMakeConfigurationKitAspect::additionalConfiguration(k)); const QStringList additionalInitialCMake = ProcessArgs::splitArgs( m_buildSystem->buildConfiguration()->aspect()->value()); QStringList mergedArgumentList; std::set_union(additionalInitialCMake.begin(), additionalInitialCMake.end(), additionalKitCMake.begin(), additionalKitCMake.end(), std::back_inserter(mergedArgumentList)); m_buildSystem->buildConfiguration()->aspect()->setValue( ProcessArgs::joinArgs(mergedArgumentList)); } void CMakeBuildSettingsWidget::updateConfigurationStateIndex(int index) { if (index == 0) { m_configFilterModel->setFilterRole(ConfigModel::ItemIsInitialRole); m_configFilterModel->setFilterFixedString("1"); } else { updateAdvancedCheckBox(); } m_showAdvancedCheckBox->setEnabled(index != 0); updateButtonState(); } CMakeConfig CMakeBuildSettingsWidget::getQmlDebugCxxFlags() { const auto aspect = m_buildSystem->buildConfiguration()->aspect(); const TriState qmlDebuggingState = aspect->value(); if (qmlDebuggingState == TriState::Default) // don't touch anything return {}; const bool enable = aspect->value() == TriState::Enabled; const CMakeConfig configList = m_buildSystem->configurationFromCMake(); const QByteArrayList cxxFlagsPrev{"CMAKE_CXX_FLAGS", "CMAKE_CXX_FLAGS_DEBUG", "CMAKE_CXX_FLAGS_RELWITHDEBINFO", "CMAKE_CXX_FLAGS_INIT"}; const QByteArrayList cxxFlags{"CMAKE_CXX_FLAGS_INIT", "CMAKE_CXX_FLAGS"}; const QByteArray qmlDebug("-DQT_QML_DEBUG"); CMakeConfig changedConfig; if (enable) { const FilePath cmakeCache = m_buildSystem->cmakeBuildConfiguration()->buildDirectory().pathAppended("CMakeCache.txt"); // Only modify the CMAKE_CXX_FLAGS variable if the project was previously configured // otherwise CMAKE_CXX_FLAGS_INIT will take care of setting the qmlDebug define if (cmakeCache.exists()) { for (const CMakeConfigItem &item : configList) { if (!cxxFlags.contains(item.key)) continue; CMakeConfigItem it(item); if (!it.value.contains(qmlDebug)) { it.value = it.value.append(' ').append(qmlDebug).trimmed(); changedConfig.append(it); } } } } else { // Remove -DQT_QML_DEBUG from all configurations, potentially set by previous Qt Creator versions for (const CMakeConfigItem &item : configList) { if (!cxxFlagsPrev.contains(item.key)) continue; CMakeConfigItem it(item); int index = it.value.indexOf(qmlDebug); if (index != -1) { it.value.remove(index, qmlDebug.length()); it.value = it.value.trimmed(); changedConfig.append(it); } } } return changedConfig; } CMakeConfig CMakeBuildSettingsWidget::getSigningFlagsChanges() { const CMakeConfig flags = m_buildSystem->cmakeBuildConfiguration()->signingFlags(); if (flags.isEmpty()) return {}; const CMakeConfig configList = m_buildSystem->configurationFromCMake(); if (configList.isEmpty()) { // we don't have any configuration --> initial configuration takes care of this itself return {}; } CMakeConfig changedConfig; for (const CMakeConfigItem &signingFlag : flags) { const CMakeConfigItem existingFlag = Utils::findOrDefault(configList, Utils::equal(&CMakeConfigItem::key, signingFlag.key)); const bool notInConfig = existingFlag.key.isEmpty(); if (notInConfig != signingFlag.isUnset || existingFlag.value != signingFlag.value) changedConfig.append(signingFlag); } return changedConfig; } void CMakeBuildSettingsWidget::updateSelection() { const QModelIndexList selectedIndexes = m_configView->selectionModel()->selectedIndexes(); unsigned int setableCount = 0; unsigned int unsetableCount = 0; unsigned int editableCount = 0; for (const QModelIndex &index : selectedIndexes) { if (index.isValid() && index.flags().testFlag(Qt::ItemIsSelectable)) { const ConfigModel::DataItem di = ConfigModel::dataItemFromIndex(index); if (di.isUnset) setableCount++; else unsetableCount++; } if (index.isValid() && index.flags().testFlag(Qt::ItemIsEditable)) editableCount++; } m_setButton->setEnabled(setableCount > 0); m_unsetButton->setEnabled(unsetableCount > 0); m_editButton->setEnabled(editableCount == 1); } void CMakeBuildSettingsWidget::updateConfigurationStateSelection() { const bool hasReplyFile = FileApiParser::scanForCMakeReplyFile( m_buildSystem->buildConfiguration()->buildDirectory()).exists(); const int switchToIndex = hasReplyFile ? 1 : 0; if (m_configurationStates->currentIndex() != switchToIndex) m_configurationStates->setCurrentIndex(switchToIndex); else emit m_configurationStates->currentChanged(switchToIndex); } bool CMakeBuildSettingsWidget::isInitialConfiguration() const { return m_configurationStates->currentIndex() == 0; } void CMakeBuildSettingsWidget::setVariableUnsetFlag(bool unsetFlag) { const QModelIndexList selectedIndexes = m_configView->selectionModel()->selectedIndexes(); bool unsetFlagToggled = false; for (const QModelIndex &index : selectedIndexes) { if (index.isValid()) { const ConfigModel::DataItem di = ConfigModel::dataItemFromIndex(index); if (di.isUnset != unsetFlag) { m_configModel->toggleUnsetFlag(mapToSource(m_configView, index)); unsetFlagToggled = true; } } } if (unsetFlagToggled) updateSelection(); } QAction *CMakeBuildSettingsWidget::createForceAction(int type, const QModelIndex &idx) { auto t = static_cast(type); QString typeString; switch (type) { case ConfigModel::DataItem::BOOLEAN: typeString = tr("bool", "display string for cmake type BOOLEAN"); break; case ConfigModel::DataItem::FILE: typeString = tr("file", "display string for cmake type FILE"); break; case ConfigModel::DataItem::DIRECTORY: typeString = tr("directory", "display string for cmake type DIRECTORY"); break; case ConfigModel::DataItem::STRING: typeString = tr("string", "display string for cmake type STRING"); break; case ConfigModel::DataItem::UNKNOWN: return nullptr; } QAction *forceAction = new QAction(tr("Force to %1").arg(typeString), nullptr); forceAction->setEnabled(m_configModel->canForceTo(idx, t)); connect(forceAction, &QAction::triggered, this, [this, idx, t]() { m_configModel->forceTo(idx, t); }); return forceAction; } bool CMakeBuildSettingsWidget::eventFilter(QObject *target, QEvent *event) { // handle context menu events: if (target != m_configView->viewport() || event->type() != QEvent::ContextMenu) return false; auto e = static_cast(event); const QModelIndex idx = mapToSource(m_configView, m_configView->indexAt(e->pos())); if (!idx.isValid()) return false; auto menu = new QMenu(this); connect(menu, &QMenu::triggered, menu, &QMenu::deleteLater); auto help = new QAction(tr("Help"), this); menu->addAction(help); connect(help, &QAction::triggered, this, [=] { const CMakeConfigItem item = ConfigModel::dataItemFromIndex(idx).toCMakeConfigItem(); const CMakeTool *tool = CMakeKitAspect::cmakeTool(m_buildSystem->target()->kit()); const QString linkUrl = "%1/variable/" + QString::fromUtf8(item.key) + ".html"; CMakeTool::openCMakeHelpUrl(tool, linkUrl); }); menu->addSeparator(); QAction *action = nullptr; if ((action = createForceAction(ConfigModel::DataItem::BOOLEAN, idx))) menu->addAction(action); if ((action = createForceAction(ConfigModel::DataItem::FILE, idx))) menu->addAction(action); if ((action = createForceAction(ConfigModel::DataItem::DIRECTORY, idx))) menu->addAction(action); if ((action = createForceAction(ConfigModel::DataItem::STRING, idx))) menu->addAction(action); menu->addSeparator(); auto applyKitOrInitialValue = new QAction(isInitialConfiguration() ? tr("Apply Kit Value") : tr("Apply Initial Configuration Value"), this); menu->addAction(applyKitOrInitialValue); connect(applyKitOrInitialValue, &QAction::triggered, this, [this] { const QModelIndexList selectedIndexes = m_configView->selectionModel()->selectedIndexes(); const QModelIndexList validIndexes = Utils::filtered(selectedIndexes, [](const QModelIndex &index) { return index.isValid() && index.flags().testFlag(Qt::ItemIsSelectable); }); for (const QModelIndex &index : validIndexes) { if (isInitialConfiguration()) m_configModel->applyKitValue(mapToSource(m_configView, index)); else m_configModel->applyInitialValue(mapToSource(m_configView, index)); } }); menu->addSeparator(); auto copy = new QAction(tr("Copy"), this); menu->addAction(copy); connect(copy, &QAction::triggered, this, [this] { const QModelIndexList selectedIndexes = m_configView->selectionModel()->selectedIndexes(); const QModelIndexList validIndexes = Utils::filtered(selectedIndexes, [](const QModelIndex &index) { return index.isValid() && index.flags().testFlag(Qt::ItemIsSelectable); }); const QStringList variableList = Utils::transform(validIndexes, [this](const QModelIndex &index) { return ConfigModel::dataItemFromIndex(index).toCMakeConfigItem().toArgument( isInitialConfiguration() ? nullptr : m_buildSystem->buildConfiguration()->macroExpander()); }); setClipboardAndSelection(variableList.join('\n')); }); menu->move(e->globalPos()); menu->show(); return true; } static bool isWebAssembly(const Kit *k) { return DeviceTypeKitAspect::deviceTypeId(k) == WebAssembly::Constants::WEBASSEMBLY_DEVICE_TYPE; } static bool isQnx(const Kit *k) { return DeviceTypeKitAspect::deviceTypeId(k) == Qnx::Constants::QNX_QNX_OS_TYPE; } static bool isWindowsARM64(const Kit *k) { ToolChain *toolchain = ToolChainKitAspect::cxxToolChain(k); if (!toolchain) return false; const Abi targetAbi = toolchain->targetAbi(); return targetAbi.os() == Abi::WindowsOS && targetAbi.architecture() == Abi::ArmArchitecture && targetAbi.wordWidth() == 64; } static CommandLine defaultInitialCMakeCommand(const Kit *k, const QString buildType) { // Generator: CMakeTool *tool = CMakeKitAspect::cmakeTool(k); QTC_ASSERT(tool, return {}); CommandLine cmd{tool->cmakeExecutable()}; cmd.addArgs(CMakeGeneratorKitAspect::generatorArguments(k)); // CMAKE_BUILD_TYPE: if (!buildType.isEmpty() && !CMakeGeneratorKitAspect::isMultiConfigGenerator(k)) cmd.addArg("-DCMAKE_BUILD_TYPE:STRING=" + buildType); Internal::CMakeSpecificSettings *settings = Internal::CMakeProjectPlugin::projectTypeSpecificSettings(); // Package manager auto setup. The file auto-setup.cmake resides on the host, // so it's not accessible for remotely running cmakes. We need to exclude that case. if (!cmd.executable().needsDevice() && settings->packageManagerAutoSetup.value()) { cmd.addArg("-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=" "%{IDE:ResourcePath}/package-manager/auto-setup.cmake"); } // Cross-compilation settings: if (!CMakeBuildConfiguration::isIos(k)) { // iOS handles this differently const QString sysRoot = SysRootKitAspect::sysRoot(k).path(); if (!sysRoot.isEmpty()) { cmd.addArg("-DCMAKE_SYSROOT:PATH=" + sysRoot); if (ToolChain *tc = ToolChainKitAspect::cxxToolChain(k)) { const QString targetTriple = tc->originalTargetTriple(); cmd.addArg("-DCMAKE_C_COMPILER_TARGET:STRING=" + targetTriple); cmd.addArg("-DCMAKE_CXX_COMPILER_TARGET:STRING=" + targetTriple); } } } cmd.addArgs(CMakeConfigurationKitAspect::toArgumentsList(k)); cmd.addArgs(CMakeConfigurationKitAspect::additionalConfiguration(k), CommandLine::Raw); return cmd; } } // namespace Internal // ----------------------------------------------------------------------------- // CMakeBuildConfiguration: // ----------------------------------------------------------------------------- CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id) : BuildConfiguration(target, id) { m_buildSystem = new CMakeBuildSystem(this); const auto buildDirAspect = aspect(); buildDirAspect->setValueAcceptor( [](const QString &oldDir, const QString &newDir) -> Utils::optional { if (oldDir.isEmpty()) return newDir; if (QDir(oldDir).exists("CMakeCache.txt") && !QDir(newDir).exists("CMakeCache.txt")) { if (QMessageBox::information( Core::ICore::dialogParent(), tr("Changing Build Directory"), tr("Change the build directory to \"%1\" and start with a " "basic CMake configuration?") .arg(newDir), QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok) { return newDir; } return Utils::nullopt; } return newDir; }); auto initialCMakeArgumentsAspect = addAspect(); initialCMakeArgumentsAspect->setMacroExpanderProvider([this] { return macroExpander(); }); auto additionalCMakeArgumentsAspect = addAspect(); additionalCMakeArgumentsAspect->setMacroExpanderProvider([this] { return macroExpander(); }); macroExpander()->registerVariable(DEVELOPMENT_TEAM_FLAG, tr("The CMake flag for the development team"), [this] { const CMakeConfig flags = signingFlags(); if (!flags.isEmpty()) return flags.first().toArgument(); return QString(); }); macroExpander()->registerVariable(PROVISIONING_PROFILE_FLAG, tr("The CMake flag for the provisioning profile"), [this] { const CMakeConfig flags = signingFlags(); if (flags.size() > 1 && !flags.at(1).isUnset) { return flags.at(1).toArgument(); } return QString(); }); macroExpander()->registerVariable(CMAKE_OSX_ARCHITECTURES_FLAG, tr("The CMake flag for the architecture on macOS"), [target] { if (HostOsInfo::isRunningUnderRosetta()) { if (auto *qt = QtSupport::QtKitAspect::qtVersion(target->kit())) { const Abis abis = qt->qtAbis(); for (const Abi &abi : abis) { if (abi.architecture() == Abi::ArmArchitecture) return QLatin1String("-DCMAKE_OSX_ARCHITECTURES=arm64"); } } } return QLatin1String(); }); macroExpander()->registerVariable(QT_QML_DEBUG_FLAG, tr("The CMake flag for QML debugging, if enabled"), [this] { if (aspect()->value() == TriState::Enabled) { return QLatin1String( "-DQT_QML_DEBUG"); } return QLatin1String(); }); addAspect(); addAspect(); addAspect(this); appendInitialBuildStep(Constants::CMAKE_BUILD_STEP_ID); appendInitialCleanStep(Constants::CMAKE_BUILD_STEP_ID); setInitializer([this, target](const BuildInfo &info) { const Kit *k = target->kit(); const QtSupport::QtVersion *qt = QtSupport::QtKitAspect::qtVersion(k); const QVariantMap extraInfoMap = info.extraInfo.value(); const QString buildType = extraInfoMap.contains(CMAKE_BUILD_TYPE) ? extraInfoMap.value(CMAKE_BUILD_TYPE).toString() : info.typeName; const TriState qmlDebugging = extraInfoMap.contains(Constants::QML_DEBUG_SETTING) ? TriState::fromVariant( extraInfoMap.value(Constants::QML_DEBUG_SETTING)) : TriState::Default; CommandLine cmd = defaultInitialCMakeCommand(k, buildType); m_buildSystem->setIsMultiConfig(CMakeGeneratorKitAspect::isMultiConfigGenerator(k)); // Android magic: if (DeviceTypeKitAspect::deviceTypeId(k) == Android::Constants::ANDROID_DEVICE_TYPE) { buildSteps()->appendStep(Android::Constants::ANDROID_BUILD_APK_ID); const auto &bs = buildSteps()->steps().constLast(); cmd.addArg("-DANDROID_NATIVE_API_LEVEL:STRING=" + bs->data(Android::Constants::AndroidNdkPlatform).toString()); auto ndkLocation = bs->data(Android::Constants::NdkLocation).value(); cmd.addArg("-DANDROID_NDK:PATH=" + ndkLocation.path()); cmd.addArg("-DCMAKE_TOOLCHAIN_FILE:FILEPATH=" + ndkLocation.pathAppended("build/cmake/android.toolchain.cmake").path()); auto androidAbis = bs->data(Android::Constants::AndroidMkSpecAbis).toStringList(); QString preferredAbi; if (androidAbis.contains(ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A)) { preferredAbi = ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A; } else if (androidAbis.isEmpty() || androidAbis.contains(ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A)) { preferredAbi = ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A; } else { preferredAbi = androidAbis.first(); } cmd.addArg("-DANDROID_ABI:STRING=" + preferredAbi); cmd.addArg("-DANDROID_STL:STRING=c++_shared"); cmd.addArg("-DCMAKE_FIND_ROOT_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}"); auto sdkLocation = bs->data(Android::Constants::SdkLocation).value(); if (qt && qt->qtVersion() >= QtSupport::QtVersionNumber{6, 0, 0}) { // Don't build apk under ALL target because Qt Creator will handle it if (qt->qtVersion() >= QtSupport::QtVersionNumber{6, 1, 0}) cmd.addArg("-DQT_NO_GLOBAL_APK_TARGET_PART_OF_ALL:BOOL=ON"); cmd.addArg("-DQT_HOST_PATH:PATH=%{Qt:QT_HOST_PREFIX}"); cmd.addArg("-DANDROID_SDK_ROOT:PATH=" + sdkLocation.path()); } else { cmd.addArg("-DANDROID_SDK:PATH=" + sdkLocation.path()); } } const IDevice::ConstPtr device = DeviceKitAspect::device(k); if (CMakeBuildConfiguration::isIos(k)) { if (qt && qt->qtVersion().majorVersion >= 6) { // TODO it would be better if we could set // CMAKE_SYSTEM_NAME=iOS and CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=YES // and build with "cmake --build . -- -arch " instead of setting the architecture // and sysroot in the CMake configuration, but that currently doesn't work with Qt/CMake // https://gitlab.kitware.com/cmake/cmake/-/issues/21276 const Id deviceType = DeviceTypeKitAspect::deviceTypeId(k); // TODO the architectures are probably not correct with Apple Silicon in the mix... const QString architecture = deviceType == Ios::Constants::IOS_DEVICE_TYPE ? QLatin1String("arm64") : QLatin1String("x86_64"); const QString sysroot = deviceType == Ios::Constants::IOS_DEVICE_TYPE ? QLatin1String("iphoneos") : QLatin1String("iphonesimulator"); cmd.addArg(CMAKE_QT6_TOOLCHAIN_FILE_ARG); cmd.addArg("-DCMAKE_OSX_ARCHITECTURES:STRING=" + architecture); cmd.addArg("-DCMAKE_OSX_SYSROOT:STRING=" + sysroot); cmd.addArg("%{" + QLatin1String(DEVELOPMENT_TEAM_FLAG) + "}"); cmd.addArg("%{" + QLatin1String(PROVISIONING_PROFILE_FLAG) + "}"); } } else if (device && device->osType() == Utils::OsTypeMac) { cmd.addArg("%{" + QLatin1String(CMAKE_OSX_ARCHITECTURES_FLAG) + "}"); } if (isWebAssembly(k) || isQnx(k) || isWindowsARM64(k)) { if (qt && qt->qtVersion().majorVersion >= 6) cmd.addArg(CMAKE_QT6_TOOLCHAIN_FILE_ARG); } if (info.buildDirectory.isEmpty()) { setBuildDirectory(shadowBuildDirectory(target->project()->projectFilePath(), k, info.displayName, info.buildType)); } if (extraInfoMap.contains(Constants::CMAKE_HOME_DIR)) setSourceDirectory(FilePath::fromVariant(extraInfoMap.value(Constants::CMAKE_HOME_DIR))); aspect()->setValue(qmlDebugging); if (qt && qt->isQmlDebuggingSupported()) cmd.addArg("-DCMAKE_CXX_FLAGS_INIT:STRING=%{" + QLatin1String(QT_QML_DEBUG_FLAG) + "}"); m_buildSystem->setInitialCMakeArguments(cmd.splitArguments()); m_buildSystem->setCMakeBuildType(buildType); }); } CMakeBuildConfiguration::~CMakeBuildConfiguration() { delete m_buildSystem; } QVariantMap CMakeBuildConfiguration::toMap() const { QVariantMap map(BuildConfiguration::toMap()); return map; } bool CMakeBuildConfiguration::fromMap(const QVariantMap &map) { if (!BuildConfiguration::fromMap(map)) return false; const CMakeConfig conf = Utils::filtered(Utils::transform(map.value(QLatin1String(CONFIGURATION_KEY)).toStringList(), [](const QString &v) { return CMakeConfigItem::fromString(v); }), [](const CMakeConfigItem &c) { return !c.isNull(); }); // TODO: Upgrade from Qt Creator < 4.13: Remove when no longer supported! const QString buildTypeName = [this]() { switch (buildType()) { case Debug: return QString("Debug"); case Profile: return QString("RelWithDebInfo"); case Release: return QString("Release"); case Unknown: default: return QString(""); } }(); if (m_buildSystem->initialCMakeArguments().isEmpty()) { CommandLine cmd = defaultInitialCMakeCommand(kit(), buildTypeName); for (const CMakeConfigItem &item : conf) cmd.addArg(item.toArgument(macroExpander())); m_buildSystem->setInitialCMakeArguments(cmd.splitArguments()); } return true; } FilePath CMakeBuildConfiguration::shadowBuildDirectory(const FilePath &projectFilePath, const Kit *k, const QString &bcName, BuildConfiguration::BuildType buildType) { if (projectFilePath.isEmpty()) return FilePath(); const QString projectName = projectFilePath.parentDir().fileName(); const FilePath projectDir = Project::projectDirectory(projectFilePath); FilePath buildPath = buildDirectoryFromTemplate(projectDir, projectFilePath, projectName, k, bcName, buildType, "cmake"); if (CMakeGeneratorKitAspect::isMultiConfigGenerator(k)) { QString path = buildPath.path(); path = path.left(path.lastIndexOf(QString("-%1").arg(bcName))); buildPath.setPath(path); } return buildPath; } bool CMakeBuildConfiguration::isIos(const Kit *k) { const Id deviceType = DeviceTypeKitAspect::deviceTypeId(k); return deviceType == Ios::Constants::IOS_DEVICE_TYPE || deviceType == Ios::Constants::IOS_SIMULATOR_TYPE; } bool CMakeBuildConfiguration::hasQmlDebugging(const CMakeConfig &config) { // Determine QML debugging flags. This must match what we do in // CMakeBuildSettingsWidget::getQmlDebugCxxFlags() // such that in doubt we leave the QML Debugging setting at "Leave at default" const QString cxxFlagsInit = config.stringValueOf("CMAKE_CXX_FLAGS_INIT"); const QString cxxFlags = config.stringValueOf("CMAKE_CXX_FLAGS"); return cxxFlagsInit.contains("-DQT_QML_DEBUG") && cxxFlags.contains("-DQT_QML_DEBUG"); } void CMakeBuildConfiguration::buildTarget(const QString &buildTarget) { auto cmBs = qobject_cast(findOrDefault( buildSteps()->steps(), [](const BuildStep *bs) { return bs->id() == Constants::CMAKE_BUILD_STEP_ID; })); QStringList originalBuildTargets; if (cmBs) { originalBuildTargets = cmBs->buildTargets(); cmBs->setBuildTargets({buildTarget}); } BuildManager::buildList(buildSteps()); if (cmBs) cmBs->setBuildTargets(originalBuildTargets); } CMakeConfig CMakeBuildSystem::configurationFromCMake() const { return m_configurationFromCMake; } CMakeConfig CMakeBuildSystem::configurationChanges() const { return m_configurationChanges; } QStringList CMakeBuildSystem::configurationChangesArguments(bool initialParameters) const { const QList filteredInitials = Utils::filtered(m_configurationChanges, [initialParameters](const CMakeConfigItem &ci) { return initialParameters ? ci.isInitial : !ci.isInitial; }); return Utils::transform(filteredInitials, &CMakeConfigItem::toArgument); } QStringList CMakeBuildSystem::initialCMakeArguments() const { return buildConfiguration()->aspect()->allValues(); } CMakeConfig CMakeBuildSystem::initialCMakeConfiguration() const { return buildConfiguration()->aspect()->cmakeConfiguration(); } void CMakeBuildSystem::setConfigurationFromCMake(const CMakeConfig &config) { m_configurationFromCMake = config; } void CMakeBuildSystem::setConfigurationChanges(const CMakeConfig &config) { qCDebug(cmakeBuildConfigurationLog) << "Configuration changes before:" << configurationChangesArguments(); m_configurationChanges = config; qCDebug(cmakeBuildConfigurationLog) << "Configuration changes after:" << configurationChangesArguments(); } // FIXME: Run clean steps when a setting starting with "ANDROID_BUILD_ABI_" is changed. // FIXME: Warn when kit settings are overridden by a project. void CMakeBuildSystem::clearError(ForceEnabledChanged fec) { if (!m_error.isEmpty()) { m_error.clear(); fec = ForceEnabledChanged::True; } if (fec == ForceEnabledChanged::True) { qCDebug(cmakeBuildConfigurationLog) << "Emitting enabledChanged signal"; emit buildConfiguration()->enabledChanged(); } } void CMakeBuildSystem::setInitialCMakeArguments(const QStringList &args) { QStringList additionalArguments; buildConfiguration()->aspect()->setAllValues(args.join('\n'), additionalArguments); // Set the unknown additional arguments also for the "Current Configuration" setAdditionalCMakeArguments(additionalArguments); } QStringList CMakeBuildSystem::additionalCMakeArguments() const { return ProcessArgs::splitArgs(buildConfiguration()->aspect()->value()); } void CMakeBuildSystem::setAdditionalCMakeArguments(const QStringList &args) { const QStringList expandedAdditionalArguments = Utils::transform(args, [this](const QString &s) { return buildConfiguration()->macroExpander()->expand(s); }); const QStringList nonEmptyAdditionalArguments = Utils::filtered(expandedAdditionalArguments, [](const QString &s) { return !s.isEmpty(); }); buildConfiguration()->aspect()->setValue( ProcessArgs::joinArgs(nonEmptyAdditionalArguments)); } void CMakeBuildSystem::filterConfigArgumentsFromAdditionalCMakeArguments() { // On iOS the %{Ios:DevelopmentTeam:Flag} evalues to something like // -DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM:STRING=MAGICSTRING // which is already part of the CMake variables and should not be also // in the addtional CMake options const QStringList arguments = ProcessArgs::splitArgs( buildConfiguration()->aspect()->value()); QStringList unknownOptions; const CMakeConfig config = CMakeConfig::fromArguments(arguments, unknownOptions); buildConfiguration()->aspect()->setValue(ProcessArgs::joinArgs(unknownOptions)); } void CMakeBuildSystem::setError(const QString &message) { qCDebug(cmakeBuildConfigurationLog) << "Setting error to" << message; QTC_ASSERT(!message.isEmpty(), return ); const QString oldMessage = m_error; if (m_error != message) m_error = message; if (oldMessage.isEmpty() != !message.isEmpty()) { qCDebug(cmakeBuildConfigurationLog) << "Emitting enabledChanged signal"; emit buildConfiguration()->enabledChanged(); } TaskHub::addTask(BuildSystemTask(Task::TaskType::Error, message)); emit errorOccurred(m_error); } void CMakeBuildSystem::setWarning(const QString &message) { if (m_warning == message) return; m_warning = message; TaskHub::addTask(BuildSystemTask(Task::TaskType::Warning, message)); emit warningOccurred(m_warning); } QString CMakeBuildSystem::error() const { return m_error; } QString CMakeBuildSystem::warning() const { return m_warning; } NamedWidget *CMakeBuildConfiguration::createConfigWidget() { return new CMakeBuildSettingsWidget(m_buildSystem); } CMakeConfig CMakeBuildConfiguration::signingFlags() const { return {}; } /*! \class CMakeBuildConfigurationFactory */ CMakeBuildConfigurationFactory::CMakeBuildConfigurationFactory() { registerBuildConfiguration(Constants::CMAKE_BUILDCONFIGURATION_ID); setSupportedProjectType(CMakeProjectManager::Constants::CMAKE_PROJECT_ID); setSupportedProjectMimeTypeName(Constants::CMAKE_PROJECT_MIMETYPE); setBuildGenerator([](const Kit *k, const FilePath &projectPath, bool forSetup) { QList result; FilePath path = forSetup ? Project::projectDirectory(projectPath) : projectPath; for (int type = BuildTypeDebug; type != BuildTypeLast; ++type) { BuildInfo info = createBuildInfo(BuildType(type)); if (forSetup) { info.buildDirectory = CMakeBuildConfiguration::shadowBuildDirectory(projectPath, k, info.typeName, info.buildType); } result << info; } return result; }); } CMakeBuildConfigurationFactory::BuildType CMakeBuildConfigurationFactory::buildTypeFromByteArray( const QByteArray &in) { const QByteArray bt = in.toLower(); if (bt == "debug") return BuildTypeDebug; if (bt == "release") return BuildTypeRelease; if (bt == "relwithdebinfo") return BuildTypeRelWithDebInfo; if (bt == "minsizerel") return BuildTypeMinSizeRel; return BuildTypeNone; } BuildConfiguration::BuildType CMakeBuildConfigurationFactory::cmakeBuildTypeToBuildType( const CMakeBuildConfigurationFactory::BuildType &in) { return createBuildInfo(in).buildType; } BuildInfo CMakeBuildConfigurationFactory::createBuildInfo(BuildType buildType) { BuildInfo info; switch (buildType) { case BuildTypeNone: info.typeName = "Build"; info.displayName = BuildConfiguration::tr("Build"); info.buildType = BuildConfiguration::Unknown; break; case BuildTypeDebug: { info.typeName = "Debug"; info.displayName = BuildConfiguration::tr("Debug"); info.buildType = BuildConfiguration::Debug; QVariantMap extraInfo; // enable QML debugging by default extraInfo.insert(Constants::QML_DEBUG_SETTING, TriState::Enabled.toVariant()); info.extraInfo = extraInfo; break; } case BuildTypeRelease: info.typeName = "Release"; info.displayName = BuildConfiguration::tr("Release"); info.buildType = BuildConfiguration::Release; break; case BuildTypeMinSizeRel: info.typeName = "MinSizeRel"; info.displayName = CMakeBuildConfiguration::tr("Minimum Size Release"); info.buildType = BuildConfiguration::Release; break; case BuildTypeRelWithDebInfo: info.typeName = "RelWithDebInfo"; info.displayName = CMakeBuildConfiguration::tr("Release with Debug Information"); info.buildType = BuildConfiguration::Profile; break; case BuildTypeProfile: { info.typeName = "Profile"; info.displayName = CMakeBuildConfiguration::tr("Profile"); info.buildType = BuildConfiguration::Profile; QVariantMap extraInfo; // override CMake build type, which defaults to info.typeName extraInfo.insert(CMAKE_BUILD_TYPE, "RelWithDebInfo"); // enable QML debugging by default extraInfo.insert(Constants::QML_DEBUG_SETTING, TriState::Enabled.toVariant()); info.extraInfo = extraInfo; break; } default: QTC_CHECK(false); break; } return info; } BuildConfiguration::BuildType CMakeBuildConfiguration::buildType() const { return m_buildSystem->buildType(); } BuildConfiguration::BuildType CMakeBuildSystem::buildType() const { QByteArray cmakeBuildTypeName = m_configurationFromCMake.valueOf("CMAKE_BUILD_TYPE"); if (cmakeBuildTypeName.isEmpty()) { QByteArray cmakeCfgTypes = m_configurationFromCMake.valueOf("CMAKE_CONFIGURATION_TYPES"); if (!cmakeCfgTypes.isEmpty()) cmakeBuildTypeName = cmakeBuildType().toUtf8(); } // Cover all common CMake build types const CMakeBuildConfigurationFactory::BuildType cmakeBuildType = CMakeBuildConfigurationFactory::buildTypeFromByteArray(cmakeBuildTypeName); return CMakeBuildConfigurationFactory::cmakeBuildTypeToBuildType(cmakeBuildType); } BuildSystem *CMakeBuildConfiguration::buildSystem() const { return m_buildSystem; } void CMakeBuildConfiguration::setSourceDirectory(const FilePath &path) { aspect()->setFilePath(path); } FilePath CMakeBuildConfiguration::sourceDirectory() const { return aspect()->filePath(); } void CMakeBuildConfiguration::addToEnvironment(Utils::Environment &env) const { CMakeSpecificSettings *settings = CMakeProjectPlugin::projectTypeSpecificSettings(); if (!settings->ninjaPath.filePath().isEmpty()) { const Utils::FilePath ninja = settings->ninjaPath.filePath(); env.appendOrSetPath(ninja.isFile() ? ninja.parentDir() : ninja); } } QString CMakeBuildSystem::cmakeBuildType() const { auto setBuildTypeFromConfig = [this](const CMakeConfig &config) { auto it = std::find_if(config.begin(), config.end(), [](const CMakeConfigItem &item) { return item.key == "CMAKE_BUILD_TYPE" && !item.isInitial; }); if (it != config.end()) const_cast(this) ->setCMakeBuildType(QString::fromUtf8(it->value)); }; if (!isMultiConfig()) setBuildTypeFromConfig(configurationChanges()); QString cmakeBuildType = buildConfiguration()->aspect()->value(); const Utils::FilePath cmakeCacheTxt = buildConfiguration()->buildDirectory().pathAppended("CMakeCache.txt"); const bool hasCMakeCache = cmakeCacheTxt.exists(); CMakeConfig config; if (cmakeBuildType == "Unknown") { // The "Unknown" type is the case of loading of an existing project // that doesn't have the "CMake.Build.Type" aspect saved if (hasCMakeCache) { QString errorMessage; config = CMakeConfig::fromFile(cmakeCacheTxt, &errorMessage); } else { config = initialCMakeConfiguration(); } } else if (!hasCMakeCache) { config = initialCMakeConfiguration(); } if (!config.isEmpty() && !isMultiConfig()) setBuildTypeFromConfig(config); return cmakeBuildType; } void CMakeBuildSystem::setCMakeBuildType(const QString &cmakeBuildType, bool quiet) { auto aspect = buildConfiguration()->aspect(); if (quiet) { aspect->setValueQuietly(cmakeBuildType); aspect->update(); } else { aspect->setValue(cmakeBuildType); } } namespace Internal { // ---------------------------------------------------------------------- // - InitialCMakeParametersAspect: // ---------------------------------------------------------------------- const CMakeConfig &InitialCMakeArgumentsAspect::cmakeConfiguration() const { return m_cmakeConfiguration; } const QStringList InitialCMakeArgumentsAspect::allValues() const { QStringList initialCMakeArguments = Utils::transform(m_cmakeConfiguration.toList(), [](const CMakeConfigItem &ci) { return ci.toArgument(nullptr); }); initialCMakeArguments.append(ProcessArgs::splitArgs(value())); return initialCMakeArguments; } void InitialCMakeArgumentsAspect::setAllValues(const QString &values, QStringList &additionalOptions) { QStringList arguments = values.split('\n', Qt::SkipEmptyParts); QString cmakeGenerator; for (QString &arg: arguments) { if (arg.startsWith("-G")) { const QString strDash(" - "); const int idxDash = arg.indexOf(strDash); if (idxDash > 0) { // -GCodeBlocks - Ninja cmakeGenerator = "-DCMAKE_GENERATOR:STRING=" + arg.mid(idxDash + strDash.length()); arg = arg.left(idxDash); arg.replace("-G", "-DCMAKE_EXTRA_GENERATOR:STRING="); } else { // -GNinja arg.replace("-G", "-DCMAKE_GENERATOR:STRING="); } } if (arg.startsWith("-A")) arg.replace("-A", "-DCMAKE_GENERATOR_PLATFORM:STRING="); if (arg.startsWith("-T")) arg.replace("-T", "-DCMAKE_GENERATOR_TOOLSET:STRING="); } if (!cmakeGenerator.isEmpty()) arguments.append(cmakeGenerator); m_cmakeConfiguration = CMakeConfig::fromArguments(arguments, additionalOptions); for (CMakeConfigItem &ci : m_cmakeConfiguration) ci.isInitial = true; // Display the unknown arguments in "Additional CMake Options" const QString additionalOptionsValue = ProcessArgs::joinArgs(additionalOptions); BaseAspect::setValueQuietly(additionalOptionsValue); } void InitialCMakeArgumentsAspect::setCMakeConfiguration(const CMakeConfig &config) { m_cmakeConfiguration = config; for (CMakeConfigItem &ci : m_cmakeConfiguration) ci.isInitial = true; } void InitialCMakeArgumentsAspect::fromMap(const QVariantMap &map) { const QString value = map.value(settingsKey(), defaultValue()).toString(); QStringList additionalArguments; setAllValues(value, additionalArguments); } void InitialCMakeArgumentsAspect::toMap(QVariantMap &map) const { saveToMap(map, allValues().join('\n'), defaultValue(), settingsKey()); } InitialCMakeArgumentsAspect::InitialCMakeArgumentsAspect() { setSettingsKey("CMake.Initial.Parameters"); setLabelText(tr("Additional CMake options:")); setDisplayStyle(LineEditDisplay); } // ---------------------------------------------------------------------- // - AdditionalCMakeOptionsAspect: // ---------------------------------------------------------------------- AdditionalCMakeOptionsAspect::AdditionalCMakeOptionsAspect() { setSettingsKey("CMake.Additional.Options"); setLabelText(tr("Additional CMake options:")); setDisplayStyle(LineEditDisplay); } // ----------------------------------------------------------------------------- // SourceDirectoryAspect: // ----------------------------------------------------------------------------- SourceDirectoryAspect::SourceDirectoryAspect() { // Will not be displayed, only persisted setSettingsKey("CMake.Source.Directory"); } // ----------------------------------------------------------------------------- // BuildTypeAspect: // ----------------------------------------------------------------------------- BuildTypeAspect::BuildTypeAspect() { setSettingsKey(CMAKE_BUILD_TYPE); setLabelText(tr("Build type:")); setDisplayStyle(LineEditDisplay); setDefaultValue("Unknown"); } } // namespace Internal } // namespace CMakeProjectManager