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"