ClangTools: Use compilation database when running clang-tidy and clazy

Fixes: QTCREATORBUG-29529
Change-Id: I917c53352e448dfd9e58e7a6e4dabb71e8f79491
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2024-09-02 16:49:51 +02:00
parent 97648298f3
commit a360d855ae
10 changed files with 233 additions and 59 deletions

View File

@@ -21,6 +21,7 @@ add_qtc_plugin(ClangTools
clangtool.cpp clangtool.h
clangtoolrunner.cpp clangtoolrunner.h
clangtools_global.h
clangtoolscompilationdb.cpp clangtoolscompilationdb.h
clangtoolstr.h
clangtoolsconstants.h
clangtoolsdiagnostic.cpp clangtoolsdiagnostic.h

View File

@@ -5,6 +5,7 @@
#include "clangselectablefilesdialog.h"
#include "clangtoolrunner.h"
#include "clangtoolscompilationdb.h"
#include "clangtoolsconstants.h"
#include "clangtoolsdiagnosticview.h"
#include "clangtoolsprojectsettings.h"
@@ -753,7 +754,7 @@ Group ClangTool::runRecipe(const RunSettings &runSettings,
for (const FileInfo &fileInfo : fileInfos) {
if (diagnosticConfig.isEnabled(tool)
|| runSettings.hasConfigFileForSourceFile(fileInfo.file)) {
unitsToProcess.append({fileInfo, includeDir, clangVersion});
unitsToProcess.append({fileInfo, tool});
}
}
qCDebug(LOG) << Q_FUNC_INFO << executable << includeDir << clangVersion;
@@ -798,7 +799,8 @@ Group ClangTool::runRecipe(const RunSettings &runSettings,
const AnalyzeInputData input{tool, runSettings, diagnosticConfig, tempDir->path(),
environment};
taskTree.setRecipe({clangToolTask(unitsToProcess, input, setupHandler, outputHandler)});
taskTree.setRecipe(
{clangToolTask(tool, unitsToProcess, input, setupHandler, outputHandler)});
return SetupResult::Continue;
};
@@ -830,6 +832,9 @@ Group ClangTool::runRecipe(const RunSettings &runSettings,
void ClangTool::startTool(FileSelection fileSelection, const RunSettings &runSettings,
const ClangDiagnosticConfig &diagnosticConfig)
{
ClangToolsCompilationDb &db = ClangToolsCompilationDb::getDb(m_type);
db.disconnect(this);
Project *project = ProjectManager::startupProject();
QTC_ASSERT(project, return);
QTC_ASSERT(project->activeTarget(), return);
@@ -841,6 +846,14 @@ void ClangTool::startTool(FileSelection fileSelection, const RunSettings &runSet
return;
}
if (db.generateIfNecessary()) {
connect(&db, &ClangToolsCompilationDb::generated, this, [=, this](bool success) {
if (success)
startTool(fileSelection, runSettings, diagnosticConfig);
}, Qt::SingleShotConnection);
return;
}
TaskHub::clearTasks(taskCategory());
// Collect files to analyze

View File

@@ -3,6 +3,7 @@
#include "clangtoolrunner.h"
#include "clangtoolscompilationdb.h"
#include "clangtoolslogfilereader.h"
#include "clangtoolstr.h"
#include "clangtoolsutils.h"
@@ -34,31 +35,6 @@ using namespace Tasking;
namespace ClangTools {
namespace Internal {
AnalyzeUnit::AnalyzeUnit(const FileInfo &fileInfo,
const FilePath &clangIncludeDir,
const QString &clangVersion)
{
const FilePath actualClangIncludeDir = Core::ICore::clangIncludeDirectory(
clangVersion, clangIncludeDir);
CompilerOptionsBuilder optionsBuilder(*fileInfo.projectPart,
UseSystemHeader::No,
UseTweakedHeaderPaths::Tools,
UseLanguageDefines::No,
UseBuildSystemWarnings::No,
actualClangIncludeDir);
file = fileInfo.file;
arguments = extraClangToolsPrependOptions();
arguments.append(
optionsBuilder.build(fileInfo.kind,
CppCodeModelSettings(fileInfo.settings).usePrecompiledHeaders()));
arguments.append(extraClangToolsAppendOptions());
}
static bool isClMode(const QStringList &options)
{
return options.contains("--driver-mode=cl");
}
static QStringList checksArguments(const AnalyzeUnit &unit, const AnalyzeInputData &input)
{
if (input.tool == ClangToolType::Tidy) {
@@ -78,24 +54,6 @@ static QStringList checksArguments(const AnalyzeUnit &unit, const AnalyzeInputDa
return {};
}
static QStringList clangArguments(const AnalyzeUnit &unit, const AnalyzeInputData &input)
{
QStringList arguments;
const ClangDiagnosticConfig &diagnosticConfig = input.config;
const QStringList &baseOptions = unit.arguments;
arguments << ClangDiagnosticConfigsModel::globalDiagnosticOptions()
<< (isClMode(baseOptions) ? clangArgsForCl(diagnosticConfig.clangOptions())
: diagnosticConfig.clangOptions())
<< baseOptions;
if (ProjectFile::isHeader(unit.file))
arguments << "-Wno-pragma-once-outside-header";
if (LOG().isDebugEnabled())
arguments << QLatin1String("-v");
return arguments;
}
static FilePath createOutputFilePath(const FilePath &dirPath, const FilePath &fileToAnalyze)
{
const QString fileName = fileToAnalyze.fileName();
@@ -111,7 +69,8 @@ static FilePath createOutputFilePath(const FilePath &dirPath, const FilePath &fi
return {};
}
GroupItem clangToolTask(const AnalyzeUnits &units,
GroupItem clangToolTask(CppEditor::ClangToolType toolType,
const AnalyzeUnits &units,
const AnalyzeInputData &input,
const AnalyzeSetupHandler &setupHandler,
const AnalyzeOutputHandler &outputHandler)
@@ -124,8 +83,9 @@ GroupItem clangToolTask(const AnalyzeUnits &units,
const Storage<ClangToolStorage> storage;
const LoopList iterator(units);
const auto mainToolArguments = [input, iterator](const ClangToolStorage &data) {
const auto mainToolArguments = [input, iterator, toolType](const ClangToolStorage &data) {
QStringList result;
result << "-p" << ClangToolsCompilationDb::getDb(toolType).parentDir().nativePath();
result << "-export-fixes=" + data.outputFilePath.nativePath();
if (!input.overlayFilePath.isEmpty() && isVFSOverlaySupported(data.executable))
result << "--vfsoverlay=" + input.overlayFilePath;
@@ -146,8 +106,6 @@ GroupItem clangToolTask(const AnalyzeUnits &units,
return SetupResult::StopWithError;
}
QTC_CHECK(!unit.arguments.contains(QLatin1String("-o")));
QTC_CHECK(!unit.arguments.contains(unit.file.nativePath()));
QTC_ASSERT(unit.file.exists(), return SetupResult::StopWithError);
data->outputFilePath = createOutputFilePath(input.outputDirPath, unit.file);
QTC_ASSERT(!data->outputFilePath.isEmpty(), return SetupResult::StopWithError);
@@ -163,8 +121,7 @@ GroupItem clangToolTask(const AnalyzeUnits &units,
const ClangToolStorage &data = *storage;
const CommandLine commandLine{data.executable, {checksArguments(unit, input),
mainToolArguments(data), "--",
clangArguments(unit, input)}};
mainToolArguments(data)}};
qCDebug(LOG).noquote() << "Starting" << commandLine.toUserOutput();
process.setCommand(commandLine);
};

View File

@@ -19,12 +19,14 @@ namespace Internal {
struct AnalyzeUnit
{
AnalyzeUnit(const FileInfo &fileInfo,
const Utils::FilePath &clangResourceDir,
const QString &clangVersion);
AnalyzeUnit(const FileInfo &fileInfo, CppEditor::ClangToolType toolType)
: file(fileInfo.file)
, toolType(toolType)
{}
Utils::FilePath file;
QStringList arguments; // without file itself and "-o somePath"
CppEditor::ClangToolType toolType;
};
using AnalyzeUnits = QList<AnalyzeUnit>;
@@ -53,7 +55,8 @@ struct AnalyzeOutputData
using AnalyzeSetupHandler = std::function<bool(const AnalyzeUnit &)>;
using AnalyzeOutputHandler = std::function<void(const AnalyzeOutputData &)>;
Tasking::GroupItem clangToolTask(const AnalyzeUnits &units,
Tasking::GroupItem clangToolTask(CppEditor::ClangToolType toolType,
const AnalyzeUnits &units,
const AnalyzeInputData &input,
const AnalyzeSetupHandler &setupHandler,
const AnalyzeOutputHandler &outputHandler);

View File

@@ -33,6 +33,8 @@ QtcPlugin {
"clangtoolrunner.cpp",
"clangtoolrunner.h",
"clangtools_global.h",
"clangtoolscompilationdb.cpp",
"clangtoolscompilationdb.h",
"clangtoolstr.h",
"clangtoolsconstants.h",
"clangtoolsdiagnostic.cpp",

View File

@@ -0,0 +1,162 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "clangtoolscompilationdb.h"
#include "clangtoolsprojectsettings.h"
#include "clangtoolstr.h"
#include "clangtoolsutils.h"
#include "executableinfo.h"
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
#include <cppeditor/clangdiagnosticconfigsmodel.h>
#include <cppeditor/compilationdb.h>
#include <cppeditor/cppmodelmanager.h>
#include <utils/async.h>
#include <utils/futuresynchronizer.h>
#include <utils/temporarydirectory.h>
#include <QFutureWatcher>
#include <memory>
using namespace CppEditor;
using namespace Utils;
namespace ClangTools::Internal {
class ClangToolsCompilationDb::Private
{
public:
Private(ClangToolType toolType, ClangToolsCompilationDb *q) : q(q), toolType(toolType) {}
void generate();
QString toolName() const { return clangToolName(toolType); }
static inline std::unique_ptr<ClangToolsCompilationDb> clangTidyDb;
static inline std::unique_ptr<ClangToolsCompilationDb> clazyDb;
ClangToolsCompilationDb * const q;
const ClangToolType toolType;
TemporaryDirectory dir{toolName()};
QFutureWatcher<GenerateCompilationDbResult> generatorWatcher;
FutureSynchronizer generatorSynchronizer;
bool readyAndUpToDate = false;
};
ClangToolsCompilationDb::ClangToolsCompilationDb(ClangToolType toolType)
: d(new Private(toolType, this))
{
connect(&d->generatorWatcher, &QFutureWatcher<GenerateCompilationDbResult>::finished,
this, [this] {
const auto result = d->generatorWatcher.result();
const bool success = result.has_value();
QTC_CHECK(!d->readyAndUpToDate);
d->readyAndUpToDate = success;
if (success) {
Core::MessageManager::writeSilently(
Tr::tr("Compilation database for %1 successfully generated at %2.")
.arg(d->toolName(), d->dir.path().toUserOutput()));
} else {
Core::MessageManager::writeDisrupting(
Tr::tr("Generating compilation database for %1 failed: %2")
.arg(d->toolName(), result.error()));
}
emit generated(success);
});
connect(ClangToolsSettings::instance(), &BaseAspect::changed,
this, &ClangToolsCompilationDb::invalidate);
connect(CppModelManager::instance(), &CppModelManager::projectPartsUpdated,
this, &ClangToolsCompilationDb::invalidate);
}
ClangToolsCompilationDb::~ClangToolsCompilationDb() { delete d; }
void ClangToolsCompilationDb::invalidate() { d->readyAndUpToDate = false; }
FilePath ClangToolsCompilationDb::parentDir() const { return d->dir.path(); }
bool ClangToolsCompilationDb::generateIfNecessary()
{
if (d->readyAndUpToDate)
return false;
d->generate();
return true;
}
ClangToolsCompilationDb &ClangToolsCompilationDb::getDb(ClangToolType toolType)
{
if (toolType == ClangToolType::Tidy) {
if (!Private::clangTidyDb)
Private::clangTidyDb.reset(new ClangToolsCompilationDb(toolType));
return *Private::clangTidyDb;
}
if (!Private::clazyDb)
Private::clazyDb.reset(new ClangToolsCompilationDb(toolType));
return *Private::clazyDb;
}
void ClangToolsCompilationDb::Private::generate()
{
QTC_CHECK(!readyAndUpToDate);
if (generatorWatcher.isRunning())
generatorWatcher.cancel();
Core::MessageManager::writeSilently(
Tr::tr("Generating compilation database for %1 at %2 ...")
.arg(clangToolName(toolType), dir.path().toUserOutput()));
const auto getCompilerOptionsBuilder = [this](const ProjectPart &pp) {
const auto projectSettings = ClangToolsProjectSettings::getSettings(pp.project());
QTC_ASSERT(projectSettings, return CompilerOptionsBuilder(pp));
connect(projectSettings.get(), &ClangToolsProjectSettings::changed,
q, &ClangToolsCompilationDb::invalidate);
const Id configId = projectSettings->runSettings().diagnosticConfigId();
const ClangDiagnosticConfig config = Utils::findOrDefault(
ClangToolsSettings::instance()->diagnosticConfigs(),
[configId](const ClangDiagnosticConfig &c) { return c.id() == configId; });
const auto useBuildSystemWarnings = config.useBuildSystemWarnings()
? UseBuildSystemWarnings::Yes
: UseBuildSystemWarnings::No;
const FilePath executable = toolExecutable(toolType);
const auto [includeDir, clangVersion] = getClangIncludeDirAndVersion(executable);
const FilePath actualClangIncludeDir = Core::ICore::clangIncludeDirectory(
clangVersion, includeDir);
CompilerOptionsBuilder optionsBuilder(pp,
UseSystemHeader::No,
UseTweakedHeaderPaths::Tools,
UseLanguageDefines::No,
useBuildSystemWarnings,
actualClangIncludeDir);
optionsBuilder.build(ProjectFile::Unclassified, UsePrecompiledHeaders::No);
if (useBuildSystemWarnings == UseBuildSystemWarnings::No) {
for (const QString &opt : config.clangOptions())
optionsBuilder.add(opt, true);
}
const QStringList extraArgsToPrepend = extraClangToolsPrependOptions();
for (const QString &arg : extraArgsToPrepend)
optionsBuilder.prepend(arg);
const QStringList extraArgsToAppend = extraClangToolsAppendOptions();
for (const QString &arg : extraArgsToAppend)
optionsBuilder.add(arg);
return optionsBuilder;
};
generatorWatcher.setFuture(
Utils::asyncRun(
&generateCompilationDB,
CppModelManager::projectInfos(),
dir.path(),
CompilationDbPurpose::Analysis,
ClangDiagnosticConfigsModel::globalDiagnosticOptions(),
getCompilerOptionsBuilder));
generatorSynchronizer.addFuture(generatorWatcher.future());
}
} // namespace ClangTools::Internal

View File

@@ -0,0 +1,33 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <cppeditor/clangdiagnosticconfig.h>
#include <utils/filepath.h>
#include <QObject>
namespace ClangTools::Internal {
class ClangToolsCompilationDb : public QObject
{
Q_OBJECT
public:
~ClangToolsCompilationDb();
bool generateIfNecessary();
Utils::FilePath parentDir() const;
static ClangToolsCompilationDb &getDb(CppEditor::ClangToolType toolType);
signals:
void generated(bool success);
private:
explicit ClangToolsCompilationDb(CppEditor::ClangToolType toolType);
void invalidate();
class Private;
Private * const d;
};
} // namespace ClangTools::Internal

View File

@@ -205,7 +205,7 @@ void DocumentClangToolRunner::run()
const auto [includeDir, clangVersion] = getClangIncludeDirAndVersion(executable);
if (includeDir.isEmpty() || clangVersion.isEmpty())
return;
const AnalyzeUnits units{{m_fileInfo, includeDir, clangVersion}};
const AnalyzeUnits units{{m_fileInfo, tool}};
const auto diagnosticFilter = [mappedPath = vfso().autoSavedFilePath(m_document)](
const FilePath &path) { return path == mappedPath; };
const AnalyzeInputData input{tool,
@@ -219,7 +219,8 @@ void DocumentClangToolRunner::run()
return !m_document->isModified() || isVFSOverlaySupported(executable);
};
const auto outputHandler = [this](const AnalyzeOutputData &output) { onDone(output); };
tasks.append(Group{finishAllAndSuccess, clangToolTask(units, input, setupHandler, outputHandler)});
tasks.append(Group{finishAllAndSuccess,
clangToolTask(tool, units, input, setupHandler, outputHandler)});
};
addClangTool(ClangToolType::Tidy);
addClangTool(ClangToolType::Clazy);

View File

@@ -135,6 +135,8 @@ static QJsonObject createFileObject(const FilePath &buildDir,
}
} else {
args = clangOptionsForFile(projFile, projectPart, projectPartOptions, usePch, clStyle);
if (purpose == CompilationDbPurpose::Analysis && projFile.isHeader())
args << "-Wno-pragma-once-outside-header";
args.prepend("clang"); // TODO: clang-cl for MSVC targets? Does it matter at all what we put here?
}

View File

@@ -19,7 +19,7 @@ class ClangDiagnosticConfig;
using GenerateCompilationDbResult = Utils::expected_str<Utils::FilePath>;
using GetOptionsBuilder = std::function<CompilerOptionsBuilder(const ProjectPart &)>;
enum class CompilationDbPurpose { Project, CodeModel };
enum class CompilationDbPurpose { Project, CodeModel, Analysis };
QJsonArray CPPEDITOR_EXPORT fullProjectPartOptions(
const CppEditor::CompilerOptionsBuilder &optionsBuilder,