File indexing completed on 2024-05-19 04:48:23
0001 /* This file is part of KDevelop 0002 0003 Copyright 2020 Milian Wolff <mail@milianw.de> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Library General Public 0007 License as published by the Free Software Foundation; either 0008 version 2 of the License, or (at your option) any later version. 0009 0010 This library is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Library General Public License for more details. 0014 0015 You should have received a copy of the GNU Library General Public License 0016 along with this library; see the file COPYING.LIB. If not, write to 0017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "cmakeapi.h" 0022 #include "cmakedata.h" 0023 0024 #include <QDebug> 0025 #include <QDir> 0026 #include <QFile> 0027 #include <QFileInfo> 0028 #include <QJsonDocument> 0029 #include <QJsonObject> 0030 #include <QJsonArray> 0031 #include <QVersionNumber> 0032 0033 namespace { 0034 QUrl toCanonical(const QUrl& path) 0035 { 0036 QFileInfo info(path.toString()); 0037 if (!info.exists()) 0038 return path; 0039 const auto canonical = info.canonicalFilePath(); 0040 if (info.filePath() != canonical) { 0041 return QUrl(canonical); 0042 } else { 0043 return path; 0044 } 0045 } 0046 0047 QJsonObject parseFile(const QString& path) 0048 { 0049 QFile file(path); 0050 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 0051 qWarning() << "failed to read json file" << path << file.errorString(); 0052 return {}; 0053 } 0054 QJsonParseError error; 0055 const auto document = QJsonDocument::fromJson(file.readAll(), &error); 0056 if (error.error) { 0057 qWarning() << "failed to parse json file" << path << error.errorString() << error.offset; 0058 return {}; 0059 } 0060 return document.object(); 0061 } 0062 0063 bool isStrikeClientResponse(const QJsonObject& indexObject) 0064 { 0065 return indexObject.value(QLatin1String("reply")).toObject().contains(QLatin1String("client-strike")); 0066 } 0067 } 0068 0069 namespace CMake 0070 { 0071 namespace FileApi 0072 { 0073 0074 static QUrl resolvedUrl(const QUrl &base, const QString &relative) 0075 { 0076 if(QUrl(relative).isRelative()) 0077 { 0078 return QUrl(base.toString() + "/" + relative).adjusted(QUrl::NormalizePathSegments); 0079 }else 0080 { 0081 return QUrl(relative); 0082 } 0083 } 0084 0085 void writeClientQueryFile(const QString& buildDirectory) 0086 { 0087 const QDir queryDir(buildDirectory + QLatin1String("/.cmake/api/v1/query/client-strike/")); 0088 if (!queryDir.exists() && !queryDir.mkpath(QStringLiteral("."))) { 0089 qWarning() << "failed to create file API query dir:" << queryDir.absolutePath(); 0090 return; 0091 } 0092 0093 QFile queryFile(queryDir.absoluteFilePath(QStringLiteral("query.json"))); 0094 if (!queryFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 0095 qWarning() << "failed to open query file for writing:" << queryFile.fileName() << queryFile.errorString(); 0096 return; 0097 } 0098 0099 queryFile.write(R"({"requests": [{"kind": "codemodel", "version": 2}, {"kind": "cmakeFiles", "version": 1}]})"); 0100 } 0101 0102 static QDir toReplyDir(const QString& buildDirectory) 0103 { 0104 QDir replyDir(buildDirectory + QLatin1String("/.cmake/api/v1/reply/")); 0105 if (!replyDir.exists()) { 0106 qWarning() << "cmake-file-api reply directory does not exist:" << replyDir.path(); 0107 } 0108 return replyDir; 0109 } 0110 0111 QJsonObject findReplyIndexFile(const QString& buildDirectory) 0112 { 0113 const auto replyDir = toReplyDir(buildDirectory); 0114 for (const auto& entry : replyDir.entryInfoList({QStringLiteral("index-*.json")}, QDir::Files, QDir::Name | QDir::Reversed)) { 0115 const auto object = parseFile(entry.absoluteFilePath()); 0116 if (isStrikeClientResponse(object)) { 0117 return object; 0118 } 0119 } 0120 qWarning() << "no cmake-file-api reply index file found in" << replyDir.absolutePath(); 0121 return {}; 0122 } 0123 0124 static CMakeTarget parseTarget(const QJsonObject& target, QUrl& sourcePathInterner, QUrl& buildPathInterner, 0125 CMakeFilesCompilationData& compilationData) 0126 { 0127 CMakeTarget ret; 0128 ret.name = target.value(QLatin1String("name")).toString(); 0129 ret.type = CMakeTarget::typeToEnum(target.value(QLatin1String("type")).toString()); 0130 ret.folder = target.value(QLatin1String("folder")).toObject().value(QLatin1String("name")).toString(); //? 0131 0132 for (const auto& jsonArtifact : target.value(QLatin1String("artifacts")).toArray()) 0133 { 0134 const auto artifact = jsonArtifact.toObject(); 0135 const auto buildPath = resolvedUrl(buildPathInterner, artifact.value(QLatin1String("path")).toString()); 0136 0137 if (buildPath.isValid()) 0138 { 0139 ret.artifacts.append(buildPath); 0140 } 0141 } 0142 0143 for (const auto& jsonSource : target.value(QLatin1String("sources")).toArray()) 0144 { 0145 const auto source = jsonSource.toObject(); 0146 const auto sourcePath = resolvedUrl(sourcePathInterner, source.value(QLatin1String("path")).toString()); 0147 0148 if (sourcePath.isValid()) 0149 { 0150 ret.sources.append(sourcePath); 0151 } 0152 } 0153 0154 QVector<CMakeFile> compileGroups; 0155 for (const auto& jsonCompileGroup : target.value(QLatin1String("compileGroups")).toArray()) 0156 { 0157 CMakeFile cmakeFile; 0158 const auto compileGroup = jsonCompileGroup.toObject(); 0159 0160 cmakeFile.language = compileGroup.value(QLatin1String("language")).toString(); 0161 0162 for (const auto& jsonFragment : compileGroup.value(QLatin1String("compileCommandFragments")).toArray()) 0163 { 0164 const auto fragment = jsonFragment.toObject(); 0165 cmakeFile.compileFlags += fragment.value(QLatin1String("fragment")).toString(); 0166 cmakeFile.compileFlags += QLatin1Char(' '); 0167 } 0168 // cmakeFile.compileFlags = stringInterner.internString(cmakeFile.compileFlags); 0169 0170 for (const auto& jsonDefine : compileGroup.value(QLatin1String("defines")).toArray()) 0171 { 0172 const auto define = jsonDefine.toObject(); 0173 cmakeFile.addDefine(define.value(QLatin1String("define")).toString()); 0174 } 0175 // cmakeFile.defines = MakeFileResolver::extractDefinesFromCompileFlags(cmakeFile.compileFlags, stringInterner, cmakeFile.defines); 0176 0177 for (const auto& jsonInclude : compileGroup.value(QLatin1String("includes")).toArray()) 0178 { 0179 const auto include = jsonInclude.toObject(); 0180 const auto path = resolvedUrl(sourcePathInterner, include.value(QLatin1String("path")).toString()); 0181 if (path.isValid()) 0182 { 0183 cmakeFile.includes.append(path); 0184 } 0185 } 0186 0187 compileGroups.append(cmakeFile); 0188 } 0189 0190 for (const auto& jsonSource : target.value(QLatin1String("sources")).toArray()) 0191 { 0192 const auto source = jsonSource.toObject(); 0193 const auto compileGroupIndex = source.value(QLatin1String("compileGroupIndex")).toInt(-1); 0194 0195 const auto sourceGroupIndex = source.value(QLatin1String("sourceGroupIndex")).toInt(-1); 0196 const auto sourceGroups = target.value(QLatin1String("sourceGroups")).toArray(); 0197 auto group = sourceGroups.at(sourceGroupIndex).toObject(); 0198 0199 if (compileGroupIndex < 0 || compileGroupIndex > compileGroups.size()) 0200 { 0201 continue; 0202 } 0203 0204 auto compileGroup = compileGroups[compileGroupIndex]; 0205 const auto path = resolvedUrl(sourcePathInterner, source.value(QLatin1String("path")).toString()); 0206 if (path.isValid()) 0207 { 0208 compileGroup.group = group.value(QLatin1String("name")).toString(); 0209 compilationData.files[toCanonical(path)] = compileGroup; 0210 compilationData.files[toCanonical(path)] = compileGroup; 0211 } 0212 } 0213 return ret; 0214 } 0215 0216 static QVector<CMakeProjectData> parseCodeModel(const QJsonObject& codeModel, const QDir& replyDir, QUrl& sourcePathInterner, QUrl& buildPathInterner) 0217 { 0218 QVector<CMakeProjectData> res; 0219 // for now, we only use the first available configuration and don't support multi configurations 0220 const auto configuration = codeModel.value(QLatin1String("configurations")).toArray().at(0).toObject(); 0221 const auto projects = configuration.value(QLatin1String("projects")).toArray(); 0222 const auto targets = configuration.value(QLatin1String("targets")).toArray(); 0223 const auto directories = configuration.value(QLatin1String("directories")).toArray(); 0224 0225 for(const auto &projectsValues : projects) 0226 { 0227 qDebug() << "Parsing projects"; 0228 CMakeProjectData projectRes; 0229 const auto project = projectsValues.toObject(); 0230 0231 projectRes.name = project.value(QLatin1String("name")).toString(); 0232 0233 if (!project.contains(QLatin1String("directoryIndexes"))) 0234 { 0235 continue; 0236 } 0237 0238 for (const auto& directoryIndex : project.value(QLatin1String("directoryIndexes")).toArray()) 0239 { 0240 const auto directory = directories.at(directoryIndex.toInt(-1)).toObject(); 0241 if (directory.isEmpty()) 0242 { 0243 continue; 0244 } 0245 0246 if (!directory.contains(QLatin1String("targetIndexes"))) 0247 { 0248 continue; 0249 } 0250 0251 const auto dirSourcePath = resolvedUrl(sourcePathInterner, directory.value(QLatin1String("source")).toString()); 0252 0253 qDebug() << "MEH" << dirSourcePath << sourcePathInterner; 0254 0255 auto& dirTargets = projectRes.targets[dirSourcePath]; 0256 0257 for (const auto& targetIndex : directory.value(QLatin1String("targetIndexes")).toArray()) 0258 { 0259 const auto jsonTarget = targets.at(targetIndex.toInt(-1)).toObject(); 0260 if (jsonTarget.isEmpty()) 0261 { 0262 continue; 0263 } 0264 0265 const auto targetFile = jsonTarget.value(QLatin1String("jsonFile")).toString(); 0266 const auto target = parseTarget(parseFile(replyDir.absoluteFilePath(targetFile)), sourcePathInterner, buildPathInterner, projectRes.compilationData); 0267 0268 if (target.name.isEmpty()) 0269 { 0270 continue; 0271 } 0272 0273 dirTargets.append(target); 0274 } 0275 } 0276 0277 projectRes.compilationData.isValid = !codeModel.isEmpty(); 0278 projectRes.compilationData.rebuildFileForFolderMapping(); 0279 0280 if (!projectRes.compilationData.isValid) 0281 { 0282 qWarning() << "failed to parse code model" << codeModel; 0283 } 0284 0285 res << projectRes; 0286 } 0287 0288 return res; 0289 } 0290 0291 static QHash<QUrl, CMakeProjectData::CMakeFileFlags> parseCMakeFiles(const QJsonObject& cmakeFiles, 0292 QUrl&) 0293 { 0294 QHash<QUrl, CMakeProjectData::CMakeFileFlags> ret; 0295 for (const auto& jsonInput : cmakeFiles.value(QLatin1String("inputs")).toArray()) { 0296 const auto input = jsonInput.toObject(); 0297 const auto path = QUrl(input.value(QLatin1String("path")).toString()); 0298 CMakeProjectData::CMakeFileFlags flags; 0299 flags.isGenerated = input.value(QLatin1String("isGenerated")).toBool(); 0300 flags.isExternal = input.value(QLatin1String("isExternal")).toBool(); 0301 flags.isCMake = input.value(QLatin1String("isCMake")).toBool(); 0302 ret[path] = flags; 0303 } 0304 return ret; 0305 } 0306 0307 QVector<CMakeProjectData> parseReplyIndexFile(const QJsonObject& replyIndex, const QUrl &sourceDirectory, const QUrl &buildDirectory) 0308 { 0309 const auto reply = replyIndex.value(QLatin1String("reply")).toObject(); 0310 const auto clientKDevelop = reply.value(QLatin1String("client-strike")).toObject(); 0311 const auto query = clientKDevelop.value(QLatin1String("query.json")).toObject(); 0312 const auto responses = query.value(QLatin1String("responses")).toArray(); 0313 const auto replyDir = toReplyDir(buildDirectory.toString()); 0314 0315 QUrl sourcePathInterner(toCanonical(sourceDirectory)); 0316 QUrl buildPathInterner(buildDirectory); 0317 0318 qDebug() << "Canonical << " << sourcePathInterner << buildPathInterner; 0319 0320 QVector<CMakeProjectData> projects; 0321 QHash<QUrl, CMakeProjectData::CMakeFileFlags> cmakeFiles; 0322 0323 for (const auto& responseValue : responses) 0324 { 0325 const auto response = responseValue.toObject(); 0326 const auto kind = response.value(QLatin1String("kind")); 0327 const auto jsonFile = response.value(QLatin1String("jsonFile")).toString(); 0328 const auto jsonFilePath = replyDir.absoluteFilePath(jsonFile); 0329 0330 if (kind == QLatin1String("codemodel")) 0331 { 0332 qDebug() << "Parsing code model"; 0333 projects = parseCodeModel(parseFile(jsonFilePath), replyDir, sourcePathInterner, buildPathInterner); 0334 } else if (kind == QLatin1String("cmakeFiles")) 0335 { 0336 cmakeFiles = parseCMakeFiles(parseFile(jsonFilePath), sourcePathInterner); 0337 } 0338 } 0339 0340 for(auto &project : projects) 0341 { 0342 if (!project.compilationData.isValid) 0343 { 0344 qWarning() << "failed to find code model in reply index" << sourceDirectory << buildDirectory << replyIndex; 0345 return {}; 0346 } 0347 project.cmakeFiles = cmakeFiles; 0348 } 0349 0350 return projects; 0351 } 0352 } 0353 }