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
23class 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
37CmakeProjectGenerator::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
62CmakeProjectGenerator::~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
80QStringList CmakeProjectGenerator::supportLanguages()
81{
82 return { dpfservice::MWMFA_CXX };
83}
84
85QStringList CmakeProjectGenerator::supportFileNames()
86{
87 return { "cmakelists.txt", "CMakeLists.txt" };
88}
89
90QDialog *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
114bool 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
146QStandardItem *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
171void 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
187QMenu *CmakeProjectGenerator::createItemMenu(const QStandardItem *item)
188{
189 QMenu *menu = 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
234void 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
266void 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
294void 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
334void 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
354void 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
367void 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