File indexing completed on 2024-04-28 04:38:10

0001 /*
0002     SPDX-FileCopyrightText: 2016 Carlos Nihelton <carlosnsoliveira@gmail.com>
0003     SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau <kossebau@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "job.h"
0009 
0010 // KDevPlatform
0011 #include <interfaces/icore.h>
0012 #include <interfaces/iuicontroller.h>
0013 #include <sublime/message.h>
0014 // KF
0015 #include <KLocalizedString>
0016 // Qt
0017 #include <QApplication>
0018 #include <QFile>
0019 #include <QRegularExpression>
0020 
0021 namespace ClangTidy
0022 {
0023 
0024 // uses ' for quoting
0025 QString inlineYaml(const Job::Parameters& parameters)
0026 {
0027     QString result;
0028 
0029     result.append(QLatin1String("{Checks: '") + parameters.enabledChecks + QLatin1Char('\''));
0030 
0031     if (!parameters.headerFilter.isEmpty()) {
0032         // TODO: the regex might need escpaing for potential quotes of all kinds
0033         result.append(QLatin1String(", HeaderFilterRegex: '") + parameters.headerFilter + QLatin1Char('\''));
0034     }
0035     result.append(QLatin1Char('}'));
0036 
0037     return result;
0038 }
0039 
0040 // uses " for quoting
0041 QStringList commandLineArgs(const Job::Parameters& parameters)
0042 {
0043     QStringList args{
0044         QLatin1Char('\"') + parameters.executablePath + QLatin1Char('\"'),
0045         QLatin1String("-p=\"") + parameters.buildDir + QLatin1Char('\"'),
0046         // don't add statistics we are not interested in to parse anyway
0047         QStringLiteral("-quiet"),
0048     };
0049     if (!parameters.additionalParameters.isEmpty()) {
0050         args << parameters.additionalParameters;
0051     }
0052     if (parameters.checkSystemHeaders) {
0053         args << QStringLiteral("--system-headers");
0054     }
0055 
0056     if (!parameters.useConfigFile) {
0057         args << QLatin1String("--config=\"") + inlineYaml(parameters) + QLatin1Char('\"');
0058     }
0059 
0060     return args;
0061 }
0062 
0063 
0064 Job::Job(const Parameters& params, QObject* parent)
0065     : KDevelop::CompileAnalyzeJob(parent)
0066     , m_parameters(params)
0067 {
0068     setJobName(i18n("Clang-Tidy Analysis"));
0069 
0070     setParallelJobCount(m_parameters.parallelJobCount);
0071     setBuildDirectoryRoot(m_parameters.buildDir);
0072     const auto commandLine = commandLineArgs(m_parameters);
0073     setCommand(commandLine.join(QLatin1Char(' ')), false);
0074     setToolDisplayName(QStringLiteral("Clang-Tidy"));
0075     setSources(m_parameters.filePaths);
0076 
0077     connect(&m_parser, &ClangTidyParser::problemsDetected,
0078             this, &Job::problemsDetected);
0079 
0080     qCDebug(KDEV_CLANGTIDY) << "checking files" << params.filePaths;
0081 }
0082 
0083 Job::~Job()
0084 {
0085 }
0086 
0087 void Job::processStdoutLines(const QStringList& lines)
0088 {
0089     m_parser.addData(lines);
0090     m_standardOutput << lines;
0091 }
0092 
0093 void Job::processStderrLines(const QStringList& lines)
0094 {
0095     static const auto xmlStartRegex = QRegularExpression(QStringLiteral("\\s*<"));
0096 
0097     for (const QString& line : lines) {
0098         // unfortunately sometime clangtidy send non-XML messages to stderr.
0099         // For example, if we pass '-I /missing_include_dir' to the argument list,
0100         // then stderr output will contains such line (tested on clangtidy 1.72):
0101         //
0102         // (information) Couldn't find path given by -I '/missing_include_dir'
0103         //
0104         // Therefore we must 'move' such messages to m_standardOutput.
0105 
0106         if (line.indexOf(xmlStartRegex) != -1) { // the line contains XML
0107             m_xmlOutput << line;
0108         } else {
0109             m_standardOutput << line;
0110         }
0111     }
0112 }
0113 
0114 void Job::postProcessStdout(const QStringList& lines)
0115 {
0116     processStdoutLines(lines);
0117 
0118     KDevelop::CompileAnalyzeJob::postProcessStdout(lines);
0119 }
0120 
0121 void Job::postProcessStderr(const QStringList& lines)
0122 {
0123     processStderrLines(lines);
0124 
0125     KDevelop::CompileAnalyzeJob::postProcessStderr(lines);
0126 }
0127 
0128 void Job::start()
0129 {
0130     m_standardOutput.clear();
0131     m_xmlOutput.clear();
0132 
0133     KDevelop::CompileAnalyzeJob::start();
0134 }
0135 
0136 void Job::childProcessError(QProcess::ProcessError processError)
0137 {
0138     QString messageText;
0139 
0140     switch (processError) {
0141     case QProcess::FailedToStart: {
0142         messageText = i18n("Failed to start Clang-Tidy process.");
0143         break;
0144     }
0145 
0146     case QProcess::Crashed:
0147         messageText = i18n("Clang-Tidy crashed.");
0148         break;
0149 
0150     case QProcess::Timedout:
0151         messageText = i18n("Clang-Tidy process timed out.");
0152         break;
0153 
0154     case QProcess::WriteError:
0155         messageText = i18n("Write to Clang-Tidy process failed.");
0156         break;
0157 
0158     case QProcess::ReadError:
0159         messageText = i18n("Read from Clang-Tidy process failed.");
0160         break;
0161 
0162     case QProcess::UnknownError:
0163         // current clangtidy errors will be displayed in the output view
0164         // don't notify the user
0165         break;
0166     }
0167 
0168     if (!messageText.isEmpty()) {
0169         auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
0170         KDevelop::ICore::self()->uiController()->postMessage(message);
0171     }
0172 
0173     KDevelop::CompileAnalyzeJob::childProcessError(processError);
0174 }
0175 
0176 void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
0177 {
0178     if (exitCode != 0) {
0179         qCDebug(KDEV_CLANGTIDY) << "clang-tidy failed, standard output: ";
0180         qCDebug(KDEV_CLANGTIDY) << m_standardOutput.join(QLatin1Char('\n'));
0181         qCDebug(KDEV_CLANGTIDY) << "clang-tidy failed, XML output: ";
0182         qCDebug(KDEV_CLANGTIDY) << m_xmlOutput.join(QLatin1Char('\n'));
0183     }
0184 
0185     KDevelop::CompileAnalyzeJob::childProcessExited(exitCode, exitStatus);
0186 }
0187 
0188 } // namespace ClangTidy
0189 
0190 #include "moc_job.cpp"