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

0001 /*
0002     SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "cmakeimportjsonjob.h"
0008 
0009 #include "cmakeutils.h"
0010 #include "cmakeprojectdata.h"
0011 #include "cmakemodelitems.h"
0012 #include "debug.h"
0013 
0014 #include <makefileresolver/makefileresolver.h>
0015 #include <language/duchain/duchain.h>
0016 #include <language/duchain/duchainlock.h>
0017 #include <interfaces/iproject.h>
0018 #include <interfaces/icore.h>
0019 #include <interfaces/iruntime.h>
0020 #include <interfaces/iruntimecontroller.h>
0021 
0022 #include <KShell>
0023 #include <QJsonDocument>
0024 #include <QJsonObject>
0025 #include <QJsonArray>
0026 #include <QtConcurrentRun>
0027 #include <QFutureWatcher>
0028 #include <QRegularExpression>
0029 
0030 using namespace KDevelop;
0031 
0032 namespace {
0033 
0034 CMakeFilesCompilationData importCommands(const Path& commandsFile)
0035 {
0036     // NOTE: to get compile_commands.json, you need -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
0037     QFile f(commandsFile.toLocalFile());
0038     bool r = f.open(QFile::ReadOnly|QFile::Text);
0039     if(!r) {
0040         qCWarning(CMAKE) << "Couldn't open commands file" << commandsFile;
0041         return {};
0042     }
0043 
0044     qCDebug(CMAKE) << "Found commands file" << commandsFile;
0045 
0046     CMakeFilesCompilationData data;
0047     QJsonParseError error;
0048     const QJsonDocument document = QJsonDocument::fromJson(f.readAll(), &error);
0049     if (error.error) {
0050         qCWarning(CMAKE) << "Failed to parse JSON in commands file:" << error.errorString() << commandsFile;
0051         data.isValid = false;
0052         return data;
0053     } else if (!document.isArray()) {
0054         qCWarning(CMAKE) << "JSON document in commands file is not an array: " << commandsFile;
0055         data.isValid = false;
0056         return data;
0057     }
0058 
0059     MakeFileResolver resolver;
0060     const QString KEY_COMMAND = QStringLiteral("command");
0061     const QString KEY_DIRECTORY = QStringLiteral("directory");
0062     const QString KEY_FILE = QStringLiteral("file");
0063     auto rt = ICore::self()->runtimeController()->currentRuntime();
0064     const auto values = document.array();
0065     for (const QJsonValue& value : values) {
0066         if (!value.isObject()) {
0067             qCWarning(CMAKE) << "JSON command file entry is not an object:" << value;
0068             continue;
0069         }
0070         const QJsonObject entry = value.toObject();
0071         if (!entry.contains(KEY_FILE) || !entry.contains(KEY_COMMAND) || !entry.contains(KEY_DIRECTORY)) {
0072             qCWarning(CMAKE) << "JSON command file entry does not contain required keys:" << entry;
0073             continue;
0074         }
0075 
0076         PathResolutionResult result = resolver.processOutput(entry[KEY_COMMAND].toString(), entry[KEY_DIRECTORY].toString());
0077 
0078         auto convert = [rt](const Path &path) { return rt->pathInHost(path); };
0079 
0080         CMakeFile ret;
0081         ret.includes = kTransform<Path::List>(result.paths, convert);
0082         ret.frameworkDirectories = kTransform<Path::List>(result.frameworkDirectories, convert);
0083         ret.defines = result.defines;
0084         const Path path(rt->pathInHost(Path(entry[KEY_FILE].toString())));
0085         qCDebug(CMAKE) << "entering..." << path << entry[KEY_FILE];
0086         data.files[path] = ret;
0087     }
0088 
0089     data.isValid = true;
0090     data.rebuildFileForFolderMapping();
0091     return data;
0092 }
0093 
0094 ImportData import(const Path& commandsFile, const Path &targetsFilePath, const QString &sourceDir, const KDevelop::Path &buildPath)
0095 {
0096     QHash<KDevelop::Path, QVector<CMakeTarget>> cmakeTargets;
0097 
0098     //we don't have target type information in json, so we just announce all of them as exes
0099     const auto targets = CMake::enumerateTargets(targetsFilePath, sourceDir, buildPath);
0100     for(auto it = targets.constBegin(), itEnd = targets.constEnd(); it!=itEnd; ++it) {
0101         cmakeTargets[it.key()] = kTransform<QVector<CMakeTarget>>(*it, [](const QString &targetName) {
0102             return CMakeTarget{
0103                 CMakeTarget::Executable,
0104                 targetName,
0105                 KDevelop::Path::List(),
0106                 KDevelop::Path::List(),
0107                 QString()
0108             };
0109         });
0110     }
0111 
0112     return ImportData {
0113         importCommands(commandsFile),
0114         cmakeTargets,
0115         CMake::importTestSuites(buildPath)
0116     };
0117 }
0118 
0119 }
0120 
0121 CMakeImportJsonJob::CMakeImportJsonJob(IProject* project, QObject* parent)
0122     : KJob(parent)
0123     , m_project(project)
0124     , m_data({})
0125 {
0126     connect(&m_futureWatcher, &QFutureWatcher<ImportData>::finished, this, &CMakeImportJsonJob::importCompileCommandsJsonFinished);
0127 }
0128 
0129 CMakeImportJsonJob::~CMakeImportJsonJob()
0130 {}
0131 
0132 void CMakeImportJsonJob::start()
0133 {
0134     auto commandsFile = CMake::commandsFile(project());
0135     if (!QFileInfo::exists(commandsFile.toLocalFile())) {
0136         qCWarning(CMAKE) << "Could not import CMake project" << project()->path() << "('compile_commands.json' missing)";
0137         emitResult();
0138         return;
0139     }
0140 
0141     const Path currentBuildDir = CMake::currentBuildDir(m_project);
0142     Q_ASSERT (!currentBuildDir.isEmpty());
0143 
0144     const Path targetsFilePath = CMake::targetDirectoriesFile(m_project);
0145     const QString sourceDir = m_project->path().toLocalFile();
0146     auto rt = ICore::self()->runtimeController()->currentRuntime();
0147 
0148     auto future = QtConcurrent::run(import, commandsFile, targetsFilePath, sourceDir, rt->pathInRuntime(currentBuildDir));
0149     m_futureWatcher.setFuture(future);
0150 }
0151 
0152 void CMakeImportJsonJob::importCompileCommandsJsonFinished()
0153 {
0154     Q_ASSERT(m_project->thread() == QThread::currentThread());
0155     Q_ASSERT(m_futureWatcher.isFinished());
0156 
0157     auto future = m_futureWatcher.future();
0158     auto data = future.result();
0159     if (!data.compilationData.isValid) {
0160         qCWarning(CMAKE) << "Could not import CMake project ('compile_commands.json' invalid)";
0161         emitResult();
0162         return;
0163     }
0164 
0165     m_data = {data.compilationData, data.targets, data.testSuites, {}};
0166     qCDebug(CMAKE) << "Done importing, found" << data.compilationData.files.count() << "entries for" << project()->path();
0167 
0168     emitResult();
0169 }
0170 
0171 IProject* CMakeImportJsonJob::project() const
0172 {
0173     return m_project;
0174 }
0175 
0176 CMakeProjectData CMakeImportJsonJob::projectData() const
0177 {
0178     Q_ASSERT(!m_futureWatcher.isRunning());
0179     return m_data;
0180 }
0181 
0182 #include "moc_cmakeimportjsonjob.cpp"