2019-06-13 14:24:04 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** 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"
|
|
|
|
|
|
2020-07-31 17:41:28 +02:00
|
|
|
#include <app/app_version.h>
|
2019-06-13 14:24:04 +02:00
|
|
|
#include <coreplugin/messagemanager.h>
|
2019-08-28 18:22:45 +02:00
|
|
|
#include <projectexplorer/rawprojectpart.h>
|
2019-06-13 14:24:04 +02:00
|
|
|
|
|
|
|
|
#include <utils/algorithm.h>
|
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
|
|
|
|
|
#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";
|
|
|
|
|
|
2020-01-15 14:39:23 +01:00
|
|
|
static Q_LOGGING_CATEGORY(cmakeFileApi, "qtc.cmake.fileApi", QtWarningMsg);
|
2019-06-13 14:24:04 +02:00
|
|
|
|
2020-05-13 12:24:59 +02:00
|
|
|
const QStringList CMAKE_QUERY_FILENAMES = {"cache-v2", "codemodel-v2", "cmakeFiles-v1"};
|
|
|
|
|
|
2019-06-13 14:24:04 +02:00
|
|
|
// --------------------------------------------------------------------
|
|
|
|
|
// Helper:
|
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
|
|
2020-05-13 12:24:59 +02:00
|
|
|
static FilePath cmakeReplyDirectory(const FilePath &buildDirectory)
|
|
|
|
|
{
|
|
|
|
|
return buildDirectory.pathAppended(CMAKE_RELATIVE_REPLY_PATH);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-13 14:24:04 +02:00
|
|
|
static void reportFileApiSetupFailure()
|
|
|
|
|
{
|
2020-12-01 12:52:59 +01:00
|
|
|
Core::MessageManager::writeFlashing(
|
|
|
|
|
QCoreApplication::translate("CMakeProjectManager::Internal",
|
|
|
|
|
"Failed to set up CMake file API support. %1 cannot "
|
|
|
|
|
"extract project information.")
|
|
|
|
|
.arg(Core::Constants::IDE_DISPLAY_NAME));
|
2019-06-13 14:24:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
static QJsonDocument readJsonFile(const FilePath &filePath)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
2021-06-08 14:42:59 +02:00
|
|
|
qCDebug(cmakeFileApi) << "readJsonFile:" << filePath;
|
2021-06-16 09:06:34 +02:00
|
|
|
QTC_ASSERT(!filePath.isEmpty(), return {});
|
2019-06-13 14:24:04 +02:00
|
|
|
|
2021-09-09 12:29:22 +02:00
|
|
|
const QJsonDocument doc = QJsonDocument::fromJson(filePath.fileContents());
|
2019-06-13 14:24:04 +02:00
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
static ReplyFileContents readReplyFile(const FilePath &filePath, QString &errorMessage)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
2021-06-08 14:42:59 +02:00
|
|
|
const QJsonDocument document = readJsonFile(filePath);
|
2019-06-13 14:24:04 +02:00
|
|
|
static const QString msg = QCoreApplication::translate("CMakeProjectManager::Internal",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid reply file created by CMake.");
|
2019-06-13 14:24:04 +02:00
|
|
|
|
|
|
|
|
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();
|
2020-09-28 15:10:04 +02:00
|
|
|
result.ctestExecutable = paths.value("ctest").toString();
|
2019-06-13 14:24:04 +02:00
|
|
|
result.cmakeRoot = paths.value("root").toString();
|
|
|
|
|
}
|
|
|
|
|
const QJsonObject generator = cmakeObject.value("generator").toObject();
|
|
|
|
|
{
|
|
|
|
|
result.generator = generator.value("name").toString();
|
2021-01-14 16:38:55 +01:00
|
|
|
result.isMultiConfig = generator.value("multiConfig").toBool();
|
2019-06-13 14:24:04 +02:00
|
|
|
}
|
2021-08-19 18:51:18 +02:00
|
|
|
const QJsonObject version = cmakeObject.value("version").toObject();
|
|
|
|
|
{
|
|
|
|
|
int major = version.value("major").toInt();
|
|
|
|
|
int minor = version.value("minor").toInt();
|
|
|
|
|
int patch = version.value("patch").toInt();
|
|
|
|
|
result.cmakeVersion = QVersionNumber(major, minor, patch);
|
|
|
|
|
}
|
2019-06-13 14:24:04 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
static CMakeConfig readCacheFile(const FilePath &cacheFile, QString &errorMessage)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
|
|
|
|
CMakeConfig result;
|
|
|
|
|
|
|
|
|
|
const QJsonDocument doc = readJsonFile(cacheFile);
|
|
|
|
|
const QJsonObject root = doc.object();
|
|
|
|
|
|
|
|
|
|
if (!checkJsonObject(root, "cache", 2)) {
|
|
|
|
|
errorMessage = QCoreApplication::translate("CMakeProjectManager::Internal",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid cache file generated by CMake.");
|
2019-06-13 14:24:04 +02:00
|
|
|
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") {
|
2021-07-06 10:58:54 +02:00
|
|
|
const auto boolValue = CMakeConfigItem::toBool(nv.second);
|
2019-06-13 14:24:04 +02:00
|
|
|
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:
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
static std::vector<CMakeFileInfo> readCMakeFilesFile(const FilePath &cmakeFilesFile, QString &errorMessage)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
|
|
|
|
std::vector<CMakeFileInfo> result;
|
|
|
|
|
|
|
|
|
|
const QJsonDocument doc = readJsonFile(cmakeFilesFile);
|
|
|
|
|
const QJsonObject root = doc.object();
|
|
|
|
|
|
|
|
|
|
if (!checkJsonObject(root, "cmakeFiles", 1)) {
|
|
|
|
|
errorMessage = QCoreApplication::translate("CMakeProjectManager::Internal",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid cmakeFiles file generated by CMake.");
|
2019-06-13 14:24:04 +02:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QJsonArray inputs = root.value("inputs").toArray();
|
|
|
|
|
for (const QJsonValue &v : inputs) {
|
|
|
|
|
CMakeFileInfo info;
|
|
|
|
|
const QJsonObject input = v.toObject();
|
2021-09-09 15:50:19 +02:00
|
|
|
info.path = cmakeFilesFile.withNewPath(input.value("path").toString());
|
2019-06-13 14:24:04 +02:00
|
|
|
|
|
|
|
|
info.isCMake = input.value("isCMake").toBool();
|
2021-09-01 19:49:08 +02:00
|
|
|
const QString filename = info.path.fileName();
|
2019-06-13 14:24:04 +02:00
|
|
|
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",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake: No directories.");
|
2019-06-13 14:24:04 +02:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<Directory> result;
|
|
|
|
|
for (const QJsonValue &v : directories) {
|
|
|
|
|
const QJsonObject obj = v.toObject();
|
|
|
|
|
if (obj.isEmpty()) {
|
|
|
|
|
errorMessage = QCoreApplication::translate(
|
|
|
|
|
"CMakeProjectManager::Internal",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake: Empty directory object.");
|
2019-06-13 14:24:04 +02:00
|
|
|
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",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake: No projects.");
|
2019-06-13 14:24:04 +02:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<Project> result;
|
|
|
|
|
for (const QJsonValue &v : projects) {
|
|
|
|
|
const QJsonObject obj = v.toObject();
|
|
|
|
|
if (obj.isEmpty()) {
|
2020-06-10 11:31:15 +02:00
|
|
|
qCDebug(cmakeFileApi) << "Empty project skipped!";
|
2019-06-13 14:24:04 +02:00
|
|
|
errorMessage = QCoreApplication::translate(
|
|
|
|
|
"CMakeProjectManager::Internal",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake: Empty project object.");
|
2019-06-13 14:24:04 +02:00
|
|
|
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"));
|
|
|
|
|
|
2020-06-10 11:39:12 +02:00
|
|
|
if (project.directories.empty()) {
|
2020-06-10 11:31:15 +02:00
|
|
|
qCDebug(cmakeFileApi) << "Invalid project skipped!";
|
2019-06-13 14:24:04 +02:00
|
|
|
errorMessage = QCoreApplication::translate(
|
|
|
|
|
"CMakeProjectManager::Internal",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake: Broken project data.");
|
2019-06-13 14:24:04 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-10 11:31:15 +02:00
|
|
|
qCDebug(cmakeFileApi) << "Project read:" << project.name << project.directories;
|
2019-06-13 14:24:04 +02:00
|
|
|
result.emplace_back(std::move(project));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::vector<Target> extractTargets(const QJsonArray &targets, QString &errorMessage)
|
|
|
|
|
{
|
|
|
|
|
std::vector<Target> result;
|
|
|
|
|
for (const QJsonValue &v : targets) {
|
|
|
|
|
const QJsonObject obj = v.toObject();
|
|
|
|
|
if (obj.isEmpty()) {
|
|
|
|
|
errorMessage = QCoreApplication::translate(
|
|
|
|
|
"CMakeProjectManager::Internal",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake: Empty target object.");
|
2019-06-13 14:24:04 +02:00
|
|
|
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",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake: Broken target data.");
|
2019-06-13 14:24:04 +02:00
|
|
|
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",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake: No configurations.");
|
2019-06-13 14:24:04 +02:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<FileApiDetails::Configuration> result;
|
|
|
|
|
for (const QJsonValue &v : configs) {
|
|
|
|
|
const QJsonObject obj = v.toObject();
|
|
|
|
|
if (obj.isEmpty()) {
|
|
|
|
|
errorMessage = QCoreApplication::translate(
|
|
|
|
|
"CMakeProjectManager::Internal",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake: Empty configuration object.");
|
2019-06-13 14:24:04 +02:00
|
|
|
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",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake: Broken "
|
|
|
|
|
"indexes in directories, projects, or targets.");
|
2019-06-13 14:24:04 +02:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.emplace_back(std::move(config));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
static std::vector<Configuration> readCodemodelFile(const FilePath &codemodelFile,
|
2019-06-13 14:24:04 +02:00
|
|
|
QString &errorMessage)
|
|
|
|
|
{
|
|
|
|
|
const QJsonDocument doc = readJsonFile(codemodelFile);
|
|
|
|
|
const QJsonObject root = doc.object();
|
|
|
|
|
|
|
|
|
|
if (!checkJsonObject(root, "codemodel", 2)) {
|
|
|
|
|
errorMessage = QCoreApplication::translate("CMakeProjectManager::Internal",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid codemodel file generated by CMake.");
|
2019-06-13 14:24:04 +02:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return extractConfigurations(root.value("configurations").toArray(), errorMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TargetDetails:
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
static std::vector<FileApiDetails::FragmentInfo> extractFragments(const QJsonObject &obj)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
|
|
|
|
const QJsonArray fragments = obj.value("commandFragments").toArray();
|
2020-05-13 12:24:59 +02:00
|
|
|
return transform<std::vector>(fragments, [](const QJsonValue &v) {
|
2019-06-13 14:24:04 +02:00
|
|
|
const QJsonObject o = v.toObject();
|
|
|
|
|
return FileApiDetails::FragmentInfo{o.value("fragment").toString(),
|
|
|
|
|
o.value("role").toString()};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
static TargetDetails extractTargetDetails(const QJsonObject &root, QString &errorMessage)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
2019-08-28 18:22:45 +02:00
|
|
|
return IncludeInfo{
|
|
|
|
|
ProjectExplorer::RawProjectPart::frameworkDetectionHeuristic(hp),
|
|
|
|
|
i.value("backtrace").toInt(-1)};
|
2019-06-13 14:24:04 +02:00
|
|
|
}),
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
static int validateBacktraceGraph(const TargetDetails &t)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
static bool validateTargetDetails(const TargetDetails &t)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
static TargetDetails readTargetFile(const FilePath &targetFile, QString &errorMessage)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
|
|
|
|
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",
|
2019-10-28 16:12:21 +01:00
|
|
|
"Invalid target file generated by CMake: Broken indexes in target details.");
|
2019-06-13 14:24:04 +02:00
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
|
// ReplyFileContents:
|
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
FilePath FileApiDetails::ReplyFileContents::jsonFile(const QString &kind, const FilePath &replyDir) const
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
|
|
|
|
const auto ro = findOrDefault(replies, equal(&ReplyObject::kind, kind));
|
|
|
|
|
if (ro.file.isEmpty())
|
2021-06-08 14:42:59 +02:00
|
|
|
return {};
|
2019-06-13 14:24:04 +02:00
|
|
|
else
|
2021-06-08 14:42:59 +02:00
|
|
|
return (replyDir / ro.file).absoluteFilePath();
|
2019-06-13 14:24:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
|
// FileApi:
|
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
|
|
2020-05-13 12:24:59 +02:00
|
|
|
bool FileApiParser::setupCMakeFileApi(const FilePath &buildDirectory, Utils::FileSystemWatcher &watcher)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
2021-06-16 09:06:34 +02:00
|
|
|
// So that we have a directory to watch.
|
|
|
|
|
buildDirectory.pathAppended(CMAKE_RELATIVE_REPLY_PATH).ensureWritableDir();
|
2019-06-13 14:24:04 +02:00
|
|
|
|
2021-06-16 09:06:34 +02:00
|
|
|
FilePath queryDir = buildDirectory.pathAppended(CMAKE_RELATIVE_QUERY_PATH);
|
|
|
|
|
queryDir.ensureWritableDir();
|
2019-06-13 14:24:04 +02:00
|
|
|
|
|
|
|
|
if (!queryDir.exists()) {
|
|
|
|
|
reportFileApiSetupFailure();
|
2020-05-13 12:24:59 +02:00
|
|
|
return false;
|
2019-06-13 14:24:04 +02:00
|
|
|
}
|
2021-06-16 09:06:34 +02:00
|
|
|
QTC_ASSERT(queryDir.exists(), return false);
|
2019-06-13 14:24:04 +02:00
|
|
|
|
|
|
|
|
bool failedBefore = false;
|
2021-06-08 14:42:59 +02:00
|
|
|
for (const FilePath &filePath : cmakeQueryFilePaths(buildDirectory)) {
|
2021-06-29 09:40:56 +02:00
|
|
|
const bool success = filePath.ensureExistingFile();
|
|
|
|
|
if (!success && !failedBefore) {
|
2019-06-13 14:24:04 +02:00
|
|
|
failedBefore = true;
|
|
|
|
|
reportFileApiSetupFailure();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-13 12:24:59 +02:00
|
|
|
|
2021-06-29 09:45:30 +02:00
|
|
|
watcher.addDirectory(cmakeReplyDirectory(buildDirectory).path(), FileSystemWatcher::WatchAllChanges);
|
2020-05-13 12:24:59 +02:00
|
|
|
return true;
|
2019-06-13 14:24:04 +02:00
|
|
|
}
|
|
|
|
|
|
2021-01-14 16:38:55 +01:00
|
|
|
static QStringList uniqueTargetFiles(const Configuration &config)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
|
|
|
|
QSet<QString> knownIds;
|
|
|
|
|
QStringList files;
|
2021-01-14 16:38:55 +01:00
|
|
|
for (const Target &t : config.targets) {
|
|
|
|
|
const int knownCount = knownIds.count();
|
|
|
|
|
knownIds.insert(t.id);
|
|
|
|
|
if (knownIds.count() > knownCount) {
|
|
|
|
|
files.append(t.jsonFile);
|
2019-06-13 14:24:04 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return files;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-31 15:09:28 +02:00
|
|
|
FileApiData FileApiParser::parseData(QFutureInterface<std::shared_ptr<FileApiQtcData>> &fi,
|
2021-06-08 14:42:59 +02:00
|
|
|
const FilePath &replyFilePath,
|
2021-05-31 15:09:28 +02:00
|
|
|
const QString &cmakeBuildType,
|
2021-01-14 16:38:55 +01:00
|
|
|
QString &errorMessage)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
|
|
|
|
QTC_CHECK(errorMessage.isEmpty());
|
2021-06-08 14:42:59 +02:00
|
|
|
QTC_CHECK(!replyFilePath.needsDevice());
|
|
|
|
|
const FilePath replyDir = replyFilePath.parentDir();
|
2019-06-13 14:24:04 +02:00
|
|
|
|
|
|
|
|
FileApiData result;
|
|
|
|
|
|
2021-05-31 15:09:28 +02:00
|
|
|
const auto cancelCheck = [&fi, &errorMessage]() -> bool {
|
|
|
|
|
if (fi.isCanceled()) {
|
|
|
|
|
errorMessage = FileApiParser::tr("CMake parsing was cancelled.");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
result.replyFile = readReplyFile(replyFilePath, errorMessage);
|
2021-05-31 15:09:28 +02:00
|
|
|
if (cancelCheck())
|
|
|
|
|
return {};
|
2019-06-13 14:24:04 +02:00
|
|
|
result.cache = readCacheFile(result.replyFile.jsonFile("cache", replyDir), errorMessage);
|
2021-05-31 15:09:28 +02:00
|
|
|
if (cancelCheck())
|
|
|
|
|
return {};
|
2019-06-13 14:24:04 +02:00
|
|
|
result.cmakeFiles = readCMakeFilesFile(result.replyFile.jsonFile("cmakeFiles", replyDir),
|
|
|
|
|
errorMessage);
|
2021-05-31 15:09:28 +02:00
|
|
|
if (cancelCheck())
|
|
|
|
|
return {};
|
2021-01-14 16:38:55 +01:00
|
|
|
auto codeModels = readCodemodelFile(result.replyFile.jsonFile("codemodel", replyDir),
|
2019-06-13 14:24:04 +02:00
|
|
|
errorMessage);
|
|
|
|
|
|
2021-01-14 16:38:55 +01:00
|
|
|
if (codeModels.size() == 0) {
|
|
|
|
|
errorMessage = "No CMake configuration found!";
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto it = std::find_if(codeModels.cbegin(), codeModels.cend(),
|
2021-03-05 12:20:14 +01:00
|
|
|
[cmakeBuildType](const Configuration& cfg) {
|
|
|
|
|
return QString::compare(cfg.name, cmakeBuildType, Qt::CaseInsensitive) == 0;
|
|
|
|
|
});
|
2021-01-14 16:38:55 +01:00
|
|
|
if (it == codeModels.cend()) {
|
2021-03-23 12:28:09 +01:00
|
|
|
QStringList buildTypes;
|
|
|
|
|
for (const Configuration &cfg: codeModels)
|
|
|
|
|
buildTypes << cfg.name;
|
|
|
|
|
|
|
|
|
|
if (result.replyFile.isMultiConfig) {
|
|
|
|
|
errorMessage = tr("No \"%1\" CMake configuration found. Available configurations: \"%2\".\n"
|
|
|
|
|
"Make sure that CMAKE_CONFIGURATION_TYPES variable contains the \"Build type\" field.")
|
|
|
|
|
.arg(cmakeBuildType)
|
|
|
|
|
.arg(buildTypes.join(", "));
|
|
|
|
|
} else {
|
|
|
|
|
errorMessage = tr("No \"%1\" CMake configuration found. Available configuration: \"%2\".\n"
|
|
|
|
|
"Make sure that CMAKE_BUILD_TYPE variable matches the \"Build type\" field.")
|
|
|
|
|
.arg(cmakeBuildType)
|
|
|
|
|
.arg(buildTypes.join(", "));
|
|
|
|
|
}
|
2021-01-14 16:38:55 +01:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
result.codemodel = std::move(*it);
|
2021-05-31 15:09:28 +02:00
|
|
|
if (cancelCheck())
|
|
|
|
|
return {};
|
2021-01-14 16:38:55 +01:00
|
|
|
|
2019-06-13 14:24:04 +02:00
|
|
|
const QStringList targetFiles = uniqueTargetFiles(result.codemodel);
|
|
|
|
|
|
|
|
|
|
for (const QString &targetFile : targetFiles) {
|
2021-05-31 15:09:28 +02:00
|
|
|
if (cancelCheck())
|
|
|
|
|
return {};
|
2019-06-13 14:24:04 +02:00
|
|
|
QString targetErrorMessage;
|
2021-06-08 14:42:59 +02:00
|
|
|
TargetDetails td = readTargetFile((replyDir / targetFile).absoluteFilePath(), targetErrorMessage);
|
2019-06-13 14:24:04 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
FilePath FileApiParser::scanForCMakeReplyFile(const FilePath &buildDirectory)
|
2019-06-13 14:24:04 +02:00
|
|
|
{
|
2021-06-29 14:36:23 +02:00
|
|
|
const FilePath replyDir = cmakeReplyDirectory(buildDirectory);
|
2019-06-13 14:24:04 +02:00
|
|
|
if (!replyDir.exists())
|
|
|
|
|
return {};
|
|
|
|
|
|
2021-06-29 14:36:23 +02:00
|
|
|
const FilePaths entries = replyDir.dirEntries({"index-*.json"}, QDir::Files, QDir::Name);
|
|
|
|
|
return entries.isEmpty() ? FilePath() : entries.first();
|
2019-06-13 14:24:04 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:42:59 +02:00
|
|
|
FilePaths FileApiParser::cmakeQueryFilePaths(const FilePath &buildDirectory)
|
2019-06-21 14:14:31 +02:00
|
|
|
{
|
2021-06-16 09:06:34 +02:00
|
|
|
FilePath queryDir = buildDirectory / CMAKE_RELATIVE_QUERY_PATH;
|
2021-06-08 14:42:59 +02:00
|
|
|
return transform(CMAKE_QUERY_FILENAMES, [&queryDir](const QString &name) {
|
2021-09-08 16:13:43 +02:00
|
|
|
return queryDir.resolvePath(FilePath::fromString(name));
|
2021-06-08 14:42:59 +02:00
|
|
|
});
|
2019-06-21 14:14:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-06-13 14:24:04 +02:00
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace CMakeProjectManager
|