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