CMake: Add initial fileapireader class

Change-Id: I620cba7cc1c2a5ac56789fa9770dce573c6b19cd
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Tobias Hunger
2019-06-13 14:24:04 +02:00
parent f02fcaf02c
commit a95eb53d3b
20 changed files with 2296 additions and 50 deletions

View File

@@ -37,6 +37,9 @@ add_qtc_plugin(CMakeProjectManager
cmaketoolsettingsaccessor.cpp cmaketoolsettingsaccessor.h
configmodel.cpp configmodel.h
configmodelitemdelegate.cpp configmodelitemdelegate.h
fileapidataextractor.cpp fileapidataextractor.h
fileapiparser.cpp fileapiparser.h
fileapireader.cpp fileapireader.h
projecttreehelper.cpp projecttreehelper.h
servermode.cpp servermode.h
servermodereader.cpp servermodereader.h

View File

@@ -302,14 +302,13 @@ void BuildDirManager::parse(int reparseParameters)
reparseParameters & REPARSE_FORCE_CONFIGURATION);
}
void BuildDirManager::generateProjectTree(CMakeProjectNode *root,
const QList<const FileNode *> &allFiles,
QString &errorMessage) const
std::unique_ptr<CMakeProjectNode> BuildDirManager::generateProjectTree(
const QList<const FileNode *> &allFiles, QString &errorMessage) const
{
QTC_ASSERT(!m_isHandlingError, return);
QTC_ASSERT(m_reader, return);
QTC_ASSERT(!m_isHandlingError, return {});
QTC_ASSERT(m_reader, return {});
m_reader->generateProjectTree(root, allFiles, errorMessage);
return m_reader->generateProjectTree(allFiles, errorMessage);
}
CppTools::RawProjectParts BuildDirManager::createRawProjectParts(QString &errorMessage) const

View File

@@ -78,8 +78,7 @@ public:
void parse(int reparseParameters);
void generateProjectTree(CMakeProjectNode *root,
const QList<const ProjectExplorer::FileNode *> &allFiles,
std::unique_ptr<CMakeProjectNode> generateProjectTree(const QList<const ProjectExplorer::FileNode *> &allFiles,
QString &errorMessage) const;
CppTools::RawProjectParts createRawProjectParts(QString &errorMessage) const;

View File

@@ -25,6 +25,7 @@
#include "builddirreader.h"
#include "fileapireader.h"
#include "servermodereader.h"
#include "tealeafreader.h"
@@ -43,6 +44,8 @@ std::unique_ptr<BuildDirReader> BuildDirReader::createReader(const BuildDirParam
{
CMakeTool *cmake = p.cmakeTool();
QTC_ASSERT(p.isValid() && cmake, return {});
if (cmake->hasFileApi())
return std::make_unique<FileApiReader>();
if (cmake->hasServerMode())
return std::make_unique<ServerModeReader>();
return std::make_unique<TeaLeafReader>();

View File

@@ -64,10 +64,10 @@ public:
virtual QList<CMakeBuildTarget> takeBuildTargets(QString &errorMessage) = 0;
virtual CMakeConfig takeParsedConfiguration(QString &errorMessage) = 0;
virtual void generateProjectTree(CMakeProjectNode *root,
const QList<const ProjectExplorer::FileNode *> &allFiles,
QString &errorMessage) = 0;
virtual CppTools::RawProjectParts createRawProjectParts(QString &errorMessage) const = 0;
virtual std::unique_ptr<CMakeProjectNode> generateProjectTree(
const QList<const ProjectExplorer::FileNode *> &allFiles, QString &errorMessage)
= 0;
virtual CppTools::RawProjectParts createRawProjectParts(QString &errorMessage) = 0;
signals:
void isReadyNow() const;

View File

@@ -401,9 +401,8 @@ CMakeProject::generateProjectTree(const QList<const FileNode *> &allFiles) const
if (m_buildDirManager.isParsing())
return nullptr;
auto root = std::make_unique<CMakeProjectNode>(projectDirectory());
QString errorMessage;
m_buildDirManager.generateProjectTree(root.get(), allFiles, errorMessage);
auto root = m_buildDirManager.generateProjectTree(allFiles, errorMessage);
checkAndReportError(errorMessage);
return root;
}

View File

@@ -34,6 +34,9 @@ HEADERS = builddirmanager.h \
cmakespecificsettingspage.h \
configmodel.h \
configmodelitemdelegate.h \
fileapidataextractor.h \
fileapiparser.h \
fileapireader.h \
projecttreehelper.h \
servermode.h \
servermodereader.h \
@@ -69,6 +72,9 @@ SOURCES = builddirmanager.cpp \
cmakespecificsettingspage.cpp \
configmodel.cpp \
configmodelitemdelegate.cpp \
fileapidataextractor.cpp \
fileapiparser.cpp \
fileapireader.cpp \
projecttreehelper.cpp \
servermode.cpp \
servermodereader.cpp \

View File

@@ -84,6 +84,12 @@ QtcPlugin {
"configmodel.h",
"configmodelitemdelegate.cpp",
"configmodelitemdelegate.h",
"fileapidataextractor.cpp",
"fileapidataextractor.h",
"fileapiparser.cpp",
"fileapiparser.h",
"fileapireader.cpp",
"fileapireader.h",
"projecttreehelper.cpp",
"projecttreehelper.h",
"servermode.cpp",

View File

@@ -0,0 +1,558 @@
/****************************************************************************
**
** 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 "fileapidataextractor.h"
#include "cmakeprojectnodes.h"
#include "projecttreehelper.h"
#include <projectexplorer/projectnodes.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <QDir>
using namespace ProjectExplorer;
using namespace Utils;
namespace {
using namespace CMakeProjectManager;
using namespace CMakeProjectManager::Internal;
using namespace CMakeProjectManager::Internal::FileApiDetails;
// --------------------------------------------------------------------
// Helpers:
// --------------------------------------------------------------------
class CMakeFileResult
{
public:
QSet<FilePath> cmakeFiles;
std::vector<std::unique_ptr<ProjectExplorer::FileNode>> cmakeNodesSource;
std::vector<std::unique_ptr<ProjectExplorer::FileNode>> cmakeNodesBuild;
std::vector<std::unique_ptr<ProjectExplorer::FileNode>> cmakeNodesOther;
std::vector<std::unique_ptr<ProjectExplorer::FileNode>> cmakeListNodes;
};
CMakeFileResult extractCMakeFilesData(const std::vector<FileApiDetails::CMakeFileInfo> &cmakefiles,
const FilePath &sourceDirectory,
const FilePath &buildDirectory)
{
CMakeFileResult result;
QDir sourceDir(sourceDirectory.toString());
QDir buildDir(buildDirectory.toString());
for (const CMakeFileInfo &info : cmakefiles) {
const FilePath sfn = FilePath::fromString(
QDir::cleanPath(sourceDir.absoluteFilePath(info.path)));
const int oldCount = result.cmakeFiles.count();
result.cmakeFiles.insert(sfn);
if (oldCount < result.cmakeFiles.count()) {
if (info.isCMake && !info.isCMakeListsDotTxt) {
// Skip files that cmake considers to be part of the installation -- but include
// CMakeLists.txt files. This unbreaks cmake binaries running from their own
// build directory.
continue;
}
auto node = std::make_unique<FileNode>(sfn, FileType::Project);
node->setIsGenerated(info.isGenerated
&& !info.isCMakeListsDotTxt); // CMakeLists.txt are never
// generated, independent
// what cmake thinks:-)
if (info.isCMakeListsDotTxt) {
result.cmakeListNodes.emplace_back(std::move(node));
} else if (sfn.isChildOf(sourceDir)) {
result.cmakeNodesSource.emplace_back(std::move(node));
} else if (sfn.isChildOf(buildDir)) {
result.cmakeNodesBuild.emplace_back(std::move(node));
} else {
result.cmakeNodesOther.emplace_back(std::move(node));
}
}
}
return result;
}
Configuration extractConfiguration(std::vector<Configuration> &codemodel, QString &errorMessage)
{
if (codemodel.size() == 0) {
qWarning() << "No configuration found!";
errorMessage = "No configuration found!";
return {};
}
if (codemodel.size() > 1)
qWarning() << "Multi-configuration generator found, ignoring all but first configuration";
Configuration result = std::move(codemodel[0]);
codemodel.clear();
return result;
}
class PreprocessedData
{
public:
CMakeProjectManager::CMakeConfig cache;
QSet<FilePath> cmakeFiles;
std::vector<std::unique_ptr<ProjectExplorer::FileNode>> cmakeNodesSource;
std::vector<std::unique_ptr<ProjectExplorer::FileNode>> cmakeNodesBuild;
std::vector<std::unique_ptr<ProjectExplorer::FileNode>> cmakeNodesOther;
std::vector<std::unique_ptr<ProjectExplorer::FileNode>> cmakeListNodes;
Configuration codemodel;
std::vector<TargetDetails> targetDetails;
};
PreprocessedData preprocess(FileApiData &data,
const FilePath &sourceDirectory,
const FilePath &buildDirectory,
QString &errorMessage)
{
PreprocessedData result;
result.cache = std::move(data.cache); // Make sure this is available, even when nothing else is
// Simplify to only one configuration:
result.codemodel = extractConfiguration(data.codemodel, errorMessage);
if (!errorMessage.isEmpty()) {
return result;
}
CMakeFileResult cmakeFileResult = extractCMakeFilesData(data.cmakeFiles,
sourceDirectory,
buildDirectory);
result.cmakeFiles = std::move(cmakeFileResult.cmakeFiles);
result.cmakeNodesSource = std::move(cmakeFileResult.cmakeNodesSource);
result.cmakeNodesBuild = std::move(cmakeFileResult.cmakeNodesBuild);
result.cmakeNodesOther = std::move(cmakeFileResult.cmakeNodesOther);
result.cmakeListNodes = std::move(cmakeFileResult.cmakeListNodes);
result.targetDetails = std::move(data.targetDetails);
return result;
}
QList<CMakeBuildTarget> generateBuildTargets(const PreprocessedData &input,
const FilePath &sourceDirectory)
{
QDir sourceDir(sourceDirectory.toString());
const QList<CMakeBuildTarget> result = transform<
QList>(input.targetDetails, [&sourceDir](const TargetDetails &t) -> CMakeBuildTarget {
CMakeBuildTarget ct;
ct.title = t.name;
ct.executable = t.artifacts.isEmpty() ? FilePath() : t.artifacts.at(0);
TargetType type = UtilityType;
if (t.type == "EXECUTABLE")
type = ExecutableType;
else if (t.type == "STATIC_LIBRARY")
type = StaticLibraryType;
else if (t.type == "OBJECT_LIBRARY")
type = ObjectLibraryType;
else if (t.type == "MODULE_LIBRARY" || t.type == "SHARED_LIBRARY")
type = DynamicLibraryType;
else
type = UtilityType;
ct.targetType = type;
if (t.artifacts.isEmpty()) {
ct.workingDirectory = t.buildDir;
} else {
ct.workingDirectory = FilePath::fromString(QDir::cleanPath(
QDir(t.buildDir.toString()).absoluteFilePath(t.artifacts.at(0).toString() + "/..")));
}
ct.sourceDirectory = FilePath::fromString(
QDir::cleanPath(sourceDir.absoluteFilePath(t.sourceDir.toString())));
return ct;
});
return result;
}
static QStringList splitFragments(const QStringList &fragments)
{
QStringList result;
for (const QString &f : fragments) {
result += QtcProcess::splitArgs(f);
}
return result;
}
CppTools::RawProjectParts generateRawProjectParts(const PreprocessedData &input,
const FilePath &sourceDirectory)
{
CppTools::RawProjectParts rpps;
int counter = 0;
for (const TargetDetails &t : input.targetDetails) {
QDir sourceDir(sourceDirectory.toString());
for (const CompileInfo &ci : t.compileGroups) {
if (ci.language != "C" && ci.language != "CXX" && ci.language != "CUDA")
continue; // No need to bother the C++ codemodel
// CMake users worked around Creator's inability of listing header files by creating
// custom targets with all the header files. This target breaks the code model, so
// keep quiet about it:-)
if (ci.defines.empty() && ci.includes.empty() && allOf(ci.sources, [t](const int sid) {
const SourceInfo &source = t.sources[static_cast<size_t>(sid)];
return Node::fileTypeForFileName(FilePath::fromString(source.path))
== FileType::Header;
})) {
qWarning() << "Not reporting all-header compilegroup of target" << t.name
<< "to code model.";
continue;
}
++counter;
CppTools::RawProjectPart rpp;
rpp.setProjectFileLocation(t.sourceDir.pathAppended("CMakeLists.txt").toString());
rpp.setBuildSystemTarget(CMakeTargetNode::generateId(t.sourceDir, t.name));
rpp.setDisplayName(t.id);
rpp.setMacros(transform<QVector>(ci.defines, &DefineInfo::define));
rpp.setHeaderPaths(transform<QVector>(ci.includes, &IncludeInfo::path));
CppTools::RawProjectPartFlags cProjectFlags;
cProjectFlags.commandLineFlags = splitFragments(ci.fragments);
rpp.setFlagsForC(cProjectFlags);
CppTools::RawProjectPartFlags cxxProjectFlags;
cxxProjectFlags.commandLineFlags = cProjectFlags.commandLineFlags;
rpp.setFlagsForCxx(cxxProjectFlags);
rpp.setFiles(transform<QList>(ci.sources, [&t, &sourceDir](const int si) {
return sourceDir.absoluteFilePath(t.sources[static_cast<size_t>(si)].path);
}));
const bool isExecutable = t.type == "EXECUTABLE";
rpp.setBuildTargetType(isExecutable ? CppTools::ProjectPart::Executable
: CppTools::ProjectPart::Library);
rpps.append(rpp);
}
}
return rpps;
}
FilePath directorySourceDir(const Configuration &c, const QDir &sourceDir, int directoryIndex)
{
const size_t di = static_cast<size_t>(directoryIndex);
QTC_ASSERT(di >= 0 && di < c.directories.size(), return FilePath());
return FilePath::fromString(
QDir::cleanPath(sourceDir.absoluteFilePath(c.directories[di].sourcePath)));
}
FilePath directoryBuildDir(const Configuration &c, const QDir &buildDir, int directoryIndex)
{
const size_t di = static_cast<size_t>(directoryIndex);
QTC_ASSERT(di >= 0 && di < c.directories.size(), return FilePath());
return FilePath::fromString(
QDir::cleanPath(buildDir.absoluteFilePath(c.directories[di].buildPath)));
}
void addProjects(const QHash<Utils::FilePath, ProjectNode *> &cmakeListsNodes,
const Configuration &config,
const QDir &sourceDir)
{
for (const FileApiDetails::Project &p : config.projects) {
if (p.parent == -1)
continue; // Top-level project has already been covered
FilePath dir = directorySourceDir(config, sourceDir, p.directories[0]);
createProjectNode(cmakeListsNodes, dir, p.name);
}
}
void addBacktraceInformation(FolderNode *node,
const BacktraceInfo &backtraces,
const QDir &sourceDir,
int backtraceIndex)
{
QList<FolderNode::LocationInfo> info;
// Set up a default target path:
FilePath targetPath = node->filePath().pathAppended("CMakeLists.txt");
while (backtraceIndex != -1) {
const size_t bi = static_cast<size_t>(backtraceIndex);
QTC_ASSERT((bi >= 0 && bi < backtraces.nodes.size()), break);
const BacktraceNode &btNode = backtraces.nodes[bi];
backtraceIndex = btNode.parent; // advance to next node
const size_t fileIndex = static_cast<size_t>(btNode.file);
QTC_ASSERT((fileIndex >= 0 && fileIndex < backtraces.files.size()), break);
const FilePath path = FilePath::fromString(
sourceDir.absoluteFilePath(backtraces.files[fileIndex]));
if (btNode.command < 0) {
// No command, skip: The file itself is already covered:-)
continue;
}
const size_t commandIndex = static_cast<size_t>(btNode.command);
QTC_ASSERT((commandIndex >= 0 && commandIndex < backtraces.commands.size()), break);
const QString command = backtraces.commands[commandIndex];
QString dn;
if (path == targetPath) {
if (btNode.line > 0) {
dn = QCoreApplication::translate("CMakeProjectManager::Internal::FileApiReader",
"%1 in line %2")
.arg(command)
.arg(btNode.line);
} else {
dn = command;
}
} else {
if (btNode.line > 0) {
dn = QCoreApplication::translate("CMakeProjectManager::Internal::FileApiReader",
"%1 in %2:%3")
.arg(command)
.arg(path.toUserOutput())
.arg(btNode.line);
} else {
dn = QCoreApplication::translate("CMakeProjectManager::Internal::FileApiReader",
"%1 in %2")
.arg(command)
.arg(path.toUserOutput());
}
}
info.append(FolderNode::LocationInfo(dn, path, btNode.line));
}
node->setLocationInfo(info);
}
QVector<FolderNode *> addSourceGroups(ProjectNode *targetRoot,
const TargetDetails &td,
const Utils::FileName &sourceDirectory)
{
QVector<FolderNode *> sourceGroupNodes;
if (td.sourceGroups.size() == 1) {
sourceGroupNodes.append(
targetRoot); // Only one source group, so do not bother to display any:-)
} else {
for (const QString &sg : td.sourceGroups) {
if (sg.isEmpty() || sg == "Source Files") {
sourceGroupNodes.append(targetRoot);
} else {
auto sgNode = createCMakeVFolder(sourceDirectory,
Node::DefaultFolderPriority + 5,
sg);
sgNode->setListInProject(false);
sourceGroupNodes.append(sgNode.get());
targetRoot->addNode(std::move(sgNode));
}
}
}
return sourceGroupNodes;
}
void addCompileGroups(ProjectNode *targetRoot,
const Utils::FilePath &topSourceDirectory,
const Utils::FilePath &sourceDirectory,
const Utils::FilePath &buildDirectory,
const TargetDetails &td,
QVector<FileNode *> &knownHeaderNodes)
{
const bool inSourceBuild = (sourceDirectory == buildDirectory);
const QDir currentSourceDir(sourceDirectory.toString());
std::vector<std::unique_ptr<FileNode>> toList;
QSet<Utils::FilePath> alreadyListed;
// Files already added by other configurations:
targetRoot->forEachGenericNode(
[&alreadyListed](const Node *n) { alreadyListed.insert(n->filePath()); });
QVector<FolderNode *> sourceGroupNodes = addSourceGroups(targetRoot, td, sourceDirectory);
const QDir topSourceDir(topSourceDirectory.toString());
std::vector<std::unique_ptr<FileNode>> buildFileNodes;
std::vector<std::unique_ptr<FileNode>> otherFileNodes;
for (const SourceInfo &si : td.sources) {
const FilePath sourcePath = FilePath::fromString(
QDir::cleanPath(topSourceDir.absoluteFilePath(si.path)));
// Filter out already known files:
const int count = alreadyListed.count();
alreadyListed.insert(sourcePath);
if (count == alreadyListed.count())
continue;
// Create FileNodes from the file
auto node = std::make_unique<FileNode>(sourcePath, Node::fileTypeForFileName(sourcePath));
node->setIsGenerated(si.isGenerated);
// Register headers:
if (node->fileType() == FileType::Header)
knownHeaderNodes.append(node.get());
// Where does the file node need to go?
if (sourcePath.isChildOf(buildDirectory) && !inSourceBuild) {
buildFileNodes.emplace_back(std::move(node));
} else if (sourcePath.isChildOf(sourceDirectory)) {
sourceGroupNodes[si.sourceGroup]->addNode(std::move(node));
} else {
otherFileNodes.emplace_back(std::move(node));
}
}
addCMakeVFolder(targetRoot,
buildDirectory,
100,
QCoreApplication::translate("CMakeProjectManager::Internal::FileApi",
"<Build Directory>"),
std::move(buildFileNodes));
addCMakeVFolder(targetRoot,
Utils::FilePath(),
10,
QCoreApplication::translate("CMakeProjectManager::Internal::FileApi",
"<Other Locations>"),
std::move(otherFileNodes));
}
void addTargets(const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes,
const Configuration &config,
const std::vector<TargetDetails> &targetDetails,
const FilePath &topSourceDir,
const QDir &sourceDir,
const QDir &buildDir,
QVector<ProjectExplorer::FileNode *> &knownHeaderNodes)
{
for (const FileApiDetails::Target &t : config.targets) {
const TargetDetails &td = Utils::findOrDefault(targetDetails,
Utils::equal(&TargetDetails::id, t.id));
const FilePath dir = directorySourceDir(config, sourceDir, t.directory);
CMakeTargetNode *tNode = createTargetNode(cmakeListsNodes, dir, t.name);
QTC_ASSERT(tNode, continue);
tNode->setTargetInformation(td.artifacts, td.type);
tNode->setBuildDirectory(directoryBuildDir(config, buildDir, t.directory));
addBacktraceInformation(tNode, td.backtraceGraph, sourceDir, td.backtrace);
addCompileGroups(tNode, topSourceDir, dir, tNode->buildDirectory(), td, knownHeaderNodes);
}
}
std::pair<std::unique_ptr<CMakeProjectNode>, QVector<FileNode *>> generateRootProjectNode(
PreprocessedData &data, const FilePath &sourceDirectory, const FilePath &buildDirectory)
{
std::pair<std::unique_ptr<CMakeProjectNode>, QVector<FileNode *>> result;
result.first = std::make_unique<CMakeProjectNode>(sourceDirectory);
const QDir sourceDir(sourceDirectory.toString());
const QDir buildDir(buildDirectory.toString());
const FileApiDetails::Project topLevelProject
= findOrDefault(data.codemodel.projects, equal(&FileApiDetails::Project::parent, -1));
if (!topLevelProject.name.isEmpty())
result.first->setDisplayName(topLevelProject.name);
QHash<FilePath, ProjectNode *> cmakeListsNodes = addCMakeLists(result.first.get(),
std::move(data.cmakeListNodes));
data.cmakeListNodes.clear(); // Remove all the nullptr in the vector...
QVector<FileNode *> knownHeaders;
addProjects(cmakeListsNodes, data.codemodel, sourceDir);
addTargets(cmakeListsNodes,
data.codemodel,
data.targetDetails,
sourceDirectory,
sourceDir,
buildDir,
knownHeaders);
// addHeaderNodes(root.get(), knownHeaders, allFiles);
if (!data.cmakeNodesSource.empty() || !data.cmakeNodesBuild.empty()
|| !data.cmakeNodesOther.empty())
addCMakeInputs(result.first.get(),
sourceDirectory,
buildDirectory,
std::move(data.cmakeNodesSource),
std::move(data.cmakeNodesBuild),
std::move(data.cmakeNodesOther));
data.cmakeNodesSource.clear(); // Remove all the nullptr in the vector...
data.cmakeNodesBuild.clear(); // Remove all the nullptr in the vector...
data.cmakeNodesOther.clear(); // Remove all the nullptr in the vector...
result.second = knownHeaders;
return result;
}
} // namespace
namespace CMakeProjectManager {
namespace Internal {
using namespace FileApiDetails;
// --------------------------------------------------------------------
// extractData:
// --------------------------------------------------------------------
FileApiQtcData extractData(FileApiData &input,
const FilePath &sourceDirectory,
const FilePath &buildDirectory)
{
FileApiQtcData result;
// Preprocess our input:
PreprocessedData data = preprocess(input, sourceDirectory, buildDirectory, result.errorMessage);
result.cache = std::move(data.cache); // Make sure this is available, even when nothing else is
if (!result.errorMessage.isEmpty()) {
return {};
}
result.buildTargets = generateBuildTargets(data, sourceDirectory);
result.cmakeFiles = std::move(data.cmakeFiles);
result.projectParts = generateRawProjectParts(data, sourceDirectory);
auto pair = generateRootProjectNode(data, sourceDirectory, buildDirectory);
result.rootProjectNode = std::move(pair.first);
result.knownHeaders = std::move(pair.second);
return result;
}
} // namespace Internal
} // namespace CMakeProjectManager

View File

@@ -0,0 +1,58 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "fileapiparser.h"
#include "cmakebuildtarget.h"
#include "cmakeprocess.h"
#include "cmakeprojectnodes.h"
#include <cpptools/cpprawprojectpart.h>
#include <memory>
namespace CMakeProjectManager {
namespace Internal {
class FileApiQtcData
{
public:
QString errorMessage;
CMakeConfig cache;
QSet<Utils::FilePath> cmakeFiles;
QList<CMakeBuildTarget> buildTargets;
CppTools::RawProjectParts projectParts;
std::unique_ptr<CMakeProjectNode> rootProjectNode;
QVector<ProjectExplorer::FileNode *> knownHeaders;
};
FileApiQtcData extractData(FileApiData &data,
const Utils::FilePath &sourceDirectory,
const Utils::FilePath &buildDirectory);
} // namespace Internal
} // namespace CMakeProjectManager

View 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

View File

@@ -0,0 +1,269 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "cmakeconfigitem.h"
#include <projectexplorer/headerpath.h>
#include <projectexplorer/projectmacro.h>
#include <utils/filesystemwatcher.h>
#include <utils/fileutils.h>
#include <QObject>
namespace CMakeProjectManager {
namespace Internal {
namespace FileApiDetails {
class ReplyObject
{
public:
QString kind;
QString file;
std::pair<int, int> version;
};
class ReplyFileContents
{
public:
QString generator;
QString cmakeExecutable;
QString cmakeRoot;
QVector<ReplyObject> replies;
QString jsonFile(const QString &kind, const QDir &replyDir) const;
};
class CMakeFileInfo
{
public:
QString path;
bool isCMake = false;
bool isCMakeListsDotTxt = false;
bool isExternal = false;
bool isGenerated = false;
};
class Directory
{
public:
QString buildPath;
QString sourcePath;
int parent = -1;
int project = -1;
std::vector<int> children;
std::vector<int> targets;
bool hasInstallRule = false;
};
class Project
{
public:
QString name;
int parent = -1;
std::vector<int> children;
std::vector<int> directories;
std::vector<int> targets;
};
class Target
{
public:
// From codemodel file:
QString name;
QString id;
int directory = -1;
int project = -1;
QString jsonFile;
};
class Configuration
{
public:
QString name;
std::vector<Directory> directories;
std::vector<Project> projects;
std::vector<Target> targets;
};
class InstallDestination
{
public:
QString path;
int backtrace;
};
class FragmentInfo
{
public:
QString fragment;
QString role;
};
class LinkInfo
{
public:
QString language;
std::vector<FragmentInfo> fragments;
bool isLto = false;
QString sysroot;
};
class ArchiveInfo
{
public:
std::vector<FragmentInfo> fragments;
bool isLto = false;
};
class DependencyInfo
{
public:
QString targetId;
int backtrace;
};
class SourceInfo
{
public:
QString path;
int compileGroup = -1;
int sourceGroup = -1;
int backtrace = -1;
bool isGenerated = false;
};
class IncludeInfo
{
public:
ProjectExplorer::HeaderPath path;
int backtrace;
};
class DefineInfo
{
public:
ProjectExplorer::Macro define;
int backtrace;
};
class CompileInfo
{
public:
std::vector<int> sources;
QString language;
QStringList fragments;
std::vector<IncludeInfo> includes;
std::vector<DefineInfo> defines;
QString sysroot;
};
class BacktraceNode
{
public:
int file = -1;
int line = -1;
int command = -1;
int parent = -1;
};
class BacktraceInfo
{
public:
std::vector<QString> commands;
std::vector<QString> files;
std::vector<BacktraceNode> nodes;
};
class TargetDetails
{
public:
QString name;
QString id;
QString type;
QString folderTargetProperty;
Utils::FilePath sourceDir;
Utils::FilePath buildDir;
int backtrace = -1;
bool isGeneratorProvided = false;
QString nameOnDisk;
QList<Utils::FilePath> artifacts;
QString installPrefix;
std::vector<InstallDestination> installDestination;
Utils::optional<LinkInfo> link;
Utils::optional<ArchiveInfo> archive;
std::vector<DependencyInfo> dependencies;
std::vector<SourceInfo> sources;
std::vector<QString> sourceGroups;
std::vector<CompileInfo> compileGroups;
BacktraceInfo backtraceGraph;
};
} // namespace FileApiDetails
class FileApiData
{
public:
FileApiDetails::ReplyFileContents replyFile;
CMakeConfig cache;
std::vector<FileApiDetails::CMakeFileInfo> cmakeFiles;
std::vector<FileApiDetails::Configuration> codemodel;
std::vector<FileApiDetails::TargetDetails> targetDetails;
};
class FileApiParser : public QObject
{
Q_OBJECT
public:
FileApiParser(const Utils::FilePath &sourceDirectory, const Utils::FilePath &buildDirectory);
~FileApiParser() final;
Utils::FilePath cmakeReplyDirectory() const;
QFileInfo scanForCMakeReplyFile() const;
static FileApiData parseData(const QFileInfo &replyFileInfo, QString &errorMessage);
signals:
void dataAvailable() const;
void errorOccurred(const QString &message) const;
void dirty() const;
private:
void setupCMakeFileApi() const;
const Utils::FilePath &m_sourceDirectory;
const Utils::FilePath &m_buildDirectory;
void replyDirectoryHasChanged(const QString &directory) const;
Utils::FileSystemWatcher m_watcher;
};
} // namespace Internal
} // namespace CMakeProjectManager

View File

@@ -0,0 +1,283 @@
/****************************************************************************
**
** 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 "fileapireader.h"
#include "cmakebuildconfiguration.h"
#include "cmakeprojectconstants.h"
#include "cmakeprojectmanager.h"
#include "fileapidataextractor.h"
#include "projecttreehelper.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/fileiconprovider.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/task.h>
#include <projectexplorer/taskhub.h>
#include <projectexplorer/toolchain.h>
#include <utils/algorithm.h>
#include <utils/optional.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <QDateTime>
#include <QLoggingCategory>
using namespace ProjectExplorer;
using namespace Utils;
namespace CMakeProjectManager {
namespace Internal {
Q_LOGGING_CATEGORY(cmakeFileApiMode, "qtc.cmake.fileApiMode", QtWarningMsg);
using namespace FileApiDetails;
// --------------------------------------------------------------------
// FileApiReader:
// --------------------------------------------------------------------
FileApiReader::FileApiReader()
{
connect(Core::EditorManager::instance(),
&Core::EditorManager::aboutToSave,
this,
[this](const Core::IDocument *document) {
if (m_cmakeFiles.contains(document->filePath()) || !m_parameters.cmakeTool()
|| !m_parameters.cmakeTool()->isAutoRun()) {
qCDebug(cmakeFileApiMode) << "FileApiReader: DIRTY SIGNAL";
emit dirty();
}
});
}
FileApiReader::~FileApiReader()
{
stop();
resetData();
}
void FileApiReader::setParameters(const BuildDirParameters &p)
{
qCDebug(cmakeFileApiMode)
<< "\n\n\n\n\n=============================================================\n";
// Update:
m_parameters = p;
qCDebug(cmakeFileApiMode) << "Work directory:" << m_parameters.workDirectory.toUserOutput();
resetData();
m_fileApi = std::make_unique<FileApiParser>(m_parameters.sourceDirectory, m_parameters.workDirectory);
connect(m_fileApi.get(), &FileApiParser::dirty, this, [this]() {
if (!m_isParsing)
emit dirty();
});
qCDebug(cmakeFileApiMode) << "FileApiReader: IS READY NOW SIGNAL";
emit isReadyNow();
}
bool FileApiReader::isCompatible(const BuildDirParameters &p)
{
const CMakeTool *cmakeTool = p.cmakeTool();
return cmakeTool && cmakeTool->hasFileApi();
}
void FileApiReader::resetData()
{
m_cmakeFiles.clear();
if (!m_parameters.sourceDirectory.isEmpty())
m_cmakeFiles.insert(m_parameters.sourceDirectory.pathAppended("CMakeLists.txt"));
m_cache.clear();
m_buildTargets.clear();
m_projectParts.clear();
m_rootProjectNode.reset();
m_knownHeaders.clear();
}
void FileApiReader::parse(bool forceCMakeRun, bool forceConfiguration)
{
qCDebug(cmakeFileApiMode) << "\n\nParse: ForceCMakeRun:" << forceCMakeRun
<< " - forceConfiguration:" << forceConfiguration;
startState();
if (forceConfiguration) {
// Initial create:
qCDebug(cmakeFileApiMode) << "FileApiReader: Starting CMake with forced configuration.";
startCMakeState(
CMakeProcess::toArguments(m_parameters.configuration, m_parameters.expander));
// Keep m_isParsing enabled!
return;
}
const QFileInfo replyFi = m_fileApi->scanForCMakeReplyFile();
const bool mustUpdate = forceCMakeRun || !replyFi.exists() || m_cmakeFiles.isEmpty()
|| anyOf(m_cmakeFiles, [&replyFi](const FilePath &f) {
return f.toFileInfo().lastModified() > replyFi.lastModified();
});
if (mustUpdate) {
qCDebug(cmakeFileApiMode) << "FileApiReader: Starting CMake with no arguments.";
startCMakeState(QStringList());
// Keep m_isParsing enabled!
return;
}
endState(replyFi);
}
void FileApiReader::stop()
{
m_cmakeProcess.reset();
}
bool FileApiReader::isParsing() const
{
return m_isParsing;
}
QList<CMakeBuildTarget> FileApiReader::takeBuildTargets(QString &errorMessage){
Q_UNUSED(errorMessage)
auto result = std::move(m_buildTargets);
m_buildTargets.clear();
return result;
}
CMakeConfig FileApiReader::takeParsedConfiguration(QString &errorMessage)
{
Q_UNUSED(errorMessage)
CMakeConfig cache = m_cache;
m_cache.clear();
return cache;
}
std::unique_ptr<CMakeProjectNode> FileApiReader::generateProjectTree(
const QList<const FileNode *> &allFiles, QString &errorMessage)
{
Q_UNUSED(errorMessage)
addHeaderNodes(m_rootProjectNode.get(), m_knownHeaders, allFiles);
return std::move(m_rootProjectNode);
}
CppTools::RawProjectParts FileApiReader::createRawProjectParts(QString &errorMessage)
{
Q_UNUSED(errorMessage)
CppTools::RawProjectParts result = std::move(m_projectParts);
m_projectParts.clear();
return result;
}
void FileApiReader::startState()
{
qCDebug(cmakeFileApiMode) << "FileApiReader: START STATE.";
QTC_ASSERT(!m_isParsing, return );
QTC_ASSERT(!m_future.has_value(), return );
m_isParsing = true;
qCDebug(cmakeFileApiMode) << "FileApiReader: CONFIGURATION STARTED SIGNAL";
emit configurationStarted();
}
void FileApiReader::endState(const QFileInfo &replyFi)
{
qCDebug(cmakeFileApiMode) << "FileApiReader: END STATE.";
QTC_ASSERT(m_isParsing, return );
QTC_ASSERT(!m_future.has_value(), return );
const FilePath sourceDirectory = m_parameters.sourceDirectory;
const FilePath buildDirectory = m_parameters.workDirectory;
m_future = runAsync(ProjectExplorerPlugin::sharedThreadPool(),
[replyFi, sourceDirectory, buildDirectory]() {
auto result = std::make_unique<FileApiQtcData>();
FileApiData data = FileApiParser::parseData(replyFi,
result->errorMessage);
if (!result->errorMessage.isEmpty()) {
qWarning() << result->errorMessage;
return result.release();
}
*result = extractData(data, sourceDirectory, buildDirectory);
if (!result->errorMessage.isEmpty()) {
qWarning() << result->errorMessage;
}
return result.release();
});
onFinished(m_future.value(), this, [this](const QFuture<FileApiQtcData *> &f) {
std::unique_ptr<FileApiQtcData> value(f.result()); // Adopt the pointer again:-)
m_future = {};
m_isParsing = false;
m_cache = std::move(value->cache);
m_cmakeFiles = std::move(value->cmakeFiles);
m_buildTargets = std::move(value->buildTargets);
m_projectParts = std::move(value->projectParts);
m_rootProjectNode = std::move(value->rootProjectNode);
m_knownHeaders = std::move(value->knownHeaders);
if (value->errorMessage.isEmpty()) {
emit this->dataAvailable();
} else {
emit this->errorOccured(value->errorMessage);
}
});
}
void FileApiReader::startCMakeState(const QStringList &configurationArguments)
{
qCDebug(cmakeFileApiMode) << "FileApiReader: START CMAKE STATE.";
QTC_ASSERT(!m_cmakeProcess, return );
m_cmakeProcess = std::make_unique<CMakeProcess>();
connect(m_cmakeProcess.get(), &CMakeProcess::finished, this, &FileApiReader::cmakeFinishedState);
qCDebug(cmakeFileApiMode) << ">>>>>> Running cmake with arguments:" << configurationArguments;
m_cmakeProcess->run(m_parameters, configurationArguments);
}
void FileApiReader::cmakeFinishedState(int code, QProcess::ExitStatus status)
{
qCDebug(cmakeFileApiMode) << "FileApiReader: CMAKE FINISHED STATE.";
Q_UNUSED(code)
Q_UNUSED(status)
endState(m_fileApi->scanForCMakeReplyFile());
}
} // namespace Internal
} // namespace CMakeProjectManager

View File

@@ -0,0 +1,96 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "builddirreader.h"
#include "fileapiparser.h"
#include "cmakeprocess.h"
#include <utils/optional.h>
#include <memory>
#include <QFuture>
namespace ProjectExplorer {
class ProjectNode;
}
namespace CMakeProjectManager {
namespace Internal {
class FileApiQtcData;
class FileApiReader : public BuildDirReader
{
Q_OBJECT
public:
FileApiReader();
~FileApiReader() final;
void setParameters(const BuildDirParameters &p) final;
bool isCompatible(const BuildDirParameters &p) final;
void resetData() final;
void parse(bool forceCMakeRun, bool forceConfiguration) final;
void stop() final;
bool isParsing() const final;
QList<CMakeBuildTarget> takeBuildTargets(QString &errorMessage) final;
CMakeConfig takeParsedConfiguration(QString &errorMessage) final;
std::unique_ptr<CMakeProjectNode> generateProjectTree(
const QList<const ProjectExplorer::FileNode *> &allFiles, QString &errorMessage) final;
CppTools::RawProjectParts createRawProjectParts(QString &errorMessage) final;
private:
void startState();
void endState(const QFileInfo &replyFi);
void startCMakeState(const QStringList &configurationArguments);
void cmakeFinishedState(int code, QProcess::ExitStatus status);
std::unique_ptr<CMakeProcess> m_cmakeProcess;
// cmake data:
CMakeConfig m_cache;
QSet<Utils::FilePath> m_cmakeFiles;
QList<CMakeBuildTarget> m_buildTargets;
CppTools::RawProjectParts m_projectParts;
std::unique_ptr<CMakeProjectNode> m_rootProjectNode;
QVector<ProjectExplorer::FileNode *> m_knownHeaders;
Utils::optional<QFuture<FileApiQtcData *>> m_future;
// Update related:
bool m_isParsing = false;
std::unique_ptr<FileApiParser> m_fileApi;
};
} // namespace Internal
} // namespace CMakeProjectManager

View File

@@ -36,6 +36,16 @@ using namespace ProjectExplorer;
namespace CMakeProjectManager {
namespace Internal {
std::unique_ptr<FolderNode> createCMakeVFolder(const Utils::FilePath &basePath,
int priority,
const QString &displayName)
{
auto newFolder = std::make_unique<VirtualFolderNode>(basePath);
newFolder->setPriority(priority);
newFolder->setDisplayName(displayName);
return std::move(newFolder);
}
void addCMakeVFolder(FolderNode *base,
const Utils::FilePath &basePath,
int priority,
@@ -46,9 +56,7 @@ void addCMakeVFolder(FolderNode *base,
return;
FolderNode *folder = base;
if (!displayName.isEmpty()) {
auto newFolder = std::make_unique<VirtualFolderNode>(basePath);
newFolder->setPriority(priority);
newFolder->setDisplayName(displayName);
auto newFolder = createCMakeVFolder(basePath, priority, displayName);
folder = newFolder.get();
base->addNode(std::move(newFolder));
}
@@ -166,7 +174,7 @@ CMakeTargetNode *createTargetNode(const QHash<Utils::FilePath, ProjectNode *> &c
}
void addHeaderNodes(ProjectNode *root,
const QList<FileNode *> knownHeaders,
const QVector<FileNode *> knownHeaders,
const QList<const FileNode *> &allFiles)
{
if (root->isEmpty())

View File

@@ -34,6 +34,10 @@
namespace CMakeProjectManager {
namespace Internal {
std::unique_ptr<ProjectExplorer::FolderNode> createCMakeVFolder(const Utils::FilePath &basePath,
int priority,
const QString &displayName);
void addCMakeVFolder(ProjectExplorer::FolderNode *base,
const Utils::FilePath &basePath,
int priority,
@@ -63,7 +67,7 @@ CMakeTargetNode *createTargetNode(
const QString &displayName);
void addHeaderNodes(ProjectExplorer::ProjectNode *root,
const QList<ProjectExplorer::FileNode *> knownHeaders,
const QVector<ProjectExplorer::FileNode *> knownHeaders,
const QList<const ProjectExplorer::FileNode *> &allFiles);
} // namespace Internal

View File

@@ -229,11 +229,12 @@ CMakeConfig ServerModeReader::takeParsedConfiguration(QString &errorMessage)
return config;
}
void ServerModeReader::generateProjectTree(CMakeProjectNode *root,
const QList<const FileNode *> &allFiles,
std::unique_ptr<CMakeProjectNode> ServerModeReader::generateProjectTree(const QList<const FileNode *> &allFiles,
QString &errorMessage)
{
Q_UNUSED(errorMessage)
auto root = std::make_unique<CMakeProjectNode>(m_parameters.sourceDirectory);
// Split up cmake inputs into useful chunks:
std::vector<std::unique_ptr<FileNode>> cmakeFilesSource;
std::vector<std::unique_ptr<FileNode>> cmakeFilesBuild;
@@ -259,20 +260,25 @@ void ServerModeReader::generateProjectTree(CMakeProjectNode *root,
if (topLevel)
root->setDisplayName(topLevel->name);
QHash<Utils::FilePath, ProjectNode *> cmakeListsNodes
= addCMakeLists(root, std::move(cmakeLists));
QList<FileNode *> knownHeaders;
QHash<Utils::FilePath, ProjectNode *> cmakeListsNodes = addCMakeLists(root.get(),
std::move(cmakeLists));
QVector<FileNode *> knownHeaders;
addProjects(cmakeListsNodes, m_projects, knownHeaders);
addHeaderNodes(root, knownHeaders, allFiles);
addHeaderNodes(root.get(), knownHeaders, allFiles);
if (cmakeFilesSource.size() > 0 || cmakeFilesBuild.size() > 0 || cmakeFilesOther.size() > 0)
addCMakeInputs(root, m_parameters.sourceDirectory, m_parameters.workDirectory,
std::move(cmakeFilesSource), std::move(cmakeFilesBuild),
addCMakeInputs(root.get(),
m_parameters.sourceDirectory,
m_parameters.workDirectory,
std::move(cmakeFilesSource),
std::move(cmakeFilesBuild),
std::move(cmakeFilesOther));
return root;
}
CppTools::RawProjectParts ServerModeReader::createRawProjectParts(QString &errorMessage) const
CppTools::RawProjectParts ServerModeReader::createRawProjectParts(QString &errorMessage)
{
Q_UNUSED(errorMessage)
CppTools::RawProjectParts rpps;
@@ -742,7 +748,7 @@ void ServerModeReader::fixTarget(ServerModeReader::Target *target) const
void ServerModeReader::addProjects(const QHash<Utils::FilePath, ProjectNode *> &cmakeListsNodes,
const QList<Project *> &projects,
QList<FileNode *> &knownHeaderNodes)
QVector<FileNode *> &knownHeaderNodes)
{
for (const Project *p : projects) {
createProjectNode(cmakeListsNodes, p->sourceDirectory, p->name);
@@ -750,9 +756,10 @@ void ServerModeReader::addProjects(const QHash<Utils::FilePath, ProjectNode *> &
}
}
void ServerModeReader::addTargets(const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes,
const QList<Target *> &targets,
QList<ProjectExplorer::FileNode *> &knownHeaderNodes)
void ServerModeReader::addTargets(
const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes,
const QList<Target *> &targets,
QVector<ProjectExplorer::FileNode *> &knownHeaderNodes)
{
for (const Target *t : targets) {
CMakeTargetNode *tNode = createTargetNode(cmakeListsNodes, t->sourceDirectory, t->name);
@@ -796,7 +803,7 @@ void ServerModeReader::addFileGroups(ProjectNode *targetRoot,
const Utils::FilePath &sourceDirectory,
const Utils::FilePath &buildDirectory,
const QList<ServerModeReader::FileGroup *> &fileGroups,
QList<FileNode *> &knownHeaderNodes)
QVector<FileNode *> &knownHeaderNodes)
{
std::vector<std::unique_ptr<FileNode>> toList;
QSet<Utils::FilePath> alreadyListed;

View File

@@ -57,10 +57,9 @@ public:
QList<CMakeBuildTarget> takeBuildTargets(QString &errorMessage) final;
CMakeConfig takeParsedConfiguration(QString &errorMessage) final;
void generateProjectTree(CMakeProjectNode *root,
const QList<const ProjectExplorer::FileNode *> &allFiles,
QString &errorMessage) final;
CppTools::RawProjectParts createRawProjectParts(QString &errorMessage) const final;
std::unique_ptr<CMakeProjectNode> generateProjectTree(
const QList<const ProjectExplorer::FileNode *> &allFiles, QString &errorMessage) final;
CppTools::RawProjectParts createRawProjectParts(QString &errorMessage) final;
private:
void createNewServer();
@@ -148,14 +147,15 @@ private:
void addProjects(const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes,
const QList<Project *> &projects,
QList<ProjectExplorer::FileNode *> &knownHeaderNodes);
QVector<ProjectExplorer::FileNode *> &knownHeaderNodes);
void addTargets(const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes,
const QList<Target *> &targets,
QList<ProjectExplorer::FileNode *> &knownHeaderNodes);
QVector<ProjectExplorer::FileNode *> &knownHeaderNodes);
void addFileGroups(ProjectExplorer::ProjectNode *targetRoot,
const Utils::FilePath &sourceDirectory,
const Utils::FilePath &buildDirectory, const QList<FileGroup *> &fileGroups,
QList<ProjectExplorer::FileNode *> &knowHeaderNodes);
const Utils::FilePath &buildDirectory,
const QList<FileGroup *> &fileGroups,
QVector<ProjectExplorer::FileNode *> &knowHeaderNodes);
std::unique_ptr<ServerMode> m_cmakeServer;
std::unique_ptr<QFutureInterface<void>> m_future;

View File

@@ -236,14 +236,14 @@ CMakeConfig TeaLeafReader::takeParsedConfiguration(QString &errorMessage)
return result;
}
void TeaLeafReader::generateProjectTree(CMakeProjectNode *root,
const QList<const FileNode *> &allFiles,
QString &errorMessage)
std::unique_ptr<CMakeProjectNode> TeaLeafReader::generateProjectTree(
const QList<const FileNode *> &allFiles, QString &errorMessage)
{
Q_UNUSED(errorMessage)
if (m_files.size() == 0)
return;
return {};
auto root = std::make_unique<CMakeProjectNode>(m_parameters.sourceDirectory);
root->setDisplayName(m_projectName);
// Delete no longer necessary file watcher based on m_cmakeFiles:
@@ -302,6 +302,8 @@ void TeaLeafReader::generateProjectTree(CMakeProjectNode *root,
return std::unique_ptr<FileNode>(fn->clone());
});
root->addNestedNodes(std::move(fileNodes), m_parameters.sourceDirectory);
return root;
}
static void processCMakeIncludes(const CMakeBuildTarget &cbt, const ToolChain *tc,
@@ -319,7 +321,7 @@ static void processCMakeIncludes(const CMakeBuildTarget &cbt, const ToolChain *t
}
}
CppTools::RawProjectParts TeaLeafReader::createRawProjectParts(QString &errorMessage) const
CppTools::RawProjectParts TeaLeafReader::createRawProjectParts(QString &errorMessage)
{
Q_UNUSED(errorMessage)
const ToolChain *tcCxx = ToolChainManager::findToolChain(m_parameters.cxxToolChainId);

View File

@@ -58,10 +58,9 @@ public:
QList<CMakeBuildTarget> takeBuildTargets(QString &errorMessage) final;
CMakeConfig takeParsedConfiguration(QString &errorMessage) final;
void generateProjectTree(CMakeProjectNode *root,
const QList<const ProjectExplorer::FileNode *> &allFiles,
QString &errorMessage) final;
CppTools::RawProjectParts createRawProjectParts(QString &errorMessage) const final;
std::unique_ptr<CMakeProjectNode> generateProjectTree(
const QList<const ProjectExplorer::FileNode *> &allFiles, QString &errorMessage) final;
CppTools::RawProjectParts createRawProjectParts(QString &errorMessage) final;
private:
void extractData();