/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include 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(config, env); }; } if (config.isClazyEnabled()) { m_runnerCreators << [this, env, config]() { return createRunner(config, env); }; } } } } } else { deleteLater(); } runNext(); } QPair getClangIncludeDirAndVersion(ClangToolRunner *runner) { static QMap> 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; if (clangIncludeDir.isEmpty() || clangVersion.isEmpty() || (m_document->isModified() && !m_currentRunner->supportsVFSOverlay())) { runNext(); } else { 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 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