| 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 | |