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 cmaketoolsettingsaccessor.cpp cmaketoolsettingsaccessor.h
configmodel.cpp configmodel.h configmodel.cpp configmodel.h
configmodelitemdelegate.cpp configmodelitemdelegate.h configmodelitemdelegate.cpp configmodelitemdelegate.h
fileapidataextractor.cpp fileapidataextractor.h
fileapiparser.cpp fileapiparser.h
fileapireader.cpp fileapireader.h
projecttreehelper.cpp projecttreehelper.h projecttreehelper.cpp projecttreehelper.h
servermode.cpp servermode.h servermode.cpp servermode.h
servermodereader.cpp servermodereader.h servermodereader.cpp servermodereader.h

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -84,6 +84,12 @@ QtcPlugin {
"configmodel.h", "configmodel.h",
"configmodelitemdelegate.cpp", "configmodelitemdelegate.cpp",
"configmodelitemdelegate.h", "configmodelitemdelegate.h",
"fileapidataextractor.cpp",
"fileapidataextractor.h",
"fileapiparser.cpp",
"fileapiparser.h",
"fileapireader.cpp",
"fileapireader.h",
"projecttreehelper.cpp", "projecttreehelper.cpp",
"projecttreehelper.h", "projecttreehelper.h",
"servermode.cpp", "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 CMakeProjectManager {
namespace Internal { 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, void addCMakeVFolder(FolderNode *base,
const Utils::FilePath &basePath, const Utils::FilePath &basePath,
int priority, int priority,
@@ -46,9 +56,7 @@ void addCMakeVFolder(FolderNode *base,
return; return;
FolderNode *folder = base; FolderNode *folder = base;
if (!displayName.isEmpty()) { if (!displayName.isEmpty()) {
auto newFolder = std::make_unique<VirtualFolderNode>(basePath); auto newFolder = createCMakeVFolder(basePath, priority, displayName);
newFolder->setPriority(priority);
newFolder->setDisplayName(displayName);
folder = newFolder.get(); folder = newFolder.get();
base->addNode(std::move(newFolder)); base->addNode(std::move(newFolder));
} }
@@ -166,7 +174,7 @@ CMakeTargetNode *createTargetNode(const QHash<Utils::FilePath, ProjectNode *> &c
} }
void addHeaderNodes(ProjectNode *root, void addHeaderNodes(ProjectNode *root,
const QList<FileNode *> knownHeaders, const QVector<FileNode *> knownHeaders,
const QList<const FileNode *> &allFiles) const QList<const FileNode *> &allFiles)
{ {
if (root->isEmpty()) if (root->isEmpty())

View File

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

View File

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

View File

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

View File

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

View File

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