1 /*
2  * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
3  * http://www.gnu.org/licenses/lgpl-3.0.html
4  *
5  * $Revision: 11905 $
6  * $Id: cbproject.cpp 11905 2019-11-09 12:05:32Z fuscated $
7  * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/sdk/cbproject.cpp $
8  */
9 
10 #include "sdk_precomp.h"
11 
12 #ifndef wxUSE_CHOICEDLG
13     #define wxUSE_CHOICEDLG 1
14 #endif
15 
16 #include <wx/choicdlg.h>
17 #include <wx/filedlg.h>
18 #include <wx/filename.h>
19 
20 #ifndef CB_PRECOMP
21     #include <wx/dir.h>
22 
23     #include "cbproject.h" // class's header file
24     #include "cbeditor.h"
25     #include "cbtreectrl.h"
26     #include "compiler.h" // GetSwitches
27     #include "compilerfactory.h"
28     #include "configmanager.h"
29     #include "editormanager.h"
30     #include "filemanager.h"
31     #include "globals.h"
32     #include "infowindow.h"
33     #include "logmanager.h"
34     #include "macrosmanager.h"
35     #include "manager.h"
36     #include "pluginmanager.h"
37     #include "projectbuildtarget.h"
38     #include "projectfile.h"
39     #include "projectmanager.h"
40     #include "sdk_events.h"
41 #endif
42 
43 #include <map>
44 #include "projectloader.h"
45 #include "projectlayoutloader.h"
46 #include "selecttargetdlg.h"
47 #include "filegroupsandmasks.h"
48 #include "filefilters.h"
49 #include "annoyingdialog.h"
50 #include "genericmultilinenotesdlg.h"
51 #include "compilercommandgenerator.h"
52 #include "cbcolourmanager.h"
53 
54 // class constructor
cbProject(const wxString & filename)55 cbProject::cbProject(const wxString& filename) :
56     m_CustomMakefile(false),
57     m_Globs(),
58     m_FileArray(ProjectFile::CompareProjectFiles),
59     m_Loaded(false),
60     m_CurrentlyLoading(false),
61     m_PCHMode(pchSourceFile),
62     m_CurrentlyCompilingTarget(nullptr),
63     m_ExtendedObjectNamesGeneration(false),
64     m_AutoShowNotesOnLoad(false),
65     m_CheckForExternallyModifiedFiles(true),
66     m_pExtensionsElement(nullptr)
67 {
68     SetCompilerID(CompilerFactory::GetDefaultCompilerID());
69     SetModified(false);
70 
71     wxString realFile = realpath(filename);
72 
73     m_Files.clear();
74     m_FileArray.Clear();
75     if (!realFile.IsEmpty() && (wxFileExists(realFile) || wxDirExists(realFile)))
76     {
77         // existing project
78         m_Filename = realFile;
79         m_BasePath = GetBasePath();
80         Open();
81     }
82     else
83     {
84         // new project
85         SetModified(true);
86         if (realFile.IsEmpty())
87         {
88             m_Filename = CreateUniqueFilename();
89             m_Loaded   = SaveAs();
90         }
91         else
92         {
93             m_Filename = realFile;
94             m_Loaded   = Save();
95         }
96         if (m_Loaded)
97         {
98             wxFileName fname(m_Filename);
99             m_Title              = fname.GetName();
100             m_BasePath           = GetBasePath();
101             m_CommonTopLevelPath = GetBasePath() + wxFileName::GetPathSeparator();
102 
103             // moved to ProjectManager::LoadProject()
104             // see explanation there...
105 //            NotifyPlugins(cbEVT_PROJECT_OPEN);
106         }
107     }
108 }
109 
110 // class destructor
~cbProject()111 cbProject::~cbProject()
112 {
113     // moved to ProjectManager::CloseProject()
114     // see explanation there...
115 //    NotifyPlugins(cbEVT_PROJECT_CLOSE);
116 
117     ClearAllProperties();
118 }
119 
operator =(const cbProject & p)120 cbProject& cbProject::operator=(const cbProject &p)
121 {
122     CompileTargetBase::operator=(p);
123 
124     m_VirtualTargets = p.m_VirtualTargets;
125     m_CurrentlyCompilingTarget = nullptr;
126 
127     for (FilesList::iterator it = m_Files.begin(); it != m_Files.end();++it)
128         delete(*it);
129     m_Files.clear();
130 
131     typedef std::map<const ProjectFile*, ProjectFile*> OldToNewFilesMap;
132     OldToNewFilesMap oldToNewFiles;
133     for (ProjectFile *oldFile : p.m_Files)
134     {
135         ProjectFile *pf = new ProjectFile(this, *oldFile);
136         m_Files.insert(pf);
137         oldToNewFiles[oldFile] = pf;
138     }
139 
140     // Setup the autogenerated files. Probably this step could be moved in the previous loop.
141     for (ProjectFile *src : p.m_Files)
142     {
143         ProjectFile *dst = oldToNewFiles[src];
144         if (src->autoGeneratedBy)
145         {
146             OldToNewFilesMap::const_iterator itGen = oldToNewFiles.find(src->autoGeneratedBy);
147             dst->autoGeneratedBy = ((itGen != oldToNewFiles.end()) ? itGen->second : nullptr);
148         }
149 
150         for (ProjectFile *generatedFile : src->generatedFiles)
151         {
152             OldToNewFilesMap::const_iterator itGen = oldToNewFiles.find(generatedFile);
153             if (itGen != oldToNewFiles.end())
154                 dst->generatedFiles.push_back(itGen->second);
155         }
156     }
157 
158     for (size_t ii = 0; ii < m_Targets.GetCount(); ++ii)
159         delete m_Targets[ii];
160     m_Targets.Clear();
161     for (size_t ii = 0; ii < p.m_Targets.GetCount(); ++ii)
162     {
163         const ProjectBuildTarget *oldTarget = p.m_Targets[ii];
164         ProjectBuildTarget* target = new ProjectBuildTarget(*oldTarget, this);
165         m_Targets.Add(target);
166 
167         if (oldTarget == p.m_CurrentlyCompilingTarget)
168             m_CurrentlyCompilingTarget = target;
169 
170         // Copy the files for the target.
171         const FilesList &oldFiles = oldTarget->GetFilesList();
172         FilesList &newFiles = target->GetFilesList();
173         for (ProjectFile *pf : oldFiles)
174         {
175             OldToNewFilesMap::const_iterator itNew = oldToNewFiles.find(pf);
176             if (itNew != oldToNewFiles.end())
177                 newFiles.insert(itNew->second);
178         }
179     }
180 
181     m_ActiveTarget = p.m_ActiveTarget;
182     m_LastSavedActiveTarget = p.m_LastSavedActiveTarget;
183     m_DefaultExecuteTarget = p.m_DefaultExecuteTarget;
184     m_Makefile = p.m_Makefile;
185     m_CustomMakefile = p.m_CustomMakefile;
186     m_MakefileExecutionDir = p.m_MakefileExecutionDir;
187 
188     m_Globs = p.m_Globs;
189     m_FileArray.Clear();
190     m_ExpandedNodes = p.m_ExpandedNodes;
191     m_SelectedNodes = p.m_SelectedNodes;
192     m_Loaded = p.m_Loaded;
193     m_ProjectNode = p.m_ProjectNode;
194 
195     m_VirtualFolders = p.m_VirtualFolders;
196 
197     m_CurrentlyLoading = p.m_CurrentlyLoading;
198     m_CommonTopLevelPath = p.m_CommonTopLevelPath;
199     m_BasePath = p.m_BasePath;
200 
201     m_PCHMode = p.m_PCHMode;
202 
203     // Just regenerate the hash map for faster lookup of files.
204     m_ProjectFilesMap.clear();
205     for (ProjectFile *pf : m_Files)
206         m_ProjectFilesMap[UnixFilename(pf->relativeFilename)] = pf;
207 
208     m_LastModified = p.m_LastModified;
209 
210     m_ExtendedObjectNamesGeneration = p.m_ExtendedObjectNamesGeneration;
211     m_Notes = p.m_Notes;
212     m_AutoShowNotesOnLoad = p.m_AutoShowNotesOnLoad;
213     m_CheckForExternallyModifiedFiles = p.m_CheckForExternallyModifiedFiles;
214 
215     Delete(m_pExtensionsElement);
216     if (p.m_pExtensionsElement)
217         m_pExtensionsElement = new TiXmlElement(*p.m_pExtensionsElement);
218     else
219         m_pExtensionsElement = nullptr;
220     return *this;
221 }
222 
NotifyPlugins(wxEventType type,const wxString & targetName,const wxString & oldTargetName)223 void cbProject::NotifyPlugins(wxEventType type, const wxString& targetName, const wxString& oldTargetName)
224 {
225     CodeBlocksEvent event(type);
226     event.SetProject(this);
227     event.SetBuildTargetName(targetName);
228     event.SetOldBuildTargetName(oldTargetName);
229     Manager::Get()->ProcessEvent(event);
230 }
231 
SetCompilerID(const wxString & id)232 void cbProject::SetCompilerID(const wxString& id)
233 {
234 // TODO (mandrav##): Is this needed? The project's compiler has nothing to do with the targets' compilers...
235 
236     CompileTargetBase::SetCompilerID(id);
237     if (id != GetCompilerID())
238     {
239         // update object filenames
240         for (unsigned int i = 0; i < m_Targets.GetCount(); ++i)
241         {
242             ProjectBuildTarget* target = m_Targets[i];
243             if (target)
244             {
245                 Compiler* compiler = CompilerFactory::GetCompiler(target->GetCompilerID());
246                 if (!compiler)
247                     continue;
248 
249                 for (FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it)
250                 {
251                     ProjectFile* pf = *it;
252                     wxFileName obj(pf->GetObjName());
253                     if (   (FileTypeOf(pf->relativeFilename) != ftResource)
254                         && (obj.GetExt() == compiler->GetSwitches().objectExtension) )
255                     {
256                         obj.SetExt(compiler->GetSwitches().objectExtension);
257                         pf->SetObjName(obj.GetFullName());
258                     }
259                 }
260             }
261         }
262     }
263 }
264 
GetModified() const265 bool cbProject::GetModified() const
266 {
267     // check base options
268     if (CompileOptionsBase::GetModified())
269         return true;
270 
271     // check targets
272     for (unsigned int i = 0; i < m_Targets.GetCount(); ++i)
273     {
274         ProjectBuildTarget* target = m_Targets[i];
275         if (target->GetModified())
276             return true;
277     }
278 
279     return false;
280 }
281 
SetModified(bool modified)282 void cbProject::SetModified(bool modified)
283 {
284     CompileOptionsBase::SetModified(modified);
285 
286     // modify targets
287     for (unsigned int i = 0; i < m_Targets.GetCount(); ++i)
288     {
289         ProjectBuildTarget* target = m_Targets[i];
290         target->SetModified(modified);
291     }
292 
293     if (!modified)
294         m_LastSavedActiveTarget = m_ActiveTarget;
295 }
296 
SetMakefileCustom(bool custom)297 void cbProject::SetMakefileCustom(bool custom)
298 {
299     if (m_CustomMakefile != custom)
300     {
301         m_CustomMakefile = custom;
302         SetModified(true);
303     }
304 }
305 
CreateUniqueFilename()306 wxString cbProject::CreateUniqueFilename()
307 {
308     const wxString prefix = _("Untitled");
309     wxString tmp;
310     ProjectsArray* arr = Manager::Get()->GetProjectManager()->GetProjects();
311     int projCount = arr->GetCount();
312     int iter = 1;
313     bool ok = false;
314     tmp << prefix << wxString::Format(_T("%d"), iter);
315     while (!ok)
316     {
317         tmp.Clear();
318         tmp << prefix << wxString::Format(_T("%d"), iter);
319 
320         ok = true;
321         for (int i = 0; i < projCount; ++i)
322         {
323             cbProject* prj = arr->Item(i);
324             wxFileName fname(prj->GetFilename());
325 
326             if (fname.GetName().Matches(tmp))
327             {
328                 ok = false;
329                 break;
330             }
331         }
332         if (ok)
333             break;
334         ++iter;
335     }
336     return tmp << _T(".") << FileFilters::CODEBLOCKS_EXT;
337 }
338 
ClearAllProperties()339 void cbProject::ClearAllProperties()
340 {
341     // The macro manager stores pointers to projects and targets, so we need to clear it to prevent
342     // dangling pointer bugs.
343     Manager::Get()->GetMacrosManager()->Reset();
344 
345     Delete(m_pExtensionsElement);
346 
347     for (FilesList::iterator it = m_Files.begin(); it != m_Files.end();++it)
348         delete(*it);
349     m_Files.clear();
350     m_FileArray.Clear();
351     m_CompilerOptions.Clear();
352     m_LinkerOptions.Clear();
353     m_IncludeDirs.Clear();
354     m_LibDirs.Clear();
355 
356     while (m_Targets.GetCount())
357     {
358         ProjectBuildTarget* target = m_Targets[0];
359         delete target;
360         m_Targets.RemoveAt(0);
361     }
362     SetModified(true);
363 
364     NotifyPlugins(cbEVT_BUILDTARGET_SELECTED);
365 }
366 
Open()367 void cbProject::Open()
368 {
369     wxStopWatch timer;
370 
371     m_Loaded = false;
372     m_ProjectFilesMap.clear();
373     Delete(m_pExtensionsElement);
374 
375     if (!wxFileName::FileExists(m_Filename) && !wxFileName::DirExists(m_Filename))
376     {
377         wxString msg;
378         msg.Printf(_("Project '%s' does not exist..."), m_Filename.c_str());
379         cbMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
380         return;
381     }
382 
383     bool fileUpgraded = false;
384     bool fileModified = false;
385     wxFileName fname(m_Filename);
386     FileType ft = FileTypeOf(m_Filename);
387     if (ft == ftCodeBlocksProject)
388     {
389         Manager::Get()->GetLogManager()->Log(_("Opening ") + m_Filename);
390         m_CurrentlyLoading = true;
391         ProjectLoader loader(this);
392         m_Loaded = loader.Open(m_Filename, &m_pExtensionsElement);
393         fileUpgraded = loader.FileUpgraded();
394         fileModified = loader.FileModified();
395         m_CurrentlyLoading = false;
396 
397         if (m_Loaded)
398         {
399             CalculateCommonTopLevelPath();
400             Manager::Get()->GetLogManager()->Log(_("Done."));
401             if (!m_Targets.GetCount())
402                 AddDefaultBuildTarget();
403             // in case of batch build discard upgrade messages
404             fileUpgraded = fileUpgraded && !Manager::IsBatchBuild();
405             SetModified(ft != ftCodeBlocksProject || fileUpgraded || fileModified);
406 
407             // moved to ProjectManager::LoadProject()
408             // see explanation there...
409     //        NotifyPlugins(cbEVT_PROJECT_OPEN);
410 
411             if (fileUpgraded)
412             {
413                 InfoWindow::Display(m_Title,
414                   _("The loaded project file was generated\n"
415                     "with an older version of Code::Blocks.\n\n"
416                     "Code::Blocks can import older project files,\n"
417                     "but will always save in the current format."), 12000, 2000);
418             }
419             m_LastModified = fname.GetModificationTime();
420         }
421     }
422 
423     long time = timer.Time();
424     if (time >= 100)
425         Manager::Get()->GetLogManager()->Log(F(wxT("cbProject::Open took: %.3f seconds."),
426                                                time / 1000.0f));
427 } // end of Open
428 
CalculateCommonTopLevelPath()429 void cbProject::CalculateCommonTopLevelPath()
430 {
431     // find the common toplevel path
432     // for simple projects, this might be the path to the project file
433     // for projects where the project file is in a subdir, files will
434     // have ".." in their paths
435     const wxString sep       = wxFileName::GetPathSeparator();
436     wxFileName base           = GetBasePath() + sep;
437     wxString   vol            = base.GetVolume();
438     bool       prjHasUNCName  = base.GetFullPath().StartsWith(_T("\\\\"));
439 
440     Manager::Get()->GetLogManager()->DebugLog(_T("Project's base path: ") + base.GetFullPath());
441 
442     // This loop takes ~30ms for 1000 project files
443     // it's as fast as it can get, considered that it used to take ~1200ms ;)
444     // don't even bother making it faster - you can't :)
445 #ifdef ctlp_measuring
446     wxStopWatch sw;
447 #endif
448     for (FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it)
449     {
450         ProjectFile* f = (*it);
451         if (!f)
452             continue;
453 
454         if ( !vol.IsSameAs(f->file.GetVolume()) )
455             continue;
456 
457         bool fileHasUNCName = f->file.GetFullPath().StartsWith(_T("\\\\"));
458 
459         if (   (!prjHasUNCName &&  fileHasUNCName)
460             || ( prjHasUNCName && !fileHasUNCName) )
461         {
462             continue;
463         }
464 
465         wxString tmp     = f->relativeFilename;
466         wxString tmpbase = m_BasePath;
467 
468         size_t pos = 0;
469         while (   (pos < tmp.Length())
470                && (   (tmp.GetChar(pos) == _T('.'))
471                    || (tmp.GetChar(pos) == _T('/'))
472                    || (tmp.GetChar(pos) == _T('\\')) ) )
473         {
474             ++pos;
475         }
476         if ( (pos > 0) && (pos < tmp.Length()) )
477             tmpbase << sep << tmp.Left(pos) << sep;
478 
479         wxFileName tmpbaseF(tmpbase); tmpbaseF.Normalize(wxPATH_NORM_DOTS);
480         if (   (tmpbaseF.GetDirCount() < base.GetDirCount())
481             && (base.GetPath().StartsWith(tmpbaseF.GetPath())) )
482         {
483             base = tmpbaseF;
484         }
485     }
486 #ifdef ctlp_measuring
487     Manager::Get()->GetLogManager()->DebugLogError(F(_T("%s::%s:%d  took : %d ms"), cbC2U(__FILE__).c_str(),cbC2U(__PRETTY_FUNCTION__).c_str(), __LINE__, (int)sw.Time()));
488 #endif
489 
490     m_CommonTopLevelPath = base.GetFullPath();
491     Manager::Get()->GetLogManager()->DebugLog(_T("Project's common toplevel path: ") + m_CommonTopLevelPath);
492 
493     const wxString &projectBasePath = GetBasePath();
494 
495     for (FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it)
496     {
497         ProjectFile* f = (*it);
498         if (!f)
499             continue;
500 
501         wxString fileName = f->file.GetFullPath();
502         bool fileHasUNCName = fileName.StartsWith(_T("\\\\"));
503 
504         if (   (prjHasUNCName && fileHasUNCName)
505             || (   !prjHasUNCName
506                 && !fileHasUNCName
507                 && vol.IsSameAs(f->file.GetVolume()) ) )
508         {
509             wxFileName relFileCTLP(f->file);
510             relFileCTLP.MakeRelativeTo( m_CommonTopLevelPath );
511             wxFileName relFileBase(f->file);
512             relFileBase.MakeRelativeTo(projectBasePath);
513 
514             // The commented (old) method to obtain the relativeToCommonTopLevelPath is fast, but does *not* work, if you save
515             // the project on a different drive in a sub-folder of an existing source file on that (different) drive:
516             // I.e.: Project on C:\Folder\Project.cbp has file C:\Folder\SubFolder\foo.cpp and D:\Folder\bar.cpp
517             // Saved the project under D:\Folder\SubFolder\ProjectNew.cbp would cause a wrong computation of bar.cpp otherwise!!!
518 //            f->relativeToCommonTopLevelPath = fileName.Right(fileName.Length() - m_CommonTopLevelPath.Length());
519             // Using wxFileName instead, although its costly:
520             f->relativeToCommonTopLevelPath = relFileCTLP.GetFullPath();
521             f->relativeFilename             = relFileBase.GetFullPath();
522         }
523         else
524         {
525             f->relativeToCommonTopLevelPath = fileName;
526             f->relativeFilename             = fileName;
527         }
528 
529         f->SetObjName(f->relativeToCommonTopLevelPath);
530     }
531 }
532 
GetCommonTopLevelPath() const533 wxString cbProject::GetCommonTopLevelPath() const
534 {
535     return m_CommonTopLevelPath;
536 }
537 
Touch()538 void cbProject::Touch()
539 {
540     m_LastModified = wxDateTime::Now();
541     SetModified(true);
542 }
543 
SaveAs()544 bool cbProject::SaveAs()
545 {
546     wxFileName fname;
547     fname.Assign(m_Filename);
548     wxFileDialog dlg(Manager::Get()->GetAppWindow(),
549                      _("Save file"),
550                      fname.GetPath(),
551                      fname.GetFullName(),
552                      FileFilters::GetFilterString(_T('.') + FileFilters::CODEBLOCKS_EXT),
553                      wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
554 
555     PlaceWindow(&dlg);
556     if (dlg.ShowModal() != wxID_OK)
557         return false;
558 
559     wxFileName newName(dlg.GetPath());
560 
561     // if the filename has changed, we need to recalculate the common toplevel path
562     bool pathChanged = !newName.GetPath().IsSameAs(fname.GetPath());
563 
564     m_Filename = newName.GetFullPath();
565     fname.Assign(m_Filename);
566 
567     // make sure the project file uses the correct extension
568     // we don't use wxFileName::SetExt() because if the user has added a dot
569     // in the filename, the part after it would be interpreted as extension
570     // (and it might not be)
571     // so we just append the correct extension
572     if (!fname.GetExt().Matches(FileFilters::CODEBLOCKS_EXT))
573         fname.Assign(m_Filename + _T('.') + FileFilters::CODEBLOCKS_EXT);
574 
575 //    Manager::Get()->GetProjectManager()->GetTree()->SetItemText(m_ProjectNode, fname.GetFullName());
576     if (!m_Loaded)
577         AddDefaultBuildTarget();
578     if (pathChanged)
579         CalculateCommonTopLevelPath();
580     ProjectLoader loader(this);
581     if (loader.Save(m_Filename, m_pExtensionsElement))
582     {
583         fname = m_Filename;
584         m_LastModified = fname.GetModificationTime();
585         NotifyPlugins(cbEVT_PROJECT_SAVE);
586         return true;
587     }
588 
589     cbMessageBox(_("Couldn't save project ") + m_Filename + _("\n(Maybe the file is write-protected?)"), _("Warning"), wxICON_WARNING);
590     return false;
591 }
592 
Save()593 bool cbProject::Save()
594 {
595     if (m_Filename.IsEmpty())
596         return SaveAs();
597     ProjectLoader loader(this);
598     if (loader.Save(m_Filename, m_pExtensionsElement))
599     {
600         wxFileName fname(m_Filename);
601         m_LastModified = fname.GetModificationTime();
602         NotifyPlugins(cbEVT_PROJECT_SAVE);
603         return true;
604     }
605 
606     cbMessageBox(_("Couldn't save project ") + m_Filename + _("\n(Maybe the file is write-protected?)"), _("Warning"), wxICON_WARNING);
607     return false;
608 }
609 
SaveLayout()610 bool cbProject::SaveLayout()
611 {
612     if (m_Filename.IsEmpty())
613         return false;
614 
615     if (Manager::Get()->GetConfigManager(_T("app"))->ReadBool(_T("/environment/enable_project_layout"), true) == false)
616         return true;
617 
618     wxFileName fname(m_Filename);
619     fname.SetExt(_T("layout"));
620     ProjectLayoutLoader loader(this);
621     return loader.Save(fname.GetFullPath());
622 }
623 
LoadLayout()624 bool cbProject::LoadLayout()
625 {
626     if (m_Filename.IsEmpty())
627         return false;
628 
629     if (Manager::Get()->GetConfigManager(_T("app"))->ReadBool(_T("/environment/enable_project_layout"), true) == false)
630         return true;
631 
632     int openmode = Manager::Get()->GetConfigManager(_T("project_manager"))->ReadInt(_T("/open_files"), (long int)1);
633     if (openmode==2)
634         return true; // Do not open any files
635 
636     Manager::Get()->GetEditorManager()->HideNotebook();
637 
638     bool result = false;
639     if (openmode == 0) // Open all files
640     {
641         FilesList::iterator it = m_Files.begin();
642         while (it != m_Files.end())
643         {
644             ProjectFile* f = *it++;
645             Manager::Get()->GetEditorManager()->Open(f->file.GetFullPath(),0,f);
646         }
647         result = true;
648     }
649     else if (openmode == 1)// Open last open files
650     {
651         wxFileName fname(m_Filename);
652         fname.SetExt(_T("layout"));
653         ProjectLayoutLoader loader(this);
654         if (loader.Open(fname.GetFullPath()))
655         {
656             typedef std::map<int, ProjectFile*> open_files_map;
657             open_files_map open_files;
658 
659             // Get all files to open and sort them according to their tab-position:
660             FilesList::iterator it = m_Files.begin();
661             while (it != m_Files.end())
662             {
663                 ProjectFile* f = *it++;
664                 // do not try to open files that do not exist, but have fileOpen set to true
665                 if (f->editorOpen && wxFileExists(f->file.GetFullPath()))
666                     open_files[f->editorTabPos] = f;
667                 else
668                     f->editorOpen = false;
669             }
670 
671             // Load all requested files
672             std::vector<LoaderBase*> filesInMemory;
673             for (open_files_map::iterator ofm_it = open_files.begin(); ofm_it != open_files.end(); ++ofm_it)
674                 filesInMemory.push_back(Manager::Get()->GetFileManager()->Load((*ofm_it).second->file.GetFullPath()));
675 
676             // Open all requested files:
677             size_t i = 0;
678             for (open_files_map::iterator ofm_it = open_files.begin(); ofm_it != open_files.end(); ++ofm_it)
679             {
680                 cbEditor* ed = Manager::Get()->GetEditorManager()->Open(filesInMemory[i], (*ofm_it).second->file.GetFullPath(),0,(*ofm_it).second);
681                 if (ed)
682                     ed->SetProjectFile((*ofm_it).second);
683                 ++i;
684             }
685 
686             ProjectFile* f = loader.GetTopProjectFile();
687             if (f)
688             {
689                 Manager::Get()->GetLogManager()->DebugLog(_T("Top Editor: ") + f->file.GetFullPath());
690                 EditorBase* eb = Manager::Get()->GetEditorManager()->Open(f->file.GetFullPath());
691                 if (eb)
692                     eb->Activate();
693             }
694             loader.LoadNotebookLayout();
695         }
696         result = true;
697     }
698 
699     Manager::Get()->GetEditorManager()->ShowNotebook();
700 
701     return result;
702 }
703 
BeginAddFiles()704 void cbProject::BeginAddFiles()
705 {
706     CodeBlocksEvent event(cbEVT_PROJECT_BEGIN_ADD_FILES);
707     event.SetProject(this);
708     Manager::Get()->ProcessEvent(event);
709 }
710 
EndAddFiles()711 void cbProject::EndAddFiles()
712 {
713     CodeBlocksEvent event(cbEVT_PROJECT_END_ADD_FILES);
714     event.SetProject(this);
715     Manager::Get()->ProcessEvent(event);
716 }
717 
BeginRemoveFiles()718 void cbProject::BeginRemoveFiles()
719 {
720     CodeBlocksEvent event(cbEVT_PROJECT_BEGIN_REMOVE_FILES);
721     event.SetProject(this);
722     Manager::Get()->ProcessEvent(event);
723 }
724 
EndRemoveFiles()725 void cbProject::EndRemoveFiles()
726 {
727     CodeBlocksEvent event(cbEVT_PROJECT_END_REMOVE_FILES);
728     event.SetProject(this);
729     Manager::Get()->ProcessEvent(event);
730 }
731 
AddFile(const wxString & targetName,const wxString & filename,bool compile,bool link,unsigned short int weight)732 ProjectFile* cbProject::AddFile(const wxString& targetName, const wxString& filename, bool compile, bool link, unsigned short int weight)
733 {
734     int idx = IndexOfBuildTargetName(targetName);
735     return AddFile(idx, filename, compile, link, weight);
736 }
737 
AddFile(int targetIndex,const wxString & filename,bool compile,bool link,cb_unused unsigned short int weight)738 ProjectFile* cbProject::AddFile(int targetIndex, const wxString& filename, bool compile, bool link, cb_unused unsigned short int weight)
739 {
740 //  NOTE (Rick#1#): When loading the project, do not search for existing files
741 //  (Assuming that there are no duplicate entries in the .cbp file)
742 //  This saves us a lot of processing when loading large projects.
743 //  Remove the if to do the search anyway
744 
745 //  NOTE (mandrav#1#): We can't ignore that because even if we can rely on .cbp
746 //  containing discrete files, we can't do that for imported projects...
747 //  This means we have to search anyway.
748 //  NP though, I added a hashmap for fast searches in GetFileByFilename()
749 
750 /* NOTE (mandrav#1#): Calling GetFileByFilename() twice, is costly.
751     Instead of searching for duplicate files when entering here,
752     we 'll search before exiting.
753     The rationale is that by then, we'll have the relative filename
754     in our own representation and this will make everything quicker
755     (check GetFileByFilename implementation to understand why)...
756 */
757 //    f = GetFileByFilename(filename, true, true);
758 //    if (!f)
759 //        f = GetFileByFilename(filename, false, true);
760 //    if (f)
761 //    {
762 //        if (targetIndex >= 0 && targetIndex < (int)m_Targets.GetCount())
763 //            f->AddBuildTarget(m_Targets[targetIndex]->GetTitle());
764 //        return f;
765 //    }
766 
767     // quick test
768     ProjectFile* pf = m_ProjectFilesMap[UnixFilename(filename)];
769     if (pf)
770         return pf;
771 
772     // create file
773     pf = new ProjectFile(this);
774     bool localCompile, localLink;
775     wxFileName fname(filename);
776 
777     const wxString &ext = fname.GetExt();
778     if (ext.IsSameAs(FileFilters::C_EXT, false))
779         pf->compilerVar = _T("CC");
780     else if (platform::windows && ext.IsSameAs(FileFilters::RESOURCE_EXT))
781         pf->compilerVar = _T("WINDRES");
782     else
783         pf->compilerVar = _T("CPP"); // default
784 
785     if (!m_Targets.GetCount())
786     {
787         // no targets in project; add default
788         AddDefaultBuildTarget();
789         if (!m_Targets.GetCount())
790         {
791             delete pf;
792             return nullptr; // if that failed, fail addition of file...
793         }
794     }
795 
796     const FileType ft = FileTypeOf(filename);
797     const bool isResource = (ft == ftResource);
798 
799 // NOTE (mandrav#1#): targetIndex == -1 means "don't add file to any targets"
800 // This case gives us problems though because then we don't know the compiler
801 // this file will be using (per-target) which means we can't decide if it
802 // generates any files...
803 // We solve this issue with a hack (only for the targetIndex == -1 case!):
804 // We iterate all available target compilers tool and use generatedFiles from
805 // all of them. It works and is also safe.
806     std::map<Compiler*, const CompilerTool*> GenFilesHackMap;
807     if (targetIndex < 0 || targetIndex >= (int)m_Targets.GetCount())
808     {
809         Compiler* c = CompilerFactory::GetCompiler( GetCompilerID() );
810         if (c)
811         {
812             const CompilerTool* t = c->GetCompilerTool(isResource ? ctCompileResourceCmd : ctCompileObjectCmd, ext);
813             if (t && t->generatedFiles.GetCount())
814                 GenFilesHackMap[c] = t;
815         }
816 
817         for (unsigned int i = 0; i < m_Targets.GetCount(); ++i)
818         {
819             c = CompilerFactory::GetCompiler(m_Targets[i]->GetCompilerID());
820             if (GenFilesHackMap.find(c) != GenFilesHackMap.end())
821                 continue; // compiler already in map
822 
823             if (c)
824             {
825                 const CompilerTool* t = c->GetCompilerTool(isResource ? ctCompileResourceCmd : ctCompileObjectCmd, ext);
826                 if (t && t->generatedFiles.GetCount())
827                     GenFilesHackMap[c] = t;
828             }
829         }
830     }
831     else
832     {
833         // targetIndex is valid: just add a single entry to the map
834         Compiler* c = CompilerFactory::GetCompiler(m_Targets[targetIndex]->GetCompilerID());
835         if (c)
836         {
837             const CompilerTool* t = c->GetCompilerTool(isResource ? ctCompileResourceCmd : ctCompileObjectCmd, ext);
838             if (t && t->generatedFiles.GetCount())
839                 GenFilesHackMap[c] = t;
840         }
841     }
842 
843     // so... now, if GenFilesHackMap is not empty, we know
844     // 1) this file generates other files and
845     // 2) iterating the map will give us the generated file names :)
846 
847     // add the build target
848     if (targetIndex >= 0 && targetIndex < (int)m_Targets.GetCount())
849         pf->AddBuildTarget(m_Targets[targetIndex]->GetTitle());
850 
851     localCompile =    compile
852                    && (   ft == ftSource
853                        || ft == ftResource
854                        || !GenFilesHackMap.empty() );
855     localLink =    link
856                 && (   ft == ftSource
857                     || ft == ftResource
858                     || ft == ftObject
859                     || ft == ftResourceBin
860                     || ft == ftStaticLib );
861 
862     pf->compile = localCompile;
863     pf->link    = localLink;
864 
865     wxString local_filename = filename;
866     const wxString &projectBasePath = GetBasePath();
867 
868 #ifdef __WXMSW__
869     // for Windows, make sure the filename is not on another drive...
870     if (   (local_filename.Length() > 1)
871         && (local_filename.GetChar(1) == _T(':'))
872         && (fname.GetVolume() != wxFileName(m_Filename).GetVolume()) )
873         // (this is a quick test to avoid the costly wxFileName ctor below)
874     {
875         fname.Assign(filename);
876     }
877     else if (fname.GetFullPath().StartsWith(_T("\\\\"))) // UNC path
878     {
879         fname.Assign(filename);
880     }
881     else
882 #endif
883     {
884         // make sure the filename is relative to the project's base path
885         if (fname.IsAbsolute())
886         {
887             fname.MakeRelativeTo(projectBasePath);
888             local_filename = fname.GetFullPath();
889         }
890         // this call is costly (wxFileName ctor):
891         fname.Assign(projectBasePath + wxFILE_SEP_PATH + local_filename);
892     }
893     fname.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_TILDE, projectBasePath);
894 
895     const wxString &fullFilename = realpath(fname.GetFullPath());
896     pf->file = fullFilename;
897 
898     // Make sure the relativeFilename is really relative to the project file.
899     // This is a bit slower but a bit more correct.
900     fname.MakeRelativeTo(projectBasePath);
901     const wxString &fixedRelativePath = fname.GetFullPath();
902     pf->relativeFilename = UnixFilename(fixedRelativePath);
903 
904     // now check if we have already added this file
905     // if we have, return the existing file, but add the specified target
906     ProjectFile* existing = GetFileByFilename(pf->relativeFilename, true, true);
907     if (existing)
908     {
909         delete pf;
910         if (targetIndex >= 0 && targetIndex < (int)m_Targets.GetCount())
911             existing->AddBuildTarget(m_Targets[targetIndex]->GetTitle());
912         return existing;
913     }
914 
915     m_Files.insert(pf);
916     if (!m_CurrentlyLoading)
917     {
918         // Only add the file, if we are not currently loading the project and
919         // m_FileArray is already initialised.
920         // Initialising is done in the getter-function (GetFile(index), to save time,
921         // because in many cases m_FileArray is not needed
922         if ( m_FileArray.GetCount() > 0 )
923             m_FileArray.Add(pf);
924         // check if we really need to recalculate the common top-level path for the project
925         if ( !fullFilename.StartsWith(m_CommonTopLevelPath) )
926             CalculateCommonTopLevelPath();
927         else
928         {
929             // set f->relativeToCommonTopLevelPath
930             pf->relativeToCommonTopLevelPath = fullFilename.Right(fullFilename.Length() - m_CommonTopLevelPath.Length());
931         }
932     }
933     SetModified(true);
934     m_ProjectFilesMap[pf->relativeFilename] = pf; // add to hashmap
935 
936     if (!wxFileExists(fullFilename))
937         pf->SetFileState(fvsMissing);
938     else if (!wxFile::Access(fullFilename.c_str(), wxFile::write)) // readonly
939         pf->SetFileState(fvsReadOnly);
940 
941     if ( !GenFilesHackMap.empty() )
942     {
943         // auto-generated files!
944         wxFileName tmp = pf->file;
945         for (std::map<Compiler*, const CompilerTool*>::const_iterator it = GenFilesHackMap.begin(); it != GenFilesHackMap.end(); ++it)
946         {
947             const CompilerTool* tool = it->second;
948             for (size_t i = 0; i < tool->generatedFiles.GetCount(); ++i)
949             {
950                 tmp.SetFullName(tool->generatedFiles[i]);
951                 wxString tmps = tmp.GetFullPath();
952                 // any macro replacements here, should also be done in
953                 // CompilerCommandGenerator::GenerateCommandLine !!!
954                 tmps.Replace(_T("$file_basename"), pf->file.GetName()); // old way - remove later
955                 tmps.Replace(_T("$file_name"),     pf->file.GetName());
956                 tmps.Replace(_T("$file_dir"),      pf->file.GetPath());
957                 tmps.Replace(_T("$file_ext"),      pf->file.GetExt());
958                 tmps.Replace(_T("$file"),          pf->file.GetFullName());
959                 Manager::Get()->GetMacrosManager()->ReplaceMacros(tmps);
960 
961                 ProjectFile* pfile = AddFile(targetIndex, UnixFilename(tmps));
962                 if (!pfile)
963                     Manager::Get()->GetLogManager()->DebugLog(_T("Can't add auto-generated file ") + tmps);
964                 else
965                 {
966                     pf->generatedFiles.push_back(pfile);
967                     pfile->SetAutoGeneratedBy(pf);
968                 }
969             }
970         }
971     }
972 
973     return pf;
974 }
975 
RemoveFile(ProjectFile * pf)976 bool cbProject::RemoveFile(ProjectFile* pf)
977 {
978     if (!pf)
979         return false;
980     m_ProjectFilesMap.erase(UnixFilename(pf->relativeFilename)); // remove from hashmap
981     Manager::Get()->GetEditorManager()->Close(pf->file.GetFullPath());
982 
983     {
984 		FilesList::iterator it = m_Files.find(pf);
985 
986 		if (it == m_Files.end())
987 		{
988 			Manager::Get()->GetLogManager()->DebugLog(_T("Can't locate node for ProjectFile* !"));
989 		}
990 		else
991 		{
992 			if (!m_FileArray.IsEmpty())
993 				m_FileArray.Remove(*it);
994 
995 			m_Files.erase(it);
996 		}
997 	}
998 	// remove this file from all targets too
999     for (unsigned int i = 0; i < m_Targets.GetCount(); ++i)
1000     {
1001         if (ProjectBuildTarget* target = m_Targets[i])
1002             target->RemoveFile(pf);
1003     }
1004 
1005     // if this is auto-generated, inform "parent"
1006     if (pf->AutoGeneratedBy())
1007     {
1008         ProjectFilesVector::iterator it = std::find(pf->AutoGeneratedBy()->generatedFiles.begin(),
1009             pf->AutoGeneratedBy()->generatedFiles.end(), pf);
1010         pf->AutoGeneratedBy()->generatedFiles.erase(it);
1011     }
1012 
1013     // also remove generated files (see above code: files will empty the vector)
1014     while (pf->generatedFiles.size())
1015         RemoveFile(pf->generatedFiles[0]);
1016     pf->generatedFiles.clear();
1017 
1018     delete pf;
1019 
1020     SetModified(true);
1021     return true;
1022 }
1023 
GetVirtualFolders() const1024 const wxArrayString& cbProject::GetVirtualFolders() const
1025 {
1026     return m_VirtualFolders;
1027 }
1028 
AppendUniqueVirtualFolder(const wxString & folder)1029 bool cbProject::AppendUniqueVirtualFolder(const wxString &folder)
1030 {
1031     if (m_VirtualFolders.Index(folder)==wxNOT_FOUND)
1032     {
1033         m_VirtualFolders.push_back(folder);
1034         return true;
1035     }
1036     else
1037         return false;
1038 }
1039 
RemoveVirtualFolders(const wxString & folder)1040 void cbProject::RemoveVirtualFolders(const wxString &folder)
1041 {
1042     for (int i = (int)m_VirtualFolders.GetCount() - 1; i >= 0; --i)
1043     {
1044         if (m_VirtualFolders[i].StartsWith(folder))
1045             m_VirtualFolders.RemoveAt(i);
1046     }
1047     // now loop all project files and remove them from this virtual folder
1048     for (FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it)
1049     {
1050         ProjectFile* f = *it;
1051         if (f && !f->virtual_path.IsEmpty())
1052         {
1053             if (f->virtual_path.StartsWith(folder)) // need 2 checks because of last separator
1054                 f->virtual_path.Clear();
1055         }
1056     }
1057 
1058     SetModified(true);
1059 }
1060 
ReplaceVirtualFolder(const wxString & oldFolder,const wxString & newFolder)1061 void cbProject::ReplaceVirtualFolder(const wxString &oldFolder, const wxString &newFolder)
1062 {
1063     int idx = m_VirtualFolders.Index(oldFolder);
1064     if (idx != wxNOT_FOUND)
1065         m_VirtualFolders[idx] = newFolder;
1066     else
1067         m_VirtualFolders.Add(newFolder);
1068 
1069     // now loop all project files and rename this virtual folder
1070     for (FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it)
1071     {
1072         ProjectFile* f = *it;
1073         if (f && !f->virtual_path.IsEmpty())
1074         {
1075             if (f->virtual_path.StartsWith(oldFolder))
1076                 f->virtual_path.Replace(oldFolder, newFolder);
1077         }
1078     }
1079 
1080     SetModified(true);
1081 }
1082 
1083 
SetVirtualFolders(const wxArrayString & folders)1084 void cbProject::SetVirtualFolders(const wxArrayString& folders)
1085 {
1086     m_VirtualFolders = folders;
1087     for (size_t i = 0; i < m_VirtualFolders.GetCount(); ++i)
1088     {
1089         m_VirtualFolders[i].Replace(_T("/"), wxString(wxFILE_SEP_PATH));
1090         m_VirtualFolders[i].Replace(_T("\\"), wxString(wxFILE_SEP_PATH));
1091     }
1092 }
1093 
SaveTreeState(wxTreeCtrl * tree)1094 void cbProject::SaveTreeState(wxTreeCtrl* tree)
1095 {
1096     ::SaveTreeState(tree, m_ProjectNode, m_ExpandedNodes, m_SelectedNodes);
1097 }
1098 
RestoreTreeState(wxTreeCtrl * tree)1099 void cbProject::RestoreTreeState(wxTreeCtrl* tree)
1100 {
1101     ::RestoreTreeState(tree, m_ProjectNode, m_ExpandedNodes, m_SelectedNodes);
1102 }
1103 
GetMakefile() const1104 const wxString& cbProject::GetMakefile() const
1105 {
1106     if (!m_Makefile.IsEmpty())
1107         return m_Makefile;
1108 
1109     wxFileName makefile(m_Makefile);
1110     makefile.Assign(m_Filename);
1111     makefile.SetName(_T("Makefile"));
1112     makefile.SetExt(_T(""));
1113     makefile.MakeRelativeTo( GetBasePath() );
1114 
1115     m_Makefile = makefile.GetFullPath();
1116 
1117     return m_Makefile;
1118 }
1119 
SetMakefileExecutionDir(const wxString & dir)1120 void cbProject::SetMakefileExecutionDir(const wxString& dir)
1121 {
1122     if (m_MakefileExecutionDir != dir)
1123     {
1124         m_MakefileExecutionDir = dir;
1125         SetModified(true);
1126     }
1127 }
1128 
GetMakefileExecutionDir()1129 wxString cbProject::GetMakefileExecutionDir()
1130 {
1131     if (m_MakefileExecutionDir.IsEmpty())
1132     {
1133         wxFileName execution_dir( GetBasePath() );
1134         m_MakefileExecutionDir = execution_dir.GetFullPath();
1135     }
1136     return m_MakefileExecutionDir;
1137 }
1138 
GetExecutionDir()1139 wxString cbProject::GetExecutionDir()
1140 {
1141     if (!m_CustomMakefile)
1142         return GetBasePath();
1143 
1144     return GetMakefileExecutionDir();
1145 }
1146 
GetFile(int index)1147 ProjectFile* cbProject::GetFile(int index)
1148 {
1149     if (m_FileArray.GetCount() == 0)
1150     {
1151         for (FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it)
1152         {
1153             if (!*it)
1154                 continue;
1155             m_FileArray.Add((ProjectFile*)*it);
1156         }
1157     }
1158 
1159     if (index < 0 || index >= static_cast<int>(m_Files.size()))
1160         return NULL;
1161 
1162     return m_FileArray.Item(index);
1163 }
1164 
GetFileByFilename(const wxString & filename,bool isRelative,bool isUnixFilename)1165 ProjectFile* cbProject::GetFileByFilename(const wxString& filename, bool isRelative, bool isUnixFilename)
1166 {
1167     // m_ProjectFilesMap keeps UnixFilename(ProjectFile::relativeFilename)
1168     wxString tmp = filename;
1169     if (!isRelative)
1170     {
1171         // if the search is not relative, make it
1172         wxFileName fname(realpath(filename));
1173         fname.MakeRelativeTo( GetBasePath() );
1174         tmp = fname.GetFullPath();
1175     }
1176     else
1177     {
1178         // make sure filename doesn't start with ".\"
1179         // our own relative files don't have it, so the search would fail
1180         // this happens when importing MS projects...
1181         if (tmp.StartsWith(_T(".\\")) ||
1182             tmp.StartsWith(_T("./")))
1183         {
1184             tmp.Remove(0, 2);
1185         }
1186     }
1187 
1188     if (isUnixFilename)
1189         return m_ProjectFilesMap[tmp];
1190 
1191     return m_ProjectFilesMap[UnixFilename(tmp)];
1192 }
1193 
QueryCloseAllFiles()1194 bool cbProject::QueryCloseAllFiles()
1195 {
1196     FilesList::iterator it = m_Files.begin();
1197     while (it != m_Files.end())
1198     {
1199         ProjectFile* f = *it++;
1200         cbEditor* ed = Manager::Get()->GetEditorManager()->IsBuiltinOpen(f->file.GetFullPath());
1201         if (ed && ed->GetModified())
1202         {
1203             if (!Manager::Get()->GetEditorManager()->QueryClose(ed))
1204                 return false;
1205         }
1206     }
1207     return true;
1208 }
1209 
CloseAllFiles(bool dontsave)1210 bool cbProject::CloseAllFiles(bool dontsave)
1211 {
1212     // first try to close modified editors
1213 
1214     if (!dontsave && !QueryCloseAllFiles())
1215             return false;
1216 
1217     // now free the rest of the project files
1218     Manager::Get()->GetEditorManager()->HideNotebook();
1219     for (FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it)
1220     {
1221         ProjectFile* f = *it;
1222         if (f)
1223             Manager::Get()->GetEditorManager()->Close(f->file.GetFullPath(),true);
1224         delete f;
1225     }
1226     m_FileArray.Clear();
1227     m_Files.clear();
1228     Manager::Get()->GetEditorManager()->ShowNotebook();
1229 
1230     return true;
1231 }
1232 
SaveAllFiles()1233 bool cbProject::SaveAllFiles()
1234 {
1235     int count = m_Files.size();
1236     FilesList::iterator it = m_Files.begin();
1237     while (it != m_Files.end())
1238     {
1239         ProjectFile* f = *it++;
1240         if (Manager::Get()->GetEditorManager()->Save(f->file.GetFullPath()))
1241             --count;
1242     }
1243     return count == 0;
1244 }
1245 
SelectTarget(int initial,bool evenIfOne)1246 int cbProject::SelectTarget(int initial, bool evenIfOne)
1247 {
1248     if (!evenIfOne && GetBuildTargetsCount() == 1)
1249         return 0;
1250 
1251     SelectTargetDlg dlg(nullptr, this, initial);
1252     PlaceWindow(&dlg);
1253     if (dlg.ShowModal() == wxID_OK)
1254         return dlg.GetSelection();
1255 
1256     return -1;
1257 }
1258 
1259 // Build targets
1260 
AddDefaultBuildTarget()1261 ProjectBuildTarget* cbProject::AddDefaultBuildTarget()
1262 {
1263     return AddBuildTarget(_T("default"));
1264 }
1265 
AddBuildTarget(const wxString & targetName)1266 ProjectBuildTarget* cbProject::AddBuildTarget(const wxString& targetName)
1267 {
1268     if (GetBuildTarget(targetName)) // Don't add the target if it exists
1269         return nullptr;
1270     ProjectBuildTarget* target = new ProjectBuildTarget(this);
1271     target->m_Filename = m_Filename; // really important
1272     target->SetTitle(targetName);
1273     target->SetCompilerID(GetCompilerID()); // same compiler as project's
1274     target->SetOutputFilename(wxFileName(GetOutputFilename()).GetFullName());
1275     target->SetWorkingDir(_T("."));
1276     target->SetObjectOutput(_T(".objs"));
1277     target->SetDepsOutput(_T(".deps"));
1278     m_Targets.Add(target);
1279 
1280     // remove any virtual targets with the same name
1281     if (HasVirtualBuildTarget(targetName))
1282     {
1283         RemoveVirtualBuildTarget(targetName);
1284         Manager::Get()->GetLogManager()->LogWarning(F(_T("Deleted existing virtual target '%s' because real target was added with the same name"), targetName.wx_str()));
1285     }
1286 
1287     SetModified(true);
1288 
1289     NotifyPlugins(cbEVT_BUILDTARGET_ADDED, targetName);
1290     NotifyPlugins(cbEVT_PROJECT_TARGETS_MODIFIED);
1291     return target;
1292 }
1293 
RenameBuildTarget(int index,const wxString & targetName)1294 bool cbProject::RenameBuildTarget(int index, const wxString& targetName)
1295 {
1296     ProjectBuildTarget* target = GetBuildTarget(index);
1297     if (target)
1298     {
1299         wxString oldTargetName = target->GetTitle();
1300 
1301         // rename target if referenced in any virtual target too
1302         for (VirtualBuildTargetsMap::iterator it = m_VirtualTargets.begin(); it != m_VirtualTargets.end(); ++it)
1303         {
1304             wxArrayString& tgts = it->second;
1305             index = tgts.Index(target->GetTitle());
1306             if (index != -1)
1307                 tgts[index] = targetName;
1308         }
1309 
1310         // rename target for all files that reference it
1311         for (FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it)
1312         {
1313             ProjectFile* pf = *it;
1314             pf->RenameBuildTarget(target->GetTitle(), targetName);
1315         }
1316 
1317         // finally rename the target
1318         target->SetTitle(targetName);
1319         SetModified(true);
1320         NotifyPlugins(cbEVT_BUILDTARGET_RENAMED, targetName, oldTargetName);
1321         NotifyPlugins(cbEVT_PROJECT_TARGETS_MODIFIED);
1322         return true;
1323     }
1324     return false;
1325 }
1326 
RenameBuildTarget(const wxString & oldTargetName,const wxString & newTargetName)1327 bool cbProject::RenameBuildTarget(const wxString& oldTargetName, const wxString& newTargetName)
1328 {
1329     return RenameBuildTarget(IndexOfBuildTargetName(oldTargetName), newTargetName);
1330 }
1331 
DuplicateBuildTarget(int index,const wxString & newName)1332 ProjectBuildTarget* cbProject::DuplicateBuildTarget(int index, const wxString& newName)
1333 {
1334     ProjectBuildTarget* newTarget = nullptr;
1335     ProjectBuildTarget* target = GetBuildTarget(index);
1336     if (target)
1337     {
1338         newTarget = new ProjectBuildTarget(*target);
1339         wxString newTargetName = !newName.IsEmpty() ? newName : (_("Copy of ") + target->GetTitle());
1340         newTarget->SetTitle(newTargetName);
1341         // just notify the files of this target that they belong to the new target too
1342         for (FilesList::iterator it = newTarget->GetFilesList().begin(); it != newTarget->GetFilesList().end(); ++it)
1343         {
1344             ProjectFile* pf = *it;
1345             pf->AddBuildTarget(newTargetName);
1346         }
1347         SetModified(true);
1348         m_Targets.Add(newTarget);
1349         // send also the old target name, so plugins see that the target is duplicated and not a new one added
1350         // so that plugin specific parameters can be copied, too.
1351         NotifyPlugins(cbEVT_BUILDTARGET_ADDED, newName, target->GetTitle());
1352         NotifyPlugins(cbEVT_PROJECT_TARGETS_MODIFIED);
1353     }
1354     return newTarget;
1355 }
1356 
DuplicateBuildTarget(const wxString & targetName,const wxString & newName)1357 ProjectBuildTarget* cbProject::DuplicateBuildTarget(const wxString& targetName, const wxString& newName)
1358 {
1359     return DuplicateBuildTarget(IndexOfBuildTargetName(targetName), newName);
1360 }
1361 
ExportTargetAsProject(int index)1362 bool cbProject::ExportTargetAsProject(int index)
1363 {
1364     ProjectBuildTarget* target = GetBuildTarget(index);
1365     if (!target)
1366         return false;
1367     return ExportTargetAsProject(target->GetTitle());
1368 }
1369 
ExportTargetAsProject(const wxString & targetName)1370 bool cbProject::ExportTargetAsProject(const wxString& targetName)
1371 {
1372     ProjectBuildTarget* target = GetBuildTarget(targetName);
1373     if (!target)
1374         return false;
1375 
1376     // ask for the new project's name
1377     wxString newName = cbGetTextFromUser(_("Please enter the new project's name (no path, no extension)."),
1378                                         _("Export target as new project"),
1379                                         target->GetTitle());
1380     if (newName.IsEmpty())
1381         return false;
1382     wxFileName fname(GetFilename());
1383     fname.SetName(newName);
1384 
1385     Save();
1386     bool alreadyModified = GetModified();
1387     wxString oldTitle = GetTitle();
1388     SetTitle(targetName);
1389 
1390     ProjectLoader loader(this);
1391     bool ret = loader.ExportTargetAsProject(fname.GetFullPath(), target->GetTitle(), m_pExtensionsElement);
1392 
1393     SetTitle(oldTitle);
1394     if (!alreadyModified)
1395         SetModified(false);
1396 
1397     return ret;
1398 }
1399 
RemoveBuildTarget(int index)1400 bool cbProject::RemoveBuildTarget(int index)
1401 {
1402     ProjectBuildTarget* target = GetBuildTarget(index);
1403     if (target)
1404     {
1405         const wxString targetTitle = target->GetTitle();
1406 
1407         // remove target from any virtual targets it belongs to
1408         for (VirtualBuildTargetsMap::iterator it = m_VirtualTargets.begin(); it != m_VirtualTargets.end(); ++it)
1409         {
1410             wxArrayString& tgts = it->second;
1411             int virt_idx = tgts.Index(targetTitle);
1412             if (virt_idx != -1)
1413                 tgts.RemoveAt(virt_idx);
1414         }
1415 
1416         // remove target from any project files that reference it
1417         for (FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it)
1418         {
1419             ProjectFile* pf = *it;
1420             pf->RemoveBuildTarget(targetTitle);
1421         }
1422 
1423         // notify plugins, before the target is deleted, to make a cleanup possible before the target is really deleted
1424         NotifyPlugins(cbEVT_BUILDTARGET_REMOVED, targetTitle);
1425         // finally remove the target
1426         delete target;
1427         m_Targets.RemoveAt(index);
1428         SetModified(true);
1429         NotifyPlugins(cbEVT_PROJECT_TARGETS_MODIFIED);
1430         return true;
1431     }
1432     return false;
1433 }
1434 
RemoveBuildTarget(const wxString & targetName)1435 bool cbProject::RemoveBuildTarget(const wxString& targetName)
1436 {
1437     return RemoveBuildTarget(IndexOfBuildTargetName(targetName));
1438 }
1439 
IndexOfBuildTargetName(const wxString & targetName) const1440 int cbProject::IndexOfBuildTargetName(const wxString& targetName) const
1441 {
1442     for (unsigned int i = 0; i < m_Targets.GetCount(); ++i)
1443     {
1444         ProjectBuildTarget* target = m_Targets[i];
1445         if (target->GetTitle().Matches(targetName))
1446             return i;
1447     }
1448     return -1;
1449 }
1450 
BuildTargetValid(const wxString & name,bool virtuals_too) const1451 bool cbProject::BuildTargetValid(const wxString& name, bool virtuals_too) const
1452 {
1453     if (virtuals_too && HasVirtualBuildTarget(name))
1454         return true;
1455     else if (IndexOfBuildTargetName(name) != -1)
1456         return true;
1457 
1458     return false;
1459 }
1460 
GetFirstValidBuildTargetName(bool virtuals_too) const1461 wxString cbProject::GetFirstValidBuildTargetName(bool virtuals_too) const
1462 {
1463     if (virtuals_too && !m_VirtualTargets.empty())
1464         return m_VirtualTargets.begin()->first;
1465     else if (m_Targets.GetCount() && m_Targets[0])
1466         return m_Targets[0]->GetTitle();
1467 
1468     return wxEmptyString;
1469 }
1470 
SetActiveBuildTarget(const wxString & name)1471 bool cbProject::SetActiveBuildTarget(const wxString& name)
1472 {
1473     if (name == m_ActiveTarget)
1474         return true;
1475 
1476     wxString oldActiveTarget = m_ActiveTarget;
1477     m_ActiveTarget = name;
1478 
1479     bool valid = BuildTargetValid(name);
1480 
1481     if (!valid) // no target (virtual or real) by that name
1482         m_ActiveTarget = GetFirstValidBuildTargetName();
1483 
1484     NotifyPlugins(cbEVT_BUILDTARGET_SELECTED, m_ActiveTarget, oldActiveTarget);
1485 
1486     return valid;
1487 }
1488 
GetActiveBuildTarget() const1489 const wxString& cbProject::GetActiveBuildTarget() const
1490 {
1491     return m_ActiveTarget;
1492 }
1493 
SetDefaultExecuteTarget(const wxString & name)1494 void cbProject::SetDefaultExecuteTarget(const wxString& name)
1495 {
1496     if (name == m_DefaultExecuteTarget)
1497         return;
1498 
1499     m_DefaultExecuteTarget = name;
1500     SetModified(true);
1501 }
1502 
GetDefaultExecuteTarget() const1503 const wxString& cbProject::GetDefaultExecuteTarget() const
1504 {
1505     return m_DefaultExecuteTarget;
1506 }
1507 
GetBuildTarget(int index)1508 ProjectBuildTarget* cbProject::GetBuildTarget(int index)
1509 {
1510     if (index >= 0 && index < (int)m_Targets.GetCount())
1511         return m_Targets[index];
1512     return nullptr;
1513 }
1514 
GetBuildTarget(int index) const1515 const ProjectBuildTarget* cbProject::GetBuildTarget(int index) const
1516 {
1517     if (index >= 0 && index < (int)m_Targets.GetCount())
1518         return m_Targets[index];
1519     return nullptr;
1520 }
1521 
GetBuildTarget(const wxString & targetName)1522 ProjectBuildTarget* cbProject::GetBuildTarget(const wxString& targetName)
1523 {
1524     int idx = IndexOfBuildTargetName(targetName);
1525     return GetBuildTarget(idx);
1526 }
1527 
GetBuildTarget(const wxString & targetName) const1528 const ProjectBuildTarget* cbProject::GetBuildTarget(const wxString& targetName) const
1529 {
1530     int idx = IndexOfBuildTargetName(targetName);
1531     return GetBuildTarget(idx);
1532 }
1533 
ReOrderTargets(const wxArrayString & nameOrder)1534 void cbProject::ReOrderTargets(const wxArrayString& nameOrder)
1535 {
1536     LogManager* msgMan = Manager::Get()->GetLogManager();
1537     if (nameOrder.GetCount() != m_Targets.GetCount())
1538     {
1539         msgMan->DebugLog(F(_T("cbProject::ReOrderTargets() : Count does not match (%lu sent, %lu had)..."),
1540                            static_cast<unsigned long>(nameOrder.GetCount()),
1541                            static_cast<unsigned long>(m_Targets.GetCount())));
1542         return;
1543     }
1544 
1545     for (unsigned int i = 0; i < nameOrder.GetCount(); ++i)
1546     {
1547         ProjectBuildTarget* target = GetBuildTarget(nameOrder[i]);
1548         if (!target)
1549         {
1550             msgMan->DebugLog(F(_T("cbProject::ReOrderTargets() : Target \"%s\" not found..."), nameOrder[i].wx_str()));
1551             break;
1552         }
1553 
1554         m_Targets.Remove(target);
1555         m_Targets.Insert(target, i);
1556 
1557         // we have to re-order the targets which are kept inside
1558         // the virtual targets array too!
1559         VirtualBuildTargetsMap::iterator it;
1560         for (it = m_VirtualTargets.begin(); it != m_VirtualTargets.end(); ++it)
1561         {
1562             wxArrayString& vt = it->second;
1563             if (vt.Index(nameOrder[i]) != wxNOT_FOUND)
1564             {
1565                 vt.Remove(nameOrder[i]);
1566                 vt.Insert(nameOrder[i], (vt.Count()<=i) ? vt.Count()-1 : i);
1567             }
1568         }
1569     }
1570     SetModified(true);
1571 }
1572 
SetCurrentlyCompilingTarget(ProjectBuildTarget * bt)1573 void cbProject::SetCurrentlyCompilingTarget(ProjectBuildTarget* bt)
1574 {
1575     m_CurrentlyCompilingTarget = bt;
1576 }
1577 
DefineVirtualBuildTarget(const wxString & alias,const wxArrayString & targets)1578 bool cbProject::DefineVirtualBuildTarget(const wxString& alias, const wxArrayString& targets)
1579 {
1580     if (targets.GetCount() == 0)
1581     {
1582         Manager::Get()->GetLogManager()->LogWarning(F(_T("Can't define virtual build target '%s': Group of build targets is empty!"), alias.wx_str()));
1583         return false;
1584     }
1585 
1586     ProjectBuildTarget* existing = GetBuildTarget(alias);
1587     if (existing)
1588     {
1589         Manager::Get()->GetLogManager()->LogWarning(F(_T("Can't define virtual build target '%s': Real build target exists with that name!"), alias.wx_str()));
1590         return false;
1591     }
1592 
1593     m_VirtualTargets[alias] = targets;
1594     SetModified(true);
1595     NotifyPlugins(cbEVT_PROJECT_TARGETS_MODIFIED);
1596     return true;
1597 }
1598 
HasVirtualBuildTarget(const wxString & alias) const1599 bool cbProject::HasVirtualBuildTarget(const wxString& alias) const
1600 {
1601     return m_VirtualTargets.find(alias) != m_VirtualTargets.end();
1602 }
1603 
RemoveVirtualBuildTarget(const wxString & alias)1604 bool cbProject::RemoveVirtualBuildTarget(const wxString& alias)
1605 {
1606     VirtualBuildTargetsMap::iterator it = m_VirtualTargets.find(alias);
1607     if (it == m_VirtualTargets.end())
1608         return false;
1609 
1610     m_VirtualTargets.erase(it);
1611     SetModified(true);
1612     NotifyPlugins(cbEVT_PROJECT_TARGETS_MODIFIED);
1613     return true;
1614 }
1615 
GetVirtualBuildTargets() const1616 wxArrayString cbProject::GetVirtualBuildTargets() const
1617 {
1618     wxArrayString result;
1619     for (VirtualBuildTargetsMap::const_iterator it = m_VirtualTargets.begin(); it != m_VirtualTargets.end(); ++it)
1620         result.Add(it->first);
1621 
1622     return result;
1623 }
1624 
GetVirtualBuildTargetGroup(const wxString & alias) const1625 const wxArrayString& cbProject::GetVirtualBuildTargetGroup(const wxString& alias) const
1626 {
1627     static wxArrayString resultIfError;
1628 
1629     VirtualBuildTargetsMap::const_iterator it = m_VirtualTargets.find(alias);
1630     if (it == m_VirtualTargets.end())
1631         return resultIfError;
1632 
1633     return it->second;
1634 }
1635 
GetExpandedVirtualBuildTargetGroup(const wxString & alias) const1636 wxArrayString cbProject::GetExpandedVirtualBuildTargetGroup(const wxString& alias) const
1637 {
1638     wxArrayString result;
1639 
1640     VirtualBuildTargetsMap::const_iterator it = m_VirtualTargets.find(alias);
1641     if (it == m_VirtualTargets.end())
1642         return result;
1643     ExpandVirtualBuildTargetGroup(alias, result);
1644 
1645     return result;
1646 }
1647 
CanAddToVirtualBuildTarget(const wxString & alias,const wxString & target)1648 bool cbProject::CanAddToVirtualBuildTarget(const wxString& alias, const wxString& target)
1649 {
1650     // virtual not there?
1651     if (!HasVirtualBuildTarget(alias))
1652         return false;
1653 
1654     // real targets can be added safely (as long as they 're unique)
1655     if (!HasVirtualBuildTarget(target))
1656         return true;
1657 
1658     // virtual targets are checked in two ways:
1659     // 1) it is checked if it contains alias
1660     // 2) all its virtual targets are recursively checked if they contain alias
1661     const wxArrayString& group = GetVirtualBuildTargetGroup(target);
1662     if (group.Index(alias) != wxNOT_FOUND)
1663         return false;
1664 
1665     for (size_t i = 0; i < group.GetCount(); ++i)
1666     {
1667         // only virtuals
1668         if (HasVirtualBuildTarget(group[i]))
1669         {
1670             if (!CanAddToVirtualBuildTarget(group[i], alias))
1671                 return false;
1672         }
1673     }
1674     return true;
1675 }
1676 
ExpandVirtualBuildTargetGroup(const wxString & alias,wxArrayString & result) const1677 void cbProject::ExpandVirtualBuildTargetGroup(const wxString& alias, wxArrayString& result) const
1678 {
1679     const wxArrayString& group = GetVirtualBuildTargetGroup(alias);
1680     for (size_t i = 0; i < group.GetCount(); ++i)
1681     {
1682         // real targets get added
1683         if (IndexOfBuildTargetName(group[i]) != -1)
1684         {
1685             if (result.Index(group[i]) == wxNOT_FOUND)
1686                 result.Add(group[i]);
1687         }
1688         // virtual targets recurse
1689         else
1690             ExpandVirtualBuildTargetGroup(group[i], result);
1691     }
1692 }
1693 
SetExtendedObjectNamesGeneration(bool ext)1694 void cbProject::SetExtendedObjectNamesGeneration(bool ext)
1695 {
1696     bool changed = m_ExtendedObjectNamesGeneration != ext;
1697 
1698     // update it now because SetObjName() below will call GetExtendedObjectNamesGeneration()
1699     // so it must be up-to-date
1700     m_ExtendedObjectNamesGeneration = ext;
1701 
1702     if (changed)
1703     {
1704         for (FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it)
1705         {
1706             ProjectFile* f = *it;
1707             f->SetObjName(f->relativeToCommonTopLevelPath);
1708             f->UpdateFileDetails();
1709         }
1710 
1711         SetModified(true);
1712     }
1713 }
1714 
GetExtendedObjectNamesGeneration() const1715 bool cbProject::GetExtendedObjectNamesGeneration() const
1716 {
1717     return m_ExtendedObjectNamesGeneration;
1718 }
1719 
SetNotes(const wxString & notes)1720 void cbProject::SetNotes(const wxString& notes)
1721 {
1722     if (m_Notes != notes)
1723     {
1724         m_Notes = notes;
1725         SetModified(true);
1726     }
1727 }
1728 
GetNotes() const1729 const wxString& cbProject::GetNotes() const
1730 {
1731     return m_Notes;
1732 }
1733 
SetShowNotesOnLoad(bool show)1734 void cbProject::SetShowNotesOnLoad(bool show)
1735 {
1736     if (m_AutoShowNotesOnLoad != show)
1737     {
1738         m_AutoShowNotesOnLoad = show;
1739         SetModified(true);
1740     }
1741 }
1742 
GetShowNotesOnLoad() const1743 bool cbProject::GetShowNotesOnLoad() const
1744 {
1745     return m_AutoShowNotesOnLoad;
1746 }
1747 
SetCheckForExternallyModifiedFiles(bool check)1748 void cbProject::SetCheckForExternallyModifiedFiles(bool check)
1749 {
1750     if (m_CheckForExternallyModifiedFiles != check)
1751     {
1752         m_CheckForExternallyModifiedFiles = check;
1753         SetModified(true);
1754     }
1755 }
1756 
GetCheckForExternallyModifiedFiles() const1757 bool cbProject::GetCheckForExternallyModifiedFiles() const
1758 {
1759     return m_CheckForExternallyModifiedFiles;
1760 }
1761 
ShowNotes(bool nonEmptyOnly,bool editable)1762 void cbProject::ShowNotes(bool nonEmptyOnly, bool editable)
1763 {
1764     if (!editable && nonEmptyOnly && m_Notes.IsEmpty())
1765         return;
1766 
1767     GenericMultiLineNotesDlg dlg(Manager::Get()->GetAppWindow(),
1768                                 _("Notes about ") + m_Title,
1769                                 m_Notes,
1770                                 !editable);
1771     PlaceWindow(&dlg);
1772     if (dlg.ShowModal() == wxID_OK)
1773     {
1774         if (editable)
1775             SetNotes(dlg.GetNotes());
1776     }
1777 }
1778 
SetTitle(const wxString & title)1779 void cbProject::SetTitle(const wxString& title)
1780 {
1781     if ( title != GetTitle() )
1782     {
1783         CompileTargetBase::SetTitle(title);
1784         NotifyPlugins(cbEVT_PROJECT_RENAMED);
1785     }
1786 }
1787 
GetExtensionsNode()1788 TiXmlNode* cbProject::GetExtensionsNode()
1789 {
1790     if (!m_pExtensionsElement)
1791         m_pExtensionsElement = new TiXmlElement(cbU2C(_T("Extensions")));
1792     return m_pExtensionsElement;
1793 }
1794 
GetExtensionsNode() const1795 const TiXmlNode* cbProject::GetExtensionsNode() const
1796 {
1797     return m_pExtensionsElement;
1798 }
1799 
AddToExtensions(const wxString & stringDesc)1800 void cbProject::AddToExtensions(const wxString& stringDesc)
1801 {
1802     // sample stringDesc:
1803     // node/+subnode/subsubnode:attr=val
1804 
1805     TiXmlElement* elem = GetExtensionsNode()->ToElement();
1806     size_t pos = 0;
1807     while (true)
1808     {
1809         // ignore consecutive slashes
1810         while (pos < stringDesc.Length() && stringDesc.GetChar(pos) == _T('/'))
1811         {
1812             ++pos;
1813         }
1814 
1815         // find next slash or colon
1816         size_t nextPos = pos;
1817         while (nextPos < stringDesc.Length() && stringDesc.GetChar(++nextPos) != _T('/') && stringDesc.GetChar(nextPos) != _T(':'))
1818             ;
1819 
1820         wxString current = stringDesc.Mid(pos, nextPos - pos);
1821         if (current.IsEmpty() || current[0] == _T(':')) // abort on invalid case: "node/:attr=val" (consecutive "/:")
1822             break;
1823 
1824         // find or create the subnode
1825         bool forceAdd = current[0] == _T('+');
1826         if (forceAdd)
1827             current.Remove(0, 1); // remove '+'
1828         TiXmlElement* sub = !forceAdd ? elem->FirstChildElement(cbU2C(current)) : nullptr;
1829         if (!sub)
1830         {
1831             sub = elem->InsertEndChild(TiXmlElement(cbU2C(current)))->ToElement();
1832             SetModified(true);
1833         }
1834         elem = sub;
1835 
1836         // last node?
1837         if (stringDesc.GetChar(nextPos) == _T(':'))
1838         {
1839             // yes, just parse the attribute now
1840             pos = nextPos + 1; // skip the colon
1841             nextPos = pos;
1842             while (nextPos < stringDesc.Length() && stringDesc.GetChar(++nextPos) != _T('='))
1843                 ;
1844             if (pos == nextPos || nextPos == stringDesc.Length())
1845             {
1846                 // invalid attribute
1847             }
1848             else
1849             {
1850                 wxString key = stringDesc.Mid(pos, nextPos - pos);
1851                 wxString val = stringDesc.Mid(nextPos + 1, stringDesc.Length() - nextPos - 1);
1852                 sub->SetAttribute(cbU2C(key), cbU2C(val));
1853                 SetModified(true);
1854             }
1855 
1856             // all done
1857             break;
1858         }
1859 
1860         pos = nextPos; // prepare for next loop
1861     }
1862 }
1863 
ProjectFileRenamed(ProjectFile * pf)1864 void cbProject::ProjectFileRenamed(ProjectFile* pf)
1865 {
1866     for (ProjectFiles::iterator it = m_ProjectFilesMap.begin(); it != m_ProjectFilesMap.end(); ++it)
1867     {
1868         ProjectFile* itpf = it->second;
1869         if (itpf == pf)
1870         {
1871             // got it
1872             m_ProjectFilesMap.erase(it);
1873             m_ProjectFilesMap[UnixFilename(pf->relativeFilename)] = pf;
1874             break;
1875         }
1876     }
1877 }
1878 
SetGlobs(const std::vector<Glob> & globs)1879 void cbProject::SetGlobs(const std::vector<Glob>& globs)
1880 {
1881     m_Globs = globs;
1882 }
1883 
GetGlobs() const1884 std::vector<cbProject::Glob> cbProject::GetGlobs() const
1885 {
1886     return m_Globs;
1887 }
1888 
cbGetDynamicLinkerPathForTarget(cbProject * project,ProjectBuildTarget * target)1889 wxString cbGetDynamicLinkerPathForTarget(cbProject *project, ProjectBuildTarget* target)
1890 {
1891     if (!target)
1892         return wxEmptyString;
1893 
1894     Compiler* compiler = CompilerFactory::GetCompiler(target->GetCompilerID());
1895     if (compiler)
1896     {
1897         CompilerCommandGenerator* generator = compiler->GetCommandGenerator(project);
1898         wxString libPath;
1899         const wxString libPathSep = platform::windows ? _T(";") : _T(":");
1900         libPath << _T(".") << libPathSep;
1901         libPath << GetStringFromArray(generator->GetLinkerSearchDirs(target), libPathSep);
1902         if (!libPath.IsEmpty() && libPath.Mid(libPath.Length() - 1, 1) == libPathSep)
1903             libPath.Truncate(libPath.Length() - 1);
1904 
1905         delete generator;
1906         return libPath;
1907     }
1908     return wxEmptyString;
1909 }
1910 
cbMergeLibPaths(const wxString & oldPath,const wxString & newPath)1911 wxString cbMergeLibPaths(const wxString &oldPath, const wxString &newPath)
1912 {
1913     wxString result = newPath;
1914     const wxString libPathSep = platform::windows ? _T(";") : _T(":");
1915     if (!newPath.IsEmpty() && newPath.Mid(newPath.Length() - 1, 1) != libPathSep)
1916         result << libPathSep;
1917     result << oldPath;
1918     return result;
1919 }
1920