1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 Sergey Morozov
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "cppcheckrunner.h"
27 #include "cppchecktool.h"
28 
29 #include <utils/hostosinfo.h>
30 #include <utils/qtcassert.h>
31 #include <utils/qtcprocess.h>
32 
33 #include <coreplugin/messagemanager.h>
34 
35 using namespace Utils;
36 
37 namespace Cppcheck {
38 namespace Internal {
39 
CppcheckRunner(CppcheckTool & tool)40 CppcheckRunner::CppcheckRunner(CppcheckTool &tool) :
41     m_tool(tool),
42     m_process(new Utils::QtcProcess(this))
43 {
44     if (Utils::HostOsInfo::hostOs() == Utils::OsTypeLinux) {
45         QProcess getConf;
46         getConf.start("getconf", {"ARG_MAX"});
47         getConf.waitForFinished(2000);
48         const QByteArray argMax = getConf.readAllStandardOutput().replace("\n", "");
49         m_maxArgumentsLength = std::max(argMax.toInt(), m_maxArgumentsLength);
50     }
51 
52     m_process->setStdOutLineCallback([this](const QString &line) {
53         m_tool.parseOutputLine(line);
54     });
55     m_process->setStdErrLineCallback([this](const QString &line) {
56        m_tool.parseErrorLine(line);
57     });
58 
59     connect(m_process, &QtcProcess::started,
60             this, &CppcheckRunner::handleStarted);
61     connect(m_process, &QtcProcess::finished,
62             this, &CppcheckRunner::handleFinished);
63 
64     m_queueTimer.setSingleShot(true);
65     const int checkDelayInMs = 200;
66     m_queueTimer.setInterval(checkDelayInMs);
67     connect(&m_queueTimer, &QTimer::timeout,
68             this, &CppcheckRunner::checkQueued);
69 }
70 
~CppcheckRunner()71 CppcheckRunner::~CppcheckRunner()
72 {
73     stop();
74     m_queueTimer.stop();
75 }
76 
reconfigure(const QString & binary,const QString & arguments)77 void CppcheckRunner::reconfigure(const QString &binary, const QString &arguments)
78 {
79     m_binary = binary;
80     m_arguments = arguments;
81 }
82 
addToQueue(const Utils::FilePaths & files,const QString & additionalArguments)83 void CppcheckRunner::addToQueue(const Utils::FilePaths &files,
84                                 const QString &additionalArguments)
85 {
86     Utils::FilePaths &existing = m_queue[additionalArguments];
87     if (existing.isEmpty()) {
88         existing = files;
89     } else {
90         std::copy_if(files.cbegin(), files.cend(), std::back_inserter(existing),
91                      [&existing](const Utils::FilePath &file) { return !existing.contains(file); });
92     }
93 
94     if (m_isRunning) {
95         stop(existing);
96         return;
97     }
98 
99     m_queueTimer.start();
100 }
101 
stop(const Utils::FilePaths & files)102 void CppcheckRunner::stop(const Utils::FilePaths &files)
103 {
104     if (!m_isRunning)
105         return;
106 
107     if (files.isEmpty() || m_currentFiles == files)
108         m_process->kill();
109 }
110 
removeFromQueue(const Utils::FilePaths & files)111 void CppcheckRunner::removeFromQueue(const Utils::FilePaths &files)
112 {
113     if (m_queue.isEmpty())
114         return;
115 
116     if (files.isEmpty()) {
117         m_queue.clear();
118     } else {
119         for (auto it = m_queue.begin(), end = m_queue.end(); it != end;) {
120             for (const Utils::FilePath &file : files)
121                 it.value().removeOne(file);
122             it = !it.value().isEmpty() ? ++it : m_queue.erase(it);
123         }
124     }
125 }
126 
currentFiles() const127 const Utils::FilePaths &CppcheckRunner::currentFiles() const
128 {
129     return m_currentFiles;
130 }
131 
currentCommand() const132 QString CppcheckRunner::currentCommand() const
133 {
134     return m_process->commandLine().toUserOutput();
135 }
136 
checkQueued()137 void CppcheckRunner::checkQueued()
138 {
139     if (m_queue.isEmpty() || m_binary.isEmpty())
140         return;
141 
142     Utils::FilePaths files = m_queue.begin().value();
143     QString arguments = m_arguments + ' ' + m_queue.begin().key();
144     m_currentFiles.clear();
145     int argumentsLength = arguments.length();
146     while (!files.isEmpty()) {
147         argumentsLength += files.first().toString().size() + 1; // +1 for separator
148         if (argumentsLength >= m_maxArgumentsLength)
149             break;
150         m_currentFiles.push_back(files.first());
151         arguments += ' ' + files.first().toString();
152         files.pop_front();
153     }
154 
155     if (files.isEmpty())
156         m_queue.erase(m_queue.begin());
157     else
158         m_queue.begin().value() = files;
159 
160     m_process->setCommand(CommandLine(FilePath::fromString(m_binary), arguments, CommandLine::Raw));
161     m_process->start();
162 }
163 
handleStarted()164 void CppcheckRunner::handleStarted()
165 {
166     if (m_isRunning)
167         return;
168 
169     m_isRunning = true;
170     m_tool.startParsing();
171 }
172 
handleFinished()173 void CppcheckRunner::handleFinished()
174 {
175     if (m_process->error() != QProcess::FailedToStart) {
176         m_tool.finishParsing();
177     } else {
178         const QString message = tr("Cppcheck failed to start: \"%1\".").arg(currentCommand());
179         Core::MessageManager::writeSilently(message);
180     }
181     m_currentFiles.clear();
182     m_process->close();
183     m_isRunning = false;
184 
185     if (!m_queue.isEmpty())
186         checkQueued();
187 }
188 
189 } // namespace Internal
190 } // namespace Cppcheck
191