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 "compiler.h"
18#include "utils.h"
19#include "compilermanager.h"
20#include "../systemconsts.h"
21
22#include <QFileInfo>
23#include <QProcess>
24#include <QString>
25#include <QTextCodec>
26#include <QTime>
27#include <QApplication>
28#include "../editor.h"
29#include "../mainwindow.h"
30#include "../editorlist.h"
31#include "../parser/cppparser.h"
32#include "../autolinkmanager.h"
33#include "qt_utils/charsetinfo.h"
34#include "../project.h"
35
36#define COMPILE_PROCESS_END "---//END//----"
37
38Compiler::Compiler(const QString &filename, bool silent, bool onlyCheckSyntax):
39 QThread(),
40 mSilent(silent),
41 mOnlyCheckSyntax(onlyCheckSyntax),
42 mFilename(filename),
43 mRebuild(false)
44{
45
46}
47
48void Compiler::run()
49{
50 emit compileStarted();
51 auto action = finally([this]{
52 emit compileFinished();
53 });
54 try {
55 if (!prepareForCompile()){
56 return;
57 }
58 if (mRebuild && !prepareForRebuild()) {
59 throw CompileError(tr("Clean before rebuild failed."));
60 }
61 mErrorCount = 0;
62 mWarningCount = 0;
63 QElapsedTimer timer;
64 timer.start();
65 runCommand(mCompiler, mArguments, mDirectory, pipedText());
66 log("");
67 log(tr("Compile Result:"));
68 log("------------------");
69 log(tr("- Errors: %1").arg(mErrorCount));
70 log(tr("- Warnings: %1").arg(mWarningCount));
71 if (!mOutputFile.isEmpty()) {
72 log(tr("- Output Filename: %1").arg(mOutputFile));
73 QLocale locale = QLocale::system();
74 log(tr("- Output Size: %1").arg(locale.formattedDataSize(QFileInfo(mOutputFile).size())));
75 }
76 log(tr("- Compilation Time: %1 secs").arg(timer.elapsed() / 1000.0));
77 } catch (CompileError e) {
78 emit compileErrorOccured(e.reason());
79 }
80
81}
82
83QString Compiler::getFileNameFromOutputLine(QString &line) {
84 QString temp;
85 line = line.trimmed();
86 while (true) {
87 int pos;
88 if (line.length() > 2 && line[1]==':') { // full file path at start, ignore this ':'
89 pos = line.indexOf(':',2);
90 } else {
91 pos = line.indexOf(':');
92 }
93 if ( pos < 0) {
94 break;
95 }
96 temp = line.mid(0,pos);
97 line.remove(0,pos+1);
98 line=line.trimmed();
99 if (temp.compare("<stdin>", Qt::CaseInsensitive)==0 ) {
100 temp = mFilename;
101 break;
102 }
103
104 if (QFileInfo(temp).fileName() == QLatin1String("ld.exe")) { // skip ld.exe
105 continue;
106 } else if (QFileInfo(temp).suffix()=="o") { // skip obj file
107 continue;
108 } else {
109 break;
110 }
111 }
112 return temp;
113}
114
115int Compiler::getLineNumberFromOutputLine(QString &line)
116{
117 line = line.trimmed();
118 int pos = line.indexOf(':');
119 int result=0;
120 if (pos < 0) {
121 pos = line.indexOf(',');
122 }
123 if (pos>=0) {
124 result = line.midRef(0,pos).toInt();
125 if (result > 0)
126 line.remove(0,pos+1);
127 } else {
128 result = line.toInt();
129 if (result > 0)
130 line="";
131 }
132 return result;
133}
134
135int Compiler::getColunmnFromOutputLine(QString &line)
136{
137 line = line.trimmed();
138 int pos = line.indexOf(':');
139 int result=0;
140 if (pos < 0) {
141 pos = line.indexOf(',');
142 }
143 if (pos>=0) {
144 result = line.midRef(0,pos).toInt();
145 if (result > 0)
146 line.remove(0,pos+1);
147 }
148 return result;
149}
150
151CompileIssueType Compiler::getIssueTypeFromOutputLine(QString &line)
152{
153 CompileIssueType result = CompileIssueType::Other;
154 line = line.trimmed();
155 int pos = line.indexOf(':');
156 if (pos>=0) {
157 QString s=line.mid(0,pos);
158 if (s == "error" || s == "fatal error") {
159 mErrorCount += 1;
160 line = tr("[Error] ")+line.mid(pos+1);
161 result = CompileIssueType::Error;
162 } else if (s == "warning") {
163 mWarningCount += 1;
164 line = tr("[Warning] ")+line.mid(pos+1);
165 result = CompileIssueType::Warning;
166 } else if (s == "info") {
167 mWarningCount += 1;
168 line = tr("[Info] ")+line.mid(pos+1);
169 result = CompileIssueType::Info;
170 } else if (s == "note") {
171 mWarningCount += 1;
172 line = tr("[Note] ")+line.mid(pos+1);
173 result = CompileIssueType::Note;
174 }
175 }
176 return result;
177}
178
179Settings::PCompilerSet Compiler::compilerSet()
180{
181 if (mProject) {
182 int index = mProject->options().compilerSet;
183 Settings::PCompilerSet set = pSettings->compilerSets().getSet(index);
184 if (set)
185 return set;
186 }
187 return pSettings->compilerSets().defaultSet();
188}
189
190QByteArray Compiler::pipedText()
191{
192 return QByteArray();
193}
194
195void Compiler::processOutput(QString &line)
196{
197 if (line == COMPILE_PROCESS_END) {
198 if (mLastIssue) {
199 emit compileIssue(mLastIssue);
200 mLastIssue.reset();
201 }
202 return;
203 }
204 if (line.startsWith(">>>"))
205 line.remove(0,3);
206 QString referencePrefix = QString(" referenced by ");
207 if(mLastIssue && line.startsWith(referencePrefix)) {
208 line.remove(0,referencePrefix.length());
209 mLastIssue->filename = getFileNameFromOutputLine(line);
210 qDebug()<<line;
211 mLastIssue->line = getLineNumberFromOutputLine(line);
212 emit compileIssue(mLastIssue);
213 mLastIssue.reset();
214 return;
215 }
216 QString inFilePrefix = QString("In file included from ");
217 QString fromPrefix = QString("from ");
218 PCompileIssue issue = std::make_shared<CompileIssue>();
219 issue->type = CompileIssueType::Other;
220 issue->endColumn = -1;
221 if (line.startsWith(inFilePrefix)) {
222 line.remove(0,inFilePrefix.length());
223 issue->filename = getFileNameFromOutputLine(line);
224 issue->line = getLineNumberFromOutputLine(line);
225 if (issue->line > 0)
226 issue->column = getColunmnFromOutputLine(line);
227 issue->type = getIssueTypeFromOutputLine(line);
228 issue->description = inFilePrefix + issue->filename;
229 emit compileIssue(issue);
230 return;
231 } else if(line.startsWith(fromPrefix)) {
232 line.remove(0,fromPrefix.length());
233 issue->filename = getFileNameFromOutputLine(line);
234 issue->line = getLineNumberFromOutputLine(line);
235 if (issue->line > 0)
236 issue->column = getColunmnFromOutputLine(line);
237 issue->type = getIssueTypeFromOutputLine(line);
238 issue->description = " from " + issue->filename;
239 emit compileIssue(issue);
240 return;
241 }
242
243 // Ignore code snippets that GCC produces
244 // they always start with a space
245 if (line.length()>0 && line[0] == ' ') {
246 if (!mLastIssue)
247 return;
248 QString s = line.trimmed();
249 if (s.startsWith('|') && s.indexOf('^')) {
250 int pos = 0;
251 while (pos < s.length()) {
252 if (s[pos]=='^')
253 break;
254 pos++;
255 }
256 if (pos<s.length()) {
257 int i=pos+1;
258 while (i<s.length()) {
259 if (s[i]!='~' && s[i]!='^')
260 break;
261 i++;
262 }
263 mLastIssue->endColumn = mLastIssue->column+i-pos;
264 emit compileIssue(mLastIssue);
265 mLastIssue.reset();
266 }
267 }
268 return;
269 }
270
271 if (mLastIssue) {
272 emit compileIssue(mLastIssue);
273 mLastIssue.reset();
274 }
275
276 // assume regular main.cpp:line:col: message
277 issue->filename = getFileNameFromOutputLine(line);
278 issue->line = getLineNumberFromOutputLine(line);
279 if (issue->line > 0) {
280 issue->column = getColunmnFromOutputLine(line);
281 issue->type = getIssueTypeFromOutputLine(line);
282 if (issue->column<=0 && issue->type == CompileIssueType::Other) {
283 issue->type = CompileIssueType::Error; //linkage error
284 mErrorCount += 1;
285 }
286 } else {
287 issue->column = -1;
288 issue->type = getIssueTypeFromOutputLine(line);
289 }
290 issue->description = line.trimmed();
291 if (issue->line<=0 && (issue->filename=="ld" || issue->filename=="lld")) {
292 mLastIssue = issue;
293 } else if (issue->line<=0) {
294 emit compileIssue(issue);
295 } else
296 mLastIssue = issue;
297}
298
299void Compiler::stopCompile()
300{
301 mStop = true;
302}
303
304QString Compiler::getCharsetArgument(const QByteArray& encoding,FileType fileType, bool checkSyntax)
305{
306 QString result;
307 bool forceExecUTF8=false;
308 // test if force utf8 from autolink infos
309 if ((fileType == FileType::CSource ||
310 fileType == FileType::CppSource) && pSettings->editor().enableAutolink() ){
311 Editor* editor = pMainWindow->editorList()->getEditor();
312 if (editor) {
313 PCppParser parser = editor->parser();
314 if (parser) {
315 int waitCount = 0;
316 //wait parsing ends, at most 1 second
317 while(parser->parsing()) {
318 if (waitCount>10)
319 break;
320 waitCount++;
321 QThread::msleep(100);
322 QApplication *app=dynamic_cast<QApplication*>(
323 QApplication::instance());
324 app->processEvents();
325 }
326 QSet<QString> parsedFiles;
327 forceExecUTF8 = parseForceUTF8ForAutolink(
328 editor->filename(),
329 parsedFiles,
330 parser);
331 }
332 }
333
334 }
335 if ((forceExecUTF8 || compilerSet()->autoAddCharsetParams()) && encoding != ENCODING_ASCII
336 && compilerSet()->compilerType()!=COMPILER_CLANG) {
337 QString encodingName;
338 QString execEncodingName;
339 QString compilerSetExecCharset = compilerSet()->execCharset();
340 QString systemEncodingName=pCharsetInfoManager->getDefaultSystemEncoding();
341 if (encoding == ENCODING_SYSTEM_DEFAULT) {
342 encodingName = systemEncodingName;
343 } else if (encoding == ENCODING_UTF8_BOM) {
344 encodingName = "UTF-8";
345 } else {
346 encodingName = encoding;
347 }
348 if (forceExecUTF8) {
349 execEncodingName = "UTF-8";
350 } else if (compilerSetExecCharset == ENCODING_SYSTEM_DEFAULT || compilerSetExecCharset.isEmpty()) {
351 execEncodingName = systemEncodingName;
352 } else {
353 execEncodingName = compilerSetExecCharset;
354 }
355 qDebug()<<encodingName<<execEncodingName;
356 if (checkSyntax) {
357 result += QString(" -finput-charset=%1")
358 .arg(encodingName);
359 } else if (encodingName!=execEncodingName) {
360 result += QString(" -finput-charset=%1 -fexec-charset=%2")
361 .arg(encodingName, execEncodingName);
362 }
363 }
364 return result;
365}
366
367QString Compiler::getCCompileArguments(bool checkSyntax)
368{
369 QString result;
370 if (checkSyntax) {
371 result += " -fsyntax-only";
372 }
373
374 QMap<QString, QString> compileOptions;
375 if (mProject && !mProject->options().compilerOptions.isEmpty()) {
376 compileOptions = mProject->options().compilerOptions;
377 } else {
378 compileOptions = compilerSet()->compileOptions();
379 }
380 foreach (const QString& key, compileOptions.keys()) {
381 if (compileOptions[key]=="")
382 continue;
383 PCompilerOption pOption = CompilerInfoManager::getCompilerOption(compilerSet()->compilerType(), key);
384 if (pOption && pOption->isC && !pOption->isLinker) {
385 if (pOption->choices.isEmpty())
386 result += " " + pOption->setting;
387 else
388 result += " " + pOption->setting + compilerSet()->getCompileOptionValue(key);
389 }
390 }
391
392 if (compilerSet()->useCustomCompileParams() && !compilerSet()->customCompileParams().isEmpty()) {
393 QStringList params = textToLines(compilerSet()->customCompileParams());
394 foreach(const QString& param, params)
395 result += " "+ parseMacros(param);
396 }
397
398 if (mProject) {
399 QString s = mProject->options().compilerCmd;
400 if (!s.isEmpty()) {
401 s.replace("_@@_", " ");
402 QStringList params = textToLines(s);
403 foreach(const QString& param, params)
404 result += " "+ parseMacros(param);
405 }
406 }
407 return result;
408}
409
410QString Compiler::getCppCompileArguments(bool checkSyntax)
411{
412 QString result;
413 if (checkSyntax) {
414 result += " -fsyntax-only";
415 }
416 QMap<QString, QString> compileOptions;
417 if (mProject && !mProject->options().compilerOptions.isEmpty()) {
418 compileOptions = mProject->options().compilerOptions;
419 } else {
420 compileOptions = compilerSet()->compileOptions();
421 }
422 foreach (const QString& key, compileOptions.keys()) {
423 if (compileOptions[key]=="")
424 continue;
425 PCompilerOption pOption = CompilerInfoManager::getCompilerOption(compilerSet()->compilerType(), key);
426 if (pOption && pOption->isCpp && !pOption->isLinker) {
427 if (pOption->choices.isEmpty())
428 result += " " + pOption->setting;
429 else
430 result += " " + pOption->setting + compilerSet()->getCompileOptionValue(key);
431 }
432 }
433 if (compilerSet()->useCustomCompileParams() && !compilerSet()->customCompileParams().isEmpty()) {
434 QStringList params = textToLines(compilerSet()->customCompileParams());
435 foreach(const QString& param, params)
436 result += " "+ parseMacros(param);
437 }
438 if (mProject) {
439 QString s = mProject->options().cppCompilerCmd;
440 if (!s.isEmpty()) {
441 s.replace("_@@_", " ");
442 QStringList params = textToLines(s);
443 foreach(const QString& param, params)
444 result += " "+ parseMacros(param);
445 }
446 }
447 return result;
448}
449
450
451QString Compiler::getCIncludeArguments()
452{
453 QString result;
454 foreach (const QString& folder,compilerSet()->CIncludeDirs()) {
455 result += QString(" -I\"%1\"").arg(folder);
456 }
457 return result;
458}
459
460QString Compiler::getProjectIncludeArguments()
461{
462 QString result;
463 if (mProject) {
464 foreach (const QString& folder,mProject->options().includeDirs) {
465 result += QString(" -I\"%1\"").arg(folder);
466 }
467// result += QString(" -I\"%1\"").arg(extractFilePath(mProject->filename()));
468 }
469 return result;
470}
471
472QString Compiler::getCppIncludeArguments()
473{
474 QString result;
475 foreach (const QString& folder,compilerSet()->CppIncludeDirs()) {
476 result += QString(" -I\"%1\"").arg(folder);
477 }
478 return result;
479}
480
481QString Compiler::getLibraryArguments(FileType fileType)
482{
483 QString result;
484
485 //Add libraries
486 foreach (const QString& folder, compilerSet()->libDirs()) {
487 result += QString(" -L\"%1\"").arg(folder);
488 }
489
490 //add libs added via project
491 if (mProject) {
492 foreach (const QString& folder, mProject->options().libDirs){
493 result += QString(" -L\"%1\"").arg(folder);
494 }
495 }
496
497 //Add auto links
498 // is file and auto link enabled
499 if (pSettings->editor().enableAutolink() && (fileType == FileType::CSource ||
500 fileType == FileType::CppSource)){
501 Editor* editor = pMainWindow->editorList()->getEditor();
502 if (editor) {
503 PCppParser parser = editor->parser();
504 if (parser) {
505 int waitCount = 0;
506 //wait parsing ends, at most 1 second
507 while(parser->parsing()) {
508 if (waitCount>10)
509 break;
510 waitCount++;
511 QThread::msleep(100);
512 QApplication *app=dynamic_cast<QApplication*>(
513 QApplication::instance());
514 app->processEvents();
515 }
516 QSet<QString> parsedFiles;
517 result += parseFileIncludesForAutolink(
518 editor->filename(),
519 parsedFiles,
520 parser);
521 }
522 }
523
524 }
525
526 //add compiler set link options
527 //options like "-static" must be added after "-lxxx"
528 QMap<QString, QString> compileOptions;
529 if (mProject && !mProject->options().compilerOptions.isEmpty()) {
530 compileOptions = mProject->options().compilerOptions;
531 } else {
532 compileOptions = compilerSet()->compileOptions();
533 }
534 foreach (const QString& key, compileOptions.keys()) {
535 if (compileOptions[key]=="")
536 continue;
537 PCompilerOption pOption = CompilerInfoManager::getCompilerOption(compilerSet()->compilerType(), key);
538 if (pOption && pOption->isLinker) {
539 if (pOption->choices.isEmpty())
540 result += " " + pOption->setting;
541 else
542 result += " " + pOption->setting + compilerSet()->getCompileOptionValue(key);
543 }
544 }
545
546
547 // Add global compiler linker extras
548 if (compilerSet()->useCustomLinkParams() && !compilerSet()->customLinkParams().isEmpty()) {
549 QStringList params = textToLines(compilerSet()->customLinkParams());
550 if (!params.isEmpty()) {
551 foreach(const QString& param, params)
552 result += " " + param;
553 }
554 }
555
556 if (mProject) {
557 if (mProject->options().type == ProjectType::GUI) {
558 result += " -mwindows";
559 }
560
561 if (!mProject->options().linkerCmd.isEmpty()) {
562 QString s = mProject->options().linkerCmd;
563 if (!s.isEmpty()) {
564 s.replace("_@@_", " ");
565 QStringList params = textToLines(s);
566 if (!params.isEmpty()) {
567 foreach(const QString& param, params)
568 result += " " + param;
569 }
570 }
571 }
572 if (mProject->options().staticLink)
573 result += " -static";
574 } else if (compilerSet()->staticLink()) {
575 result += " -static";
576 }
577 return result;
578}
579
580QString Compiler::parseFileIncludesForAutolink(
581 const QString &filename,
582 QSet<QString>& parsedFiles,
583 PCppParser& parser)
584{
585 QString result;
586 if (parsedFiles.contains(filename))
587 return result;
588 parsedFiles.insert(filename);
589 PAutolink autolink = pAutolinkManager->getLink(filename);
590 if (autolink) {
591 result += ' '+autolink->linkOption;
592 }
593 QStringList includedFiles = parser->getFileDirectIncludes(filename);
594// log(QString("File %1 included:").arg(filename));
595// for (int i=includedFiles.size()-1;i>=0;i--) {
596// QString includeFilename = includedFiles[i];
597// log(includeFilename);
598// }
599
600 for (int i=includedFiles.size()-1;i>=0;i--) {
601 QString includeFilename = includedFiles[i];
602 result += parseFileIncludesForAutolink(
603 includeFilename,
604 parsedFiles,
605 parser);
606 }
607 return result;
608}
609
610bool Compiler::parseForceUTF8ForAutolink(const QString &filename, QSet<QString> &parsedFiles, PCppParser &parser)
611{
612 bool result;
613 if (parsedFiles.contains(filename))
614 return false;
615 parsedFiles.insert(filename);
616 PAutolink autolink = pAutolinkManager->getLink(filename);
617 if (autolink && autolink->execUseUTF8) {
618 return true;
619 }
620 QStringList includedFiles = parser->getFileDirectIncludes(filename);
621// log(QString("File %1 included:").arg(filename));
622// for (int i=includedFiles.size()-1;i>=0;i--) {
623// QString includeFilename = includedFiles[i];
624// log(includeFilename);
625// }
626
627 for (int i=includedFiles.size()-1;i>=0;i--) {
628 QString includeFilename = includedFiles[i];
629 result = parseForceUTF8ForAutolink(
630 includeFilename,
631 parsedFiles,
632 parser);
633 if (result)
634 return true;
635 }
636 return false;
637}
638
639void Compiler::runCommand(const QString &cmd, const QString &arguments, const QString &workingDir, const QByteArray& inputText)
640{
641 QProcess process;
642 mStop = false;
643 bool errorOccurred = false;
644 process.setProgram(cmd);
645 QString cmdDir = extractFileDir(cmd);
646 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
647 if (!cmdDir.isEmpty()) {
648 QString path = env.value("PATH");
649 if (path.isEmpty()) {
650 path = cmdDir;
651 } else {
652 path = cmdDir + PATH_SEPARATOR + path;
653 }
654 env.insert("PATH",path);
655 }
656 env.insert("LANG","en");
657 env.insert("LDFLAGS","-Wl,--stack,12582912");
658 env.insert("CFLAGS","");
659 env.insert("CXXFLAGS","");
660 process.setProcessEnvironment(env);
661 process.setArguments(splitProcessCommand(arguments));
662 process.setWorkingDirectory(workingDir);
663
664 process.connect(&process, &QProcess::errorOccurred,
665 [&](){
666 errorOccurred= true;
667 });
668 process.connect(&process, &QProcess::readyReadStandardError,[&process,this](){
669 if (compilerSet()->compilerType() == COMPILER_CLANG)
670 this->error(QString::fromUtf8(process.readAllStandardError()));
671 else
672 this->error(QString::fromLocal8Bit( process.readAllStandardError()));
673 });
674 process.connect(&process, &QProcess::readyReadStandardOutput,[&process,this](){
675 if (compilerSet()->compilerType() == COMPILER_CLANG)
676 this->log(QString::fromUtf8(process.readAllStandardOutput()));
677 else
678 this->log(QString::fromLocal8Bit( process.readAllStandardOutput()));
679 });
680 process.connect(&process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),[this](){
681 this->error(COMPILE_PROCESS_END);
682 });
683 process.start();
684 process.waitForStarted(5000);
685 if (!inputText.isEmpty()) {
686 process.write(inputText);
687 process.waitForFinished(0);
688 }
689 bool writeChannelClosed = false;
690 while (true) {
691 if (process.bytesToWrite()==0 && !writeChannelClosed ) {
692 writeChannelClosed=true;
693 process.closeWriteChannel();
694 }
695 process.waitForFinished(100);
696 if (process.state()!=QProcess::Running) {
697 break;
698 }
699 if (mStop) {
700 process.terminate();
701 }
702 if (errorOccurred)
703 break;
704 }
705 if (errorOccurred) {
706 switch (process.error()) {
707 case QProcess::FailedToStart:
708 throw CompileError(tr("The compiler process for '%1' failed to start.").arg(mFilename));
709 break;
710 case QProcess::Crashed:
711 if (!mStop)
712 throw CompileError(tr("The compiler process crashed after starting successfully."));
713 break;
714 case QProcess::Timedout:
715 throw CompileError(tr("The last waitFor...() function timed out."));
716 break;
717 case QProcess::WriteError:
718 throw CompileError(tr("An error occurred when attempting to write to the compiler process."));
719 break;
720 case QProcess::ReadError:
721 throw CompileError(tr("An error occurred when attempting to read from the compiler process."));
722 break;
723 default:
724 throw CompileError(tr("An unknown error occurred."));
725 }
726 }
727}
728
729const std::shared_ptr<Project> &Compiler::project() const
730{
731 return mProject;
732}
733
734void Compiler::setProject(const std::shared_ptr<Project> &newProject)
735{
736 mProject = newProject;
737}
738
739bool Compiler::isRebuild() const
740{
741 return mRebuild;
742}
743
744void Compiler::setRebuild(bool isRebuild)
745{
746 mRebuild = isRebuild;
747}
748
749void Compiler::log(const QString &msg)
750{
751 emit compileOutput(msg);
752}
753
754void Compiler::error(const QString &msg)
755{
756 if (msg != COMPILE_PROCESS_END)
757 emit compileOutput(msg);
758 for (QString& s:msg.split("\n")) {
759 if (!s.isEmpty())
760 processOutput(s);
761 }
762}
763