/* * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "compiler.h" #include "utils.h" #include "utils/escape.h" #include "utils/parsearg.h" #include "compilermanager.h" #include "../systemconsts.h" #include #include #include #include #include #include #include "../editor.h" #include "../mainwindow.h" #include "../editorlist.h" #include "../parser/cppparser.h" #include "../autolinkmanager.h" #include "qt_utils/charsetinfo.h" #include "../project.h" #define COMPILE_PROCESS_END "---//END//----" Compiler::Compiler(const QString &filename, bool onlyCheckSyntax): QThread{}, mOnlyCheckSyntax{onlyCheckSyntax}, mFilename{filename}, mRebuild{false}, mParserForFile{}, mForceEnglishOutput{false} { getParserForFile(filename); } void Compiler::run() { emit compileStarted(); auto action = finally([this]{ emit compileFinished(mFilename); }); try { if (!prepareForCompile()){ return; } if (mRebuild && !prepareForRebuild()) { throw CompileError(tr("Clean before rebuild failed.")); } mErrorCount = 0; mWarningCount = 0; QElapsedTimer timer; timer.start(); runCommand(mCompiler, mArguments, mDirectory, pipedText()); for(int i=0;i %2").arg(command, escapeArgumentForPlatformShell(mExtraOutputFilesList[i], false))); } runCommand(mExtraCompilersList[i],mExtraArgumentsList[i],mDirectory, pipedText(),mExtraOutputFilesList[i]); } log(""); log(tr("Compile Result:")); log("------------------"); log(tr("- Errors: %1").arg(mErrorCount)); log(tr("- Warnings: %1").arg(mWarningCount)); if (!mOutputFile.isEmpty()) { log(tr("- Output Filename: %1").arg(mOutputFile)); QLocale locale = QLocale::system(); log(tr("- Output Size: %1").arg(locale.formattedDataSize(QFileInfo(mOutputFile).size()))); } log(tr("- Compilation Time: %1 secs").arg(timer.elapsed() / 1000.0)); } catch (CompileError e) { emit compileErrorOccured(e.reason()); } } QString Compiler::getFileNameFromOutputLine(QString &line) { QString temp; line = line.trimmed(); while (true) { int pos; if (line.length() > 2 && line[1]==':') { // full file path at start, ignore this ':' pos = line.indexOf(':',2); } else { pos = line.indexOf(':'); } if ( pos < 0) { break; } temp = line.mid(0,pos); line.remove(0,pos+1); line=line.trimmed(); if (temp.compare("", Qt::CaseInsensitive)==0 ) { temp = mFilename; return temp; } else if (temp.compare("{standard input}", Qt::CaseInsensitive)==0 ) { temp = mFilename; return temp; } QFileInfo fileInfo(temp); if (fileInfo.fileName() == QLatin1String("ld.exe")) { // skip ld.exe continue; } else if (fileInfo.fileName() == QLatin1String("make")) { // skip make.exe continue; } else if (fileInfo.fileName() == QLatin1String("mingw32-make")) { // skip mingw32-make.exe continue; } else if (fileInfo.suffix()=="o") { // skip obj file continue; } else { break; } } if (!mDirectory.isEmpty()) { QFileInfo info(temp); return info.isRelative()?generateAbsolutePath(mDirectory,temp):cleanPath(temp); } return temp; } int Compiler::getLineNumberFromOutputLine(QString &line) { line = line.trimmed(); int pos = line.indexOf(':'); int result=0; if (pos < 0) { pos = line.indexOf(','); } if (pos>=0) { result = QStringView(line.constData(), pos).toInt(); if (result > 0) line.remove(0,pos+1); } else { result = line.toInt(); if (result > 0) line=""; } return result; } int Compiler::getColunmnFromOutputLine(QString &line) { line = line.trimmed(); int pos = line.indexOf(':'); int result=0; if (pos < 0) { pos = line.indexOf(','); } if (pos>=0) { result = QStringView(line.constData(), pos).toInt(); if (result > 0) line.remove(0,pos+1); } return result; } CompileIssueType Compiler::getIssueTypeFromOutputLine(QString &line) { CompileIssueType result = CompileIssueType::Other; line = line.trimmed(); if (line.startsWith(tr("error:"))) { mErrorCount += 1; line = tr("[Error] ")+line.mid(tr("error:").length()); result = CompileIssueType::Error; } else if (line.startsWith(tr("warning:"))) { mWarningCount += 1; line = tr("[Warning] ")+line.mid(tr("warning:").length()); result = CompileIssueType::Warning; } else { int pos = line.indexOf(':'); if (pos>=0) { QString s=line.mid(0,pos); if (s == "error" || s == "fatal error" || s == "syntax error") { mErrorCount += 1; line = tr("[Error] ")+line.mid(pos+1); result = CompileIssueType::Error; } else if (s.startsWith("warning") || s.startsWith(tr("warning"))) { mWarningCount += 1; line = tr("[Warning] ")+line.mid(pos+1); result = CompileIssueType::Warning; } else if (s == "info" || s == tr("info")) { mWarningCount += 1; line = tr("[Info] ")+line.mid(pos+1); result = CompileIssueType::Info; } else if (s == "note" || s == tr("note")) { mWarningCount += 1; line = tr("[Note] ")+line.mid(pos+1); result = CompileIssueType::Note; } } } return result; } Settings::PCompilerSet Compiler::compilerSet() { if (mProject) { int index = mProject->options().compilerSet; Settings::PCompilerSet set = pSettings->compilerSets().getSet(index); if (set) return set; } return pSettings->compilerSets().defaultSet(); } QByteArray Compiler::pipedText() { return QByteArray(); } bool Compiler::beforeRunExtraCommand(int /* idx */) { return true; } void Compiler::processOutput(QString &line) { if (line == COMPILE_PROCESS_END) { if (mLastIssue) { emit compileIssue(mLastIssue); mLastIssue.reset(); } return; } if (line.startsWith(">>>")) line.remove(0,3); QString referencePrefix = QString(" referenced by "); if(mLastIssue && line.startsWith(referencePrefix)) { line.remove(0,referencePrefix.length()); mLastIssue->filename = getFileNameFromOutputLine(line); //qDebug()<line = getLineNumberFromOutputLine(line); emit compileIssue(mLastIssue); mLastIssue.reset(); return; } QString inFilePrefix = QString("In file included from "); QString fromPrefix = QString("from "); PCompileIssue issue = std::make_shared(); issue->type = CompileIssueType::Other; issue->endColumn = -1; if (line.startsWith(inFilePrefix)) { line.remove(0,inFilePrefix.length()); issue->filename = getFileNameFromOutputLine(line); issue->line = getLineNumberFromOutputLine(line); if (issue->line > 0) issue->column = getColunmnFromOutputLine(line); issue->type = getIssueTypeFromOutputLine(line); issue->description = inFilePrefix + issue->filename; emit compileIssue(issue); return; } else if(line.startsWith(fromPrefix)) { line.remove(0,fromPrefix.length()); issue->filename = getFileNameFromOutputLine(line); issue->line = getLineNumberFromOutputLine(line); if (issue->line > 0) issue->column = getColunmnFromOutputLine(line); issue->type = getIssueTypeFromOutputLine(line); issue->description = " from " + issue->filename; emit compileIssue(issue); return; } // Ignore code snippets that GCC produces // they always start with a space if (line.length()>0 && line[0] == ' ') { if (!mLastIssue) return; QString s = line.trimmed(); if (s.startsWith('|') && s.indexOf('^')) { int pos = 0; while (pos < s.length()) { if (s[pos]=='^') break; pos++; } if (posendColumn = mLastIssue->column+i-pos; emit compileIssue(mLastIssue); mLastIssue.reset(); } } return; } if (mLastIssue) { emit compileIssue(mLastIssue); mLastIssue.reset(); } // assume regular main.cpp:line:col: message issue->filename = getFileNameFromOutputLine(line); issue->line = getLineNumberFromOutputLine(line); if (issue->line > 0) { issue->column = getColunmnFromOutputLine(line); issue->type = getIssueTypeFromOutputLine(line); if (issue->column<=0 && issue->type == CompileIssueType::Other) { issue->type = CompileIssueType::Error; //linkage error mErrorCount += 1; } } else { issue->column = -1; issue->type = getIssueTypeFromOutputLine(line); } issue->description = line.trimmed(); if (issue->line<=0 && (issue->filename=="ld" || issue->filename=="lld")) { mLastIssue = issue; } else if (issue->line<=0) { emit compileIssue(issue); } else mLastIssue = issue; } void Compiler::stopCompile() { mStop = true; } QStringList Compiler::getCharsetArgument(const QByteArray& encoding,FileType fileType, bool checkSyntax) { QStringList result; bool forceExecUTF8=false; // test if force utf8 from autolink infos if ((fileType == FileType::CSource || fileType == FileType::CppSource) && pSettings->editor().enableAutolink() && mParserForFile){ int waitCount = 0; //wait parsing ends, at most 1 second while(mParserForFile->parsing()) { if (waitCount>10) break; waitCount++; QThread::msleep(100); QApplication *app=dynamic_cast( QApplication::instance()); app->processEvents(); } if (waitCount<=10) { QSet parsedFiles; forceExecUTF8 = parseForceUTF8ForAutolink( mFilename, parsedFiles); } } if ((forceExecUTF8 || compilerSet()->autoAddCharsetParams()) && encoding != ENCODING_ASCII && compilerSet()->compilerType()!=CompilerType::Clang) { QString encodingName; QString execEncodingName; QString compilerSetExecCharset = compilerSet()->execCharset(); QString systemEncodingName=pCharsetInfoManager->getDefaultSystemEncoding(); if (encoding == ENCODING_SYSTEM_DEFAULT) { encodingName = systemEncodingName; } else if (encoding == ENCODING_UTF8_BOM) { encodingName = "UTF-8"; } else if (encoding == ENCODING_UTF16_BOM) { encodingName = "UTF-16"; } else if (encoding == ENCODING_UTF32_BOM) { encodingName = "UTF-32"; } else { encodingName = encoding; } if (forceExecUTF8) { execEncodingName = "UTF-8"; } else if (compilerSetExecCharset == ENCODING_SYSTEM_DEFAULT || compilerSetExecCharset.isEmpty()) { execEncodingName = systemEncodingName; } else { execEncodingName = compilerSetExecCharset; } //qDebug()< compileOptions; if (mProject && !mProject->options().compilerOptions.isEmpty()) { compileOptions = mProject->options().compilerOptions; } else { compileOptions = compilerSet()->compileOptions(); } foreach (const QString& key, compileOptions.keys()) { if (compileOptions[key]=="") continue; PCompilerOption pOption = CompilerInfoManager::getCompilerOption(compilerSet()->compilerType(), key); if (pOption && pOption->isC && !pOption->isLinker) { if (pOption->type == CompilerOptionType::Checkbox) result << pOption->setting; else if (pOption->type == CompilerOptionType::Input) result += {pOption->setting, compileOptions[key]}; else if (pOption->type == CompilerOptionType::Number) { bool ok; int val = compileOptions[key].toInt(&ok); if (ok && val>0) { val = pOption->scale * val; result += QString("%1%2").arg(pOption->setting).arg(val); } } else { result << pOption->setting + compileOptions[key]; } } } QMap macros = devCppMacroVariables(); if (compilerSet()->useCustomCompileParams() && !compilerSet()->customCompileParams().isEmpty()) { result << parseArguments(compilerSet()->customCompileParams(), macros, true); } if (mProject) { QString s = mProject->options().compilerCmd; if (!s.isEmpty()) { s.replace("_@@_", " "); result << parseArguments(s, macros, true); } } if (result.contains("-g3")) { result << "-D_DEBUG"; } return result; } QStringList Compiler::getCppCompileArguments(bool checkSyntax) { QStringList result; if (checkSyntax) { result << "-fsyntax-only"; } QMap compileOptions; if (mProject && !mProject->options().compilerOptions.isEmpty()) { compileOptions = mProject->options().compilerOptions; } else { compileOptions = compilerSet()->compileOptions(); } foreach (const QString& key, compileOptions.keys()) { if (compileOptions[key]=="") continue; PCompilerOption pOption = CompilerInfoManager::getCompilerOption(compilerSet()->compilerType(), key); if (pOption && pOption->isCpp && !pOption->isLinker) { if (pOption->type == CompilerOptionType::Checkbox) result << pOption->setting; else if (pOption->type == CompilerOptionType::Input) result += {pOption->setting, compileOptions[key]}; else if (pOption->type == CompilerOptionType::Number) { bool ok; int val = compileOptions[key].toInt(&ok); if (ok && val>0) { val = pOption->scale * val; result += QString("%1%2").arg(pOption->setting).arg(val); } } else { result << pOption->setting + compileOptions[key]; } } } QMap macros = devCppMacroVariables(); if (compilerSet()->useCustomCompileParams() && !compilerSet()->customCompileParams().isEmpty()) { result << parseArguments(compilerSet()->customCompileParams(), macros, true); } if (mProject) { QString s = mProject->options().cppCompilerCmd; if (!s.isEmpty()) { s.replace("_@@_", " "); result << parseArguments(s, macros, true); } } if (result.contains("-g3")) { result << "-D_DEBUG"; } return result; } QStringList Compiler::getCIncludeArguments() { QStringList result; foreach (const QString& folder,compilerSet()->CIncludeDirs()) { result << "-I" + folder; } return result; } QStringList Compiler::getProjectIncludeArguments() { QStringList result; if (mProject) { foreach (const QString& folder,mProject->options().includeDirs) { result << "-I" + folder; } // result += QString(" -I\"%1\"").arg(extractFilePath(mProject->filename())); } return result; } QStringList Compiler::getCppIncludeArguments() { QStringList result; foreach (const QString& folder,compilerSet()->CppIncludeDirs()) { result << "-I" + folder; } return result; } QStringList Compiler::getLibraryArguments(FileType fileType) { QStringList result; //Add libraries foreach (const QString& folder, compilerSet()->libDirs()) { result << "-L" + folder; } //add libs added via project if (mProject) { foreach (const QString& folder, mProject->options().libDirs){ result << "-L" + folder; } } //Add auto links // is file and auto link enabled if (pSettings->editor().enableAutolink() && (fileType == FileType::CSource || fileType == FileType::CppSource) && mParserForFile){ int waitCount = 0; //wait parsing ends, at most 1 second while(mParserForFile->parsing()) { if (waitCount>10) break; waitCount++; QThread::msleep(100); QApplication *app=dynamic_cast( QApplication::instance()); app->processEvents(); } if (waitCount<=10) { QSet parsedFiles; result += parseFileIncludesForAutolink(mFilename, parsedFiles); } } //add compiler set link options //options like "-static" must be added after "-lxxx" QMap compileOptions; if (mProject && !mProject->options().compilerOptions.isEmpty()) { compileOptions = mProject->options().compilerOptions; } else { compileOptions = compilerSet()->compileOptions(); } foreach (const QString& key, compileOptions.keys()) { if (compileOptions[key]=="") continue; PCompilerOption pOption = CompilerInfoManager::getCompilerOption(compilerSet()->compilerType(), key); if (pOption && pOption->isLinker) { if (pOption->type == CompilerOptionType::Checkbox) result << pOption->setting; else if (pOption->type == CompilerOptionType::Input) result += {pOption->setting, compileOptions[key]}; else if (pOption->type == CompilerOptionType::Number) { bool ok; int val = compileOptions[key].toInt(&ok); if (ok && val>0) { val = pOption->scale * val; result += QString("%1%2").arg(pOption->setting).arg(val); } } else { result << pOption->setting + compileOptions[key]; } } } // Add global compiler linker extras if (compilerSet()->useCustomLinkParams() && !compilerSet()->customLinkParams().isEmpty()) { QMap macros = devCppMacroVariables(); QStringList params = parseArguments(compilerSet()->customLinkParams(), macros, true); if (!params.isEmpty()) { foreach(const QString& param, params) result << param; } } if (mProject) { if (mProject->options().type == ProjectType::GUI) { result << "-mwindows"; } if (!mProject->options().linkerCmd.isEmpty()) { QString s = mProject->options().linkerCmd; if (!s.isEmpty()) { s.replace("_@@_", " "); // historical reason result += parseArguments(s, {}, true); } } if (mProject->options().staticLink) result << "-static"; } else { if (compilerSet()->staticLink()) { result << "-static"; } } return result; } QStringList Compiler::parseFileIncludesForAutolink( const QString &filename, QSet& parsedFiles) { QStringList result; if (parsedFiles.contains(filename)) return result; parsedFiles.insert(filename); PAutolink autolink = pAutolinkManager->getLink(filename); if (autolink) { result += parseArgumentsWithoutVariables(autolink->linkOption); } QStringList includedFiles = mParserForFile->getFileDirectIncludes(filename); // log(QString("File %1 included:").arg(filename)); // for (int i=includedFiles.size()-1;i>=0;i--) { // QString includeFilename = includedFiles[i]; // log(includeFilename); // } for (int i=includedFiles.size()-1;i>=0;i--) { QString includeFilename = includedFiles[i]; result += parseFileIncludesForAutolink(includeFilename, parsedFiles); } return result; } bool Compiler::parseForceUTF8ForAutolink(const QString &filename, QSet &parsedFiles) { bool result; if (parsedFiles.contains(filename)) return false; parsedFiles.insert(filename); PAutolink autolink = pAutolinkManager->getLink(filename); if (autolink && autolink->execUseUTF8) { return true; } QStringList includedFiles = mParserForFile->getFileDirectIncludes(filename); // log(QString("File %1 included:").arg(filename)); // for (int i=includedFiles.size()-1;i>=0;i--) { // QString includeFilename = includedFiles[i]; // log(includeFilename); // } for (int i=includedFiles.size()-1;i>=0;i--) { QString includeFilename = includedFiles[i]; result = parseForceUTF8ForAutolink( includeFilename, parsedFiles); if (result) return true; } return false; } void Compiler::runCommand(const QString &cmd, const QStringList &arguments, const QString &workingDir, const QByteArray& inputText, const QString& outputFile) { QProcess process; mStop = false; bool errorOccurred = false; process.setProgram(cmd); QString cmdDir = extractFileDir(cmd); bool compilerErrorUTF8=compilerSet()->isCompilerInfoUsingUTF8(); bool outputUTF8=compilerSet()->forceUTF8(); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); #ifdef Q_OS_WIN QStringList binDirs=compilerSet()->binDirs(); if (!cmdDir.isEmpty()) binDirs.insert(0, cmdDir); QString windir = env.value("windir"); binDirs.append(windir+"\\system32"); binDirs.append(windir); env.insert("PATH",binDirs.join(PATH_SEPARATOR)); #else if (!cmdDir.isEmpty()) { QString path = env.value("PATH"); if (path.isEmpty()) { path = cmdDir; } else { path = cmdDir + PATH_SEPARATOR + path; } env.insert("PATH",path); } #endif if (compilerSet() && compilerSet()->forceEnglishOutput()) env.insert("LANG","en"); //env.insert("LDFLAGS","-Wl,--stack,12582912"); env.insert("LDFLAGS",""); env.insert("CFLAGS",""); env.insert("CXXFLAGS",""); process.setProcessEnvironment(env); process.setArguments(arguments); process.setWorkingDirectory(workingDir); QFile output; if (!outputFile.isEmpty()) { output.setFileName(outputFile); if (!output.open(QFile::WriteOnly | QFile::Truncate)) { this->error(tr("Can't open file \"%1\" for write!")); return; }; } process.connect(&process, &QProcess::errorOccurred, [&](){ errorOccurred= true; }); process.connect(&process, &QProcess::readyReadStandardError,[&process,this,compilerErrorUTF8](){ if (compilerErrorUTF8) this->error(QString::fromUtf8(process.readAllStandardError())); else this->error(QString::fromLocal8Bit( process.readAllStandardError())); }); process.connect(&process, &QProcess::readyReadStandardOutput,[&process,this,outputUTF8,&outputFile,&output](){ if (!outputFile.isEmpty()) { output.write(process.readAllStandardOutput()); } else { if (outputUTF8) this->log(QString::fromUtf8(process.readAllStandardOutput())); else this->log(QString::fromLocal8Bit( process.readAllStandardOutput())); } }); process.connect(&process, QOverload::of(&QProcess::finished),[this](){ this->error(COMPILE_PROCESS_END); }); process.start(); process.waitForStarted(5000); if (!inputText.isEmpty()) { process.write(inputText); process.waitForFinished(0); } bool writeChannelClosed = false; while (true) { if (process.bytesToWrite()==0 && !writeChannelClosed ) { writeChannelClosed=true; process.closeWriteChannel(); } process.waitForFinished(100); if (process.state()!=QProcess::Running) { break; } if (mStop) { process.terminate(); } if (errorOccurred) break; } if (errorOccurred) { switch (process.error()) { case QProcess::FailedToStart: throw CompileError(tr("The compiler process for '%1' failed to start.").arg(mFilename)); break; case QProcess::Crashed: if (!mStop) throw CompileError(tr("The compiler process crashed after starting successfully.")); break; case QProcess::Timedout: throw CompileError(tr("The last waitFor...() function timed out.")); break; case QProcess::WriteError: throw CompileError(tr("An error occurred when attempting to write to the compiler process.")); break; case QProcess::ReadError: throw CompileError(tr("An error occurred when attempting to read from the compiler process.")); break; default: throw CompileError(tr("An unknown error occurred.")); } } if (!outputFile.isEmpty()) output.close(); } QString Compiler::escapeCommandForLog(const QString &cmd, const QStringList &arguments) { return escapeCommandForPlatformShell(extractFileName(cmd), arguments); } PCppParser Compiler::parser() const { return mParserForFile; } void Compiler::getParserForFile(const QString &filename) { FileType fileType = getFileType(filename); if (fileType == FileType::CSource || fileType == FileType::CppSource){ Editor* editor = pMainWindow->editorList()->getOpenedEditorByFilename(filename); if (editor && editor->parser()) { mParserForFile=editor->parser(); } } } const std::shared_ptr &Compiler::project() const { return mProject; } void Compiler::setProject(const std::shared_ptr &newProject) { mProject = newProject; } bool Compiler::isRebuild() const { return mRebuild; } void Compiler::setRebuild(bool isRebuild) { mRebuild = isRebuild; } QStringList Compiler::getReflextiveArguments() { QStringList result{ "-Wall", "-Werror", "-Wextra", "-pedantic", "-Wconversion" }; return result; } void Compiler::log(const QString &msg) { emit compileOutput(msg); } void Compiler::error(const QString &msg) { if (msg != COMPILE_PROCESS_END) emit compileOutput(msg); for (QString& s:msg.split("\n")) { if (!s.isEmpty()) processOutput(s); } }