forked from qt-creator/qt-creator
ClangTools: Split generic part from static analyzer tool
To reuse it for other clang-based tools. Change-Id: I6c0d8e9eee543fa08faf3bf93c9fac33e43c6820 Reviewed-by: Nikolai Kosjar <nikolai.kosjar@qt.io> Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
429
src/plugins/clangtools/clangtoolruncontrol.cpp
Normal file
429
src/plugins/clangtools/clangtoolruncontrol.cpp
Normal file
@@ -0,0 +1,429 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "clangtoolruncontrol.h"
|
||||
|
||||
#include "clangtool.h"
|
||||
#include "clangtoolslogfilereader.h"
|
||||
#include "clangtoolssettings.h"
|
||||
#include "clangtoolsutils.h"
|
||||
#include "clangtoolrunner.h"
|
||||
|
||||
#include <debugger/analyzer/analyzerconstants.h>
|
||||
|
||||
#include <clangcodemodel/clangutils.h>
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/progressmanager/futureprogress.h>
|
||||
#include <coreplugin/progressmanager/progressmanager.h>
|
||||
|
||||
#include <cpptools/compileroptionsbuilder.h>
|
||||
#include <cpptools/cppmodelmanager.h>
|
||||
#include <cpptools/cppprojectfile.h>
|
||||
#include <cpptools/cpptoolsreuse.h>
|
||||
#include <cpptools/projectinfo.h>
|
||||
|
||||
#include <projectexplorer/abi.h>
|
||||
#include <projectexplorer/buildconfiguration.h>
|
||||
#include <projectexplorer/buildmanager.h>
|
||||
#include <projectexplorer/kitinformation.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/projectexplorericons.h>
|
||||
#include <projectexplorer/runconfiguration.h>
|
||||
#include <projectexplorer/target.h>
|
||||
#include <projectexplorer/taskhub.h>
|
||||
#include <projectexplorer/toolchain.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/checkablemessagebox.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
#include <utils/temporarydirectory.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
|
||||
#include <QAction>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
using namespace CppTools;
|
||||
using namespace ProjectExplorer;
|
||||
using namespace Utils;
|
||||
|
||||
static Q_LOGGING_CATEGORY(LOG, "qtc.clangtools.runcontrol")
|
||||
|
||||
static QStringList splitArgs(QString &argsString)
|
||||
{
|
||||
QStringList result;
|
||||
Utils::QtcProcess::ArgIterator it(&argsString);
|
||||
while (it.next())
|
||||
result.append(it.value());
|
||||
return result;
|
||||
}
|
||||
|
||||
template<size_t Size>
|
||||
static QStringList extraOptions(const char(&environment)[Size])
|
||||
{
|
||||
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 {
|
||||
|
||||
static AnalyzeUnits unitsToAnalyzeFromProjectParts(const QVector<ProjectPart::Ptr> projectParts,
|
||||
const QString &clangVersion,
|
||||
const QString &clangResourceDirectory)
|
||||
{
|
||||
qCDebug(LOG) << "Taking arguments for analyzing from ProjectParts.";
|
||||
|
||||
AnalyzeUnits unitsToAnalyze;
|
||||
|
||||
foreach (const ProjectPart::Ptr &projectPart, projectParts) {
|
||||
if (!projectPart->selectedForBuilding || !projectPart.data())
|
||||
continue;
|
||||
|
||||
foreach (const ProjectFile &file, projectPart->files) {
|
||||
if (file.path == CppModelManager::configurationFileName())
|
||||
continue;
|
||||
QTC_CHECK(file.kind != ProjectFile::Unclassified);
|
||||
QTC_CHECK(file.kind != ProjectFile::Unsupported);
|
||||
if (ProjectFile::isSource(file.kind)) {
|
||||
const CompilerOptionsBuilder::PchUsage pchUsage = CppTools::getPchUsage();
|
||||
CompilerOptionsBuilder optionsBuilder(*projectPart, clangVersion,
|
||||
clangResourceDirectory);
|
||||
QStringList arguments = extraClangToolsPrependOptions();
|
||||
arguments.append(optionsBuilder.build(file.kind, pchUsage));
|
||||
arguments.append(extraClangToolsAppendOptions());
|
||||
unitsToAnalyze << AnalyzeUnit(file.path, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unitsToAnalyze;
|
||||
}
|
||||
|
||||
static QString clangResourceDir(const QString &clangExecutable, const QString &clangVersion)
|
||||
{
|
||||
QDir llvmDir = QFileInfo(clangExecutable).dir();
|
||||
llvmDir.cdUp();
|
||||
return llvmDir.absolutePath() + clangIncludePath(clangVersion);
|
||||
}
|
||||
|
||||
AnalyzeUnits ClangToolRunControl::sortedUnitsToAnalyze(const QString &clangVersion)
|
||||
{
|
||||
QTC_ASSERT(m_projectInfo.isValid(), return AnalyzeUnits());
|
||||
|
||||
const QString clangResourceDirectory = clangResourceDir(m_clangExecutable, clangVersion);
|
||||
AnalyzeUnits units = unitsToAnalyzeFromProjectParts(m_projectInfo.projectParts(), clangVersion,
|
||||
clangResourceDirectory);
|
||||
|
||||
Utils::sort(units, &AnalyzeUnit::file);
|
||||
return units;
|
||||
}
|
||||
|
||||
static QDebug operator<<(QDebug debug, const Utils::Environment &environment)
|
||||
{
|
||||
foreach (const QString &entry, environment.toStringList())
|
||||
debug << "\n " << entry;
|
||||
return debug;
|
||||
}
|
||||
|
||||
static QDebug operator<<(QDebug debug, const AnalyzeUnits &analyzeUnits)
|
||||
{
|
||||
foreach (const AnalyzeUnit &unit, analyzeUnits)
|
||||
debug << "\n " << unit.file;
|
||||
return debug;
|
||||
}
|
||||
|
||||
ClangToolRunControl::ClangToolRunControl(RunControl *runControl, Target *target)
|
||||
: RunWorker(runControl), m_target(target)
|
||||
{
|
||||
}
|
||||
|
||||
void ClangToolRunControl::init()
|
||||
{
|
||||
setSupportsReRunning(false);
|
||||
m_projectInfoBeforeBuild = CppTools::CppModelManager::instance()->projectInfo(
|
||||
m_target->project());
|
||||
|
||||
BuildConfiguration *buildConfiguration = m_target->activeBuildConfiguration();
|
||||
QTC_ASSERT(buildConfiguration, return);
|
||||
m_environment = buildConfiguration->environment();
|
||||
|
||||
ToolChain *toolChain = ToolChainKitInformation::toolChain(m_target->kit(),
|
||||
ProjectExplorer::Constants::CXX_LANGUAGE_ID);
|
||||
QTC_ASSERT(toolChain, return);
|
||||
m_targetTriple = toolChain->originalTargetTriple();
|
||||
m_toolChainType = toolChain->typeId();
|
||||
}
|
||||
|
||||
void ClangToolRunControl::start()
|
||||
{
|
||||
m_success = m_projectBuilder ? m_projectBuilder->success() : true;
|
||||
if (!m_success) {
|
||||
reportFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
m_projectInfo = CppTools::CppModelManager::instance()->projectInfo(m_target->project());
|
||||
|
||||
const QString toolName = tool()->name();
|
||||
// Some projects provides CompilerCallData once a build is finished,
|
||||
if (m_projectInfo.configurationOrFilesChanged(m_projectInfoBeforeBuild)) {
|
||||
// If it's more than a release/debug build configuration change, e.g.
|
||||
// a version control checkout, files might be not valid C++ anymore
|
||||
// or even gone, so better stop here.
|
||||
reportFailure(tr("The project configuration changed since the start of "
|
||||
"the %1. Please re-run with current configuration.").arg(toolName));
|
||||
return;
|
||||
}
|
||||
|
||||
const Utils::FileName projectFile = m_projectInfo.project()->projectFilePath();
|
||||
appendMessage(tr("Running %1 on %2").arg(toolName).arg(projectFile.toUserOutput()),
|
||||
Utils::NormalMessageFormat);
|
||||
|
||||
// Check clang executable
|
||||
bool isValidClangExecutable;
|
||||
const QString executable = clangExecutableFromSettings(&isValidClangExecutable);
|
||||
if (!isValidClangExecutable) {
|
||||
const QString errorMessage = toolName
|
||||
+ tr(": Invalid executable \"%1\", stop.").arg(executable);
|
||||
appendMessage(errorMessage, Utils::ErrorMessageFormat);
|
||||
TaskHub::addTask(Task::Error, errorMessage, Debugger::Constants::ANALYZERTASK_ID);
|
||||
TaskHub::requestPopup();
|
||||
reportFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check clang version
|
||||
const ClangExecutableVersion version = clangExecutableVersion(executable);
|
||||
if (!version.isValid()) {
|
||||
const QString warningMessage
|
||||
= toolName + tr(": Running with possibly unsupported version, "
|
||||
"could not determine version from executable \"%1\".")
|
||||
.arg(executable);
|
||||
appendMessage(warningMessage, Utils::StdErrFormat);
|
||||
TaskHub::addTask(Task::Warning, warningMessage, Debugger::Constants::ANALYZERTASK_ID);
|
||||
TaskHub::requestPopup();
|
||||
} else if (!version.isSupportedVersion()) {
|
||||
const QString warningMessage
|
||||
= toolName + tr(": Running with unsupported version %1, "
|
||||
"supported version is %2.")
|
||||
.arg(version.toString())
|
||||
.arg(ClangExecutableVersion::supportedVersionAsString());
|
||||
appendMessage(warningMessage, Utils::StdErrFormat);
|
||||
TaskHub::addTask(Task::Warning, warningMessage, Debugger::Constants::ANALYZERTASK_ID);
|
||||
TaskHub::requestPopup();
|
||||
}
|
||||
|
||||
m_clangExecutable = executable;
|
||||
|
||||
// Create log dir
|
||||
Utils::TemporaryDirectory temporaryDir("qtc-clangtools-XXXXXX");
|
||||
temporaryDir.setAutoRemove(false);
|
||||
if (!temporaryDir.isValid()) {
|
||||
const QString errorMessage
|
||||
= toolName + tr(": Failed to create temporary dir, stop.");
|
||||
appendMessage(errorMessage, Utils::ErrorMessageFormat);
|
||||
TaskHub::addTask(Task::Error, errorMessage, Debugger::Constants::ANALYZERTASK_ID);
|
||||
TaskHub::requestPopup();
|
||||
reportFailure(errorMessage);
|
||||
return;
|
||||
}
|
||||
m_clangLogFileDir = temporaryDir.path();
|
||||
|
||||
// Collect files
|
||||
const AnalyzeUnits unitsToProcess = sortedUnitsToAnalyze(version.toString());
|
||||
qCDebug(LOG) << "Files to process:" << unitsToProcess;
|
||||
m_unitsToProcess = unitsToProcess;
|
||||
m_initialFilesToProcessSize = m_unitsToProcess.count();
|
||||
m_filesAnalyzed = 0;
|
||||
m_filesNotAnalyzed = 0;
|
||||
|
||||
// Set up progress information
|
||||
using namespace Core;
|
||||
m_progress = QFutureInterface<void>();
|
||||
FutureProgress *futureProgress
|
||||
= ProgressManager::addTask(m_progress.future(), tr("Analyzing"),
|
||||
toolName.toStdString().c_str());
|
||||
futureProgress->setKeepOnFinish(FutureProgress::HideOnFinish);
|
||||
connect(futureProgress, &FutureProgress::canceled,
|
||||
this, &ClangToolRunControl::onProgressCanceled);
|
||||
m_progress.setProgressRange(0, m_initialFilesToProcessSize);
|
||||
m_progress.reportStarted();
|
||||
|
||||
// Start process(es)
|
||||
qCDebug(LOG) << "Environment:" << m_environment;
|
||||
m_runners.clear();
|
||||
const int parallelRuns = ClangToolsSettings::instance()->simultaneousProcesses();
|
||||
QTC_ASSERT(parallelRuns >= 1, reportFailure(); return);
|
||||
m_success = true;
|
||||
|
||||
if (m_unitsToProcess.isEmpty()) {
|
||||
finalize();
|
||||
return;
|
||||
}
|
||||
|
||||
reportStarted();
|
||||
|
||||
while (m_runners.size() < parallelRuns && !m_unitsToProcess.isEmpty())
|
||||
analyzeNextFile();
|
||||
}
|
||||
|
||||
void ClangToolRunControl::stop()
|
||||
{
|
||||
QSetIterator<ClangToolRunner *> i(m_runners);
|
||||
while (i.hasNext()) {
|
||||
ClangToolRunner *runner = i.next();
|
||||
QObject::disconnect(runner, 0, this, 0);
|
||||
delete runner;
|
||||
}
|
||||
m_runners.clear();
|
||||
m_unitsToProcess.clear();
|
||||
m_progress.reportFinished();
|
||||
|
||||
reportStopped();
|
||||
}
|
||||
|
||||
void ClangToolRunControl::analyzeNextFile()
|
||||
{
|
||||
if (m_progress.isFinished())
|
||||
return; // The previous call already reported that we are finished.
|
||||
|
||||
if (m_unitsToProcess.isEmpty()) {
|
||||
if (m_runners.isEmpty())
|
||||
finalize();
|
||||
return;
|
||||
}
|
||||
|
||||
const AnalyzeUnit unit = m_unitsToProcess.takeFirst();
|
||||
qCDebug(LOG) << "analyzeNextFile:" << unit.file;
|
||||
|
||||
ClangToolRunner *runner = createRunner();
|
||||
m_runners.insert(runner);
|
||||
QTC_ASSERT(runner->run(unit.file, unit.arguments), return);
|
||||
|
||||
appendMessage(tr("Analyzing \"%1\".").arg(
|
||||
Utils::FileName::fromString(unit.file).toUserOutput()),
|
||||
Utils::StdOutFormat);
|
||||
}
|
||||
|
||||
void ClangToolRunControl::onRunnerFinishedWithSuccess(const QString &logFilePath)
|
||||
{
|
||||
qCDebug(LOG) << "onRunnerFinishedWithSuccess:" << logFilePath;
|
||||
|
||||
QString errorMessage;
|
||||
const QList<Diagnostic> diagnostics = tool()->read(logFilePath, &errorMessage);
|
||||
if (!errorMessage.isEmpty()) {
|
||||
qCDebug(LOG) << "onRunnerFinishedWithSuccess: Error reading log file:" << errorMessage;
|
||||
const QString filePath = qobject_cast<ClangToolRunner *>(sender())->filePath();
|
||||
appendMessage(tr("Failed to analyze \"%1\": %2").arg(filePath, errorMessage),
|
||||
Utils::StdErrFormat);
|
||||
} else {
|
||||
++m_filesAnalyzed;
|
||||
if (!diagnostics.isEmpty())
|
||||
tool()->onNewDiagnosticsAvailable(diagnostics);
|
||||
}
|
||||
|
||||
handleFinished();
|
||||
}
|
||||
|
||||
void ClangToolRunControl::onRunnerFinishedWithFailure(const QString &errorMessage,
|
||||
const QString &errorDetails)
|
||||
{
|
||||
qCDebug(LOG).noquote() << "onRunnerFinishedWithFailure:"
|
||||
<< errorMessage << '\n' << errorDetails;
|
||||
|
||||
++m_filesNotAnalyzed;
|
||||
m_success = false;
|
||||
const QString filePath = qobject_cast<ClangToolRunner *>(sender())->filePath();
|
||||
appendMessage(tr("Failed to analyze \"%1\": %2").arg(filePath, errorMessage),
|
||||
Utils::StdErrFormat);
|
||||
appendMessage(errorDetails, Utils::StdErrFormat);
|
||||
TaskHub::addTask(Task::Warning, errorMessage, Debugger::Constants::ANALYZERTASK_ID);
|
||||
TaskHub::addTask(Task::Warning, errorDetails, Debugger::Constants::ANALYZERTASK_ID);
|
||||
handleFinished();
|
||||
}
|
||||
|
||||
void ClangToolRunControl::handleFinished()
|
||||
{
|
||||
m_runners.remove(qobject_cast<ClangToolRunner *>(sender()));
|
||||
updateProgressValue();
|
||||
sender()->deleteLater();
|
||||
analyzeNextFile();
|
||||
}
|
||||
|
||||
void ClangToolRunControl::onProgressCanceled()
|
||||
{
|
||||
m_progress.reportCanceled();
|
||||
runControl()->initiateStop();
|
||||
}
|
||||
|
||||
void ClangToolRunControl::updateProgressValue()
|
||||
{
|
||||
m_progress.setProgressValue(m_initialFilesToProcessSize - m_unitsToProcess.size());
|
||||
}
|
||||
|
||||
void ClangToolRunControl::finalize()
|
||||
{
|
||||
const QString toolName = tool()->name();
|
||||
appendMessage(toolName + tr(" finished: "
|
||||
"Processed %1 files successfully, %2 failed.")
|
||||
.arg(m_filesAnalyzed).arg(m_filesNotAnalyzed),
|
||||
Utils::NormalMessageFormat);
|
||||
|
||||
if (m_filesNotAnalyzed != 0) {
|
||||
QString msg = toolName + tr(": Not all files could be analyzed.");
|
||||
TaskHub::addTask(Task::Error, msg, Debugger::Constants::ANALYZERTASK_ID);
|
||||
TaskHub::requestPopup();
|
||||
}
|
||||
|
||||
m_progress.reportFinished();
|
||||
runControl()->initiateStop();
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace ClangTools
|
||||
Reference in New Issue
Block a user