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 }