diff options
author | Tobias Hunger <tobias.hunger@qt.io> | 2019-06-13 14:24:04 +0200 |
---|---|---|
committer | Tobias Hunger <tobias.hunger@qt.io> | 2019-06-20 12:25:36 +0000 |
commit | a95eb53d3ba880efd880616a2725ac7657dffea2 (patch) | |
tree | 7c3357c6e5a519adb64ee76676d7e16ae0126304 | |
parent | f02fcaf02c4a023efc102fae230943a0c0f1458f (diff) | |
download | qt-creator-a95eb53d3ba880efd880616a2725ac7657dffea2.tar.gz |
CMake: Add initial fileapireader class
Change-Id: I620cba7cc1c2a5ac56789fa9770dce573c6b19cd
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
20 files changed, 2296 insertions, 50 deletions
diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index 8a5abb739b..93113b643b 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -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 diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.cpp b/src/plugins/cmakeprojectmanager/builddirmanager.cpp index 4c06e4fddd..da2dcc5458 100644 --- a/src/plugins/cmakeprojectmanager/builddirmanager.cpp +++ b/src/plugins/cmakeprojectmanager/builddirmanager.cpp @@ -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 diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.h b/src/plugins/cmakeprojectmanager/builddirmanager.h index 0b364c764a..a7dca6db9a 100644 --- a/src/plugins/cmakeprojectmanager/builddirmanager.h +++ b/src/plugins/cmakeprojectmanager/builddirmanager.h @@ -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; diff --git a/src/plugins/cmakeprojectmanager/builddirreader.cpp b/src/plugins/cmakeprojectmanager/builddirreader.cpp index c0dea25350..6c71218ec7 100644 --- a/src/plugins/cmakeprojectmanager/builddirreader.cpp +++ b/src/plugins/cmakeprojectmanager/builddirreader.cpp @@ -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>(); diff --git a/src/plugins/cmakeprojectmanager/builddirreader.h b/src/plugins/cmakeprojectmanager/builddirreader.h index 04f243df79..755e7ed095 100644 --- a/src/plugins/cmakeprojectmanager/builddirreader.h +++ b/src/plugins/cmakeprojectmanager/builddirreader.h @@ -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; diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index 34d546335e..6c6b9f4c37 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -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; } diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro index 07ee7f3555..6407308ad9 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro @@ -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 \ diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index c2352684b0..68f8fc47d2 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -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", diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp new file mode 100644 index 0000000000..aa881e856e --- /dev/null +++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp @@ -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 diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.h b/src/plugins/cmakeprojectmanager/fileapidataextractor.h new file mode 100644 index 0000000000..b84abbf7c8 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.h @@ -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 diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.cpp b/src/plugins/cmakeprojectmanager/fileapiparser.cpp new file mode 100644 index 0000000000..19af8d2a72 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/fileapiparser.cpp @@ -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 diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.h b/src/plugins/cmakeprojectmanager/fileapiparser.h new file mode 100644 index 0000000000..3a4fdfbd82 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/fileapiparser.h @@ -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 diff --git a/src/plugins/cmakeprojectmanager/fileapireader.cpp b/src/plugins/cmakeprojectmanager/fileapireader.cpp new file mode 100644 index 0000000000..957ebaaea0 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/fileapireader.cpp @@ -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 diff --git a/src/plugins/cmakeprojectmanager/fileapireader.h b/src/plugins/cmakeprojectmanager/fileapireader.h new file mode 100644 index 0000000000..b6e2218f0f --- /dev/null +++ b/src/plugins/cmakeprojectmanager/fileapireader.h @@ -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 diff --git a/src/plugins/cmakeprojectmanager/projecttreehelper.cpp b/src/plugins/cmakeprojectmanager/projecttreehelper.cpp index c1a3abaaae..1bc86189d7 100644 --- a/src/plugins/cmakeprojectmanager/projecttreehelper.cpp +++ b/src/plugins/cmakeprojectmanager/projecttreehelper.cpp @@ -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()) diff --git a/src/plugins/cmakeprojectmanager/projecttreehelper.h b/src/plugins/cmakeprojectmanager/projecttreehelper.h index 79eeecf0da..58266cd401 100644 --- a/src/plugins/cmakeprojectmanager/projecttreehelper.h +++ b/src/plugins/cmakeprojectmanager/projecttreehelper.h @@ -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 diff --git a/src/plugins/cmakeprojectmanager/servermodereader.cpp b/src/plugins/cmakeprojectmanager/servermodereader.cpp index 7dd06ef0ea..d4104dbe1d 100644 --- a/src/plugins/cmakeprojectmanager/servermodereader.cpp +++ b/src/plugins/cmakeprojectmanager/servermodereader.cpp @@ -229,11 +229,12 @@ CMakeConfig ServerModeReader::takeParsedConfiguration(QString &errorMessage) return config; } -void ServerModeReader::generateProjectTree(CMakeProjectNode *root, - const QList<const FileNode *> &allFiles, +std::unique_ptr<CMakeProjectNode> ServerModeReader::generateProjectTree(const QList<const FileNode *> &allFiles, QString &errorMessage) { Q_UNUSED(errorMessage) + auto root = std::make_unique<CMakeProjectNode>(m_parameters.sourceDirectory); + // Split up cmake inputs into useful chunks: std::vector<std::unique_ptr<FileNode>> cmakeFilesSource; std::vector<std::unique_ptr<FileNode>> cmakeFilesBuild; @@ -259,20 +260,25 @@ void ServerModeReader::generateProjectTree(CMakeProjectNode *root, if (topLevel) root->setDisplayName(topLevel->name); - QHash<Utils::FilePath, ProjectNode *> cmakeListsNodes - = addCMakeLists(root, std::move(cmakeLists)); - QList<FileNode *> knownHeaders; + QHash<Utils::FilePath, ProjectNode *> cmakeListsNodes = addCMakeLists(root.get(), + std::move(cmakeLists)); + QVector<FileNode *> knownHeaders; addProjects(cmakeListsNodes, m_projects, knownHeaders); - addHeaderNodes(root, knownHeaders, allFiles); + addHeaderNodes(root.get(), knownHeaders, allFiles); if (cmakeFilesSource.size() > 0 || cmakeFilesBuild.size() > 0 || cmakeFilesOther.size() > 0) - addCMakeInputs(root, m_parameters.sourceDirectory, m_parameters.workDirectory, - std::move(cmakeFilesSource), std::move(cmakeFilesBuild), + addCMakeInputs(root.get(), + m_parameters.sourceDirectory, + m_parameters.workDirectory, + std::move(cmakeFilesSource), + std::move(cmakeFilesBuild), std::move(cmakeFilesOther)); + + return root; } -CppTools::RawProjectParts ServerModeReader::createRawProjectParts(QString &errorMessage) const +CppTools::RawProjectParts ServerModeReader::createRawProjectParts(QString &errorMessage) { Q_UNUSED(errorMessage) CppTools::RawProjectParts rpps; @@ -742,7 +748,7 @@ void ServerModeReader::fixTarget(ServerModeReader::Target *target) const void ServerModeReader::addProjects(const QHash<Utils::FilePath, ProjectNode *> &cmakeListsNodes, const QList<Project *> &projects, - QList<FileNode *> &knownHeaderNodes) + QVector<FileNode *> &knownHeaderNodes) { for (const Project *p : projects) { createProjectNode(cmakeListsNodes, p->sourceDirectory, p->name); @@ -750,9 +756,10 @@ void ServerModeReader::addProjects(const QHash<Utils::FilePath, ProjectNode *> & } } -void ServerModeReader::addTargets(const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes, - const QList<Target *> &targets, - QList<ProjectExplorer::FileNode *> &knownHeaderNodes) +void ServerModeReader::addTargets( + const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes, + const QList<Target *> &targets, + QVector<ProjectExplorer::FileNode *> &knownHeaderNodes) { for (const Target *t : targets) { CMakeTargetNode *tNode = createTargetNode(cmakeListsNodes, t->sourceDirectory, t->name); @@ -796,7 +803,7 @@ void ServerModeReader::addFileGroups(ProjectNode *targetRoot, const Utils::FilePath &sourceDirectory, const Utils::FilePath &buildDirectory, const QList<ServerModeReader::FileGroup *> &fileGroups, - QList<FileNode *> &knownHeaderNodes) + QVector<FileNode *> &knownHeaderNodes) { std::vector<std::unique_ptr<FileNode>> toList; QSet<Utils::FilePath> alreadyListed; diff --git a/src/plugins/cmakeprojectmanager/servermodereader.h b/src/plugins/cmakeprojectmanager/servermodereader.h index a3b29fd871..17737990f0 100644 --- a/src/plugins/cmakeprojectmanager/servermodereader.h +++ b/src/plugins/cmakeprojectmanager/servermodereader.h @@ -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; diff --git a/src/plugins/cmakeprojectmanager/tealeafreader.cpp b/src/plugins/cmakeprojectmanager/tealeafreader.cpp index 2ed39cfb74..4e355a003c 100644 --- a/src/plugins/cmakeprojectmanager/tealeafreader.cpp +++ b/src/plugins/cmakeprojectmanager/tealeafreader.cpp @@ -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); diff --git a/src/plugins/cmakeprojectmanager/tealeafreader.h b/src/plugins/cmakeprojectmanager/tealeafreader.h index d1ad3026eb..db64cbaf9a 100644 --- a/src/plugins/cmakeprojectmanager/tealeafreader.h +++ b/src/plugins/cmakeprojectmanager/tealeafreader.h @@ -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(); |