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