Files
qt-creator/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp
Tobias Hunger 5bcd59c94f CMake: Simplify buildkey generation
Just use the target name as buildkey. This is unique in cmake projects, so
there is no need to mangle the source directory into the whole thing.

This is a problem since different readers might report different source
directories. That will then result in RunConfigurations getting duplicated
after switching the reader types.

Task-number: QTCREATORBUG-22129
Change-Id: I849ab68f221d732341e98faa9a4e757d3a495b2a
Reviewed-by: hjk <hjk@qt.io>
2019-06-20 14:41:04 +00:00

559 lines
22 KiB
C++

/****************************************************************************
**
** 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(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,
QSet<FilePath> &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.insert(node->filePath());
// 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,
QSet<FilePath> &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>, QSet<FilePath>> generateRootProjectNode(
PreprocessedData &data, const FilePath &sourceDirectory, const FilePath &buildDirectory)
{
std::pair<std::unique_ptr<CMakeProjectNode>, QSet<FilePath>> 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...
QSet<FilePath> 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