ide: settings, installer: Fixed migration of existing global settings
[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       CompilerConfig defaultCompiler = null;
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; data.displayDriver = CopyString(oldSettings.displayDriver);
207                data.projectDefaultTargetDir = oldSettings.projectDefaultTargetDir;
208                data.projectDefaultIntermediateObjDir = oldSettings.projectDefaultIntermediateObjDir;
209                         
210                Save();
211                result = success;
212             }
213             delete oldSettings;
214          }
215          if(result == fileNotFound || !data)
216          {
217             data = (IDESettings)this.data;
218             data.useFreeCaret = true;
219             data.showLineNumbers = true;
220             data.caretFollowsScrolling = true;
221          }
222       }
223       // Ensure we have a default compiler
224       defaultCompiler = data.GetCompilerConfig(defaultCompilerName);
225       if(!defaultCompiler)
226       {
227          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
228          data.compilerConfigs.Add(defaultCompiler);
229       }
230
231       // We incref the compilers below, so reset refCount to 0
232       defaultCompiler._refCount = 0;
233
234       CloseAndMonitor();
235       if(data.compilerConfigs)
236       {
237          for(c : data.compilerConfigs)
238          {
239             CompilerConfig compiler = c;
240             incref compiler;
241          }
242       }
243       if(portable)
244       {
245          char location[MAX_LOCATION];
246          LocateModule(null, location);
247          StripLastDirectory(location, location);
248          if(location[0] && FileExists(location).isDirectory)
249          {
250             if(data.portableLocation && data.portableLocation[0] && strcmp(data.portableLocation, location))
251             {
252                data.UpdatePortablePaths(data.portableLocation, location);
253                data.portableLocation = location;
254                Save();
255             }
256          }
257       }
258       data.ForcePathSeparatorStyle(true);
259       OnLoad(data);
260    }
261
262    void Save()
263    {
264       IDESettings data = (IDESettings)this.data;
265       Platform runtimePlatform = GetRuntimePlatform();
266       data.ForcePathSeparatorStyle(runtimePlatform != win32);
267       if(!GlobalSettings::Save())
268          PrintLn("Error saving IDE settings");
269       if(runtimePlatform == win32)
270          data.ForcePathSeparatorStyle(true);
271       CloseAndMonitor();
272    }
273 }
274
275 class IDESettings : GlobalSettingsData
276 {
277 public:
278    List<CompilerConfig> compilerConfigs { };
279    Array<String> recentFiles { };
280    Array<String> recentProjects { };
281    property char * docDir
282    {
283       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
284       get { return docDir ? docDir : ""; }
285       isset { return docDir && docDir[0]; }
286    }
287    property char * ideFileDialogLocation
288    {
289       set { delete ideFileDialogLocation; if(value && value[0]) ideFileDialogLocation = CopyString(value); }
290       get { return ideFileDialogLocation ? ideFileDialogLocation : ""; }
291       isset { return ideFileDialogLocation && ideFileDialogLocation[0]; }
292    }
293    property char * ideProjectFileDialogLocation
294    {
295       set { delete ideProjectFileDialogLocation; if(value && value[0]) ideProjectFileDialogLocation = CopyString(value); }
296       get { return ideProjectFileDialogLocation ? ideProjectFileDialogLocation : ""; }
297       isset { return ideProjectFileDialogLocation && ideProjectFileDialogLocation[0]; }
298    }
299    bool useFreeCaret;
300    bool showLineNumbers;
301    bool caretFollowsScrolling;
302    char * displayDriver;
303    
304    // TODO: Classify settings
305    //EditorSettings editor { };
306
307    property char * projectDefaultTargetDir
308    {
309       set { delete projectDefaultTargetDir; if(value && value[0]) projectDefaultTargetDir = CopyValidateMakefilePath(value); }
310       get { return projectDefaultTargetDir ? projectDefaultTargetDir : ""; }
311       isset { return projectDefaultTargetDir && projectDefaultTargetDir[0]; }
312    }
313    property char * projectDefaultIntermediateObjDir
314    {
315       set { delete projectDefaultIntermediateObjDir; if(value && value[0]) projectDefaultIntermediateObjDir = CopyValidateMakefilePath(value); }
316       get { return projectDefaultIntermediateObjDir ? projectDefaultIntermediateObjDir : ""; }
317       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
318    }
319
320    property char * portableLocation
321    {
322       set { delete portableLocation; if(value && value[0]) portableLocation = CopyString(value); }
323       get { return portableLocation ? portableLocation : ""; }
324       isset { return portableLocation && portableLocation[0]; }
325    }
326
327    property char * defaultCompiler
328    {
329       set { delete defaultCompiler; if(value && value[0]) defaultCompiler = CopyString(value); }
330       get { return defaultCompiler && defaultCompiler[0] ? defaultCompiler : defaultCompilerName; }
331       isset { return defaultCompiler && defaultCompiler[0]; }
332    }
333
334 private:
335    char * docDir;
336    char * ideFileDialogLocation;
337    char * ideProjectFileDialogLocation;
338    char * projectDefaultTargetDir;
339    char * projectDefaultIntermediateObjDir;
340    char * portableLocation;
341    char * defaultCompiler;
342
343    CompilerConfig GetCompilerConfig(String compilerName)
344    {
345       char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
346       CompilerConfig compilerConfig = null;
347       for(compiler : compilerConfigs)
348       {
349          if(!strcmp(compiler.name, name))
350          {
351             compilerConfig = compiler;
352             break;
353          }
354       }
355       if(!compilerConfig && compilerConfigs.count)
356          compilerConfig = compilerConfigs.firstIterator.data;
357       if(compilerConfig)
358          incref compilerConfig;
359       return compilerConfig;
360    }
361
362    ~IDESettings()
363    {
364       compilerConfigs.Free();
365       delete compilerConfigs;
366       recentFiles.Free();
367       delete recentFiles;
368       recentProjects.Free();
369       delete recentProjects;
370       delete docDir;
371    
372       delete projectDefaultTargetDir;
373       delete projectDefaultIntermediateObjDir;
374       delete portableLocation;
375       delete defaultCompiler;
376
377       delete ideFileDialogLocation;
378       delete ideProjectFileDialogLocation;
379       delete displayDriver;
380    }
381
382    void ForcePathSeparatorStyle(bool unixStyle)
383    {
384       char from, to;
385       if(unixStyle)
386          from = '\\', to = '/';
387       else
388          from = '/', to = '\\';
389       if(compilerConfigs && compilerConfigs.count)
390       {
391          int i;
392          for(config : compilerConfigs)
393          {
394             if(config.includeDirs && config.includeDirs.count)
395             {
396                for(i = 0; i < config.includeDirs.count; i++)
397                {
398                   if(config.includeDirs[i] && config.includeDirs[i][0])
399                      ChangeCh(config.includeDirs[i], from, to);
400                }
401             }
402             if(config.libraryDirs && config.libraryDirs.count)
403             {
404                for(i = 0; i < config.libraryDirs.count; i++)
405                {
406                   if(config.libraryDirs[i] && config.libraryDirs[i][0])
407                      ChangeCh(config.libraryDirs[i], from, to);
408                }
409             }
410             if(config.executableDirs && config.executableDirs.count)
411             {
412                for(i = 0; i < config.executableDirs.count; i++)
413                {
414                   if(config.executableDirs[i] && config.executableDirs[i][0])
415                      ChangeCh(config.executableDirs[i], from, to);
416                }
417             }
418          }
419       }
420       if(recentFiles && recentFiles.count)
421       {
422          int c;
423          for(c = 0; c < recentFiles.count; c++)
424          {
425             if(recentFiles[c] && recentFiles[c][0])
426                ChangeCh(recentFiles[c], from, to);
427          }
428       }
429       if(recentProjects && recentProjects.count)
430       {
431          int c;
432          for(c = 0; c < recentProjects.count; c++)
433          {
434             if(recentProjects[c] && recentProjects[c][0])
435                ChangeCh(recentProjects[c], from, to);
436          }
437       }
438       if(docDir && docDir[0])
439          ChangeCh(docDir, from, to);
440       if(ideFileDialogLocation && ideFileDialogLocation[0])
441          ChangeCh(ideFileDialogLocation, from, to);
442       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
443          ChangeCh(ideProjectFileDialogLocation, from, to);
444
445       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
446          ChangeCh(projectDefaultTargetDir, from, to);
447       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
448          ChangeCh(projectDefaultIntermediateObjDir, from, to);
449
450       if(portableLocation && portableLocation[0])
451          ChangeCh(portableLocation, from, to);
452    }
453
454    bool UpdatePortablePaths(char * oldPath, char * newPath)
455    {
456       int oldLen = strlen(oldPath);
457       int newLen = strlen(newPath);
458       if(compilerConfigs && compilerConfigs.count)
459       {
460          for(config : compilerConfigs)
461          {
462             DirTypes c;
463             for(c = 0; c < DirTypes::enumSize; c++)
464             {
465                Array<String> dirs;
466                if(c == executables) dirs = config.executableDirs;
467                else if(c == includes) dirs = config.includeDirs;
468                else if(c == libraries) dirs = config.libraryDirs;
469                if(dirs && dirs.count)
470                {
471                   int i;
472                   for(i = 0; i < dirs.count; i++)
473                   {
474                      if(dirs[i] && dirs[i][0])
475                         dirs[i] = ReplaceInCopyString(dirs[i], oldPath, oldLen, newPath, newLen);
476                   }
477                }
478             }
479          }
480       }
481       if(recentFiles && recentFiles.count)
482       {
483          int c;
484          for(c = 0; c < recentFiles.count; c++)
485          {
486             if(recentFiles[c] && recentFiles[c][0])
487                recentFiles[c] = ReplaceInCopyString(recentFiles[c], oldPath, oldLen, newPath, newLen);
488          }
489       }
490       if(recentProjects && recentProjects.count)
491       {
492          int c;
493          for(c = 0; c < recentProjects.count; c++)
494          {
495             if(recentProjects[c] && recentProjects[c][0])
496                recentProjects[c] = ReplaceInCopyString(recentProjects[c], oldPath, oldLen, newPath, newLen);
497          }
498       }
499       if(docDir && docDir[0])
500          docDir = ReplaceInCopyString(docDir, oldPath, oldLen, newPath, newLen);
501       if(ideFileDialogLocation && ideFileDialogLocation[0])
502          ideFileDialogLocation = ReplaceInCopyString(ideFileDialogLocation, oldPath, oldLen, newPath, newLen);
503       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
504          ideProjectFileDialogLocation = ReplaceInCopyString(ideProjectFileDialogLocation, oldPath, oldLen, newPath, newLen);
505
506       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
507          projectDefaultTargetDir = ReplaceInCopyString(projectDefaultTargetDir, oldPath, oldLen, newPath, newLen);
508       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
509          projectDefaultIntermediateObjDir = ReplaceInCopyString(projectDefaultIntermediateObjDir, oldPath, oldLen, newPath, newLen);
510       return true;
511    }
512
513    char * ReplaceInCopyString(char * string, char * find, int lenFind, char * replace, int lenReplace)
514    {
515       char * output = string;
516       char * start;
517       if(/*string && string[0] && */(start = strstr(string, find)))
518       {
519          if(lenFind == lenReplace)
520             strncpy(start, replace, lenReplace);
521          else
522          {
523             int newLen = strlen(string) + lenReplace - lenFind + 1;
524             char * newString = new char[newLen];
525             start[0] = '\0';
526             strcpy(newString, string);
527             start += lenFind;
528             strcat(newString, replace);
529             strcat(newString, start);
530             delete string;
531             return newString;
532          }
533       }
534       return output;
535    }
536
537    void AddRecentFile(char * fileName)
538    {
539       int c;
540       char * filePath = CopyString(fileName);
541       ChangeCh(filePath, '\\', '/');
542       for(c = 0; c<recentFiles.count; c++)
543       {
544          if(recentFiles[c] && !fstrcmp(recentFiles[c], filePath))
545          {
546             recentFiles.Delete((void *)&recentFiles[c]);
547             c--;
548          }
549       }
550       while(recentFiles.count >= MaxRecent)
551          recentFiles.Delete(recentFiles.GetLast());
552       recentFiles.Insert(null, filePath);
553       //UpdateRecentMenus(owner);
554    }
555
556    void AddRecentProject(char * projectName)
557    {
558       int c;
559       char * filePath = CopyString(projectName);
560       ChangeCh(filePath, '\\', '/');
561       for(c = 0; c<recentProjects.count; c++)
562       {
563          if(recentProjects[c] && !fstrcmp(recentProjects[c], filePath))
564          {
565             recentProjects.Delete((void *)&recentProjects[c]);
566             c--;
567          }
568       }
569       while(recentProjects.count >= MaxRecent)
570          recentProjects.Delete(recentProjects.GetLast());
571       recentProjects.Insert(null, filePath);
572       //UpdateRecentMenus(owner);
573    }
574 }
575
576 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
577 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
578 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
579 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
580 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
581 // TODO: i18n with Array
582 static Array<String> compilerTypeLongNames
583 { [
584    $"GNU Compiler Collection (GCC) / GNU Make",
585    $"Tiny C Compiler / GNU Make",
586    $"Portable C Compiler / GNU Make",
587    $"Microsoft Visual Studio 2005 (8.0) Compiler",
588    $"Microsoft Visual Studio 2008 (9.0) Compiler",
589    $"Microsoft Visual Studio 2010 (10.0) Compiler"
590 ] };
591 const CompilerType firstCompilerType = gcc;
592 const CompilerType lastCompilerType = vs10;
593 public enum CompilerType
594 {
595    gcc, tcc, pcc, vs8, vs9, vs10;
596
597    property bool isVC
598    {
599       get { return this == vs8 || this == vs9 || this == vs10; }
600    }
601
602    property char *
603    {
604       get { return OnGetString(null, null, null); }
605       set
606       {  
607          if(value)
608          {
609             Platform c;
610             for(c = firstCompilerType; c <= lastCompilerType; c++)
611                if(!strcmpi(value, compilerTypeNames[c]))
612                   return c;
613          }
614          return gcc;
615       }
616    };
617
618    property char * longName { get { return OnGetString(null, (void*)1, null); } };
619    property char * versionString { get { return OnGetString(null, (void*)2, null); } };
620    property char * yearString { get { return OnGetString(null, (void*)3, null); } };
621    property char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
622    property char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
623
624    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
625    {
626       if(this >= firstCompilerType && this <= lastCompilerType)
627       {
628          if(tempString)
629             strcpy(tempString, compilerTypeNames[this]);
630          if(fieldData == null)
631             return compilerTypeNames[this];
632          else if(fieldData == (void*)1)
633             return compilerTypeLongNames[this];
634          else if(fieldData == (void*)2)
635             return compilerTypeVersionString[this];
636          else if(fieldData == (void*)3)
637             return compilerTypeYearString[this];
638          else if(fieldData == (void*)4)
639             return compilerTypeProjectFileExtension[this];
640          else if(fieldData == (void*)5)
641             return compilerTypeSolutionFileVersionString[this];
642       }
643       return null;
644    }
645 };
646
647 class CompilerConfig
648 {
649    class_no_expansion;
650
651    numJobs = 1;
652 public:
653    property char * name
654    {
655       set
656       {
657          delete name;
658          if(value)
659             name = CopyString(value);
660       }
661       get { return name; }
662    }
663    bool readOnly;
664    CompilerType type;
665    Platform targetPlatform;
666    int numJobs;
667    property char * makeCommand
668    {
669       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
670       get { return makeCommand; }
671       isset { return makeCommand && makeCommand[0]; }
672    }
673    property char * ecpCommand
674    {
675       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
676       get { return ecpCommand; }
677       isset { return ecpCommand && ecpCommand[0]; }
678    }
679    property char * eccCommand
680    {
681       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
682       get { return eccCommand; }
683       isset { return eccCommand && eccCommand[0]; }
684    }
685    property char * ecsCommand
686    {
687       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
688       get { return ecsCommand; }
689       isset { return ecsCommand && ecsCommand[0]; }
690    }
691    property char * earCommand
692    {
693       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
694       get { return earCommand; }
695       isset { return earCommand && earCommand[0]; }
696    }
697    property char * cppCommand
698    {
699       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
700       get { return cppCommand; }
701       isset { return cppCommand && cppCommand[0]; }
702    }
703    property char * ccCommand
704    {
705       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
706       get { return ccCommand; }
707       isset { return ccCommand && ccCommand[0]; }
708    }
709    property char * execPrefixCommand
710    {
711       set { delete execPrefixCommand; if(value && value[0]) execPrefixCommand = CopyString(value); }
712       get { return execPrefixCommand; }
713       isset { return execPrefixCommand && execPrefixCommand[0]; }
714    }
715    bool ccacheEnabled;
716    bool distccEnabled;
717    property char * distccHosts
718    {
719       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
720       get { return distccHosts; }
721       isset { return distccHosts && distccHosts[0]; }
722    }
723    property Array<String> includeDirs
724    {
725       set
726       {
727          includeDirs.Free();
728          if(value)
729          {
730             delete includeDirs;
731             includeDirs = value;
732          }
733       }
734       get { return includeDirs; }
735       isset { return includeDirs.count != 0; }
736    }
737    property Array<String> libraryDirs
738    {
739       set
740       {
741          libraryDirs.Free();
742          if(value)
743          {
744             delete libraryDirs;
745             libraryDirs = value;
746          }
747       }
748       get { return libraryDirs; }
749       isset { return libraryDirs.count != 0; }
750    }
751    property Array<String> executableDirs
752    {
753       set
754       {
755          executableDirs.Free();
756          if(value)
757          {
758             delete executableDirs;
759             executableDirs = value;
760          }
761       }
762       get { return executableDirs; }
763       isset { return executableDirs.count != 0; }
764    }
765    property Array<NamedString> environmentVars
766    {
767       set
768       {
769          environmentVars.Free();
770          if(value)
771          {
772             delete environmentVars;
773             environmentVars = value;
774          }
775       }
776       get { return environmentVars; }
777       isset { return environmentVars.count != 0; }
778    }
779 private:
780    Array<String> includeDirs { };
781    Array<String> libraryDirs { };
782    Array<String> executableDirs { };
783    // TODO: Can JSON parse and serialize maps?
784    //EnvironmentVariables { };
785    Array<NamedString> environmentVars { };
786    char * name;
787    char * makeCommand;
788    char * ecpCommand;
789    char * eccCommand;
790    char * ecsCommand;
791    char * earCommand;
792    char * cppCommand;
793    char * ccCommand;
794    char * execPrefixCommand;
795    char * distccHosts;
796    /*union
797    {
798       struct { Array<String> includes, libraries, executables; };
799       Array<String> dirs[DirTypes];
800    }*/
801
802    ~CompilerConfig()
803    {
804       delete name;
805       delete ecpCommand;
806       delete eccCommand;
807       delete ecsCommand;
808       delete earCommand;
809       delete cppCommand;
810       delete ccCommand;
811       delete makeCommand;
812       delete execPrefixCommand;
813       delete distccHosts;
814       if(environmentVars) environmentVars.Free();
815       if(includeDirs) { includeDirs.Free(); }
816       if(libraryDirs) { libraryDirs.Free(); }
817       if(executableDirs) { executableDirs.Free(); }
818    }
819    CompilerConfig Copy()
820    {
821       CompilerConfig copy
822       {
823          name,
824          readOnly,
825          type,
826          targetPlatform,
827          numJobs,
828          makeCommand,
829          ecpCommand,
830          eccCommand,
831          ecsCommand,
832          earCommand,
833          cppCommand,
834          ccCommand,
835          execPrefixCommand,
836          ccacheEnabled,
837          distccEnabled,
838          distccHosts
839       };
840       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
841       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
842       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
843       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
844
845       incref copy;
846       return copy;
847    }
848 }