1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "cmakeprojectmanager.h"
27 
28 #include "cmakebuildsystem.h"
29 #include "cmakekitinformation.h"
30 #include "cmakeproject.h"
31 #include "cmakeprojectconstants.h"
32 #include "cmakeprojectnodes.h"
33 #include "fileapiparser.h"
34 
35 #include <coreplugin/actionmanager/actioncontainer.h>
36 #include <coreplugin/actionmanager/actionmanager.h>
37 #include <coreplugin/editormanager/editormanager.h>
38 #include <coreplugin/editormanager/ieditor.h>
39 #include <coreplugin/icore.h>
40 #include <coreplugin/messagemanager.h>
41 #include <projectexplorer/buildmanager.h>
42 #include <projectexplorer/projectexplorer.h>
43 #include <projectexplorer/projectexplorerconstants.h>
44 #include <projectexplorer/projecttree.h>
45 #include <projectexplorer/session.h>
46 #include <projectexplorer/target.h>
47 
48 #include <utils/parameteraction.h>
49 
50 #include <QAction>
51 #include <QFileDialog>
52 #include <QMessageBox>
53 
54 using namespace ProjectExplorer;
55 using namespace CMakeProjectManager::Internal;
56 
CMakeManager()57 CMakeManager::CMakeManager()
58     : m_runCMakeAction(new QAction(QIcon(), tr("Run CMake"), this))
59     , m_clearCMakeCacheAction(new QAction(QIcon(), tr("Clear CMake Configuration"), this))
60     , m_runCMakeActionContextMenu(new QAction(QIcon(), tr("Run CMake"), this))
61     , m_rescanProjectAction(new QAction(QIcon(), tr("Rescan Project"), this))
62 {
63     Core::ActionContainer *mbuild =
64             Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_BUILDPROJECT);
65     Core::ActionContainer *mproject =
66             Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_PROJECTCONTEXT);
67     Core::ActionContainer *msubproject =
68             Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_SUBPROJECTCONTEXT);
69     Core::ActionContainer *mfile =
70             Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_FILECONTEXT);
71 
72     const Core::Context projectContext(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
73     const Core::Context globalContext(Core::Constants::C_GLOBAL);
74 
75     Core::Command *command = Core::ActionManager::registerAction(m_runCMakeAction,
76                                                                  Constants::RUN_CMAKE,
77                                                                  globalContext);
78     command->setAttribute(Core::Command::CA_Hide);
79     mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD);
80     connect(m_runCMakeAction, &QAction::triggered, [this]() {
81         runCMake(SessionManager::startupBuildSystem());
82     });
83 
84     command = Core::ActionManager::registerAction(m_clearCMakeCacheAction,
85                                                   Constants::CLEAR_CMAKE_CACHE,
86                                                   globalContext);
87     command->setAttribute(Core::Command::CA_Hide);
88     mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD);
89     connect(m_clearCMakeCacheAction, &QAction::triggered, [this]() {
90         clearCMakeCache(SessionManager::startupBuildSystem());
91     });
92 
93     command = Core::ActionManager::registerAction(m_runCMakeActionContextMenu,
94                                                   Constants::RUN_CMAKE_CONTEXT_MENU,
95                                                   projectContext);
96     command->setAttribute(Core::Command::CA_Hide);
97     mproject->addAction(command, ProjectExplorer::Constants::G_PROJECT_BUILD);
98     msubproject->addAction(command, ProjectExplorer::Constants::G_PROJECT_BUILD);
99     connect(m_runCMakeActionContextMenu, &QAction::triggered, [this]() {
100         runCMake(ProjectTree::currentBuildSystem());
101     });
102 
103     m_buildFileContextMenu = new QAction(tr("Build"), this);
104     command = Core::ActionManager::registerAction(m_buildFileContextMenu,
105                                                   Constants::BUILD_FILE_CONTEXT_MENU,
106                                                   projectContext);
107     command->setAttribute(Core::Command::CA_Hide);
108     mfile->addAction(command, ProjectExplorer::Constants::G_FILE_OTHER);
109     connect(m_buildFileContextMenu, &QAction::triggered,
110             this, &CMakeManager::buildFileContextMenu);
111 
112     command = Core::ActionManager::registerAction(m_rescanProjectAction,
113                                                   Constants::RESCAN_PROJECT,
114                                                   globalContext);
115     command->setAttribute(Core::Command::CA_Hide);
116     mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD);
117     connect(m_rescanProjectAction, &QAction::triggered, [this]() {
118         rescanProject(ProjectTree::currentBuildSystem());
119     });
120 
121     m_buildFileAction = new Utils::ParameterAction(tr("Build File"),
122                                                    tr("Build File \"%1\""),
123                                                    Utils::ParameterAction::AlwaysEnabled,
124                                                    this);
125     command = Core::ActionManager::registerAction(m_buildFileAction, Constants::BUILD_FILE);
126     command->setAttribute(Core::Command::CA_Hide);
127     command->setAttribute(Core::Command::CA_UpdateText);
128     command->setDescription(m_buildFileAction->text());
129     command->setDefaultKeySequence(QKeySequence(tr("Ctrl+Alt+B")));
130     mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD);
131     connect(m_buildFileAction, &QAction::triggered, this, [this] { buildFile(); });
132 
133     connect(SessionManager::instance(), &SessionManager::startupProjectChanged, this, [this] {
134         updateCmakeActions(ProjectTree::currentNode());
135     });
136     connect(BuildManager::instance(), &BuildManager::buildStateChanged, this, [this] {
137         updateCmakeActions(ProjectTree::currentNode());
138     });
139     connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
140             this, &CMakeManager::updateBuildFileAction);
141     connect(ProjectTree::instance(), &ProjectTree::currentNodeChanged,
142             this, &CMakeManager::updateCmakeActions);
143 
144     updateCmakeActions(ProjectTree::currentNode());
145 }
146 
updateCmakeActions(Node * node)147 void CMakeManager::updateCmakeActions(Node *node)
148 {
149     auto project = qobject_cast<CMakeProject *>(SessionManager::startupProject());
150     const bool visible = project && !BuildManager::isBuilding(project);
151     m_runCMakeAction->setVisible(visible);
152     m_clearCMakeCacheAction->setVisible(visible);
153     m_rescanProjectAction->setVisible(visible);
154     enableBuildFileMenus(node);
155 }
156 
clearCMakeCache(BuildSystem * buildSystem)157 void CMakeManager::clearCMakeCache(BuildSystem *buildSystem)
158 {
159     auto cmakeBuildSystem = dynamic_cast<CMakeBuildSystem *>(buildSystem);
160     QTC_ASSERT(cmakeBuildSystem, return);
161 
162     cmakeBuildSystem->clearCMakeCache();
163 }
164 
runCMake(BuildSystem * buildSystem)165 void CMakeManager::runCMake(BuildSystem *buildSystem)
166 {
167     auto cmakeBuildSystem = dynamic_cast<CMakeBuildSystem *>(buildSystem);
168     QTC_ASSERT(cmakeBuildSystem, return );
169 
170     if (ProjectExplorerPlugin::saveModifiedFiles())
171         cmakeBuildSystem->runCMake();
172 }
173 
rescanProject(BuildSystem * buildSystem)174 void CMakeManager::rescanProject(BuildSystem *buildSystem)
175 {
176     auto cmakeBuildSystem = dynamic_cast<CMakeBuildSystem *>(buildSystem);
177     QTC_ASSERT(cmakeBuildSystem, return);
178 
179     cmakeBuildSystem->runCMakeAndScanProjectTree();// by my experience: every rescan run requires cmake run too
180 }
181 
updateBuildFileAction()182 void CMakeManager::updateBuildFileAction()
183 {
184     Node *node = nullptr;
185     if (Core::IDocument *currentDocument = Core::EditorManager::currentDocument())
186         node = ProjectTree::nodeForFile(currentDocument->filePath());
187     enableBuildFileMenus(node);
188 }
189 
enableBuildFileMenus(Node * node)190 void CMakeManager::enableBuildFileMenus(Node *node)
191 {
192     m_buildFileAction->setVisible(false);
193     m_buildFileAction->setEnabled(false);
194     m_buildFileAction->setParameter(QString());
195     m_buildFileContextMenu->setEnabled(false);
196 
197     if (!node)
198         return;
199     Project *project = ProjectTree::projectForNode(node);
200     if (!project)
201         return;
202     Target *target = project->activeTarget();
203     if (!target)
204         return;
205     const QString generator = CMakeGeneratorKitAspect::generator(target->kit());
206     if (generator != "Ninja" && !generator.contains("Makefiles"))
207         return;
208 
209     if (const FileNode *fileNode = node->asFileNode()) {
210         const FileType type = fileNode->fileType();
211         const bool visible = qobject_cast<CMakeProject *>(project)
212                 && dynamic_cast<CMakeTargetNode *>(node->parentProjectNode())
213                 && (type == FileType::Source || type == FileType::Header);
214 
215         const bool enabled = visible && !BuildManager::isBuilding(project);
216         m_buildFileAction->setVisible(visible);
217         m_buildFileAction->setEnabled(enabled);
218         m_buildFileAction->setParameter(node->filePath().fileName());
219         m_buildFileContextMenu->setEnabled(enabled);
220     }
221 }
222 
buildFile(Node * node)223 void CMakeManager::buildFile(Node *node)
224 {
225     if (!node) {
226         Core::IDocument *currentDocument= Core::EditorManager::currentDocument();
227         if (!currentDocument)
228             return;
229         const Utils::FilePath file = currentDocument->filePath();
230         node = ProjectTree::nodeForFile(file);
231     }
232     FileNode *fileNode  = node ? node->asFileNode() : nullptr;
233     if (!fileNode)
234         return;
235     Project *project = ProjectTree::projectForNode(fileNode);
236     if (!project)
237         return;
238     CMakeTargetNode *targetNode = dynamic_cast<CMakeTargetNode *>(fileNode->parentProjectNode());
239     if (!targetNode)
240         return;
241     Target *target = project->activeTarget();
242     QTC_ASSERT(target, return);
243     const QString generator = CMakeGeneratorKitAspect::generator(target->kit());
244     const QString relativeSource = fileNode->filePath().relativeChildPath(targetNode->filePath()).toString();
245     const QString objExtension = Utils::HostOsInfo::isWindowsHost() ? QString(".obj") : QString(".o");
246     Utils::FilePath targetBase;
247     BuildConfiguration *bc = target->activeBuildConfiguration();
248     QTC_ASSERT(bc, return);
249     if (generator == "Ninja") {
250         const Utils::FilePath relativeBuildDir = targetNode->buildDirectory().relativeChildPath(
251                     bc->buildDirectory());
252         targetBase = relativeBuildDir / "CMakeFiles" / (targetNode->displayName() + ".dir");
253     } else if (!generator.contains("Makefiles")) {
254         Core::MessageManager::writeFlashing(
255             tr("Build File is not supported for generator \"%1\"").arg(generator));
256         return;
257     }
258 
259     static_cast<CMakeBuildSystem *>(bc->buildSystem())
260             ->buildCMakeTarget(targetBase.pathAppended(relativeSource).toString() + objExtension);
261 }
262 
buildFileContextMenu()263 void CMakeManager::buildFileContextMenu()
264 {
265     if (Node *node = ProjectTree::currentNode())
266         buildFile(node);
267 }
268