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