diff --git a/src/job.cpp b/src/job.cpp index 3c3a11f825..b44884aabf 100644 --- a/src/job.cpp +++ b/src/job.cpp @@ -1,208 +1,258 @@ /* * This file is part of KDevelop * * Copyright 2016 Carlos Nihelton + * Copyright 2018 Friedrich W. H. Kossebau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "job.h" // plugin #include "parsers/clangtidyparser.h" // KF #include #include // Qt #include #include #include namespace ClangTidy { -QString inlineYaml(const Job::Parameters& params) +// uses ' for quoting +QString inlineYaml(const Job::Parameters& parameters) { QString result; - result.append(QLatin1String("{Checks: '") + params.enabledChecks + QLatin1Char('\'')); + result.append(QLatin1String("{Checks: '") + parameters.enabledChecks + QLatin1Char('\'')); - if (!params.headerFilter.isEmpty()) { - result.append(QLatin1String(", HeaderFilterRegex: '") + params.headerFilter + QLatin1Char('\'')); + if (!parameters.headerFilter.isEmpty()) { + // TODO: the regex might need escpaing for potential quotes of all kinds + result.append(QLatin1String(", HeaderFilterRegex: '") + parameters.headerFilter + QLatin1Char('\'')); } result.append(QLatin1Char('}')); return result; } +// uses " for quoting +QStringList commandLineArgs(const Job::Parameters& parameters) +{ + QStringList args{ + parameters.executablePath, + QLatin1String("-p=\"") + parameters.buildDir + QLatin1Char('\"'), + // don't add statistics we are not interested in to parse anyway + QStringLiteral("-quiet"), + }; + if (!parameters.additionalParameters.isEmpty()) { + args << parameters.additionalParameters; + } + if (parameters.checkSystemHeaders) { + args << QStringLiteral("--system-headers"); + } + + if (!parameters.useConfigFile) { + args << QLatin1String("--config=\"") + inlineYaml(parameters) + QLatin1Char('\"'); + } + + return args; +} + + Job::Job(const Parameters& params, QObject* parent) : KDevelop::OutputExecuteJob(parent) , m_parameters(params) { setJobName(i18n("Clang-Tidy Analysis")); setCapabilities(KJob::Killable); setStandardToolView(KDevelop::IOutputView::TestView); setBehaviours(KDevelop::IOutputView::AutoScroll); setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStdout | KDevelop::OutputExecuteJob::JobProperty::DisplayStderr | KDevelop::OutputExecuteJob::JobProperty::PostProcessOutput); - *this << params.executablePath; + // TODO: check success of creation + generateMakefile(); - *this << QLatin1String("-p=") + params.buildDir; - *this << params.filePath; + *this << QStringList{ + QStringLiteral("make"), + QStringLiteral("-f"), + m_makeFilePath, + }; + + qCDebug(KDEV_CLANGTIDY) << "checking files" << params.filePaths; +} - // don't add statistics we are not interested in to parse anyway - *this << QStringLiteral("-quiet"); +Job::~Job() +{ + doKill(); - if (!params.additionalParameters.isEmpty()) { - *this << params.additionalParameters; + if (!m_makeFilePath.isEmpty()) { + QFile::remove(m_makeFilePath); } - if (params.checkSystemHeaders) { - *this << QStringLiteral("--system-headers"); +} + +void Job::generateMakefile() +{ + m_makeFilePath = m_parameters.buildDir + QLatin1String("/kdevclangtidy.makefile"); + + QFile makefile(m_makeFilePath); + makefile.open(QIODevice::WriteOnly); + + QTextStream scriptStream(&makefile); + + scriptStream << QStringLiteral("SOURCES ="); + for (const auto& source : m_parameters.filePaths) { + // TODO: how to escape " in a filename, for those people who like to go extreme? + scriptStream << QLatin1String(" \\\n\t\"") + source + QLatin1Char('\"'); } + scriptStream << QLatin1Char('\n'); - if (!params.useConfigFile) { - *this << QLatin1String("--config=") + inlineYaml(params); + scriptStream << QStringLiteral("COMMAND ="); + const auto commandLine = commandLineArgs(m_parameters); + for (const auto& commandPart : commandLine) { + scriptStream << QLatin1Char(' ') << commandPart; } + scriptStream << QLatin1Char('\n'); - qCDebug(KDEV_CLANGTIDY) << "checking path" << params.filePath; -} + scriptStream << QStringLiteral(".PHONY: all $(SOURCES)\n"); + scriptStream << QStringLiteral("all: $(SOURCES)\n"); + scriptStream << QStringLiteral("$(SOURCES):\n"); -Job::~Job() -{ - doKill(); + scriptStream << QStringLiteral("\t@echo 'Clang-Tidy check started for $@'\n"); + scriptStream << QStringLiteral("\t$(COMMAND) $@\n"); + scriptStream << QStringLiteral("\t@echo 'Clang-Tidy check finished for $@'\n"); + + makefile.close(); } void Job::processStdoutLines(const QStringList& lines) { m_standardOutput << lines; } void Job::processStderrLines(const QStringList& lines) { static const auto xmlStartRegex = QRegularExpression(QStringLiteral("\\s*<")); for (const QString& line : lines) { // unfortunately sometime clangtidy send non-XML messages to stderr. // For example, if we pass '-I /missing_include_dir' to the argument list, // then stderr output will contains such line (tested on clangtidy 1.72): // // (information) Couldn't find path given by -I '/missing_include_dir' // // Therefore we must 'move' such messages to m_standardOutput. if (line.indexOf(xmlStartRegex) != -1) { // the line contains XML m_xmlOutput << line; } else { m_standardOutput << line; } } } void Job::postProcessStdout(const QStringList& lines) { processStdoutLines(lines); KDevelop::OutputExecuteJob::postProcessStdout(lines); } void Job::postProcessStderr(const QStringList& lines) { processStderrLines(lines); KDevelop::OutputExecuteJob::postProcessStderr(lines); } void Job::start() { m_standardOutput.clear(); m_xmlOutput.clear(); qCDebug(KDEV_CLANGTIDY) << "executing:" << commandLine().join(QLatin1Char(' ')); KDevelop::OutputExecuteJob::start(); } QVector Job::problems() const { return m_problems; } void Job::childProcessError(QProcess::ProcessError processError) { QString message; switch (processError) { case QProcess::FailedToStart: { - const auto binaryPath = commandLine().value(0); - if (binaryPath.isEmpty()) { - message = i18n("Failed to find clang-tidy binary."); - } else { - message = i18n("Failed to start clang-tidy from %1.", binaryPath); - } + message = i18n("Failed to start Clang-Tidy process."); break; } case QProcess::Crashed: message = i18n("Clang-tidy crashed."); break; case QProcess::Timedout: message = i18n("Clang-tidy process timed out."); break; case QProcess::WriteError: message = i18n("Write to Clang-tidy process failed."); break; case QProcess::ReadError: message = i18n("Read from Clang-tidy process failed."); break; case QProcess::UnknownError: // current clangtidy errors will be displayed in the output view // don't notify the user break; } if (!message.isEmpty()) { KMessageBox::error(qApp->activeWindow(), message, i18n("Clang-tidy Error")); } KDevelop::OutputExecuteJob::childProcessError(processError); } void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(KDEV_CLANGTIDY) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus; if (exitCode != 0) { qCDebug(KDEV_CLANGTIDY) << "clang-tidy failed, standard output: "; qCDebug(KDEV_CLANGTIDY) << m_standardOutput.join(QLatin1Char('\n')); qCDebug(KDEV_CLANGTIDY) << "clang-tidy failed, XML output: "; qCDebug(KDEV_CLANGTIDY) << m_xmlOutput.join(QLatin1Char('\n')); } else { ClangTidyParser parser; parser.addData(m_standardOutput); parser.parse(); m_problems = parser.problems(); } KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus); } } // namespace ClangTidy diff --git a/src/job.h b/src/job.h index 88b220290c..612d2fb745 100644 --- a/src/job.h +++ b/src/job.h @@ -1,86 +1,90 @@ /* * This file is part of KDevelop * * Copyright 2016 Carlos Nihelton * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CLANGTIDY_JOB_H #define CLANGTIDY_JOB_H // plugin #include // KDevPlatform #include #include namespace ClangTidy { /** * \class * \brief specializes a KJob for running clang-tidy. */ class Job : public KDevelop::OutputExecuteJob { Q_OBJECT public: /** * \class * \brief command line parameters. */ struct Parameters { QString projectRootDir; QString executablePath; - QString filePath; + QStringList filePaths; QString buildDir; QString additionalParameters; QString enabledChecks; bool useConfigFile; QString headerFilter; bool checkSystemHeaders; }; explicit Job(const Parameters& params, QObject* parent = nullptr); ~Job() override; public: // KJob API void start() override; public: QVector problems() const; protected Q_SLOTS: void postProcessStdout(const QStringList& lines) override; void postProcessStderr(const QStringList& lines) override; void childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) override; void childProcessError(QProcess::ProcessError processError) override; protected: void processStdoutLines(const QStringList& lines); void processStderrLines(const QStringList& lines); +private: + void generateMakefile(); + protected: QStringList m_standardOutput; QStringList m_xmlOutput; const Job::Parameters m_parameters; + QString m_makeFilePath; QVector m_problems; }; } #endif diff --git a/src/kdevclangtidy.rc b/src/kdevclangtidy.rc index 548fe68af5..801994d737 100644 --- a/src/kdevclangtidy.rc +++ b/src/kdevclangtidy.rc @@ -1,13 +1,13 @@ - + diff --git a/src/plugin.cpp b/src/plugin.cpp index 99ebae9e1f..8745f068df 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -1,314 +1,321 @@ /* * This file is part of KDevelop * * Copyright 2016 Carlos Nihelton + * Copyright 2018 Friedrich W. H. Kossebau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "plugin.h" // plugin #include #include #include "config/clangtidypreferences.h" #include "config/clangtidyprojectconfigpage.h" #include "job.h" +#include "utils.h" // KDevPlatform #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KF #include #include #include #include // Qt #include #include #include #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(ClangTidyFactory, "kdevclangtidy.json", registerPlugin();) namespace ClangTidy { namespace Strings { QString modelId() { return QStringLiteral("ClangTidy"); } } static bool isSupportedMimeType(const QMimeType& mimeType) { const QString mime = mimeType.name(); return (mime == QLatin1String("text/x-c++src") || mime == QLatin1String("text/x-csrc")); } Plugin::Plugin(QObject* parent, const QVariantList& /*unused*/) : IPlugin(QStringLiteral("kdevclangtidy"), parent) , m_model(new KDevelop::ProblemModel(parent)) { setXMLFile(QStringLiteral("kdevclangtidy.rc")); m_checkFileAction = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("Analyze Current File with Clang-Tidy"), this); connect(m_checkFileAction, &QAction::triggered, this, &Plugin::runClangTidyFile); actionCollection()->addAction(QStringLiteral("clangtidy_file"), m_checkFileAction); - /* TODO: Uncomment this only when discover a safe way to run clang-tidy on - the whole project. - // QAction* act_check_all_files; - // act_check_all_files = actionCollection()->addAction ( "clangtidy_all", - this, SLOT ( runClangTidyAll() ) ); - // act_check_all_files->setText(i18n("Analyze Current Project with Clang-Tidy)")); - // act_check_all_files->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok"))); - */ + m_checkProjectAction = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), + i18n("Analyze Current Project with Clang-Tidy"), this); + connect(m_checkProjectAction, &QAction::triggered, this, &Plugin::runClangTidyAll); + actionCollection()->addAction(QStringLiteral("clangtidy_project"), m_checkProjectAction); ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->addModel(Strings::modelId(), i18n("Clang-Tidy"), m_model.data()); auto clangTidyPath = KDevelop::Path(ClangTidySettings::clangtidyPath()).toLocalFile(); // TODO: not only collect on plugin loading, but also on every change in the settings // TODO: should also check version on every job start to see if there was an update // behind our back while kdevelop running // TODO: collect in async job m_checkSet.setClangTidyPath(clangTidyPath); connect(core()->documentController(), &KDevelop::IDocumentController::documentClosed, this, &Plugin::updateActions); connect(core()->documentController(), &KDevelop::IDocumentController::documentActivated, this, &Plugin::updateActions); connect(core()->projectController(), &KDevelop::IProjectController::projectOpened, this, &Plugin::updateActions); updateActions(); } Plugin::~Plugin() = default; void Plugin::unload() { ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->removeModel(Strings::modelId()); } + void Plugin::updateActions() { m_checkFileAction->setEnabled(false); + m_checkProjectAction->setEnabled(false); if (isRunning()) { return; } KDevelop::IDocument* activeDocument = core()->documentController()->activeDocument(); if (!activeDocument) { return; } - if (!isSupportedMimeType(activeDocument->mimeType())) { - return; - } - auto currentProject = core()->projectController()->findProjectForUrl(activeDocument->url()); if (!currentProject) { return; } - m_checkFileAction->setEnabled(true); + if (isSupportedMimeType(activeDocument->mimeType())) { + m_checkFileAction->setEnabled(true); + } + + m_checkProjectAction->setEnabled(true); } void Plugin::runClangTidy(bool allFiles) { auto doc = core()->documentController()->activeDocument(); if (doc == nullptr) { QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"), i18n("No suitable active file, unable to deduce project.")); return; } runClangTidy(doc->url(), allFiles); } void Plugin::runClangTidy(const QUrl& url, bool allFiles) { KDevelop::IProject* project = core()->projectController()->findProjectForUrl(url); if (project == nullptr) { QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"), i18n("Active file isn't in a project")); return; } + const auto buildDir = project->buildSystemManager()->buildDirectory(project->projectItem()); + + QString error; + const auto filePaths = Utils::filesFromCompilationDatabase(buildDir, url, allFiles, error); + ClangTidyProjectSettings projectSettings; projectSettings.setSharedConfig(project->projectConfiguration()); projectSettings.load(); Job::Parameters params; params.projectRootDir = project->path().toLocalFile(); auto clangTidyPath = KDevelop::Path(ClangTidySettings::clangtidyPath()).toLocalFile(); params.executablePath = clangTidyPath; - if (allFiles) { - params.filePath = project->path().toUrl().toLocalFile(); - } else { - params.filePath = url.toLocalFile(); - } - if (const auto buildSystem = project->buildSystemManager()) { - params.buildDir = buildSystem->buildDirectory(project->projectItem()).toLocalFile(); - } + params.filePaths = filePaths; + params.buildDir = buildDir.toLocalFile(); + params.additionalParameters = projectSettings.additionalParameters(); const auto enabledChecks = projectSettings.enabledChecks(); if (!enabledChecks.isEmpty()) { params.enabledChecks = enabledChecks; } else { // make sure the defaults are up-to-date TODO: make async m_checkSet.setClangTidyPath(clangTidyPath); params.enabledChecks = m_checkSet.defaults().join(QLatin1Char(',')); } params.useConfigFile = projectSettings.useConfigFile(); params.headerFilter = projectSettings.headerFilter(); params.checkSystemHeaders = projectSettings.checkSystemHeaders(); auto job = new ClangTidy::Job(params, this); connect(job, &KJob::finished, this, &Plugin::result); core()->runController()->registerJob(job); m_runningJob = job; updateActions(); } bool Plugin::isRunning() const { return !m_runningJob.isNull(); } void Plugin::runClangTidyFile() { bool allFiles = false; runClangTidy(allFiles); } void Plugin::runClangTidyAll() { bool allFiles = true; runClangTidy(allFiles); } void Plugin::result(KJob* job) { auto* aj = qobject_cast(job); if (aj == nullptr) { return; } if (aj->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded || aj->status() == KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { m_model->setProblems(aj->problems()); ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->showModel(Strings::modelId()); } else { core()->uiController()->findToolView(i18ndc("kdevstandardoutputview", "@title:window", "Test"), nullptr, KDevelop::IUiController::FindFlags::Raise); } updateActions(); } ContextMenuExtension Plugin::contextMenuExtension(Context* context, QWidget* parent) { ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context, parent); if (context->hasType(KDevelop::Context::EditorContext) && !isRunning()) { IDocument* doc = core()->documentController()->activeDocument(); if (isSupportedMimeType(doc->mimeType())) { auto action = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("Clang-Tidy"), parent); connect(action, &QAction::triggered, this, &Plugin::runClangTidyFile); extension.addAction(KDevelop::ContextMenuExtension::AnalyzeFileGroup, action); } + auto action = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("Clang-Tidy"), parent); + connect(action, &QAction::triggered, this, &Plugin::runClangTidyAll); + extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, action); } if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) { auto pContext = dynamic_cast(context); const auto items = pContext->items(); if (items.size() != 1) { return extension; } - auto item = items.first(); - - if (item->type() != KDevelop::ProjectBaseItem::File) { + const auto item = items.first(); + const auto itemType = item->type(); + if ((itemType != KDevelop::ProjectBaseItem::File) && + (itemType != KDevelop::ProjectBaseItem::Folder) && + (itemType != KDevelop::ProjectBaseItem::BuildFolder)) { return extension; } - const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(item->path().toUrl()); - if (!isSupportedMimeType(mimetype)) { - return extension; + if (itemType == KDevelop::ProjectBaseItem::File) { + const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(item->path().toUrl()); + if (!isSupportedMimeType(mimetype)) { + return extension; + } } auto action = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("Clang-Tidy"), parent); connect(action, &QAction::triggered, this, [this, item]() { runClangTidy(item->path().toUrl()); }); extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, action); } return extension; } KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number != 0) { return nullptr; } // ensure checkset is up-to-date TODO: async auto clangTidyPath = KDevelop::Path(ClangTidySettings::clangtidyPath()).toLocalFile(); m_checkSet.setClangTidyPath(clangTidyPath); return new ProjectConfigPage(this, options.project, &m_checkSet, parent); } KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent) { if (number != 0) { return nullptr; } return new ClangTidyPreferences(this, parent); } } // namespace ClangTidy #include "plugin.moc" diff --git a/src/plugin.h b/src/plugin.h index d4cd28acda..6203aff7aa 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -1,102 +1,103 @@ /* * This file is part of KDevelop * * Copyright 2016 Carlos Nihelton * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CLANGTIDY_PLUGIN_H #define CLANGTIDY_PLUGIN_H // plugin #include "checkset.h" #include // KDevPlatform #include // Qt #include #include class KJob; class QAction; namespace KDevelop { class ProblemModel; } namespace ClangTidy { /** * \class * \brief implements the support for clang-tidy inside KDevelop by using the IPlugin interface. */ class Plugin : public KDevelop::IPlugin { Q_OBJECT public: explicit Plugin(QObject* parent, const QVariantList& = QVariantList()); ~Plugin(); public: // KDevelop::IPlugin API void unload() override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; int configPages() const override { return 1; } /** * \function * \return the session configuration page for clang-tidy plugin. */ KDevelop::ConfigPage* configPage(int number, QWidget* parent) override; int perProjectConfigPages() const override { return 1; } /** * \function * \return the clang-tidy configuration page for the current project. */ KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; public: /** *\function *\returns all available checks, obtained from clang-tidy program, ran with with "--checks=* --list-checks" * parameters. */ QStringList allAvailableChecks() { return m_checkSet.all(); } private Q_SLOTS: void runClangTidy(bool allFiles = false); void runClangTidy(const QUrl& url, bool allFiles = false); void runClangTidyFile(); void runClangTidyAll(); void result(KJob* job); void updateActions(); private: bool isRunning() const; private: QPointer m_runningJob; QAction* m_checkFileAction; + QAction* m_checkProjectAction; QScopedPointer m_model; CheckSet m_checkSet; }; } // namespace ClangTidy #endif // CLANGTIDY_PLUGIN_H diff --git a/src/utils.cpp b/src/utils.cpp index 8241406205..541beb0581 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,41 +1,116 @@ /* * This file is part of KDevelop * * Copyright 2018 Friedrich W. H. Kossebau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "utils.h" +// plugin +#include +// KDevPlatform +#include +// KF +#include // Qt #include +#include +#include +#include +#include namespace ClangTidy { namespace Utils { QString findExecutable(const QString& fallbackExecutablePath) { const QString executablePath = QStandardPaths::findExecutable(fallbackExecutablePath); return executablePath.isEmpty() ? fallbackExecutablePath : executablePath; } +QStringList filesFromCompilationDatabase(const KDevelop::Path& buildPath, + const QUrl& urlToCheck, bool allFiles, + QString& error) +{ + QStringList result; + + const auto commandsFilePath = KDevelop::Path(buildPath, QStringLiteral("compile_commands.json")).toLocalFile(); + + if (!QFile::exists(commandsFilePath)) { + error = i18n("Compilation database file not found: '%1'", commandsFilePath); + return result; + } + + const auto pathToCheck = urlToCheck.toLocalFile(); + if (pathToCheck.isEmpty()) { + return result; + } + + QFile commandsFile(commandsFilePath); + if (!commandsFile.open(QFile::ReadOnly | QFile::Text)) { + error = i18n("Could not open compilation database file for reading: '%1'", commandsFilePath); + return result; + } + + QJsonParseError jsonError; + const auto commandsDocument = QJsonDocument::fromJson(commandsFile.readAll(), &jsonError); + + if (jsonError.error) { + error = i18n("JSON error during parsing compilation database file '%1': %2", commandsFilePath, jsonError.errorString()); + return result; + } + + if (!commandsDocument.isArray()) { + error = i18n("JSON error during parsing compilation database file '%1': document is not an array.", commandsFilePath); + return result; + } + + const auto fileDataArray = commandsDocument.array(); + for (const auto& value : fileDataArray) { + if (!value.isObject()) { + continue; + } + + const auto entry = value.toObject(); + const auto it = entry.find(QLatin1String("file")); + if (it != entry.end()) { + auto path = it->toString(); + if (QFile::exists(path)) { + if (allFiles) { + result += path; + } else { + if (path == pathToCheck) { + result = QStringList{path}; + break; + } else if (path.startsWith(pathToCheck)) { + result.append(path); + } + } + } + } + } + + return result; } -} \ No newline at end of file +} + +} diff --git a/src/utils.h b/src/utils.h index 70010f7324..e687c66120 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,38 +1,44 @@ /* * This file is part of KDevelop * * Copyright 2018 Friedrich W. H. Kossebau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CLANGTIDY_UTILS_H #define CLANGTIDY_UTILS_H +namespace KDevelop { class Path; } +class QUrl; class QString; +class QStringList; namespace ClangTidy { namespace Utils { QString findExecutable(const QString& fallbackExecutablePath); +QStringList filesFromCompilationDatabase(const KDevelop::Path& buildPath, + const QUrl& urlToCheck, bool allFiles, + QString& error); } } #endif