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