1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
2 | // |
3 | // SPDX-License-Identifier: GPL-3.0-or-later |
4 | |
5 | #include "buildmanager.h" |
6 | #include "common/widget/outputpane.h" |
7 | #include "common/util/commandparser.h" |
8 | #include "problemoutputpane.h" |
9 | #include "commonparser.h" |
10 | #include "transceiver/buildersender.h" |
11 | #include "compileoutputpane.h" |
12 | |
13 | #include "services/builder/builderservice.h" |
14 | #include "services/builder/ioutputparser.h" |
15 | #include "services/window/windowservice.h" |
16 | #include "services/project/projectinfo.h" |
17 | #include "services/builder/buildergenerator.h" |
18 | #include "services/option/optionmanager.h" |
19 | #include "services/project/projectservice.h" |
20 | |
21 | #include "base/abstractaction.h" |
22 | |
23 | #include <QCoreApplication> |
24 | |
25 | using namespace dpfservice; |
26 | |
27 | class BuildManagerPrivate |
28 | { |
29 | friend class BuildManager; |
30 | |
31 | QSharedPointer<QAction> buildAction; |
32 | QSharedPointer<QAction> rebuildAction; |
33 | QSharedPointer<QAction> cleanAction; |
34 | QSharedPointer<QAction> cancelAction; |
35 | |
36 | CompileOutputPane *compileOutputPane = nullptr; |
37 | ProblemOutputPane *problemOutputPane = nullptr; |
38 | |
39 | QString activedKitName; |
40 | QString activedWorkingDir; |
41 | |
42 | std::unique_ptr<IOutputParser> outputParser = nullptr; |
43 | |
44 | QProcess cmdProcess; |
45 | QFuture<void> buildThread; |
46 | |
47 | BuildState currentState = BuildState::kNoBuild; |
48 | }; |
49 | |
50 | BuildManager *BuildManager::instance() |
51 | { |
52 | static BuildManager ins; |
53 | return &ins; |
54 | } |
55 | |
56 | BuildManager::BuildManager(QObject *parent) |
57 | : QObject(parent) |
58 | , d(new BuildManagerPrivate()) |
59 | { |
60 | addMenu(); |
61 | |
62 | d->compileOutputPane = new CompileOutputPane(); |
63 | d->problemOutputPane = new ProblemOutputPane(); |
64 | d->outputParser.reset(new CommonParser()); |
65 | connect(d->outputParser.get(), &IOutputParser::addOutput, this, &BuildManager::addOutput, Qt::DirectConnection); |
66 | connect(d->outputParser.get(), &IOutputParser::addTask, d->problemOutputPane, &ProblemOutputPane::addTask, Qt::DirectConnection); |
67 | |
68 | QObject::connect(this, &BuildManager::sigOutputCompileInfo, this, &BuildManager::slotOutputCompileInfo); |
69 | QObject::connect(this, &BuildManager::sigOutputProblemInfo, this, &BuildManager::slotOutputProblemInfo); |
70 | |
71 | qRegisterMetaType<BuildState>("BuildState" ); |
72 | qRegisterMetaType<BuildCommandInfo>("BuildCommandInfo" ); |
73 | QObject::connect(this, &BuildManager::sigBuildState, this, &BuildManager::slotBuildState); |
74 | QObject::connect(this, &BuildManager::sigOutputNotify, this, &BuildManager::slotOutputNotify); |
75 | QObject::connect(this, &BuildManager::sigResetBuildUI, this, &BuildManager::slotResetBuildUI); |
76 | } |
77 | |
78 | BuildManager::~BuildManager() |
79 | { |
80 | if (d) { |
81 | delete d; |
82 | } |
83 | } |
84 | |
85 | void BuildManager::() |
86 | { |
87 | auto &ctx = dpfInstance.serviceContext(); |
88 | auto windowService = ctx.service<WindowService>(WindowService::name()); |
89 | if (!windowService) |
90 | return; |
91 | |
92 | auto actionInit = [&](QAction *action, QString actionID, QKeySequence key, QString iconFileName){ |
93 | ActionManager::getInstance()->registerAction(action, actionID, action->text(), key, iconFileName); |
94 | AbstractAction *actionImpl = new AbstractAction(action); |
95 | windowService->addAction(dpfservice::MWM_BUILD, actionImpl); |
96 | }; |
97 | |
98 | d->buildAction.reset(new QAction(MWMBA_BUILD)); |
99 | actionInit(d->buildAction.get(), "Build.Build" , QKeySequence(Qt::Modifier::CTRL | Qt::Key::Key_B), |
100 | ":/buildercore/images/build.png" ); |
101 | windowService->addToolBarActionItem("toolbar.Build" , d->buildAction.get(), "Build.End" ); |
102 | |
103 | d->rebuildAction.reset(new QAction(MWMBA_REBUILD)); |
104 | actionInit(d->rebuildAction.get(), "Build.Rebuild" , QKeySequence(Qt::Modifier::CTRL | Qt::Modifier::SHIFT | Qt::Key::Key_B), |
105 | ":/buildercore/images/rebuild.png" ); |
106 | |
107 | d->cleanAction.reset(new QAction(MWMBA_CLEAN)); |
108 | actionInit(d->cleanAction.get(), "Build.Clean" , QKeySequence(Qt::Modifier::CTRL | Qt::Modifier::SHIFT | Qt::Key::Key_C), |
109 | ":/buildercore/images/clean.svg" ); |
110 | |
111 | d->cancelAction.reset(new QAction(MWMBA_CANCEL)); |
112 | actionInit(d->cancelAction.get(), "Build.Cancel" , QKeySequence(Qt::Modifier::ALT | Qt::Key::Key_Backspace), |
113 | ":/buildercore/images/cancel.svg" ); |
114 | |
115 | QObject::connect(d->buildAction.get(), &QAction::triggered, |
116 | this, &BuildManager::buildProject, Qt::DirectConnection); |
117 | QObject::connect(d->rebuildAction.get(), &QAction::triggered, |
118 | this, &BuildManager::rebuildProject, Qt::DirectConnection); |
119 | QObject::connect(d->cleanAction.get(), &QAction::triggered, |
120 | this, &BuildManager::cleanProject, Qt::DirectConnection); |
121 | QObject::connect(d->cancelAction.get(), &QAction::triggered, |
122 | this, &BuildManager::cancelBuild, Qt::DirectConnection); |
123 | } |
124 | |
125 | void BuildManager::buildProject() |
126 | { |
127 | execBuildStep({Build}); |
128 | } |
129 | |
130 | void BuildManager::rebuildProject() |
131 | { |
132 | execBuildStep({Clean, Build}); |
133 | } |
134 | |
135 | void BuildManager::cleanProject() |
136 | { |
137 | execBuildStep({Clean}); |
138 | } |
139 | |
140 | void BuildManager::cancelBuild() |
141 | { |
142 | if (d->currentState == kBuilding) { |
143 | d->buildThread.cancel(); |
144 | disconnectSignals(); |
145 | d->cmdProcess.kill(); |
146 | } |
147 | } |
148 | |
149 | |
150 | void BuildManager::(QList<BuildMenuType> ) |
151 | { |
152 | if(!canStartBuild()) { |
153 | QMetaObject::invokeMethod(this, "message" , |
154 | Q_ARG(QString, "The builder is running, please try again later!" )); |
155 | return; |
156 | } |
157 | |
158 | auto &ctx = dpfInstance.serviceContext(); |
159 | ProjectService *projectService = ctx.service<ProjectService>(ProjectService::name()); |
160 | if (!projectService || !projectService->getProjectInfo) |
161 | return; |
162 | |
163 | ProjectInfo projectInfo = projectService->getProjectInfo(d->activedKitName, d->activedWorkingDir); |
164 | if (!projectInfo.isVaild()) |
165 | return; |
166 | |
167 | auto builderService = ctx.service<BuilderService>(BuilderService::name()); |
168 | if (builderService) { |
169 | auto generator = builderService->create<BuilderGenerator>(d->activedKitName); |
170 | if (generator) { |
171 | emit sigResetBuildUI(); |
172 | generator->appendOutputParser(d->outputParser); |
173 | QList<BuildCommandInfo> list; |
174 | foreach (auto , menuTypelist) { |
175 | BuildCommandInfo info = generator->getMenuCommand(menuType, projectInfo); |
176 | QString retMsg; |
177 | bool ret = generator->checkCommandValidity(info, retMsg); |
178 | if (!ret) { |
179 | outputLog(retMsg, OutputPane::OutputFormat::StdErr); |
180 | continue; |
181 | } |
182 | list.append(info); |
183 | } |
184 | execCommands(list, false); |
185 | } |
186 | } |
187 | } |
188 | |
189 | CompileOutputPane *BuildManager::getCompileOutputPane() const |
190 | { |
191 | return d->compileOutputPane; |
192 | } |
193 | |
194 | ProblemOutputPane *BuildManager::getProblemOutputPane() const |
195 | { |
196 | return d->problemOutputPane; |
197 | } |
198 | |
199 | void BuildManager::slotResetBuildUI() |
200 | { |
201 | d->compileOutputPane->clearContents(); |
202 | d->problemOutputPane->clearContents(); |
203 | |
204 | editor.switchContext(tr("Co&mpile Output" )); |
205 | } |
206 | |
207 | void BuildManager::setActivedProjectInfo(const QString &kitName, const QString &workingDir) |
208 | { |
209 | d->activedKitName = kitName; |
210 | d->activedWorkingDir = workingDir; |
211 | } |
212 | |
213 | void BuildManager::clearActivedProjectInfo() |
214 | { |
215 | d->activedKitName.clear(); |
216 | d->activedWorkingDir.clear(); |
217 | } |
218 | |
219 | bool BuildManager::handleCommand(const QList<BuildCommandInfo> &commandInfo, bool isSynchronous) |
220 | { |
221 | if(!canStartBuild()) { |
222 | QMetaObject::invokeMethod(this, "message" , |
223 | Q_ARG(QString, "The builder is running, please try again later!" )); |
224 | return false; |
225 | } |
226 | |
227 | auto &ctx = dpfInstance.serviceContext(); |
228 | auto builderService = ctx.service<BuilderService>(BuilderService::name()); |
229 | if (builderService) { |
230 | auto generator = builderService->create<BuilderGenerator>(commandInfo.at(0).kitName); |
231 | if (generator) { |
232 | emit sigResetBuildUI(); |
233 | generator->appendOutputParser(d->outputParser); |
234 | QString retMsg; |
235 | bool ret = generator->checkCommandValidity(commandInfo.at(0), retMsg); |
236 | if (!ret) { |
237 | outputLog(retMsg, OutputPane::OutputFormat::StdErr); |
238 | return false; |
239 | } |
240 | } |
241 | execCommands(commandInfo, isSynchronous); |
242 | } |
243 | return true; |
244 | } |
245 | |
246 | bool BuildManager::execCommands(const QList<BuildCommandInfo> &commandList, bool isSynchronous) |
247 | { |
248 | //Synchronous execution is required in commandLine build model |
249 | if (isSynchronous) { |
250 | if (!commandList.isEmpty()) { |
251 | for (auto command : commandList) { |
252 | execCommand(command); |
253 | } |
254 | } |
255 | } else { |
256 | if (!commandList.isEmpty()) { |
257 | d->buildThread = QtConcurrent::run([=](){ |
258 | QMutexLocker locker(&releaseMutex); |
259 | for (auto command : commandList) { |
260 | execCommand(command); |
261 | } |
262 | }); |
263 | } |
264 | } |
265 | |
266 | return true; |
267 | } |
268 | |
269 | bool BuildManager::execCommand(const BuildCommandInfo &info) |
270 | { |
271 | outBuildState(BuildState::kBuilding); |
272 | bool ret = false; |
273 | QString executeResult = tr("Execute command failed!\n" ); |
274 | |
275 | d->cmdProcess.setWorkingDirectory(info.workingDir); |
276 | |
277 | QString startMsg = tr("Start execute command: \"%1\" \"%2\" in workspace \"%3\".\n" ) |
278 | .arg(info.program, info.arguments.join(" " ), info.workingDir); |
279 | outputLog(startMsg, OutputPane::OutputFormat::NormalMessage); |
280 | |
281 | connect(&d->cmdProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), |
282 | [&](int exitcode, QProcess::ExitStatus exitStatus) { |
283 | if (0 == exitcode && exitStatus == QProcess::ExitStatus::NormalExit) { |
284 | ret = true; |
285 | executeResult = tr("The process \"%1\" exited normally.\n" ).arg(d->cmdProcess.program()); |
286 | } else if (exitStatus == QProcess::NormalExit) { |
287 | ret = false; |
288 | executeResult = tr("The process \"%1\" exited with code %2.\n" ) |
289 | .arg(d->cmdProcess.program(), QString::number(exitcode)); |
290 | } else { |
291 | ret = false; |
292 | executeResult = tr("The process \"%1\" crashed.\n" ) |
293 | .arg(d->cmdProcess.program()); |
294 | } |
295 | }); |
296 | |
297 | connect(&d->cmdProcess, &QProcess::readyReadStandardOutput, [&]() { |
298 | d->cmdProcess.setReadChannel(QProcess::StandardOutput); |
299 | while (d->cmdProcess.canReadLine()) { |
300 | QString line = QString::fromUtf8(d->cmdProcess.readLine()); |
301 | outputLog(line, OutputPane::OutputFormat::StdOut); |
302 | } |
303 | }); |
304 | |
305 | connect(&d->cmdProcess, &QProcess::readyReadStandardError, [&]() { |
306 | d->cmdProcess.setReadChannel(QProcess::StandardError); |
307 | while (d->cmdProcess.canReadLine()) { |
308 | QString line = QString::fromUtf8(d->cmdProcess.readLine()); |
309 | outputLog(line, OutputPane::OutputFormat::StdErr); |
310 | outputError(line); |
311 | } |
312 | }); |
313 | |
314 | d->cmdProcess.start(info.program, info.arguments); |
315 | d->cmdProcess.waitForFinished(-1); |
316 | |
317 | disconnectSignals(); |
318 | outputLog(executeResult, ret ? OutputPane::OutputFormat::NormalMessage : OutputPane::OutputFormat::StdErr); |
319 | |
320 | QString endMsg = tr("Execute command finished.\n" ); |
321 | outputLog(endMsg, OutputPane::OutputFormat::NormalMessage); |
322 | |
323 | BuildState buildState = ret ? BuildState::kNoBuild : BuildState::kBuildFailed; |
324 | outBuildState(buildState); |
325 | |
326 | outputNotify(buildState, info); |
327 | return ret; |
328 | } |
329 | |
330 | void BuildManager::outputLog(const QString &content, const OutputPane::OutputFormat format) |
331 | { |
332 | emit sigOutputCompileInfo(content, format); |
333 | } |
334 | |
335 | void BuildManager::outputError(const QString &content) |
336 | { |
337 | emit sigOutputProblemInfo(content); |
338 | } |
339 | |
340 | void BuildManager::outputNotify(const BuildState &state, const BuildCommandInfo &commandInfo) |
341 | { |
342 | emit sigOutputNotify(state, commandInfo); |
343 | } |
344 | |
345 | void BuildManager::slotOutputCompileInfo(const QString &content, const OutputPane::OutputFormat format) |
346 | { |
347 | if (format == OutputPane::OutputFormat::StdOut || OutputPane::OutputFormat::NormalMessage) { |
348 | std::cout << content.toStdString() << std::endl; |
349 | } else if (format == OutputPane::OutputFormat::StdErr) { |
350 | std::cerr << content.toStdString() << std::endl; |
351 | } |
352 | d->outputParser->stdOutput(content, format); |
353 | } |
354 | |
355 | void BuildManager::slotOutputProblemInfo(const QString &content) |
356 | { |
357 | d->outputParser->stdError(content); |
358 | } |
359 | |
360 | void BuildManager::slotOutputNotify(const BuildState &state, const BuildCommandInfo &commandInfo) |
361 | { |
362 | BuilderSender::notifyBuildState(state, commandInfo); |
363 | } |
364 | |
365 | void BuildManager::addOutput(const QString &content, const OutputPane::OutputFormat format) |
366 | { |
367 | QString newContent = content; |
368 | if (OutputPane::OutputFormat::NormalMessage == format |
369 | || OutputPane::OutputFormat::ErrorMessage == format |
370 | || OutputPane::OutputFormat::StdOut == format) { |
371 | |
372 | QDateTime curDatetime = QDateTime::currentDateTime(); |
373 | QString time = curDatetime.toString("hh:mm:ss" ); |
374 | newContent = time + ": " + newContent; |
375 | } |
376 | d->compileOutputPane->appendText(newContent, format); |
377 | } |
378 | |
379 | void BuildManager::outBuildState(const BuildState &buildState) |
380 | { |
381 | emit sigBuildState(buildState); |
382 | } |
383 | |
384 | void BuildManager::slotBuildState(const BuildState &buildState) |
385 | { |
386 | d->currentState = buildState; |
387 | |
388 | switch (buildState) { |
389 | case BuildState::kNoBuild: |
390 | case BuildState::kBuildFailed: |
391 | d->buildAction->setEnabled(true); |
392 | d->rebuildAction->setEnabled(true); |
393 | d->cleanAction->setEnabled(true); |
394 | d->cancelAction->setEnabled(false); |
395 | break; |
396 | case BuildState::kBuilding: |
397 | d->buildAction->setEnabled(false); |
398 | d->rebuildAction->setEnabled(false); |
399 | d->cleanAction->setEnabled(false); |
400 | d->cancelAction->setEnabled(true); |
401 | break; |
402 | } |
403 | } |
404 | |
405 | bool BuildManager::canStartBuild() |
406 | { |
407 | return BuildState::kBuilding != d->currentState; |
408 | } |
409 | |
410 | void BuildManager::disconnectSignals() |
411 | { |
412 | disconnect(&d->cmdProcess, static_cast<void (QProcess::*)\ |
413 | (int, QProcess::ExitStatus)>(&QProcess::finished), nullptr, nullptr); |
414 | disconnect(&d->cmdProcess, &QProcess::readyReadStandardOutput, nullptr, nullptr); |
415 | disconnect(&d->cmdProcess, &QProcess::readyReadStandardError, nullptr, nullptr); |
416 | } |
417 | |