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: 11851 $
6  * $Id: projectfile.cpp 11851 2019-09-17 06:12:21Z fuscated $
7  * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/sdk/projectfile.cpp $
8  */
9 
10 #include "sdk_precomp.h"
11 
12 #ifndef CB_PRECOMP
13     #include "projectfile.h"
14     #include "projectbuildtarget.h"
15     #include "cbproject.h"
16     #include "compilerfactory.h"
17     #include "manager.h"
18     #include "projectmanager.h"
19     #include "macrosmanager.h"
20     #include "globals.h"
21 #endif
22 
23 #include "projectfileoptionsdlg.h"
24 #include "filefilters.h"
25 
ProjectFile(cbProject * prj)26 ProjectFile::ProjectFile(cbProject* prj) :
27     compile(false),
28     link(false),
29     weight(50),
30     editorOpen(false),
31     editorSplit(0),
32     editorSplitActive(1),
33     editorSplitPos(0),
34     editorPos(0),
35     editorTopLine(0),
36     editorZoom(0),
37     editorPos_2(0),
38     editorTopLine_2(0),
39     editorZoom_2(0),
40     editorTabPos(0),
41     autoGeneratedBy(nullptr),
42     project(prj),
43     m_VisualState(fvsNormal)
44 {
45 }
46 
~ProjectFile()47 ProjectFile::~ProjectFile()
48 {
49     // clear PFDMap
50     for (PFDMap::iterator it = m_PFDMap.begin(); it != m_PFDMap.end(); ++it)
51     {
52         delete it->second;
53     }
54     m_PFDMap.clear();
55 }
56 
57 // Leave m_PFDMap empty, it will be generated automatically when needed.
58 // Leave autoGeneratedBy and generatedFiles empty, they will be filled by cbProject::operator=.
ProjectFile(cbProject * prj,const ProjectFile & pf)59 ProjectFile::ProjectFile(cbProject* prj, const ProjectFile &pf) :
60     file(pf.file),
61     relativeFilename(pf.relativeFilename),
62     relativeToCommonTopLevelPath(pf.relativeToCommonTopLevelPath),
63     compile(pf.compile),
64     link(pf.link),
65     weight(pf.weight),
66     editorOpen(pf.editorOpen),
67     editorSplit(pf.editorSplit),
68     editorSplitActive(pf.editorSplitActive),
69     editorSplitPos(pf.editorSplitPos),
70     editorPos(pf.editorPos),
71     editorTopLine(pf.editorTopLine),
72     editorZoom(pf.editorZoom),
73     editorPos_2(pf.editorPos_2),
74     editorTopLine_2(pf.editorTopLine_2),
75     editorZoom_2(pf.editorZoom_2),
76     editorTabPos(pf.editorTabPos),
77     editorFoldLinesArray(pf.editorFoldLinesArray),
78     customBuild(pf.customBuild),
79     compilerVar(pf.compilerVar),
80     buildTargets(pf.buildTargets),
81     virtual_path(pf.virtual_path),
82     autoGeneratedBy(nullptr),
83     project(prj),
84     m_VisualState(pf.m_VisualState),
85     m_TreeItemId(pf.m_TreeItemId),
86     m_ObjName(pf.m_ObjName)
87 {
88 }
89 
Rename(const wxString & new_name)90 void ProjectFile::Rename(const wxString& new_name)
91 {
92     wxString path = file.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
93 
94     file.Assign(path + new_name);
95     relativeFilename = relativeFilename.BeforeLast(wxFILE_SEP_PATH);
96     if (!relativeFilename.IsEmpty())
97     {
98         relativeFilename.Append(wxFILE_SEP_PATH);
99     }
100     relativeFilename.Append(new_name);
101 
102     if (project)
103     {
104         project->ProjectFileRenamed(this);
105         project->CalculateCommonTopLevelPath();
106         project->SetModified(true);
107     }
108     UpdateFileDetails();
109 }
110 
AddBuildTarget(const wxString & targetName)111 void ProjectFile::AddBuildTarget(const wxString& targetName)
112 {
113     if (buildTargets.Index(targetName) == wxNOT_FOUND)
114         buildTargets.Add(targetName);
115 
116     // add this file to the target's list of files
117     if (project)
118     {
119         ProjectBuildTarget* target = project->GetBuildTarget(targetName);
120         if (target && (target->m_Files.find(this) == target->m_Files.end()))
121         {
122             target->m_Files.insert(this);
123             // Only add the file, if we are not currently loading the project and m_FileArray is already initialised
124             // initialising is done in the getter-function (GetFile(index), to save time, if it is not needed
125             if ( target->m_FileArray.GetCount() > 0 )
126                 target->m_FileArray.Add(this);
127         }
128     }
129 
130     // also do this for auto-generated files
131     for (size_t i = 0; i < generatedFiles.size(); ++i)
132         generatedFiles[i]->AddBuildTarget(targetName);
133 }
134 
RenameBuildTarget(const wxString & oldTargetName,const wxString & newTargetName)135 void ProjectFile::RenameBuildTarget(const wxString& oldTargetName, const wxString& newTargetName)
136 {
137     int idx = buildTargets.Index(oldTargetName);
138     if (idx != wxNOT_FOUND)
139         buildTargets[idx] = newTargetName;
140 
141     // also do this for auto-generated files
142     for (size_t i = 0; i < generatedFiles.size(); ++i)
143         generatedFiles[i]->RenameBuildTarget(oldTargetName, newTargetName);
144 }
145 
RemoveBuildTarget(const wxString & targetName)146 void ProjectFile::RemoveBuildTarget(const wxString& targetName)
147 {
148     int idx = buildTargets.Index(targetName);
149     if (idx != wxNOT_FOUND)
150         buildTargets.RemoveAt(idx);
151 
152     // remove this file from the target's list of files
153     if (project)
154     {
155         ProjectBuildTarget* target = project->GetBuildTarget(targetName);
156         if (target)
157         {
158             FilesList::iterator it = target->m_Files.find(this);
159             if (it != target->m_Files.end())
160             {
161                 int item = target->m_FileArray.Index(*it);
162                 if (item != wxNOT_FOUND)
163                     target->m_FileArray.RemoveAt(item);
164                 target->m_Files.erase(it);
165             }
166         }
167     }
168 
169     // also do this for auto-generated files
170     for (size_t i = 0; i < generatedFiles.size(); ++i)
171         generatedFiles[i]->RemoveBuildTarget(targetName);
172 }
173 
GetBuildTargets() const174 const wxArrayString& ProjectFile::GetBuildTargets() const
175 {
176     return buildTargets;
177 }
178 
ShowOptions(wxWindow * parent)179 bool ProjectFile::ShowOptions(wxWindow* parent)
180 {
181     ProjectFileOptionsDlg dlg(parent, this);
182     PlaceWindow(&dlg);
183     return dlg.ShowModal() == wxID_OK;
184 }
185 
GetBaseName() const186 wxString ProjectFile::GetBaseName() const
187 {
188     wxFileName fname(relativeFilename);
189     fname.SetExt(wxEmptyString);
190     return fname.GetFullPath();
191 }
192 
GetObjName()193 const wxString& ProjectFile::GetObjName()
194 {
195     if (generatedFiles.size())
196     {
197         // for files generating other files,
198         // report the first generated file's "object name"
199         return generatedFiles[0]->GetObjName();
200     }
201 
202     if (m_ObjName.IsEmpty())
203         SetObjName(relativeToCommonTopLevelPath);
204     return m_ObjName;
205 }
206 
SetObjName(const wxString & name)207 void ProjectFile::SetObjName(const wxString& name)
208 {
209     bool extendedObjectNames = project->GetExtendedObjectNamesGeneration();
210     wxFileName fname(name);
211     m_ObjName = name;
212     FileType ft = FileTypeOf(name);
213     if (ft == ftResource || ft == ftResourceBin)
214     {
215         if (extendedObjectNames)
216             m_ObjName += FileFilters::RESOURCEBIN_DOT_EXT;
217         else
218         {
219             fname.SetExt(FileFilters::RESOURCEBIN_EXT);
220             m_ObjName = fname.GetFullPath();
221         }
222     }
223     else if (ft == ftHeader) // support precompiled headers?
224     {
225         Compiler* compiler = CompilerFactory::GetCompiler(project->GetCompilerID());
226         if (compiler && compiler->GetSwitches().supportsPCH)
227         {
228             // PCHs are always using the extended name mode (at least for GCC)
229             // the extension is set to "h.gch" for .h files
230             if (project->GetModeForPCH() == pchSourceFile)
231                 fname.Assign(relativeFilename);
232             // Make the current file extension part of the filename
233             fname.SetName(fname.GetFullName());
234             // PCHExtension will contain, for example, 'gch'
235             fname.SetExt(compiler->GetSwitches().PCHExtension);
236             m_ObjName = fname.GetFullPath();
237         }
238     }
239     else
240     {
241         if (project)
242         {
243             Compiler* compiler = CompilerFactory::GetCompiler(project->GetCompilerID());
244             if (compiler)
245             {
246                 if (extendedObjectNames)
247                     m_ObjName += _T('.') + compiler->GetSwitches().objectExtension;
248                 else
249                 {
250                     fname.SetExt(compiler->GetSwitches().objectExtension);
251                     m_ObjName = fname.GetFullPath();
252                 }
253             }
254         }
255         else
256         {
257             if (extendedObjectNames)
258                 m_ObjName += _T(".o"); // fallback?
259             else
260             {
261                 fname.SetExt(_T(".o"));
262                 m_ObjName = fname.GetFullPath();
263             }
264         }
265     }
266 //#ifdef __WXMSW__
267 //    // special case for windows and files on a different drive
268 //    if (name.Length() > 1 && name.GetChar(1) == _T(':'))
269 //    {
270 //        m_ObjName.Remove(1, 1); // NOTE (mandrav): why remove the colon???
271 //    }
272 //#endif
273 }
274 
275 // map target to pfDetails
UpdateFileDetails(ProjectBuildTarget * target)276 void ProjectFile::UpdateFileDetails(ProjectBuildTarget* target)
277 {
278     if (!project)
279         return;
280 
281     if (!compile && !link)
282         return;
283 
284     // update PCH output name (in case project PCH mode was changed)
285     if (FileTypeOf(relativeFilename) == ftHeader)
286         SetObjName(relativeToCommonTopLevelPath);
287 
288     if (!target) // update all targets
289     {
290         int tcount = project->GetBuildTargetsCount();
291         for (int x = 0; x < tcount; ++x)
292         {
293             ProjectBuildTarget* bt = project->GetBuildTarget(x);
294             DoUpdateFileDetails(bt);
295         }
296     }
297     else
298         DoUpdateFileDetails(target);
299 }
300 
DoUpdateFileDetails(ProjectBuildTarget * target)301 void ProjectFile::DoUpdateFileDetails(ProjectBuildTarget* target)
302 {
303     // if we don't belong in this target, abort
304     if (!target || buildTargets.Index(target->GetTitle()) == wxNOT_FOUND)
305         return;
306     // delete old PFD
307     pfDetails* pfd = m_PFDMap[target];
308     if (pfd)
309         pfd->Update(target, this);
310     else
311     {
312         pfd = new pfDetails(target, this);
313         m_PFDMap[target] = pfd;
314     }
315 }
316 
GetFileDetails(ProjectBuildTarget * target)317 const pfDetails& ProjectFile::GetFileDetails(ProjectBuildTarget* target)
318 {
319     pfDetails* pfd = m_PFDMap[target];
320     if (!pfd)
321     {
322         DoUpdateFileDetails(target);
323         pfd = m_PFDMap[target];
324     }
325     return *pfd;
326 }
327 
GetFileState() const328 FileVisualState ProjectFile::GetFileState() const
329 {
330     return m_VisualState;
331 }
332 
SetFileState(FileVisualState state)333 void ProjectFile::SetFileState(FileVisualState state)
334 {
335     if (state != m_VisualState)
336     {
337         m_VisualState = state;
338         wxTreeCtrl* tree = Manager::Get()->GetProjectManager()->GetUI().GetTree();
339         if (tree && m_TreeItemId.IsOk())
340         {
341             tree->SetItemImage(m_TreeItemId, (int)state, wxTreeItemIcon_Normal);
342             tree->SetItemImage(m_TreeItemId, (int)state, wxTreeItemIcon_Selected);
343         }
344     }
345 }
346 
SetUseCustomBuildCommand(const wxString & compilerId,bool useCustomBuildCommand)347 void ProjectFile::SetUseCustomBuildCommand(const wxString& compilerId, bool useCustomBuildCommand)
348 {
349     customBuild[compilerId].useCustomBuildCommand = useCustomBuildCommand;
350 }
351 
SetCustomBuildCommand(const wxString & compilerId,const wxString & newBuildCommand)352 void ProjectFile::SetCustomBuildCommand(const wxString& compilerId, const wxString& newBuildCommand)
353 {
354     customBuild[compilerId].buildCommand = newBuildCommand;
355 }
356 
GetUseCustomBuildCommand(const wxString & compilerId)357 bool ProjectFile::GetUseCustomBuildCommand(const wxString& compilerId)
358 {
359     return customBuild[compilerId].useCustomBuildCommand;
360 }
361 
GetCustomBuildCommand(const wxString & compilerId)362 wxString ProjectFile::GetCustomBuildCommand(const wxString& compilerId)
363 {
364     return customBuild[compilerId].buildCommand;
365 }
366 
CompareProjectFiles(ProjectFile * item1,ProjectFile * item2)367 int ProjectFile::CompareProjectFiles(ProjectFile* item1, ProjectFile* item2)
368 {
369     return wxStrcmp(item1->relativeFilename, item2->relativeFilename);
370 }
371 
372 ////////////////////////////////////////////////////////////////////////////////
373 // pfDetails
374 ////////////////////////////////////////////////////////////////////////////////
375 
pfDetails(ProjectBuildTarget * target,ProjectFile * pf)376 pfDetails::pfDetails(ProjectBuildTarget* target, ProjectFile* pf)
377 {
378     Update(target, pf);
379 }
380 
Update(ProjectBuildTarget * target,ProjectFile * pf)381 void pfDetails::Update(ProjectBuildTarget* target, ProjectFile* pf)
382 {
383     wxString sep = wxFILE_SEP_PATH;
384 
385     wxFileName prjbase(target->GetParentProject()->GetBasePath());
386 
387     wxString objOut  = target ? target->GetObjectOutput() : _T(".");
388     wxString depsOut = target ? target->GetDepsOutput()   : _T(".");
389 
390     // we must replace any macros here early because if the macros expand
391     // to absolute paths (like global vars usually do), we 're gonna create
392     // invalid filenames below
393     Manager::Get()->GetMacrosManager()->ReplaceMacros(objOut,  target);
394     Manager::Get()->GetMacrosManager()->ReplaceMacros(depsOut, target);
395 
396     source_file_native          = pf->relativeFilename;
397     source_file_absolute_native = pf->file.GetFullPath();
398 
399     wxFileName obj_name( pf->GetObjName() );
400     FileType ft = FileTypeOf(pf->relativeFilename);
401 
402     Compiler* compiler = target ? CompilerFactory::GetCompiler(target->GetCompilerID())
403                                 : CompilerFactory::GetDefaultCompiler();
404 
405     // support for precompiled headers
406     if (target && ft == ftHeader && compiler && compiler->GetSwitches().supportsPCH)
407     {
408         switch (target->GetParentProject()->GetModeForPCH())
409         {
410             case pchSourceDir:
411             {
412                 // if PCH is for a file called all.h, we create
413                 // all.h.gch/<target>_all.h.gch
414                 // (that's right: a directory)
415                 wxString new_gch = target->GetTitle() + _T('_') + pf->GetObjName();
416                 // make sure we 're not generating subdirs
417                 size_t len = new_gch.Length();
418                 for (size_t i = 0; i < len; ++i)
419                 {
420                     wxChar c = new_gch[i];
421                     if (c == _T('/') || c == _T('\\') || c == _T('.'))
422                         new_gch[i] = _T('_');
423                 }
424 
425                 wxFileName fn(source_file_native);
426                 object_file_native = fn.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) +
427                                     fn.GetFullName() + _T('.') + compiler->GetSwitches().PCHExtension +
428                                     wxFILE_SEP_PATH +
429                                     new_gch;
430                 object_file_flat_native = object_file_native;
431                 break;
432             }
433 
434             case pchObjectDir:
435             {
436                 object_file_native      = objOut + sep + obj_name.GetFullPath();
437                 object_file_flat_native = objOut + sep + obj_name.GetFullName();
438                 break;
439             }
440 
441             case pchSourceFile:
442             {
443                 object_file_native      = pf->GetObjName();
444                 object_file_flat_native = object_file_native;
445                 break;
446             }
447 
448             default:
449                 break;
450         }
451     }
452     else
453     {
454         if (pf->GetParentProject())
455         {
456             wxFileName fname(pf->relativeToCommonTopLevelPath);
457             if (pf->generatedFiles.size())
458             {
459                 // for files generating other files,
460                 // use the first generated file's "object name"
461                 fname.Assign(pf->generatedFiles[0]->relativeToCommonTopLevelPath);
462             }
463             /* NOTE: In case the source file resides in a different volume
464              * than the volume where project file is,
465              * then the object file will be created as follows.
466              *
467              * Project object output dir: C:\Foo\obj\Debug
468              * Source: D:\Source\foo.cpp
469              * Obj file: C:\Foo\obj\Debug\D\Source\foo.o
470              */
471             wxString fileVol            = fname.GetVolume();
472             wxString obj_file_full_path = fname.GetFullPath();
473             bool     diffVolume         = false;
474 
475             if (   platform::windows
476                 && (!fileVol.IsEmpty() && !fileVol.IsSameAs(prjbase.GetVolume())) )
477             {
478                 objOut += fileVol;
479                 obj_file_full_path = obj_file_full_path.AfterFirst(_T('\\'));
480                 diffVolume = true;
481             }
482 
483             if (ft == ftResource || ft == ftResourceBin)
484             {
485                 if (pf->GetParentProject()->GetExtendedObjectNamesGeneration())
486                 {
487                     object_file_native      = objOut + sep + obj_file_full_path;
488                     object_file_flat_native = objOut + sep + fname.GetFullName();
489 
490                     object_file_native      += FileFilters::RESOURCEBIN_DOT_EXT;
491                     object_file_flat_native += FileFilters::RESOURCEBIN_DOT_EXT;
492                 }
493                 else
494                 {
495                     fname.SetExt(FileFilters::RESOURCEBIN_EXT);
496                     wxString obj_file_path = fname.GetFullPath();
497                     if (diffVolume)
498                         obj_file_path = obj_file_path.AfterFirst(_T('\\'));
499 
500                     object_file_native      = objOut + sep + obj_file_path;
501                     object_file_flat_native = objOut + sep + fname.GetFullName();
502                 }
503             }
504             else if (ft == ftObject)
505             {
506                 // TODO (Morten#1#): Does this work in all cases (flat objects, extended object generation, generated files...)?
507                 object_file_native      = obj_file_full_path;
508                 object_file_flat_native = fname.GetFullName();
509             }
510             else if (ft == ftStaticLib || ft == ftDynamicLib)
511             {
512                 cbMessageBox(_("You have added a static/dynamic library to the project files and enabled to link against it. "
513                                "This is likely to fail as Code::Blocks cannot control the link order which is relevant.\n"
514                                "Instead, add the library to the project linker options."), _("Error"), wxICON_ERROR | wxOK);
515                 // This will be wrong and most likely not working but spoil the build process
516                 object_file_native      = obj_file_full_path;
517                 object_file_flat_native = fname.GetFullName();
518             }
519             else
520             {
521                 if (pf->GetParentProject()->GetExtendedObjectNamesGeneration())
522                 {
523                     object_file_native      = objOut + sep + obj_file_full_path;
524                     object_file_flat_native = objOut + sep + fname.GetFullName();
525 
526                     if (compiler)
527                     {
528                         object_file_native      += _T('.') + compiler->GetSwitches().objectExtension;
529                         object_file_flat_native += _T('.') + compiler->GetSwitches().objectExtension;
530                     }
531                 }
532                 else
533                 {
534                     if (compiler)
535                         fname.SetExt(compiler->GetSwitches().objectExtension);
536                     wxString obj_file_path = fname.GetFullPath();
537                     if (diffVolume)
538                         obj_file_path = obj_file_path.AfterFirst(_T('\\'));
539 
540                     object_file_native      = objOut + sep + obj_file_path;
541                     object_file_flat_native = objOut + sep + fname.GetFullName();
542                 }
543             }
544         }
545     }
546 
547     wxFileName o_file(object_file_native);
548     wxFileName o_file_flat(object_file_flat_native);
549     o_file.MakeAbsolute(prjbase.GetFullPath());
550     o_file_flat.MakeAbsolute(prjbase.GetFullPath());
551 
552     object_dir_native                = o_file.GetPath(wxPATH_GET_VOLUME      | wxPATH_GET_SEPARATOR);
553     object_dir_flat_native           = o_file_flat.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
554     object_file_absolute_native      = o_file.GetFullPath();
555     object_file_flat_absolute_native = o_file_flat.GetFullPath();
556 
557     obj_name.SetExt(_T("depend"));
558     dep_file_native = depsOut + sep + obj_name.GetFullPath();
559 
560     wxFileName d_file(dep_file_native);
561     d_file.MakeAbsolute(prjbase.GetFullPath());
562     dep_dir_native           = d_file.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
563     dep_file_absolute_native = o_file.GetFullPath();
564 
565     source_file = UnixFilename(source_file_native);
566     QuoteStringIfNeeded(source_file);
567 
568     object_file = UnixFilename(object_file_native);
569     QuoteStringIfNeeded(object_file);
570 
571     object_file_flat = UnixFilename(object_file_flat_native);
572     QuoteStringIfNeeded(object_file_flat);
573 
574     dep_file = UnixFilename(dep_file_native);
575     QuoteStringIfNeeded(dep_file);
576 
577     object_dir = UnixFilename(object_dir_native);
578     QuoteStringIfNeeded(object_dir);
579 
580     object_dir_flat = UnixFilename(object_dir_flat_native);
581     QuoteStringIfNeeded(object_dir_flat);
582 
583     dep_dir = UnixFilename(dep_dir_native);
584     QuoteStringIfNeeded(dep_dir);
585 
586     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(object_file_native);
587     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(object_file_flat_native);
588     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(object_dir_native);
589     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(object_dir_flat_native);
590     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(object_file_absolute_native);
591     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(object_file_flat_absolute_native);
592     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(dep_file_native);
593     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(dep_dir_native);
594     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(dep_file_absolute_native);
595     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(dep_dir);
596     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(object_dir);
597     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(object_dir_flat);
598     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(dep_file);
599     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(object_file);
600     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(object_file_flat);
601     Manager::Get()->GetMacrosManager()->ReplaceEnvVars(source_file);
602 }
603