1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "cmakeasynparse.h"
6#include "cmakeitemkeeper.h"
7#include "cbp/cbpparser.h"
8#include "services/project/projectgenerator.h"
9#include "services/project/projectservice.h"
10#include "properties/targetsmanager.h"
11#include "common/common.h"
12
13#include <QAction>
14#include <QDebug>
15
16using namespace dpfservice;
17
18namespace {
19
20enum_def(CDT_TARGETS_TYPE, QString)
21{
22 enum_exp Subprojects = "[Subprojects]";
23 enum_exp Targets = "[Targets]";
24 enum_exp Lib = "[lib]";
25 enum_exp Exe = "[exe]";
26};
27
28QIcon cmakeFolderIcon()
29{
30 static QIcon cmakeFolderIcon;
31 if (cmakeFolderIcon.isNull()) {
32 cmakeFolderIcon = CustomIcons::icon(QFileIconProvider::Folder);
33 cmakeFolderIcon.addFile(":/cmakeproject/images/fileoverlay_cmake@2x.png");
34 }
35 return cmakeFolderIcon;
36}
37
38QIcon libBuildIcon()
39{
40 static QIcon libBuildIcon;
41 if (libBuildIcon.isNull()) {
42 libBuildIcon = CustomIcons::icon(CustomIcons::Lib);
43 libBuildIcon.addFile(":/cmakeproject/images/build@2x.png");
44 }
45 return libBuildIcon;
46}
47
48QIcon exeBuildIcon()
49{
50 static QIcon exeBuildIcon;
51 if (exeBuildIcon.isNull()) {
52 exeBuildIcon = CustomIcons::icon(CustomIcons::Exe);
53 // TODO(any):use a different png.
54 exeBuildIcon.addFile(":/cmakeproject/images/build@2x.png");
55 }
56 return exeBuildIcon;
57}
58
59} // namespace
60
61const QString kProjectFile = "CMakeLists.txt";
62
63CmakeAsynParse::CmakeAsynParse()
64{
65}
66
67CmakeAsynParse::~CmakeAsynParse()
68{
69}
70
71QString getTargetRootPath(const QList<QString> &srcFiles, const QString &topPath)
72{
73 if (srcFiles.isEmpty()) {
74 return QString();
75 }
76
77 // Divide all paths into multiple lists according to directory hierarchy
78 QList<QList<QString>> pathPartsList;
79 for (const QString &filePath : srcFiles) {
80 // remove outter file.
81 if ((!topPath.isEmpty() && !filePath.startsWith(topPath)) || QFileInfo(filePath).suffix() == "h" || QFileInfo(filePath).suffix() == "hpp") {
82 continue;
83 }
84
85 QList<QString> pathParts = filePath.split(QDir::separator());
86 pathPartsList.append(pathParts);
87 }
88
89 // Find the shortest path list
90 int minPathLength = INT_MAX;
91 for (const QList<QString> &pathParts : pathPartsList) {
92 if (pathParts.length() < minPathLength) {
93 minPathLength = pathParts.length();
94 }
95 }
96 if (pathPartsList.size() == 0)
97 return {};
98
99 // Starting from the shortest path list, compare whether the paths are the same layer by layer
100 QList<QString> rootPathParts;
101 for (int i = 0; i < minPathLength; i++) {
102 QString currentPart = pathPartsList[0][i];
103 bool allMatched = true;
104 for (int j = 1; j < pathPartsList.length(); j++) {
105 if (pathPartsList[j][i] != currentPart) {
106 allMatched = false;
107 break;
108 }
109 }
110 if (allMatched) {
111 rootPathParts.append(currentPart);
112 } else {
113 break;
114 }
115 }
116
117 // Concatenates matching path parts
118 QString rootPath = rootPathParts.join(QDir::separator());
119 if (rootPath.isEmpty()) {
120 rootPath = QDir::separator();
121 }
122
123 QFileInfo fileInfo(rootPath);
124 if (fileInfo.isFile())
125 rootPath = fileInfo.dir().path();
126
127 return rootPath;
128}
129
130QStandardItem *CmakeAsynParse::parseProject(QStandardItem *rootItem, const dpfservice::ProjectInfo &prjInfo)
131{
132 if (!rootItem)
133 return nullptr;
134
135 TargetsManager::instance()->readTargets(prjInfo.buildFolder(), prjInfo.workspaceFolder());
136 auto cbpParser = TargetsManager::instance()->cbpParser();
137
138 // add cmakefile to tree first.
139 auto cmakeList = cbpParser->getCmakeFileList();
140 for (auto &cmakeFile : cmakeList) {
141 QString cmakeFilePath = cmakeFile.get()->getfilePath();
142 QFileInfo cmakeFileInfo(cmakeFilePath);
143 if (cmakeFileInfo.fileName().toLower() == kProjectFile.toLower()) {
144 auto cmakeParentItem = rootItem;
145 QString relativePath = QDir(prjInfo.workspaceFolder()).relativeFilePath(cmakeFileInfo.dir().path());
146 if (!relativePath.isEmpty() && relativePath != ".") {
147 cmakeParentItem = createParentItem(rootItem, relativePath);
148 }
149 auto cmakeFileItem = new QStandardItem();
150 cmakeFileItem->setIcon(CustomIcons::icon(cmakeFileInfo));
151 cmakeFileItem->setText(cmakeFileInfo.fileName());
152 cmakeFileItem->setToolTip(cmakeFileInfo.filePath());
153 cmakeParentItem->appendRow(cmakeFileItem);
154
155 // monitor cmake file change to refresh project tree.
156 if (cmakeParentItem == rootItem) {
157 CmakeItemKeeper::instance()->addCmakeRootFile(rootItem, cmakeFilePath);
158 } else {
159 CmakeItemKeeper::instance()->addCmakeSubFiles(rootItem, {cmakeFilePath});
160 }
161 }
162 }
163
164 QSet<QString> allFiles {};
165 const QList<CMakeBuildTarget> &targets = cbpParser->getBuildTargets();
166 for (auto target : targets) {
167 if (target.type == kUtility) {
168 continue;
169 }
170 QString targetRootPath = getTargetRootPath(target.srcfiles, target.sourceDirectory);
171 QString relativePath = QDir(prjInfo.workspaceFolder()).relativeFilePath(QDir(targetRootPath).path());
172 QStandardItem *targetRootItem = rootItem;
173 if (!relativePath.isEmpty() && relativePath != ".") {
174 targetRootItem = createParentItem(rootItem, relativePath);
175 }
176 QStandardItem *targetItem = new QStandardItem();
177 QString prefix = "";
178 if (target.type == kExecutable) {
179 prefix = CDT_TARGETS_TYPE::get()->Exe;
180 targetItem->setIcon(::exeBuildIcon());
181 } else if (target.type == kStaticLibrary || target.type == kDynamicLibrary) {
182 prefix = CDT_TARGETS_TYPE::get()->Lib;
183 targetItem->setIcon(::libBuildIcon());
184 }
185 QString title = prefix + target.title;
186 targetItem->setText(title);
187
188 targetItem->setData(QVariant::fromValue(target));
189 targetRootItem->appendRow(targetItem);
190
191 for (const auto &src : target.srcfiles) {
192 QFileInfo srcFileInfo(src);
193 relativePath = QDir(targetRootPath).relativeFilePath(srcFileInfo.dir().path());
194 relativePath.remove(".");
195 if (relativePath.startsWith("/"))
196 relativePath.remove(0, 1);
197 if (srcFileInfo.suffix() == "qm" || srcFileInfo.fileName().startsWith("moc_")
198 || srcFileInfo.fileName().startsWith("mocs_")
199 || srcFileInfo.fileName().startsWith("qrc_") || srcFileInfo.fileName().startsWith("ui_")) {
200 continue;
201 }
202
203 QString upDirName = relativePath.split("/").last();
204 QStandardItem *parentItem = findItem(targetItem, upDirName, relativePath);
205 if (!parentItem) {
206 parentItem = createParentItem(targetItem, relativePath);
207 }
208
209 QStandardItem *srcItem = new QStandardItem();
210 srcItem->setText(srcFileInfo.fileName());
211 srcItem->setToolTip(srcFileInfo.filePath());
212 srcItem->setIcon(CustomIcons::icon(srcFileInfo));
213
214 parentItem->appendRow(srcItem);
215
216 allFiles.insert(src);
217 }
218 }
219
220 ProjectInfo tempInfo = prjInfo;
221 if (tempInfo.runProgram().isEmpty()) {
222 auto activeExecTarget = TargetsManager::instance()->
223 getActivedTargetByTargetType(dpfservice::TargetType::kActiveExecTarget);
224 tempInfo.setRunProgram(activeExecTarget.output);
225 tempInfo.setRunWorkspaceDir(activeExecTarget.workingDir);
226 }
227 tempInfo.setSourceFiles(allFiles);
228 ProjectInfo::set(rootItem, tempInfo);
229 emit parseProjectEnd({ rootItem, true });
230 return rootItem;
231}
232
233QList<CmakeAsynParse::TargetBuild> CmakeAsynParse::parseActions(const QStandardItem *item)
234{
235 QList<TargetBuild> buildMenuList;
236
237 if (!item)
238 return {};
239
240 CMakeBuildTarget buildTarget = item->data().value<CMakeBuildTarget>();
241 QStringList commandItems = buildTarget.makeCommand.split(" ");
242 commandItems.removeAll("");
243 if (commandItems.isEmpty())
244 return {};
245
246 TargetBuild build;
247 build.buildName = tr("build");
248 build.buildCommand = commandItems.first();
249 commandItems.pop_front();
250 build.buildArguments = commandItems.join(" ");
251 build.buildTarget = QFileInfo(buildTarget.output).dir().path();
252 buildMenuList.push_back(build);
253
254 parseActionsEnd({ buildMenuList, true });
255 return buildMenuList;
256}
257
258QStandardItem *CmakeAsynParse::findParentItem(QStandardItem *rootItem, QString &name)
259{
260 if (!rootItem) {
261 return nullptr;
262 }
263 for (int row = 0; row < rootItem->rowCount(); row++) {
264 QStandardItem *childItem = rootItem->child(row);
265 QString childDisplayText = childItem->data(Qt::DisplayRole).toString();
266 if (name.startsWith(childDisplayText + "/")) {
267 QString childName = name;
268 childName.remove(0, childDisplayText.size() + 1);
269 return findParentItem(childItem, childName);
270 }
271 }
272 return rootItem;
273}
274
275QStandardItem *CmakeAsynParse::createParentItem(QStandardItem *rootItem, QString &relativeName)
276{
277 QStandardItem *retItem = nullptr;
278 QStringList nameItems = relativeName.split("/");
279 QString preItems;
280 for (auto nameItem : nameItems) {
281 QString relative = preItems + nameItem;
282 QStandardItem *item = findItem(rootItem, nameItem, relative);
283 if (!item) {
284 // create new one.
285 item = new QStandardItem();
286 item->setText(nameItem);
287 item->setToolTip(relative);
288 item->setIcon(::cmakeFolderIcon());
289 // append to parent.
290 QStandardItem *parentItem = findParentItem(rootItem, relative);
291 QString test = parentItem->text();
292 parentItem->appendRow(item);
293 }
294 preItems += nameItem + "/";
295 retItem = item;
296 }
297
298 return retItem;
299}
300
301QStandardItem *CmakeAsynParse::findItem(QStandardItem *rootItem, QString &name, QString &relativePath)
302{
303 if (!rootItem) {
304 return nullptr;
305 }
306
307 if (relativePath.isEmpty())
308 return rootItem;
309
310 QStandardItem *parentItem = findParentItem(rootItem, relativePath);
311 if (parentItem) {
312 for (int row = 0; row < parentItem->rowCount(); row++) {
313 QStandardItem *childItem = parentItem->child(row);
314 qInfo() << parentItem->data(Qt::DisplayRole) << childItem->data(Qt::DisplayRole);
315 if (childItem->data(Qt::DisplayRole) == name) {
316 return childItem;
317 }
318 }
319 }
320 return nullptr;
321}
322