CompilationDatabase: Support both code models

Extract headers, defines and fileKind from flags in
order to have complete project parts.

Side-effect: better support for MSVC-specific flags.
Change-Id: Iaa1413c91c96c3cf89ddbe76a7a1f0f46c5289c0
Reviewed-by: Nikolai Kosjar <nikolai.kosjar@qt.io>
This commit is contained in:
Ivan Donchevskii
2018-10-30 10:31:34 +01:00
parent cb75aa3e58
commit f604c8a77c
20 changed files with 970 additions and 198 deletions

View File

@@ -26,133 +26,48 @@
#include "compilationdatabaseproject.h"
#include "compilationdatabaseconstants.h"
#include "compilationdatabaseutils.h"
#include <coreplugin/icontext.h>
#include <cpptools/projectinfo.h>
#include <cpptools/cppprojectupdater.h>
#include <projectexplorer/gcctoolchain.h>
#include <projectexplorer/headerpath.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/target.h>
#include <projectexplorer/toolchainconfigwidget.h>
#include <projectexplorer/toolchainmanager.h>
#include <texteditor/textdocument.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#ifdef Q_OS_WIN
#include <Windows.h>
#endif
using namespace ProjectExplorer;
namespace CompilationDatabaseProjectManager {
namespace Internal {
class DBProjectNode : public ProjectExplorer::ProjectNode
namespace {
class DBProjectNode : public ProjectNode
{
public:
explicit DBProjectNode(const Utils::FileName &projectFilePath)
: ProjectExplorer::ProjectNode(projectFilePath)
: ProjectNode(projectFilePath)
{}
};
static QStringList splitCommandLine(QString commandLine)
{
QStringList result;
bool insideQuotes = false;
// Remove escaped quotes.
commandLine.replace("\\\"", "'");
for (const QString &part : commandLine.split(QRegularExpression("\""))) {
if (insideQuotes) {
const QString quotedPart = "\"" + part + "\"";
if (result.last().endsWith("="))
result.last().append(quotedPart);
else
result.append(quotedPart);
} else { // If 's' is outside quotes ...
result.append(part.split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
}
insideQuotes = !insideQuotes;
}
return result;
}
static QString updatedPathFlag(const QString &pathStr, const QString &workingDir,
const QString &originalFlag)
{
QString result = pathStr;
if (!QDir(pathStr).exists()
&& QDir(workingDir + "/" + pathStr).exists()) {
result = workingDir + "/" + pathStr;
}
if (originalFlag.startsWith("-I"))
return "-I" + result;
if (originalFlag.startsWith("-isystem"))
return "-isystem" + result;
return result;
}
static QStringList filteredFlags(const QStringList &flags, const QString &fileName,
const QString &workingDir)
{
QStringList filtered;
// Skip compiler call if present.
bool skipNext = !flags.first().startsWith('-');
bool includePath = false;
for (const QString &flag : flags) {
if (skipNext) {
skipNext = false;
continue;
}
QString pathStr;
if (includePath) {
includePath = false;
pathStr = flag;
} else if ((flag.startsWith("-I") || flag.startsWith("-isystem"))
&& flag != "-I" && flag != "-isystem") {
pathStr = flag.mid(flag.startsWith("-I") ? 2 : 8);
}
if (!pathStr.isEmpty()) {
filtered.push_back(updatedPathFlag(pathStr, workingDir, flag));
continue;
}
if (flag == "-c" || flag == "-pedantic" || flag.startsWith("/") || flag.startsWith("-m")
|| flag.startsWith("-O") || flag.startsWith("-W") || flag.startsWith("-w")
|| flag.startsWith("--sysroot=")) {
continue;
}
if (flag == "-target" || flag == "-triple" || flag == "-isysroot" || flag == "-isystem"
|| flag == "--sysroot") {
skipNext = true;
continue;
}
if (flag.endsWith(fileName))
continue;
if (flag == "-I" || flag == "-isystem")
includePath = true;
filtered.push_back(flag);
}
return filtered;
}
static CppTools::RawProjectPart makeRawProjectPart(const Utils::FileName &projectFile,
const QJsonObject &object,
const QString &workingDir,
const Utils::FileName &fileName)
QStringList jsonObjectFlags(const QJsonObject &object)
{
QStringList flags;
const QJsonArray arguments = object["arguments"].toArray();
@@ -163,21 +78,234 @@ static CppTools::RawProjectPart makeRawProjectPart(const Utils::FileName &projec
flags.append(arg.toString());
}
flags = filteredFlags(flags, fileName.fileName(), workingDir);
return flags;
}
bool isGccCompiler(const QString &compilerName)
{
return compilerName.contains("gcc") || compilerName.contains("g++");
}
Core::Id getCompilerId(QString compilerName)
{
if (Utils::HostOsInfo::isWindowsHost()) {
if (compilerName.endsWith(".exe"))
compilerName.chop(4);
if (isGccCompiler(compilerName))
return ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID;
// Default is clang-cl
return ProjectExplorer::Constants::CLANG_CL_TOOLCHAIN_TYPEID;
}
if (isGccCompiler(compilerName))
return ProjectExplorer::Constants::GCC_TOOLCHAIN_TYPEID;
// Default is clang
return ProjectExplorer::Constants::CLANG_TOOLCHAIN_TYPEID;
}
ToolChain *toolchainFromCompilerId(const Core::Id &compilerId, const Core::Id &language)
{
return ToolChainManager::toolChain([&compilerId, &language](const ToolChain *tc) {
if (!tc->isValid() || tc->language() != language)
return false;
return tc->typeId() == compilerId;
});
}
QString compilerPath(QString pathFlag)
{
if (pathFlag.isEmpty())
return pathFlag;
#ifdef Q_OS_WIN
// Handle short DOS style file names (cmake can generate them).
const DWORD pathLength = GetLongPathNameW((LPCWSTR)pathFlag.utf16(), 0, 0);
wchar_t* buffer = new wchar_t[pathLength];
GetLongPathNameW((LPCWSTR)pathFlag.utf16(), buffer, pathLength);
pathFlag = QString::fromUtf16((ushort *)buffer, pathLength - 1);
delete[] buffer;
#endif
return QDir::fromNativeSeparators(pathFlag);
}
ToolChain *toolchainFromFlags(const Kit *kit, const QStringList &flags, const Core::Id &language)
{
if (flags.empty())
return ToolChainKitInformation::toolChain(kit, language);
// Try exact compiler match.
const Utils::FileName compiler = Utils::FileName::fromString(compilerPath(flags.front()));
ToolChain *toolchain = ToolChainManager::toolChain([&compiler, &language](const ToolChain *tc) {
return tc->isValid() && tc->language() == language && tc->compilerCommand() == compiler;
});
if (toolchain)
return toolchain;
Core::Id compilerId = getCompilerId(compiler.fileName());
if ((toolchain = toolchainFromCompilerId(compilerId, language)))
return toolchain;
if (compilerId != ProjectExplorer::Constants::CLANG_TOOLCHAIN_TYPEID &&
compilerId != ProjectExplorer::Constants::CLANG_CL_TOOLCHAIN_TYPEID) {
compilerId = Utils::HostOsInfo::isWindowsHost()
? ProjectExplorer::Constants::CLANG_CL_TOOLCHAIN_TYPEID
: ProjectExplorer::Constants::CLANG_TOOLCHAIN_TYPEID;
if ((toolchain = toolchainFromCompilerId(compilerId, language)))
return toolchain;
}
toolchain = ToolChainKitInformation::toolChain(kit, language);
qWarning() << QCoreApplication::translate("CompilationDatabaseProject",
"No matching toolchain found, use the default.");
return toolchain;
}
Utils::FileName jsonObjectFilename(const QJsonObject &object)
{
const QString workingDir = object["directory"].toString();
Utils::FileName fileName = Utils::FileName::fromString(
QDir::fromNativeSeparators(object["file"].toString()));
if (fileName.toFileInfo().isRelative()) {
fileName = Utils::FileUtils::canonicalPath(
Utils::FileName::fromString(workingDir + "/" + fileName.toString()));
}
return fileName;
}
void addDriverModeFlagIfNeeded(const ToolChain *toolchain, QStringList &flags)
{
if (toolchain->typeId() == ProjectExplorer::Constants::CLANG_CL_TOOLCHAIN_TYPEID
&& !flags.empty() && !flags.front().endsWith("cl")
&& !flags.front().endsWith("cl.exe")) {
flags.insert(1, "--driver-mode=g++");
}
}
CppTools::RawProjectPart makeRawProjectPart(const Utils::FileName &projectFile,
Kit *kit,
ToolChain *&cToolchain,
ToolChain *&cxxToolchain,
const QString &workingDir,
const Utils::FileName &fileName,
QStringList flags)
{
HeaderPaths headerPaths;
Macros macros;
CppTools::ProjectFile::Kind fileKind = CppTools::ProjectFile::Unclassified;
const QStringList originalFlags = flags;
filteredFlags(fileName.fileName(),
workingDir,
flags,
headerPaths,
macros,
fileKind);
CppTools::RawProjectPart rpp;
rpp.setProjectFileLocation(projectFile.toString());
rpp.setBuildSystemTarget(workingDir);
rpp.setDisplayName(fileName.fileName());
rpp.setFiles({fileName.toString()});
rpp.setHeaderPaths(headerPaths);
rpp.setMacros(macros);
CppTools::RawProjectPartFlags cxxProjectFlags;
cxxProjectFlags.commandLineFlags = flags;
rpp.setFlagsForCxx(cxxProjectFlags);
if (fileKind == CppTools::ProjectFile::Kind::CHeader
|| fileKind == CppTools::ProjectFile::Kind::CSource) {
if (!cToolchain) {
cToolchain = toolchainFromFlags(kit, originalFlags,
ProjectExplorer::Constants::C_LANGUAGE_ID);
ToolChainKitInformation::setToolChain(kit, cToolchain);
}
addDriverModeFlagIfNeeded(cToolchain, flags);
rpp.setFlagsForC({cToolchain, flags});
} else {
if (!cxxToolchain) {
cxxToolchain = toolchainFromFlags(kit, originalFlags,
ProjectExplorer::Constants::CXX_LANGUAGE_ID);
ToolChainKitInformation::setToolChain(kit, cxxToolchain);
}
addDriverModeFlagIfNeeded(cxxToolchain, flags);
rpp.setFlagsForCxx({cxxToolchain, flags});
}
return rpp;
}
} // anonymous namespace
void CompilationDatabaseProject::buildTreeAndProjectParts(const Utils::FileName &projectFile)
{
QFile file(projectFilePath().toString());
if (!file.open(QIODevice::ReadOnly)) {
emitParsingFinished(false);
return;
}
const QJsonArray array = QJsonDocument::fromJson(file.readAll()).array();
auto root = std::make_unique<DBProjectNode>(projectDirectory());
root->addNode(std::make_unique<FileNode>(
projectFile,
FileType::Project,
false));
auto headers = std::make_unique<VirtualFolderNode>(
Utils::FileName::fromString("Headers"), 0);
auto sources = std::make_unique<VirtualFolderNode>(
Utils::FileName::fromString("Sources"), 0);
CppTools::RawProjectParts rpps;
ToolChain *cToolchain = nullptr;
ToolChain *cxxToolchain = nullptr;
for (const QJsonValue &element : array) {
const QJsonObject object = element.toObject();
Utils::FileName fileName = jsonObjectFilename(object);
const QStringList flags = jsonObjectFlags(object);
const QString filePath = fileName.toString();
const CppTools::ProjectFile::Kind kind = CppTools::ProjectFile::classify(filePath);
FolderNode *parent = nullptr;
FileType type = FileType::Unknown;
if (CppTools::ProjectFile::isHeader(kind)) {
parent = headers.get();
type = FileType::Header;
} else if (CppTools::ProjectFile::isSource(kind)) {
parent = sources.get();
type = FileType::Source;
} else {
parent = root.get();
}
parent->addNode(std::make_unique<FileNode>(fileName, type, false));
CppTools::RawProjectPart rpp = makeRawProjectPart(projectFile,
m_kit.get(),
cToolchain,
cxxToolchain,
object["directory"].toString(),
fileName,
flags);
int rppIndex = Utils::indexOf(rpps, [&rpp](const CppTools::RawProjectPart &currentRpp) {
return rpp.buildSystemTarget == currentRpp.buildSystemTarget
&& rpp.headerPaths == currentRpp.headerPaths
&& rpp.projectMacros == currentRpp.projectMacros
&& rpp.flagsForCxx.commandLineFlags == currentRpp.flagsForCxx.commandLineFlags;
});
if (rppIndex == -1)
rpps.append(rpp);
else
rpps[rppIndex].files.append(rpp.files);
}
root->addNode(std::move(headers));
root->addNode(std::move(sources));
setRootProjectNode(std::move(root));
m_cppCodeModelUpdater->update({this, cToolchain, cxxToolchain, m_kit.get(), rpps});
emitParsingFinished(true);
}
CompilationDatabaseProject::CompilationDatabaseProject(const Utils::FileName &projectFile)
: Project(Constants::COMPILATIONDATABASEMIMETYPE, projectFile)
, m_cppCodeModelUpdater(std::make_unique<CppTools::CppProjectUpdater>(this))
@@ -185,90 +313,17 @@ CompilationDatabaseProject::CompilationDatabaseProject(const Utils::FileName &pr
setId(Constants::COMPILATIONDATABASEPROJECT_ID);
setProjectLanguages(Core::Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID));
setDisplayName(projectDirectory().fileName());
setRequiredKitPredicate([](const Kit *) { return false; });
setPreferredKitPredicate([](const Kit *) { return false; });
connect(this, &Project::activeTargetChanged, [this, projectFile](ProjectExplorer::Target *target) {
if (!target)
return;
m_kit.reset(KitManager::defaultKit()->clone());
ProjectExplorer::Kit *kit = target->kit();
if (!kit)
return;
emitParsingStarted();
auto toolchains = ProjectExplorer::ToolChainKitInformation::toolChains(kit);
if (toolchains.isEmpty())
return;
emitParsingStarted();
const QFuture<void> future = ::Utils::runAsync([this, projectFile, kit,
tc = toolchains.first()](){
QFile file(projectFilePath().toString());
if (!file.open(QIODevice::ReadOnly)) {
emitParsingFinished(false);
return;
}
const QJsonArray array = QJsonDocument::fromJson(file.readAll()).array();
auto root = std::make_unique<DBProjectNode>(projectDirectory());
root->addNode(std::make_unique<ProjectExplorer::FileNode>(
projectFile,
ProjectExplorer::FileType::Project,
false));
auto headers = std::make_unique<ProjectExplorer::VirtualFolderNode>(
Utils::FileName::fromString("Headers"), 0);
auto sources = std::make_unique<ProjectExplorer::VirtualFolderNode>(
Utils::FileName::fromString("Sources"), 0);
CppTools::RawProjectParts rpps;
for (const QJsonValue &element : array) {
const QJsonObject object = element.toObject();
const QString workingDir = object["directory"].toString();
Utils::FileName fileName = Utils::FileName::fromString(
QDir::fromNativeSeparators(object["file"].toString()));
if (!fileName.exists()) {
fileName = Utils::FileUtils::canonicalPath(
Utils::FileName::fromString(workingDir + "/" + fileName.toString()));
}
const QString filePath = fileName.toString();
const CppTools::ProjectFile::Kind kind = CppTools::ProjectFile::classify(filePath);
ProjectExplorer::FolderNode *parent = nullptr;
ProjectExplorer::FileType type = ProjectExplorer::FileType::Unknown;
if (CppTools::ProjectFile::isHeader(kind)) {
parent = headers.get();
type = ProjectExplorer::FileType::Header;
} else if (CppTools::ProjectFile::isSource(kind)) {
parent = sources.get();
type = ProjectExplorer::FileType::Source;
} else {
parent = root.get();
}
parent->addNode(std::make_unique<ProjectExplorer::FileNode>(
fileName, type, false));
rpps.append(makeRawProjectPart(projectFile, object, workingDir, fileName));
}
root->addNode(std::move(headers));
root->addNode(std::move(sources));
setRootProjectNode(std::move(root));
CppTools::ToolChainInfo tcInfo;
tcInfo.type = ProjectExplorer::Constants::COMPILATION_DATABASE_TOOLCHAIN_TYPEID;
tcInfo.isMsvc2015ToolChain
= tc->targetAbi().osFlavor() == ProjectExplorer::Abi::WindowsMsvc2015Flavor;
tcInfo.wordWidth = tc->targetAbi().wordWidth();
tcInfo.targetTriple = tc->originalTargetTriple();
tcInfo.sysRootPath = ProjectExplorer::SysRootKitInformation::sysRoot(kit).toString();
tcInfo.headerPathsRunner = tc->createBuiltInHeaderPathsRunner();
tcInfo.macroInspectionRunner = tc->createMacroInspectionRunner();
m_cppCodeModelUpdater->update({this, tcInfo, tcInfo, rpps});
emitParsingFinished(true);
});
m_parserWatcher.setFuture(future);
const QFuture<void> future = ::Utils::runAsync([this, projectFile](){
buildTreeAndProjectParts(projectFile);
});
m_parserWatcher.setFuture(future);
}
CompilationDatabaseProject::~CompilationDatabaseProject()

View File

@@ -35,6 +35,8 @@ namespace CppTools {
class CppProjectUpdater;
}
namespace ProjectExplorer { class Kit; }
namespace CompilationDatabaseProjectManager {
namespace Internal {
@@ -45,10 +47,15 @@ class CompilationDatabaseProject : public ProjectExplorer::Project
public:
explicit CompilationDatabaseProject(const Utils::FileName &filename);
~CompilationDatabaseProject() override;
bool needsConfiguration() const override { return false; }
bool needsBuildConfigurations() const override { return false; }
private:
void buildTreeAndProjectParts(const Utils::FileName &projectFile);
QFutureWatcher<void> m_parserWatcher;
std::unique_ptr<CppTools::CppProjectUpdater> m_cppCodeModelUpdater;
std::unique_ptr<ProjectExplorer::Kit> m_kit;
};
class CompilationDatabaseEditorFactory : public TextEditor::TextEditorFactory

View File

@@ -2,9 +2,21 @@ include(../../qtcreatorplugin.pri)
SOURCES = \
compilationdatabaseproject.cpp \
compilationdatabaseprojectmanagerplugin.cpp
compilationdatabaseprojectmanagerplugin.cpp \
compilationdatabaseutils.cpp
HEADERS = \
compilationdatabaseproject.h \
compilationdatabaseprojectmanagerplugin.h \
compilationdatabaseconstants.h
compilationdatabaseconstants.h \
compilationdatabaseutils.h
equals(TEST, 1) {
HEADERS += \
compilationdatabasetests.h
SOURCES += \
compilationdatabasetests.cpp
RESOURCES += compilationdatabasetests.qrc
}

View File

@@ -13,7 +13,26 @@ QtcPlugin {
"compilationdatabaseconstants.h",
"compilationdatabaseproject.cpp",
"compilationdatabaseproject.h",
"compilationdatabaseutils.cpp",
"compilationdatabaseutils.h",
"compilationdatabaseprojectmanagerplugin.cpp",
"compilationdatabaseprojectmanagerplugin.h",
]
Group {
name: "Tests"
condition: qtc.testsEnabled
files: [
"compilationdatabasetests.cpp",
"compilationdatabasetests.h",
"compilationdatabasetests.qrc",
]
}
Group {
name: "Test resources"
prefix: "database_samples/"
fileTags: []
files: ["**/*"]
}
}

View File

@@ -27,6 +27,7 @@
#include "compilationdatabaseconstants.h"
#include "compilationdatabaseproject.h"
#include "compilationdatabasetests.h"
#include <coreplugin/fileiconprovider.h>
#include <projectexplorer/projectmanager.h>
@@ -52,5 +53,14 @@ void CompilationDatabaseProjectManagerPlugin::extensionsInitialized()
{
}
QList<QObject *> CompilationDatabaseProjectManagerPlugin::createTestObjects() const
{
QList<QObject *> tests;
#ifdef WITH_TESTS
tests << new CompilationDatabaseTests;
#endif
return tests;
}
} // namespace Internal
} // namespace CompilationDatabaseProjectManager

View File

@@ -43,6 +43,8 @@ public:
bool initialize(const QStringList &arguments, QString *errorMessage) final;
void extensionsInitialized() final;
private:
QList<QObject *> createTestObjects() const final;
CompilationDatabaseEditorFactory factory;
};

View File

@@ -0,0 +1,105 @@
/****************************************************************************
**
** Copyright (C) 2018 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 "compilationdatabasetests.h"
#include <coreplugin/icore.h>
#include <cpptools/cpptoolstestcase.h>
#include <cpptools/projectinfo.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/toolchain.h>
#include <projectexplorer/toolchainmanager.h>
#include <QtTest>
using namespace ProjectExplorer;
namespace CompilationDatabaseProjectManager {
CompilationDatabaseTests::CompilationDatabaseTests(QObject *parent)
: QObject(parent)
{}
CompilationDatabaseTests::~CompilationDatabaseTests() = default;
void CompilationDatabaseTests::initTestCase()
{
const QList<Kit *> allKits = KitManager::kits();
if (allKits.empty())
QSKIP("This test requires at least one kit to be present.");
ToolChain *toolchain = ToolChainManager::toolChain([](const ToolChain *tc) {
return tc->isValid() && tc->language() == Constants::CXX_LANGUAGE_ID;
});
if (!toolchain)
QSKIP("This test requires that there is at least one C++ toolchain present.");
m_tmpDir = std::make_unique<CppTools::Tests::TemporaryCopiedDir>(":/database_samples");
QVERIFY(m_tmpDir->isValid());
}
void CompilationDatabaseTests::cleanupTestCase()
{
m_tmpDir.reset();
}
void CompilationDatabaseTests::testProject()
{
QFETCH(QString, projectFilePath);
CppTools::Tests::ProjectOpenerAndCloser projectManager;
const CppTools::ProjectInfo projectInfo = projectManager.open(projectFilePath, true);
QVERIFY(projectInfo.isValid());
projectInfo.projectParts();
QVector<CppTools::ProjectPart::Ptr> projectParts = projectInfo.projectParts();
QVERIFY(!projectParts.isEmpty());
CppTools::ProjectPart &projectPart = *projectParts.first();
QVERIFY(!projectPart.headerPaths.isEmpty());
QVERIFY(!projectPart.projectMacros.isEmpty());
QVERIFY(!projectPart.toolChainMacros.isEmpty());
QVERIFY(!projectPart.files.isEmpty());
}
void CompilationDatabaseTests::testProject_data()
{
QTest::addColumn<QString>("projectFilePath");
addTestRow("qtc/compile_commands.json");
addTestRow("llvm/compile_commands.json");
}
void CompilationDatabaseTests::addTestRow(const QByteArray &relativeFilePath)
{
const QString absoluteFilePath = m_tmpDir->absolutePath(relativeFilePath);
const QString fileName = QFileInfo(absoluteFilePath).fileName();
QTest::newRow(fileName.toUtf8().constData()) << absoluteFilePath;
}
} // namespace CompilationDatabaseProjectManager

View File

@@ -0,0 +1,54 @@
/****************************************************************************
**
** Copyright (C) 2018 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 <QObject>
#include <memory>
namespace CppTools { namespace Tests { class TemporaryCopiedDir; } }
namespace CompilationDatabaseProjectManager {
class CompilationDatabaseTests : public QObject
{
Q_OBJECT
public:
explicit CompilationDatabaseTests(QObject *parent = nullptr);
~CompilationDatabaseTests();
private slots:
void initTestCase();
void cleanupTestCase();
void testProject();
void testProject_data();
private:
void addTestRow(const QByteArray &relativeFilePath);
std::unique_ptr<CppTools::Tests::TemporaryCopiedDir> m_tmpDir;
};
} // namespace CompilationDatabaseProjectManager

View File

@@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file>database_samples/llvm/compile_commands.json</file>
<file>database_samples/qtc/compile_commands.json</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,7 @@
INCLUDEPATH += $$PWD
SOURCES += \
$$PWD/compilationdatabaseutils.cpp
HEADERS += \
$$PWD/compilationdatabaseutils.h

View File

@@ -0,0 +1,216 @@
/****************************************************************************
**
** Copyright (C) 2018 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 "compilationdatabaseutils.h"
#include <projectexplorer/headerpath.h>
#include <projectexplorer/projectmacro.h>
#include <utils/hostosinfo.h>
#include <utils/optional.h>
#include <QDir>
#include <QRegularExpression>
using namespace ProjectExplorer;
namespace CompilationDatabaseProjectManager {
static QString updatedPathFlag(const QString &pathStr, const QString &workingDir)
{
QString result = pathStr;
if (!QDir(pathStr).exists()
&& QDir(workingDir + "/" + pathStr).exists()) {
result = workingDir + "/" + pathStr;
}
return result;
}
static CppTools::ProjectFile::Kind fileKindFromString(const QString &flag)
{
using namespace CppTools;
if (flag == "c++-header")
return ProjectFile::CXXHeader;
if (flag == "c-header")
return ProjectFile::CHeader;
if (flag == "c++" || flag == "/TP" || flag.startsWith("/Tp"))
return ProjectFile::CXXSource;
if (flag == "c" || flag == "/TC" || flag.startsWith("/Tc"))
return ProjectFile::CSource;
if (flag == "objective-c++")
return ProjectFile::ObjCXXSource;
if (flag == "objective-c++-header")
return ProjectFile::ObjCXXHeader;
if (flag == "objective-c")
return ProjectFile::ObjCSource;
if (flag == "objective-c-header")
return ProjectFile::ObjCHeader;
if (flag == "cl")
return ProjectFile::OpenCLSource;
if (flag == "cuda")
return ProjectFile::CudaSource;
return ProjectFile::Unclassified;
}
void filteredFlags(const QString &fileName,
const QString &workingDir,
QStringList &flags,
HeaderPaths &headerPaths,
Macros &macros,
CppTools::ProjectFile::Kind &fileKind)
{
if (flags.isEmpty())
return;
// Skip compiler call if present.
bool skipNext = Utils::HostOsInfo::isWindowsHost()
? (!flags.first().startsWith('/') && !flags.first().startsWith('-'))
: (!flags.first().startsWith('-'));
Utils::optional<HeaderPathType> includePathType;
Utils::optional<MacroType> macroType;
bool fileKindIsNext = false;
QStringList filtered;
for (const QString &flag : flags) {
if (skipNext) {
skipNext = false;
continue;
}
if (includePathType) {
const QString pathStr = updatedPathFlag(flag, workingDir);
headerPaths.append({pathStr, includePathType.value()});
includePathType.reset();
continue;
}
if (macroType) {
Macro macro = Macro::fromKeyValue(flag);
macro.type = macroType.value();
macros.append(macro);
macroType.reset();
continue;
}
if (fileKindIsNext || flag == "/TC" || flag == "/TP"
|| flag.startsWith("/Tc") || flag.startsWith("/Tp")) {
fileKindIsNext = false;
fileKind = fileKindFromString(flag);
continue;
}
if (flag == "-x") {
fileKindIsNext = true;
continue;
}
if (flag == "-c" || flag == "-pedantic"
|| flag.startsWith("-O") || flag.startsWith("-W") || flag.startsWith("-w")
|| QString::compare(flag, "-fpic", Qt::CaseInsensitive) == 0
|| QString::compare(flag, "-fpie", Qt::CaseInsensitive) == 0) {
continue;
}
if (flag.endsWith(fileName))
continue;
if ((flag.startsWith("-I") || flag.startsWith("-isystem") || flag.startsWith("/I"))
&& flag != "-I" && flag != "-isystem" && flag != "/I") {
bool userInclude = flag.startsWith("-I");
const QString pathStr = updatedPathFlag(flag.mid(userInclude ? 2 : 8),
workingDir);
headerPaths.append({pathStr, userInclude
? HeaderPathType::User
: HeaderPathType::System});
continue;
}
if ((flag.startsWith("-D") || flag.startsWith("-U") || flag.startsWith("/D") || flag.startsWith("/U"))
&& flag != "-D" && flag != "-U" && flag != "/D" && flag != "/U") {
Macro macro = Macro::fromKeyValue(flag.mid(2));
macro.type = (flag.startsWith("-D") || flag.startsWith("/D")) ? MacroType::Define : MacroType::Undefine;
macros.append(macro);
continue;
}
if (flag == "-I" || flag == "-isystem" || flag == "/I") {
includePathType = (flag != "-isystem") ? HeaderPathType::User : HeaderPathType::System;
continue;
}
if (flag == "-D" || flag == "-U" || flag == "/D" || flag == "/U") {
macroType = (flag == "-D" || flag == "/D") ? MacroType::Define : MacroType::Undefine;
continue;
}
if ((flag.startsWith("-std=") || flag.startsWith("/std:"))
&& fileKind == CppTools::ProjectFile::Unclassified) {
const bool cpp = (flag.contains("c++") || flag.contains("gnu++"));
if (CppTools::ProjectFile::isHeader(CppTools::ProjectFile::classify(fileName)))
fileKind = cpp ? CppTools::ProjectFile::CXXHeader : CppTools::ProjectFile::CHeader;
else
fileKind = cpp ? CppTools::ProjectFile::CXXSource : CppTools::ProjectFile::CXXHeader;
}
// Skip all remaining Windows flags except feature flags.
if (flag.startsWith("/") && !flag.startsWith("/Z"))
continue;
filtered.push_back(flag);
}
if (fileKind == CppTools::ProjectFile::Unclassified)
fileKind = CppTools::ProjectFile::classify(fileName);
flags = filtered;
}
QStringList splitCommandLine(QString commandLine)
{
QStringList result;
bool insideQuotes = false;
// Remove escaped quotes.
commandLine.replace("\\\"", "'");
for (const QString &part : commandLine.split(QRegularExpression("\""))) {
if (insideQuotes) {
const QString quotedPart = "\"" + part + "\"";
if (result.last().endsWith("="))
result.last().append(quotedPart);
else
result.append(quotedPart);
} else { // If 's' is outside quotes ...
result.append(part.split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
}
insideQuotes = !insideQuotes;
}
return result;
}
} // namespace CompilationDatabaseProjectManager

View File

@@ -0,0 +1,50 @@
/****************************************************************************
**
** Copyright (C) 2018 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 "compilationdatabaseconstants.h"
#include <cpptools/cppprojectfile.h>
#include <QStringList>
namespace ProjectExplorer {
class HeaderPath;
class Macro;
}
namespace CompilationDatabaseProjectManager {
void filteredFlags(const QString &fileName,
const QString &workingDir,
QStringList &flags,
QVector<ProjectExplorer::HeaderPath> &headerPaths,
QVector<ProjectExplorer::Macro> &macros,
CppTools::ProjectFile::Kind &fileKind);
QStringList splitCommandLine(QString commandLine);
} // namespace CompilationDatabaseProjectManager

View File

@@ -0,0 +1,7 @@
[
{
"directory": "C:/build-qt_llvm-msvc2017_64bit-Debug",
"command": "C:\\PROGRA~2\\MICROS~2\\2017\\COMMUN~1\\VC\\Tools\\MSVC\\1415~1.267\\bin\\HostX64\\x64\\cl.exe /nologo /TP -DUNICODE -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS -D_GNU_SOURCE -D_HAS_EXCEPTIONS=0 -D_SCL_SECURE_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -Dclang=clang_qtcreator -Dllvm=llvm_qtcreator -Itools\\clang\\lib\\Sema -IC:\\qt_llvm\\tools\\clang\\lib\\Sema -IC:\\qt_llvm\\tools\\clang\\include -Itools\\clang\\include -Iinclude -IC:\\qt_llvm\\include /DWIN32 /D_WINDOWS /Zc:inline /Zc:strictStrings /Oi /Zc:rvalueCast /W4 -wd4141 -wd4146 -wd4180 -wd4244 -wd4258 -wd4267 -wd4291 -wd4345 -wd4351 -wd4355 -wd4456 -wd4457 -wd4458 -wd4459 -wd4503 -wd4624 -wd4722 -wd4800 -wd4100 -wd4127 -wd4512 -wd4505 -wd4610 -wd4510 -wd4702 -wd4245 -wd4706 -wd4310 -wd4701 -wd4703 -wd4389 -wd4611 -wd4805 -wd4204 -wd4577 -wd4091 -wd4592 -wd4319 -wd4324 -w14062 -we4238 /MDd /Zi /Ob0 /Od /RTC1 /EHs-c- /GR /Fotools\\clang\\lib\\Sema\\CMakeFiles\\clangSema.dir\\SemaCodeComplete.cpp.obj /FdTARGET_COMPILE_PDB /FS -c C:\\qt_llvm\\tools\\clang\\lib\\Sema\\SemaCodeComplete.cpp",
"file": "C:\\qt_llvm\\tools\\clang\\lib\\Sema\\SemaCodeComplete.cpp"
}
]

View File

@@ -0,0 +1,62 @@
[
{
"arguments": [
"clang++",
"-c",
"-m32",
"-target",
"i686-w64-mingw32",
"-std=gnu++14",
"-fcxx-exceptions",
"-fexceptions",
"-DUNICODE",
"-D_UNICODE",
"-DCPPTOOLS_LIBRARY",
"-DWITH_TESTS",
"-DRELATIVE_PLUGIN_PATH=\"../lib/qtcreator/plugins\"",
"-DRELATIVE_LIBEXEC_PATH=\".\"",
"-DRELATIVE_DATA_PATH=\"../share/qtcreator\"",
"-DRELATIVE_DOC_PATH=\"../share/doc/qtcreator\"",
"-DIDE_LIBRARY_BASENAME=\"lib\"",
"-DQT_CREATOR",
"-DQT_NO_CAST_TO_ASCII",
"-DQT_RESTRICTED_CAST_FROM_ASCII",
"-DQT_DISABLE_DEPRECATED_BEFORE=0x050600",
"-DQT_USE_FAST_OPERATOR_PLUS",
"-DQT_USE_FAST_CONCATENATION",
"-DSRCDIR=\"C:/qt-creator/src/plugins/cpptools\"",
"-DQT_QML_DEBUG",
"-DQT_PLUGIN",
"-DQT_WIDGETS_LIB",
"-DQT_GUI_LIB",
"-DQT_TESTLIB_LIB",
"-DQT_CONCURRENT_LIB",
"-DQT_NETWORK_LIB",
"-DQT_CORE_LIB",
"-fPIC",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\include",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\include\\QtWidgets",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\include\\QtGui",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\include\\QtANGLE",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\include\\QtTest",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\include\\QtConcurrent",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\include\\QtNetwork",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\include\\QtCore",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\mkspecs\\win32-g++",
"-x",
"c++",
"C:\\qt-creator\\src\\plugins\\cpptools\\compileroptionsbuilder.cpp"
],
"directory": "C:/build-qtcreator-MinGW_32bit-Debug",
"file": "C:/qt-creator/src/plugins/cpptools/compileroptionsbuilder.cpp"
}
]

View File

@@ -76,13 +76,6 @@ QStringList CompilerOptionsBuilder::build(CppTools::ProjectFile::Kind fileKind,
addTargetTriple();
addExtraCodeModelFlags();
if (m_projectPart.toolchainType
== ProjectExplorer::Constants::COMPILATION_DATABASE_TOOLCHAIN_TYPEID) {
addHeaderPathOptions();
insertWrappedQtHeaders();
return options();
}
updateLanguageOption(fileKind);
addOptionsForLanguage(/*checkForBorlandExtensions*/ true);
enableExceptions();

View File

@@ -165,9 +165,6 @@ ProjectPart::Ptr ProjectInfoGenerator::createProjectPart(
part->warningFlags = flags.warningFlags;
part->languageExtensions = flags.languageExtensions;
if (part->toolchainType == ProjectExplorer::Constants::COMPILATION_DATABASE_TOOLCHAIN_TYPEID)
part->extraCodeModelFlags = flags.commandLineFlags;
// Toolchain macros and language version
if (tcInfo.macroInspectionRunner) {
auto macroInspectionReport = tcInfo.macroInspectionRunner(flags.commandLineFlags);

View File

@@ -163,7 +163,6 @@ const char MINGW_TOOLCHAIN_TYPEID[] = "ProjectExplorer.ToolChain.Mingw";
const char MSVC_TOOLCHAIN_TYPEID[] = "ProjectExplorer.ToolChain.Msvc";
const char CLANG_CL_TOOLCHAIN_TYPEID[] = "ProjectExplorer.ToolChain.ClangCl";
const char CUSTOM_TOOLCHAIN_TYPEID[] = "ProjectExplorer.ToolChain.Custom";
const char COMPILATION_DATABASE_TOOLCHAIN_TYPEID[] = "ProjectExplorer.ToolChain.Empty";
// Default directory to run custom (build) commands in.
const char DEFAULT_WORKING_DIR[] = "%{buildDir}";

View File

@@ -0,0 +1,169 @@
/****************************************************************************
**
** Copyright (C) 2018 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 "googletest.h"
#include <compilationdatabaseprojectmanager/compilationdatabaseutils.h>
#include <projectexplorer/headerpath.h>
#include <projectexplorer/projectmacro.h>
#include <utils/fileutils.h>
using namespace ProjectExplorer;
using namespace CompilationDatabaseProjectManager;
namespace {
class CompilationDatabaseUtils : public ::testing::Test
{
protected:
HeaderPaths headerPaths;
Macros macros;
CppTools::ProjectFile::Kind fileKind = CppTools::ProjectFile::Unclassified;
QStringList flags;
QString fileName;
QString workingDir;
};
TEST_F(CompilationDatabaseUtils, FilterEmptyFlags)
{
filteredFlags(fileName, workingDir, flags, headerPaths, macros, fileKind);
ASSERT_THAT(flags.isEmpty(), true);
}
TEST_F(CompilationDatabaseUtils, FilterArguments)
{
fileName = "compileroptionsbuilder.cpp";
workingDir = "C:/build-qtcreator-MinGW_32bit-Debug";
flags = QStringList {
"clang++",
"-c",
"-m32",
"-target",
"i686-w64-mingw32",
"-std=gnu++14",
"-fcxx-exceptions",
"-fexceptions",
"-DUNICODE",
"-DRELATIVE_PLUGIN_PATH=\"../lib/qtcreator/plugins\"",
"-DQT_CREATOR",
"-fPIC",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\include",
"-I",
"C:\\Qt\\5.9.2\\mingw53_32\\include\\QtWidgets",
"-x",
"c++",
"C:\\qt-creator\\src\\plugins\\cpptools\\compileroptionsbuilder.cpp"
};
filteredFlags(fileName, workingDir, flags, headerPaths, macros, fileKind);
ASSERT_THAT(flags, Eq(QStringList{"-m32",
"-target",
"i686-w64-mingw32",
"-std=gnu++14",
"-fcxx-exceptions",
"-fexceptions"}));
ASSERT_THAT(headerPaths, Eq(HeaderPaths{
{"C:\\Qt\\5.9.2\\mingw53_32\\include", HeaderPathType::User},
{"C:\\Qt\\5.9.2\\mingw53_32\\include\\QtWidgets", HeaderPathType::User}
}));
ASSERT_THAT(macros, Eq(Macros{
{"UNICODE", "1"},
{"RELATIVE_PLUGIN_PATH", "\"../lib/qtcreator/plugins\""},
{"QT_CREATOR", "1"}
}));
ASSERT_THAT(fileKind, CppTools::ProjectFile::Kind::CXXSource);
}
static QString kCmakeCommand = "C:\\PROGRA~2\\MICROS~2\\2017\\COMMUN~1\\VC\\Tools\\MSVC\\1415~1.267\\bin\\HostX64\\x64\\cl.exe "
"/nologo "
"/TP "
"-DUNICODE "
"-D_HAS_EXCEPTIONS=0 "
"-Itools\\clang\\lib\\Sema "
"/DWIN32 "
"/D_WINDOWS "
"/Zc:inline "
"/Zc:strictStrings "
"/Oi "
"/Zc:rvalueCast "
"/W4 "
"-wd4141 "
"-wd4146 "
"/MDd "
"/Zi "
"/Ob0 "
"/Od "
"/RTC1 "
"/EHs-c- "
"/GR "
"/Fotools\\clang\\lib\\Sema\\CMakeFiles\\clangSema.dir\\SemaCodeComplete.cpp.obj "
"/FdTARGET_COMPILE_PDB "
"/FS "
"-c "
"C:\\qt_llvm\\tools\\clang\\lib\\Sema\\SemaCodeComplete.cpp";
TEST_F(CompilationDatabaseUtils, SplitFlags)
{
flags = splitCommandLine(kCmakeCommand);
ASSERT_THAT(flags.size(), 27);
}
TEST_F(CompilationDatabaseUtils, SplitFlagsWithEscapedQuotes)
{
flags = splitCommandLine("-DRC_FILE_VERSION=\\\"7.0.0\\\" "
"-DRELATIVE_PLUGIN_PATH=\"../lib/qtcreator/plugins\"");
ASSERT_THAT(flags.size(), 2);
}
TEST_F(CompilationDatabaseUtils, FilterCommand)
{
fileName = "SemaCodeComplete.cpp";
workingDir = "C:/build-qt_llvm-msvc2017_64bit-Debug";
flags = splitCommandLine(kCmakeCommand);
filteredFlags(fileName, workingDir, flags, headerPaths, macros, fileKind);
ASSERT_THAT(flags, Eq(QStringList{"/Zc:inline",
"/Zc:strictStrings",
"/Zc:rvalueCast",
"/Zi"}));
ASSERT_THAT(headerPaths, Eq(HeaderPaths{
{"tools\\clang\\lib\\Sema", HeaderPathType::User}
}));
ASSERT_THAT(macros, Eq(Macros{
{"UNICODE", "1"},
{"_HAS_EXCEPTIONS", "0"},
{"WIN32", "1"},
{"_WINDOWS", "1"}
}));
ASSERT_THAT(fileKind, CppTools::ProjectFile::Kind::CXXSource);
}
}

View File

@@ -13,6 +13,7 @@ include($$PWD/../../../src/tools/clangpchmanagerbackend/source/clangpchmanagerba
include($$PWD/../../../src/plugins/clangrefactoring/clangrefactoring-source.pri)
include($$PWD/../../../src/plugins/clangpchmanager/clangpchmanager-source.pri)
include($$PWD/../../../src/plugins/cpptools/cpptoolsunittestfiles.pri)
include($$PWD/../../../src/plugins/compilationdatabaseprojectmanager/compilationdatabaseunittestfiles.pri)
include(cplusplus.pri)
!isEmpty(LLVM_VERSION) {
include($$PWD/../../../src/shared/clang/clang_defines.pri)

View File

@@ -104,7 +104,8 @@ SOURCES += \
taskscheduler-test.cpp \
compileroptionsbuilder-test.cpp \
usedmacroandsourcestorage-test.cpp \
pchtaskgenerator-test.cpp
pchtaskgenerator-test.cpp \
compilationdatabaseutils-test.cpp
!isEmpty(LIBCLANG_LIBS) {
SOURCES += \