| 1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
| 2 | // |
| 3 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 4 | |
| 5 | #include "runner.h" |
| 6 | |
| 7 | #include "debuggersignals.h" |
| 8 | #include "debuggerglobals.h" |
| 9 | #include "base/abstractmenu.h" |
| 10 | #include "services/language/languageservice.h" |
| 11 | #include "services/project/projectservice.h" |
| 12 | #include "services/builder/builderservice.h" |
| 13 | #include "services/window/windowservice.h" |
| 14 | |
| 15 | #include <QMenu> |
| 16 | #include <QTextBlock> |
| 17 | |
| 18 | using namespace dpfservice; |
| 19 | |
| 20 | class RunnerPrivate |
| 21 | { |
| 22 | friend class Runner; |
| 23 | QString currentBuildUuid; |
| 24 | QString currentOpenedFilePath; |
| 25 | QSharedPointer<QAction> runAction; |
| 26 | bool isRunning = false; |
| 27 | }; |
| 28 | |
| 29 | Runner::Runner(QObject *parent) |
| 30 | : QObject(parent) |
| 31 | , d(new RunnerPrivate()) |
| 32 | { |
| 33 | connect(debuggerSignals, &DebuggerSignals::receivedEvent, this, &Runner::handleEvents); |
| 34 | |
| 35 | d->runAction.reset(new QAction(MWMDA_RUNNING)); |
| 36 | ActionManager::getInstance()->registerAction(d->runAction.get(), "Debug.Running" , |
| 37 | MWMDA_RUNNING, QKeySequence(Qt::Modifier::CTRL | Qt::Key::Key_F5), |
| 38 | ":/resource/images/run.png" ); |
| 39 | connect(d->runAction.get(), &QAction::triggered, this, &Runner::run); |
| 40 | WindowService *service = dpfGetService(WindowService); |
| 41 | service->addToolBarActionItem(tr("Running" ), d->runAction.get(), "Debug.Start" ); |
| 42 | } |
| 43 | |
| 44 | void Runner::run() |
| 45 | { |
| 46 | LanguageService *service = dpfGetService(LanguageService); |
| 47 | if (service) { |
| 48 | auto generator = service->create<LanguageGenerator>(getActiveProjectInfo().kitName()); |
| 49 | if (generator) { |
| 50 | if (generator->isNeedBuild()) { |
| 51 | d->currentBuildUuid = generator->build(getActiveProjectInfo().workspaceFolder()); |
| 52 | } else { |
| 53 | running(); |
| 54 | } |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | void Runner::handleEvents(const dpf::Event &event) |
| 60 | { |
| 61 | QString topic = event.topic(); |
| 62 | QString data = event.data().toString(); |
| 63 | if (topic == T_BUILDER) { |
| 64 | if (data == D_BUILD_STATE) { |
| 65 | int state = event.property(P_STATE).toInt(); |
| 66 | BuildCommandInfo commandInfo = qvariant_cast<BuildCommandInfo>(event.property(P_ORIGINCMD)); |
| 67 | if (commandInfo.uuid == d->currentBuildUuid) { |
| 68 | int buildSuccess = 0; |
| 69 | if (state == buildSuccess) { |
| 70 | running(); |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | } else if (event.data() == editor.switchedFile.name) { |
| 75 | QString filePath = event.property(editor.switchedFile.pKeys[0]).toString(); |
| 76 | if (d->currentOpenedFilePath != filePath) { |
| 77 | d->currentOpenedFilePath = filePath; |
| 78 | } |
| 79 | } else if (event.data() == editor.openedFile.name) { |
| 80 | QString filePath = event.property(editor.switchedFile.pKeys[0]).toString(); |
| 81 | d->currentOpenedFilePath = filePath; |
| 82 | } else if (event.data() == editor.closedFile.name) { |
| 83 | QString filePath = event.property(editor.switchedFile.pKeys[0]).toString(); |
| 84 | if (d->currentOpenedFilePath == filePath) { |
| 85 | d->currentOpenedFilePath.clear(); |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | void Runner::running() |
| 91 | { |
| 92 | if(d->isRunning) { |
| 93 | outputMsg("The program is running, please try again later!" , OutputPane::OutputFormat::ErrorMessage); |
| 94 | return; |
| 95 | } |
| 96 | |
| 97 | editor.switchContext(tr("&Application Output" )); |
| 98 | |
| 99 | LanguageService *service = dpfGetService(LanguageService); |
| 100 | if (service) { |
| 101 | auto generator = service->create<LanguageGenerator>(getActiveProjectInfo().kitName()); |
| 102 | if (generator) { |
| 103 | dpfservice::ProjectInfo activeProjInfo = dpfGetService(ProjectService)->getActiveProjectInfo(); |
| 104 | RunCommandInfo args = generator->getRunArguments(activeProjInfo, d->currentOpenedFilePath); |
| 105 | QtConcurrent::run([=](){ |
| 106 | execCommand(args); |
| 107 | }); |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | bool Runner::execCommand(const RunCommandInfo &info) |
| 113 | { |
| 114 | bool ret = false; |
| 115 | QString retMsg = tr("Error: execute command error! The reason is unknown.\n" ); |
| 116 | QProcess process; |
| 117 | process.setWorkingDirectory(info.workingDir); |
| 118 | |
| 119 | QString startMsg = tr("Start execute command: \"%1\" \"%2\" in workspace \"%3\".\n" ) |
| 120 | .arg(info.program, info.arguments.join(" " ), info.workingDir); |
| 121 | outputMsg(startMsg, OutputPane::OutputFormat::NormalMessage); |
| 122 | |
| 123 | connect(&process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), |
| 124 | [&](int exitcode, QProcess::ExitStatus exitStatus) { |
| 125 | if (0 == exitcode && exitStatus == QProcess::ExitStatus::NormalExit) { |
| 126 | ret = true; |
| 127 | retMsg = tr("The process \"%1\" exited normally.\n" ).arg(process.program()); |
| 128 | } else if (exitStatus == QProcess::NormalExit) { |
| 129 | ret = false; |
| 130 | retMsg = tr("The process \"%1\" exited with code %2.\n" ) |
| 131 | .arg(process.program(), QString::number(exitcode)); |
| 132 | } else { |
| 133 | ret = false; |
| 134 | retMsg = tr("The process \"%1\" crashed.\n" ) |
| 135 | .arg(process.program()); |
| 136 | } |
| 137 | }); |
| 138 | |
| 139 | connect(&process, &QProcess::readyReadStandardOutput, [&]() { |
| 140 | process.setReadChannel(QProcess::StandardOutput); |
| 141 | while (process.canReadLine()) { |
| 142 | QString line = QString::fromUtf8(process.readLine()); |
| 143 | outputMsg(line, OutputPane::OutputFormat::StdOut); |
| 144 | } |
| 145 | }); |
| 146 | |
| 147 | connect(&process, &QProcess::readyReadStandardError, [&]() { |
| 148 | process.setReadChannel(QProcess::StandardError); |
| 149 | while (process.canReadLine()) { |
| 150 | QString line = QString::fromUtf8(process.readLine()); |
| 151 | outputMsg(line, OutputPane::OutputFormat::StdErr); |
| 152 | } |
| 153 | }); |
| 154 | |
| 155 | process.start(info.program, info.arguments); |
| 156 | process.waitForFinished(-1); |
| 157 | |
| 158 | outputMsg(retMsg, ret ? OutputPane::OutputFormat::NormalMessage : OutputPane::OutputFormat::StdErr); |
| 159 | |
| 160 | QString endMsg = tr("Execute command finished.\n" ); |
| 161 | outputMsg(endMsg, OutputPane::OutputFormat::NormalMessage); |
| 162 | |
| 163 | return ret; |
| 164 | } |
| 165 | |
| 166 | void Runner::outputMsg(const QString &content, OutputPane::OutputFormat format) |
| 167 | { |
| 168 | QMetaObject::invokeMethod(this, "synOutputMsg" , Q_ARG(QString, content), Q_ARG(OutputPane::OutputFormat, format)); |
| 169 | } |
| 170 | |
| 171 | ProjectInfo Runner::getActiveProjectInfo() const |
| 172 | { |
| 173 | return dpfGetService(ProjectService)->getActiveProjectInfo(); |
| 174 | } |
| 175 | |
| 176 | void Runner::synOutputMsg(const QString &content, OutputPane::OutputFormat format) |
| 177 | { |
| 178 | auto outputPane = OutputPane::instance(); |
| 179 | QString outputContent = content; |
| 180 | if (format == OutputPane::OutputFormat::NormalMessage) { |
| 181 | QTextDocument *doc = outputPane->document(); |
| 182 | QTextBlock tb = doc->lastBlock(); |
| 183 | QString lastLineText = tb.text(); |
| 184 | QString prefix = "\n" ; |
| 185 | if (lastLineText.isEmpty()) { |
| 186 | prefix = "" ; |
| 187 | } |
| 188 | QDateTime curDatetime = QDateTime::currentDateTime(); |
| 189 | QString time = curDatetime.toString("hh:mm:ss" ); |
| 190 | outputContent = prefix + time + ":" + content + "\n" ; |
| 191 | } |
| 192 | OutputPane::AppendMode mode = OutputPane::AppendMode::Normal; |
| 193 | outputPane->appendText(outputContent, format, mode); |
| 194 | } |
| 195 | |
| 196 | |