1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
2 | // |
3 | // SPDX-License-Identifier: GPL-3.0-or-later |
4 | |
5 | #include "cmakeprojectgenerator.h" |
6 | #include "cmakeasynparse.h" |
7 | #include "cmakeitemkeeper.h" |
8 | #include "cmake/project/transceiver/projectcmakereceiver.h" |
9 | #include "properties/buildpropertywidget.h" |
10 | #include "properties/runpropertywidget.h" |
11 | #include "properties/configpropertywidget.h" |
12 | #include "properties/configutil.h" |
13 | #include "properties/targetsmanager.h" |
14 | #include "services/builder/builderservice.h" |
15 | #include "services/window/windowservice.h" |
16 | #include "common/dialog/propertiesdialog.h" |
17 | #include "common/util/eventdefinitions.h" |
18 | |
19 | #include <QtXml> |
20 | #include <QFileIconProvider> |
21 | #include <QPushButton> |
22 | |
23 | class CmakeProjectGeneratorPrivate |
24 | { |
25 | friend class CmakeProjectGenerator; |
26 | |
27 | enum CreateItemMode { |
28 | NewCreateProject, |
29 | RebuildProject, |
30 | }; |
31 | |
32 | QHash<QStandardItem *, QThreadPool *> asynItemThreadPolls; |
33 | QList<QStandardItem *> reloadCmakeFileItems; |
34 | dpfservice::ProjectInfo configureProjectInfo; |
35 | }; |
36 | |
37 | CmakeProjectGenerator::CmakeProjectGenerator() |
38 | : d(new CmakeProjectGeneratorPrivate()) |
39 | { |
40 | // when execute command end can create root Item |
41 | QObject::connect(ProjectCmakeProxy::instance(), |
42 | &ProjectCmakeProxy::buildExecuteEnd, |
43 | this, &CmakeProjectGenerator::doBuildCmdExecuteEnd); |
44 | |
45 | // main thread init watcher class |
46 | CmakeItemKeeper::instance(); |
47 | |
48 | // build cmake file changed notify |
49 | QObject::connect(CmakeItemKeeper::instance(), |
50 | &CmakeItemKeeper::cmakeFileNodeNotify, |
51 | this, &CmakeProjectGenerator::doCmakeFileNodeChanged); |
52 | |
53 | using namespace dpfservice; |
54 | auto &ctx = dpfInstance.serviceContext(); |
55 | BuilderService *builderService = ctx.service<BuilderService>(BuilderService::name()); |
56 | if (!builderService) { |
57 | qCritical() << "Failed, not found service : builderService" ; |
58 | abort(); |
59 | } |
60 | } |
61 | |
62 | CmakeProjectGenerator::~CmakeProjectGenerator() |
63 | { |
64 | qInfo() << __FUNCTION__; |
65 | for (auto val : d->asynItemThreadPolls.keys()) { |
66 | auto threadPoll = d->asynItemThreadPolls[val]; |
67 | if (threadPoll) { |
68 | threadPoll->clear(); |
69 | while (threadPoll->activeThreadCount() != 0) {} |
70 | delete threadPoll; |
71 | } |
72 | } |
73 | |
74 | d->asynItemThreadPolls.clear(); |
75 | |
76 | if (d) |
77 | delete d; |
78 | } |
79 | |
80 | QStringList CmakeProjectGenerator::supportLanguages() |
81 | { |
82 | return { dpfservice::MWMFA_CXX }; |
83 | } |
84 | |
85 | QStringList CmakeProjectGenerator::supportFileNames() |
86 | { |
87 | return { "cmakelists.txt" , "CMakeLists.txt" }; |
88 | } |
89 | |
90 | QDialog *CmakeProjectGenerator::configureWidget(const QString &language, |
91 | const QString &workspace) |
92 | { |
93 | ProjectGenerator::configureWidget(language, workspace); |
94 | |
95 | config::ConfigureParam *param = config::ConfigUtil::instance()->getConfigureParamPointer(); |
96 | if (!config::ConfigUtil::instance()->isNeedConfig(workspace, *param)) { |
97 | dpfservice::ProjectInfo info; |
98 | if (config::ConfigUtil::instance()->updateProjectInfo(info, param)) { |
99 | configure(info); |
100 | return nullptr; |
101 | } |
102 | } |
103 | |
104 | // show build type config pane. |
105 | ConfigPropertyWidget *configPropertyWidget = new ConfigPropertyWidget(language, workspace); |
106 | QObject::connect(config::ConfigUtil::instance(), &config::ConfigUtil::configureDone, |
107 | [this](const dpfservice::ProjectInfo &info) { |
108 | configure(info); |
109 | }); |
110 | |
111 | return configPropertyWidget; |
112 | } |
113 | |
114 | bool CmakeProjectGenerator::configure(const dpfservice::ProjectInfo &projInfo) |
115 | { |
116 | using namespace dpfservice; |
117 | auto &ctx = dpfInstance.serviceContext(); |
118 | BuilderService *builderService = ctx.service<BuilderService>(BuilderService::name()); |
119 | if (builderService) { |
120 | |
121 | |
122 | BuildCommandInfo commandInfo; |
123 | commandInfo.kitName = projInfo.kitName(); |
124 | commandInfo.program = projInfo.buildProgram(); |
125 | commandInfo.arguments = projInfo.configCustomArgs(); |
126 | commandInfo.workingDir = projInfo.workspaceFolder(); |
127 | |
128 | bool isSuccess = builderService->interface.builderCommand({commandInfo}, false); |
129 | if (isSuccess) { |
130 | ProjectCmakeProxy::instance()->setBuildCommandUuid(commandInfo.uuid); |
131 | // display root item before everything is done. |
132 | rootItem = ProjectGenerator::createRootItem(projInfo); |
133 | setRootItemToView(rootItem); |
134 | |
135 | dpfservice::ProjectGenerator::configure(projInfo); |
136 | |
137 | // cache project info, asyn end to use |
138 | d->configureProjectInfo = projInfo; |
139 | |
140 | return true; |
141 | } |
142 | } |
143 | return false; |
144 | } |
145 | |
146 | QStandardItem *CmakeProjectGenerator::createRootItem(const dpfservice::ProjectInfo &info) |
147 | { |
148 | using namespace dpfservice; |
149 | |
150 | d->asynItemThreadPolls[rootItem] = new QThreadPool; |
151 | |
152 | auto parse = new CmakeAsynParse; |
153 | |
154 | // asyn free parse, that .project file parse |
155 | QObject::connect(parse, &CmakeAsynParse::parseProjectEnd, |
156 | [=](CmakeAsynParse::ParseInfo<QStandardItem *> parseInfo) { |
157 | d->asynItemThreadPolls.remove(parseInfo.result); |
158 | // active after everything done. |
159 | project.activeProject(info.kitName(), info.language(), info.workspaceFolder()); |
160 | delete parse; |
161 | }); |
162 | |
163 | // asyn execute logic, that .project file parse |
164 | QtConcurrent::run(d->asynItemThreadPolls[rootItem], |
165 | parse, &CmakeAsynParse::parseProject, |
166 | rootItem, info); |
167 | |
168 | return rootItem; |
169 | } |
170 | |
171 | void CmakeProjectGenerator::removeRootItem(QStandardItem *root) |
172 | { |
173 | // remove watcher from current root item |
174 | CmakeItemKeeper::instance()->delCmakeFileNode(root); |
175 | |
176 | auto threadPoll = d->asynItemThreadPolls[root]; |
177 | if (threadPoll) { |
178 | threadPoll->clear(); |
179 | while(threadPoll->waitForDone()); |
180 | delete threadPoll; |
181 | d->asynItemThreadPolls.remove(root); |
182 | } |
183 | |
184 | recursionRemoveItem(root); |
185 | } |
186 | |
187 | QMenu *CmakeProjectGenerator::createItemMenu(const QStandardItem *item) |
188 | { |
189 | QMenu * = nullptr; |
190 | |
191 | // create parse |
192 | CmakeAsynParse *parse = new CmakeAsynParse(); |
193 | |
194 | // create item from syn |
195 | auto targetBuilds = parse->parseActions(item); |
196 | |
197 | // free parse from syn |
198 | delete parse; |
199 | |
200 | auto root = ProjectGenerator::root(const_cast<QStandardItem *>(item)); |
201 | if (!root) |
202 | return menu; |
203 | |
204 | if (!targetBuilds.isEmpty()) { |
205 | menu = new QMenu(); |
206 | for (auto val : targetBuilds) { |
207 | QAction *action = new QAction(); |
208 | action->setText(val.buildName); |
209 | action->setProperty("workDir" , dpfservice::ProjectInfo::get(root).workspaceFolder()); |
210 | action->setProperty(CDT_CPROJECT_KEY::get()->buildCommand.toLatin1(), val.buildCommand); |
211 | action->setProperty(CDT_CPROJECT_KEY::get()->buildArguments.toLatin1(), val.buildArguments); |
212 | action->setProperty(CDT_CPROJECT_KEY::get()->buildTarget.toLatin1(), val.buildTarget); |
213 | action->setProperty(CDT_CPROJECT_KEY::get()->stopOnError.toLatin1(), val.stopOnError); |
214 | action->setProperty(CDT_CPROJECT_KEY::get()->useDefaultCommand.toLatin1(), val.useDefaultCommand); |
215 | QObject::connect(action, &QAction::triggered, this, &CmakeProjectGenerator::actionTriggered, Qt::UniqueConnection); |
216 | menu->addAction(action); |
217 | } |
218 | } |
219 | |
220 | if (!menu) { |
221 | menu = new QMenu(); |
222 | } |
223 | |
224 | QAction *action = new QAction(tr("Properties" )); |
225 | menu->addAction(action); |
226 | dpfservice::ProjectInfo info = dpfservice::ProjectInfo::get(item); |
227 | QObject::connect(action, &QAction::triggered, [=]() { |
228 | actionProperties(info, root); |
229 | }); |
230 | |
231 | return menu; |
232 | } |
233 | |
234 | void CmakeProjectGenerator::actionTriggered() |
235 | { |
236 | using namespace dpfservice; |
237 | QAction *action = qobject_cast<QAction *>(sender()); |
238 | if (action) { |
239 | QString program = action->property(CDT_CPROJECT_KEY::get()->buildCommand.toLatin1()).toString(); |
240 | QStringList args = action->property(CDT_CPROJECT_KEY::get()->buildArguments.toLatin1()).toString().split(" " ); |
241 | args << action->property(CDT_CPROJECT_KEY::get()->buildTarget.toLatin1()).toString(); |
242 | QString workDir = action->property("workDir" ).toString(); |
243 | |
244 | // remove extra quotes and empty argument. |
245 | QStringList argsFiltered; |
246 | for (auto &arg : args) { |
247 | if (!arg.isEmpty()) { |
248 | argsFiltered << arg.replace("\"" , "" ); |
249 | } |
250 | } |
251 | |
252 | using namespace dpfservice; |
253 | auto &ctx = dpfInstance.serviceContext(); |
254 | BuilderService *builderService = ctx.service<BuilderService>(BuilderService::name()); |
255 | if (builderService) { |
256 | BuildCommandInfo commandInfo; |
257 | commandInfo.kitName = CmakeProjectGenerator::toolKitName(); |
258 | commandInfo.program = program; |
259 | commandInfo.arguments = args; |
260 | commandInfo.workingDir = workDir; |
261 | builderService->interface.builderCommand({commandInfo}, false); |
262 | } |
263 | } |
264 | } |
265 | |
266 | void CmakeProjectGenerator::setRootItemToView(QStandardItem *root) |
267 | { |
268 | d->asynItemThreadPolls.remove(root); |
269 | |
270 | using namespace dpfservice; |
271 | auto &ctx = dpfInstance.serviceContext(); |
272 | ProjectService *projectService = ctx.service<ProjectService>(ProjectService::name()); |
273 | if (!projectService) |
274 | return; |
275 | |
276 | WindowService *windowService = ctx.service<WindowService>(WindowService::name()); |
277 | if (!windowService) |
278 | return; |
279 | |
280 | if (root) { |
281 | // setting item to view |
282 | if (projectService->projectView.addRootItem) |
283 | projectService->projectView.addRootItem(root); |
284 | |
285 | // expand view from tree two level |
286 | if (projectService->projectView.expandedDepth) |
287 | projectService->projectView.expandedDepth(root, 2); |
288 | |
289 | navigation.doSwitch(MWNA_EDIT); |
290 | editor.switchWorkspace(MWCWT_PROJECTS); |
291 | } |
292 | } |
293 | |
294 | void CmakeProjectGenerator::doBuildCmdExecuteEnd(const BuildCommandInfo &info, int status) |
295 | { |
296 | // configure function cached info |
297 | if (d->configureProjectInfo.isEmpty()) |
298 | return; |
299 | |
300 | using namespace dpfservice; |
301 | auto &ctx = dpfInstance.serviceContext(); |
302 | ProjectService *projectService = ctx.service<ProjectService>(ProjectService::name()); |
303 | if (!projectService) |
304 | return; |
305 | |
306 | // get reload item from reload cmake file cache |
307 | mutex.lock(); |
308 | QStandardItem *reloadItem = nullptr; |
309 | for (auto val : d->reloadCmakeFileItems) { |
310 | if (info.workingDir == d->configureProjectInfo.workspaceFolder()) { |
311 | reloadItem = val; |
312 | break; |
313 | } |
314 | } |
315 | mutex.unlock(); |
316 | |
317 | if (reloadItem) { |
318 | d->reloadCmakeFileItems.removeOne(reloadItem); //clean cache |
319 | if (status == 0) { |
320 | projectService->projectView.removeRootItem(reloadItem); |
321 | createRootItem(d->configureProjectInfo); |
322 | } else { |
323 | qCritical() << "Failed execute cmd : " |
324 | << info.program |
325 | << info.arguments.join(" " ) |
326 | << "status : " << status; |
327 | } |
328 | } else { |
329 | createRootItem(d->configureProjectInfo); |
330 | } |
331 | emit projectService->projectConfigureDone(QString()); |
332 | } |
333 | |
334 | void CmakeProjectGenerator::doCmakeFileNodeChanged(QStandardItem *root, const QPair<QString, QStringList> &files) |
335 | { |
336 | Q_UNUSED(files) |
337 | |
338 | if (d->reloadCmakeFileItems.contains(root)) |
339 | return; |
340 | |
341 | qInfo() << __FUNCTION__; |
342 | using namespace dpfservice; |
343 | |
344 | // get current project info |
345 | auto proInfo = ProjectInfo::get(root); |
346 | |
347 | // cache the reload item |
348 | d->reloadCmakeFileItems.append(root); |
349 | |
350 | // reconfigure project info |
351 | configure(proInfo); |
352 | } |
353 | |
354 | void CmakeProjectGenerator::actionProperties(const dpfservice::ProjectInfo &info, QStandardItem *item) |
355 | { |
356 | PropertiesDialog dlg; |
357 | |
358 | BuildPropertyWidget *buildWidget = new BuildPropertyWidget(info); |
359 | RunPropertyWidget *runWidget = new RunPropertyWidget(info, item); |
360 | |
361 | dlg.insertPropertyPanel(tr("Build" ), buildWidget); |
362 | dlg.insertPropertyPanel(tr("Run" ), runWidget); |
363 | |
364 | dlg.exec(); |
365 | } |
366 | |
367 | void CmakeProjectGenerator::recursionRemoveItem(QStandardItem *item) |
368 | { |
369 | if (!item) |
370 | return; |
371 | |
372 | for (int row = 0; row < item->rowCount(); row++) { |
373 | auto child = item->takeChild(row); |
374 | if (!child->hasChildren()) { |
375 | delete child; |
376 | } else { |
377 | recursionRemoveItem(child); |
378 | } |
379 | } |
380 | |
381 | delete item; |
382 | return; |
383 | } |
384 | |