forked from qt-creator/qt-creator
CMake: Add initial fileapireader class
Change-Id: I620cba7cc1c2a5ac56789fa9770dce573c6b19cd Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
947
src/plugins/cmakeprojectmanager/fileapiparser.cpp
Normal file
947
src/plugins/cmakeprojectmanager/fileapiparser.cpp
Normal file
@@ -0,0 +1,947 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "fileapiparser.h"
|
||||
|
||||
#include <coreplugin/messagemanager.h>
|
||||
#include <cpptools/cpprawprojectpart.h>
|
||||
#include <projectexplorer/headerpath.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
namespace CMakeProjectManager {
|
||||
namespace Internal {
|
||||
|
||||
using namespace FileApiDetails;
|
||||
using namespace Utils;
|
||||
|
||||
const char CMAKE_RELATIVE_REPLY_PATH[] = ".cmake/api/v1/reply";
|
||||
const char CMAKE_RELATIVE_QUERY_PATH[] = ".cmake/api/v1/query";
|
||||
|
||||
Q_LOGGING_CATEGORY(cmakeFileApi, "qtc.cmake.fileApi", QtWarningMsg);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Helper:
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
static void reportFileApiSetupFailure()
|
||||
{
|
||||
Core::MessageManager::write(QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Failed to set up cmake fileapi support. Creator can not extract project information."));
|
||||
}
|
||||
|
||||
static bool shouldProcessFile(const QString &filePath, bool update = true)
|
||||
{
|
||||
static QString lastSeenFilePath;
|
||||
if (filePath == lastSeenFilePath)
|
||||
return false;
|
||||
if (update)
|
||||
lastSeenFilePath = filePath;
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::pair<int, int> cmakeVersion(const QJsonObject &obj)
|
||||
{
|
||||
const QJsonObject version = obj.value("version").toObject();
|
||||
const int major = version.value("major").toInt(-1);
|
||||
const int minor = version.value("minor").toInt(-1);
|
||||
return std::make_pair(major, minor);
|
||||
}
|
||||
|
||||
static bool checkJsonObject(const QJsonObject &obj, const QString &kind, int major, int minor = -1)
|
||||
{
|
||||
auto version = cmakeVersion(obj);
|
||||
if (major == -1)
|
||||
version.first = major;
|
||||
if (minor == -1)
|
||||
version.second = minor;
|
||||
return obj.value("kind").toString() == kind && version == std::make_pair(major, minor);
|
||||
}
|
||||
|
||||
static std::pair<QString, QString> nameValue(const QJsonObject &obj)
|
||||
{
|
||||
return std::make_pair(obj.value("name").toString(), obj.value("value").toString());
|
||||
}
|
||||
|
||||
static QJsonDocument readJsonFile(const QString &path)
|
||||
{
|
||||
qCDebug(cmakeFileApi) << "readJsonFile:" << path;
|
||||
|
||||
QFile file(path);
|
||||
file.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
std::vector<int> indexList(const QJsonValue &v)
|
||||
{
|
||||
const QJsonArray &indexList = v.toArray();
|
||||
std::vector<int> result;
|
||||
result.reserve(static_cast<size_t>(indexList.count()));
|
||||
|
||||
for (const QJsonValue &v : indexList) {
|
||||
result.push_back(v.toInt(-1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reply file:
|
||||
|
||||
static ReplyFileContents readReplyFile(const QFileInfo &fi, QString &errorMessage)
|
||||
{
|
||||
const QJsonDocument document = readJsonFile(fi.filePath());
|
||||
static const QString msg = QCoreApplication::translate("CMakeProjectManager::Internal",
|
||||
"Invalid reply file created by cmake.");
|
||||
|
||||
ReplyFileContents result;
|
||||
if (document.isNull() || document.isEmpty() || !document.isObject()) {
|
||||
errorMessage = msg;
|
||||
return result;
|
||||
}
|
||||
|
||||
const QJsonObject rootObject = document.object();
|
||||
|
||||
{
|
||||
const QJsonObject cmakeObject = rootObject.value("cmake").toObject();
|
||||
{
|
||||
const QJsonObject paths = cmakeObject.value("paths").toObject();
|
||||
{
|
||||
result.cmakeExecutable = paths.value("cmake").toString();
|
||||
result.cmakeRoot = paths.value("root").toString();
|
||||
}
|
||||
const QJsonObject generator = cmakeObject.value("generator").toObject();
|
||||
{
|
||||
result.generator = generator.value("name").toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hadInvalidObject = false;
|
||||
{
|
||||
const QJsonArray objects = rootObject.value("objects").toArray();
|
||||
for (const QJsonValue &v : objects) {
|
||||
const QJsonObject object = v.toObject();
|
||||
{
|
||||
ReplyObject r;
|
||||
r.kind = object.value("kind").toString();
|
||||
r.file = object.value("jsonFile").toString();
|
||||
r.version = cmakeVersion(object);
|
||||
|
||||
if (r.kind.isEmpty() || r.file.isEmpty() || r.version.first == -1
|
||||
|| r.version.second == -1)
|
||||
hadInvalidObject = true;
|
||||
else
|
||||
result.replies.append(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.generator.isEmpty() || result.cmakeExecutable.isEmpty() || result.cmakeRoot.isEmpty()
|
||||
|| result.replies.isEmpty() || hadInvalidObject)
|
||||
errorMessage = msg;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Cache file:
|
||||
|
||||
static CMakeConfig readCacheFile(const QString &cacheFile, QString &errorMessage)
|
||||
{
|
||||
CMakeConfig result;
|
||||
|
||||
const QJsonDocument doc = readJsonFile(cacheFile);
|
||||
const QJsonObject root = doc.object();
|
||||
|
||||
if (!checkJsonObject(root, "cache", 2)) {
|
||||
errorMessage = QCoreApplication::translate("CMakeProjectManager::Internal",
|
||||
"Invalid cache file generated by cmake.");
|
||||
return {};
|
||||
}
|
||||
|
||||
const QJsonArray entries = root.value("entries").toArray();
|
||||
for (const QJsonValue &v : entries) {
|
||||
CMakeConfigItem item;
|
||||
|
||||
const QJsonObject entry = v.toObject();
|
||||
auto nv = nameValue(entry);
|
||||
item.key = nv.first.toUtf8();
|
||||
item.value = nv.second.toUtf8();
|
||||
|
||||
item.type = CMakeConfigItem::typeStringToType(entry.value("type").toString().toUtf8());
|
||||
|
||||
{
|
||||
const QJsonArray properties = entry.value("properties").toArray();
|
||||
for (const QJsonValue &v : properties) {
|
||||
const QJsonObject prop = v.toObject();
|
||||
auto nv = nameValue(prop);
|
||||
if (nv.first == "ADVANCED") {
|
||||
const auto boolValue = CMakeConfigItem::toBool(nv.second.toUtf8());
|
||||
item.isAdvanced = boolValue.has_value() && boolValue.value();
|
||||
} else if (nv.first == "HELPSTRING") {
|
||||
item.documentation = nv.second.toUtf8();
|
||||
} else if (nv.first == "STRINGS") {
|
||||
item.values = nv.second.split(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
result.append(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// CMake Files:
|
||||
|
||||
std::vector<CMakeFileInfo> readCMakeFilesFile(const QString &cmakeFilesFile, QString &errorMessage)
|
||||
{
|
||||
std::vector<CMakeFileInfo> result;
|
||||
|
||||
const QJsonDocument doc = readJsonFile(cmakeFilesFile);
|
||||
const QJsonObject root = doc.object();
|
||||
|
||||
if (!checkJsonObject(root, "cmakeFiles", 1)) {
|
||||
errorMessage = QCoreApplication::translate("CMakeProjectManager::Internal",
|
||||
"Invalid cmakeFiles file generated by cmake.");
|
||||
return {};
|
||||
}
|
||||
|
||||
const QJsonArray inputs = root.value("inputs").toArray();
|
||||
for (const QJsonValue &v : inputs) {
|
||||
CMakeFileInfo info;
|
||||
const QJsonObject input = v.toObject();
|
||||
info.path = input.value("path").toString();
|
||||
|
||||
info.isCMake = input.value("isCMake").toBool();
|
||||
const QString filename = FilePath::fromString(info.path).fileName();
|
||||
info.isCMakeListsDotTxt = (filename.compare("CMakeLists.txt",
|
||||
HostOsInfo::fileNameCaseSensitivity())
|
||||
== 0);
|
||||
|
||||
info.isGenerated = input.value("isGenerated").toBool();
|
||||
info.isExternal = input.value("isExternal").toBool();
|
||||
|
||||
result.emplace_back(std::move(info));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Codemodel file:
|
||||
|
||||
std::vector<Directory> extractDirectories(const QJsonArray &directories, QString &errorMessage)
|
||||
{
|
||||
if (directories.isEmpty()) {
|
||||
errorMessage = QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: No directories.");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Directory> result;
|
||||
for (const QJsonValue &v : directories) {
|
||||
const QJsonObject obj = v.toObject();
|
||||
if (obj.isEmpty()) {
|
||||
errorMessage = QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: Empty directory object.");
|
||||
continue;
|
||||
}
|
||||
Directory dir;
|
||||
dir.sourcePath = obj.value("source").toString();
|
||||
dir.buildPath = obj.value("build").toString();
|
||||
dir.parent = obj.value("parentIndex").toInt(-1);
|
||||
dir.project = obj.value("projectIndex").toInt(-1);
|
||||
dir.children = indexList(obj.value("childIndexes"));
|
||||
dir.targets = indexList(obj.value("targetIndexes"));
|
||||
dir.hasInstallRule = obj.value("hasInstallRule").toBool();
|
||||
|
||||
result.emplace_back(std::move(dir));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<Project> extractProjects(const QJsonArray &projects, QString &errorMessage)
|
||||
{
|
||||
if (projects.isEmpty()) {
|
||||
errorMessage = QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: No projects.");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Project> result;
|
||||
for (const QJsonValue &v : projects) {
|
||||
const QJsonObject obj = v.toObject();
|
||||
if (obj.isEmpty()) {
|
||||
errorMessage = QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: Empty project object.");
|
||||
continue;
|
||||
}
|
||||
Project project;
|
||||
project.name = obj.value("name").toString();
|
||||
project.parent = obj.value("parentIndex").toInt(-1);
|
||||
project.children = indexList(obj.value("childIndexes"));
|
||||
project.directories = indexList(obj.value("directoryIndexes"));
|
||||
project.targets = indexList(obj.value("targetIndexes"));
|
||||
|
||||
qCDebug(cmakeFileApi) << "Project read:" << project.name << project.directories;
|
||||
|
||||
if (project.name.isEmpty() || project.directories.empty()) {
|
||||
errorMessage = QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: Broken project data.");
|
||||
continue;
|
||||
}
|
||||
|
||||
result.emplace_back(std::move(project));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<Target> extractTargets(const QJsonArray &targets, QString &errorMessage)
|
||||
{
|
||||
if (targets.isEmpty()) {
|
||||
errorMessage
|
||||
= QCoreApplication::translate("CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: No targets.");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Target> result;
|
||||
for (const QJsonValue &v : targets) {
|
||||
const QJsonObject obj = v.toObject();
|
||||
if (obj.isEmpty()) {
|
||||
errorMessage = QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: Empty target object.");
|
||||
continue;
|
||||
}
|
||||
Target target;
|
||||
target.name = obj.value("name").toString();
|
||||
target.id = obj.value("id").toString();
|
||||
target.directory = obj.value("directoryIndex").toInt(-1);
|
||||
target.project = obj.value("projectIndex").toInt(-1);
|
||||
target.jsonFile = obj.value("jsonFile").toString();
|
||||
|
||||
if (target.name.isEmpty() || target.id.isEmpty() || target.jsonFile.isEmpty()
|
||||
|| target.directory == -1 || target.project == -1) {
|
||||
errorMessage = QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: Broken target data.");
|
||||
continue;
|
||||
}
|
||||
|
||||
result.emplace_back(std::move(target));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool validateIndexes(const Configuration &config)
|
||||
{
|
||||
const int directoryCount = static_cast<int>(config.directories.size());
|
||||
const int projectCount = static_cast<int>(config.projects.size());
|
||||
const int targetCount = static_cast<int>(config.targets.size());
|
||||
|
||||
int topLevelCount = 0;
|
||||
for (const Directory &d : config.directories) {
|
||||
if (d.parent == -1)
|
||||
++topLevelCount;
|
||||
|
||||
if (d.parent < -1 || d.parent >= directoryCount) {
|
||||
qCWarning(cmakeFileApi)
|
||||
<< "Directory" << d.sourcePath << ": parent index" << d.parent << "is broken.";
|
||||
return false;
|
||||
}
|
||||
if (d.project < 0 || d.project >= projectCount) {
|
||||
qCWarning(cmakeFileApi)
|
||||
<< "Directory" << d.sourcePath << ": project index" << d.project << "is broken.";
|
||||
return false;
|
||||
}
|
||||
if (contains(d.children, [directoryCount](int c) { return c < 0 || c >= directoryCount; })) {
|
||||
qCWarning(cmakeFileApi)
|
||||
<< "Directory" << d.sourcePath << ": A child index" << d.children << "is broken.";
|
||||
return false;
|
||||
}
|
||||
if (contains(d.targets, [targetCount](int t) { return t < 0 || t >= targetCount; })) {
|
||||
qCWarning(cmakeFileApi)
|
||||
<< "Directory" << d.sourcePath << ": A target index" << d.targets << "is broken.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (topLevelCount != 1) {
|
||||
qCWarning(cmakeFileApi) << "Directories: Invalid number of top level directories, "
|
||||
<< topLevelCount << " (expected: 1).";
|
||||
return false;
|
||||
}
|
||||
|
||||
topLevelCount = 0;
|
||||
for (const Project &p : config.projects) {
|
||||
if (p.parent == -1)
|
||||
++topLevelCount;
|
||||
|
||||
if (p.parent < -1 || p.parent >= projectCount) {
|
||||
qCWarning(cmakeFileApi)
|
||||
<< "Project" << p.name << ": parent index" << p.parent << "is broken.";
|
||||
return false;
|
||||
}
|
||||
if (contains(p.children, [projectCount](int p) { return p < 0 || p >= projectCount; })) {
|
||||
qCWarning(cmakeFileApi)
|
||||
<< "Project" << p.name << ": A child index" << p.children << "is broken.";
|
||||
return false;
|
||||
}
|
||||
if (contains(p.targets, [targetCount](int t) { return t < 0 || t >= targetCount; })) {
|
||||
qCWarning(cmakeFileApi)
|
||||
<< "Project" << p.name << ": A target index" << p.targets << "is broken.";
|
||||
return false;
|
||||
}
|
||||
if (contains(p.directories,
|
||||
[directoryCount](int d) { return d < 0 || d >= directoryCount; })) {
|
||||
qCWarning(cmakeFileApi)
|
||||
<< "Project" << p.name << ": A directory index" << p.directories << "is broken.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (topLevelCount != 1) {
|
||||
qCWarning(cmakeFileApi) << "Projects: Invalid number of top level projects, "
|
||||
<< topLevelCount << " (expected: 1).";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const Target &t : config.targets) {
|
||||
if (t.directory < 0 || t.directory >= directoryCount) {
|
||||
qCWarning(cmakeFileApi)
|
||||
<< "Target" << t.name << ": directory index" << t.directory << "is broken.";
|
||||
return false;
|
||||
}
|
||||
if (t.project < 0 || t.project >= projectCount) {
|
||||
qCWarning(cmakeFileApi)
|
||||
<< "Target" << t.name << ": project index" << t.project << "is broken.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<Configuration> extractConfigurations(const QJsonArray &configs,
|
||||
QString &errorMessage)
|
||||
{
|
||||
if (configs.isEmpty()) {
|
||||
errorMessage = QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: No configurations.");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<FileApiDetails::Configuration> result;
|
||||
for (const QJsonValue &v : configs) {
|
||||
const QJsonObject obj = v.toObject();
|
||||
if (obj.isEmpty()) {
|
||||
errorMessage = QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: Empty configuration object.");
|
||||
continue;
|
||||
}
|
||||
Configuration config;
|
||||
config.name = obj.value("name").toString();
|
||||
|
||||
config.directories = extractDirectories(obj.value("directories").toArray(), errorMessage);
|
||||
config.projects = extractProjects(obj.value("projects").toArray(), errorMessage);
|
||||
config.targets = extractTargets(obj.value("targets").toArray(), errorMessage);
|
||||
|
||||
if (!validateIndexes(config)) {
|
||||
errorMessage
|
||||
= QCoreApplication::translate("CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake: Broken "
|
||||
"indexes in directories/projects/targets.");
|
||||
return {};
|
||||
}
|
||||
|
||||
result.emplace_back(std::move(config));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<Configuration> readCodemodelFile(const QString &codemodelFile,
|
||||
QString &errorMessage)
|
||||
{
|
||||
const QJsonDocument doc = readJsonFile(codemodelFile);
|
||||
const QJsonObject root = doc.object();
|
||||
|
||||
if (!checkJsonObject(root, "codemodel", 2)) {
|
||||
errorMessage = QCoreApplication::translate("CMakeProjectManager::Internal",
|
||||
"Invalid codemodel file generated by cmake.");
|
||||
return {};
|
||||
}
|
||||
|
||||
return extractConfigurations(root.value("configurations").toArray(), errorMessage);
|
||||
}
|
||||
|
||||
// TargetDetails:
|
||||
|
||||
std::vector<FileApiDetails::FragmentInfo> extractFragments(const QJsonObject &obj)
|
||||
{
|
||||
const QJsonArray fragments = obj.value("commandFragments").toArray();
|
||||
return Utils::transform<std::vector>(fragments, [](const QJsonValue &v) {
|
||||
const QJsonObject o = v.toObject();
|
||||
return FileApiDetails::FragmentInfo{o.value("fragment").toString(),
|
||||
o.value("role").toString()};
|
||||
});
|
||||
}
|
||||
|
||||
TargetDetails extractTargetDetails(const QJsonObject &root, QString &errorMessage)
|
||||
{
|
||||
TargetDetails t;
|
||||
t.name = root.value("name").toString();
|
||||
t.id = root.value("id").toString();
|
||||
t.type = root.value("type").toString();
|
||||
|
||||
if (t.name.isEmpty() || t.id.isEmpty() || t.type.isEmpty()) {
|
||||
errorMessage = QCoreApplication::translate("CMakeProjectManager::Internal",
|
||||
"Invalid target file: Information is missing.");
|
||||
return {};
|
||||
}
|
||||
|
||||
t.backtrace = root.value("backtrace").toInt(-1);
|
||||
{
|
||||
const QJsonObject folder = root.value("folder").toObject();
|
||||
t.folderTargetProperty = folder.value("name").toString();
|
||||
}
|
||||
{
|
||||
const QJsonObject paths = root.value("paths").toObject();
|
||||
t.sourceDir = FilePath::fromString(paths.value("source").toString());
|
||||
t.buildDir = FilePath::fromString(paths.value("build").toString());
|
||||
}
|
||||
t.nameOnDisk = root.value("nameOnDisk").toString();
|
||||
{
|
||||
const QJsonArray artifacts = root.value("artifacts").toArray();
|
||||
t.artifacts = transform<QList>(artifacts, [](const QJsonValue &v) {
|
||||
return FilePath::fromString(v.toObject().value("path").toString());
|
||||
});
|
||||
}
|
||||
t.isGeneratorProvided = root.value("isGeneratorProvided").toBool();
|
||||
{
|
||||
const QJsonObject install = root.value("install").toObject();
|
||||
t.installPrefix = install.value("prefix").toObject().value("path").toString();
|
||||
{
|
||||
const QJsonArray destinations = install.value("destinations").toArray();
|
||||
t.installDestination = transform<std::vector>(destinations, [](const QJsonValue &v) {
|
||||
const QJsonObject o = v.toObject();
|
||||
return InstallDestination{o.value("path").toString(),
|
||||
o.value("backtrace").toInt(-1)};
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
const QJsonObject link = root.value("link").toObject();
|
||||
if (link.isEmpty()) {
|
||||
t.link = {};
|
||||
} else {
|
||||
LinkInfo info;
|
||||
info.language = link.value("language").toString();
|
||||
info.isLto = link.value("lto").toBool();
|
||||
info.sysroot = link.value("sysroot").toObject().value("path").toString();
|
||||
info.fragments = extractFragments(link);
|
||||
t.link = info;
|
||||
}
|
||||
}
|
||||
{
|
||||
const QJsonObject archive = root.value("archive").toObject();
|
||||
if (archive.isEmpty()) {
|
||||
t.archive = {};
|
||||
} else {
|
||||
ArchiveInfo info;
|
||||
info.isLto = archive.value("lto").toBool();
|
||||
info.fragments = extractFragments(archive);
|
||||
t.archive = info;
|
||||
}
|
||||
}
|
||||
{
|
||||
const QJsonArray dependencies = root.value("dependencies").toArray();
|
||||
t.dependencies = transform<std::vector>(dependencies, [](const QJsonValue &v) {
|
||||
const QJsonObject o = v.toObject();
|
||||
return DependencyInfo{o.value("id").toString(), o.value("backtrace").toInt(-1)};
|
||||
});
|
||||
}
|
||||
{
|
||||
const QJsonArray sources = root.value("sources").toArray();
|
||||
t.sources = transform<std::vector>(sources, [](const QJsonValue &v) {
|
||||
const QJsonObject o = v.toObject();
|
||||
return SourceInfo{o.value("path").toString(),
|
||||
o.value("compileGroupIndex").toInt(-1),
|
||||
o.value("sourceGroupIndex").toInt(-1),
|
||||
o.value("backtrace").toInt(-1),
|
||||
o.value("isGenerated").toBool()};
|
||||
});
|
||||
}
|
||||
{
|
||||
const QJsonArray sourceGroups = root.value("sourceGroups").toArray();
|
||||
t.sourceGroups = transform<std::vector>(sourceGroups, [](const QJsonValue &v) {
|
||||
const QJsonObject o = v.toObject();
|
||||
return o.value("name").toString();
|
||||
});
|
||||
}
|
||||
{
|
||||
const QJsonArray compileGroups = root.value("compileGroups").toArray();
|
||||
t.compileGroups = transform<std::vector>(compileGroups, [](const QJsonValue &v) {
|
||||
const QJsonObject o = v.toObject();
|
||||
return CompileInfo{
|
||||
transform<std::vector>(o.value("sourceIndexes").toArray(),
|
||||
[](const QJsonValue &v) { return v.toInt(-1); }),
|
||||
o.value("language").toString(),
|
||||
transform<QList>(o.value("compileCommandFragments").toArray(),
|
||||
[](const QJsonValue &v) {
|
||||
const QJsonObject o = v.toObject();
|
||||
return o.value("fragment").toString();
|
||||
}),
|
||||
transform<std::vector>(
|
||||
o.value("includes").toArray(),
|
||||
[](const QJsonValue &v) {
|
||||
const QJsonObject i = v.toObject();
|
||||
const QString path = i.value("path").toString();
|
||||
const bool isSystem = i.value("isSystem").toBool();
|
||||
const ProjectExplorer::HeaderPath
|
||||
hp(path,
|
||||
isSystem ? ProjectExplorer::HeaderPathType::System
|
||||
: ProjectExplorer::HeaderPathType::User);
|
||||
|
||||
return IncludeInfo{CppTools::RawProjectPart::frameworkDetectionHeuristic(hp),
|
||||
i.value("backtrace").toInt(-1)};
|
||||
}),
|
||||
transform<std::vector>(o.value("defines").toArray(),
|
||||
[](const QJsonValue &v) {
|
||||
const QJsonObject d = v.toObject();
|
||||
return DefineInfo{
|
||||
ProjectExplorer::Macro::fromKeyValue(
|
||||
d.value("define").toString()),
|
||||
d.value("backtrace").toInt(-1),
|
||||
};
|
||||
}),
|
||||
o.value("sysroot").toString(),
|
||||
};
|
||||
});
|
||||
}
|
||||
{
|
||||
const QJsonObject backtraceGraph = root.value("backtraceGraph").toObject();
|
||||
t.backtraceGraph.files = transform<std::vector>(backtraceGraph.value("files").toArray(),
|
||||
[](const QJsonValue &v) {
|
||||
return v.toString();
|
||||
});
|
||||
t.backtraceGraph.commands
|
||||
= transform<std::vector>(backtraceGraph.value("commands").toArray(),
|
||||
[](const QJsonValue &v) { return v.toString(); });
|
||||
t.backtraceGraph.nodes = transform<std::vector>(backtraceGraph.value("nodes").toArray(),
|
||||
[](const QJsonValue &v) {
|
||||
const QJsonObject o = v.toObject();
|
||||
return BacktraceNode{
|
||||
o.value("file").toInt(-1),
|
||||
o.value("line").toInt(-1),
|
||||
o.value("command").toInt(-1),
|
||||
o.value("parent").toInt(-1),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
int validateBacktraceGraph(const TargetDetails &t)
|
||||
{
|
||||
const int backtraceFilesCount = static_cast<int>(t.backtraceGraph.files.size());
|
||||
const int backtraceCommandsCount = static_cast<int>(t.backtraceGraph.commands.size());
|
||||
const int backtraceNodeCount = static_cast<int>(t.backtraceGraph.nodes.size());
|
||||
|
||||
int topLevelNodeCount = 0;
|
||||
for (const BacktraceNode &n : t.backtraceGraph.nodes) {
|
||||
if (n.parent == -1) {
|
||||
++topLevelNodeCount;
|
||||
}
|
||||
if (n.file < 0 || n.file >= backtraceFilesCount) {
|
||||
qCWarning(cmakeFileApi) << "BacktraceNode: file index" << n.file << "is broken.";
|
||||
return -1;
|
||||
}
|
||||
if (n.command < -1 || n.command >= backtraceCommandsCount) {
|
||||
qCWarning(cmakeFileApi) << "BacktraceNode: command index" << n.command << "is broken.";
|
||||
return -1;
|
||||
}
|
||||
if (n.parent < -1 || n.parent >= backtraceNodeCount) {
|
||||
qCWarning(cmakeFileApi) << "BacktraceNode: parent index" << n.parent << "is broken.";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (topLevelNodeCount == 0 && backtraceNodeCount > 0) { // This is a forest, not a tree
|
||||
qCWarning(cmakeFileApi) << "BacktraceNode: Invalid number of top level nodes"
|
||||
<< topLevelNodeCount;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return backtraceNodeCount;
|
||||
}
|
||||
|
||||
bool validateTargetDetails(const TargetDetails &t)
|
||||
{
|
||||
// The part filled in by the codemodel file has already been covered!
|
||||
|
||||
// Internal consistency of backtraceGraph:
|
||||
const int backtraceCount = validateBacktraceGraph(t);
|
||||
if (backtraceCount < 0)
|
||||
return false;
|
||||
|
||||
const int sourcesCount = static_cast<int>(t.sources.size());
|
||||
const int sourceGroupsCount = static_cast<int>(t.sourceGroups.size());
|
||||
const int compileGroupsCount = static_cast<int>(t.compileGroups.size());
|
||||
|
||||
if (t.backtrace < -1 || t.backtrace >= backtraceCount) {
|
||||
qCWarning(cmakeFileApi) << "TargetDetails" << t.name << ": backtrace index" << t.backtrace
|
||||
<< "is broken.";
|
||||
return false;
|
||||
}
|
||||
for (const InstallDestination &id : t.installDestination) {
|
||||
if (id.backtrace < -1 || id.backtrace >= backtraceCount) {
|
||||
qCWarning(cmakeFileApi) << "TargetDetails" << t.name << ": backtrace index"
|
||||
<< t.backtrace << "of install destination is broken.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const DependencyInfo &dep : t.dependencies) {
|
||||
if (dep.backtrace < -1 || dep.backtrace >= backtraceCount) {
|
||||
qCWarning(cmakeFileApi) << "TargetDetails" << t.name << ": backtrace index"
|
||||
<< t.backtrace << "of dependency is broken.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const SourceInfo &s : t.sources) {
|
||||
if (s.compileGroup < -1 || s.compileGroup >= compileGroupsCount) {
|
||||
qCWarning(cmakeFileApi) << "TargetDetails" << t.name << ": compile group index"
|
||||
<< s.compileGroup << "of source info is broken.";
|
||||
return false;
|
||||
}
|
||||
if (s.sourceGroup < -1 || s.sourceGroup >= sourceGroupsCount) {
|
||||
qCWarning(cmakeFileApi) << "TargetDetails" << t.name << ": source group index"
|
||||
<< s.sourceGroup << "of source info is broken.";
|
||||
return false;
|
||||
}
|
||||
if (s.backtrace < -1 || s.backtrace >= backtraceCount) {
|
||||
qCWarning(cmakeFileApi) << "TargetDetails" << t.name << ": backtrace index"
|
||||
<< s.backtrace << "of source info is broken.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const CompileInfo &cg : t.compileGroups) {
|
||||
for (int s : cg.sources) {
|
||||
if (s < 0 || s >= sourcesCount) {
|
||||
qCWarning(cmakeFileApi) << "TargetDetails" << t.name << ": sources index" << s
|
||||
<< "of compile group is broken.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const IncludeInfo &i : cg.includes) {
|
||||
if (i.backtrace < -1 || i.backtrace >= backtraceCount) {
|
||||
qCWarning(cmakeFileApi) << "TargetDetails" << t.name << ": includes/backtrace index"
|
||||
<< i.backtrace << "of compile group is broken.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const DefineInfo &d : cg.defines) {
|
||||
if (d.backtrace < -1 || d.backtrace >= backtraceCount) {
|
||||
qCWarning(cmakeFileApi) << "TargetDetails" << t.name << ": defines/backtrace index"
|
||||
<< d.backtrace << "of compile group is broken.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TargetDetails readTargetFile(const QString &targetFile, QString &errorMessage)
|
||||
{
|
||||
const QJsonDocument doc = readJsonFile(targetFile);
|
||||
const QJsonObject root = doc.object();
|
||||
|
||||
TargetDetails result = extractTargetDetails(root, errorMessage);
|
||||
if (errorMessage.isEmpty() && !validateTargetDetails(result)) {
|
||||
errorMessage = QCoreApplication::translate(
|
||||
"CMakeProjectManager::Internal",
|
||||
"Invalid target file generated by cmake: Broken indexes in target details.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// ReplyFileContents:
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
QString FileApiDetails::ReplyFileContents::jsonFile(const QString &kind, const QDir &replyDir) const
|
||||
{
|
||||
const auto ro = findOrDefault(replies, equal(&ReplyObject::kind, kind));
|
||||
if (ro.file.isEmpty())
|
||||
return QString();
|
||||
else
|
||||
return replyDir.absoluteFilePath(ro.file);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// FileApi:
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
FileApiParser::FileApiParser(const FilePath &sourceDirectory, const FilePath &buildDirectory)
|
||||
: m_sourceDirectory(sourceDirectory)
|
||||
, m_buildDirectory(buildDirectory)
|
||||
{
|
||||
setupCMakeFileApi();
|
||||
|
||||
QObject::connect(&m_watcher,
|
||||
&FileSystemWatcher::directoryChanged,
|
||||
this,
|
||||
&FileApiParser::replyDirectoryHasChanged);
|
||||
|
||||
m_watcher.addDirectory(cmakeReplyDirectory().toString(), FileSystemWatcher::WatchAllChanges);
|
||||
}
|
||||
|
||||
FilePath FileApiParser::cmakeReplyDirectory() const
|
||||
{
|
||||
return m_buildDirectory.pathAppended(CMAKE_RELATIVE_REPLY_PATH);
|
||||
}
|
||||
|
||||
FileApiParser::~FileApiParser() = default;
|
||||
|
||||
void FileApiParser::setupCMakeFileApi() const
|
||||
{
|
||||
const QDir buildDir = QDir(m_buildDirectory.toString());
|
||||
const QString relativeQueryPath = QString::fromLatin1(CMAKE_RELATIVE_QUERY_PATH);
|
||||
|
||||
buildDir.mkpath(relativeQueryPath);
|
||||
buildDir.mkpath(
|
||||
QString::fromLatin1(CMAKE_RELATIVE_REPLY_PATH)); // So that we have a directory to watch!
|
||||
|
||||
QDir queryDir = buildDir;
|
||||
queryDir.cd(relativeQueryPath);
|
||||
|
||||
if (!queryDir.exists()) {
|
||||
reportFileApiSetupFailure();
|
||||
return;
|
||||
}
|
||||
QTC_ASSERT(queryDir.exists(), );
|
||||
|
||||
bool failedBefore = false;
|
||||
for (const QString &fileName : QStringList({"cache-v2", "codemodel-v2", "cmakeFiles-v1"})) {
|
||||
const QString filePath = queryDir.filePath(fileName);
|
||||
|
||||
QFile f(filePath);
|
||||
if (!f.exists()) {
|
||||
f.open(QFile::WriteOnly);
|
||||
f.close();
|
||||
}
|
||||
|
||||
if (!f.exists() && !failedBefore) {
|
||||
failedBefore = true;
|
||||
reportFileApiSetupFailure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QStringList uniqueTargetFiles(const std::vector<Configuration> &configs)
|
||||
{
|
||||
QSet<QString> knownIds;
|
||||
QStringList files;
|
||||
for (const Configuration &config : configs) {
|
||||
for (const Target &t : config.targets) {
|
||||
const int knownCount = knownIds.count();
|
||||
knownIds.insert(t.id);
|
||||
if (knownIds.count() > knownCount) {
|
||||
files.append(t.jsonFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
FileApiData FileApiParser::parseData(const QFileInfo &replyFileInfo, QString &errorMessage)
|
||||
{
|
||||
QTC_CHECK(errorMessage.isEmpty());
|
||||
const QDir replyDir = replyFileInfo.dir();
|
||||
|
||||
FileApiData result;
|
||||
|
||||
result.replyFile = readReplyFile(replyFileInfo, errorMessage);
|
||||
result.cache = readCacheFile(result.replyFile.jsonFile("cache", replyDir), errorMessage);
|
||||
result.cmakeFiles = readCMakeFilesFile(result.replyFile.jsonFile("cmakeFiles", replyDir),
|
||||
errorMessage);
|
||||
result.codemodel = readCodemodelFile(result.replyFile.jsonFile("codemodel", replyDir),
|
||||
errorMessage);
|
||||
|
||||
const QStringList targetFiles = uniqueTargetFiles(result.codemodel);
|
||||
|
||||
for (const QString &targetFile : targetFiles) {
|
||||
QString targetErrorMessage;
|
||||
TargetDetails td = readTargetFile(replyDir.absoluteFilePath(targetFile), targetErrorMessage);
|
||||
if (targetErrorMessage.isEmpty()) {
|
||||
result.targetDetails.emplace_back(std::move(td));
|
||||
} else {
|
||||
qWarning() << "Failed to retrieve target data from cmake fileapi:"
|
||||
<< targetErrorMessage;
|
||||
errorMessage = targetErrorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QFileInfo FileApiParser::scanForCMakeReplyFile() const
|
||||
{
|
||||
QDir replyDir(cmakeReplyDirectory().toString());
|
||||
if (!replyDir.exists())
|
||||
return {};
|
||||
|
||||
const QFileInfoList fis = replyDir.entryInfoList(QStringList("index-*.json"),
|
||||
QDir::Files,
|
||||
QDir::Name);
|
||||
return fis.isEmpty() ? QFileInfo() : fis.last();
|
||||
}
|
||||
|
||||
void FileApiParser::replyDirectoryHasChanged(const QString &directory) const
|
||||
{
|
||||
if (directory == cmakeReplyDirectory().toString()) {
|
||||
QFileInfo fi = scanForCMakeReplyFile();
|
||||
if (fi.isFile() && shouldProcessFile(fi.filePath(), false)) {
|
||||
emit dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace CMakeProjectManager
|
||||
Reference in New Issue
Block a user