1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "valgrindrunner.h"
6#include "valgrindbar.h"
7
8#include "common/common.h"
9#include "base/abstractaction.h"
10#include "services/window/windowservice.h"
11#include "services/builder/builderservice.h"
12#include "services/language/languageservice.h"
13
14#include <QProcess>
15#include <QDebug>
16#include <QTextBlock>
17
18typedef FileOperation FO;
19using namespace dpfservice;
20
21class ValgrindRunnerPrivate
22{
23 friend class ValgrindRunner;
24
25 QStringList ValgrindArgs;
26 dpfservice::ProjectInfo projectInfo;
27 QString activedProjectKitName;
28 QString workingDir;
29 QString currentFilePath;
30 QString targetPath;
31 QString xmlFilePath;
32
33 QSharedPointer<QAction> memcheckAction;
34 QSharedPointer<QAction> helgrindAction;
35};
36
37ValgrindRunner::ValgrindRunner(QObject *parent)
38 : QObject(parent)
39 , d(new ValgrindRunnerPrivate)
40{
41
42}
43
44void ValgrindRunner::initialize()
45{
46 auto &ctx = dpfInstance.serviceContext();
47 auto windowService = ctx.service<WindowService>(WindowService::name());
48 if (!windowService)
49 return;
50
51 d->memcheckAction.reset(new QAction(MWMAA_VALGRIND_MEMCHECK));
52 ActionManager::getInstance()->registerAction(d->memcheckAction.get(), "Analyze.ValgrindMemcheck",
53 d->memcheckAction->text(), QKeySequence());
54 windowService->addAction(MWM_ANALYZE, new AbstractAction(d->memcheckAction.get()));
55
56 d->helgrindAction.reset(new QAction(MWMAA_VALGRIND_HELGRIND));
57 ActionManager::getInstance()->registerAction(d->helgrindAction.get(), "Analyze.ValgrindHelgrind",
58 d->helgrindAction->text(), QKeySequence());
59 windowService->addAction(MWM_ANALYZE, new AbstractAction(d->helgrindAction.get()));
60
61 QObject::connect(d->memcheckAction.get(), &QAction::triggered, [=]() {
62 QtConcurrent::run([=]() {
63 ValgrindRunner::instance()->runValgrind("memcheck");
64 });
65 });
66
67 QObject::connect(d->helgrindAction.get(), &QAction::triggered, [=]() {
68 QtConcurrent::run([=]() {
69 ValgrindRunner::instance()->runValgrind("helgrind");
70 });
71 });
72
73 setActionsStatus(d->activedProjectKitName);
74}
75
76void ValgrindRunner::runValgrind(const QString &type)
77{
78 if(!checkValgrindToolPath())
79 return;
80 runBuilding();
81 setValgrindArgs(type);
82 QProcess procValgrind;
83 connect(&procValgrind, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
84 [&]() {
85 emit valgrindFinished(d->xmlFilePath, type);
86 });
87
88 connect(&procValgrind, &QProcess::readyReadStandardError, [&]() {
89 procValgrind.setReadChannel(QProcess::StandardError);
90 while (procValgrind.canReadLine()) {
91 QString line = QString::fromUtf8(procValgrind.readLine());
92 outputMsg(line, OutputPane::OutputFormat::StdErr);
93 }
94 });
95
96 connect(&procValgrind, &QProcess::readyReadStandardOutput, [&]() {
97 procValgrind.setReadChannel(QProcess::StandardError);
98 while (procValgrind.canReadLine()) {
99 QString line = QString::fromUtf8(procValgrind.readLine());
100 outputMsg(line, OutputPane::OutputFormat::StdOut);
101 }
102 });
103
104 procValgrind.start("valgrind", d->ValgrindArgs);
105 emit clearValgrindBar(type);
106 procValgrind.waitForFinished(-1);
107}
108
109ValgrindRunner *ValgrindRunner::instance()
110{
111 static ValgrindRunner ins;
112 return &ins;
113}
114
115void ValgrindRunner::setValgrindArgs(const QString &type)
116{
117 QString storage = FO::checkCreateDir(FO::checkCreateDir(d->workingDir, ".unioncode"), "valgrind");
118
119 if (MEMCHECK == type) {
120 d->ValgrindArgs.clear();
121 d->xmlFilePath = storage + QDir::separator() + "memcheck.xml";
122 d->ValgrindArgs << "--leak-check=full" << "--xml=yes" << "--show-leak-kinds=definite"
123 << "--xml-file=" + d->xmlFilePath << d->targetPath;
124 } else if (HELGRIND == type) {
125 d->ValgrindArgs.clear();
126 d->xmlFilePath = storage + QDir::separator() + "helgrind.xml";
127 d->ValgrindArgs << "--tool=helgrind" << "--xml=yes" << "--xml-file=" + d->xmlFilePath << d->targetPath;
128 }
129}
130
131void ValgrindRunner::setMemcheckArgs(QStringList &args)
132{
133 //TODO:add config arguments
134 args << "--leak-check=full" << "--xml=yes" << "--show-leak-kinds=definite";
135}
136
137void ValgrindRunner::setHelgrindArgs(QStringList &args)
138{
139 //TODO:add config arguments
140 args << "--tool=helgrind" << "--xml=yes";
141}
142
143void ValgrindRunner::setActionsStatus(const QString &kitName)
144{
145 if (kitName == "ninja" || kitName == "cmake") {
146 d->memcheckAction.get()->setEnabled(true);
147 d->helgrindAction.get()->setEnabled(true);
148 } else {
149 d->memcheckAction.get()->setEnabled(false);
150 d->helgrindAction.get()->setEnabled(false);
151 }
152}
153
154void ValgrindRunner::saveCurrentProjectInfo(const ProjectInfo &projectInfo)
155{
156 d->projectInfo = projectInfo;
157 d->activedProjectKitName = d->projectInfo.kitName();
158 setActionsStatus(d->activedProjectKitName);
159}
160
161void ValgrindRunner::removeProjectInfo()
162{
163 d->activedProjectKitName.clear();
164 setActionsStatus("");
165}
166
167void ValgrindRunner::saveCurrentFilePath(const QString &filePath)
168{
169 d->currentFilePath = filePath;
170}
171
172void ValgrindRunner::removeCurrentFilePath()
173{
174 d->currentFilePath.clear();
175}
176
177void ValgrindRunner::runBuilding()
178{
179 auto &ctx = dpfInstance.serviceContext();
180 LanguageService *service = ctx.service<LanguageService>(LanguageService::name());
181 if (service) {
182 auto generator = service->create<LanguageGenerator>(d->activedProjectKitName);
183 if (generator) {
184 if (generator->isNeedBuild()) {
185 generator->build(d->projectInfo.workspaceFolder());
186 }
187 RunCommandInfo args = generator->getRunArguments(d->projectInfo, d->currentFilePath);
188 d->targetPath = args.program.trimmed();
189 d->workingDir = args.workingDir.trimmed();
190 }
191 }
192}
193
194void ValgrindRunner::outputMsg(const QString &content, OutputPane::OutputFormat format)
195{
196 QMetaObject::invokeMethod(this, "printOutput", Q_ARG(QString, content), Q_ARG(OutputPane::OutputFormat, format));
197}
198
199bool ValgrindRunner::checkValgrindToolPath()
200{
201 if (!QFile("/usr/bin/valgrind").exists()) {
202 QString retMsg = tr("please install valgrind tool in console with \"sudo apt install valgrind\".");
203 outputMsg(retMsg, OutputPane::OutputFormat::StdErr);
204 return false;
205 }
206 return true;
207}
208
209void ValgrindRunner::printOutput(const QString &content, OutputPane::OutputFormat format)
210{
211 editor.switchContext(tr("&Application Output"));
212 auto outputPane = OutputPane::instance();
213 QString outputContent = content;
214 if (format == OutputPane::OutputFormat::NormalMessage) {
215 QTextDocument *doc = outputPane->document();
216 QTextBlock tb = doc->lastBlock();
217 QString lastLineText = tb.text();
218 QString prefix = "\n";
219 if (lastLineText.isEmpty()) {
220 prefix = "";
221 }
222 QDateTime curDatetime = QDateTime::currentDateTime();
223 QString time = curDatetime.toString("hh:mm:ss");
224 outputContent = prefix + time + ":" + content;
225 }
226 outputContent += "\n";
227 OutputPane::AppendMode mode = OutputPane::AppendMode::Normal;
228 outputPane->appendText(outputContent, format, mode);
229}
230