| 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 | |
| 18 | typedef FileOperation FO; |
| 19 | using namespace dpfservice; |
| 20 | |
| 21 | class 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 | |
| 37 | ValgrindRunner::ValgrindRunner(QObject *parent) |
| 38 | : QObject(parent) |
| 39 | , d(new ValgrindRunnerPrivate) |
| 40 | { |
| 41 | |
| 42 | } |
| 43 | |
| 44 | void 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 | |
| 76 | void 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 | |
| 109 | ValgrindRunner *ValgrindRunner::instance() |
| 110 | { |
| 111 | static ValgrindRunner ins; |
| 112 | return &ins; |
| 113 | } |
| 114 | |
| 115 | void 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 | |
| 131 | void ValgrindRunner::setMemcheckArgs(QStringList &args) |
| 132 | { |
| 133 | //TODO:add config arguments |
| 134 | args << "--leak-check=full" << "--xml=yes" << "--show-leak-kinds=definite" ; |
| 135 | } |
| 136 | |
| 137 | void ValgrindRunner::setHelgrindArgs(QStringList &args) |
| 138 | { |
| 139 | //TODO:add config arguments |
| 140 | args << "--tool=helgrind" << "--xml=yes" ; |
| 141 | } |
| 142 | |
| 143 | void 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 | |
| 154 | void ValgrindRunner::saveCurrentProjectInfo(const ProjectInfo &projectInfo) |
| 155 | { |
| 156 | d->projectInfo = projectInfo; |
| 157 | d->activedProjectKitName = d->projectInfo.kitName(); |
| 158 | setActionsStatus(d->activedProjectKitName); |
| 159 | } |
| 160 | |
| 161 | void ValgrindRunner::removeProjectInfo() |
| 162 | { |
| 163 | d->activedProjectKitName.clear(); |
| 164 | setActionsStatus("" ); |
| 165 | } |
| 166 | |
| 167 | void ValgrindRunner::saveCurrentFilePath(const QString &filePath) |
| 168 | { |
| 169 | d->currentFilePath = filePath; |
| 170 | } |
| 171 | |
| 172 | void ValgrindRunner::removeCurrentFilePath() |
| 173 | { |
| 174 | d->currentFilePath.clear(); |
| 175 | } |
| 176 | |
| 177 | void 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 | |
| 194 | void 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 | |
| 199 | bool 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 | |
| 209 | void 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 | |