/*
* 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 "projectcompiler.h"
#include "../project.h"
#include "compilermanager.h"
#include "../systemconsts.h"
#include "qt_utils/charsetinfo.h"
#include "../editor.h"
#include "qt_utils/utils.h"
#include "utils.h"
#include "utils/escape.h"
#include "utils/parsearg.h"
#include
ProjectCompiler::ProjectCompiler(std::shared_ptr project):
Compiler("",false),
mOnlyClean(false)
{
setProject(project);
}
void ProjectCompiler::buildMakeFile()
{
//we are using custom make file, don't overwrite it
if (mProject->options().useCustomMakefile && !mProject->options().customMakefile.isEmpty())
return;
switch(mProject->options().type) {
case ProjectType::StaticLib:
createStaticMakeFile();
break;
case ProjectType::DynamicLib:
createDynamicMakeFile();
break;
default:
createStandardMakeFile();
}
}
void ProjectCompiler::createStandardMakeFile()
{
QFile file(mProject->makeFileName());
newMakeFile(file);
QString executable = extractRelativePath(mProject->makeFileName(), mProject->executable());
QString exeTarget = escapeFilenameForMakefileTarget(executable);
QString exeCommand = escapeArgumentForMakefileRecipe(executable, false);
writeln(file, exeTarget + ": $(OBJ)\n");
if (!mOnlyCheckSyntax) {
if (mProject->options().isCpp) {
writeln(file, "\t$(CXX) $(LINKOBJ) -o " + exeCommand + " $(LIBS)");
} else
writeln(file, "\t$(CC) $(LINKOBJ) -o " + exeCommand + " $(LIBS)");
}
writeMakeObjFilesRules(file);
}
void ProjectCompiler::createStaticMakeFile()
{
QFile file(mProject->makeFileName());
newMakeFile(file);
QString executable = extractRelativePath(mProject->makeFileName(), mProject->executable());
QString exeTarget = escapeFilenameForMakefileTarget(executable);
QString exeCommand = escapeArgumentForMakefileRecipe(executable, false);
writeln(file, exeTarget + ": $(OBJ)");
writeln(file, "\tar r " + exeCommand + " $(LINKOBJ)");
writeln(file, "\tranlib " + exeCommand);
writeMakeObjFilesRules(file);
}
void ProjectCompiler::createDynamicMakeFile()
{
QFile file(mProject->makeFileName());
newMakeFile(file);
QString executable = extractRelativePath(mProject->makeFileName(), mProject->executable());
QString exeTarget = escapeFilenameForMakefileTarget(executable);
QString exeCommand = escapeArgumentForMakefileRecipe(executable, false);
writeln(file, exeTarget + ": $(OBJ)");
if (mProject->options().isCpp) {
writeln(file, "\t$(CXX) -mdll $(LINKOBJ) -o " + exeCommand + " $(LIBS) -Wl,--output-def,$(DEF),--out-implib,$(STATIC)");
} else {
writeln(file, "\t$(CC) -mdll $(LINKOBJ) -o " + exeCommand + " $(LIBS) -Wl,--output-def,$(DEF),--out-implib,$(STATIC)");
}
writeMakeObjFilesRules(file);
}
void ProjectCompiler::newMakeFile(QFile& file)
{
// Create OBJ output directory
if (!mProject->options().objectOutput.isEmpty()) {
QDir(mProject->directory()).mkpath(mProject->options().objectOutput);
}
// Create executable output directory
if (!mProject->options().exeOutput.isEmpty()) {
QDir(mProject->directory()).mkpath(mProject->options().exeOutput);
}
// Write more information to the log file than before
log(tr("Building makefile..."));
log("--------");
log(tr("- Filename: %1").arg(mProject->makeFileName()));
// Create the actual file
if (!file.open(QFile::WriteOnly | QFile::Truncate))
throw CompileError(tr("Can't open '%1' for write!").arg(mProject->makeFileName()));
// Write header
writeMakeHeader(file);
// Writes definition list
writeMakeDefines(file);
// Write PHONY and all targets
writeMakeTarget(file);
// Write list of includes
writeMakeIncludes(file);
// Write clean command
writeMakeClean(file);
// PCH
if (mProject->options().usePrecompiledHeader
&& fileExists(mProject->options().precompiledHeader)) {
QString pchH = extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader);
QString pchHCommand = escapeArgumentForMakefileRecipe(pchH, false);
QString pch = extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader + "." GCH_EXT);
QString pchTarget = escapeFilenameForMakefileTarget(pch);
QString pchCommand = escapeArgumentForMakefileRecipe(pch, false);
writeln(file, pchTarget + ": $(PCH_H)");
writeln(file, "\t$(CXX) -c " + pchHCommand + " -o " + pchCommand + " $(CXXFLAGS)");
writeln(file);
}
}
void ProjectCompiler::writeMakeHeader(QFile &file)
{
writeln(file,"# Project: " + mProject->name());
writeln(file,QString("# Makefile created by Red Panda C++ ") + REDPANDA_CPP_VERSION);
writeln(file);
}
void ProjectCompiler::writeMakeDefines(QFile &file)
{
// Get list of object files
QStringList Objects;
QStringList LinkObjects;
QStringList cleanObjects;
// Create a list of object files
foreach(const PProjectUnit &unit, mProject->unitList()) {
if (!unit->compile() && !unit->link())
continue;
// Only process source files
QString RelativeName = extractRelativePath(mProject->directory(), unit->fileName());
FileType fileType = getFileType(RelativeName);
if (fileType == FileType::CSource || fileType == FileType::CppSource
|| fileType==FileType::GAS) {
if (!mProject->options().objectOutput.isEmpty()) {
// ofile = C:\MyProgram\obj\main.o
QString fullObjFile = includeTrailingPathDelimiter(mProject->options().objectOutput)
+ extractFileName(unit->fileName());
QString relativeObjFile = extractRelativePath(mProject->directory(), changeFileExt(fullObjFile, OBJ_EXT));
Objects << relativeObjFile;
cleanObjects << localizePath(relativeObjFile);
if (unit->link()) {
LinkObjects << relativeObjFile;
}
} else {
Objects << changeFileExt(RelativeName, OBJ_EXT);
cleanObjects << localizePath(changeFileExt(RelativeName, OBJ_EXT));
if (unit->link())
LinkObjects << changeFileExt(RelativeName, OBJ_EXT);
}
}
}
// Get windres file
QString objResFile;
QString cleanRes;
#ifdef Q_OS_WIN
if (!mProject->options().privateResource.isEmpty()) {
if (!mProject->options().objectOutput.isEmpty()) {
QString fullResFile = includeTrailingPathDelimiter(mProject->options().objectOutput) +
changeFileExt(mProject->options().privateResource, RES_EXT);
QString relativeResFile = extractRelativePath(mProject->directory(), fullResFile);
objResFile = relativeResFile;
cleanRes = localizePath(changeFileExt(relativeResFile, RES_EXT));
} else {
objResFile = changeFileExt(mProject->options().privateResource, RES_EXT);
cleanRes = localizePath(changeFileExt(mProject->options().privateResource, RES_EXT));
}
}
#endif
// Mention progress in the logs
if (!objResFile.isEmpty()) {
QString absolutePath = generateAbsolutePath(mProject->directory(),objResFile);
QString escaped = escapeArgumentForPlatformShell(absolutePath, false);
log(tr("- Resource File: %1").arg(escaped));
}
log("");
QString cxx = extractFileName(compilerSet()->cppCompiler());
QString cc = extractFileName(compilerSet()->CCompiler());
#ifdef Q_OS_WIN
QString windres = extractFileName(compilerSet()->resourceCompiler());
#endif
// Get list of applicable flags
QStringList cCompileArguments = getCCompileArguments(false);
QStringList cxxCompileArguments = getCppCompileArguments(false);
QStringList libraryArguments = getLibraryArguments(FileType::Project);
QStringList cIncludeArguments = getCIncludeArguments();
QStringList cxxIncludeArguments = getCppIncludeArguments();
#ifdef Q_OS_WIN
QStringList resourceArguments = parseArguments(mProject->options().resourceCmd, devCppMacroVariables(), true);
#endif
QString executable = extractRelativePath(mProject->makeFileName(), mProject->executable());
QString cleanExe = localizePath(executable);
QString pchH = extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader);
QString pch = extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader + "." GCH_EXT);
// programs
writeln(file, "CXX = " + escapeArgumentForMakefileVariableValue(cxx, true));
writeln(file, "CC = " + escapeArgumentForMakefileVariableValue(cc, true));
#ifdef Q_OS_WIN
writeln(file, "WINDRES = " + escapeArgumentForMakefileVariableValue(windres, true));
#endif
writeln(file, "RM = " CLEAN_PROGRAM);
// compiler flags
writeln(file, "LIBS = " + escapeArgumentsForMakefileVariableValue(libraryArguments));
writeln(file, "INCS = " + escapeArgumentsForMakefileVariableValue(cIncludeArguments));
writeln(file, "CXXINCS = " + escapeArgumentsForMakefileVariableValue(cxxIncludeArguments));
writeln(file, "CXXFLAGS = $(CXXINCS) " + escapeArgumentsForMakefileVariableValue(cxxCompileArguments));
writeln(file, "CFLAGS = $(INCS) " + escapeArgumentsForMakefileVariableValue(cCompileArguments));
#ifdef Q_OS_WIN
writeln(file, "WINDRESFLAGS = " + escapeArgumentsForMakefileVariableValue(resourceArguments));
#endif
// objects referenced in prerequisites
// do not use them in targets or command arguments, they have different escaping rules
if (!objResFile.isEmpty()) {
writeln(file, "RES = " + escapeFilenameForMakefilePrerequisite(objResFile));
writeln(file, "OBJ = " + escapeFilenamesForMakefilePrerequisite(Objects) + " $(RES)");
} else {
writeln(file, "OBJ = " + escapeFilenamesForMakefilePrerequisite(Objects));
};
writeln(file, "BIN = " + escapeFilenameForMakefilePrerequisite(executable));
if (mProject->options().usePrecompiledHeader
&& fileExists(mProject->options().precompiledHeader)){
writeln(file, "PCH_H = " + escapeFilenameForMakefilePrerequisite(pchH));
writeln(file, "PCH = " + escapeFilenameForMakefilePrerequisite(pch));
}
// object referenced in command arguments
// use them in targets or prerequisites, they have different escaping rules
if (!objResFile.isEmpty()) {
writeln(file, "LINKOBJ = " + escapeArgumentsForMakefileVariableValue(LinkObjects) + " " + escapeArgumentForMakefileVariableValue(objResFile, false));
writeln(file, "CLEANOBJ = " + escapeArgumentsForMakefileVariableValue(cleanObjects) + " " + escapeArgumentForMakefileVariableValue(cleanRes, false) + " " + escapeArgumentForMakefileVariableValue(cleanExe, false));
} else {
writeln(file, "LINKOBJ = " + escapeArgumentsForMakefileVariableValue(LinkObjects));
writeln(file, "CLEANOBJ = " + escapeArgumentsForMakefileVariableValue(cleanObjects) + " " + escapeArgumentForMakefileVariableValue(cleanExe, false));
};
// This needs to be put in before the clean command.
if (mProject->options().type == ProjectType::DynamicLib) {
QString outputFileDir = extractFilePath(mProject->executable());
QString outputFilename = extractFileName(mProject->executable());
QString libOutputFile;
if (!outputFilename.startsWith("lib")) {
libOutputFile = includeTrailingPathDelimiter(outputFileDir) + "lib" + outputFilename;
} else {
libOutputFile = includeTrailingPathDelimiter(outputFileDir) + outputFilename;
}
if (QFileInfo(libOutputFile).absoluteFilePath()
== mProject->directory())
libOutputFile = extractFileName(libOutputFile);
else
libOutputFile = extractRelativePath(mProject->makeFileName(), libOutputFile);
QString defFile = localizePath(changeFileExt(libOutputFile, DEF_EXT));
QString staticFile = localizePath(changeFileExt(libOutputFile, LIB_EXT));
writeln(file,"DEF = " + escapeArgumentForMakefileVariableValue(defFile, false));
writeln(file,"STATIC = " + escapeArgumentForMakefileVariableValue(staticFile, false));
}
writeln(file);
}
void ProjectCompiler::writeMakeTarget(QFile &file)
{
writeln(file, ".PHONY: all all-before all-after clean clean-custom");
writeln(file);
writeln(file, "all: all-before $(BIN) all-after");
writeln(file);
}
void ProjectCompiler::writeMakeIncludes(QFile &file)
{
foreach(const QString& s, mProject->options().makeIncludes) {
writeln(file, "include " + escapeFilenameForMakefileInclude(s));
}
if (!mProject->options().makeIncludes.isEmpty()) {
writeln(file);
}
}
void ProjectCompiler::writeMakeClean(QFile &file)
{
writeln(file, "clean: clean-custom");
QString target="$(CLEANOBJ)";
if (mProject->options().usePrecompiledHeader
&& fileExists(mProject->options().precompiledHeader)) {
QString pch = extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader + "." GCH_EXT);
target += ' ' + escapeArgumentForMakefileRecipe(pch, false);
}
if (mProject->options().type == ProjectType::DynamicLib) {
target +=" $(DEF) $(STATIC)";
}
writeln(file, QString("\t-$(RM) %1 > %2 2>&1").arg(target,NULL_FILE));
writeln(file);
}
void ProjectCompiler::writeMakeObjFilesRules(QFile &file)
{
PCppParser parser = mProject->cppParser();
QString precompileStr;
QList projectUnits=mProject->unitList();
foreach(const PProjectUnit &unit, projectUnits) {
if (!unit->compile())
continue;
FileType fileType = getFileType(unit->fileName());
// Only process source files
if (fileType!=FileType::CSource && fileType!=FileType::CppSource
&& fileType!=FileType::GAS)
continue;
QString shortFileName = extractRelativePath(mProject->makeFileName(),unit->fileName());
writeln(file);
QString objStr = escapeFilenameForMakefilePrerequisite(shortFileName);
// if we have scanned it, use scanned info
if (parser && parser->fileScanned(unit->fileName())) {
QSet fileIncludes = parser->getIncludedFiles(unit->fileName());
foreach(const PProjectUnit &unit2, projectUnits) {
if (unit2==unit)
continue;
if (fileIncludes.contains(unit2->fileName())) {
if (mProject->options().usePrecompiledHeader &&
unit2->fileName() == mProject->options().precompiledHeader)
precompileStr = " $(PCH) ";
else {
QString prereq = extractRelativePath(mProject->makeFileName(), unit2->fileName());
objStr = objStr + ' ' + escapeFilenameForMakefilePrerequisite(prereq);
}
}
}
} else {
foreach(const PProjectUnit &unit2, projectUnits) {
FileType fileType = getFileType(unit2->fileName());
if (fileType == FileType::CHeader || fileType==FileType::CppHeader) {
QString prereq = extractRelativePath(mProject->makeFileName(), unit2->fileName());
objStr = objStr + ' ' + escapeFilenameForMakefilePrerequisite(prereq);
}
}
}
QString objFileNameTarget;
QString objFileNameCommand;
if (!mProject->options().objectOutput.isEmpty()) {
QString fullObjname = includeTrailingPathDelimiter(mProject->options().objectOutput) +
extractFileName(unit->fileName());
QString objectFile = extractRelativePath(mProject->makeFileName(), changeFileExt(fullObjname, OBJ_EXT));
objFileNameTarget = escapeFilenameForMakefileTarget(objectFile);
objFileNameCommand = escapeArgumentForMakefileRecipe(objectFile, false);
} else {
QString objectFile = changeFileExt(shortFileName, OBJ_EXT);
objFileNameTarget = escapeFilenameForMakefileTarget(objectFile);
objFileNameCommand = escapeArgumentForMakefileRecipe(objectFile, false);
}
objStr = objFileNameTarget + ": " + objStr + precompileStr;
writeln(file,objStr);
// Write custom build command
if (unit->overrideBuildCmd() && !unit->buildCmd().isEmpty()) {
QString BuildCmd = unit->buildCmd();
BuildCmd.replace("", "\n\t");
writeln(file, '\t' + BuildCmd);
// Or roll our own
} else {
QString encodingStr;
if (compilerSet()->compilerType() != CompilerType::Clang && mProject->options().addCharset) {
QByteArray defaultSystemEncoding=pCharsetInfoManager->getDefaultSystemEncoding();
QByteArray encoding = mProject->options().execEncoding;
QByteArray targetEncoding;
QByteArray sourceEncoding;
if ( encoding == ENCODING_SYSTEM_DEFAULT || encoding.isEmpty()) {
targetEncoding = defaultSystemEncoding;
} else if (encoding == ENCODING_UTF8_BOM) {
targetEncoding = "UTF-8";
} else if (encoding == ENCODING_UTF16_BOM) {
targetEncoding = "UTF-16";
} else if (encoding == ENCODING_UTF32_BOM) {
targetEncoding = "UTF-32";
} else {
targetEncoding = encoding;
}
if (unit->realEncoding().isEmpty()) {
if (unit->encoding() == ENCODING_AUTO_DETECT) {
Editor* editor = mProject->unitEditor(unit);
if (editor && editor->fileEncoding()!=ENCODING_ASCII
&& editor->fileEncoding()!=targetEncoding) {
sourceEncoding = editor->fileEncoding();
} else {
sourceEncoding = targetEncoding;
}
} else if (unit->encoding()==ENCODING_PROJECT) {
sourceEncoding=mProject->options().encoding;
} else if (unit->encoding()==ENCODING_SYSTEM_DEFAULT) {
sourceEncoding = defaultSystemEncoding;
} else if (unit->encoding()!=ENCODING_ASCII && !unit->encoding().isEmpty()) {
sourceEncoding = unit->encoding();
} else {
sourceEncoding = targetEncoding;
}
} else if (unit->realEncoding()==ENCODING_ASCII) {
sourceEncoding = targetEncoding;
} else {
sourceEncoding = unit->realEncoding();
}
if (sourceEncoding==ENCODING_SYSTEM_DEFAULT)
sourceEncoding = defaultSystemEncoding;
if (sourceEncoding!=targetEncoding) {
encodingStr = QString(" -finput-charset=%1 -fexec-charset=%2")
.arg(QString(sourceEncoding),
QString(targetEncoding));
}
}
if (fileType==FileType::CSource || fileType==FileType::CppSource) {
if (unit->compileCpp())
writeln(file, "\t$(CXX) -c " + escapeArgumentForMakefileRecipe(shortFileName, false) + " -o " + objFileNameCommand + " $(CXXFLAGS) " + encodingStr);
else
writeln(file, "\t$(CC) -c " + escapeArgumentForMakefileRecipe(shortFileName, false) + " -o " + objFileNameCommand + " $(CFLAGS) " + encodingStr);
} else if (fileType==FileType::GAS) {
writeln(file, "\t$(CC) -c " + escapeArgumentForMakefileRecipe(shortFileName, false) + " -o " + objFileNameCommand + " $(CFLAGS) " + encodingStr);
}
}
}
#ifdef Q_OS_WIN
if (!mProject->options().privateResource.isEmpty()) {
// Concatenate all resource include directories
QString ResIncludes(" ");
for (int i=0;ioptions().resourceIncludes.count();i++) {
QString filename = mProject->options().resourceIncludes[i];
if (!filename.isEmpty())
ResIncludes += " --include-dir " + escapeArgumentForMakefileRecipe(filename, false);
}
QString resFiles;
// Concatenate all resource filenames (not created when syntax checking)
foreach(const PProjectUnit& unit, mProject->unitList()) {
if (getFileType(unit->fileName())!=FileType::WindowsResourceSource)
continue;
if (fileExists(unit->fileName())) {
QString ResFile = extractRelativePath(mProject->makeFileName(), unit->fileName());
resFiles = resFiles + escapeFilenameForMakefilePrerequisite(ResFile) + ' ';
}
}
// Determine resource output file
QString fullName;
if (!mProject->options().objectOutput.isEmpty()) {
fullName = includeTrailingPathDelimiter(mProject->options().objectOutput) +
changeFileExt(mProject->options().privateResource, RES_EXT);
} else {
fullName = changeFileExt(mProject->options().privateResource, RES_EXT);
}
QString objFile = extractRelativePath(mProject->filename(), fullName);
QString objFileNameCommand = escapeArgumentForMakefileRecipe(objFile, false);
QString objFileNameTarget = escapeFilenameForMakefileTarget(objFile);
QString privRes = extractRelativePath(mProject->filename(), mProject->options().privateResource);
QString privResNameCommand = escapeArgumentForMakefileRecipe(privRes, false);
QString privResNamePrereq = escapeFilenameForMakefilePrerequisite(privRes);
// Build final cmd
QString windresArgs;
if (mProject->getCompileOption(CC_CMD_OPT_POINTER_SIZE)=="32")
windresArgs = " -F pe-i386";
writeln(file);
writeln(file, objFileNameTarget + ": " + privResNamePrereq + ' ' + resFiles);
writeln(file, "\t$(WINDRES) -i " + privResNameCommand + windresArgs + " --input-format=rc -o " + objFileNameCommand + " -O coff $(WINDRESFLAGS)"
+ ResIncludes);
writeln(file);
}
#endif
}
void ProjectCompiler::writeln(QFile &file, const QString &s)
{
if (!s.isEmpty())
file.write(s.toLocal8Bit());
file.write("\n");
}
bool ProjectCompiler::onlyClean() const
{
return mOnlyClean;
}
void ProjectCompiler::setOnlyClean(bool newOnlyClean)
{
mOnlyClean = newOnlyClean;
}
bool ProjectCompiler::prepareForRebuild()
{
//we use make argument to clean
return true;
}
bool ProjectCompiler::prepareForCompile()
{
if (!mProject)
return false;
//initProgressForm();
log(tr("Compiling project changes..."));
log("--------");
log(tr("- Project Filename: %1").arg(mProject->filename()));
log(tr("- Compiler Set Name: %1").arg(compilerSet()->name()));
log("");
buildMakeFile();
mCompiler = compilerSet()->make();
if (!fileExists(mCompiler)) {
throw CompileError(
tr("Make program '%1' doesn't exists!").arg(mCompiler)
+"
"
+tr("Please check the \"program\" page of compiler settings."));
}
QString parallelParam;
if (mProject->options().allowParallelBuilding) {
if (mProject->options().parellelBuildingJobs==0) {
parallelParam = "--jobs";
} else {
parallelParam = QString("-j%1").arg(mProject->options().parellelBuildingJobs);
}
} else {
parallelParam = "-j1";
}
QString makefile = extractRelativePath(mProject->directory(), mProject->makeFileName());
QStringList cleanArgs{
"-f",
makefile,
"clean",
};
QStringList makeAllArgs{
parallelParam,
"-f",
makefile,
"all",
};
if (mOnlyClean) {
mArguments = cleanArgs;
} else if (mRebuild) {
mArguments = cleanArgs;
mExtraCompilersList << mCompiler;
mExtraOutputFilesList << "";
mExtraArgumentsList << makeAllArgs;
} else {
mArguments = makeAllArgs;
}
mDirectory = mProject->directory();
log(tr("Processing makefile:"));
log("--------");
log(tr("- makefile processer: %1").arg(mCompiler));
QString command = escapeCommandForLog(mCompiler, mArguments);
log(tr("- Command: %1").arg(command));
log("");
return true;
}