aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDavid Schulz <david.schulz@qt.io>2020-07-22 14:52:06 +0200
committerDavid Schulz <david.schulz@qt.io>2020-08-28 07:20:32 +0000
commite176958da112984a634b355142cdd5d59a748efc (patch)
tree388d14991d0bc58ddbf4d6bfe7bd528d95869caa /src
parent7f562c4d33a042fe51946c273581b2e9ad64159f (diff)
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>
Diffstat (limited to 'src')
-rw-r--r--src/plugins/clangtools/CMakeLists.txt2
-rw-r--r--src/plugins/clangtools/clangtidyclazyrunner.cpp17
-rw-r--r--src/plugins/clangtools/clangtool.cpp7
-rw-r--r--src/plugins/clangtools/clangtoolruncontrol.cpp79
-rw-r--r--src/plugins/clangtools/clangtoolruncontrol.h5
-rw-r--r--src/plugins/clangtools/clangtoolrunner.cpp10
-rw-r--r--src/plugins/clangtools/clangtoolrunner.h4
-rw-r--r--src/plugins/clangtools/clangtools.pro4
-rw-r--r--src/plugins/clangtools/clangtools.qbs4
-rw-r--r--src/plugins/clangtools/clangtoolsplugin.cpp21
-rw-r--r--src/plugins/clangtools/clangtoolsplugin.h2
-rw-r--r--src/plugins/clangtools/clangtoolsprojectsettings.cpp34
-rw-r--r--src/plugins/clangtools/clangtoolsprojectsettings.h9
-rw-r--r--src/plugins/clangtools/clangtoolssettings.cpp13
-rw-r--r--src/plugins/clangtools/clangtoolssettings.h6
-rw-r--r--src/plugins/clangtools/clangtoolsutils.cpp47
-rw-r--r--src/plugins/clangtools/clangtoolsutils.h5
-rw-r--r--src/plugins/clangtools/diagnosticmark.h2
-rw-r--r--src/plugins/clangtools/documentclangtoolrunner.cpp314
-rw-r--r--src/plugins/clangtools/documentclangtoolrunner.h81
-rw-r--r--src/plugins/clangtools/runsettingswidget.cpp4
-rw-r--r--src/plugins/clangtools/runsettingswidget.ui7
-rw-r--r--src/plugins/clangtools/virtualfilesystemoverlay.cpp124
-rw-r--r--src/plugins/clangtools/virtualfilesystemoverlay.h59
24 files changed, 781 insertions, 79 deletions
diff --git a/src/plugins/clangtools/CMakeLists.txt b/src/plugins/clangtools/CMakeLists.txt
index 4ac94e7c25..829b77c97a 100644
--- a/src/plugins/clangtools/CMakeLists.txt
+++ b/src/plugins/clangtools/CMakeLists.txt
@@ -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
diff --git a/src/plugins/clangtools/clangtidyclazyrunner.cpp b/src/plugins/clangtools/clangtidyclazyrunner.cpp
index 8fbc09ff5a..d3e2b34198 100644
--- a/src/plugins/clangtools/clangtidyclazyrunner.cpp
+++ b/src/plugins/clangtools/clangtidyclazyrunner.cpp
@@ -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);
});
}
diff --git a/src/plugins/clangtools/clangtool.cpp b/src/plugins/clangtools/clangtool.cpp
index 49f389b4f2..fde54fe130 100644
--- a/src/plugins/clangtools/clangtool.cpp
+++ b/src/plugins/clangtools/clangtool.cpp
@@ -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;
diff --git a/src/plugins/clangtools/clangtoolruncontrol.cpp b/src/plugins/clangtools/clangtoolruncontrol.cpp
index ac054135de..738e98fa6c 100644
--- a/src/plugins/clangtools/clangtoolruncontrol.cpp
+++ b/src/plugins/clangtools/clangtoolruncontrol.cpp
@@ -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();
diff --git a/src/plugins/clangtools/clangtoolruncontrol.h b/src/plugins/clangtools/clangtoolruncontrol.h
index e72df21c68..9f46d0f2e3 100644
--- a/src/plugins/clangtools/clangtoolruncontrol.h
+++ b/src/plugins/clangtools/clangtoolruncontrol.h
@@ -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"
diff --git a/src/plugins/clangtools/clangtoolrunner.cpp b/src/plugins/clangtools/clangtoolrunner.cpp
index 0a63e8731b..b0fc220e04 100644
--- a/src/plugins/clangtools/clangtoolrunner.cpp
+++ b/src/plugins/clangtools/clangtoolrunner.cpp
@@ -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)
diff --git a/src/plugins/clangtools/clangtoolrunner.h b/src/plugins/clangtools/clangtoolrunner.h
index 575876ff6c..87ec5d5f06 100644
--- a/src/plugins/clangtools/clangtoolrunner.h
+++ b/src/plugins/clangtools/clangtoolrunner.h
@@ -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);
diff --git a/src/plugins/clangtools/clangtools.pro b/src/plugins/clangtools/clangtools.pro
index 2d2c3b3c27..f4a85c24e6 100644
--- a/src/plugins/clangtools/clangtools.pro
+++ b/src/plugins/clangtools/clangtools.pro
@@ -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 \
diff --git a/src/plugins/clangtools/clangtools.qbs b/src/plugins/clangtools/clangtools.qbs
index eab5cd240c..6e9d83b14d 100644
--- a/src/plugins/clangtools/clangtools.qbs
+++ b/src/plugins/clangtools/clangtools.qbs
@@ -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 {
diff --git a/src/plugins/clangtools/clangtoolsplugin.cpp b/src/plugins/clangtools/clangtoolsplugin.cpp
index 242575d5fc..63f5df80cc 100644
--- a/src/plugins/clangtools/clangtoolsplugin.cpp
+++ b/src/plugins/clangtools/clangtoolsplugin.cpp
@@ -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);
diff --git a/src/plugins/clangtools/clangtoolsplugin.h b/src/plugins/clangtools/clangtoolsplugin.h
index aaf1711fd6..fb6d4b279c 100644
--- a/src/plugins/clangtools/clangtoolsplugin.h
+++ b/src/plugins/clangtools/clangtoolsplugin.h
@@ -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;
diff --git a/src/plugins/clangtools/clangtoolsprojectsettings.cpp b/src/plugins/clangtools/clangtoolsprojectsettings.cpp
index e83c3b36f1..e92d87771b 100644
--- a/src/plugins/clangtools/clangtoolsprojectsettings.cpp
+++ b/src/plugins/clangtools/clangtoolsprojectsettings.cpp
@@ -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);
diff --git a/src/plugins/clangtools/clangtoolsprojectsettings.h b/src/plugins/clangtools/clangtoolsprojectsettings.h
index aa6c035395..ed3c57fd72 100644
--- a/src/plugins/clangtools/clangtoolsprojectsettings.h
+++ b/src/plugins/clangtools/clangtoolsprojectsettings.h
@@ -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();
diff --git a/src/plugins/clangtools/clangtoolssettings.cpp b/src/plugins/clangtools/clangtoolssettings.cpp
index ec76cdd729..2342e12b1d 100644
--- a/src/plugins/clangtools/clangtoolssettings.cpp
+++ b/src/plugins/clangtools/clangtoolssettings.cpp
@@ -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()));
diff --git a/src/plugins/clangtools/clangtoolssettings.h b/src/plugins/clangtools/clangtoolssettings.h
index 21b62e02a4..ac2a6b4185 100644
--- a/src/plugins/clangtools/clangtoolssettings.h
+++ b/src/plugins/clangtools/clangtoolssettings.h
@@ -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
diff --git a/src/plugins/clangtools/clangtoolsutils.cpp b/src/plugins/clangtools/clangtoolsutils.cpp
index 6c394aebc0..99cb1c9dd3 100644
--- a/src/plugins/clangtools/clangtoolsutils.cpp
+++ b/src/plugins/clangtools/clangtoolsutils.cpp
@@ -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
diff --git a/src/plugins/clangtools/clangtoolsutils.h b/src/plugins/clangtools/clangtoolsutils.h
index 4aa6f7eac9..9b6ddfa4c2 100644
--- a/src/plugins/clangtools/clangtoolsutils.h
+++ b/src/plugins/clangtools/clangtoolsutils.h
@@ -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
diff --git a/src/plugins/clangtools/diagnosticmark.h b/src/plugins/clangtools/diagnosticmark.h
index 040ce16adc..38cbe281a2 100644
--- a/src/plugins/clangtools/diagnosticmark.h
+++ b/src/plugins/clangtools/diagnosticmark.h
@@ -41,6 +41,8 @@ public:
void disable();
bool enabled() const;
+ QString source;
+
private:
const Diagnostic m_diagnostic;
bool m_enabled = true;
diff --git a/src/plugins/clangtools/documentclangtoolrunner.cpp b/src/plugins/clangtools/documentclangtoolrunner.cpp
new file mode 100644
index 0000000000..b37c39087d
--- /dev/null
+++ b/src/plugins/clangtools/documentclangtoolrunner.cpp
@@ -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
diff --git a/src/plugins/clangtools/documentclangtoolrunner.h b/src/plugins/clangtools/documentclangtoolrunner.h
new file mode 100644
index 0000000000..4cf263c1aa
--- /dev/null
+++ b/src/plugins/clangtools/documentclangtoolrunner.h
@@ -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
diff --git a/src/plugins/clangtools/runsettingswidget.cpp b/src/plugins/clangtools/runsettingswidget.cpp
index 76fc820373..64fd25836e 100644
--- a/src/plugins/clangtools/runsettingswidget.cpp
+++ b/src/plugins/clangtools/runsettingswidget.cpp
@@ -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;
}
diff --git a/src/plugins/clangtools/runsettingswidget.ui b/src/plugins/clangtools/runsettingswidget.ui
index e7ad362c84..370932c9a8 100644
--- a/src/plugins/clangtools/runsettingswidget.ui
+++ b/src/plugins/clangtools/runsettingswidget.ui
@@ -43,6 +43,13 @@
</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>
<widget class="QLabel" name="label_2">
diff --git a/src/plugins/clangtools/virtualfilesystemoverlay.cpp b/src/plugins/clangtools/virtualfilesystemoverlay.cpp
new file mode 100644
index 0000000000..a0f9f2725c
--- /dev/null
+++ b/src/plugins/clangtools/virtualfilesystemoverlay.cpp
@@ -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
diff --git a/src/plugins/clangtools/virtualfilesystemoverlay.h b/src/plugins/clangtools/virtualfilesystemoverlay.h
new file mode 100644
index 0000000000..f6077f12a5
--- /dev/null
+++ b/src/plugins/clangtools/virtualfilesystemoverlay.h
@@ -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