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