// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cmakeprojectmanager.h" #include "cmakebuildsystem.h" #include "cmakekitaspect.h" #include "cmakeprocess.h" #include "cmakeproject.h" #include "cmakeprojectconstants.h" #include "cmakeprojectmanagertr.h" #include "cmakeprojectnodes.h" #include "cmakespecificsettings.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 using namespace Core; using namespace CppEditor; using namespace ProjectExplorer; using namespace Utils; namespace CMakeProjectManager::Internal { CMakeManager::CMakeManager() { namespace PEC = ProjectExplorer::Constants; const Context projectContext(CMakeProjectManager::Constants::CMAKE_PROJECT_ID); ActionBuilder runCMakeAction(this, Constants::RUN_CMAKE); runCMakeAction.setText(Tr::tr("Run CMake")); runCMakeAction.setIcon(ProjectExplorer::Icons::CMAKE_LOGO.icon()); runCMakeAction.bindContextAction(&m_runCMakeAction); runCMakeAction.setCommandAttribute(Command::CA_Hide); runCMakeAction.setContainer(PEC::M_BUILDPROJECT, PEC::G_BUILD_BUILD); runCMakeAction.setOnTriggered(this, [this] { runCMake(ProjectManager::startupBuildSystem()); }); ActionBuilder clearCMakeCacheAction(this, Constants::CLEAR_CMAKE_CACHE); clearCMakeCacheAction.setText(Tr::tr("Clear CMake Configuration")); clearCMakeCacheAction.bindContextAction(&m_clearCMakeCacheAction); clearCMakeCacheAction.setCommandAttribute(Command::CA_Hide); clearCMakeCacheAction.setContainer(PEC::M_BUILDPROJECT, PEC::G_BUILD_BUILD); clearCMakeCacheAction.setOnTriggered(this, [this] { clearCMakeCache(ProjectManager::startupBuildSystem()); }); ActionBuilder runCMakeActionContextMenu(this, Constants::RUN_CMAKE_CONTEXT_MENU); runCMakeActionContextMenu.setText(Tr::tr("Run CMake")); runCMakeActionContextMenu.setIcon(ProjectExplorer::Icons::CMAKE_LOGO.icon()); runCMakeActionContextMenu.setContext(projectContext); runCMakeActionContextMenu.bindContextAction(&m_runCMakeActionContextMenu); runCMakeActionContextMenu.setCommandAttribute(Command::CA_Hide); runCMakeActionContextMenu.setContainer(PEC::M_PROJECTCONTEXT, PEC::G_PROJECT_BUILD); runCMakeActionContextMenu.setOnTriggered(this, [this] { runCMake(ProjectTree::currentBuildSystem()); }); ActionBuilder buildFileContextAction(this, Constants::BUILD_FILE_CONTEXT_MENU); buildFileContextAction.setText(Tr::tr("Build")); buildFileContextAction.bindContextAction(&m_buildFileContextMenu); buildFileContextAction.setContext(projectContext); buildFileContextAction.setCommandAttribute(Command::CA_Hide); buildFileContextAction.setContainer(PEC::M_FILECONTEXT, PEC::G_FILE_OTHER); buildFileContextAction.setOnTriggered(this, [this] { buildFileContextMenu(); }); ActionBuilder rescanProjectAction(this, Constants::RESCAN_PROJECT); rescanProjectAction.setText(Tr::tr("Rescan Project")); rescanProjectAction.bindContextAction(&m_rescanProjectAction); rescanProjectAction.setCommandAttribute(Command::CA_Hide); rescanProjectAction.setContainer(PEC::M_BUILDPROJECT, PEC::G_BUILD_BUILD); rescanProjectAction.setOnTriggered(this, [this] { rescanProject(ProjectTree::currentBuildSystem()); }); ActionBuilder reloadCMakePresetsAction(this, Constants::RELOAD_CMAKE_PRESETS); reloadCMakePresetsAction.setText(Tr::tr("Reload CMake Presets")); reloadCMakePresetsAction.setIcon(Utils::Icons::RELOAD.icon()); reloadCMakePresetsAction.bindContextAction(&m_reloadCMakePresetsAction); reloadCMakePresetsAction.setCommandAttribute(Command::CA_Hide); reloadCMakePresetsAction.setContainer(PEC::M_BUILDPROJECT, PEC::G_BUILD_BUILD); reloadCMakePresetsAction.setOnTriggered(this, [this] { reloadCMakePresets(); }); m_buildFileAction = new Utils::ParameterAction(Tr::tr("Build File"), Tr::tr("Build File \"%1\""), Utils::ParameterAction::AlwaysEnabled, this); Command *command = ActionManager::registerAction(m_buildFileAction, Constants::BUILD_FILE); command->setAttribute(Command::CA_Hide); command->setAttribute(Command::CA_UpdateText); command->setDescription(m_buildFileAction->text()); command->setDefaultKeySequence(QKeySequence(Tr::tr("Ctrl+Alt+B"))); ActionContainer *mbuild = ActionManager::actionContainer(PEC::M_BUILDPROJECT); mbuild->addAction(command, PEC::G_BUILD_BUILD); connect(m_buildFileAction, &QAction::triggered, this, [this] { buildFile(); }); // CMake Profiler ActionBuilder cmakeProfilerAction(this, Constants::RUN_CMAKE_PROFILER); cmakeProfilerAction.setIcon(ProjectExplorer::Icons::CMAKE_LOGO.icon()); cmakeProfilerAction.setText(Tr::tr("CMake Profiler")); cmakeProfilerAction.bindContextAction(&m_cmakeProfilerAction); cmakeProfilerAction.setCommandDescription(m_cmakeProfilerAction->text()); cmakeProfilerAction.setContainer(Debugger::Constants::M_DEBUG_ANALYZER, Debugger::Constants::G_ANALYZER_TOOLS, false); cmakeProfilerAction.setOnTriggered(this, [this] { runCMakeWithProfiling(ProjectManager::startupBuildSystem()); }); // CMake Debugger ActionContainer *mdebugger = ActionManager::actionContainer(PEC::M_DEBUG_STARTDEBUGGING); mdebugger->appendGroup(Constants::CMAKE_DEBUGGING_GROUP); mdebugger->addSeparator(Context(Core::Constants::C_GLOBAL), Constants::CMAKE_DEBUGGING_GROUP, &m_cmakeDebuggerSeparator); ActionBuilder cmakeDebuggerAction(this, Constants::RUN_CMAKE_DEBUGGER); cmakeDebuggerAction.setText(Tr::tr("Start CMake Debugging")); cmakeDebuggerAction.setIcon(ProjectExplorer::Icons::CMAKE_LOGO.icon()); cmakeDebuggerAction.bindContextAction(&m_cmakeDebuggerAction); cmakeDebuggerAction.setCommandDescription(m_cmakeDebuggerAction->text()); cmakeDebuggerAction.setContainer(PEC::M_DEBUG_STARTDEBUGGING, Constants::CMAKE_DEBUGGING_GROUP); cmakeDebuggerAction.setOnTriggered(this, [] { ProjectExplorerPlugin::runStartupProject(PEC::DAP_CMAKE_DEBUG_RUN_MODE, false); }); connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, [this] { auto cmakeBuildSystem = qobject_cast( ProjectManager::startupBuildSystem()); if (cmakeBuildSystem) { const BuildDirParameters parameters(cmakeBuildSystem); const auto tool = parameters.cmakeTool(); CMakeTool::Version version = tool ? tool->version() : CMakeTool::Version(); m_canDebugCMake = (version.major == 3 && version.minor >= 27) || version.major > 3; } updateCmakeActions(ProjectTree::currentNode()); }); connect(BuildManager::instance(), &BuildManager::buildStateChanged, this, [this] { updateCmakeActions(ProjectTree::currentNode()); }); connect(EditorManager::instance(), &EditorManager::currentEditorChanged, this, &CMakeManager::updateBuildFileAction); connect(ProjectTree::instance(), &ProjectTree::currentNodeChanged, this, &CMakeManager::updateCmakeActions); updateCmakeActions(ProjectTree::currentNode()); } void CMakeManager::updateCmakeActions(Node *node) { auto project = qobject_cast(ProjectManager::startupProject()); const bool visible = project && !BuildManager::isBuilding(project); m_runCMakeAction->setVisible(visible); m_runCMakeActionContextMenu->setEnabled(visible); m_clearCMakeCacheAction->setVisible(visible); m_rescanProjectAction->setVisible(visible); m_cmakeProfilerAction->setEnabled(visible); m_cmakeDebuggerAction->setEnabled(m_canDebugCMake && visible); m_cmakeDebuggerSeparator->setVisible(m_canDebugCMake && visible); const bool reloadPresetsVisible = [project] { if (!project) return false; const FilePath presetsPath = project->projectFilePath().parentDir().pathAppended( "CMakePresets.json"); return presetsPath.exists(); }(); m_reloadCMakePresetsAction->setVisible(reloadPresetsVisible); enableBuildFileMenus(node); } void CMakeManager::clearCMakeCache(BuildSystem *buildSystem) { auto cmakeBuildSystem = dynamic_cast(buildSystem); QTC_ASSERT(cmakeBuildSystem, return); cmakeBuildSystem->clearCMakeCache(); } void CMakeManager::runCMake(BuildSystem *buildSystem) { auto cmakeBuildSystem = dynamic_cast(buildSystem); QTC_ASSERT(cmakeBuildSystem, return ); if (ProjectExplorerPlugin::saveModifiedFiles()) cmakeBuildSystem->runCMake(); } void CMakeManager::runCMakeWithProfiling(BuildSystem *buildSystem) { auto cmakeBuildSystem = dynamic_cast(buildSystem); QTC_ASSERT(cmakeBuildSystem, return ); if (ProjectExplorerPlugin::saveModifiedFiles()) { // cmakeBuildSystem->runCMakeWithProfiling() below will trigger Target::buildSystemUpdated // which will ensure that the "cmake-profile.json" has been created and we can load the viewer std::unique_ptr context{new QObject}; QObject *pcontext = context.get(); QObject::connect(cmakeBuildSystem->target(), &Target::buildSystemUpdated, pcontext, [context = std::move(context)]() mutable { context.reset(); Core::Command *ctfVisualiserLoadTrace = Core::ActionManager::command( "Analyzer.Menu.StartAnalyzer.CtfVisualizer.LoadTrace"); if (ctfVisualiserLoadTrace) { auto *action = ctfVisualiserLoadTrace->actionForContext( Core::Constants::C_GLOBAL); const FilePath file = TemporaryDirectory::masterDirectoryFilePath() / "cmake-profile.json"; action->setData(file.nativePath()); emit ctfVisualiserLoadTrace->action()->triggered(); } }); cmakeBuildSystem->runCMakeWithProfiling(); } } void CMakeManager::rescanProject(BuildSystem *buildSystem) { auto cmakeBuildSystem = dynamic_cast(buildSystem); QTC_ASSERT(cmakeBuildSystem, return); cmakeBuildSystem->runCMakeAndScanProjectTree();// by my experience: every rescan run requires cmake run too } void CMakeManager::updateBuildFileAction() { Node *node = nullptr; if (Core::IDocument *currentDocument = Core::EditorManager::currentDocument()) node = ProjectTree::nodeForFile(currentDocument->filePath()); enableBuildFileMenus(node); } void CMakeManager::enableBuildFileMenus(Node *node) { m_buildFileAction->setVisible(false); m_buildFileAction->setEnabled(false); m_buildFileAction->setParameter(QString()); m_buildFileContextMenu->setEnabled(false); if (!node) return; Project *project = ProjectTree::projectForNode(node); if (!project) return; Target *target = project->activeTarget(); if (!target) return; const QString generator = CMakeGeneratorKitAspect::generator(target->kit()); if (generator != "Ninja" && !generator.contains("Makefiles")) return; if (const FileNode *fileNode = node->asFileNode()) { const FileType type = fileNode->fileType(); const bool visible = qobject_cast(project) && dynamic_cast(node->parentProjectNode()) && (type == FileType::Source || type == FileType::Header); const bool enabled = visible && !BuildManager::isBuilding(project); m_buildFileAction->setVisible(visible); m_buildFileAction->setEnabled(enabled); m_buildFileAction->setParameter(node->filePath().fileName()); m_buildFileContextMenu->setEnabled(enabled); } } void CMakeManager::reloadCMakePresets() { QMessageBox::StandardButton clickedButton = CheckableMessageBox::question( Core::ICore::dialogParent(), Tr::tr("Reload CMake Presets"), Tr::tr("Re-generates the kits that were created for CMake presets. All manual " "modifications to the CMake project settings will be lost."), settings().askBeforePresetsReload.askAgainCheckableDecider(), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes, QMessageBox::Yes, { {QMessageBox::Yes, Tr::tr("Reload")}, }); settings().writeSettings(); if (clickedButton == QMessageBox::Cancel) return; CMakeProject *project = static_cast(ProjectTree::currentProject()); if (!project) return; const QSet oldPresets = Utils::transform(project->presetsData().configurePresets, [](const auto &preset) { return preset.name; }); project->readPresets(); QList oldKits; for (const auto &target : project->targets()) { const CMakeConfigItem presetItem = CMakeConfigurationKitAspect::cmakePresetConfigItem( target->kit()); if (BuildManager::isBuilding(target)) BuildManager::cancel(); // Only clear the CMake configuration for preset kits. Any manual kit configuration // will get the chance to get imported afterwards in the Kit selection wizard CMakeBuildSystem *bs = static_cast(target->buildSystem()); if (!presetItem.isNull() && bs) bs->clearCMakeCache(); if (!presetItem.isNull() && oldPresets.contains(QString::fromUtf8(presetItem.value))) oldKits << target->kit(); project->removeTarget(target); } project->setOldPresetKits(oldKits); Core::ModeManager::activateMode(ProjectExplorer::Constants::MODE_SESSION); Core::ModeManager::setFocusToCurrentMode(); } void CMakeManager::buildFile(Node *node) { if (!node) { Core::IDocument *currentDocument= Core::EditorManager::currentDocument(); if (!currentDocument) return; const Utils::FilePath file = currentDocument->filePath(); node = ProjectTree::nodeForFile(file); } FileNode *fileNode = node ? node->asFileNode() : nullptr; if (!fileNode) return; Project *project = ProjectTree::projectForNode(fileNode); if (!project) return; CMakeTargetNode *targetNode = dynamic_cast(fileNode->parentProjectNode()); if (!targetNode) return; FilePath filePath = fileNode->filePath(); if (filePath.fileName().contains(".h")) { bool wasHeader = false; const FilePath sourceFile = CppEditor::correspondingHeaderOrSource(filePath, &wasHeader); if (wasHeader && !sourceFile.isEmpty()) filePath = sourceFile; } Target *target = project->activeTarget(); QTC_ASSERT(target, return); const QString generator = CMakeGeneratorKitAspect::generator(target->kit()); const QString relativeSource = filePath.relativeChildPath(targetNode->filePath()).toString(); Utils::FilePath targetBase; BuildConfiguration *bc = target->activeBuildConfiguration(); QTC_ASSERT(bc, return); if (generator == "Ninja") { const Utils::FilePath relativeBuildDir = targetNode->buildDirectory().relativeChildPath( bc->buildDirectory()); targetBase = relativeBuildDir / "CMakeFiles" / (targetNode->displayName() + ".dir"); } else if (!generator.contains("Makefiles")) { Core::MessageManager::writeFlashing(addCMakePrefix( Tr::tr("Build File is not supported for generator \"%1\"").arg(generator))); return; } auto cbc = static_cast(bc->buildSystem()); const QString sourceFile = targetBase.pathAppended(relativeSource).toString(); const QString objExtension = [&]() -> QString { const auto sourceKind = ProjectFile::classify(relativeSource); const QByteArray cmakeLangExtension = ProjectFile::isCxx(sourceKind) ? "CMAKE_CXX_OUTPUT_EXTENSION" : "CMAKE_C_OUTPUT_EXTENSION"; const QString extension = cbc->configurationFromCMake().stringValueOf(cmakeLangExtension); if (!extension.isEmpty()) return extension; const auto toolchain = ProjectFile::isCxx(sourceKind) ? ToolchainKitAspect::cxxToolChain(target->kit()) : ToolchainKitAspect::cToolChain(target->kit()); using namespace ProjectExplorer::Constants; static QSet objIds{ CLANG_CL_TOOLCHAIN_TYPEID, MSVC_TOOLCHAIN_TYPEID, MINGW_TOOLCHAIN_TYPEID, }; if (objIds.contains(toolchain->typeId())) return ".obj"; return ".o"; }(); cbc->buildCMakeTarget(sourceFile + objExtension); } void CMakeManager::buildFileContextMenu() { if (Node *node = ProjectTree::currentNode()) buildFile(node); } } // CMakeProjectManager::Internal