forked from qt-creator/qt-creator
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:
@@ -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
|
||||
|
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -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"
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
@@ -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 \
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
@@ -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()));
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -41,6 +41,8 @@ public:
|
||||
void disable();
|
||||
bool enabled() const;
|
||||
|
||||
QString source;
|
||||
|
||||
private:
|
||||
const Diagnostic m_diagnostic;
|
||||
bool m_enabled = true;
|
||||
|
314
src/plugins/clangtools/documentclangtoolrunner.cpp
Normal file
314
src/plugins/clangtools/documentclangtoolrunner.cpp
Normal 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
|
81
src/plugins/clangtools/documentclangtoolrunner.h
Normal file
81
src/plugins/clangtools/documentclangtoolrunner.h
Normal 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
|
@@ -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;
|
||||
}
|
||||
|
@@ -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>
|
||||
|
124
src/plugins/clangtools/virtualfilesystemoverlay.cpp
Normal file
124
src/plugins/clangtools/virtualfilesystemoverlay.cpp
Normal 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
|
59
src/plugins/clangtools/virtualfilesystemoverlay.h
Normal file
59
src/plugins/clangtools/virtualfilesystemoverlay.h
Normal 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
|
Reference in New Issue
Block a user