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 "projectcompiler.h"
18#include "../project.h"
19#include "compilermanager.h"
20#include "../systemconsts.h"
21#include "qt_utils/charsetinfo.h"
22#include "../editor.h"
23
24#include <QDir>
25
26ProjectCompiler::ProjectCompiler(std::shared_ptr<Project> project, bool silent, bool onlyCheckSyntax):
27 Compiler("",silent,onlyCheckSyntax),
28 mOnlyClean(false)
29{
30 setProject(project);
31}
32
33void ProjectCompiler::buildMakeFile()
34{
35 //we are using custom make file, don't overwrite it
36 if (!mProject->options().customMakefile.isEmpty())
37 return;
38
39 switch(mProject->options().type) {
40 case ProjectType::StaticLib:
41 createStaticMakeFile();
42 break;
43 case ProjectType::DynamicLib:
44 createDynamicMakeFile();
45 break;
46 default:
47 createStandardMakeFile();
48 }
49}
50
51void ProjectCompiler::createStandardMakeFile()
52{
53 QFile file(mProject->makeFileName());
54 newMakeFile(file);
55 file.write("$(BIN): $(OBJ)\n");
56 if (!mOnlyCheckSyntax) {
57 if (mProject->options().isCpp) {
58 writeln(file,"\t$(CPP) $(LINKOBJ) -o $(BIN) $(LIBS)");
59 } else
60 writeln(file,"\t$(CC) $(LINKOBJ) -o $(BIN) $(LIBS)");
61 }
62 writeMakeObjFilesRules(file);
63}
64
65void ProjectCompiler::createStaticMakeFile()
66{
67 QFile file(mProject->makeFileName());
68 newMakeFile(file);
69 writeln(file,"$(BIN): $(LINKOBJ)");
70 if (!mOnlyCheckSyntax) {
71 writeln(file,"\tar r $(BIN) $(LINKOBJ)");
72 writeln(file,"\tranlib $(BIN)");
73 }
74 writeMakeObjFilesRules(file);
75}
76
77void ProjectCompiler::createDynamicMakeFile()
78{
79 QFile file(mProject->makeFileName());
80 newMakeFile(file);
81 writeln(file,"$(BIN): $(LINKOBJ)");
82 if (!mOnlyCheckSyntax) {
83 if (mProject->options().isCpp) {
84 file.write("\t$(CPP) -mdll $(LINKOBJ) -o $(BIN) $(LIBS) -Wl,--output-def,$(DEF),--out-implib,$(STATIC)");
85 } else {
86 file.write("\t$(CC) -mdll $(LINKOBJ) -o $(BIN) $(LIBS) -Wl,--output-def,$(DEF),--out-implib,$(STATIC)");
87 }
88 }
89 writeMakeObjFilesRules(file);
90}
91
92void ProjectCompiler::newMakeFile(QFile& file)
93{
94 // Create OBJ output directory
95 if (!mProject->options().objectOutput.isEmpty()) {
96 QDir(mProject->directory()).mkpath(mProject->options().objectOutput);
97 }
98
99 // Write more information to the log file than before
100 log(tr("Building makefile..."));
101 log("--------");
102 log(tr("- Filename: %1").arg(mProject->makeFileName()));
103
104 // Create the actual file
105 if (!file.open(QFile::WriteOnly | QFile::Truncate))
106 throw CompileError(tr("Can't open '%1' for write!").arg(mProject->makeFileName()));
107
108 // Write header
109 writeMakeHeader(file);
110
111 // Writes definition list
112 writeMakeDefines(file);
113
114 // Write PHONY and all targets
115 writeMakeTarget(file);
116
117 // Write list of includes
118 writeMakeIncludes(file);
119
120 // Write clean command
121 writeMakeClean(file);
122}
123
124void ProjectCompiler::writeMakeHeader(QFile &file)
125{
126 writeln(file,"# Project: " + mProject->name());
127 writeln(file,QString("# Makefile created by Red Panda C++ ") + REDPANDA_CPP_VERSION);
128 writeln(file);
129 if (mOnlyCheckSyntax) {
130 writeln(file,"# This Makefile is written for syntax check!");
131 writeln(file,"# Regenerate it if you want to use this Makefile to build.");
132 writeln(file);
133 }
134}
135
136void ProjectCompiler::writeMakeDefines(QFile &file)
137{
138 // Get list of object files
139 QString Objects;
140 QString LinkObjects;
141 QString cleanObjects;
142
143 // Create a list of object files
144 for (int i=0;i<mProject->units().count();i++) {
145 PProjectUnit unit = mProject->units()[i];
146 if (!unit->compile() && !unit->link())
147 continue;
148
149 // Only process source files
150 QString RelativeName = extractRelativePath(mProject->directory(), unit->fileName());
151 FileType fileType = getFileType(RelativeName);
152 if (fileType == FileType::CSource || fileType == FileType::CppSource) {
153 if (!mProject->options().objectOutput.isEmpty()) {
154 // ofile = C:\MyProgram\obj\main.o
155 QString fullObjFile = includeTrailingPathDelimiter(mProject->options().objectOutput)
156 + extractFileName(unit->fileName());
157 QString relativeObjFile = extractRelativePath(mProject->directory(), changeFileExt(fullObjFile, OBJ_EXT));
158 QString ObjFile = genMakePath2(relativeObjFile);
159 Objects += ' ' + ObjFile;
160#ifdef Q_OS_WIN
161 cleanObjects += ' ' + genMakePath1(relativeObjFile).replace("/",QDir::separator());
162#else
163 cleanObjects += ' ' + genMakePath1(relativeObjFile);
164#endif
165 if (unit->link()) {
166 LinkObjects += ' ' + genMakePath1(relativeObjFile);
167 }
168 } else {
169 Objects += ' ' + genMakePath2(changeFileExt(RelativeName, OBJ_EXT));
170#ifdef Q_OS_WIN
171 cleanObjects += ' ' + genMakePath1(changeFileExt(RelativeName, OBJ_EXT)).replace("/",QDir::separator());
172#else
173 cleanObjects += ' ' + genMakePath1(changeFileExt(RelativeName, OBJ_EXT));
174#endif
175 if (unit->link())
176 LinkObjects = LinkObjects + ' ' + genMakePath1(changeFileExt(RelativeName, OBJ_EXT));
177 }
178 }
179 }
180
181 Objects = Objects.trimmed();
182 LinkObjects = LinkObjects.trimmed();
183
184 // Get windres file
185 QString ObjResFile;
186#ifdef Q_OS_WIN
187 if (!mProject->options().privateResource.isEmpty()) {
188 if (!mProject->options().objectOutput.isEmpty()) {
189 ObjResFile = includeTrailingPathDelimiter(mProject->options().objectOutput) +
190 changeFileExt(mProject->options().privateResource, RES_EXT);
191 } else
192 ObjResFile = changeFileExt(mProject->options().privateResource, RES_EXT);
193 }
194#endif
195
196 // Mention progress in the logs
197 if (!ObjResFile.isEmpty()) {
198 log(tr("- Resource File: %1").arg(QDir(mProject->directory()).absoluteFilePath(ObjResFile)));
199 }
200 log("");
201
202 // Get list of applicable flags
203 QString cCompileArguments = getCCompileArguments(mOnlyCheckSyntax);
204 QString cppCompileArguments = getCppCompileArguments(mOnlyCheckSyntax);
205 QString libraryArguments = getLibraryArguments(FileType::Project);
206 QString cIncludeArguments = getCIncludeArguments() + " " + getProjectIncludeArguments();
207 QString cppIncludeArguments = getCppIncludeArguments() + " " +getProjectIncludeArguments();
208
209 if (cCompileArguments.indexOf(" -g3")>=0
210 || cCompileArguments.startsWith("-g3")) {
211 cCompileArguments += " -D__DEBUG__";
212 cppCompileArguments+= " -D__DEBUG__";
213 }
214 writeln(file,"CPP = " + extractFileName(compilerSet()->cppCompiler()));
215 writeln(file,"CC = " + extractFileName(compilerSet()->CCompiler()));
216#ifdef Q_OS_WIN
217 writeln(file,"WINDRES = " + extractFileName(compilerSet()->resourceCompiler()));
218#endif
219 if (!ObjResFile.isEmpty()) {
220 writeln(file,"RES = " + genMakePath1(ObjResFile));
221 writeln(file,"OBJ = " + Objects + " $(RES)");
222 writeln(file,"LINKOBJ = " + LinkObjects + " $(RES)");
223#ifdef Q_OS_WIN
224 writeln(file,"CLEANOBJ = " + cleanObjects +
225 " " + genMakePath1(ObjResFile).replace("/",QDir::separator())
226 + " " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())).replace("/",QDir::separator()) );
227#else
228 writeln(file,"CLEANOBJ = " + cleanObjects +
229 " " + genMakePath1(ObjResFile)
230 + " " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())));
231#endif
232 } else {
233 writeln(file,"OBJ = " + Objects);
234 writeln(file,"LINKOBJ = " + LinkObjects);
235#ifdef Q_OS_WIN
236 writeln(file,"CLEANOBJ = " + cleanObjects +
237 + " " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())).replace("/",QDir::separator()) );
238#else
239 writeln(file,"CLEANOBJ = " + cleanObjects +
240 + " " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())));
241#endif
242 };
243 libraryArguments.replace('\\', '/');
244 writeln(file,"LIBS = " + libraryArguments);
245 cIncludeArguments.replace('\\', '/');
246 writeln(file,"INCS = " + cIncludeArguments);
247 cppIncludeArguments.replace('\\', '/');
248 writeln(file,"CXXINCS = " + cppIncludeArguments);
249 writeln(file,"BIN = " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())));
250 cppCompileArguments.replace('\\', '/');
251 writeln(file,"CXXFLAGS = $(CXXINCS) " + cppCompileArguments);
252 //writeln(file,"ENCODINGS = -finput-charset=utf-8 -fexec-charset='+GetSystemCharsetName);
253 cCompileArguments.replace('\\', '/');
254 writeln(file,"CFLAGS = $(INCS) " + cCompileArguments);
255 writeln(file, QString("RM = ") + CLEAN_PROGRAM );
256 if (mProject->options().usePrecompiledHeader){
257 writeln(file,"PCH_H = " + mProject->options().precompiledHeader );
258 writeln(file,"PCH = " + changeFileExt(mProject->options().precompiledHeader, GCH_EXT));
259 }
260
261
262 // This needs to be put in before the clean command.
263 if (mProject->options().type == ProjectType::DynamicLib) {
264 QString OutputFileDir = extractFilePath(mProject->executable());
265 QString libOutputFile = includeTrailingPathDelimiter(OutputFileDir) + "lib" + extractFileName(mProject->executable());
266 if (QFileInfo(libOutputFile).absoluteFilePath()
267 == mProject->directory())
268 libOutputFile = extractFileName(libOutputFile);
269 else
270 libOutputFile = extractRelativePath(mProject->makeFileName(), libOutputFile);
271 writeln(file,"DEF = " + genMakePath1(changeFileExt(libOutputFile, DEF_EXT)));
272 writeln(file,"STATIC = " + genMakePath1(changeFileExt(libOutputFile, LIB_EXT)));
273#ifdef Q_OS_WIN
274 writeln(file,"CLEAN_DEF = " + genMakePath1(changeFileExt(libOutputFile, DEF_EXT)).replace("/",QDir::separator()));
275 writeln(file,"CLEAN_STATIC = " + genMakePath1(changeFileExt(libOutputFile, LIB_EXT)).replace("/",QDir::separator()));
276#else
277 writeln(file,"CLEAN_DEF = " + genMakePath1(changeFileExt(libOutputFile, DEF_EXT)));
278 writeln(file,"CLEAN_STATIC = " + genMakePath1(changeFileExt(libOutputFile, LIB_EXT)));
279#endif
280 }
281 writeln(file);
282}
283
284void ProjectCompiler::writeMakeTarget(QFile &file)
285{
286 if (mOnlyCheckSyntax)
287 writeln(file, ".PHONY: all all-before all-after clean clean-custom $(OBJ) $(BIN)");
288 else
289 writeln(file, ".PHONY: all all-before all-after clean clean-custom");
290 writeln(file);
291 writeln(file, "all: all-before $(BIN) all-after");
292 writeln(file);
293 if (mProject->options().usePrecompiledHeader) {
294 writeln(file, "$(PCH) : $(PCH_H)");
295 writeln(file, " $(CPP) -x c++-header \"$(PCH_H)\" -o \"$(PCH)\" $(CXXFLAGS)");
296 writeln(file);
297 }
298}
299
300void ProjectCompiler::writeMakeIncludes(QFile &file)
301{
302 foreach(const QString& s, mProject->options().makeIncludes) {
303 writeln(file, "include " + genMakePath1(s));
304 }
305 if (!mProject->options().makeIncludes.isEmpty()) {
306 writeln(file);
307 }
308}
309
310void ProjectCompiler::writeMakeClean(QFile &file)
311{
312 writeln(file, "clean: clean-custom");
313 if (mProject->options().type == ProjectType::DynamicLib)
314 writeln(file, QString("\t${RM} $(CLEANOBJ) $(CLEAN_DEF) $(CLEAN_STATIC) > %1 2>&1").arg(NULL_FILE));
315 else
316 writeln(file, QString("\t${RM} $(CLEANOBJ) > %1 2>&1").arg(NULL_FILE));
317 writeln(file);
318}
319
320void ProjectCompiler::writeMakeObjFilesRules(QFile &file)
321{
322 PCppParser parser = mProject->cppParser();
323 QString precompileStr;
324 if (mProject->options().usePrecompiledHeader)
325 precompileStr = " $(PCH) ";
326
327 for (int i = 0;i<mProject->units().count();i++) {
328 PProjectUnit unit = mProject->units()[i];
329 FileType fileType = getFileType(unit->fileName());
330 // Only process source files
331 if (fileType!=FileType::CSource && fileType!=FileType::CppSource)
332 continue;
333
334 QString shortFileName = extractRelativePath(mProject->makeFileName(),unit->fileName());
335
336 writeln(file);
337 QString objStr=genMakePath2(shortFileName);
338 // if we have scanned it, use scanned info
339 if (parser && parser->scannedFiles().contains(unit->fileName())) {
340 QSet<QString> fileIncludes = parser->getFileIncludes(unit->fileName());
341 foreach (const QString& headerName, fileIncludes) {
342 if (headerName == unit->fileName())
343 continue;
344 if (!parser->isSystemHeaderFile(headerName)
345 && ! parser->isProjectHeaderFile(headerName)) {
346 for (int j = 0;j<mProject->units().count();j++) {
347 PProjectUnit unit2 = mProject->units()[j];
348 if (unit2->fileName()==headerName) {
349 objStr = objStr + ' ' + genMakePath2(extractRelativePath(mProject->makeFileName(),headerName));
350 break;
351 }
352 }
353 }
354 }
355 } else {
356 foreach (const PProjectUnit& u, mProject->units()) {
357 FileType fileType = getFileType(u->fileName());
358 if (fileType == FileType::CHeader || fileType==FileType::CppHeader)
359 objStr = objStr + ' ' + genMakePath2(extractRelativePath(mProject->makeFileName(),u->fileName()));
360 }
361 }
362 QString ObjFileName;
363 QString ObjFileName2;
364 if (!mProject->options().objectOutput.isEmpty()) {
365 ObjFileName = includeTrailingPathDelimiter(mProject->options().objectOutput) +
366 extractFileName(unit->fileName());
367 ObjFileName = genMakePath2(extractRelativePath(mProject->makeFileName(), changeFileExt(ObjFileName, OBJ_EXT)));
368 ObjFileName2 = genMakePath1(extractRelativePath(mProject->makeFileName(), changeFileExt(ObjFileName, OBJ_EXT)));
369// if (!extractFileDir(ObjFileName).isEmpty()) {
370// objStr = genMakePath2(includeTrailingPathDelimiter(extractFileDir(ObjFileName))) + objStr;
371// }
372 } else {
373 ObjFileName = genMakePath2(changeFileExt(shortFileName, OBJ_EXT));
374 ObjFileName2 = genMakePath1(changeFileExt(shortFileName, OBJ_EXT));
375 }
376
377 objStr = ObjFileName + ": "+objStr+precompileStr;
378
379 writeln(file,objStr);
380
381 // Write custom build command
382 if (unit->overrideBuildCmd() && !unit->buildCmd().isEmpty()) {
383 QString BuildCmd = unit->buildCmd();
384 BuildCmd.replace("<CRTAB>", "\n\t");
385 writeln(file, '\t' + BuildCmd);
386 // Or roll our own
387 } else {
388 QString encodingStr;
389 if (compilerSet()->compilerType() != COMPILER_CLANG && mProject->options().addCharset) {
390 QByteArray defaultSystemEncoding=pCharsetInfoManager->getDefaultSystemEncoding();
391 QByteArray encoding = mProject->options().execEncoding;
392 QByteArray targetEncoding;
393 QByteArray sourceEncoding;
394 if ( encoding == ENCODING_SYSTEM_DEFAULT || encoding.isEmpty()) {
395 targetEncoding = defaultSystemEncoding;
396 } else if (encoding == ENCODING_UTF8_BOM) {
397 targetEncoding = "UTF-8";
398 } else {
399 targetEncoding = encoding;
400 }
401
402 if (unit->encoding() == ENCODING_AUTO_DETECT) {
403 Editor* editor = mProject->unitEditor(unit);
404 if (editor && editor->fileEncoding()!=ENCODING_ASCII
405 && editor->fileEncoding()!=targetEncoding) {
406 sourceEncoding = editor->fileEncoding();
407 } else {
408 sourceEncoding = targetEncoding;
409 }
410 } else if (unit->encoding()==ENCODING_SYSTEM_DEFAULT) {
411 sourceEncoding = defaultSystemEncoding;
412 } else if (unit->encoding()!=ENCODING_ASCII && !unit->encoding().isEmpty()
413 && unit->encoding()!=targetEncoding) {
414 sourceEncoding = unit->encoding();
415 }
416
417 if (sourceEncoding!=targetEncoding) {
418 encodingStr = QString(" -finput-charset=%1 -fexec-charset=%2")
419 .arg(QString(sourceEncoding),
420 QString(targetEncoding));
421 }
422 }
423
424 if (mOnlyCheckSyntax) {
425 if (unit->compileCpp())
426 writeln(file, "\t$(CPP) -c " + genMakePath1(unit->fileName()) + " $(CXXFLAGS) " + encodingStr);
427 else
428 writeln(file, "\t(CC) -c " + genMakePath1(unit->fileName()) + " $(CFLAGS) " + encodingStr);
429 } else {
430 if (unit->compileCpp())
431 writeln(file, "\t$(CPP) -c " + genMakePath1(unit->fileName()) + " -o " + ObjFileName2 + " $(CXXFLAGS) " + encodingStr);
432 else
433 writeln(file, "\t$(CC) -c " + genMakePath1(unit->fileName()) + " -o " + ObjFileName2 + " $(CFLAGS) " + encodingStr);
434 }
435 }
436 }
437
438#ifdef Q_OS_WIN
439 if (!mProject->options().privateResource.isEmpty()) {
440
441 // Concatenate all resource include directories
442 QString ResIncludes(" ");
443 for (int i=0;i<mProject->options().resourceIncludes.count();i++) {
444 QString filename = mProject->options().resourceIncludes[i];
445 if (!filename.isEmpty())
446 ResIncludes = ResIncludes + " --include-dir " + genMakePath1(filename);
447 }
448
449 QString ResFiles;
450 // Concatenate all resource filenames (not created when syntax checking)
451 if (!mOnlyCheckSyntax) {
452 foreach(const PProjectUnit& unit, mProject->units()) {
453 if (getFileType(unit->fileName())!=FileType::WindowsResourceSource)
454 continue;
455 if (fileExists(unit->fileName())) {
456 QString ResFile = extractRelativePath(mProject->makeFileName(), unit->fileName());
457 ResFiles = ResFiles + genMakePath2(ResFile) + ' ';
458 }
459 }
460 ResFiles = ResFiles.trimmed();
461 }
462
463 // Determine resource output file
464 QString ObjFileName;
465 if (!mProject->options().objectOutput.isEmpty()) {
466 ObjFileName = includeTrailingPathDelimiter(mProject->options().objectOutput) +
467 changeFileExt(mProject->options().privateResource, RES_EXT);
468 } else {
469 ObjFileName = changeFileExt(mProject->options().privateResource, RES_EXT);
470 }
471 ObjFileName = genMakePath1(extractRelativePath(mProject->filename(), ObjFileName));
472 QString PrivResName = genMakePath1(extractRelativePath(mProject->filename(), mProject->options().privateResource));
473
474 // Build final cmd
475 QString WindresArgs;
476 if (getCCompileArguments(mOnlyCheckSyntax).contains("-m32"))
477 WindresArgs = " -F pe-i386";
478
479 if (mOnlyCheckSyntax) {
480 writeln(file);
481 writeln(file, ObjFileName + ':');
482 writeln(file, "\t$(WINDRES) -i " + PrivResName + WindresArgs + " --input-format=rc -o nul -O coff" + ResIncludes);
483 } else {
484 writeln(file);
485 writeln(file, ObjFileName + ": " + PrivResName + ' ' + ResFiles);
486 writeln(file, "\t$(WINDRES) -i " + PrivResName + WindresArgs + " --input-format=rc -o " + ObjFileName + " -O coff"
487 + ResIncludes);
488 }
489 writeln(file);
490 }
491#endif
492}
493
494void ProjectCompiler::writeln(QFile &file, const QString &s)
495{
496 if (!s.isEmpty())
497 file.write(s.toLocal8Bit());
498 file.write("\n");
499}
500
501bool ProjectCompiler::onlyClean() const
502{
503 return mOnlyClean;
504}
505
506void ProjectCompiler::setOnlyClean(bool newOnlyClean)
507{
508 mOnlyClean = newOnlyClean;
509}
510
511bool ProjectCompiler::prepareForRebuild()
512{
513 //we use make argument to clean
514 return true;
515}
516
517bool ProjectCompiler::prepareForCompile()
518{
519 if (!mProject)
520 return false;
521 //initProgressForm();
522 log(tr("Compiling project changes..."));
523 log("--------");
524 log(tr("- Project Filename: %1").arg(mProject->filename()));
525 log(tr("- Compiler Set Name: %1").arg(compilerSet()->name()));
526 log("");
527
528 buildMakeFile();
529
530 mCompiler = compilerSet()->make();
531 if (mOnlyClean) {
532 mArguments = QString("-f \"%1\" clean").arg(extractRelativePath(
533 mProject->directory(),
534 mProject->makeFileName()));
535 } else if (mRebuild) {
536 mArguments = QString("-f \"%1\" clean all").arg(extractRelativePath(
537 mProject->directory(),
538 mProject->makeFileName()));
539 } else {
540 mArguments = QString("-f \"%1\" all").arg(extractRelativePath(
541 mProject->directory(),
542 mProject->makeFileName()));
543 }
544 mDirectory = mProject->directory();
545
546 log(tr("Processing makefile:"));
547 log("--------");
548 log(tr("- makefile processer: %1").arg(mCompiler));
549 log(tr("- Command: %1 %2").arg(extractFileName(mCompiler)).arg(mArguments));
550 log("");
551
552 return true;
553}
554