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