ide/GlobalSettings: Fixed memory leak on assigning compiler directories in GlobalSett...
[sdk] / ide / src / IDESettings.ec
1 #ifdef ECERE_STATIC
2 import static "ecere"
3 #else
4 import "ecere"
5 #endif
6
7 import "StringsBox"
8
9 import "OldIDESettings"
10
11 define MaxRecent = 9;
12
13 enum DirTypes { includes, libraries, executables };
14
15 define defaultCompilerName = "Default";
16
17 define defaultObjDirExpression = "obj/$(CONFIG).$(PLATFORM)";
18
19 char * settingsDirectoryNames[DirTypes] = 
20 {
21    "Include Files",
22    "Library Files",
23    "Executable Files"
24 };
25
26 enum GlobalSettingsChange { none, editorSettings, projectOptions, compilerSettings };
27
28 char * CopyValidateMakefilePath(char * path)
29 {
30    const int map[]  =    { 0,           1,           2,             3,             4,           0,            1,                  7         };
31    const char * vars[] = { "$(MODULE)", "$(CONFIG)", "$(PLATFORM)", "$(COMPILER)", "$(TARGET)", "$(PROJECT)", "$(CONFIGURATION)", (char *)0 };
32
33    char * copy = null;
34    if(path)
35    {
36       int len;
37       len = strlen(path);
38       copy = CopyString(path);
39       if(len)
40       {
41          int c;
42          char * tmp = copy;
43          char * start = tmp;
44          Array<char *> parts { };
45          
46          for(c=0; c<len; c++)
47          {
48             if(tmp[c] == '$')
49             {
50                int v;
51                for(v=0; vars[v]; v++)
52                {
53                   if(SearchString(&tmp[c], 0, vars[v], false, false) == &tmp[c])
54                   {
55                      tmp[c] = '\0';
56                      parts.Add(start);
57                      parts.Add(vars[map[v]]);
58                      c += strlen(vars[v]);
59                      start = &tmp[c];
60                      c--;
61                      break;
62                   }
63                }
64             }
65          }
66          if(start[0])
67             parts.Add(start);
68
69          if(parts.count)
70          {
71             /*int c, */len = 0;
72             for(c=0; c<parts.count; c++) len += strlen(parts[c]);
73             copy = new char[++len];
74             copy[0] = '\0';
75             for(c=0; c<parts.count; c++) strcat(copy, parts[c]);
76          }
77          else
78             copy = null;
79          delete parts;
80          delete tmp;
81       }
82    }
83    return copy;
84 }
85
86 CompilerConfig MakeDefaultCompiler(char * name, bool readOnly)
87 {
88    CompilerConfig defaultCompiler
89    {
90       name,
91       readOnly,
92       gcc,
93       GetRuntimePlatform(),
94       1,
95       makeDefaultCommand,
96       ecpDefaultCommand,
97       eccDefaultCommand,
98       ecsDefaultCommand,
99       earDefaultCommand,
100       cppDefaultCommand,
101       ccDefaultCommand
102    };
103    incref defaultCompiler;
104    return defaultCompiler;
105 }
106
107 class IDESettingsContainer : GlobalSettings
108 {
109 #ifdef SETTINGS_TEST
110    settingsName = "ecereIDESettingsTest";
111 #else
112    settingsName = "ecereIDE";
113 #endif
114
115    virtual void OnLoad(GlobalSettingsData data);
116
117 private:
118    IDESettingsContainer()
119    {
120       char path[MAX_LOCATION];
121       char * start;
122       LocateModule(null, path);
123       // PortableApps.com directory structure
124       if((start = strstr(path, "\\App\\EcereSDK\\bin\\ide.exe")))
125       {
126          char configFilePath[MAX_LOCATION];
127          char defaultConfigFilePath[MAX_LOCATION];
128
129          start[0] = '\0';
130
131          strcpy(configFilePath, path);
132          PathCat(configFilePath, "Data");
133          PathCat(configFilePath, "ecereIDE.ini");
134
135          strcpy(defaultConfigFilePath, path);
136          PathCat(defaultConfigFilePath, "App");
137          PathCat(defaultConfigFilePath, "DefaultData");
138          PathCat(defaultConfigFilePath, "ecereIDE.ini");
139          
140          if(FileExists(defaultConfigFilePath))
141          {
142             if(!FileExists(configFilePath))
143             {
144                File f = FileOpen(defaultConfigFilePath, read);
145                f.CopyTo(configFilePath);
146                f.Flush();
147                delete f;
148             }
149             PathCat(path, "Data");
150             // the forced settings location will only be
151             // used if the running ide's path matches 
152             // the PortableApps.com directory structure
153             // and the default ini file is found in 
154             // the DefaultData directory
155             settingsLocation = path;
156             portable = true;
157          }
158       }
159    }
160
161    void OnAskReloadSettings()
162    {
163       /*if(MessageBox { type = YesNo, master = this, 
164             text = "Global Settings Modified Externally", 
165             contents = "The global settings were modified by another instance.\n"
166             "Would you like to reload them?" }.Modal() == Yes)*/
167       {
168          Load();
169       }
170    }
171
172    void Load()
173    {
174       SettingsIOResult result = GlobalSettings::Load();
175       IDESettings data = (IDESettings)this.data;
176       //if(result == success)
177       if(!data)
178       {
179          this.data = IDESettings { };
180          if(dataOwner)
181             *dataOwner = this.data;
182
183          if(result == fileNotCompatibleWithDriver)
184          {
185             bool loaded;
186             OldIDESettings oldSettings { };
187             Close();
188             loaded = oldSettings.Load();
189             oldSettings.Close();
190             if(loaded)
191             {
192                data = (IDESettings)this.data;
193
194                for(c : oldSettings.compilerConfigs)
195                   data.compilerConfigs.Add(c.Copy());
196
197                for(s : oldSettings.recentFiles) data.recentFiles.Add(CopyString(s));
198                for(s : oldSettings.recentProjects) data.recentProjects.Add(CopyString(s));
199
200                data.docDir = oldSettings.docDir;
201                data.ideFileDialogLocation = oldSettings.ideFileDialogLocation;
202                data.ideProjectFileDialogLocation = oldSettings.ideProjectFileDialogLocation;
203                data.useFreeCaret = oldSettings.useFreeCaret;
204                data.showLineNumbers = oldSettings.showLineNumbers;
205                data.caretFollowsScrolling = oldSettings.caretFollowsScrolling;
206                delete data.displayDriver;
207                data.displayDriver = oldSettings.displayDriver;
208                data.projectDefaultTargetDir = oldSettings.projectDefaultTargetDir;
209                data.projectDefaultIntermediateObjDir = oldSettings.projectDefaultIntermediateObjDir;
210                         
211                Save();
212                result = success;
213             }
214             delete oldSettings;
215          }
216          if(result == fileNotFound || !data)
217          {
218             CompilerConfig defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
219             // We incref the compilers below, so reset refCount to 0
220             defaultCompiler._refCount = 0;
221             data = (IDESettings)this.data;
222
223             data.compilerConfigs.Add(defaultCompiler);
224             data.useFreeCaret = true;
225             data.showLineNumbers = true;
226             data.caretFollowsScrolling = true;
227          }
228       }
229       CloseAndMonitor();
230       if(data.compilerConfigs)
231       {
232          for(c : data.compilerConfigs)
233          {
234             CompilerConfig compiler = c;
235             incref compiler;
236          }
237       }
238       if(portable)
239       {
240          char location[MAX_LOCATION];
241          LocateModule(null, location);
242          StripLastDirectory(location, location);
243          if(location[0] && FileExists(location).isDirectory)
244          {
245             if(data.portableLocation && data.portableLocation[0] && strcmp(data.portableLocation, location))
246             {
247                data.UpdatePortablePaths(data.portableLocation, location);
248                data.portableLocation = location;
249                Save();
250             }
251          }
252       }
253       data.ForcePathSeparatorStyle(true);
254       OnLoad(data);
255    }
256
257    void Save()
258    {
259       IDESettings data = (IDESettings)this.data;
260       Platform runtimePlatform = GetRuntimePlatform();
261       data.ForcePathSeparatorStyle(runtimePlatform != win32);
262       if(!GlobalSettings::Save())
263          PrintLn("Error saving IDE settings");
264       if(runtimePlatform == win32)
265          data.ForcePathSeparatorStyle(true);
266       CloseAndMonitor();
267    }
268 }
269
270 class IDESettings : GlobalSettingsData
271 {
272 public:
273    List<CompilerConfig> compilerConfigs { };
274    Array<String> recentFiles { };
275    Array<String> recentProjects { };
276    property char * docDir
277    {
278       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
279       get { return docDir ? docDir : ""; }
280       isset { return docDir && docDir[0]; }
281    }
282    property char * ideFileDialogLocation
283    {
284       set { delete ideFileDialogLocation; if(value && value[0]) ideFileDialogLocation = CopyString(value); }
285       get { return ideFileDialogLocation ? ideFileDialogLocation : ""; }
286       isset { return ideFileDialogLocation && ideFileDialogLocation[0]; }
287    }
288    property char * ideProjectFileDialogLocation
289    {
290       set { delete ideProjectFileDialogLocation; if(value && value[0]) ideProjectFileDialogLocation = CopyString(value); }
291       get { return ideProjectFileDialogLocation ? ideProjectFileDialogLocation : ""; }
292       isset { return ideProjectFileDialogLocation && ideProjectFileDialogLocation[0]; }
293    }
294    bool useFreeCaret;
295    bool showLineNumbers;
296    bool caretFollowsScrolling;
297    char * displayDriver;
298    
299    // TODO: Classify settings
300    //EditorSettings editor { };
301
302    property char * projectDefaultTargetDir
303    {
304       set { delete projectDefaultTargetDir; if(value && value[0]) projectDefaultTargetDir = CopyValidateMakefilePath(value); }
305       get { return projectDefaultTargetDir ? projectDefaultTargetDir : ""; }
306       isset { return projectDefaultTargetDir && projectDefaultTargetDir[0]; }
307    }
308    property char * projectDefaultIntermediateObjDir
309    {
310       set { delete projectDefaultIntermediateObjDir; if(value && value[0]) projectDefaultIntermediateObjDir = CopyValidateMakefilePath(value); }
311       get { return projectDefaultIntermediateObjDir ? projectDefaultIntermediateObjDir : ""; }
312       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
313    }
314
315    property char * portableLocation
316    {
317       set { delete portableLocation; if(value && value[0]) portableLocation = CopyString(value); }
318       get { return portableLocation ? portableLocation : ""; }
319       isset { return portableLocation && portableLocation[0]; }
320    }
321
322    property char * defaultCompiler
323    {
324       set { delete defaultCompiler; if(value && value[0]) defaultCompiler = CopyString(value); }
325       get { return defaultCompiler && defaultCompiler[0] ? defaultCompiler : defaultCompilerName; }
326       isset { return defaultCompiler && defaultCompiler[0]; }
327    }
328
329 private:
330    char * docDir;
331    char * ideFileDialogLocation;
332    char * ideProjectFileDialogLocation;
333    char * projectDefaultTargetDir;
334    char * projectDefaultIntermediateObjDir;
335    char * portableLocation;
336    char * defaultCompiler;
337
338    CompilerConfig GetCompilerConfig(String compilerName)
339    {
340       char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
341       CompilerConfig compilerConfig = null;
342       for(compiler : compilerConfigs)
343       {
344          if(!strcmp(compiler.name, name))
345          {
346             compilerConfig = compiler;
347             break;
348          }
349       }
350       if(!compilerConfig && compilerConfigs.count)
351          compilerConfig = compilerConfigs.firstIterator.data;
352       if(compilerConfig)
353          incref compilerConfig;
354       return compilerConfig;
355    }
356
357    ~IDESettings()
358    {
359       compilerConfigs.Free();
360       delete compilerConfigs;
361       recentFiles.Free();
362       delete recentFiles;
363       recentProjects.Free();
364       delete recentProjects;
365       delete docDir;
366    
367       delete projectDefaultTargetDir;
368       delete projectDefaultIntermediateObjDir;
369       delete portableLocation;
370       delete defaultCompiler;
371
372       delete ideFileDialogLocation;
373       delete ideProjectFileDialogLocation;
374       delete displayDriver;
375    }
376
377    void ForcePathSeparatorStyle(bool unixStyle)
378    {
379       char from, to;
380       if(unixStyle)
381          from = '\\', to = '/';
382       else
383          from = '/', to = '\\';
384       if(compilerConfigs && compilerConfigs.count)
385       {
386          int i;
387          for(config : compilerConfigs)
388          {
389             if(config.includeDirs && config.includeDirs.count)
390             {
391                for(i = 0; i < config.includeDirs.count; i++)
392                {
393                   if(config.includeDirs[i] && config.includeDirs[i][0])
394                      ChangeCh(config.includeDirs[i], from, to);
395                }
396             }
397             if(config.libraryDirs && config.libraryDirs.count)
398             {
399                for(i = 0; i < config.libraryDirs.count; i++)
400                {
401                   if(config.libraryDirs[i] && config.libraryDirs[i][0])
402                      ChangeCh(config.libraryDirs[i], from, to);
403                }
404             }
405             if(config.executableDirs && config.executableDirs.count)
406             {
407                for(i = 0; i < config.executableDirs.count; i++)
408                {
409                   if(config.executableDirs[i] && config.executableDirs[i][0])
410                      ChangeCh(config.executableDirs[i], from, to);
411                }
412             }
413          }
414       }
415       if(recentFiles && recentFiles.count)
416       {
417          int c;
418          for(c = 0; c < recentFiles.count; c++)
419          {
420             if(recentFiles[c] && recentFiles[c][0])
421                ChangeCh(recentFiles[c], from, to);
422          }
423       }
424       if(recentProjects && recentProjects.count)
425       {
426          int c;
427          for(c = 0; c < recentProjects.count; c++)
428          {
429             if(recentProjects[c] && recentProjects[c][0])
430                ChangeCh(recentProjects[c], from, to);
431          }
432       }
433       if(docDir && docDir[0])
434          ChangeCh(docDir, from, to);
435       if(ideFileDialogLocation && ideFileDialogLocation[0])
436          ChangeCh(ideFileDialogLocation, from, to);
437       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
438          ChangeCh(ideProjectFileDialogLocation, from, to);
439
440       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
441          ChangeCh(projectDefaultTargetDir, from, to);
442       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
443          ChangeCh(projectDefaultIntermediateObjDir, from, to);
444
445       if(portableLocation && portableLocation[0])
446          ChangeCh(portableLocation, from, to);
447    }
448
449    bool UpdatePortablePaths(char * oldPath, char * newPath)
450    {
451       int oldLen = strlen(oldPath);
452       int newLen = strlen(newPath);
453       if(compilerConfigs && compilerConfigs.count)
454       {
455          for(config : compilerConfigs)
456          {
457             DirTypes c;
458             for(c = 0; c < DirTypes::enumSize; c++)
459             {
460                Array<String> dirs;
461                if(c == executables) dirs = config.executableDirs;
462                else if(c == includes) dirs = config.includeDirs;
463                else if(c == libraries) dirs = config.libraryDirs;
464                if(dirs && dirs.count)
465                {
466                   int i;
467                   for(i = 0; i < dirs.count; i++)
468                   {
469                      if(dirs[i] && dirs[i][0])
470                         dirs[i] = ReplaceInCopyString(dirs[i], oldPath, oldLen, newPath, newLen);
471                   }
472                }
473             }
474          }
475       }
476       if(recentFiles && recentFiles.count)
477       {
478          int c;
479          for(c = 0; c < recentFiles.count; c++)
480          {
481             if(recentFiles[c] && recentFiles[c][0])
482                recentFiles[c] = ReplaceInCopyString(recentFiles[c], oldPath, oldLen, newPath, newLen);
483          }
484       }
485       if(recentProjects && recentProjects.count)
486       {
487          int c;
488          for(c = 0; c < recentProjects.count; c++)
489          {
490             if(recentProjects[c] && recentProjects[c][0])
491                recentProjects[c] = ReplaceInCopyString(recentProjects[c], oldPath, oldLen, newPath, newLen);
492          }
493       }
494       if(docDir && docDir[0])
495          docDir = ReplaceInCopyString(docDir, oldPath, oldLen, newPath, newLen);
496       if(ideFileDialogLocation && ideFileDialogLocation[0])
497          ideFileDialogLocation = ReplaceInCopyString(ideFileDialogLocation, oldPath, oldLen, newPath, newLen);
498       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
499          ideProjectFileDialogLocation = ReplaceInCopyString(ideProjectFileDialogLocation, oldPath, oldLen, newPath, newLen);
500
501       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
502          projectDefaultTargetDir = ReplaceInCopyString(projectDefaultTargetDir, oldPath, oldLen, newPath, newLen);
503       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
504          projectDefaultIntermediateObjDir = ReplaceInCopyString(projectDefaultIntermediateObjDir, oldPath, oldLen, newPath, newLen);
505    }
506
507    char * ReplaceInCopyString(char * string, char * find, int lenFind, char * replace, int lenReplace)
508    {
509       char * output = string;
510       char * start;
511       if(/*string && string[0] && */(start = strstr(string, find)))
512       {
513          if(lenFind == lenReplace)
514             strncpy(start, replace, lenReplace);
515          else
516          {
517             int newLen = strlen(string) + lenReplace - lenFind + 1;
518             char * newString = new char[newLen];
519             start[0] = '\0';
520             strcpy(newString, string);
521             start += lenFind;
522             strcat(newString, replace);
523             strcat(newString, start);
524             delete string;
525             return newString;
526          }
527       }
528       return output;
529    }
530
531    void AddRecentFile(char * fileName)
532    {
533       int c;
534       char * filePath = CopyString(fileName);
535       ChangeCh(filePath, '\\', '/');
536       for(c = 0; c<recentFiles.count; c++)
537       {
538          if(recentFiles[c] && !fstrcmp(recentFiles[c], filePath))
539          {
540             recentFiles.Delete((void *)&recentFiles[c]);
541             c--;
542          }
543       }
544       while(recentFiles.count >= MaxRecent)
545          recentFiles.Delete(recentFiles.GetLast());
546       recentFiles.Insert(null, filePath);
547       //UpdateRecentMenus(owner);
548    }
549
550    void AddRecentProject(char * projectName)
551    {
552       int c;
553       char * filePath = CopyString(projectName);
554       ChangeCh(filePath, '\\', '/');
555       for(c = 0; c<recentProjects.count; c++)
556       {
557          if(recentProjects[c] && !fstrcmp(recentProjects[c], filePath))
558          {
559             recentProjects.Delete((void *)&recentProjects[c]);
560             c--;
561          }
562       }
563       while(recentProjects.count >= MaxRecent)
564          recentProjects.Delete(recentProjects.GetLast());
565       recentProjects.Insert(null, filePath);
566       //UpdateRecentMenus(owner);
567    }
568 }
569
570 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
571 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
572 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
573 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
574 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
575 static const char * compilerTypeLongNames[CompilerType] =
576 {
577    "GNU Compiler Collection (GCC) / GNU Make",
578    "Tiny C Compiler / GNU Make",
579    "Portable C Compiler / GNU Make",
580    "Microsoft Visual Studio 2005 (8.0) Compiler",
581    "Microsoft Visual Studio 2008 (9.0) Compiler",
582    "Microsoft Visual Studio 2010 (10.0) Compiler"
583 };
584 const CompilerType firstCompilerType = gcc;
585 const CompilerType lastCompilerType = vs10;
586 public enum CompilerType
587 {
588    gcc, tcc, pcc, vs8, vs9, vs10;
589
590    property bool isVC
591    {
592       get { return this == vs8 || this == vs9 || this == vs10; }
593    }
594
595    property char *
596    {
597       get { return OnGetString(null, null, null); }
598       set
599       {  
600          if(value)
601          {
602             Platform c;
603             for(c = firstCompilerType; c <= lastCompilerType; c++)
604                if(!strcmpi(value, compilerTypeNames[c]))
605                   return c;
606          }
607          return gcc;
608       }
609    };
610
611    property char * longName { get { return OnGetString(null, (void*)1, null); } };
612    property char * versionString { get { return OnGetString(null, (void*)2, null); } };
613    property char * yearString { get { return OnGetString(null, (void*)3, null); } };
614    property char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
615    property char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
616
617    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
618    {
619       if(this >= firstCompilerType && this <= lastCompilerType)
620       {
621          if(tempString)
622             strcpy(tempString, compilerTypeNames[this]);
623          if(fieldData == null)
624             return compilerTypeNames[this];
625          else if(fieldData == (void*)1)
626             return compilerTypeLongNames[this];
627          else if(fieldData == (void*)2)
628             return compilerTypeVersionString[this];
629          else if(fieldData == (void*)3)
630             return compilerTypeYearString[this];
631          else if(fieldData == (void*)4)
632             return compilerTypeProjectFileExtension[this];
633          else if(fieldData == (void*)5)
634             return compilerTypeSolutionFileVersionString[this];
635       }
636       return null;
637    }
638 };
639
640 class CompilerConfig
641 {
642    class_no_expansion;
643
644    numJobs = 1;
645 public:
646    property char * name
647    {
648       set
649       {
650          delete name;
651          if(value)
652             name = CopyString(value);
653       }
654       get { return name; }
655    }
656    bool readOnly;
657    CompilerType type;
658    Platform targetPlatform;
659    int numJobs;
660    property char * makeCommand
661    {
662       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
663       get { return makeCommand; }
664       isset { return makeCommand && makeCommand[0]; }
665    }
666    property char * ecpCommand
667    {
668       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
669       get { return ecpCommand; }
670       isset { return ecpCommand && ecpCommand[0]; }
671    }
672    property char * eccCommand
673    {
674       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
675       get { return eccCommand; }
676       isset { return eccCommand && eccCommand[0]; }
677    }
678    property char * ecsCommand
679    {
680       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
681       get { return ecsCommand; }
682       isset { return ecsCommand && ecsCommand[0]; }
683    }
684    property char * earCommand
685    {
686       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
687       get { return earCommand; }
688       isset { return earCommand && earCommand[0]; }
689    }
690    property char * cppCommand
691    {
692       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
693       get { return cppCommand; }
694       isset { return cppCommand && cppCommand[0]; }
695    }
696    property char * ccCommand
697    {
698       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
699       get { return ccCommand; }
700       isset { return ccCommand && ccCommand[0]; }
701    }
702    property char * execPrefixCommand
703    {
704       set { delete execPrefixCommand; if(value && value[0]) execPrefixCommand = CopyString(value); }
705       get { return execPrefixCommand; }
706       isset { return execPrefixCommand && execPrefixCommand[0]; }
707    }
708    bool ccacheEnabled;
709    bool distccEnabled;
710    property char * distccHosts
711    {
712       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
713       get { return distccHosts; }
714       isset { return distccHosts && distccHosts[0]; }
715    }
716    property Array<String> includeDirs
717    {
718       set
719       {
720          includeDirs.Free();
721          if(value)
722          {
723             delete includeDirs;
724             includeDirs = value;
725          }
726       }
727       get { return includeDirs; }
728       isset { return includeDirs.count != 0; }
729    }
730    property Array<String> libraryDirs
731    {
732       set
733       {
734          libraryDirs.Free();
735          if(value)
736          {
737             delete libraryDirs;
738             libraryDirs = value;
739          }
740       }
741       get { return libraryDirs; }
742       isset { return libraryDirs.count != 0; }
743    }
744    property Array<String> executableDirs
745    {
746       set
747       {
748          executableDirs.Free();
749          if(value)
750          {
751             delete executableDirs;
752             executableDirs = value;
753          }
754       }
755       get { return executableDirs; }
756       isset { return executableDirs.count != 0; }
757    }
758    property Array<NamedString> environmentVars
759    {
760       set
761       {
762          environmentVars.Free();
763          if(value)
764          {
765             delete environmentVars;
766             environmentVars = value;
767          }
768       }
769       get { return environmentVars; }
770       isset { return environmentVars.count != 0; }
771    }
772 private:
773    Array<String> includeDirs { };
774    Array<String> libraryDirs { };
775    Array<String> executableDirs { };
776    // TODO: Can JSON parse and serialize maps?
777    //EnvironmentVariables { };
778    Array<NamedString> environmentVars { };
779    char * name;
780    char * makeCommand;
781    char * ecpCommand;
782    char * eccCommand;
783    char * ecsCommand;
784    char * earCommand;
785    char * cppCommand;
786    char * ccCommand;
787    char * execPrefixCommand;
788    char * distccHosts;
789    /*union
790    {
791       struct { Array<String> includes, libraries, executables; };
792       Array<String> dirs[DirTypes];
793    }*/
794
795    ~CompilerConfig()
796    {
797       delete name;
798       delete ecpCommand;
799       delete eccCommand;
800       delete ecsCommand;
801       delete earCommand;
802       delete cppCommand;
803       delete ccCommand;
804       delete makeCommand;
805       delete execPrefixCommand;
806       delete distccHosts;
807       if(environmentVars) environmentVars.Free();
808       if(includeDirs) { includeDirs.Free(); }
809       if(libraryDirs) { libraryDirs.Free(); }
810       if(executableDirs) { executableDirs.Free(); }
811    }
812    CompilerConfig Copy()
813    {
814       CompilerConfig copy
815       {
816          name,
817          readOnly,
818          type,
819          targetPlatform,
820          numJobs,
821          makeCommand,
822          ecpCommand,
823          eccCommand,
824          ecsCommand,
825          earCommand,
826          cppCommand,
827          ccCommand,
828          execPrefixCommand,
829          ccacheEnabled,
830          distccEnabled,
831          distccHosts
832       };
833       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
834       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
835       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
836       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
837
838       incref copy;
839       return copy;
840    }
841 }