Files
qt-creator/src/plugins/cmakeprojectmanager/fileapiparser.cpp
Tobias Hunger eab0df22f9 CMake: Make fileapi not race against its own reply file detection
Fix broken logic to prevent CMake fileapi from detecting the change
its own cmake run triggered via file watching.

Remember the last file that was parsed and do not attempt to parse
this again. Remember the file on a per-project basis, too:-)

Change-Id: Ia6e155b65d77994f6e3d2a3677f770a4ba53539d
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-07-25 13:32:21 +00:00

956 lines
36 KiB
C++

/****************************************************************************
**
** 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 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 : cmakeQueryFileNames()) {
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();
}
QStringList FileApiParser::cmakeQueryFileNames() const
{
return {"cache-v2", "codemodel-v2", "cmakeFiles-v1"};
}
QStringList FileApiParser::cmakeQueryFilePaths() const
{
QDir queryDir(QDir::cleanPath(m_sourceDirectory.toString() + "/"
+ QString::fromLatin1(CMAKE_RELATIVE_QUERY_PATH)));
return transform(cmakeQueryFileNames(),
[&queryDir](const QString &name) { return queryDir.absoluteFilePath(name); });
}
void FileApiParser::setParsedReplyFilePath(const QString &filePath)
{
m_lastParsedReplyFile = filePath;
}
void FileApiParser::replyDirectoryHasChanged(const QString &directory) const
{
if (directory == cmakeReplyDirectory().toString()) {
QFileInfo fi = scanForCMakeReplyFile();
if (fi.isFile() && fi.filePath() != m_lastParsedReplyFile) {
emit dirty();
}
}
}
} // namespace Internal
} // namespace CMakeProjectManager