compiler/libec Improvements to conversion from AST->Type class, and to outputting...
[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)$(COMPILER_SUFFIX)$(DEBUG_SUFFIX)";
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,                    5,                 6,            0,                   1,                    2,        7 };
73    const char * vars[] = { "$(MODULE)", "$(CONFIG)", "$(PLATFORM)", "$(COMPILER)", "$(TARGET)", "$(COMPILER_SUFFIX)", "$(DEBUG_SUFFIX)", "$(PROJECT)",  "$(CONFIGURATION)", "$(TARGET_PLATFORM)",(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       cxxDefaultCommand
145    };
146    incref defaultCompiler;
147    return defaultCompiler;
148 }
149
150 class IDESettingsContainer : GlobalSettings
151 {
152 #ifdef SETTINGS_TEST
153    settingsName = "ecereIDESettingsTest";
154 #else
155    settingsName = "ecereIDE";
156 #endif
157
158    virtual void OnLoad(GlobalSettingsData data);
159
160    char moduleLocation[MAX_LOCATION];
161
162 private:
163    IDESettingsContainer()
164    {
165       char path[MAX_LOCATION];
166       char * start;
167       LocateModule(null, moduleLocation);
168       strcpy(path, moduleLocation);
169       StripLastDirectory(moduleLocation, moduleLocation);
170       ChangeCh(moduleLocation, '\\', '/');
171       // PortableApps.com directory structure
172       if((start = strstr(path, "\\App\\EcereSDK\\bin\\ide.exe")))
173       {
174          char configFilePath[MAX_LOCATION];
175          char defaultConfigFilePath[MAX_LOCATION];
176
177          start[0] = '\0';
178
179          strcpy(configFilePath, path);
180          PathCat(configFilePath, "Data");
181          PathCat(configFilePath, "ecereIDE.ini");
182
183          strcpy(defaultConfigFilePath, path);
184          PathCat(defaultConfigFilePath, "App");
185          PathCat(defaultConfigFilePath, "DefaultData");
186          PathCat(defaultConfigFilePath, "ecereIDE.ini");
187          
188          if(FileExists(defaultConfigFilePath))
189          {
190             if(!FileExists(configFilePath))
191             {
192                File f = FileOpen(defaultConfigFilePath, read);
193                f.CopyTo(configFilePath);
194                f.Flush();
195                delete f;
196             }
197             PathCat(path, "Data");
198             // the forced settings location will only be
199             // used if the running ide's path matches 
200             // the PortableApps.com directory structure
201             // and the default ini file is found in 
202             // the DefaultData directory
203             settingsLocation = path;
204             portable = true;
205          }
206       }
207    }
208
209    void OnAskReloadSettings()
210    {
211       /*if(MessageBox { type = YesNo, master = this, 
212             text = "Global Settings Modified Externally", 
213             contents = "The global settings were modified by another instance.\n"
214             "Would you like to reload them?" }.Modal() == Yes)*/
215       {
216          Load();
217       }
218    }
219    // These must be public as they are not virtual
220    public SettingsIOResult Load()
221    {
222       SettingsIOResult result = GlobalSettings::Load();
223       IDESettings data = (IDESettings)this.data;
224       CompilerConfig defaultCompiler = null;
225       if(!data)
226       {
227          this.data = IDESettings { };
228          if(dataOwner)
229             *dataOwner = this.data;
230
231          if(result == fileNotCompatibleWithDriver)
232          {
233             bool loaded;
234             OldIDESettings oldSettings { };
235             Close();
236             loaded = oldSettings.Load() == success;
237             oldSettings.Close();
238             if(loaded)
239             {
240                data = (IDESettings)this.data;
241
242                for(c : oldSettings.compilerConfigs)
243                   data.compilerConfigs.Add(c.Copy());
244
245                for(s : oldSettings.recentFiles) data.recentFiles.Add(CopyString(s));
246                for(s : oldSettings.recentProjects) data.recentProjects.Add(CopyString(s));
247
248                data.docDir = oldSettings.docDir;
249                data.ideFileDialogLocation = oldSettings.ideFileDialogLocation;
250                data.ideProjectFileDialogLocation = oldSettings.ideProjectFileDialogLocation;
251                data.useFreeCaret = oldSettings.useFreeCaret;
252                data.showLineNumbers = oldSettings.showLineNumbers;
253                data.caretFollowsScrolling = oldSettings.caretFollowsScrolling;
254                delete data.displayDriver; data.displayDriver = CopyString(oldSettings.displayDriver);
255                data.projectDefaultTargetDir = oldSettings.projectDefaultTargetDir;
256                data.projectDefaultIntermediateObjDir = oldSettings.projectDefaultIntermediateObjDir;
257                         
258                Save();
259                result = success;
260             }
261             delete oldSettings;
262          }
263          if(result == fileNotFound || !data)
264          {
265             data = (IDESettings)this.data;
266             data.useFreeCaret = true;
267             data.showLineNumbers = true;
268             data.caretFollowsScrolling = true;
269          }
270       }
271       // Ensure we have a default compiler
272       defaultCompiler = data.GetCompilerConfig(defaultCompilerName);
273       if(!defaultCompiler)
274       {
275          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
276          data.compilerConfigs.Add(defaultCompiler);
277       }
278
279       // We incref the compilers below, so reset refCount to 0
280       defaultCompiler._refCount = 0;
281
282       CloseAndMonitor();
283       if(data.compilerConfigs)
284       {
285          for(c : data.compilerConfigs)
286          {
287             CompilerConfig compiler = c;
288             char * cxxCommand = compiler.cxxCommand;
289             if(!cxxCommand || !cxxCommand[0])
290                compiler.cxxCommand = cxxDefaultCommand;
291             incref compiler;
292          }
293       }
294       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
295          data.ManagePortablePaths(moduleLocation, true);
296       data.ForcePathSeparatorStyle(true);
297       OnLoad(data);
298       return result;
299    }
300
301    public void Save()
302    {
303       IDESettings data = (IDESettings)this.data;
304       Platform runtimePlatform = GetRuntimePlatform();
305       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
306          data.ManagePortablePaths(moduleLocation, false);
307       data.ForcePathSeparatorStyle(true);
308       if(!GlobalSettings::Save())
309          PrintLn("Error saving IDE settings");
310       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
311          data.ManagePortablePaths(moduleLocation, true);
312       CloseAndMonitor();
313    }
314 }
315
316 class IDESettings : GlobalSettingsData
317 {
318 public:
319    List<CompilerConfig> compilerConfigs { };
320    Array<String> recentFiles { };
321    Array<String> recentProjects { };
322    property char * docDir
323    {
324       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
325       get { return docDir ? docDir : ""; }
326       isset { return docDir && docDir[0]; }
327    }
328    property char * ideFileDialogLocation
329    {
330       set { delete ideFileDialogLocation; if(value && value[0]) ideFileDialogLocation = CopyString(value); }
331       get { return ideFileDialogLocation ? ideFileDialogLocation : ""; }
332       isset { return ideFileDialogLocation && ideFileDialogLocation[0]; }
333    }
334    property char * ideProjectFileDialogLocation
335    {
336       set { delete ideProjectFileDialogLocation; if(value && value[0]) ideProjectFileDialogLocation = CopyString(value); }
337       get { return ideProjectFileDialogLocation ? ideProjectFileDialogLocation : ""; }
338       isset { return ideProjectFileDialogLocation && ideProjectFileDialogLocation[0]; }
339    }
340    bool useFreeCaret;
341    bool showLineNumbers;
342    bool caretFollowsScrolling;
343    char * displayDriver;
344    
345    // TODO: Classify settings
346    //EditorSettings editor { };
347
348    property char * projectDefaultTargetDir
349    {
350       set { delete projectDefaultTargetDir; if(value && value[0]) projectDefaultTargetDir = CopyValidateMakefilePath(value); }
351       get { return projectDefaultTargetDir ? projectDefaultTargetDir : ""; }
352       isset { return projectDefaultTargetDir && projectDefaultTargetDir[0]; }
353    }
354    property char * projectDefaultIntermediateObjDir
355    {
356       set { delete projectDefaultIntermediateObjDir; if(value && value[0]) projectDefaultIntermediateObjDir = CopyValidateMakefilePath(value); }
357       get { return projectDefaultIntermediateObjDir ? projectDefaultIntermediateObjDir : ""; }
358       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
359    }
360
361    property char * compilerConfigsDir
362    {
363       set { delete compilerConfigsDir; if(value && value[0]) compilerConfigsDir = CopyString(value); }
364       get { return compilerConfigsDir ? compilerConfigsDir : ""; }
365       isset { return compilerConfigsDir && compilerConfigsDir[0]; }
366    }
367
368    property char * defaultCompiler
369    {
370       set { delete defaultCompiler; if(value && value[0]) defaultCompiler = CopyString(value); }
371       get { return defaultCompiler && defaultCompiler[0] ? defaultCompiler : defaultCompilerName; }
372       isset { return defaultCompiler && defaultCompiler[0]; }
373    }
374
375 private:
376    char * docDir;
377    char * ideFileDialogLocation;
378    char * ideProjectFileDialogLocation;
379    char * projectDefaultTargetDir;
380    char * projectDefaultIntermediateObjDir;
381    char * compilerConfigsDir;
382    char * defaultCompiler;
383
384    CompilerConfig GetCompilerConfig(String compilerName)
385    {
386       char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
387       CompilerConfig compilerConfig = null;
388       for(compiler : compilerConfigs)
389       {
390          if(!strcmp(compiler.name, name))
391          {
392             compilerConfig = compiler;
393             break;
394          }
395       }
396       if(!compilerConfig && compilerConfigs.count)
397          compilerConfig = compilerConfigs.firstIterator.data;
398       if(compilerConfig)
399          incref compilerConfig;
400       return compilerConfig;
401    }
402
403    ~IDESettings()
404    {
405       compilerConfigs.Free();
406       delete compilerConfigs;
407       recentFiles.Free();
408       delete recentFiles;
409       recentProjects.Free();
410       delete recentProjects;
411       delete docDir;
412    
413       delete projectDefaultTargetDir;
414       delete projectDefaultIntermediateObjDir;
415       delete compilerConfigsDir;
416       delete defaultCompiler;
417
418       delete ideFileDialogLocation;
419       delete ideProjectFileDialogLocation;
420       delete displayDriver;
421    }
422
423    void ForcePathSeparatorStyle(bool unixStyle)
424    {
425       char from, to;
426       if(unixStyle)
427          from = '\\', to = '/';
428       else
429          from = '/', to = '\\';
430       if(compilerConfigs && compilerConfigs.count)
431       {
432          int i;
433          for(config : compilerConfigs)
434          {
435             if(config.includeDirs && config.includeDirs.count)
436             {
437                for(i = 0; i < config.includeDirs.count; i++)
438                {
439                   if(config.includeDirs[i] && config.includeDirs[i][0])
440                      ChangeCh(config.includeDirs[i], from, to);
441                }
442             }
443             if(config.libraryDirs && config.libraryDirs.count)
444             {
445                for(i = 0; i < config.libraryDirs.count; i++)
446                {
447                   if(config.libraryDirs[i] && config.libraryDirs[i][0])
448                      ChangeCh(config.libraryDirs[i], from, to);
449                }
450             }
451             if(config.executableDirs && config.executableDirs.count)
452             {
453                for(i = 0; i < config.executableDirs.count; i++)
454                {
455                   if(config.executableDirs[i] && config.executableDirs[i][0])
456                      ChangeCh(config.executableDirs[i], from, to);
457                }
458             }
459          }
460       }
461       if(recentFiles && recentFiles.count)
462       {
463          int c;
464          for(c = 0; c < recentFiles.count; c++)
465          {
466             if(recentFiles[c] && recentFiles[c][0])
467                ChangeCh(recentFiles[c], from, to);
468          }
469       }
470       if(recentProjects && recentProjects.count)
471       {
472          int c;
473          for(c = 0; c < recentProjects.count; c++)
474          {
475             if(recentProjects[c] && recentProjects[c][0])
476                ChangeCh(recentProjects[c], from, to);
477          }
478       }
479       if(docDir && docDir[0])
480          ChangeCh(docDir, from, to);
481       if(ideFileDialogLocation && ideFileDialogLocation[0])
482          ChangeCh(ideFileDialogLocation, from, to);
483       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
484          ChangeCh(ideProjectFileDialogLocation, from, to);
485
486       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
487          ChangeCh(projectDefaultTargetDir, from, to);
488       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
489          ChangeCh(projectDefaultIntermediateObjDir, from, to);
490
491       if(compilerConfigsDir && compilerConfigsDir[0])
492          ChangeCh(compilerConfigsDir, from, to);
493    }
494
495    void ManagePortablePaths(char * location, bool makeAbsolute)
496    {
497       int c;
498       if(compilerConfigs && compilerConfigs.count)
499       {
500          for(config : compilerConfigs)
501          {
502             DirTypes t;
503             for(t = 0; t < DirTypes::enumSize; t++)
504             {
505                Array<String> dirs = null;
506                if(t == executables) dirs = config.executableDirs;
507                else if(t == includes) dirs = config.includeDirs;
508                else if(t == libraries) dirs = config.libraryDirs;
509                if(dirs && dirs.count)
510                {
511                   for(c = 0; c < dirs.count; c++)
512                   {
513                      if(dirs[c] && dirs[c][0])
514                         dirs[c] = UpdatePortablePath(dirs[c], location, makeAbsolute);
515                   }
516                }
517             }
518          }
519       }
520       if(recentFiles && recentFiles.count)
521       {
522          for(c = 0; c < recentFiles.count; c++)
523          {
524             if(recentFiles[c] && recentFiles[c][0])
525                recentFiles[c] = UpdatePortablePath(recentFiles[c], location, makeAbsolute);
526          }
527       }
528       if(recentProjects && recentProjects.count)
529       {
530          for(c = 0; c < recentProjects.count; c++)
531          {
532             if(recentProjects[c] && recentProjects[c][0])
533                recentProjects[c] = UpdatePortablePath(recentProjects[c], location, makeAbsolute);
534          }
535       }
536       if(docDir && docDir[0])
537          docDir = UpdatePortablePath(docDir, location, makeAbsolute);
538       if(ideFileDialogLocation && ideFileDialogLocation[0])
539          ideFileDialogLocation = UpdatePortablePath(ideFileDialogLocation, location, makeAbsolute);
540       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
541          ideProjectFileDialogLocation = UpdatePortablePath(ideProjectFileDialogLocation, location, makeAbsolute);
542
543       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
544          projectDefaultTargetDir = UpdatePortablePath(projectDefaultTargetDir, location, makeAbsolute);
545       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
546          projectDefaultIntermediateObjDir = UpdatePortablePath(projectDefaultIntermediateObjDir, location, makeAbsolute);
547
548       if(compilerConfigsDir && compilerConfigsDir[0])
549          compilerConfigsDir = UpdatePortablePath(compilerConfigsDir, location, makeAbsolute);
550    }
551
552    char * UpdatePortablePath(char * path, char * location, bool makeAbsolute)
553    {
554       char * output;
555       if(makeAbsolute)
556       {
557          char p[MAX_LOCATION];
558          strcpy(p, location);
559          PathCatSlash(p, path);
560          delete path;
561          output = CopyString(p);
562       }
563       else
564       {
565          PathRelationship rel = eString_PathRelated(path, location, null);
566          if(rel == subPath || rel == identical)
567          {
568             char p[MAX_LOCATION];
569             MakePathRelative(path, location, p);
570             if(!*p) strcpy(p, "./");
571             else ChangeCh(p, '\\', '/');
572             delete path;
573             output = CopyString(p);
574          }
575          else
576             output = path;
577       }
578       return output;
579    }
580
581    void AddRecentFile(char * fileName)
582    {
583       int c;
584       char * filePath = CopyString(fileName);
585       ChangeCh(filePath, '\\', '/');
586       for(c = 0; c<recentFiles.count; c++)
587       {
588          if(recentFiles[c] && !fstrcmp(recentFiles[c], filePath))
589          {
590             recentFiles.Delete((void *)&recentFiles[c]);
591             c--;
592          }
593       }
594       while(recentFiles.count >= MaxRecent)
595          recentFiles.Delete(recentFiles.GetLast());
596       recentFiles.Insert(null, filePath);
597       //UpdateRecentMenus(owner);
598    }
599
600    void AddRecentProject(char * projectName)
601    {
602       int c;
603       char * filePath = CopyString(projectName);
604       ChangeCh(filePath, '\\', '/');
605       for(c = 0; c<recentProjects.count; c++)
606       {
607          if(recentProjects[c] && !fstrcmp(recentProjects[c], filePath))
608          {
609             recentProjects.Delete((void *)&recentProjects[c]);
610             c--;
611          }
612       }
613       while(recentProjects.count >= MaxRecent)
614          recentProjects.Delete(recentProjects.GetLast());
615       recentProjects.Insert(null, filePath);
616       //UpdateRecentMenus(owner);
617    }
618 }
619
620 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
621 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
622 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
623 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
624 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
625 // TODO: i18n with Array
626 static Array<String> compilerTypeLongNames
627 { [
628    $"GNU Compiler Collection (GCC) / GNU Make",
629    $"Tiny C Compiler / GNU Make",
630    $"Portable C Compiler / GNU Make",
631    $"Microsoft Visual Studio 2005 (8.0) Compiler",
632    $"Microsoft Visual Studio 2008 (9.0) Compiler",
633    $"Microsoft Visual Studio 2010 (10.0) Compiler"
634 ] };
635 const CompilerType firstCompilerType = gcc;
636 const CompilerType lastCompilerType = vs10;
637 public enum CompilerType
638 {
639    gcc, tcc, pcc, vs8, vs9, vs10;
640
641    property bool isVC
642    {
643       get { return this == vs8 || this == vs9 || this == vs10; }
644    }
645
646    property char *
647    {
648       get { return OnGetString(null, null, null); }
649       set
650       {  
651          if(value)
652          {
653             Platform c;
654             for(c = firstCompilerType; c <= lastCompilerType; c++)
655                if(!strcmpi(value, compilerTypeNames[c]))
656                   return c;
657          }
658          return gcc;
659       }
660    };
661
662    property char * longName { get { return OnGetString(null, (void*)1, null); } };
663    property char * versionString { get { return OnGetString(null, (void*)2, null); } };
664    property char * yearString { get { return OnGetString(null, (void*)3, null); } };
665    property char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
666    property char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
667
668    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
669    {
670       if(this >= firstCompilerType && this <= lastCompilerType)
671       {
672          if(tempString)
673             strcpy(tempString, compilerTypeNames[this]);
674          if(fieldData == null)
675             return compilerTypeNames[this];
676          else if(fieldData == (void*)1)
677             return compilerTypeLongNames[this];
678          else if(fieldData == (void*)2)
679             return compilerTypeVersionString[this];
680          else if(fieldData == (void*)3)
681             return compilerTypeYearString[this];
682          else if(fieldData == (void*)4)
683             return compilerTypeProjectFileExtension[this];
684          else if(fieldData == (void*)5)
685             return compilerTypeSolutionFileVersionString[this];
686       }
687       return null;
688    }
689 };
690
691 class CompilerConfig
692 {
693    class_no_expansion;
694
695    numJobs = 1;
696 public:
697    property char * name
698    {
699       set
700       {
701          delete name;
702          if(value)
703             name = CopyString(value);
704       }
705       get { return name; }
706    }
707    bool readOnly;
708    CompilerType type;
709    Platform targetPlatform;
710    int numJobs;
711    property char * makeCommand
712    {
713       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
714       get { return makeCommand; }
715       isset { return makeCommand && makeCommand[0]; }
716    }
717    property char * ecpCommand
718    {
719       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
720       get { return ecpCommand; }
721       isset { return ecpCommand && ecpCommand[0]; }
722    }
723    property char * eccCommand
724    {
725       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
726       get { return eccCommand; }
727       isset { return eccCommand && eccCommand[0]; }
728    }
729    property char * ecsCommand
730    {
731       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
732       get { return ecsCommand; }
733       isset { return ecsCommand && ecsCommand[0]; }
734    }
735    property char * earCommand
736    {
737       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
738       get { return earCommand; }
739       isset { return earCommand && earCommand[0]; }
740    }
741    property char * cppCommand
742    {
743       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
744       get { return cppCommand; }
745       isset { return cppCommand && cppCommand[0]; }
746    }
747    property char * ccCommand
748    {
749       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
750       get { return ccCommand; }
751       isset { return ccCommand && ccCommand[0]; }
752    }
753    property char * cxxCommand
754    {
755       set { delete cxxCommand; if(value && value[0]) cxxCommand = CopyString(value); }
756       get { return cxxCommand; }
757       isset { return cxxCommand && cxxCommand[0]; }
758    }
759    property char * execPrefixCommand // <-- old name for json ide settings file compatibility
760    {
761       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
762       get { return executableLauncher; }
763       isset { return executableLauncher && executableLauncher[0]; }
764    }
765    bool ccacheEnabled;
766    bool distccEnabled;
767    // deprecated
768    property bool supportsBitDepth { set { } get { } isset { return false; } }
769
770    property char * distccHosts
771    {
772       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
773       get { return distccHosts; }
774       isset { return distccHosts && distccHosts[0]; }
775    }
776    property char * gccPrefix // <-- old name for json ide settings file compatibility
777    {
778       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
779       get { return gnuToolchainPrefix; }
780       isset { return gnuToolchainPrefix && gnuToolchainPrefix[0]; }
781    }
782    property char * sysroot
783    {
784       set { delete sysroot; if(value && value[0]) sysroot = CopyString(value); }
785       get { return sysroot; }
786       isset { return sysroot && sysroot[0]; }
787    }
788    property Array<String> includeDirs
789    {
790       set
791       {
792          includeDirs.Free();
793          if(value)
794          {
795             delete includeDirs;
796             includeDirs = value;
797          }
798       }
799       get { return includeDirs; }
800       isset { return includeDirs.count != 0; }
801    }
802    property Array<String> libraryDirs
803    {
804       set
805       {
806          libraryDirs.Free();
807          if(value)
808          {
809             delete libraryDirs;
810             libraryDirs = value;
811          }
812       }
813       get { return libraryDirs; }
814       isset { return libraryDirs.count != 0; }
815    }
816    property Array<String> executableDirs
817    {
818       set
819       {
820          executableDirs.Free();
821          if(value)
822          {
823             delete executableDirs;
824             executableDirs = value;
825          }
826       }
827       get { return executableDirs; }
828       isset { return executableDirs.count != 0; }
829    }
830    property Array<NamedString> environmentVars
831    {
832       set
833       {
834          environmentVars.Free();
835          if(value)
836          {
837             delete environmentVars;
838             environmentVars = value;
839          }
840       }
841       get { return environmentVars; }
842       isset { return environmentVars.count != 0; }
843    }
844    property Array<String> prepDirectives
845    {
846       set
847       {
848          prepDirectives.Free();
849          if(value)
850          {
851             delete prepDirectives;
852             prepDirectives = value;
853          }
854       }
855       get { return prepDirectives; }
856       isset { return prepDirectives.count != 0; }
857    }
858    property Array<String> excludeLibs
859    {
860       set
861       {
862          excludeLibs.Free();
863          if(value)
864          {
865             delete excludeLibs;
866             excludeLibs = value;
867          }
868       }
869       get { return excludeLibs; }
870       isset { return excludeLibs.count != 0; }
871    }
872    property Array<String> linkerFlags
873    {
874       set
875       {
876          linkerFlags.Free();
877          if(value)
878          {
879             delete linkerFlags;
880             linkerFlags = value;
881          }
882       }
883       get { return linkerFlags; }
884       isset { return linkerFlags.count != 0; }
885    }
886 private:
887    Array<String> includeDirs { };
888    Array<String> libraryDirs { };
889    Array<String> executableDirs { };
890    // TODO: Can JSON parse and serialize maps?
891    //EnvironmentVariables { };
892    Array<NamedString> environmentVars { };
893    Array<String> prepDirectives { };
894    Array<String> excludeLibs { };
895    Array<String> linkerFlags { };
896    char * name;
897    char * makeCommand;
898    char * ecpCommand;
899    char * eccCommand;
900    char * ecsCommand;
901    char * earCommand;
902    char * cppCommand;
903    char * ccCommand;
904    char * cxxCommand;
905    char * executableLauncher;
906    char * distccHosts;
907    char * gnuToolchainPrefix;
908    char * sysroot;
909    /*union
910    {
911       struct { Array<String> includes, libraries, executables; };
912       Array<String> dirs[DirTypes];
913    }*/
914
915    ~CompilerConfig()
916    {
917       delete name;
918       delete ecpCommand;
919       delete eccCommand;
920       delete ecsCommand;
921       delete earCommand;
922       delete cppCommand;
923       delete ccCommand;
924       delete cxxCommand;
925       delete makeCommand;
926       delete executableLauncher;
927       delete distccHosts;
928       delete gnuToolchainPrefix;
929       delete sysroot;
930       if(environmentVars) environmentVars.Free();
931       if(includeDirs) { includeDirs.Free(); }
932       if(libraryDirs) { libraryDirs.Free(); }
933       if(executableDirs) { executableDirs.Free(); }
934       if(prepDirectives) { prepDirectives.Free(); }
935       if(excludeLibs) { excludeLibs.Free(); }
936       if(linkerFlags) { linkerFlags.Free(); }
937    }
938    CompilerConfig Copy()
939    {
940       CompilerConfig copy
941       {
942          name,
943          readOnly,
944          type,
945          targetPlatform,
946          numJobs,
947          makeCommand,
948          ecpCommand,
949          eccCommand,
950          ecsCommand,
951          earCommand,
952          cppCommand,
953          ccCommand,
954          cxxCommand,
955          executableLauncher,
956          ccacheEnabled,
957          distccEnabled,
958          false,
959          distccHosts,
960          gnuToolchainPrefix,
961          sysroot
962       };
963       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
964       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
965       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
966       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
967       for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
968       for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
969       for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
970
971       incref copy;
972       return copy;
973    }
974 }