1 /*
2  * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
3  * http://www.gnu.org/licenses/gpl-3.0.html
4  *
5  * $Revision: 11886 $
6  * $Id: directcommands.cpp 11886 2019-10-26 09:12:03Z fuscated $
7  * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/plugins/compilergcc/directcommands.cpp $
8  */
9 
10 #include <sdk.h>
11 #include <wx/intl.h>
12 #include <wx/filename.h>
13 #include <wx/msgdlg.h>
14 #include <wx/stream.h>
15 #include <wx/wfstream.h>
16 #include <wx/txtstrm.h>
17 #include <wx/regex.h> // used in QUICK hack at line 574
18 #include <compiler.h>
19 #include <cbproject.h>
20 #include <projectbuildtarget.h>
21 #include <globals.h>
22 #include <manager.h>
23 #include <logmanager.h>
24 #include <configmanager.h>
25 #include <macrosmanager.h>
26 #include "directcommands.h"
27 #include "compilercommandgenerator.h"
28 #include "compilergcc.h"
29 #include "cbexception.h"
30 #include "filefilters.h"
31 #include <depslib.h>
32 
33 const wxString COMPILER_SIMPLE_LOG(_T("SLOG:"));
34 const wxString COMPILER_NOTE_LOG(_T("SLOG:NLOG:"));
35 const wxString COMPILER_WARNING_LOG(_T("SLOG:WLOG:"));
36 const wxString COMPILER_ERROR_LOG(_T("SLOG:ELOG:"));
37 const wxString COMPILER_TARGET_CHANGE(_T("TGT:"));
38 const wxString COMPILER_WAIT(_T("WAIT"));
39 const wxString COMPILER_WAIT_LINK(_T("LINK"));
40 
41 const wxString COMPILER_NOTE_ID_LOG = COMPILER_NOTE_LOG.AfterFirst(wxT(':'));
42 const wxString COMPILER_WARNING_ID_LOG = COMPILER_WARNING_LOG.AfterFirst(wxT(':'));
43 const wxString COMPILER_ERROR_ID_LOG = COMPILER_ERROR_LOG.AfterFirst(wxT(':'));
44 
DirectCommands(CompilerGCC * compilerPlugin,Compiler * compiler,cbProject * project,int logPageIndex)45 DirectCommands::DirectCommands(CompilerGCC* compilerPlugin,
46                                Compiler*    compiler,
47                                cbProject*   project,
48                                int          logPageIndex) :
49     m_doYield(false),
50     m_PageIndex(logPageIndex),
51     m_pCompilerPlugin(compilerPlugin),
52     m_pCompiler(compiler),
53     m_pProject(project),
54     m_pGenerator(0)
55 {
56     // even if there is no project, the command generator need to be
57     // initialised for single file compilation to work.
58     // it can handle a NULL pointer argument... ;-)
59     m_pGenerator = m_pCompiler->GetCommandGenerator(m_pProject);
60 
61     // ctor
62     if (!m_pProject)
63         return; // probably a compile file cmd without a project
64 
65     depsStart();
66     wxFileName cwd;
67     cwd.Assign(m_pProject->GetBasePath());
68     // depslib does special handling on Windows in case the CWD is a root
69     // folder like "R:". But this ONLY works, if its just "R:", NOT e.g. "R:/"
70     wxString depsCWD = cwd.GetPath(wxPATH_GET_VOLUME);
71     Manager::Get()->GetLogManager()->DebugLog(F(_("CWD for depslib was: %s."), depsCWD.wx_str()));
72     if (     (depsCWD.Len()==3)         && (depsCWD.GetChar(1)==':')
73         && ( (depsCWD.GetChar(2)=='\\') || (depsCWD.GetChar(2)=='/') ) )
74     {
75         depsCWD.RemoveLast();
76     }
77     Manager::Get()->GetLogManager()->DebugLog(F(_("CWD for depslib is: %s."), depsCWD.wx_str()));
78     depsSetCWD(depsCWD.mb_str());
79 
80     wxFileName fname(m_pProject->GetFilename());
81     fname.SetExt(_T("depend"));
82     depsCacheRead(fname.GetFullPath().mb_str());
83 }
84 
~DirectCommands()85 DirectCommands::~DirectCommands()
86 {
87     if (!m_pProject)
88         return; // probably a compile file cmd without a project
89 
90     struct depsStats stats;
91     depsGetStats(&stats);
92     if (stats.cache_updated)
93     {
94         wxFileName fname(m_pProject->GetFilename());
95         fname.SetExt(_T("depend"));
96         depsCacheWrite(fname.GetFullPath().mb_str());
97     }
98 
99     Manager::Get()->GetLogManager()->DebugLog(
100         F(_("Scanned %ld files for #includes, cache used %ld, cache updated %ld"),
101         stats.scanned, stats.cache_used, stats.cache_updated));
102 
103     depsDone();
104 
105     delete m_pGenerator;
106 }
107 
AddCommandsToArray(const wxString & cmds,wxArrayString & array,bool isWaitCmd,bool isLinkCmd) const108 void DirectCommands::AddCommandsToArray(const wxString& cmds, wxArrayString& array, bool isWaitCmd, bool isLinkCmd) const
109 {
110     wxString cmd = cmds;
111     while (!cmd.IsEmpty())
112     {
113         int idx = cmd.Find(_T("\n"));
114         wxString cmdpart = idx != -1 ? cmd.Left(idx) : cmd;
115         cmdpart.Trim(false);
116         cmdpart.Trim(true);
117         if (!cmdpart.IsEmpty())
118         {
119             if (isWaitCmd)
120                 array.Add(COMPILER_WAIT);
121             if (isLinkCmd)
122                 array.Add(COMPILER_WAIT_LINK);
123             array.Add(cmdpart);
124         }
125         if (idx == -1)
126             break;
127         cmd.Remove(0, idx + 1);
128     }
129 }
130 
MySortProjectFilesByWeight(ProjectFile ** one,ProjectFile ** two)131 static int MySortProjectFilesByWeight(ProjectFile** one, ProjectFile** two)
132 {
133     int diff = (*one)->weight - (*two)->weight;
134     diff = (diff == 0 ? (*one)->relativeFilename.CmpNoCase((*two)->relativeFilename) : diff);
135     return (diff == 0 ? (*one)->relativeFilename.Cmp((*two)->relativeFilename) : diff);
136 }
137 
GetProjectFilesSortedByWeight(ProjectBuildTarget * target,bool compile,bool link) const138 MyFilesArray DirectCommands::GetProjectFilesSortedByWeight(ProjectBuildTarget* target, bool compile, bool link) const
139 {
140     MyFilesArray files;
141     for (FilesList::iterator it = m_pProject->GetFilesList().begin(); it != m_pProject->GetFilesList().end(); ++it)
142     {
143         ProjectFile* pf = *it;
144         // require compile
145         if (compile && !pf->compile)
146             continue;
147         // require link
148         if (link && !pf->link)
149             continue;
150         // if the file does not belong in this target (if we have a target), skip it
151         if (target && (pf->buildTargets.Index(target->GetTitle()) == wxNOT_FOUND))
152             continue;
153         files.Add(pf);
154     }
155     files.Sort(MySortProjectFilesByWeight);
156     return files;
157 }
158 
CompileFile(ProjectBuildTarget * target,ProjectFile * pf,bool force) const159 wxArrayString DirectCommands::CompileFile(ProjectBuildTarget* target, ProjectFile* pf, bool force) const
160 {
161     wxArrayString ret;
162 
163     // is it compilable?
164     if (!pf || !pf->compile || pf->compilerVar.IsEmpty())
165         return ret;
166 
167     // might happen for single file compilation if user chose a target the file does NOT belong to:
168     if (target && pf->GetBuildTargets().Index(target->GetTitle()) == wxNOT_FOUND)
169     {
170         Manager::Get()->GetLogManager()->DebugLog(_("Invalid target selected to compile project file for: File does not belong to this target."));
171         return ret;
172     }
173 
174     if (!force)
175     {
176         DepsSearchStart(target);
177 
178         const pfDetails& pfd = pf->GetFileDetails(target);
179         wxString err;
180         if ( !IsObjectOutdated(target, pfd, &err) )
181         {
182             if ( !err.IsEmpty() )
183                 ret.Add(COMPILER_WARNING_LOG + err);
184             return ret;
185         }
186     }
187 
188     if (target)
189         ret.Add(COMPILER_TARGET_CHANGE + target->GetTitle());
190     AppendArray(GetCompileFileCommand(target, pf), ret);
191     return ret;
192 }
193 
GetCompileFileCommand(ProjectBuildTarget * target,ProjectFile * pf) const194 wxArrayString DirectCommands::GetCompileFileCommand(ProjectBuildTarget* target, ProjectFile* pf) const
195 {
196     wxArrayString ret;
197     wxArrayString ret_generated;
198 
199     // is it compilable?
200     if (!pf || !pf->compile)
201         return ret;
202 
203     if (pf->compilerVar.IsEmpty())
204     {
205         Manager::Get()->GetLogManager()->DebugLog(_("Cannot resolve compiler var for project file."));
206         return ret;
207     }
208 
209     Compiler* compiler = target
210                        ? CompilerFactory::GetCompiler(target->GetCompilerID())
211                        : m_pCompiler;
212     if (!compiler)
213     {
214         Manager::Get()->GetLogManager()->DebugLog(_("Can't access compiler for file."));
215         return ret;
216     }
217 
218     const pfDetails& pfd = pf->GetFileDetails(target);
219     wxString object      = (compiler->GetSwitches().UseFlatObjects)
220                          ? pfd.object_file_flat : pfd.object_file;
221     wxString object_dir  = (compiler->GetSwitches().UseFlatObjects)
222                          ? pfd.object_dir_flat_native : pfd.object_dir_native;
223     // create output dir
224     if (!object_dir.IsEmpty() && !CreateDirRecursively(object_dir, 0755))
225         Manager::Get()->GetLogManager()->DebugLog(_("Can't create object output directory:\n") + object_dir);
226 
227     // lookup file's type
228     const FileType ft = FileTypeOf(pf->relativeFilename);
229 
230     bool is_resource = ft == ftResource;
231     bool is_header   = ft == ftHeader;
232 
233     // allowed resources under all platforms: makes sense when cross-compiling for
234     // windows under linux.
235     // and anyway, if the user is dumb enough to try to compile resources without
236     // having a resource compiler, (s)he deserves the upcoming build error ;)
237 
238     wxString compiler_cmd;
239     if (!is_header || compiler->GetSwitches().supportsPCH)
240     {
241         const CompilerTool* tool = compiler->GetCompilerTool(is_resource ? ctCompileResourceCmd : ctCompileObjectCmd, pf->file.GetExt());
242 
243         // does it generate other files to compile?
244         for (size_t i = 0; i < pf->generatedFiles.size(); ++i)
245             AppendArray(GetCompileFileCommand(target, pf->generatedFiles[i]), ret_generated); // recurse
246 
247         pfCustomBuild& pcfb = pf->customBuild[compiler->GetID()];
248         if (pcfb.useCustomBuildCommand)
249             compiler_cmd = pcfb.buildCommand;
250         else if (tool)
251             compiler_cmd = tool->command;
252         else
253             compiler_cmd = wxEmptyString;
254 
255         wxString source_file;
256         if (compiler->GetSwitches().UseFullSourcePaths)
257             source_file = UnixFilename(pfd.source_file_absolute_native);
258         else
259             source_file = pfd.source_file;
260 
261 #ifdef command_line_generation
262         Manager::Get()->GetLogManager()->DebugLog(F(_T("GetCompileFileCommand[1]: compiler_cmd='%s', source_file='%s', object='%s', object_dir='%s'."),
263                                                     compiler_cmd.wx_str(), source_file.wx_str(), object.wx_str(), object_dir.wx_str()));
264 #endif
265 
266         // for resource files, use short from if path because if windres bug with spaces-in-paths
267         if (is_resource && compiler->GetSwitches().UseFullSourcePaths)
268             source_file = pf->file.GetShortPath();
269 
270         QuoteStringIfNeeded(source_file);
271 
272 #ifdef command_line_generation
273         Manager::Get()->GetLogManager()->DebugLog(F(_T("GetCompileFileCommand[2]: source_file='%s'."),
274                                                     source_file.wx_str()));
275 #endif
276         m_pGenerator->GenerateCommandLine(compiler_cmd, target, pf, source_file, object,
277                                           pfd.object_file_flat, pfd.dep_file);
278     }
279 
280     if (!is_header && compiler_cmd.IsEmpty())
281     {
282         ret.Add(COMPILER_SIMPLE_LOG + _("Skipping file (no compiler program set): ") + pfd.source_file_native );
283         return ret;
284     }
285 
286     switch (compiler->GetSwitches().logging)
287     {
288         case clogFull:
289             ret.Add(COMPILER_SIMPLE_LOG + compiler_cmd);
290             break;
291 
292         case clogSimple:
293             if (is_header)
294                 ret.Add(COMPILER_SIMPLE_LOG + _("Pre-compiling header: ") + pfd.source_file_native );
295             else
296                 ret.Add(COMPILER_SIMPLE_LOG + _("Compiling: ") + pfd.source_file_native );
297             break;
298 
299         case clogNone: // fall-through
300         default:
301             break;
302     }
303 
304     AddCommandsToArray(compiler_cmd, ret);
305 
306     if (is_header)
307         ret.Add(COMPILER_WAIT);
308 
309     if (ret_generated.GetCount())
310     {
311         // not only append commands for (any) generated files to be compiled
312         // but also insert a "pause" to allow this file to generate its files first
313         if (!is_header) // if is_header, the "pause" has already been added
314             ret.Add(COMPILER_WAIT);
315         AppendArray(ret_generated, ret);
316     }
317 
318     // if it's a PCH, delete the previously generated PCH to avoid problems
319     // (it 'll be recreated anyway)
320     if ( (ft == ftHeader) && pf->compile )
321     {
322         wxString object_abs = (compiler->GetSwitches().UseFlatObjects)
323                             ? pfd.object_file_flat_absolute_native
324                             : pfd.object_file_absolute_native;
325 
326         if ( wxFileExists(object_abs) && !wxRemoveFile(object_abs) )
327             Manager::Get()->GetLogManager()->DebugLog(_("Cannot remove old PCH file:\n") + object_abs);
328     }
329 
330     return ret;
331 }
332 
333 /// This is to be used *only* for files not belonging to a project!!!
GetCompileSingleFileCommand(const wxString & filename) const334 wxArrayString DirectCommands::GetCompileSingleFileCommand(const wxString& filename) const
335 {
336     wxArrayString ret;
337 
338     // lookup file's type
339     FileType ft = FileTypeOf(filename);
340 
341     // is it compilable?
342     if (ft != ftSource)
343         return ret;
344 
345     wxFileName fname(filename);
346     fname.SetExt(m_pCompiler->GetSwitches().objectExtension);
347     wxString o_filename = fname.GetFullPath();
348     wxString srcExt = fname.GetExt();
349     fname.SetExt(FileFilters::EXECUTABLE_EXT);
350     wxString exe_filename = fname.GetFullPath();
351 
352     wxString s_filename = filename;
353     QuoteStringIfNeeded(s_filename);
354     QuoteStringIfNeeded(o_filename);
355 
356     Compiler* compiler = CompilerFactory::GetDefaultCompiler();
357     if (!compiler)
358         return ret;
359 
360     // please leave this check here for convenience: single file compilation is "special"
361     if (!m_pGenerator) cbThrow(_T("Command generator not initialised through ctor!"));
362 
363     wxString compilerCmd = compiler->GetCommand(ctCompileObjectCmd, srcExt);
364     CompilerCommandGenerator::Result compilerResult(&compilerCmd);
365     CompilerCommandGenerator::Params compilerParams;
366     compilerParams.file = s_filename;
367     compilerParams.object = o_filename;
368     compilerParams.flatObject = o_filename;
369     m_pGenerator->GenerateCommandLine(compilerResult, compilerParams);
370 
371     wxString linkerCmd = compiler->GetCommand(ctLinkConsoleExeCmd, fname.GetExt());
372     CompilerCommandGenerator::Result linkerResult(&linkerCmd);
373     CompilerCommandGenerator::Params linkerParams;
374     linkerParams.object = o_filename;
375     linkerParams.flatObject = o_filename;
376     linkerParams.hasCppFilesToLink = compilerResult.processedCppFile;
377     m_pGenerator->GenerateCommandLine(linkerResult, linkerParams);
378 
379     if (!compilerCmd.IsEmpty())
380     {
381         switch (m_pCompiler->GetSwitches().logging)
382         {
383             case clogFull:
384                 ret.Add(COMPILER_SIMPLE_LOG + compilerCmd);
385                 break;
386 
387             case clogSimple:
388                 ret.Add(COMPILER_SIMPLE_LOG + _("Compiling: ") + filename);
389                 break;
390 
391             case clogNone: // fall-through
392             default:
393                 break;
394         }
395         AddCommandsToArray(compilerCmd, ret);
396     }
397     else
398         ret.Add(COMPILER_SIMPLE_LOG + _("Skipping file (no compiler program set): ") + filename);
399 
400     if (!linkerCmd.IsEmpty())
401     {
402         switch (m_pCompiler->GetSwitches().logging)
403         {
404             case clogFull:
405                 ret.Add(COMPILER_SIMPLE_LOG + linkerCmd);
406                 break;
407 
408             case clogSimple: // fall-through
409             case clogNone:   // fall-through
410             default: // linker always simple log (if not full)
411                 ret.Add(COMPILER_SIMPLE_LOG + _("Linking console executable: ") + exe_filename);
412                 break;
413         }
414         AddCommandsToArray(linkerCmd, ret, true);
415     }
416     else
417         ret.Add(COMPILER_SIMPLE_LOG + _("Skipping linking (no linker program set): ") + exe_filename);
418     return ret;
419 }
420 
421 /// This is to be used *only* for files not belonging to a project!!!
GetCleanSingleFileCommand(const wxString & filename) const422 wxArrayString DirectCommands::GetCleanSingleFileCommand(const wxString& filename) const
423 {
424     wxArrayString ret;
425 
426     // lookup file's type
427     FileType ft = FileTypeOf(filename);
428 
429     // is it compilable?
430     if (ft != ftSource)
431         return ret;
432 
433     wxFileName fname(filename);
434     fname.SetExt(m_pCompiler->GetSwitches().objectExtension);
435     wxString o_filename = fname.GetFullPath();
436     fname.SetExt(FileFilters::EXECUTABLE_EXT);
437     wxString exe_filename = fname.GetFullPath();
438 
439     ret.Add(o_filename);
440     ret.Add(exe_filename);
441 
442     return ret;
443 }
444 
GetCompileCommands(ProjectBuildTarget * target,bool force) const445 wxArrayString DirectCommands::GetCompileCommands(ProjectBuildTarget* target, bool force) const
446 {
447     wxArrayString ret;
448 
449     if (target)
450         ret = GetTargetCompileCommands(target, force);
451     else
452     {
453         for (int x = 0; x < m_pProject->GetBuildTargetsCount(); ++x)
454         {
455             ProjectBuildTarget* bt = m_pProject->GetBuildTarget(x);
456             if (bt->GetIncludeInTargetAll()) // only if target gets build with "all"
457             {
458                 wxArrayString targetcompile = GetTargetCompileCommands(bt, force);
459                 AppendArray(targetcompile, ret);
460             }
461         }
462     }
463     return ret;
464 }
465 
GetTargetCompileCommands(ProjectBuildTarget * target,bool force) const466 wxArrayString DirectCommands::GetTargetCompileCommands(ProjectBuildTarget* target, bool force) const
467 {
468     wxArrayString ret;
469 
470     // set list of #include directories
471     DepsSearchStart(target);
472 
473     // iterate all files of the project/target and add them to the build process
474     size_t counter = ret.GetCount();
475     MyFilesArray files = GetProjectFilesSortedByWeight(target, true, false);
476     size_t fcount = files.GetCount();
477     bool hasWeight = false;
478     unsigned short int lastWeight = 0;
479     for (unsigned int i = 0; i < fcount; ++i)
480     {
481         ProjectFile* pf = files[i];
482         // auto-generated files are handled automatically in GetCompileFileCommand()
483         if (pf->AutoGeneratedBy())
484             continue;
485 
486         const pfDetails& pfd = pf->GetFileDetails(target);
487         wxString err;
488         if (force || IsObjectOutdated(target, pfd, &err))
489         {
490             // Add a wait command if the weight of the current file is different from the previous one
491             // Because GetCompileFileCommand() already adds a wait command if it compiled a PCH we
492             // check the last command to prevent two consecutive wait commands
493             if (hasWeight && lastWeight != pf->weight && (ret.IsEmpty() || ret.Last() != COMPILER_WAIT))
494                 ret.Add(COMPILER_WAIT);
495 
496             // compile file
497             wxArrayString filecmd = GetCompileFileCommand(target, pf);
498             AppendArray(filecmd, ret);
499 
500             // Update the weight
501             if (!hasWeight)
502                 hasWeight = true;
503             lastWeight = pf->weight;
504         }
505         else
506         {
507             if (!err.IsEmpty())
508                 ret.Add(COMPILER_WARNING_LOG + err);
509         }
510         if (m_doYield)
511             Manager::Yield();
512     }
513 
514     // add link command
515     wxArrayString link = GetLinkCommands(target, ret.GetCount() != counter);
516     AppendArray(link, ret);
517 
518     return ret;
519 }
520 
GetPreBuildCommands(ProjectBuildTarget * target) const521 wxArrayString DirectCommands::GetPreBuildCommands(ProjectBuildTarget* target) const
522 {
523     Compiler* compiler = target ? CompilerFactory::GetCompiler(target->GetCompilerID()) : m_pCompiler;
524     wxArrayString buildcmds = target ? target->GetCommandsBeforeBuild() : m_pProject->GetCommandsBeforeBuild();
525     if (!buildcmds.IsEmpty())
526     {
527         wxString title = target ? target->GetTitle() : m_pProject->GetTitle();
528         wxArrayString tmp;
529         for (size_t i = 0; i < buildcmds.GetCount(); ++i)
530         {
531             if (compiler)
532             {
533                 if (target)
534                 {
535                     m_pGenerator->GenerateCommandLine(buildcmds[i], target, 0, wxEmptyString,
536                                                       wxEmptyString, wxEmptyString, wxEmptyString);
537                 }
538                 else
539                 {
540                     m_pGenerator->GenerateCommandLine(buildcmds[i],
541                                                       m_pProject->GetCurrentlyCompilingTarget(), 0,
542                                                       wxEmptyString, wxEmptyString, wxEmptyString,
543                                                       wxEmptyString);
544                 }
545             }
546 
547             tmp.Add(COMPILER_WAIT); // all commands should wait for queue to empty first
548             tmp.Add(COMPILER_SIMPLE_LOG + buildcmds[i]);
549             tmp.Add(buildcmds[i]);
550         }
551         buildcmds = tmp;
552         if (target)
553             buildcmds.Insert(COMPILER_SIMPLE_LOG + _("Running target pre-build steps"), 0);
554         else
555             buildcmds.Insert(COMPILER_SIMPLE_LOG + _("Running project pre-build steps"), 0);
556         if (m_doYield)
557             Manager::Yield();
558     }
559     return buildcmds;
560 }
561 
GetPostBuildCommands(ProjectBuildTarget * target) const562 wxArrayString DirectCommands::GetPostBuildCommands(ProjectBuildTarget* target) const
563 {
564     Compiler* compiler = target ? CompilerFactory::GetCompiler(target->GetCompilerID()) : m_pCompiler;
565     wxArrayString buildcmds = target ? target->GetCommandsAfterBuild() : m_pProject->GetCommandsAfterBuild();
566     if (!buildcmds.IsEmpty())
567     {
568         wxString title = target ? target->GetTitle() : m_pProject->GetTitle();
569         wxArrayString tmp;
570         for (size_t i = 0; i < buildcmds.GetCount(); ++i)
571         {
572             if (compiler)
573             {
574                 CompilerCommandGenerator::Result result(&buildcmds[i]);
575                 CompilerCommandGenerator::Params params;
576                 params.target = (target ? target : m_pProject->GetCurrentlyCompilingTarget());
577                 m_pGenerator->GenerateCommandLine(result, params);
578             }
579 
580             tmp.Add(COMPILER_WAIT); // all commands should wait for queue to empty first
581             tmp.Add(COMPILER_SIMPLE_LOG + buildcmds[i]);
582             tmp.Add(buildcmds[i]);
583         }
584         buildcmds = tmp;
585         if (target)
586             buildcmds.Insert(COMPILER_SIMPLE_LOG + _("Running target post-build steps"), 0);
587         else
588             buildcmds.Insert(COMPILER_SIMPLE_LOG + _("Running project post-build steps"), 0);
589         if (m_doYield)
590             Manager::Yield();
591     }
592     return buildcmds;
593 }
594 
GetLinkCommands(ProjectBuildTarget * target,bool force) const595 wxArrayString DirectCommands::GetLinkCommands(ProjectBuildTarget* target, bool force) const
596 {
597     wxArrayString ret;
598 
599     if (target)
600         ret = GetTargetLinkCommands(target, force);
601     else
602     {
603         for (int x = 0; x < m_pProject->GetBuildTargetsCount(); ++x)
604         {
605             ProjectBuildTarget* bt = m_pProject->GetBuildTarget(x);
606             if (bt->GetIncludeInTargetAll()) // only if target gets build with "all"
607             {
608                 wxArrayString targetlink = GetTargetLinkCommands(bt, force);
609                 AppendArray(targetlink, ret);
610             }
611         }
612     }
613     return ret;
614 }
615 
GetTargetLinkCommands(ProjectBuildTarget * target,bool force) const616 wxArrayString DirectCommands::GetTargetLinkCommands(ProjectBuildTarget* target, bool force) const
617 {
618     wxArrayString ret;
619 
620     wxString output = target->GetOutputFilename();
621     Manager::Get()->GetMacrosManager()->ReplaceMacros(output, target);
622 
623     wxFileName out = UnixFilename(output);
624     wxString linkfiles;
625     wxString FlatLinkFiles;
626     wxString resfiles;
627     bool IsOpenWatcom = target->GetCompilerID().IsSameAs(_T("ow"));
628 
629     time_t outputtime;
630     depsTimeStamp(output.mb_str(), &outputtime);
631     if (!outputtime)
632         force = true;
633     wxArrayString fileMissing;
634     if ( AreExternalDepsOutdated(target, out.GetFullPath(), &fileMissing) )
635         force = true;
636 
637     if (!fileMissing.IsEmpty())
638     {
639         wxString warn;
640 #ifdef NO_TRANSLATION
641         warn.Printf(wxT("WARNING: Target '%s': Unable to resolve %lu external dependenc%s:"),
642                     target->GetFullTitle().wx_str(), static_cast<unsigned long>(fileMissing.Count()), wxString(fileMissing.Count() == 1 ? wxT("y") : wxT("ies")).wx_str());
643 #else
644         warn.Printf(_("WARNING: Target '%s': Unable to resolve %lu external dependency/ies:"),
645                     target->GetFullTitle().wx_str(), static_cast<unsigned long>(fileMissing.Count()));
646 #endif // NO_TRANSLATION
647         ret.Add(COMPILER_WARNING_LOG + warn);
648         for (size_t i = 0; i < fileMissing.Count(); ++i)
649             ret.Add(COMPILER_NOTE_LOG + wxString(wxT(' '), 8) + fileMissing[i]);
650     }
651 
652     Compiler* compiler = target ? CompilerFactory::GetCompiler(target->GetCompilerID()) : m_pCompiler;
653 
654     wxString prependHack; // part of the following hack
655     if (target->GetTargetType() == ttStaticLib)
656     {
657         // QUICK HACK: some linkers (e.g. bcc, dmc) require a - or + in front of
658         // object files for static library. What we 'll do here until we redesign
659         // the thing, is to accept this symbol as part of the $link_objects macro
660         // like this:
661         // $+link_objects
662         // $-link_objects
663         // $-+link_objects
664         // $+-link_objects
665         //
666         // So, we first scan the command for this special case and, if found,
667         // set a flag so that the linkfiles array is filled with the correct options
668         wxString compilerCmd = compiler ? compiler->GetCommand(ctLinkStaticCmd) : wxString(wxEmptyString);
669         wxRegEx re(_T("\\$([-+]+)link_objects"));
670         if (re.Matches(compilerCmd))
671             prependHack = re.GetMatch(compilerCmd, 1);
672     }
673 
674     // get all the linkable objects for the target
675     MyFilesArray files = GetProjectFilesSortedByWeight(target, false, true);
676     if (files.GetCount() == 0)
677     {
678         if (target->GetTargetType() != ttCommandsOnly)
679             ret.Add(COMPILER_SIMPLE_LOG + _("Linking stage skipped (build target has no object files to link)"));
680         return ret;
681     }
682     if (IsOpenWatcom && target->GetTargetType() != ttStaticLib)
683         linkfiles << _T("file ");
684     bool subseq(false);
685     bool hasCppFilesToLink = false;
686     for (unsigned int i = 0; i < files.GetCount(); ++i)
687     {
688         ProjectFile* pf = files[i];
689 
690         // we have to test again for each file if it is to be compiled
691         // and we can't check the file for existence because we 're still
692         // generating the command lines that will create the files...
693         wxString macro = _T("$compiler");
694         CompilerCommandGenerator::Result result(&macro);
695         CompilerCommandGenerator::Params params;
696         params.target = target;
697         params.pf = pf;
698         m_pGenerator->GenerateCommandLine(result, params);
699         if (macro.IsEmpty())
700             continue;
701 
702         if (result.processedCppFile)
703             hasCppFilesToLink = true;
704 
705         const pfDetails& pfd = pf->GetFileDetails(target);
706         wxString Object = (compiler->GetSwitches().UseFlatObjects) ? pfd.object_file_flat
707                                                                    : pfd.object_file;
708 
709         if (FileTypeOf(pf->relativeFilename) == ftResource)
710         {
711             if (subseq)
712                 resfiles << _T(" ");
713             // -----------------------------------------
714             // Following lines have been modified for OpenWatcom
715             if (IsOpenWatcom)
716                 resfiles << _T("option resource=") << Object;
717             else
718                 resfiles << Object;
719             // ------------------------------------------
720         }
721         else
722         {
723             // -----------------------------------------
724             // Following lines have been modified for OpenWatcom
725             if (IsOpenWatcom && target->GetTargetType() == ttStaticLib)
726             {
727                 if (subseq)
728                 {
729                     linkfiles << _T(" ");
730                     FlatLinkFiles << _T(" ");
731                 }
732                 linkfiles << prependHack << Object; // see QUICK HACK above (prependHack)
733                 FlatLinkFiles << prependHack << pfd.object_file_flat; // see QUICK HACK above (prependHack)
734             }
735             else
736             {
737                 if (subseq)
738                 {
739                     linkfiles << compiler->GetSwitches().objectSeparator;
740                     FlatLinkFiles << compiler->GetSwitches().objectSeparator;
741                 }
742                 linkfiles << prependHack << Object; // see QUICK HACK above (prependHack)
743                 FlatLinkFiles << prependHack << pfd.object_file_flat; // see QUICK HACK above (prependHack)
744             }
745             // -----------------------------------------
746         }
747         subseq = true;
748 
749         // timestamp check
750         if (!force)
751         {
752             time_t objtime;
753             depsTimeStamp(pfd.object_file_native.mb_str(), &objtime);
754             // Honor compiler request to Use Flat Objects
755             // (Settings/compiler/otherSettings/advancedOptions/Others/UseFlatObjects)
756             if (compiler->GetSwitches().UseFlatObjects)
757                 depsTimeStamp(pfd.object_file_flat.mb_str(), &objtime);
758 
759             if (!objtime)
760                 force = true;
761             if (objtime > outputtime)
762                 force = true;
763         }
764     }
765     if (IsOpenWatcom)
766     {
767         linkfiles.Trim();
768     }
769 
770     if (!force)
771         return ret;
772 
773     // create output dir
774     out.MakeAbsolute(m_pProject->GetBasePath());
775     wxString dstname = out.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
776     Manager::Get()->GetMacrosManager()->ReplaceMacros(dstname, target);
777     if (!dstname.IsEmpty() && !CreateDirRecursively(dstname, 0755))
778     {
779         cbMessageBox(_("Can't create output directory ") + dstname);
780     }
781 
782     // add actual link command
783     wxString kind_of_output;
784     CommandType ct = ctCount; // get rid of compiler warning
785     switch (target->GetTargetType())
786     {
787         case ttConsoleOnly:
788             ct = ctLinkConsoleExeCmd;
789             kind_of_output = _("console executable");
790             break;
791 
792         case ttExecutable:
793             ct = ctLinkExeCmd;
794             kind_of_output = _("executable");
795             break;
796 
797         case ttDynamicLib:
798             ct = ctLinkDynamicCmd;
799             kind_of_output = _("dynamic library");
800             break;
801 
802         case ttStaticLib:
803             ct = ctLinkStaticCmd;
804             kind_of_output = _("static library");
805             break;
806 
807         case ttNative:
808             ct = ctLinkNativeCmd;
809             kind_of_output = _("native");
810             break;
811 
812         case ttCommandsOnly:
813             // add target post-build commands
814             ret.Clear();
815             AppendArray(GetPostBuildCommands(target), ret);
816             return ret;
817             break;
818         default:
819             wxString ex;
820             ex.Printf(_T("Encountered invalid TargetType (value = %d)"), target->GetTargetType());
821             cbThrow(ex);
822         break;
823     }
824 
825     // Linker command
826     wxString compilerCmd = compiler->GetCommand(ct);
827     CompilerCommandGenerator::Result result(&compilerCmd);
828     CompilerCommandGenerator::Params params;
829     params.target = target;
830     params.object = linkfiles;
831     params.flatObject = FlatLinkFiles;
832     params.deps = resfiles;
833     params.hasCppFilesToLink = hasCppFilesToLink;
834     m_pGenerator->GenerateCommandLine(result, params);
835 
836     if (!compilerCmd.IsEmpty())
837     {
838         switch (compiler->GetSwitches().logging)
839         {
840             case clogFull:
841                 ret.Add(COMPILER_SIMPLE_LOG + compilerCmd);
842                 break;
843 
844             case clogSimple: // fall-through
845             case clogNone:   // fall-through
846             default: // linker always simple log (if not full)
847                 ret.Add(COMPILER_SIMPLE_LOG + _("Linking ") + kind_of_output + _T(": ") + output);
848                 break;
849         }
850 
851         // for an explanation of the following, see GetTargetCompileCommands()
852         if (target && ret.GetCount() != 0)
853             ret.Add(COMPILER_TARGET_CHANGE + target->GetTitle());
854 
855         // the 'true' will make sure all commands will be prepended by
856         // COMPILER_WAIT signal
857         AddCommandsToArray(compilerCmd, ret, true, true);
858     }
859     else
860         ret.Add(COMPILER_SIMPLE_LOG + _("Skipping linking (no linker program set): ") + output);
861 
862     return ret;
863 }
864 
GetCleanCommands(ProjectBuildTarget * target,bool distclean) const865 wxArrayString DirectCommands::GetCleanCommands(ProjectBuildTarget* target, bool distclean) const
866 {
867     wxArrayString ret;
868 
869     if (target)
870         ret = GetTargetCleanCommands(target);
871     else
872     {
873         for (int x = 0; x < m_pProject->GetBuildTargetsCount(); ++x)
874         {
875             ProjectBuildTarget* bt = m_pProject->GetBuildTarget(x);
876             wxArrayString targetclear = GetTargetCleanCommands(bt, distclean);
877             AppendArray(targetclear, ret);
878         }
879     }
880     return ret;
881 }
882 
GetTargetCleanCommands(ProjectBuildTarget * target,bool distclean) const883 wxArrayString DirectCommands::GetTargetCleanCommands(ProjectBuildTarget* target, bool distclean) const
884 {
885     wxArrayString ret;
886 
887     // add object files
888     MyFilesArray files = GetProjectFilesSortedByWeight(target, true, false);
889     for (unsigned int i = 0; i < files.GetCount(); ++i)
890     {
891         ProjectFile* pf = files[i];
892         const pfDetails& pfd = pf->GetFileDetails(target);
893         Compiler* compiler = target ? CompilerFactory::GetCompiler(target->GetCompilerID()) : m_pCompiler;
894         if (compiler)
895         {
896             wxString ObjectAbs = (compiler->GetSwitches().UseFlatObjects)
897                                ? pfd.object_file_flat_absolute_native
898                                : pfd.object_file_absolute_native;
899             ret.Add(ObjectAbs);
900             // if this is an auto-generated file, delete it
901             if (pf->AutoGeneratedBy())
902                 ret.Add(pf->file.GetFullPath());
903 
904             if (distclean)
905                 ret.Add(pfd.dep_file_absolute_native);
906         }
907     }
908 
909     // add target output
910     wxString outputfilename = target->GetOutputFilename();
911 
912     if (target->GetTargetType() != ttCommandsOnly)
913     {
914         Manager::Get()->GetMacrosManager()->ReplaceMacros(outputfilename, target);
915         ret.Add(outputfilename);
916     }
917 
918     if (target->GetTargetType() == ttDynamicLib)
919     {
920         // for dynamic libs, delete static lib
921         outputfilename = target->GetStaticLibFilename();
922         Manager::Get()->GetMacrosManager()->ReplaceMacros(outputfilename, target);
923         ret.Add(outputfilename);
924         // .def exports file is not deleted, because it may be user-supplied
925 //        ret.Add(target->GetDynamicLibDefFilename());
926     }
927 
928     return ret;
929 }
930 
931 /** external deps are manually set by the user
932   * e.g. a static library linked to the project is an external dep (if set as such by the user)
933   * so that a re-linking is forced if the static lib is updated
934   */
AreExternalDepsOutdated(ProjectBuildTarget * target,const wxString & buildOutput,wxArrayString * filesMissing) const935 bool DirectCommands::AreExternalDepsOutdated(ProjectBuildTarget* target,
936                                              const wxString& buildOutput,
937                                              wxArrayString*  filesMissing) const
938 {
939     Compiler* compiler = CompilerFactory::GetCompiler(target->GetCompilerID());
940 
941     // if no output, probably a commands-only target; nothing to relink
942     // but we have to check other dependencies
943     time_t timeOutput = 0;
944     if (!buildOutput.IsEmpty())
945     {
946         wxString output = buildOutput;
947         Manager::Get()->GetMacrosManager()->ReplaceMacros(output);
948         depsTimeStamp(output.mb_str(), &timeOutput);
949         // if build output exists, check for updated static libraries
950         if (timeOutput)
951         {
952             // look for static libraries in target/project library dirs
953             wxArrayString libs = target->GetLinkLibs();
954             const wxArrayString& prjLibs = target->GetParentProject()->GetLinkLibs();
955             const wxArrayString& cmpLibs = compiler->GetLinkLibs();
956             AppendArray(prjLibs, libs);
957             AppendArray(cmpLibs, libs);
958 
959             const wxArrayString& prjLibDirs = target->GetParentProject()->GetLibDirs();
960             const wxArrayString& cmpLibDirs = compiler->GetLibDirs();
961             wxArrayString libDirs = target->GetLibDirs();
962             AppendArray(prjLibDirs, libDirs);
963             AppendArray(cmpLibDirs, libDirs);
964 
965             for (size_t i = 0; i < libs.GetCount(); ++i)
966             {
967                 wxString lib = libs[i];
968 
969                 // if user manually pointed to a library, without using the lib dirs,
970                 // then just check the file directly w/out involving the search dirs...
971                 if (lib.Contains(_T("/")) || lib.Contains(_T("\\")))
972                 {
973                     Manager::Get()->GetMacrosManager()->ReplaceMacros(lib, target);
974                     lib = UnixFilename(lib);
975                     time_t timeExtDep;
976                     depsTimeStamp(lib.mb_str(), &timeExtDep);
977                     if (timeExtDep > timeOutput)
978                     {
979                         // force re-link
980                         Manager::Get()->GetLogManager()->DebugLog(F(_T("Forcing re-link of '%s/%s' because '%s' is newer"),
981                                                                         target->GetParentProject()->GetTitle().wx_str(),
982                                                                         target->GetTitle().wx_str(),
983                                                                         lib.wx_str()));
984                         return true;
985                     }
986                     continue;
987                 }
988 
989                 if (!lib.StartsWith(compiler->GetSwitches().libPrefix))
990                     lib = compiler->GetSwitches().libPrefix + lib;
991                 if (!lib.EndsWith(_T(".") + compiler->GetSwitches().libExtension))
992                     lib += _T(".") + compiler->GetSwitches().libExtension;
993 
994                 for (size_t l = 0; l < libDirs.GetCount(); ++l)
995                 {
996                     wxString dir = libDirs[l] + wxFILE_SEP_PATH + lib;
997                     Manager::Get()->GetMacrosManager()->ReplaceMacros(dir, target);
998                     dir = UnixFilename(dir);
999                     time_t timeExtDep;
1000                     depsTimeStamp(dir.mb_str(), &timeExtDep);
1001                     if (timeExtDep > timeOutput)
1002                     {
1003                         // force re-link
1004                         Manager::Get()->GetLogManager()->DebugLog(F(_T("Forcing re-link of '%s/%s' because '%s' is newer"),
1005                                                                         target->GetParentProject()->GetTitle().wx_str(),
1006                                                                         target->GetTitle().wx_str(),
1007                                                                         dir.wx_str()));
1008                         return true;
1009                     }
1010                 }
1011             }
1012         }
1013     }
1014 
1015     // array is separated by ;
1016     wxArrayString extDeps  = GetArrayFromString(target->GetExternalDeps(), _T(";"));
1017     wxArrayString addFiles = GetArrayFromString(target->GetAdditionalOutputFiles(), _T(";"));
1018     for (size_t i = 0; i < extDeps.GetCount(); ++i)
1019     {
1020         if (extDeps[i].IsEmpty())
1021             continue;
1022 
1023         Manager::Get()->GetMacrosManager()->ReplaceMacros(extDeps[i]);
1024         time_t timeExtDep;
1025         depsTimeStamp(extDeps[i].mb_str(), &timeExtDep);
1026         // if external dep doesn't exist, no need to relink
1027         // but we have to check other dependencies
1028         if (!timeExtDep)
1029         {
1030             if (filesMissing) filesMissing->Add(extDeps[i]);
1031             continue;
1032         }
1033 
1034         // let's check the additional output files
1035         for (size_t j = 0; j < addFiles.GetCount(); ++j)
1036         {
1037             if (addFiles[j].IsEmpty())
1038                 continue;
1039 
1040             Manager::Get()->GetMacrosManager()->ReplaceMacros(addFiles[j]);
1041             time_t timeAddFile;
1042             depsTimeStamp(addFiles[j].mb_str(), &timeAddFile);
1043             // if additional file doesn't exist, we can skip it
1044             if (!timeAddFile)
1045             {
1046                 if (filesMissing) filesMissing->Add(addFiles[j]);
1047                 continue;
1048             }
1049 
1050             // if external dep is newer than additional file, relink
1051             if (timeExtDep > timeAddFile)
1052                 return true;
1053         }
1054 
1055         // if no output, probably a commands-only target; nothing to relink
1056         // but we have to check other dependencies
1057         if (buildOutput.IsEmpty())
1058             continue;
1059 
1060         // now check the target's output
1061         // this is moved last because, for "commands only" targets,
1062         // it would return before we had a chance to check the
1063         // additional output files (above)
1064 
1065         // if build output doesn't exist, relink
1066         if (!timeOutput)
1067             return true;
1068 
1069         // if external dep is newer than build output, relink
1070         if (timeExtDep > timeOutput)
1071             return true;
1072     }
1073     return false; // no force relink
1074 }
1075 
IsObjectOutdated(ProjectBuildTarget * target,const pfDetails & pfd,wxString * errorStr) const1076 bool DirectCommands::IsObjectOutdated(ProjectBuildTarget* target, const pfDetails& pfd, wxString* errorStr) const
1077 {
1078     // If the source file does not exist, then do not compile.
1079     time_t timeSrc;
1080     depsTimeStamp(pfd.source_file_absolute_native.mb_str(), &timeSrc);
1081     if (!timeSrc)
1082     {
1083         if (errorStr)
1084             *errorStr = _("WARNING: Can't read file's timestamp: ") + pfd.source_file_absolute_native;
1085 
1086         if (wxFileExists(pfd.source_file_absolute_native))
1087             return true; // fall-back: Its better to compile in that case
1088 
1089         return false;
1090     }
1091 
1092     // If the object file does not exist, then it must be built. In this case
1093     // there is no need to scan the source file for headers.
1094     time_t timeObj;
1095     Compiler* compiler = target ? CompilerFactory::GetCompiler(target->GetCompilerID()) : m_pCompiler;
1096     if (!compiler)
1097         return false;
1098 
1099     wxString ObjectAbs = (compiler->GetSwitches().UseFlatObjects)
1100                        ? pfd.object_file_flat_absolute_native
1101                        : pfd.object_file_absolute_native;
1102     depsTimeStamp(ObjectAbs.mb_str(), &timeObj);
1103     if (!timeObj)
1104         return true; // fall-back: Its better to compile in that case
1105 
1106     // If the source file is newer than the object file, then the object file
1107     // must be built. In this case there is no need to scan the source file
1108     // for headers.
1109     if (timeSrc > timeObj)
1110         return true;
1111 
1112     // Do the check for includes only, if not disabled by the user, e.g. in case of non C/C++ compilers
1113     if ( Manager::Get()->GetConfigManager(_T("compiler"))->ReadBool(_T("/skip_include_deps"), false) )
1114         return false;
1115 
1116     // Scan the source file for headers. Result is NULL if the file does
1117     // not exist. If one of the descendent header files is newer than the
1118     // object file, then the object file must be built.
1119     depsRef ref = depsScanForHeaders(pfd.source_file_absolute_native.mb_str());
1120     if (ref)
1121     {
1122         time_t timeNewest;
1123         (void) depsGetNewest(ref, &timeNewest);
1124         return (timeNewest > timeObj);
1125     }
1126 
1127     // object file is up to date with source file and does not depend on #includes
1128     return false;
1129 }
1130 
DepsSearchStart(ProjectBuildTarget * target) const1131 void DirectCommands::DepsSearchStart(ProjectBuildTarget* target) const
1132 {
1133     depsSearchStart();
1134 
1135     MacrosManager* mm = Manager::Get()->GetMacrosManager();
1136     wxArrayString incs = m_pGenerator->GetCompilerSearchDirs(target);
1137 
1138     for (unsigned int i = 0; i < incs.GetCount(); ++i)
1139     {
1140         // replace custom vars in include dirs
1141         mm->ReplaceMacros(incs[i], target);
1142         // actually add search dirs for deps
1143         depsAddSearchDir(incs[i].mb_str());
1144     }
1145 
1146     // We could add the "global" compiler directories too, but we normally
1147     // don't care about the modification times of system include files.
1148 }
1149