// Copyright (C) 2016 Canonical Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cmakekitaspect.h" #include "cmakeconfigitem.h" #include "cmakeprojectconstants.h" #include "cmakeprojectmanagertr.h" #include "cmakespecificsettings.h" #include "cmaketool.h" #include "cmaketoolmanager.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 using namespace ProjectExplorer; using namespace Utils; namespace CMakeProjectManager { static bool isIos(const Kit *k) { const Id deviceType = DeviceTypeKitAspect::deviceTypeId(k); return deviceType == Ios::Constants::IOS_DEVICE_TYPE || deviceType == Ios::Constants::IOS_SIMULATOR_TYPE; } static Id defaultCMakeToolId() { CMakeTool *defaultTool = CMakeToolManager::defaultCMakeTool(); return defaultTool ? defaultTool->id() : Id(); } // Factories class CMakeKitAspectFactory : public KitAspectFactory { public: CMakeKitAspectFactory(); // KitAspect interface Tasks validate(const Kit *k) const final; void setup(Kit *k) final; void fix(Kit *k) final; ItemList toUserOutput(const Kit *k) const final; KitAspect *createKitAspect(Kit *k) const final; void addToMacroExpander(Kit *k, Utils::MacroExpander *expander) const final; QSet availableFeatures(const Kit *k) const final; }; class CMakeGeneratorKitAspectFactory : public KitAspectFactory { public: CMakeGeneratorKitAspectFactory(); Tasks validate(const Kit *k) const final; void setup(Kit *k) final; void fix(Kit *k) final; void upgrade(Kit *k) final; ItemList toUserOutput(const Kit *k) const final; KitAspect *createKitAspect(Kit *k) const final; void addToBuildEnvironment(const Kit *k, Utils::Environment &env) const final; private: QVariant defaultValue(const Kit *k) const; }; class CMakeConfigurationKitAspectFactory : public KitAspectFactory { public: CMakeConfigurationKitAspectFactory(); // KitAspect interface Tasks validate(const Kit *k) const final; void setup(Kit *k) final; void fix(Kit *k) final; ItemList toUserOutput(const Kit *k) const final; KitAspect *createKitAspect(Kit *k) const final; private: QVariant defaultValue(const Kit *k) const; }; // Implementations class CMakeKitAspectImpl final : public KitAspect { public: CMakeKitAspectImpl(Kit *kit, const KitAspectFactory *factory) : KitAspect(kit, factory), m_comboBox(createSubWidget()) { setManagingPage(Constants::Settings::TOOLS_ID); m_comboBox->setSizePolicy(QSizePolicy::Ignored, m_comboBox->sizePolicy().verticalPolicy()); m_comboBox->setEnabled(false); m_comboBox->setToolTip(factory->description()); refresh(); connect(m_comboBox, &QComboBox::currentIndexChanged, this, &CMakeKitAspectImpl::currentCMakeToolChanged); CMakeToolManager *cmakeMgr = CMakeToolManager::instance(); connect(cmakeMgr, &CMakeToolManager::cmakeAdded, this, &CMakeKitAspectImpl::refresh); connect(cmakeMgr, &CMakeToolManager::cmakeRemoved, this, &CMakeKitAspectImpl::refresh); connect(cmakeMgr, &CMakeToolManager::cmakeUpdated, this, &CMakeKitAspectImpl::refresh); } ~CMakeKitAspectImpl() override { delete m_comboBox; } private: // KitAspectWidget interface void makeReadOnly() override { m_comboBox->setEnabled(false); } void addToLayoutImpl(Layouting::Layout &builder) override { addMutableAction(m_comboBox); builder.addItem(m_comboBox); } void refresh() override { const GuardLocker locker(m_ignoreChanges); m_comboBox->clear(); IDeviceConstPtr device = BuildDeviceKitAspect::device(kit()); const FilePath rootPath = device->rootPath(); const auto list = CMakeToolManager::cmakeTools(); m_comboBox->setEnabled(!list.isEmpty()); if (list.isEmpty()) { m_comboBox->addItem(Tr::tr(""), Id().toSetting()); return; } const QList same = Utils::filtered(list, [rootPath](CMakeTool *item) { return item->cmakeExecutable().isSameDevice(rootPath); }); const QList other = Utils::filtered(list, [rootPath](CMakeTool *item) { return !item->cmakeExecutable().isSameDevice(rootPath); }); for (CMakeTool *item : same) m_comboBox->addItem(item->displayName(), item->id().toSetting()); if (!same.isEmpty() && !other.isEmpty()) m_comboBox->insertSeparator(m_comboBox->count()); for (CMakeTool *item : other) m_comboBox->addItem(item->displayName(), item->id().toSetting()); CMakeTool *tool = CMakeKitAspect::cmakeTool(m_kit); m_comboBox->setCurrentIndex(tool ? indexOf(tool->id()) : -1); } int indexOf(Id id) { for (int i = 0; i < m_comboBox->count(); ++i) { if (id == Id::fromSetting(m_comboBox->itemData(i))) return i; } return -1; } void currentCMakeToolChanged(int index) { if (m_ignoreChanges.isLocked()) return; const Id id = Id::fromSetting(m_comboBox->itemData(index)); CMakeKitAspect::setCMakeTool(m_kit, id); } Guard m_ignoreChanges; QComboBox *m_comboBox; }; CMakeKitAspectFactory::CMakeKitAspectFactory() { setId(Constants::TOOL_ID); setDisplayName(Tr::tr("CMake Tool")); setDescription(Tr::tr("The CMake Tool to use when building a project with CMake.
" "This setting is ignored when using other build systems.")); setPriority(20000); auto updateKits = [this] { if (KitManager::isLoaded()) { for (Kit *k : KitManager::kits()) fix(k); } }; //make sure the default value is set if a selected CMake is removed connect(CMakeToolManager::instance(), &CMakeToolManager::cmakeRemoved, this, updateKits); //make sure the default value is set if a new default CMake is set connect(CMakeToolManager::instance(), &CMakeToolManager::defaultCMakeChanged, this, updateKits); } Id CMakeKitAspect::id() { return Constants::TOOL_ID; } Id CMakeKitAspect::cmakeToolId(const Kit *k) { if (!k) return {}; return Id::fromSetting(k->value(Constants::TOOL_ID)); } CMakeTool *CMakeKitAspect::cmakeTool(const Kit *k) { return k->isAspectRelevant(id()) ? CMakeToolManager::findById(cmakeToolId(k)) : nullptr; } void CMakeKitAspect::setCMakeTool(Kit *k, const Id id) { const Id toSet = id.isValid() ? id : defaultCMakeToolId(); QTC_ASSERT(!id.isValid() || CMakeToolManager::findById(toSet), return); if (k) k->setValue(Constants::TOOL_ID, toSet.toSetting()); } Tasks CMakeKitAspectFactory::validate(const Kit *k) const { Tasks result; CMakeTool *tool = CMakeKitAspect::cmakeTool(k); if (tool && tool->isValid()) { CMakeTool::Version version = tool->version(); if (version.major < 3 || (version.major == 3 && version.minor < 14)) { result << BuildSystemTask(Task::Warning, CMakeKitAspect::msgUnsupportedVersion(version.fullVersion)); } } return result; } void CMakeKitAspectFactory::setup(Kit *k) { CMakeTool *tool = CMakeKitAspect::cmakeTool(k); if (tool) return; // Look for a suitable auto-detected one: const QString kitSource = k->autoDetectionSource(); for (CMakeTool *tool : CMakeToolManager::cmakeTools()) { const QString toolSource = tool->detectionSource(); if (!toolSource.isEmpty() && toolSource == kitSource) { CMakeKitAspect::setCMakeTool(k, tool->id()); return; } } CMakeKitAspect::setCMakeTool(k, defaultCMakeToolId()); } void CMakeKitAspectFactory::fix(Kit *k) { setup(k); } KitAspectFactory::ItemList CMakeKitAspectFactory::toUserOutput(const Kit *k) const { const CMakeTool *const tool = CMakeKitAspect::cmakeTool(k); return {{Tr::tr("CMake"), tool ? tool->displayName() : Tr::tr("Unconfigured")}}; } KitAspect *CMakeKitAspectFactory::createKitAspect(Kit *k) const { QTC_ASSERT(k, return nullptr); return new CMakeKitAspectImpl(k, this); } void CMakeKitAspectFactory::addToMacroExpander(Kit *k, MacroExpander *expander) const { QTC_ASSERT(k, return); expander->registerFileVariables("CMake:Executable", Tr::tr("Path to the cmake executable"), [k] { CMakeTool *tool = CMakeKitAspect::cmakeTool(k); return tool ? tool->cmakeExecutable() : FilePath(); }); } QSet CMakeKitAspectFactory::availableFeatures(const Kit *k) const { if (CMakeKitAspect::cmakeTool(k)) return { CMakeProjectManager::Constants::CMAKE_FEATURE_ID }; return {}; } QString CMakeKitAspect::msgUnsupportedVersion(const QByteArray &versionString) { return Tr::tr("CMake version %1 is unsupported. Update to " "version 3.15 (with file-api) or later.") .arg(QString::fromUtf8(versionString)); } // -------------------------------------------------------------------- // CMakeGeneratorKitAspect: // -------------------------------------------------------------------- const char GENERATOR_ID[] = "CMake.GeneratorKitInformation"; const char GENERATOR_KEY[] = "Generator"; const char EXTRA_GENERATOR_KEY[] = "ExtraGenerator"; const char PLATFORM_KEY[] = "Platform"; const char TOOLSET_KEY[] = "Toolset"; class CMakeGeneratorKitAspectImpl final : public KitAspect { public: CMakeGeneratorKitAspectImpl(Kit *kit, const KitAspectFactory *factory) : KitAspect(kit, factory), m_label(createSubWidget()), m_changeButton(createSubWidget()) { const CMakeTool *tool = CMakeKitAspect::cmakeTool(kit); connect(this, &KitAspect::labelLinkActivated, this, [=](const QString &) { CMakeTool::openCMakeHelpUrl(tool, "%1/manual/cmake-generators.7.html"); }); m_label->setToolTip(factory->description()); m_changeButton->setText(Tr::tr("Change...")); refresh(); connect(m_changeButton, &QPushButton::clicked, this, &CMakeGeneratorKitAspectImpl::changeGenerator); } ~CMakeGeneratorKitAspectImpl() override { delete m_label; delete m_changeButton; } private: // KitAspectWidget interface void makeReadOnly() override { m_changeButton->setEnabled(false); } void addToLayoutImpl(Layouting::Layout &parent) override { addMutableAction(m_label); parent.addItem(m_label); parent.addItem(m_changeButton); } void refresh() override { CMakeTool *const tool = CMakeKitAspect::cmakeTool(m_kit); if (tool != m_currentTool) m_currentTool = tool; m_changeButton->setEnabled(m_currentTool); const QString generator = CMakeGeneratorKitAspect::generator(kit()); const QString platform = CMakeGeneratorKitAspect::platform(kit()); const QString toolset = CMakeGeneratorKitAspect::toolset(kit()); QStringList messageLabel; messageLabel << generator; if (!platform.isEmpty()) messageLabel << ", " << Tr::tr("Platform") << ": " << platform; if (!toolset.isEmpty()) messageLabel << ", " << Tr::tr("Toolset") << ": " << toolset; m_label->setText(messageLabel.join("")); } void changeGenerator() { QPointer changeDialog = new QDialog(m_changeButton); // Disable help button in titlebar on windows: Qt::WindowFlags flags = changeDialog->windowFlags(); flags |= Qt::MSWindowsFixedSizeDialogHint; changeDialog->setWindowFlags(flags); changeDialog->setWindowTitle(Tr::tr("CMake Generator")); auto layout = new QGridLayout(changeDialog); layout->setSizeConstraint(QLayout::SetFixedSize); auto cmakeLabel = new QLabel; cmakeLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); auto generatorCombo = new QComboBox; auto platformEdit = new QLineEdit; auto toolsetEdit = new QLineEdit; int row = 0; layout->addWidget(new QLabel(QLatin1String("Executable:"))); layout->addWidget(cmakeLabel, row, 1); ++row; layout->addWidget(new QLabel(Tr::tr("Generator:")), row, 0); layout->addWidget(generatorCombo, row, 1); ++row; layout->addWidget(new QLabel(Tr::tr("Platform:")), row, 0); layout->addWidget(platformEdit, row, 1); ++row; layout->addWidget(new QLabel(Tr::tr("Toolset:")), row, 0); layout->addWidget(toolsetEdit, row, 1); ++row; auto bb = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); layout->addWidget(bb, row, 0, 1, 2); connect(bb, &QDialogButtonBox::accepted, changeDialog.data(), &QDialog::accept); connect(bb, &QDialogButtonBox::rejected, changeDialog.data(), &QDialog::reject); cmakeLabel->setText(m_currentTool->cmakeExecutable().toUserOutput()); const QList generatorList = Utils::sorted( m_currentTool->supportedGenerators(), &CMakeTool::Generator::name); for (auto it = generatorList.constBegin(); it != generatorList.constEnd(); ++it) generatorCombo->addItem(it->name); auto updateDialog = [&generatorList, generatorCombo, platformEdit, toolsetEdit](const QString &name) { const auto it = std::find_if(generatorList.constBegin(), generatorList.constEnd(), [name](const CMakeTool::Generator &g) { return g.name == name; }); QTC_ASSERT(it != generatorList.constEnd(), return); generatorCombo->setCurrentText(name); platformEdit->setEnabled(it->supportsPlatform); toolsetEdit->setEnabled(it->supportsToolset); }; updateDialog(CMakeGeneratorKitAspect::generator(kit())); generatorCombo->setCurrentText(CMakeGeneratorKitAspect::generator(kit())); platformEdit->setText(platformEdit->isEnabled() ? CMakeGeneratorKitAspect::platform(kit()) : QString()); toolsetEdit->setText(toolsetEdit->isEnabled() ? CMakeGeneratorKitAspect::toolset(kit()) : QString()); connect(generatorCombo, &QComboBox::currentTextChanged, updateDialog); if (changeDialog->exec() == QDialog::Accepted) { if (!changeDialog) return; CMakeGeneratorKitAspect::set(kit(), generatorCombo->currentText(), platformEdit->isEnabled() ? platformEdit->text() : QString(), toolsetEdit->isEnabled() ? toolsetEdit->text() : QString()); refresh(); } } ElidingLabel *m_label; QPushButton *m_changeButton; CMakeTool *m_currentTool = nullptr; }; namespace { class GeneratorInfo { public: GeneratorInfo() = default; GeneratorInfo(const QString &generator_, const QString &platform_ = QString(), const QString &toolset_ = QString()) : generator(generator_) , platform(platform_) , toolset(toolset_) {} QVariant toVariant() const { QVariantMap result; result.insert(GENERATOR_KEY, generator); result.insert(EXTRA_GENERATOR_KEY, extraGenerator); result.insert(PLATFORM_KEY, platform); result.insert(TOOLSET_KEY, toolset); return result; } void fromVariant(const QVariant &v) { const QVariantMap value = v.toMap(); generator = value.value(GENERATOR_KEY).toString(); extraGenerator = value.value(EXTRA_GENERATOR_KEY).toString(); platform = value.value(PLATFORM_KEY).toString(); toolset = value.value(TOOLSET_KEY).toString(); } QString generator; QString extraGenerator; QString platform; QString toolset; }; } // namespace static GeneratorInfo generatorInfo(const Kit *k) { GeneratorInfo info; if (!k) return info; info.fromVariant(k->value(GENERATOR_ID)); return info; } static void setGeneratorInfo(Kit *k, const GeneratorInfo &info) { if (!k) return; k->setValue(GENERATOR_ID, info.toVariant()); } CMakeGeneratorKitAspectFactory::CMakeGeneratorKitAspectFactory() { setId(GENERATOR_ID); setDisplayName(Tr::tr("CMake generator")); setDescription(Tr::tr("CMake generator defines how a project is built when using CMake.
" "This setting is ignored when using other build systems.")); setPriority(19000); } QString CMakeGeneratorKitAspect::generator(const Kit *k) { return generatorInfo(k).generator; } QString CMakeGeneratorKitAspect::platform(const Kit *k) { return generatorInfo(k).platform; } QString CMakeGeneratorKitAspect::toolset(const Kit *k) { return generatorInfo(k).toolset; } void CMakeGeneratorKitAspect::setGenerator(Kit *k, const QString &generator) { GeneratorInfo info = generatorInfo(k); info.generator = generator; setGeneratorInfo(k, info); } void CMakeGeneratorKitAspect::setPlatform(Kit *k, const QString &platform) { GeneratorInfo info = generatorInfo(k); info.platform = platform; setGeneratorInfo(k, info); } void CMakeGeneratorKitAspect::setToolset(Kit *k, const QString &toolset) { GeneratorInfo info = generatorInfo(k); info.toolset = toolset; setGeneratorInfo(k, info); } void CMakeGeneratorKitAspect::set(Kit *k, const QString &generator, const QString &platform, const QString &toolset) { GeneratorInfo info(generator, platform, toolset); setGeneratorInfo(k, info); } QStringList CMakeGeneratorKitAspect::generatorArguments(const Kit *k) { QStringList result; GeneratorInfo info = generatorInfo(k); if (info.generator.isEmpty()) return result; result.append("-G" + info.generator); if (!info.platform.isEmpty()) result.append("-A" + info.platform); if (!info.toolset.isEmpty()) result.append("-T" + info.toolset); return result; } CMakeConfig CMakeGeneratorKitAspect::generatorCMakeConfig(const Kit *k) { CMakeConfig config; GeneratorInfo info = generatorInfo(k); if (info.generator.isEmpty()) return config; config << CMakeConfigItem("CMAKE_GENERATOR", info.generator.toUtf8()); if (!info.platform.isEmpty()) config << CMakeConfigItem("CMAKE_GENERATOR_PLATFORM", info.platform.toUtf8()); if (!info.toolset.isEmpty()) config << CMakeConfigItem("CMAKE_GENERATOR_TOOLSET", info.toolset.toUtf8()); return config; } bool CMakeGeneratorKitAspect::isMultiConfigGenerator(const Kit *k) { const QString generator = CMakeGeneratorKitAspect::generator(k); return generator.indexOf("Visual Studio") != -1 || generator == "Xcode" || generator == "Ninja Multi-Config"; } QVariant CMakeGeneratorKitAspectFactory::defaultValue(const Kit *k) const { QTC_ASSERT(k, return QVariant()); CMakeTool *tool = CMakeKitAspect::cmakeTool(k); if (!tool) return QVariant(); if (isIos(k)) return GeneratorInfo("Xcode").toVariant(); const QList known = tool->supportedGenerators(); auto it = std::find_if(known.constBegin(), known.constEnd(), [](const CMakeTool::Generator &g) { return g.matches("Ninja"); }); if (it != known.constEnd()) { const bool hasNinja = [k, tool] { if (Internal::settings(nullptr).ninjaPath().isEmpty()) { auto findNinja = [](const Environment &env) -> bool { return !env.searchInPath("ninja").isEmpty(); }; if (!findNinja(tool->filePath().deviceEnvironment())) return findNinja(k->buildEnvironment()); } return true; }(); if (hasNinja) return GeneratorInfo("Ninja").toVariant(); } if (tool->filePath().osType() == OsTypeWindows) { // *sigh* Windows with its zoo of incompatible stuff again... Toolchain *tc = ToolchainKitAspect::cxxToolchain(k); if (tc && tc->typeId() == ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID) { it = std::find_if(known.constBegin(), known.constEnd(), [](const CMakeTool::Generator &g) { return g.matches("MinGW Makefiles"); }); } else { it = std::find_if(known.constBegin(), known.constEnd(), [](const CMakeTool::Generator &g) { return g.matches("NMake Makefiles") || g.matches("NMake Makefiles JOM"); }); if (projectExplorerSettings().useJom) { it = std::find_if(known.constBegin(), known.constEnd(), [](const CMakeTool::Generator &g) { return g.matches("NMake Makefiles JOM"); }); } if (it == known.constEnd()) { it = std::find_if(known.constBegin(), known.constEnd(), [](const CMakeTool::Generator &g) { return g.matches("NMake Makefiles"); }); } } } else { // Unix-oid OSes: it = std::find_if(known.constBegin(), known.constEnd(), [](const CMakeTool::Generator &g) { return g.matches("Unix Makefiles"); }); } if (it == known.constEnd()) it = known.constBegin(); // Fallback to the first generator... if (it == known.constEnd()) return QVariant(); return GeneratorInfo(it->name).toVariant(); } Tasks CMakeGeneratorKitAspectFactory::validate(const Kit *k) const { CMakeTool *tool = CMakeKitAspect::cmakeTool(k); if (!tool) return {}; Tasks result; const auto addWarning = [&result](const QString &desc) { result << BuildSystemTask(Task::Warning, desc); }; if (!tool->isValid()) { addWarning(Tr::tr("CMake Tool is unconfigured, CMake generator will be ignored.")); } else { const GeneratorInfo info = generatorInfo(k); QList known = tool->supportedGenerators(); auto it = std::find_if(known.constBegin(), known.constEnd(), [info](const CMakeTool::Generator &g) { return g.matches(info.generator); }); if (it == known.constEnd()) { addWarning(Tr::tr("CMake Tool does not support the configured generator.")); } else { if (!it->supportsPlatform && !info.platform.isEmpty()) addWarning(Tr::tr("Platform is not supported by the selected CMake generator.")); if (!it->supportsToolset && !info.toolset.isEmpty()) addWarning(Tr::tr("Toolset is not supported by the selected CMake generator.")); } if (!tool->hasFileApi()) { addWarning(Tr::tr("The selected CMake binary does not support file-api. " "%1 will not be able to parse CMake projects.") .arg(QGuiApplication::applicationDisplayName())); } } return result; } void CMakeGeneratorKitAspectFactory::setup(Kit *k) { if (!k || k->hasValue(id())) return; GeneratorInfo info; info.fromVariant(defaultValue(k)); setGeneratorInfo(k, info); } void CMakeGeneratorKitAspectFactory::fix(Kit *k) { const CMakeTool *tool = CMakeKitAspect::cmakeTool(k); const GeneratorInfo info = generatorInfo(k); if (!tool) return; QList known = tool->supportedGenerators(); auto it = std::find_if(known.constBegin(), known.constEnd(), [info](const CMakeTool::Generator &g) { return g.matches(info.generator); }); if (it == known.constEnd()) { GeneratorInfo dv; dv.fromVariant(defaultValue(k)); setGeneratorInfo(k, dv); } else { const GeneratorInfo dv(info.generator, it->supportsPlatform ? info.platform : QString(), it->supportsToolset ? info.toolset : QString()); setGeneratorInfo(k, dv); } } void CMakeGeneratorKitAspectFactory::upgrade(Kit *k) { QTC_ASSERT(k, return); const QVariant value = k->value(GENERATOR_ID); if (value.typeId() != QMetaType::QVariantMap) { GeneratorInfo info; const QString fullName = value.toString(); const int pos = fullName.indexOf(" - "); if (pos >= 0) { info.generator = fullName.mid(pos + 3); info.extraGenerator = fullName.mid(0, pos); } else { info.generator = fullName; } setGeneratorInfo(k, info); } } KitAspectFactory::ItemList CMakeGeneratorKitAspectFactory::toUserOutput(const Kit *k) const { const GeneratorInfo info = generatorInfo(k); QString message; if (info.generator.isEmpty()) { message = Tr::tr(""); } else { message = Tr::tr("Generator: %1
Extra generator: %2").arg(info.generator).arg(info.extraGenerator); if (!info.platform.isEmpty()) message += "
" + Tr::tr("Platform: %1").arg(info.platform); if (!info.toolset.isEmpty()) message += "
" + Tr::tr("Toolset: %1").arg(info.toolset); } return {{Tr::tr("CMake Generator"), message}}; } KitAspect *CMakeGeneratorKitAspectFactory::createKitAspect(Kit *k) const { return new CMakeGeneratorKitAspectImpl(k, this); } void CMakeGeneratorKitAspectFactory::addToBuildEnvironment(const Kit *k, Environment &env) const { GeneratorInfo info = generatorInfo(k); if (info.generator == "NMake Makefiles JOM") { if (env.searchInPath("jom.exe").exists()) return; env.appendOrSetPath(Core::ICore::libexecPath()); env.appendOrSetPath(Core::ICore::libexecPath("jom")); } } // -------------------------------------------------------------------- // CMakeConfigurationKitAspect: // -------------------------------------------------------------------- const char CONFIGURATION_ID[] = "CMake.ConfigurationKitInformation"; const char ADDITIONAL_CONFIGURATION_ID[] = "CMake.AdditionalConfigurationParameters"; const char CMAKE_C_TOOLCHAIN_KEY[] = "CMAKE_C_COMPILER"; const char CMAKE_CXX_TOOLCHAIN_KEY[] = "CMAKE_CXX_COMPILER"; const char CMAKE_QMAKE_KEY[] = "QT_QMAKE_EXECUTABLE"; const char CMAKE_PREFIX_PATH_KEY[] = "CMAKE_PREFIX_PATH"; const char QTC_CMAKE_PRESET_KEY[] = "QTC_CMAKE_PRESET"; class CMakeConfigurationKitAspectWidget final : public KitAspect { public: CMakeConfigurationKitAspectWidget(Kit *kit, const KitAspectFactory *factory) : KitAspect(kit, factory), m_summaryLabel(createSubWidget()), m_manageButton(createSubWidget()) { refresh(); m_manageButton->setText(Tr::tr("Change...")); connect(m_manageButton, &QAbstractButton::clicked, this, &CMakeConfigurationKitAspectWidget::editConfigurationChanges); } private: // KitAspectWidget interface void addToLayoutImpl(Layouting::Layout &parent) override { addMutableAction(m_summaryLabel); parent.addItem(m_summaryLabel); parent.addItem(m_manageButton); } void makeReadOnly() override { m_manageButton->setEnabled(false); if (m_dialog) m_dialog->reject(); } void refresh() override { const QStringList current = CMakeConfigurationKitAspect::toArgumentsList(kit()); const QString additionalText = CMakeConfigurationKitAspect::additionalConfiguration(kit()); const QString labelText = additionalText.isEmpty() ? current.join(' ') : current.join(' ') + " " + additionalText; m_summaryLabel->setText(labelText); if (m_editor) m_editor->setPlainText(current.join('\n')); if (m_additionalEditor) m_additionalEditor->setText(additionalText); } void editConfigurationChanges() { if (m_dialog) { m_dialog->activateWindow(); m_dialog->raise(); return; } QTC_ASSERT(!m_editor, return); const CMakeTool *tool = CMakeKitAspect::cmakeTool(kit()); m_dialog = new QDialog(m_summaryLabel->window()); m_dialog->setWindowTitle(Tr::tr("Edit CMake Configuration")); auto layout = new QVBoxLayout(m_dialog); m_editor = new QPlainTextEdit; auto editorLabel = new QLabel(m_dialog); editorLabel->setText(Tr::tr("Enter one CMake variable per line.
" "To set a variable, use -D<variable>:<type>=<value>.
" "<type> can have one of the following values: FILEPATH, PATH, " "BOOL, INTERNAL, or STRING.")); connect(editorLabel, &QLabel::linkActivated, this, [=](const QString &) { CMakeTool::openCMakeHelpUrl(tool, "%1/manual/cmake-variables.7.html"); }); m_editor->setMinimumSize(800, 200); auto chooser = new VariableChooser(m_dialog); chooser->addSupportedWidget(m_editor); chooser->addMacroExpanderProvider([this] { return kit()->macroExpander(); }); m_additionalEditor = new QLineEdit; auto additionalLabel = new QLabel(m_dialog); additionalLabel->setText(Tr::tr("Additional CMake options:")); connect(additionalLabel, &QLabel::linkActivated, this, [=](const QString &) { CMakeTool::openCMakeHelpUrl(tool, "%1/manual/cmake.1.html#options"); }); auto additionalChooser = new VariableChooser(m_dialog); additionalChooser->addSupportedWidget(m_additionalEditor); additionalChooser->addMacroExpanderProvider([this] { return kit()->macroExpander(); }); auto additionalLayout = new QHBoxLayout(); additionalLayout->addWidget(additionalLabel); additionalLayout->addWidget(m_additionalEditor); auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Apply |QDialogButtonBox::Reset|QDialogButtonBox::Cancel); layout->addWidget(m_editor); layout->addWidget(editorLabel); layout->addLayout(additionalLayout); layout->addWidget(buttons); connect(buttons, &QDialogButtonBox::accepted, m_dialog, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, m_dialog, &QDialog::reject); connect(buttons, &QDialogButtonBox::clicked, m_dialog, [buttons, this](QAbstractButton *button) { if (button != buttons->button(QDialogButtonBox::Reset)) return; KitGuard guard(kit()); CMakeConfigurationKitAspect::setConfiguration(kit(), CMakeConfigurationKitAspect::defaultConfiguration(kit())); CMakeConfigurationKitAspect::setAdditionalConfiguration(kit(), QString()); }); connect(m_dialog, &QDialog::accepted, this, &CMakeConfigurationKitAspectWidget::acceptChangesDialog); connect(m_dialog, &QDialog::rejected, this, &CMakeConfigurationKitAspectWidget::closeChangesDialog); connect(buttons->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &CMakeConfigurationKitAspectWidget::applyChanges); refresh(); m_dialog->show(); } void applyChanges() { QTC_ASSERT(m_editor, return); KitGuard guard(kit()); QStringList unknownOptions; const CMakeConfig config = CMakeConfig::fromArguments(m_editor->toPlainText().split('\n'), unknownOptions); CMakeConfigurationKitAspect::setConfiguration(kit(), config); QString additionalConfiguration = m_additionalEditor->text(); if (!unknownOptions.isEmpty()) { if (!additionalConfiguration.isEmpty()) additionalConfiguration += " "; additionalConfiguration += ProcessArgs::joinArgs(unknownOptions); } CMakeConfigurationKitAspect::setAdditionalConfiguration(kit(), additionalConfiguration); } void closeChangesDialog() { m_dialog->deleteLater(); m_dialog = nullptr; m_editor = nullptr; m_additionalEditor = nullptr; } void acceptChangesDialog() { applyChanges(); closeChangesDialog(); } QLabel *m_summaryLabel; QPushButton *m_manageButton; QDialog *m_dialog = nullptr; QPlainTextEdit *m_editor = nullptr; QLineEdit *m_additionalEditor = nullptr; }; CMakeConfigurationKitAspectFactory::CMakeConfigurationKitAspectFactory() { setId(CONFIGURATION_ID); setDisplayName(Tr::tr("CMake Configuration")); setDescription(Tr::tr("Default configuration passed to CMake when setting up a project.")); setPriority(18000); } CMakeConfig CMakeConfigurationKitAspect::configuration(const Kit *k) { if (!k) return CMakeConfig(); const QStringList tmp = k->value(CONFIGURATION_ID).toStringList(); return Utils::transform(tmp, &CMakeConfigItem::fromString); } void CMakeConfigurationKitAspect::setConfiguration(Kit *k, const CMakeConfig &config) { if (!k) return; const QStringList tmp = Utils::transform(config.toList(), [](const CMakeConfigItem &i) { return i.toString(); }); k->setValue(CONFIGURATION_ID, tmp); } QString CMakeConfigurationKitAspect::additionalConfiguration(const Kit *k) { if (!k) return QString(); return k->value(ADDITIONAL_CONFIGURATION_ID).toString(); } void CMakeConfigurationKitAspect::setAdditionalConfiguration(Kit *k, const QString &config) { if (!k) return; k->setValue(ADDITIONAL_CONFIGURATION_ID, config); } QStringList CMakeConfigurationKitAspect::toStringList(const Kit *k) { QStringList current = Utils::transform(CMakeConfigurationKitAspect::configuration(k).toList(), [](const CMakeConfigItem &i) { return i.toString(); }); current = Utils::filtered(current, [](const QString &s) { return !s.isEmpty(); }); return current; } void CMakeConfigurationKitAspect::fromStringList(Kit *k, const QStringList &in) { CMakeConfig result; for (const QString &s : in) { const CMakeConfigItem item = CMakeConfigItem::fromString(s); if (!item.key.isEmpty()) result << item; } setConfiguration(k, result); } QStringList CMakeConfigurationKitAspect::toArgumentsList(const Kit *k) { QStringList current = Utils::transform(CMakeConfigurationKitAspect::configuration(k).toList(), [](const CMakeConfigItem &i) { return i.toArgument(nullptr); }); current = Utils::filtered(current, [](const QString &s) { return s != "-D" && s != "-U"; }); return current; } CMakeConfig CMakeConfigurationKitAspect::defaultConfiguration(const Kit *k) { Q_UNUSED(k) CMakeConfig config; // Qt4: config << CMakeConfigItem(CMAKE_QMAKE_KEY, CMakeConfigItem::FILEPATH, "%{Qt:qmakeExecutable}"); // Qt5: config << CMakeConfigItem(CMAKE_PREFIX_PATH_KEY, CMakeConfigItem::PATH, "%{Qt:QT_INSTALL_PREFIX}"); config << CMakeConfigItem(CMAKE_C_TOOLCHAIN_KEY, CMakeConfigItem::FILEPATH, "%{Compiler:Executable:C}"); config << CMakeConfigItem(CMAKE_CXX_TOOLCHAIN_KEY, CMakeConfigItem::FILEPATH, "%{Compiler:Executable:Cxx}"); return config; } void CMakeConfigurationKitAspect::setCMakePreset(Kit *k, const QString &presetName) { CMakeConfig config = configuration(k); config.prepend( CMakeConfigItem(QTC_CMAKE_PRESET_KEY, CMakeConfigItem::INTERNAL, presetName.toUtf8())); setConfiguration(k, config); } CMakeConfigItem CMakeConfigurationKitAspect::cmakePresetConfigItem(const Kit *k) { const CMakeConfig config = configuration(k); return Utils::findOrDefault(config, [](const CMakeConfigItem &item) { return item.key == QTC_CMAKE_PRESET_KEY; }); } QVariant CMakeConfigurationKitAspectFactory::defaultValue(const Kit *k) const { // FIXME: Convert preload scripts CMakeConfig config = CMakeConfigurationKitAspect::defaultConfiguration(k); const QStringList tmp = Utils::transform(config.toList(), [](const CMakeConfigItem &i) { return i.toString(); }); return tmp; } Tasks CMakeConfigurationKitAspectFactory::validate(const Kit *k) const { QTC_ASSERT(k, return Tasks()); const CMakeTool *const cmake = CMakeKitAspect::cmakeTool(k); if (!cmake) return Tasks(); const QtSupport::QtVersion *const version = QtSupport::QtKitAspect::qtVersion(k); const Toolchain *const tcC = ToolchainKitAspect::cToolchain(k); const Toolchain *const tcCxx = ToolchainKitAspect::cxxToolchain(k); const CMakeConfig config = CMakeConfigurationKitAspect::configuration(k); const bool isQt4 = version && version->qtVersion() < QVersionNumber(5, 0, 0); FilePath qmakePath; // This is relative to the cmake used for building. QStringList qtInstallDirs; // This is relativ to the cmake used for building. FilePath tcCPath; FilePath tcCxxPath; for (const CMakeConfigItem &i : config) { // Do not use expand(QByteArray) as we cannot be sure the input is latin1 const QString expandedValue = k->macroExpander()->expand(QString::fromUtf8(i.value)); if (i.key == CMAKE_QMAKE_KEY) qmakePath = cmake->cmakeExecutable().withNewPath(expandedValue); else if (i.key == CMAKE_C_TOOLCHAIN_KEY) tcCPath = cmake->cmakeExecutable().withNewPath(expandedValue); else if (i.key == CMAKE_CXX_TOOLCHAIN_KEY) tcCxxPath = cmake->cmakeExecutable().withNewPath(expandedValue); else if (i.key == CMAKE_PREFIX_PATH_KEY) qtInstallDirs = CMakeConfigItem::cmakeSplitValue(expandedValue); } Tasks result; const auto addWarning = [&result](const QString &desc) { result << BuildSystemTask(Task::Warning, desc); }; // Validate Qt: if (qmakePath.isEmpty()) { if (version && version->isValid() && isQt4) { addWarning(Tr::tr("CMake configuration has no path to qmake binary set, " "even though the kit has a valid Qt version.")); } } else { if (!version || !version->isValid()) { addWarning(Tr::tr("CMake configuration has a path to a qmake binary set, " "even though the kit has no valid Qt version.")); } else if (qmakePath != version->qmakeFilePath() && isQt4) { addWarning(Tr::tr("CMake configuration has a path to a qmake binary set " "that does not match the qmake binary path " "configured in the Qt version.")); } } if (version && !qtInstallDirs.contains(version->prefix().path()) && !isQt4) { if (version->isValid()) { addWarning(Tr::tr("CMake configuration has no CMAKE_PREFIX_PATH set " "that points to the kit Qt version.")); } } // Validate Toolchains: if (tcCPath.isEmpty()) { if (tcC && tcC->isValid()) { addWarning(Tr::tr("CMake configuration has no path to a C compiler set, " "even though the kit has a valid tool chain.")); } } else { if (!tcC || !tcC->isValid()) { addWarning(Tr::tr("CMake configuration has a path to a C compiler set, " "even though the kit has no valid tool chain.")); } else if (tcCPath != tcC->compilerCommand() && tcCPath != tcCPath.withNewMappedPath(tcC->compilerCommand())) { addWarning(Tr::tr("CMake configuration has a path to a C compiler set " "that does not match the compiler path " "configured in the tool chain of the kit.")); } } if (tcCxxPath.isEmpty()) { if (tcCxx && tcCxx->isValid()) { addWarning(Tr::tr("CMake configuration has no path to a C++ compiler set, " "even though the kit has a valid tool chain.")); } } else { if (!tcCxx || !tcCxx->isValid()) { addWarning(Tr::tr("CMake configuration has a path to a C++ compiler set, " "even though the kit has no valid tool chain.")); } else if (tcCxxPath != tcCxx->compilerCommand() && tcCxxPath != tcCxxPath.withNewMappedPath(tcCxx->compilerCommand())) { addWarning(Tr::tr("CMake configuration has a path to a C++ compiler set " "that does not match the compiler path " "configured in the tool chain of the kit.")); } } return result; } void CMakeConfigurationKitAspectFactory::setup(Kit *k) { if (k && !k->hasValue(CONFIGURATION_ID)) k->setValue(CONFIGURATION_ID, defaultValue(k)); } void CMakeConfigurationKitAspectFactory::fix(Kit *k) { Q_UNUSED(k) } KitAspectFactory::ItemList CMakeConfigurationKitAspectFactory::toUserOutput(const Kit *k) const { return {{Tr::tr("CMake Configuration"), CMakeConfigurationKitAspect::toStringList(k).join("
")}}; } KitAspect *CMakeConfigurationKitAspectFactory::createKitAspect(Kit *k) const { if (!k) return nullptr; return new CMakeConfigurationKitAspectWidget(k, this); } // Factory instances; CMakeKitAspectFactory &cmakeKitAspectFactory() { static CMakeKitAspectFactory theCMakeKitAspectFactory; return theCMakeKitAspectFactory; } CMakeGeneratorKitAspectFactory &cmakeGeneratorKitAspectFactory() { static CMakeGeneratorKitAspectFactory theCMakeGeneratorKitAspectFactory; return theCMakeGeneratorKitAspectFactory; } static CMakeConfigurationKitAspectFactory &cmakeConfigurationKitAspectFactory() { static CMakeConfigurationKitAspectFactory theCMakeConfigurationKitAspectFactory; return theCMakeConfigurationKitAspectFactory; } KitAspect *CMakeKitAspect::createKitAspect(Kit *k) { return cmakeKitAspectFactory().createKitAspect(k); } KitAspect *CMakeGeneratorKitAspect::createKitAspect(Kit *k) { return cmakeGeneratorKitAspectFactory().createKitAspect(k); } KitAspect *CMakeConfigurationKitAspect::createKitAspect(Kit *k) { return cmakeConfigurationKitAspectFactory().createKitAspect(k); } void Internal::setupCMakeKitAspects() { cmakeKitAspectFactory(); cmakeGeneratorKitAspectFactory(); cmakeConfigurationKitAspectFactory(); } } // namespace CMakeProjectManager