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 | |
16 | using namespace dpfservice; |
17 | |
18 | namespace { |
19 | |
20 | enum_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 | |
28 | QIcon 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 | |
38 | QIcon 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 | |
48 | QIcon 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 | |
61 | const QString kProjectFile = "CMakeLists.txt" ; |
62 | |
63 | CmakeAsynParse::CmakeAsynParse() |
64 | { |
65 | } |
66 | |
67 | CmakeAsynParse::~CmakeAsynParse() |
68 | { |
69 | } |
70 | |
71 | QString 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 | |
130 | QStandardItem *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 | |
233 | QList<CmakeAsynParse::TargetBuild> CmakeAsynParse::parseActions(const QStandardItem *item) |
234 | { |
235 | QList<TargetBuild> ; |
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 | |
258 | QStandardItem *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 | |
275 | QStandardItem *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 | |
301 | QStandardItem *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 | |