ide: completed proper implementation of portable paths for portable ide's configurati...
[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 * portableLocation
357    {
358       set { delete portableLocation; if(value && value[0]) portableLocation = CopyString(value); }
359       get { return portableLocation ? portableLocation : ""; }
360       isset { return portableLocation && portableLocation[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 * portableLocation;
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 portableLocation;
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(portableLocation && portableLocation[0])
487          ChangeCh(portableLocation, 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
544    char * UpdatePortablePath(char * path, char * location, bool makeAbsolute)
545    {
546       char * output;
547       if(makeAbsolute)
548       {
549          char p[MAX_LOCATION];
550          strcpy(p, location);
551          PathCatSlash(p, path);
552          delete path;
553          output = CopyString(p);
554       }
555       else
556       {
557          PathRelationship rel = eString_PathRelated(path, location, null);
558          if(rel == subPath || rel == identical)
559          {
560             char p[MAX_LOCATION];
561             MakePathRelative(path, location, p);
562             if(!*p) strcpy(p, "./");
563             else ChangeCh(p, '\\', '/');
564             delete path;
565             output = CopyString(p);
566          }
567          else
568             output = path;
569       }
570       return output;
571    }
572
573    void AddRecentFile(char * fileName)
574    {
575       int c;
576       char * filePath = CopyString(fileName);
577       ChangeCh(filePath, '\\', '/');
578       for(c = 0; c<recentFiles.count; c++)
579       {
580          if(recentFiles[c] && !fstrcmp(recentFiles[c], filePath))
581          {
582             recentFiles.Delete((void *)&recentFiles[c]);
583             c--;
584          }
585       }
586       while(recentFiles.count >= MaxRecent)
587          recentFiles.Delete(recentFiles.GetLast());
588       recentFiles.Insert(null, filePath);
589       //UpdateRecentMenus(owner);
590    }
591
592    void AddRecentProject(char * projectName)
593    {
594       int c;
595       char * filePath = CopyString(projectName);
596       ChangeCh(filePath, '\\', '/');
597       for(c = 0; c<recentProjects.count; c++)
598       {
599          if(recentProjects[c] && !fstrcmp(recentProjects[c], filePath))
600          {
601             recentProjects.Delete((void *)&recentProjects[c]);
602             c--;
603          }
604       }
605       while(recentProjects.count >= MaxRecent)
606          recentProjects.Delete(recentProjects.GetLast());
607       recentProjects.Insert(null, filePath);
608       //UpdateRecentMenus(owner);
609    }
610 }
611
612 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
613 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
614 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
615 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
616 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
617 // TODO: i18n with Array
618 static Array<String> compilerTypeLongNames
619 { [
620    $"GNU Compiler Collection (GCC) / GNU Make",
621    $"Tiny C Compiler / GNU Make",
622    $"Portable C Compiler / GNU Make",
623    $"Microsoft Visual Studio 2005 (8.0) Compiler",
624    $"Microsoft Visual Studio 2008 (9.0) Compiler",
625    $"Microsoft Visual Studio 2010 (10.0) Compiler"
626 ] };
627 const CompilerType firstCompilerType = gcc;
628 const CompilerType lastCompilerType = vs10;
629 public enum CompilerType
630 {
631    gcc, tcc, pcc, vs8, vs9, vs10;
632
633    property bool isVC
634    {
635       get { return this == vs8 || this == vs9 || this == vs10; }
636    }
637
638    property char *
639    {
640       get { return OnGetString(null, null, null); }
641       set
642       {  
643          if(value)
644          {
645             Platform c;
646             for(c = firstCompilerType; c <= lastCompilerType; c++)
647                if(!strcmpi(value, compilerTypeNames[c]))
648                   return c;
649          }
650          return gcc;
651       }
652    };
653
654    property char * longName { get { return OnGetString(null, (void*)1, null); } };
655    property char * versionString { get { return OnGetString(null, (void*)2, null); } };
656    property char * yearString { get { return OnGetString(null, (void*)3, null); } };
657    property char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
658    property char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
659
660    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
661    {
662       if(this >= firstCompilerType && this <= lastCompilerType)
663       {
664          if(tempString)
665             strcpy(tempString, compilerTypeNames[this]);
666          if(fieldData == null)
667             return compilerTypeNames[this];
668          else if(fieldData == (void*)1)
669             return compilerTypeLongNames[this];
670          else if(fieldData == (void*)2)
671             return compilerTypeVersionString[this];
672          else if(fieldData == (void*)3)
673             return compilerTypeYearString[this];
674          else if(fieldData == (void*)4)
675             return compilerTypeProjectFileExtension[this];
676          else if(fieldData == (void*)5)
677             return compilerTypeSolutionFileVersionString[this];
678       }
679       return null;
680    }
681 };
682
683 class CompilerConfig
684 {
685    class_no_expansion;
686
687    numJobs = 1;
688 public:
689    property char * name
690    {
691       set
692       {
693          delete name;
694          if(value)
695             name = CopyString(value);
696       }
697       get { return name; }
698    }
699    bool readOnly;
700    CompilerType type;
701    Platform targetPlatform;
702    int numJobs;
703    property char * makeCommand
704    {
705       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
706       get { return makeCommand; }
707       isset { return makeCommand && makeCommand[0]; }
708    }
709    property char * ecpCommand
710    {
711       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
712       get { return ecpCommand; }
713       isset { return ecpCommand && ecpCommand[0]; }
714    }
715    property char * eccCommand
716    {
717       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
718       get { return eccCommand; }
719       isset { return eccCommand && eccCommand[0]; }
720    }
721    property char * ecsCommand
722    {
723       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
724       get { return ecsCommand; }
725       isset { return ecsCommand && ecsCommand[0]; }
726    }
727    property char * earCommand
728    {
729       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
730       get { return earCommand; }
731       isset { return earCommand && earCommand[0]; }
732    }
733    property char * cppCommand
734    {
735       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
736       get { return cppCommand; }
737       isset { return cppCommand && cppCommand[0]; }
738    }
739    property char * ccCommand
740    {
741       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
742       get { return ccCommand; }
743       isset { return ccCommand && ccCommand[0]; }
744    }
745    property char * execPrefixCommand
746    {
747       set { delete execPrefixCommand; if(value && value[0]) execPrefixCommand = CopyString(value); }
748       get { return execPrefixCommand; }
749       isset { return execPrefixCommand && execPrefixCommand[0]; }
750    }
751    bool ccacheEnabled;
752    bool distccEnabled;
753    property char * distccHosts
754    {
755       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
756       get { return distccHosts; }
757       isset { return distccHosts && distccHosts[0]; }
758    }
759    property Array<String> includeDirs
760    {
761       set
762       {
763          includeDirs.Free();
764          if(value)
765          {
766             delete includeDirs;
767             includeDirs = value;
768          }
769       }
770       get { return includeDirs; }
771       isset { return includeDirs.count != 0; }
772    }
773    property Array<String> libraryDirs
774    {
775       set
776       {
777          libraryDirs.Free();
778          if(value)
779          {
780             delete libraryDirs;
781             libraryDirs = value;
782          }
783       }
784       get { return libraryDirs; }
785       isset { return libraryDirs.count != 0; }
786    }
787    property Array<String> executableDirs
788    {
789       set
790       {
791          executableDirs.Free();
792          if(value)
793          {
794             delete executableDirs;
795             executableDirs = value;
796          }
797       }
798       get { return executableDirs; }
799       isset { return executableDirs.count != 0; }
800    }
801    property Array<NamedString> environmentVars
802    {
803       set
804       {
805          environmentVars.Free();
806          if(value)
807          {
808             delete environmentVars;
809             environmentVars = value;
810          }
811       }
812       get { return environmentVars; }
813       isset { return environmentVars.count != 0; }
814    }
815 private:
816    Array<String> includeDirs { };
817    Array<String> libraryDirs { };
818    Array<String> executableDirs { };
819    // TODO: Can JSON parse and serialize maps?
820    //EnvironmentVariables { };
821    Array<NamedString> environmentVars { };
822    char * name;
823    char * makeCommand;
824    char * ecpCommand;
825    char * eccCommand;
826    char * ecsCommand;
827    char * earCommand;
828    char * cppCommand;
829    char * ccCommand;
830    char * execPrefixCommand;
831    char * distccHosts;
832    /*union
833    {
834       struct { Array<String> includes, libraries, executables; };
835       Array<String> dirs[DirTypes];
836    }*/
837
838    ~CompilerConfig()
839    {
840       delete name;
841       delete ecpCommand;
842       delete eccCommand;
843       delete ecsCommand;
844       delete earCommand;
845       delete cppCommand;
846       delete ccCommand;
847       delete makeCommand;
848       delete execPrefixCommand;
849       delete distccHosts;
850       if(environmentVars) environmentVars.Free();
851       if(includeDirs) { includeDirs.Free(); }
852       if(libraryDirs) { libraryDirs.Free(); }
853       if(executableDirs) { executableDirs.Free(); }
854    }
855    CompilerConfig Copy()
856    {
857       CompilerConfig copy
858       {
859          name,
860          readOnly,
861          type,
862          targetPlatform,
863          numJobs,
864          makeCommand,
865          ecpCommand,
866          eccCommand,
867          ecsCommand,
868          earCommand,
869          cppCommand,
870          ccCommand,
871          execPrefixCommand,
872          ccacheEnabled,
873          distccEnabled,
874          distccHosts
875       };
876       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
877       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
878       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
879       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
880
881       incref copy;
882       return copy;
883    }
884 }