File indexing completed on 2024-05-05 04:39:26

0001 /*
0002     SPDX-FileCopyrightText: 2017 Aleix Pol Gonzalez <aleixpol@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "cmakeserverimportjob.h"
0008 #include "cmakeutils.h"
0009 #include "cmakeserver.h"
0010 
0011 #include <interfaces/iproject.h>
0012 #include <interfaces/icore.h>
0013 #include <interfaces/iruntime.h>
0014 #include <interfaces/iruntimecontroller.h>
0015 #include <makefileresolver/makefileresolver.h>
0016 
0017 #include <QJsonObject>
0018 #include <QJsonArray>
0019 #include <QRegularExpression>
0020 #include <QFileInfo>
0021 
0022 #include "debug.h"
0023 
0024 void CMakeServerImportJob::processCodeModel(const QJsonObject &response, CMakeProjectData &data)
0025 {
0026     const auto configs = response.value(QStringLiteral("configurations")).toArray();
0027     qCDebug(CMAKE) << "process response" << response;
0028 
0029     data.targets.clear();
0030     data.compilationData.files.clear();
0031 
0032     StringInterner stringInterner;
0033 
0034     const auto rt = KDevelop::ICore::self()->runtimeController()->currentRuntime();
0035     for (const auto &config: configs) {
0036         const auto projects = config.toObject().value(QStringLiteral("projects")).toArray();
0037         for (const auto &project: projects) {
0038             const auto targets = project.toObject().value(QStringLiteral("targets")).toArray();
0039             for (const auto &targetObject: targets) {
0040                 const auto target = targetObject.toObject();
0041                 const KDevelop::Path targetDir = rt->pathInHost(KDevelop::Path(target.value(QStringLiteral("sourceDirectory")).toString()));
0042 
0043                 KDevelop::Path::List targetSources;
0044                 const auto fileGroups = target.value(QStringLiteral("fileGroups")).toArray();
0045                 for (const auto &fileGroupValue: fileGroups) {
0046                     const auto fileGroup = fileGroupValue.toObject();
0047                     CMakeFile file;
0048                     file.includes = kTransform<KDevelop::Path::List>(fileGroup.value(QStringLiteral("includePath")).toArray(), [](const QJsonValue& val) { return KDevelop::Path(val.toObject().value(QStringLiteral("path")).toString()); });
0049 
0050                     file.language = fileGroup.value(QStringLiteral("language")).toString(),
0051                     file.compileFlags = fileGroup.value(QStringLiteral("compileFlags")).toString();
0052                     for (const auto& jsonDefine : fileGroup.value(QStringLiteral("defines")).toArray()) {
0053                         file.addDefine(jsonDefine.toString());
0054                     }
0055                     file.defines = MakeFileResolver::extractDefinesFromCompileFlags(file.compileFlags, stringInterner, file.defines);
0056 
0057                     // apparently some file groups do not contain build system information
0058                     // skip these, as they would produce bogus results for us and break the fallback
0059                     // implemented in CMakeManager::fileInformation
0060                     if (file.isEmpty()) {
0061                         continue;
0062                     }
0063 
0064                     const auto sourcesArray = fileGroup.value(QStringLiteral("sources")).toArray();
0065                     const KDevelop::Path::List sources = kTransform<KDevelop::Path::List>(sourcesArray, [targetDir](const QJsonValue& val) { return KDevelop::Path(targetDir, val.toString()); });
0066                     targetSources.reserve(targetSources.size() + sources.size());
0067                     for (const auto& source: sources) {
0068                         // NOTE: we use the canonical file path to prevent issues with symlinks in the path
0069                         //       leading to lookup failures
0070                         const auto localFile = rt->pathInHost(source);
0071                         const auto canonicalFile = QFileInfo(source.toLocalFile()).canonicalFilePath();
0072                         const auto sourcePath = (canonicalFile.isEmpty() || localFile.toLocalFile() == canonicalFile)
0073                                               ? localFile : KDevelop::Path(canonicalFile);
0074                         data.compilationData.files[sourcePath] = file;
0075                         targetSources << sourcePath;
0076                     }
0077                     qCDebug(CMAKE) << "registering..." << sources << file;
0078                 }
0079 
0080                 CMakeTarget cmakeTarget{
0081                     CMakeTarget::typeToEnum(target.value(QLatin1String("type")).toString()),
0082                     target.value(QStringLiteral("name")).toString(),
0083                     kTransform<KDevelop::Path::List>(target[QLatin1String("artifacts")].toArray(), [](const QJsonValue& val) { return KDevelop::Path(val.toString()); }),
0084                     targetSources,
0085                     QString()
0086                 };
0087 
0088                 // ensure we don't add the same target multiple times, for different projects
0089                 // cf.: https://bugs.kde.org/show_bug.cgi?id=387095
0090                 auto& dirTargets = data.targets[targetDir];
0091                 if (dirTargets.contains(cmakeTarget))
0092                     continue;
0093                 dirTargets += cmakeTarget;
0094 
0095                 qCDebug(CMAKE) << "adding target" << cmakeTarget.name << "with sources" << cmakeTarget.sources;
0096             }
0097         }
0098     }
0099 }
0100 
0101 CMakeServerImportJob::CMakeServerImportJob(KDevelop::IProject* project, const QSharedPointer<CMakeServer> &server, QObject* parent)
0102     : KJob(parent)
0103     , m_server(server)
0104     , m_project(project)
0105 {
0106     connect(m_server.data(), &CMakeServer::disconnected, this, [this]() {
0107         setError(UnexpectedDisconnect);
0108         emitResult();
0109     });
0110 }
0111 
0112 void CMakeServerImportJob::start()
0113 {
0114     if (m_server->isServerAvailable())
0115         doStart();
0116     else
0117         connect(m_server.data(), &CMakeServer::connected, this, &CMakeServerImportJob::doStart);
0118 }
0119 
0120 void CMakeServerImportJob::doStart()
0121 {
0122     connect(m_server.data(), &CMakeServer::response, this, &CMakeServerImportJob::processResponse);
0123 
0124     m_server->handshake(m_project->path(), CMake::currentBuildDir(m_project));
0125 }
0126 
0127 void CMakeServerImportJob::processResponse(const QJsonObject& response)
0128 {
0129     const auto responseType = response.value(QStringLiteral("type"));
0130     if (responseType == QLatin1String("reply")) {
0131         const auto inReplyTo = response.value(QStringLiteral("inReplyTo"));
0132         qCDebug(CMAKE) << "replying..." << inReplyTo;
0133         if (inReplyTo == QLatin1String("handshake")) {
0134             m_server->configure({});
0135         } else if (inReplyTo == QLatin1String("configure")) {
0136             m_server->compute();
0137         } else if (inReplyTo == QLatin1String("compute")) {
0138             m_server->codemodel();
0139         } else if(inReplyTo == QLatin1String("codemodel")) {
0140             processCodeModel(response, m_data);
0141             m_data.testSuites = CMake::importTestSuites(CMake::currentBuildDir(m_project));
0142             m_data.compilationData.rebuildFileForFolderMapping();
0143             emitResult();
0144         } else {
0145             qCDebug(CMAKE) << "unhandled reply" << response;
0146         }
0147     } else if(responseType == QLatin1String("error")) {
0148         setError(ErrorResponse);
0149         setErrorText(response.value(QStringLiteral("errorMessage")).toString());
0150         qCWarning(CMAKE) << "error!!" << response;
0151         emitResult();
0152     } else if (responseType == QLatin1String("progress")) {
0153         int progress = response.value(QStringLiteral("progressCurrent")).toInt();
0154         int total = response.value(QStringLiteral("progressMaximum")).toInt();
0155         if (progress >= 0 && total > 0) {
0156             setPercent(100.0 * progress / total);
0157         }
0158     } else if (responseType == QLatin1String("message") || responseType == QLatin1String("hello")) {
0159         // Known, but not used for anything currently.
0160     } else {
0161         qCDebug(CMAKE) << "unhandled message" << response;
0162     }
0163 }
0164 
0165 #include "moc_cmakeserverimportjob.cpp"