ClangTools: Add automatic clang tool runner for open documents

Fixes: QTCREATORBUG-23349
Change-Id: I81197180c9d69c7df6184f8fcbf05f2256eaf7f6
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
David Schulz
2020-07-22 14:52:06 +02:00
parent 7f562c4d33
commit e176958da1
24 changed files with 781 additions and 79 deletions

View File

@@ -32,11 +32,13 @@ add_qtc_plugin(ClangTools
clazychecks.ui
diagnosticconfigswidget.cpp diagnosticconfigswidget.h
diagnosticmark.cpp diagnosticmark.h
documentclangtoolrunner.cpp documentclangtoolrunner.h
executableinfo.cpp executableinfo.h
filterdialog.cpp filterdialog.h filterdialog.ui
runsettingswidget.cpp runsettingswidget.h runsettingswidget.ui
settingswidget.cpp settingswidget.h settingswidget.ui
tidychecks.ui
virtualfilesystemoverlay.cpp virtualfilesystemoverlay.h
)
extend_qtc_plugin(ClangTools

View File

@@ -80,6 +80,13 @@ static QStringList mainToolArguments(const QString &mainFilePath, const QString
};
}
static QString virtualFileSystemOverlay(const QString &overlayFilePath)
{
if (overlayFilePath.isEmpty())
return {};
return "--vfsoverlay=" + overlayFilePath;
}
static QStringList clangArguments(const ClangDiagnosticConfig &diagnosticConfig,
const QStringList &baseOptions)
{
@@ -102,11 +109,11 @@ ClangTidyRunner::ClangTidyRunner(const ClangDiagnosticConfig &config, QObject *p
setOutputFileFormat(OutputFileFormat::Yaml);
setExecutable(clangTidyExecutable());
setArgsCreator([this, config](const QStringList &baseOptions) {
return QStringList()
<< tidyChecksArguments(config)
<< mainToolArguments(fileToAnalyze(), outputFilePath())
<< "--"
<< clangArguments(config, baseOptions);
return QStringList() << tidyChecksArguments(config)
<< mainToolArguments(fileToAnalyze(), outputFilePath())
<< virtualFileSystemOverlay(m_overlayFilePath)
<< "--"
<< clangArguments(config, baseOptions);
});
}

View File

@@ -376,13 +376,6 @@ static RunSettings runSettings()
return ClangToolsSettings::instance()->runSettings();
}
static ClangDiagnosticConfig diagnosticConfig(const Utils::Id &diagConfigId)
{
const ClangDiagnosticConfigsModel configs = diagnosticConfigsModel();
QTC_ASSERT(configs.hasConfigWithId(diagConfigId), return ClangDiagnosticConfig());
return configs.configWithId(diagConfigId);
}
ClangTool *ClangTool::instance()
{
return s_instance;

View File

@@ -73,45 +73,6 @@ using namespace Utils;
static Q_LOGGING_CATEGORY(LOG, "qtc.clangtools.runcontrol", QtWarningMsg)
static QStringList splitArgs(QString &argsString)
{
QStringList result;
Utils::QtcProcess::ArgIterator it(&argsString);
while (it.next())
result.append(it.value());
return result;
}
static QStringList extraOptions(const char *environment)
{
if (!qEnvironmentVariableIsSet(environment))
return QStringList();
QString arguments = QString::fromLocal8Bit(qgetenv(environment));
return splitArgs(arguments);
}
static QStringList extraClangToolsPrependOptions()
{
constexpr char csaPrependOptions[] = "QTC_CLANG_CSA_CMD_PREPEND";
constexpr char toolsPrependOptions[] = "QTC_CLANG_TOOLS_CMD_PREPEND";
static const QStringList options = extraOptions(csaPrependOptions)
+ extraOptions(toolsPrependOptions);
if (!options.isEmpty())
qWarning() << "ClangTools options are prepended with " << options.toVector();
return options;
}
static QStringList extraClangToolsAppendOptions()
{
constexpr char csaAppendOptions[] = "QTC_CLANG_CSA_CMD_APPEND";
constexpr char toolsAppendOptions[] = "QTC_CLANG_TOOLS_CMD_APPEND";
static const QStringList options = extraOptions(csaAppendOptions)
+ extraOptions(toolsAppendOptions);
if (!options.isEmpty())
qWarning() << "ClangTools options are appended with " << options.toVector();
return options;
}
namespace ClangTools {
namespace Internal {
@@ -154,26 +115,21 @@ private:
bool m_success = false;
};
static AnalyzeUnits toAnalyzeUnits(const FileInfos &fileInfos, const FilePath &clangIncludeDir,
const QString &clangVersion)
AnalyzeUnit::AnalyzeUnit(const FileInfo &fileInfo,
const FilePath &clangIncludeDir,
const QString &clangVersion)
{
AnalyzeUnits unitsToAnalyze;
const UsePrecompiledHeaders usePrecompiledHeaders = CppTools::getPchUsage();
for (const FileInfo &fileInfo : fileInfos) {
CompilerOptionsBuilder optionsBuilder(*fileInfo.projectPart,
UseSystemHeader::No,
UseTweakedHeaderPaths::Yes,
UseLanguageDefines::No,
UseBuildSystemWarnings::No,
clangVersion,
clangIncludeDir.toString());
QStringList arguments = extraClangToolsPrependOptions();
arguments.append(optionsBuilder.build(fileInfo.kind, usePrecompiledHeaders));
arguments.append(extraClangToolsAppendOptions());
unitsToAnalyze << AnalyzeUnit(fileInfo.file.toString(), arguments);
}
return unitsToAnalyze;
CompilerOptionsBuilder optionsBuilder(*fileInfo.projectPart,
UseSystemHeader::No,
UseTweakedHeaderPaths::Yes,
UseLanguageDefines::No,
UseBuildSystemWarnings::No,
clangVersion,
clangIncludeDir.toString());
file = fileInfo.file.toString();
arguments = extraClangToolsPrependOptions();
arguments.append(optionsBuilder.build(fileInfo.kind, CppTools::getPchUsage()));
arguments.append(extraClangToolsAppendOptions());
}
AnalyzeUnits ClangToolRunWorker::unitsToAnalyze(const FilePath &clangIncludeDir,
@@ -181,7 +137,10 @@ AnalyzeUnits ClangToolRunWorker::unitsToAnalyze(const FilePath &clangIncludeDir,
{
QTC_ASSERT(m_projectInfo.isValid(), return AnalyzeUnits());
return toAnalyzeUnits(m_fileInfos, clangIncludeDir, clangVersion);
AnalyzeUnits units;
for (const FileInfo &fileInfo : m_fileInfos)
units << AnalyzeUnit(fileInfo, clangIncludeDir, clangVersion);
return units;
}
static QDebug operator<<(QDebug debug, const Utils::Environment &environment)
@@ -292,6 +251,8 @@ void ClangToolRunWorker::start()
getClangIncludeDirAndVersion(runControl()->runnable().executable);
const AnalyzeUnits unitsToProcess = unitsToAnalyze(clangIncludeDirAndVersion.first,
clangIncludeDirAndVersion.second);
qCDebug(LOG) << Q_FUNC_INFO << runControl()->runnable().executable
<< clangIncludeDirAndVersion.first << clangIncludeDirAndVersion.second;
qCDebug(LOG) << "Files to process:" << unitsToProcess;
m_queue.clear();

View File

@@ -46,8 +46,9 @@ class ClangToolRunner;
class ProjectBuilder;
struct AnalyzeUnit {
AnalyzeUnit(const QString &file, const QStringList &options)
: file(file), arguments(options) {}
AnalyzeUnit(const FileInfo &fileInfo,
const Utils::FilePath &clangResourceDir,
const QString &clangVersion);
QString file;
QStringList arguments; // without file itself and "-o somePath"

View File

@@ -75,7 +75,15 @@ void ClangToolRunner::init(const QString &outputDirPath,
ClangToolRunner::~ClangToolRunner()
{
Utils::SynchronousProcess::stopProcess(m_process);
if (m_process.state() != QProcess::NotRunning) {
// asking politly to terminate costs ~300 ms on windows so skip the courtasy and direct kill the process
if (Utils::HostOsInfo::isWindowsHost()) {
m_process.kill();
m_process.waitForFinished(100);
} else {
Utils::SynchronousProcess::stopProcess(m_process);
}
}
}
static QString createOutputFilePath(const QString &dirPath, const QString &fileToAnalyze)

View File

@@ -52,6 +52,7 @@ public:
void setExecutable(const QString &executable) { m_executable = executable; }
void setArgsCreator(const ArgsCreator &argsCreator) { m_argsCreator = argsCreator; }
void setOutputFileFormat(const OutputFileFormat &format) { m_outputFileFormat = format; }
void setVFSOverlay(const QString overlayFilePath) { m_overlayFilePath = overlayFilePath; }
QString name() const { return m_name; }
QString executable() const { return m_executable; }
@@ -68,6 +69,9 @@ signals:
void finishedWithSuccess(const QString &fileToAnalyze);
void finishedWithFailure(const QString &errorMessage, const QString &errorDetails);
protected:
QString m_overlayFilePath;
private:
void onProcessOutput();
void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);

View File

@@ -30,10 +30,12 @@ SOURCES += \
clangtoolsutils.cpp \
diagnosticconfigswidget.cpp \
diagnosticmark.cpp \
documentclangtoolrunner.cpp \
executableinfo.cpp \
filterdialog.cpp \
runsettingswidget.cpp \
settingswidget.cpp \
virtualfilesystemoverlay.cpp \
HEADERS += \
clangfileinfo.h \
@@ -56,10 +58,12 @@ HEADERS += \
clangtoolsutils.h \
diagnosticconfigswidget.h \
diagnosticmark.h \
documentclangtoolrunner.h \
executableinfo.h \
filterdialog.h \
runsettingswidget.h \
settingswidget.h \
virtualfilesystemoverlay.h \
FORMS += \
clangselectablefilesdialog.ui \

View File

@@ -68,6 +68,8 @@ QtcPlugin {
"diagnosticconfigswidget.h",
"diagnosticmark.cpp",
"diagnosticmark.h",
"documentclangtoolrunner.cpp",
"documentclangtoolrunner.h",
"executableinfo.cpp",
"executableinfo.h",
"filterdialog.cpp",
@@ -80,6 +82,8 @@ QtcPlugin {
"settingswidget.h",
"settingswidget.ui",
"tidychecks.ui",
"virtualfilesystemoverlay.cpp",
"virtualfilesystemoverlay.h",
]
Group {

View File

@@ -29,6 +29,7 @@
#include "clangtoolsconstants.h"
#include "clangtoolsprojectsettings.h"
#include "clangtoolsprojectsettingswidget.h"
#include "documentclangtoolrunner.h"
#include "settingswidget.h"
#ifdef WITH_TESTS
@@ -84,6 +85,7 @@ class ClangToolsPluginPrivate
public:
ClangTool clangTool;
ClangToolsOptionsPage optionsPage;
QMap<Core::IDocument *, DocumentClangToolRunner *> documentRunners;
};
ClangToolsPlugin::~ClangToolsPlugin()
@@ -111,9 +113,28 @@ bool ClangToolsPlugin::initialize(const QStringList &arguments, QString *errorSt
panelFactory->setCreateWidgetFunction([](Project *project) { return new ProjectSettingsWidget(project); });
ProjectPanelFactory::registerFactory(panelFactory);
connect(Core::EditorManager::instance(),
&Core::EditorManager::currentEditorChanged,
this,
&ClangToolsPlugin::onCurrentEditorChanged);
return true;
}
void ClangToolsPlugin::onCurrentEditorChanged()
{
for (Core::IEditor *editor : Core::EditorManager::visibleEditors()) {
IDocument *document = editor->document();
if (d->documentRunners.contains(document))
continue;
auto runner = new DocumentClangToolRunner(document);
connect(runner, &DocumentClangToolRunner::destroyed, this, [this, document]() {
d->documentRunners.remove(document);
});
d->documentRunners[document] = runner;
}
}
void ClangToolsPlugin::registerAnalyzeActions()
{
ActionManager::registerAction(d->clangTool.startAction(), Constants::RUN_ON_PROJECT);

View File

@@ -27,6 +27,7 @@
#include <extensionsystem/iplugin.h>
namespace Core { class IDocument; }
namespace ProjectExplorer { class ProjectPanelFactory; }
namespace ClangTools {
@@ -46,6 +47,7 @@ public:
private:
bool initialize(const QStringList &arguments, QString *errorString) final;
void registerAnalyzeActions();
void onCurrentEditorChanged();
QVector<QObject *> createTestObjects() const final;

View File

@@ -48,6 +48,8 @@ ClangToolsProjectSettings::ClangToolsProjectSettings(ProjectExplorer::Project *p
: m_project(project)
{
load();
connect(this, &ClangToolsProjectSettings::suppressedDiagnosticsChanged,
this, &ClangToolsProjectSettings::changed);
connect(project, &ProjectExplorer::Project::settingsLoaded,
this, &ClangToolsProjectSettings::load);
connect(project, &ProjectExplorer::Project::aboutToSaveSettings, this,
@@ -59,6 +61,38 @@ ClangToolsProjectSettings::~ClangToolsProjectSettings()
store();
}
void ClangToolsProjectSettings::setUseGlobalSettings(bool useGlobalSettings)
{
if (m_useGlobalSettings == useGlobalSettings)
return;
m_useGlobalSettings = useGlobalSettings;
emit changed();
}
void ClangToolsProjectSettings::setRunSettings(const RunSettings &settings)
{
if (m_runSettings == settings)
return;
m_runSettings = settings;
emit changed();
}
void ClangToolsProjectSettings::setSelectedDirs(const QSet<Utils::FilePath> &value)
{
if (m_selectedDirs == value)
return;
m_selectedDirs = value;
emit changed();
}
void ClangToolsProjectSettings::setSelectedFiles(const QSet<Utils::FilePath> &value)
{
if (m_selectedFiles == value)
return;
m_selectedFiles = value;
emit changed();
}
void ClangToolsProjectSettings::addSuppressedDiagnostic(const SuppressedDiagnostic &diag)
{
QTC_ASSERT(!m_suppressedDiagnostics.contains(diag), return);

View File

@@ -70,16 +70,16 @@ public:
~ClangToolsProjectSettings() override;
bool useGlobalSettings() const { return m_useGlobalSettings; }
void setUseGlobalSettings(bool useGlobalSettings) { m_useGlobalSettings = useGlobalSettings; }
void setUseGlobalSettings(bool useGlobalSettings);
RunSettings runSettings() const { return m_runSettings; }
void setRunSettings(const RunSettings &settings) { m_runSettings = settings; }
void setRunSettings(const RunSettings &settings);
QSet<Utils::FilePath> selectedDirs() const { return m_selectedDirs; }
void setSelectedDirs(const QSet<Utils::FilePath> &value) { m_selectedDirs = value; }
void setSelectedDirs(const QSet<Utils::FilePath> &value);
QSet<Utils::FilePath> selectedFiles() const { return m_selectedFiles; }
void setSelectedFiles(const QSet<Utils::FilePath> &value) { m_selectedFiles = value; }
void setSelectedFiles(const QSet<Utils::FilePath> &value);
SuppressedDiagnosticsList suppressedDiagnostics() const { return m_suppressedDiagnostics; }
void addSuppressedDiagnostic(const SuppressedDiagnostic &diag);
@@ -91,6 +91,7 @@ public:
signals:
void suppressedDiagnosticsChanged();
void changed();
private:
void load();

View File

@@ -42,7 +42,7 @@ static const char clazyStandaloneExecutableKey[] = "ClazyStandaloneExecutable";
static const char parallelJobsKey[] = "ParallelJobs";
static const char buildBeforeAnalysisKey[] = "BuildBeforeAnalysis";
static const char analyzeOpenFilesKey[] = "AnalyzeOpenFiles";
static const char oldDiagnosticConfigIdKey[] = "diagnosticConfigId";
using namespace CppTools;
@@ -66,6 +66,7 @@ void RunSettings::fromMap(const QVariantMap &map, const QString &prefix)
m_diagnosticConfigId = Utils::Id::fromSetting(map.value(prefix + diagnosticConfigIdKey));
m_parallelJobs = map.value(prefix + parallelJobsKey).toInt();
m_buildBeforeAnalysis = map.value(prefix + buildBeforeAnalysisKey).toBool();
m_analyzeOpenFiles = map.value(prefix + analyzeOpenFilesKey).toBool();
}
void RunSettings::toMap(QVariantMap &map, const QString &prefix) const
@@ -73,6 +74,7 @@ void RunSettings::toMap(QVariantMap &map, const QString &prefix) const
map.insert(prefix + diagnosticConfigIdKey, m_diagnosticConfigId.toSetting());
map.insert(prefix + parallelJobsKey, m_parallelJobs);
map.insert(prefix + buildBeforeAnalysisKey, m_buildBeforeAnalysis);
map.insert(prefix + analyzeOpenFilesKey, m_analyzeOpenFiles);
}
Utils::Id RunSettings::diagnosticConfigId() const
@@ -82,6 +84,14 @@ Utils::Id RunSettings::diagnosticConfigId() const
return m_diagnosticConfigId;
}
bool RunSettings::operator==(const RunSettings &other) const
{
return m_diagnosticConfigId == other.m_diagnosticConfigId
&& m_parallelJobs == other.m_parallelJobs
&& m_buildBeforeAnalysis == other.m_buildBeforeAnalysis
&& m_analyzeOpenFiles == other.m_analyzeOpenFiles;
}
ClangToolsSettings::ClangToolsSettings()
{
readSettings();
@@ -154,6 +164,7 @@ void ClangToolsSettings::readSettings()
defaults.insert(diagnosticConfigIdKey, defaultDiagnosticId().toSetting());
defaults.insert(parallelJobsKey, m_runSettings.parallelJobs());
defaults.insert(buildBeforeAnalysisKey, m_runSettings.buildBeforeAnalysis());
defaults.insert(analyzeOpenFilesKey, m_runSettings.analyzeOpenFiles());
map = defaults;
for (QVariantMap::ConstIterator it = defaults.constBegin(); it != defaults.constEnd(); ++it)
map.insert(it.key(), s->value(it.key(), it.value()));

View File

@@ -54,10 +54,16 @@ public:
int parallelJobs() const { return m_parallelJobs; }
void setParallelJobs(int jobs) { m_parallelJobs = jobs; }
bool analyzeOpenFiles() const { return m_analyzeOpenFiles; }
void setAnalyzeOpenFiles(bool analyzeOpenFiles) { m_analyzeOpenFiles = analyzeOpenFiles; }
bool operator==(const RunSettings &other) const;
private:
Utils::Id m_diagnosticConfigId;
int m_parallelJobs = -1;
bool m_buildBeforeAnalysis = true;
bool m_analyzeOpenFiles = true;
};
class ClangToolsSettings : public QObject

View File

@@ -35,6 +35,7 @@
#include <cpptools/cpptoolsreuse.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/qtcprocess.h>
#include <utils/checkablemessagebox.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
@@ -304,5 +305,51 @@ QString documentationUrl(const QString &checkName)
return url;
}
ClangDiagnosticConfig diagnosticConfig(const Utils::Id &diagConfigId)
{
const ClangDiagnosticConfigsModel configs = diagnosticConfigsModel();
QTC_ASSERT(configs.hasConfigWithId(diagConfigId), return ClangDiagnosticConfig());
return configs.configWithId(diagConfigId);
}
QStringList splitArgs(QString &argsString)
{
QStringList result;
Utils::QtcProcess::ArgIterator it(&argsString);
while (it.next())
result.append(it.value());
return result;
}
QStringList extraOptions(const char *envVar)
{
if (!qEnvironmentVariableIsSet(envVar))
return QStringList();
QString arguments = QString::fromLocal8Bit(qgetenv(envVar));
return splitArgs(arguments);
}
QStringList extraClangToolsPrependOptions()
{
constexpr char csaPrependOptions[] = "QTC_CLANG_CSA_CMD_PREPEND";
constexpr char toolsPrependOptions[] = "QTC_CLANG_TOOLS_CMD_PREPEND";
static const QStringList options = extraOptions(csaPrependOptions)
+ extraOptions(toolsPrependOptions);
if (!options.isEmpty())
qWarning() << "ClangTools options are prepended with " << options.toVector();
return options;
}
QStringList extraClangToolsAppendOptions()
{
constexpr char csaAppendOptions[] = "QTC_CLANG_CSA_CMD_APPEND";
constexpr char toolsAppendOptions[] = "QTC_CLANG_TOOLS_CMD_APPEND";
static const QStringList options = extraOptions(csaAppendOptions)
+ extraOptions(toolsAppendOptions);
if (!options.isEmpty())
qWarning() << "ClangTools options are appended with " << options.toVector();
return options;
}
} // namespace Internal
} // namespace ClangTools

View File

@@ -80,5 +80,10 @@ CppTools::ClangDiagnosticConfigsModel diagnosticConfigsModel();
CppTools::ClangDiagnosticConfigsModel diagnosticConfigsModel(
const CppTools::ClangDiagnosticConfigs &customConfigs);
CppTools::ClangDiagnosticConfig diagnosticConfig(const Utils::Id &diagConfigId);
QStringList extraClangToolsPrependOptions();
QStringList extraClangToolsAppendOptions();
} // namespace Internal
} // namespace ClangTools

View File

@@ -41,6 +41,8 @@ public:
void disable();
bool enabled() const;
QString source;
private:
const Diagnostic m_diagnostic;
bool m_enabled = true;

View File

@@ -0,0 +1,314 @@
/****************************************************************************
**
** Copyright (C) 2020 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 "documentclangtoolrunner.h"
#include "clangfileinfo.h"
#include "clangtidyclazyrunner.h"
#include "clangtoolruncontrol.h"
#include "clangtoolsprojectsettings.h"
#include "clangtoolsutils.h"
#include "diagnosticmark.h"
#include "executableinfo.h"
#include "virtualfilesystemoverlay.h"
#include <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <cpptools/cppmodelmanager.h>
#include <projectexplorer/buildtargettype.h>
#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <texteditor/textdocument.h>
#include <texteditor/textmark.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
#include <QLoggingCategory>
static Q_LOGGING_CATEGORY(LOG, "qtc.clangtools.cftr", QtWarningMsg)
namespace ClangTools {
namespace Internal {
DocumentClangToolRunner::DocumentClangToolRunner(Core::IDocument *document)
: QObject(document)
, m_document(document)
, m_temporaryDir("clangtools-single-XXXXXX")
{
using namespace CppTools;
m_runTimer.setInterval(500);
m_runTimer.setSingleShot(true);
connect(m_document,
&Core::IDocument::contentsChanged,
this,
&DocumentClangToolRunner::scheduleRun);
connect(CppModelManager::instance(),
&CppModelManager::projectPartsUpdated,
this,
&DocumentClangToolRunner::scheduleRun);
connect(ClangToolsSettings::instance(),
&ClangToolsSettings::changed,
this,
&DocumentClangToolRunner::scheduleRun);
connect(&m_runTimer, &QTimer::timeout, this, &DocumentClangToolRunner::run);
run();
}
DocumentClangToolRunner::~DocumentClangToolRunner()
{
cancel();
qDeleteAll(m_marks);
}
void DocumentClangToolRunner::scheduleRun()
{
for (DiagnosticMark *mark : m_marks)
mark->disable();
m_runTimer.start();
}
static ProjectExplorer::Project *findProject(const Utils::FilePath &file)
{
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(file);
return project ? project : ProjectExplorer::SessionManager::startupProject();
}
static VirtualFileSystemOverlay &vfso()
{
static VirtualFileSystemOverlay overlay("clangtools-vfso-XXXXXX");
return overlay;
}
static FileInfo getFileInfo(const Utils::FilePath &file, ProjectExplorer::Project *project)
{
CppTools::ProjectInfo projectInfo = CppTools::CppModelManager::instance()->projectInfo(project);
if (!projectInfo.isValid())
return {};
FileInfo candidate;
for (const CppTools::ProjectPart::Ptr &projectPart : projectInfo.projectParts()) {
QTC_ASSERT(projectPart, continue);
for (const CppTools::ProjectFile &projectFile : qAsConst(projectPart->files)) {
QTC_ASSERT(projectFile.kind != CppTools::ProjectFile::Unclassified, continue);
QTC_ASSERT(projectFile.kind != CppTools::ProjectFile::Unsupported, continue);
if (projectFile.path == CppTools::CppModelManager::configurationFileName())
continue;
if (file.toString() != projectFile.path)
continue;
if (!projectFile.active)
continue;
if (projectPart->buildTargetType != ProjectExplorer::BuildTargetType::Unknown) {
// found the best candidate, early return
return FileInfo(Utils::FilePath::fromString(projectFile.path),
projectFile.kind,
projectPart);
}
if (candidate.projectPart.isNull()) {
// found at least something but keep looking for better candidates
candidate = FileInfo(Utils::FilePath::fromString(projectFile.path),
projectFile.kind,
projectPart);
}
}
}
return candidate;
}
static Utils::Environment projectBuildEnvironment(ProjectExplorer::Project *project)
{
Utils::Environment env;
if (ProjectExplorer::Target *target = project->activeTarget()) {
if (ProjectExplorer::BuildConfiguration *buildConfig = target->activeBuildConfiguration())
env = buildConfig->environment();
}
if (env.size() == 0)
env = Utils::Environment::systemEnvironment();
return env;
}
void DocumentClangToolRunner::run()
{
cancel();
auto isEditorForCurrentDocument = [this](const Core::IEditor *editor) {
return editor->document() == m_document;
};
if (Utils::anyOf(Core::EditorManager::visibleEditors(), isEditorForCurrentDocument)) {
const Utils::FilePath filePath = m_document->filePath();
if (ProjectExplorer::Project *project = findProject(filePath)) {
m_fileInfo = getFileInfo(filePath, project);
if (m_fileInfo.file.exists()) {
const auto projectSettings = ClangToolsProjectSettings::getSettings(project);
const RunSettings &runSettings = projectSettings->useGlobalSettings()
? ClangToolsSettings::instance()->runSettings()
: projectSettings->runSettings();
m_projectSettingsUpdate = connect(projectSettings.data(),
&ClangToolsProjectSettings::changed,
this,
&DocumentClangToolRunner::run);
if (runSettings.analyzeOpenFiles()) {
vfso().update();
CppTools::ClangDiagnosticConfig config = diagnosticConfig(
runSettings.diagnosticConfigId());
Utils::Environment env = projectBuildEnvironment(project);
if (config.isClangTidyEnabled()) {
m_runnerCreators << [this, env, config]() {
return createRunner<ClangTidyRunner>(config, env);
};
}
if (config.isClazyEnabled() && !m_document->isModified()) {
m_runnerCreators << [this, env, config]() {
return createRunner<ClazyStandaloneRunner>(config, env);
};
}
}
}
}
} else {
deleteLater();
}
runNext();
}
QPair<Utils::FilePath, QString> getClangIncludeDirAndVersion(ClangToolRunner *runner)
{
static QMap<Utils::FilePath, QPair<Utils::FilePath, QString>> cache;
const Utils::FilePath tool = Utils::FilePath::fromString(runner->executable());
auto it = cache.find(tool);
if (it == cache.end())
it = cache.insert(tool, getClangIncludeDirAndVersion(tool));
return it.value();
}
void DocumentClangToolRunner::runNext()
{
m_currentRunner.reset(m_runnerCreators.isEmpty() ? nullptr : m_runnerCreators.takeFirst()());
if (m_currentRunner) {
auto [clangIncludeDir, clangVersion] = getClangIncludeDirAndVersion(m_currentRunner.get());
qCDebug(LOG) << Q_FUNC_INFO << m_currentRunner->executable() << clangIncludeDir
<< clangVersion << m_fileInfo.file;
AnalyzeUnit unit(m_fileInfo, clangIncludeDir, clangVersion);
QTC_ASSERT(Utils::FilePath::fromString(unit.file).exists(), runNext(); return;);
m_currentRunner->setVFSOverlay(vfso().overlayFilePath().toString());
if (!m_currentRunner->run(unit.file, unit.arguments))
runNext();
} else {
finalize();
}
}
void DocumentClangToolRunner::onSuccess()
{
QString errorMessage;
Utils::FilePath mappedPath = vfso().filePath(m_document);
Diagnostics diagnostics = readExportedDiagnostics(
Utils::FilePath::fromString(m_currentRunner->outputFilePath()),
[&](const Utils::FilePath &path) { return path == mappedPath; },
&errorMessage);
if (mappedPath != m_document->filePath()) {
const QString originalPath = m_document->filePath().toString();
for (Diagnostic &diag : diagnostics)
diag.location.filePath = originalPath;
}
// remove outdated marks of the current runner
auto [toDelete, newMarks] = Utils::partition(m_marks, [this](DiagnosticMark *mark) {
return mark->source == m_currentRunner->name();
});
m_marks = newMarks;
qDeleteAll(toDelete);
m_marks << Utils::transform(diagnostics, [this](const Diagnostic &diagnostic) {
auto mark = new DiagnosticMark(diagnostic);
mark->source = m_currentRunner->name();
return mark;
});
runNext();
}
void DocumentClangToolRunner::onFailure(const QString &errorMessage, const QString &errorDetails)
{
qCDebug(LOG) << "Failed to analyze " << m_fileInfo.file << ":" << errorMessage << errorDetails;
runNext();
}
void DocumentClangToolRunner::finalize()
{
// remove all disabled textMarks
auto [newMarks, toDelete] = Utils::partition(m_marks, &DiagnosticMark::enabled);
m_marks = newMarks;
qDeleteAll(toDelete);
}
void DocumentClangToolRunner::cancel()
{
if (m_projectSettingsUpdate)
disconnect(m_projectSettingsUpdate);
m_runnerCreators.clear();
if (m_currentRunner) {
m_currentRunner->disconnect(this);
m_currentRunner.reset(nullptr);
}
}
const CppTools::ClangDiagnosticConfig DocumentClangToolRunner::getDiagnosticConfig(ProjectExplorer::Project *project)
{
const auto projectSettings = ClangToolsProjectSettings::getSettings(project);
m_projectSettingsUpdate = connect(projectSettings.data(),
&ClangToolsProjectSettings::changed,
this,
&DocumentClangToolRunner::run);
const Utils::Id &id = projectSettings->useGlobalSettings()
? ClangToolsSettings::instance()->runSettings().diagnosticConfigId()
: projectSettings->runSettings().diagnosticConfigId();
return diagnosticConfig(id);
}
template<class T>
ClangToolRunner *DocumentClangToolRunner::createRunner(const CppTools::ClangDiagnosticConfig &config,
const Utils::Environment &env)
{
auto runner = new T(config, this);
runner->init(m_temporaryDir.path(), env);
connect(runner, &ClangToolRunner::finishedWithSuccess,
this, &DocumentClangToolRunner::onSuccess);
connect(runner, &ClangToolRunner::finishedWithFailure,
this, &DocumentClangToolRunner::onFailure);
return runner;
}
} // namespace Internal
} // namespace ClangTools

View File

@@ -0,0 +1,81 @@
/****************************************************************************
**
** Copyright (C) 2020 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 "clangfileinfo.h"
#include <utils/fileutils.h>
#include <utils/temporarydirectory.h>
#include <QObject>
#include <QTimer>
namespace Core { class IDocument; }
namespace CppTools { class ClangDiagnosticConfig; }
namespace ClangTools {
namespace Internal {
class ClangToolRunner;
class DiagnosticMark;
class DocumentClangToolRunner : public QObject
{
Q_OBJECT
public:
DocumentClangToolRunner(Core::IDocument *doc);
~DocumentClangToolRunner();
private:
void scheduleRun();
void run();
void runNext();
void onSuccess();
void onFailure(const QString &errorMessage, const QString &errorDetails);
void finalize();
void cancel();
const CppTools::ClangDiagnosticConfig getDiagnosticConfig(ProjectExplorer::Project *project);
template<class T>
ClangToolRunner *createRunner(const CppTools::ClangDiagnosticConfig &config,
const Utils::Environment &env);
QTimer m_runTimer;
Core::IDocument *m_document = nullptr;
Utils::TemporaryDirectory m_temporaryDir;
std::unique_ptr<ClangToolRunner> m_currentRunner;
QList<std::function<ClangToolRunner *()>> m_runnerCreators;
QList<DiagnosticMark *> m_marks;
FileInfo m_fileInfo;
QMetaObject::Connection m_projectSettingsUpdate;
};
} // namespace Internal
} // namespace ClangTools

View File

@@ -110,6 +110,9 @@ void RunSettingsWidget::fromSettings(const RunSettings &s)
connect(m_ui->parallelJobsSpinBox,
QOverload<int>::of(&QSpinBox::valueChanged),
[this](int) { emit changed(); });
m_ui->analyzeOpenFiles->setChecked(s.analyzeOpenFiles());
connect(m_ui->analyzeOpenFiles, &QCheckBox::toggled, this, &RunSettingsWidget::changed);
}
RunSettings RunSettingsWidget::toSettings() const
@@ -118,6 +121,7 @@ RunSettings RunSettingsWidget::toSettings() const
s.setDiagnosticConfigId(m_ui->diagnosticWidget->currentConfigId());
s.setBuildBeforeAnalysis(m_ui->buildBeforeAnalysis->checkState() == Qt::CheckState::Checked);
s.setParallelJobs(m_ui->parallelJobsSpinBox->value());
s.setAnalyzeOpenFiles(m_ui->analyzeOpenFiles->checkState() == Qt::CheckState::Checked);
return s;
}

View File

@@ -42,6 +42,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="analyzeOpenFiles">
<property name="text">
<string>Analyze open files</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="processesLayout">
<item>

View File

@@ -0,0 +1,124 @@
/****************************************************************************
**
** Copyright (C) 2020 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 "virtualfilesystemoverlay.h"
#include <coreplugin/documentmanager.h>
#include <texteditor/textdocument.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QTextDocument>
static Q_LOGGING_CATEGORY(LOG, "qtc.clangtools.vfso", QtWarningMsg)
namespace ClangTools {
namespace Internal {
VirtualFileSystemOverlay::VirtualFileSystemOverlay(const QString &rootPattern)
: m_root(rootPattern)
, m_overlayFilePath(Utils::FilePath::fromString(m_root.filePath("vfso.yaml")))
{ }
void VirtualFileSystemOverlay::update()
{
Utils::FileUtils::removeRecursively(overlayFilePath());
QFile overlayFile(m_overlayFilePath.toString());
if (!overlayFile.open(QFile::ReadWrite))
return;
std::map<Utils::FilePath, QList<Core::IDocument *>> documentRoots;
const QList<Core::IDocument *> &modifiedDocuments = Core::DocumentManager::modifiedDocuments();
QMap<Core::IDocument *, AutoSavedPath> newSaved;
for (Core::IDocument *doc : modifiedDocuments) {
auto document = qobject_cast<TextEditor::TextDocument *>(doc);
if (!document)
continue;
documentRoots[doc->filePath().absolutePath()] << doc;
AutoSavedPath saved = m_saved.take(document);
if (saved.revision != document->document()->revision()) {
saved.revision = document->document()->revision();
QString error;
saved.path = Utils::FilePath::fromString(m_root.path())
.pathAppended(doc->filePath().fileName() + ".auto");
while (saved.path.exists())
saved.path = saved.path + ".1";
if (!doc->save(&error, saved.path.toString(), true)) {
qCDebug(LOG) << error;
continue;
}
}
newSaved[doc] = saved;
}
for (const AutoSavedPath &path : qAsConst(m_saved)) {
QString error;
if (!Utils::FileUtils::removeRecursively(path.path, &error))
qCDebug(LOG) << error;
}
m_saved = newSaved;
auto toContent = [this](Core::IDocument *document) {
QJsonObject content;
content["name"] = document->filePath().fileName();
content["type"] = "file";
content["external-contents"] = m_saved[document].path.toUserOutput();
return content;
};
QJsonObject main;
main["version"] = 0;
QJsonArray jsonRoots;
for (auto [root, documents] : documentRoots) {
QJsonObject jsonRoot;
jsonRoot["type"] = "directory";
jsonRoot["name"] = root.toUserOutput();
QJsonArray contents;
for (auto doc : documents)
contents << toContent(doc);
jsonRoot["contents"] = contents;
jsonRoots << jsonRoot;
}
main["roots"] = jsonRoots;
QJsonDocument overlay(main);
if (!overlayFile.write(overlay.toJson(QJsonDocument::Compact)))
qCDebug(LOG) << "failed to write vfso to " << m_overlayFilePath;
overlayFile.close();
}
Utils::FilePath VirtualFileSystemOverlay::overlayFilePath() { return m_overlayFilePath; }
Utils::FilePath VirtualFileSystemOverlay::filePath(Core::IDocument *doc)
{
auto it = m_saved.find(doc);
if (it != m_saved.end())
return it.value().path;
return doc->filePath();
}
} // namespace Internal
} // namespace ClangTools

View File

@@ -0,0 +1,59 @@
/****************************************************************************
**
** Copyright (C) 2020 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 <utils/fileutils.h>
#include <utils/temporarydirectory.h>
namespace Core { class IDocument; }
namespace ClangTools {
namespace Internal {
class VirtualFileSystemOverlay
{
public:
VirtualFileSystemOverlay(const QString &rootPattern);
void update();
Utils::FilePath overlayFilePath();
Utils::FilePath filePath(Core::IDocument *doc);
private:
Utils::TemporaryDirectory m_root;
Utils::FilePath m_overlayFilePath;
struct AutoSavedPath
{
int revision = -1;
Utils::FilePath path;
};
QMap<Core::IDocument *, AutoSavedPath> m_saved;
};
} // namespace Internal
} // namespace ClangTools