1/*
2 * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17#include "compilermanager.h"
18#include "filecompiler.h"
19#include "stdincompiler.h"
20#include "../mainwindow.h"
21#include "executablerunner.h"
22#include "ojproblemcasesrunner.h"
23#include "utils.h"
24#include "../settings.h"
25#include <QMessageBox>
26#include <QUuid>
27#include "projectcompiler.h"
28#include "qt_utils/charsetinfo.h"
29
30enum RunProgramFlag {
31 RPF_PAUSE_CONSOLE = 0x0001,
32 RPF_REDIRECT_INPUT = 0x0002
33};
34
35CompilerManager::CompilerManager(QObject *parent) : QObject(parent),
36 mCompileMutex(QMutex::Recursive),
37 mBackgroundSyntaxCheckMutex(QMutex::Recursive),
38 mRunnerMutex(QMutex::Recursive)
39
40{
41 mCompiler = nullptr;
42 mBackgroundSyntaxChecker = nullptr;
43 mRunner = nullptr;
44 mSyntaxCheckErrorCount = 0;
45 mSyntaxCheckIssueCount = 0;
46 mCompileErrorCount = 0;
47 mCompileIssueCount = 0;
48 mSyntaxCheckErrorCount = 0;
49}
50
51bool CompilerManager::compiling()
52{
53 QMutexLocker locker(&mCompileMutex);
54 return mCompiler!=nullptr;
55}
56
57bool CompilerManager::backgroundSyntaxChecking()
58{
59 QMutexLocker locker(&mBackgroundSyntaxCheckMutex);
60 return mBackgroundSyntaxChecker!=nullptr;
61}
62
63bool CompilerManager::running()
64{
65 QMutexLocker locker(&mRunnerMutex);
66 return (mRunner!=nullptr && !mRunner->pausing());
67}
68
69void CompilerManager::compile(const QString& filename, const QByteArray& encoding, bool rebuild, bool silent, bool onlyCheckSyntax)
70{
71 if (!pSettings->compilerSets().defaultSet()) {
72 QMessageBox::critical(pMainWindow,
73 tr("No compiler set"),
74 tr("No compiler set is configured.")+tr("Can't start debugging."));
75 return;
76 }
77 {
78 QMutexLocker locker(&mCompileMutex);
79 if (mCompiler!=nullptr) {
80 return;
81 }
82 mCompileErrorCount = 0;
83 mCompileIssueCount = 0;
84 mCompiler = new FileCompiler(filename,encoding,silent,onlyCheckSyntax);
85 mCompiler->setRebuild(rebuild);
86 connect(mCompiler, &Compiler::finished, mCompiler, &QObject::deleteLater);
87 connect(mCompiler, &Compiler::compileFinished, this, &CompilerManager::onCompileFinished);
88 connect(mCompiler, &Compiler::compileIssue, this, &CompilerManager::onCompileIssue);
89 connect(mCompiler, &Compiler::compileStarted, pMainWindow, &MainWindow::onCompileStarted);
90
91 connect(mCompiler, &Compiler::compileOutput, pMainWindow, &MainWindow::logToolsOutput);
92 connect(mCompiler, &Compiler::compileIssue, pMainWindow, &MainWindow::onCompileIssue);
93 connect(mCompiler, &Compiler::compileErrorOccured, pMainWindow, &MainWindow::onCompileErrorOccured);
94 mCompiler->start();
95 }
96}
97
98void CompilerManager::compileProject(std::shared_ptr<Project> project, bool rebuild, bool silent,bool onlyCheckSyntax)
99{
100 if (!pSettings->compilerSets().defaultSet()) {
101 QMessageBox::critical(pMainWindow,
102 tr("No compiler set"),
103 tr("No compiler set is configured.")+tr("Can't start debugging."));
104 return;
105 }
106 {
107 QMutexLocker locker(&mCompileMutex);
108 if (mCompiler!=nullptr) {
109 return;
110 }
111 mCompileErrorCount = 0;
112 mCompileIssueCount = 0;
113 mCompiler = new ProjectCompiler(project,silent,onlyCheckSyntax);
114 mCompiler->setRebuild(rebuild);
115 connect(mCompiler, &Compiler::finished, mCompiler, &QObject::deleteLater);
116 connect(mCompiler, &Compiler::compileFinished, this, &CompilerManager::onCompileFinished);
117
118 connect(mCompiler, &Compiler::compileIssue, this, &CompilerManager::onCompileIssue);
119 connect(mCompiler, &Compiler::compileStarted, pMainWindow, &MainWindow::onCompileStarted);
120
121 connect(mCompiler, &Compiler::compileOutput, pMainWindow, &MainWindow::logToolsOutput);
122 connect(mCompiler, &Compiler::compileIssue, pMainWindow, &MainWindow::onCompileIssue);
123 connect(mCompiler, &Compiler::compileErrorOccured, pMainWindow, &MainWindow::onCompileErrorOccured);
124 mCompiler->start();
125 }
126}
127
128void CompilerManager::cleanProject(std::shared_ptr<Project> project)
129{
130 if (!pSettings->compilerSets().defaultSet()) {
131 QMessageBox::critical(pMainWindow,
132 tr("No compiler set"),
133 tr("No compiler set is configured.")+tr("Can't start debugging."));
134 return;
135 }
136 {
137 QMutexLocker locker(&mCompileMutex);
138 if (mCompiler!=nullptr) {
139 return;
140 }
141 mCompileErrorCount = 0;
142 mCompileIssueCount = 0;
143 ProjectCompiler* compiler = new ProjectCompiler(project,false,false);
144 compiler->setOnlyClean(true);
145 mCompiler = compiler;
146 mCompiler->setRebuild(false);
147 connect(mCompiler, &Compiler::finished, mCompiler, &QObject::deleteLater);
148 connect(mCompiler, &Compiler::compileFinished, this, &CompilerManager::onCompileFinished);
149
150 connect(mCompiler, &Compiler::compileIssue, this, &CompilerManager::onCompileIssue);
151 connect(mCompiler, &Compiler::compileStarted, pMainWindow, &MainWindow::onCompileStarted);
152
153 connect(mCompiler, &Compiler::compileOutput, pMainWindow, &MainWindow::logToolsOutput);
154 connect(mCompiler, &Compiler::compileIssue, pMainWindow, &MainWindow::onCompileIssue);
155 connect(mCompiler, &Compiler::compileErrorOccured, pMainWindow, &MainWindow::onCompileErrorOccured);
156 mCompiler->start();
157 }
158}
159
160void CompilerManager::buildProjectMakefile(std::shared_ptr<Project> project)
161{
162 if (!pSettings->compilerSets().defaultSet()) {
163 QMessageBox::critical(pMainWindow,
164 tr("No compiler set"),
165 tr("No compiler set is configured.")+tr("Can't start debugging."));
166 return;
167 }
168 {
169 QMutexLocker locker(&mCompileMutex);
170 if (mCompiler!=nullptr) {
171 return;
172 }
173 ProjectCompiler compiler(project,false,false);
174 compiler.buildMakeFile();
175 }
176
177}
178
179void CompilerManager::checkSyntax(const QString &filename, const QByteArray& encoding, const QString &content, std::shared_ptr<Project> project)
180{
181 if (!pSettings->compilerSets().defaultSet()) {
182 QMessageBox::critical(pMainWindow,
183 tr("No compiler set"),
184 tr("No compiler set is configured.")+tr("Can't start debugging."));
185 return;
186 }
187 {
188 QMutexLocker locker(&mBackgroundSyntaxCheckMutex);
189 if (mBackgroundSyntaxChecker!=nullptr) {
190 return;
191 }
192
193 mSyntaxCheckErrorCount = 0;
194 mSyntaxCheckIssueCount = 0;
195 StdinCompiler *pStdinCompiler = new StdinCompiler(filename,encoding, content,true,true);
196 mBackgroundSyntaxChecker = pStdinCompiler;
197 mBackgroundSyntaxChecker->setProject(project);
198 connect(mBackgroundSyntaxChecker, &Compiler::finished, mBackgroundSyntaxChecker, &QThread::deleteLater);
199 connect(mBackgroundSyntaxChecker, &Compiler::compileIssue, this, &CompilerManager::onSyntaxCheckIssue);
200 connect(mBackgroundSyntaxChecker, &Compiler::compileStarted, pMainWindow, &MainWindow::onCompileStarted);
201 connect(mBackgroundSyntaxChecker, &Compiler::compileFinished, this, &CompilerManager::onSyntaxCheckFinished);
202 connect(mBackgroundSyntaxChecker, &Compiler::compileOutput, pMainWindow, &MainWindow::logToolsOutput);
203 connect(mBackgroundSyntaxChecker, &Compiler::compileIssue, pMainWindow, &MainWindow::onCompileIssue);
204 connect(mBackgroundSyntaxChecker, &Compiler::compileErrorOccured, pMainWindow, &MainWindow::onCompileErrorOccured);
205 mBackgroundSyntaxChecker->start();
206 }
207}
208
209void CompilerManager::run(
210 const QString &filename,
211 const QString &arguments,
212 const QString &workDir,
213 const QStringList& binDirs)
214{
215 QMutexLocker locker(&mRunnerMutex);
216 if (mRunner!=nullptr && !mRunner->pausing()) {
217 return;
218 }
219 QString redirectInputFilename;
220 bool redirectInput=false;
221 if (pSettings->executor().redirectInput()
222 && !pSettings->executor().inputFilename().isEmpty()) {
223 redirectInput =true;
224 redirectInputFilename = pSettings->executor().inputFilename();
225 }
226 ExecutableRunner * execRunner;
227 if (programHasConsole(filename)) {
228 int consoleFlag=0;
229 if (redirectInput)
230 consoleFlag |= RPF_REDIRECT_INPUT;
231 if (pSettings->executor().pauseConsole())
232 consoleFlag |= RPF_PAUSE_CONSOLE;
233#ifdef Q_OS_WIN
234 if (consoleFlag!=0) {
235 QString sharedMemoryId = QUuid::createUuid().toString();
236 QString newArguments = QString(" %1 %2 \"%3\" %4")
237 .arg(consoleFlag)
238 .arg(sharedMemoryId,localizePath(filename)).arg(arguments);
239 execRunner = new ExecutableRunner(includeTrailingPathDelimiter(pSettings->dirs().appDir())+"ConsolePauser.exe",newArguments,workDir);
240 execRunner->setShareMemoryId(sharedMemoryId);
241 } else {
242 execRunner = new ExecutableRunner(filename,arguments,workDir);
243 }
244#else
245 QString newArguments;
246 QString sharedMemoryId = "/r"+QUuid::createUuid().toString(QUuid::StringFormat::Id128);
247 if (consoleFlag!=0) {
248 QString consolePauserPath=includeTrailingPathDelimiter(pSettings->dirs().appLibexecDir())+"consolepauser";
249 if (!fileExists(consolePauserPath)) {
250 QMessageBox::critical(pMainWindow,
251 tr("Can't find Console Pauser"),
252 tr("Console Pauser \"%1\" doesn't exists!")
253 .arg(consolePauserPath));
254 return;
255
256 }
257 if (redirectInput) {
258 newArguments = QString(" -e \"%1\" %2 %3 \"%4\" \"%5\" %6")
259 .arg(consolePauserPath)
260 .arg(consoleFlag)
261 .arg(sharedMemoryId)
262 .arg(redirectInputFilename)
263 .arg(localizePath(filename))
264 .arg(arguments);
265 } else {
266 newArguments = QString(" -e \"%1\" %2 %3 \"%4\" %5")
267 .arg(consolePauserPath)
268 .arg(consoleFlag)
269 .arg(sharedMemoryId,localizePath(filename)).arg(arguments);
270 }
271 } else {
272 newArguments = QString(" -e \"%1\" %2")
273 .arg(localizePath(filename)).arg(arguments);
274 }
275 execRunner = new ExecutableRunner(pSettings->environment().terminalPath(),newArguments,workDir);
276 execRunner->setShareMemoryId(sharedMemoryId);
277#endif
278 execRunner->setStartConsole(true);
279 } else {
280 execRunner = new ExecutableRunner(filename,arguments,workDir);
281 }
282 if (redirectInput) {
283 execRunner->setRedirectInput(true);
284 execRunner->setRedirectInputFilename(redirectInputFilename);
285 }
286 execRunner->addBinDirs(binDirs);
287
288 execRunner->addBinDir(pSettings->dirs().appDir());
289
290 mRunner = execRunner;
291
292 connect(mRunner, &Runner::finished, this ,&CompilerManager::onRunnerTerminated);
293 connect(mRunner, &Runner::finished, mRunner ,&Runner::deleteLater);
294 connect(mRunner, &Runner::finished, pMainWindow ,&MainWindow::onRunFinished);
295 connect(mRunner, &Runner::pausingForFinish, pMainWindow ,&MainWindow::onRunPausingForFinish);
296 connect(mRunner, &Runner::pausingForFinish, this ,&CompilerManager::onRunnerPausing);
297 connect(mRunner, &Runner::runErrorOccurred, pMainWindow ,&MainWindow::onRunErrorOccured);
298 mRunner->start();
299}
300
301
302void CompilerManager::runProblem(const QString &filename, const QString &arguments, const QString &workDir, POJProblemCase problemCase)
303{
304 QMutexLocker locker(&mRunnerMutex);
305 doRunProblem(filename, arguments, workDir, QVector<POJProblemCase> {problemCase});
306
307}
308
309void CompilerManager::runProblem(const QString &filename, const QString &arguments, const QString &workDir, const QVector<POJProblemCase>& problemCases)
310{
311 QMutexLocker locker(&mRunnerMutex);
312 doRunProblem(filename, arguments, workDir, problemCases);
313}
314void CompilerManager::doRunProblem(const QString &filename, const QString &arguments, const QString &workDir, const QVector<POJProblemCase>& problemCases)
315{
316 if (mRunner!=nullptr) {
317 return;
318 }
319 OJProblemCasesRunner * execRunner = new OJProblemCasesRunner(filename,arguments,workDir,problemCases);
320 mRunner = execRunner;
321 if (pSettings->executor().enableCaseTimeout())
322 execRunner->setExecTimeout(pSettings->executor().caseTimeout());
323 connect(mRunner, &Runner::finished, this ,&CompilerManager::onRunnerTerminated);
324 connect(mRunner, &Runner::finished, mRunner ,&Runner::deleteLater);
325 connect(mRunner, &Runner::finished, pMainWindow ,&MainWindow::onRunProblemFinished);
326 connect(mRunner, &Runner::runErrorOccurred, pMainWindow ,&MainWindow::onRunErrorOccured);
327 connect(execRunner, &OJProblemCasesRunner::caseStarted, pMainWindow, &MainWindow::onOJProblemCaseStarted);
328 connect(execRunner, &OJProblemCasesRunner::caseFinished, pMainWindow, &MainWindow::onOJProblemCaseFinished);
329 connect(execRunner, &OJProblemCasesRunner::newOutputGetted, pMainWindow, &MainWindow::onOJProblemCaseNewOutputGetted);
330 connect(execRunner, &OJProblemCasesRunner::resetOutput, pMainWindow, &MainWindow::onOJProblemCaseResetOutput);
331 mRunner->start();
332}
333
334void CompilerManager::stopRun()
335{
336 QMutexLocker locker(&mRunnerMutex);
337 if (mRunner!=nullptr) {
338 mRunner->stop();
339 disconnect(mRunner, &Runner::finished, this ,&CompilerManager::onRunnerTerminated);
340 mRunner=nullptr;
341 }
342}
343
344void CompilerManager::stopAllRunners()
345{
346 emit signalStopAllRunners();
347}
348
349void CompilerManager::stopPausing()
350{
351 QMutexLocker locker(&mRunnerMutex);
352 if (mRunner!=nullptr && mRunner->pausing()) {
353 disconnect(mRunner, &Runner::finished, this ,&CompilerManager::onRunnerTerminated);
354 mRunner->stop();
355 mRunner=nullptr;
356 }
357}
358
359void CompilerManager::stopCompile()
360{
361 QMutexLocker locker(&mCompileMutex);
362 if (mCompiler!=nullptr)
363 mCompiler->stopCompile();
364}
365
366void CompilerManager::stopCheckSyntax()
367{
368 QMutexLocker locker(&mBackgroundSyntaxCheckMutex);
369 if (mBackgroundSyntaxChecker!=nullptr)
370 mBackgroundSyntaxChecker->stopCompile();
371}
372
373bool CompilerManager::canCompile(const QString &)
374{
375 return !compiling();
376}
377
378void CompilerManager::onCompileFinished()
379{
380 QMutexLocker locker(&mCompileMutex);
381 mCompiler=nullptr;
382 pMainWindow->onCompileFinished(false);
383}
384
385void CompilerManager::onRunnerTerminated()
386{
387 QMutexLocker locker(&mRunnerMutex);
388 mRunner=nullptr;
389}
390
391void CompilerManager::onRunnerPausing()
392{
393 QMutexLocker locker(&mRunnerMutex);
394 disconnect(mRunner, &Runner::finished, this ,&CompilerManager::onRunnerTerminated);
395 disconnect(mRunner, &Runner::finished, pMainWindow ,&MainWindow::onRunFinished);
396 disconnect(mRunner, &Runner::runErrorOccurred, pMainWindow ,&MainWindow::onRunErrorOccured);
397 connect(this, &CompilerManager::signalStopAllRunners, mRunner, &Runner::stop);
398 mRunner=nullptr;
399}
400
401void CompilerManager::onCompileIssue(PCompileIssue issue)
402{
403 if (issue->type == CompileIssueType::Error)
404 mCompileErrorCount++;
405 mCompileIssueCount++;
406}
407
408void CompilerManager::onSyntaxCheckFinished()
409{
410 QMutexLocker locker(&mBackgroundSyntaxCheckMutex);
411 mBackgroundSyntaxChecker=nullptr;
412 pMainWindow->onCompileFinished(true);
413}
414
415void CompilerManager::onSyntaxCheckIssue(PCompileIssue issue)
416{
417 if (issue->type == CompileIssueType::Error)
418 mSyntaxCheckErrorCount++;
419 if (issue->type == CompileIssueType::Error ||
420 issue->type == CompileIssueType::Warning)
421 mSyntaxCheckIssueCount++;
422}
423
424int CompilerManager::syntaxCheckIssueCount() const
425{
426 return mSyntaxCheckIssueCount;
427}
428
429int CompilerManager::compileIssueCount() const
430{
431 return mCompileIssueCount;
432}
433
434int CompilerManager::syntaxCheckErrorCount() const
435{
436 return mSyntaxCheckErrorCount;
437}
438
439int CompilerManager::compileErrorCount() const
440{
441 return mCompileErrorCount;
442}
443
444CompileError::CompileError(const QString &reason):BaseError(reason)
445{
446
447}
448
449