1 module gccbuild.config;
2 
3 import gccbuild, scriptlike, painlessjson, std.json;
4 
5 BuildMode mode = BuildMode.all;
6 Path buildConfig, sourceConfig, logFilePath;
7 string buildTriplet = "x86_64-unknown-linux-gnu";
8 Path mirrorFile = "mirrors.json";
9 bool verifyCachedSources = true;
10 bool forceDownload = false;
11 bool forceExtract = false;
12 size_t numCPUs = 1;
13 Path cacheDir;
14 string[string] buildVariables;
15 string[string] cmdVariables;
16 bool skipStripLibraries = false;
17 bool skipStripBinaries = false;
18 bool keepBuildFiles = false;
19 string hostStripCMD;
20 string targetStripCMD;
21 string[] patchDirsCMD;
22 string gdcSourcePath;
23 string targetGCCCMD;
24 
25 @property string targetGCC()
26 {
27     if (targetGCCCMD.empty)
28         return build.target ~ "-gcc";
29     else
30         return targetGCCCMD;
31 }
32 
33 @property string hostStrip()
34 {
35     if (!hostStripCMD.empty)
36         return hostStripCMD;
37     else if (build.type == ToolchainType.native)
38         return "strip";
39     else
40         return build.host ~ "-strip";
41 }
42 
43 @property string targetStrip()
44 {
45     if (!targetStripCMD.empty)
46         return hostStripCMD;
47     else if (build.type == ToolchainType.native)
48         return "strip";
49     else
50         return build.target ~ "-strip";
51 }
52 
53 Path[] patchDirectories;
54 
55 struct CMDBuildOverwrites
56 {
57     string host, target;
58     string gccFile, gccSuburl, gccMD5;
59     Nullable!ToolchainType type;
60 }
61 
62 CMDBuildOverwrites cmdOverwrites;
63 
64 @property Path sysrootDirStage1()
65 {
66     return toolchainDirStage1 ~ "sysroot";
67 }
68 
69 @property Path binDirStage1()
70 {
71     return toolchainDirStage1 ~ "bin";
72 }
73 
74 @property Path toolchainDirStage1()
75 {
76     return installDir ~ "stage1";
77 }
78 
79 @property Path sysrootDirWithPrefix()
80 {
81     return sysrootDir ~ build.relativeSysrootPrefix;
82 }
83 
84 @property Path sysrootDir()
85 {
86     return toolchainDir ~ "sysroot";
87 }
88 
89 @property Path binDir()
90 {
91     return toolchainDir ~ "bin";
92 }
93 
94 @property Path toolchainDir()
95 {
96     return installDir ~ build.target;
97 }
98 
99 @property Path hostlibDir()
100 {
101     return installDir ~ "host";
102 }
103 
104 @property Path mainPatchDir()
105 {
106     return cacheDir ~ "patches";
107 }
108 
109 @property Path installDir()
110 {
111     return cacheDir ~ "install";
112 }
113 
114 @property Path extractDir()
115 {
116     return cacheDir ~ "src";
117 }
118 
119 @property Path buildDir()
120 {
121     return cacheDir ~ "build";
122 }
123 
124 @property Path downloadDir()
125 {
126     return cacheDir ~ "download";
127 }
128 
129 MainConfig build;
130 string[][string] mirrors;
131 
132 void setDefaultPaths()
133 {
134     cacheDir = Path("cache").absolutePath;
135     logFilePath = Path("build.log").absolutePath;
136 }
137 
138 void loadMirrors()
139 {
140     startSectionLog("Loading mirror information");
141     try
142         mirrors = fromJSON!(typeof(mirrors))(mirrorFile.readText().parseJSON());
143     catch (Exception e)
144         failc("Couldn't load mirror file ", mirrorFile, ": ", e);
145     endSectionLog();
146 }
147 
148 void loadSourceConfig(Path file)
149 {
150     startSectionLog("Loading build sources configuration");
151     try
152         build = fromJSON!MainConfig(file.readText().parseJSON());
153     catch (Exception e)
154         failc("Couldn't load build sources configuration ", file, ": ", e);
155     endSectionLog();
156 }
157 
158 void loadBuildConfig(Path file)
159 {
160     startSectionLog("Loading build configuration");
161     BuildConfig commands;
162     try
163         commands = fromJSON!BuildConfig(file.readText().parseJSON());
164     catch (Exception e)
165         failc("Couldn't load build configuration ", file, ": ", e);
166 
167     build.include(commands);
168     build.include(cmdOverwrites);
169 
170     endSectionLog();
171 }
172 
173 void setupBuildVariables()
174 {
175     startSectionLog("Setting build variables");
176 
177     buildVariables["NUM_CPU"] = to!string(numCPUs);
178     buildVariables["BUILD"] = buildTriplet;
179     buildVariables["HOST"] = build.host;
180     buildVariables["TARGET"] = build.target;
181     buildVariables["ARCH"] = build.arch;
182     buildVariables["DIR_GMP_INSTALL"] = (hostlibDir ~ build.gmp.baseDirName).toString();
183     buildVariables["DIR_MPFR_INSTALL"] = (hostlibDir ~ build.mpfr.baseDirName).toString();
184     buildVariables["DIR_MPC_INSTALL"] = (hostlibDir ~ build.mpc.baseDirName).toString();
185     buildVariables["DIR_TOOLCHAIN"] = toolchainDir.toString();
186     buildVariables["DIR_SYSROOT"] = sysrootDir.toString();
187     buildVariables["DIR_SYSROOT_PREFIX"] = build.sysrootPrefix;
188     buildVariables["DIR_SYSROOT_WITH_PREFIX"] = sysrootDirWithPrefix.toString();
189     buildVariables["DIR_TOOLCHAIN_STAGE1"] = toolchainDirStage1.toString();
190     buildVariables["DIR_SYSROOT_STAGE1"] = sysrootDirStage1.toString();
191     buildVariables["DIR_SYSROOT_STAGE1_WITH_PREFIX"] = (
192         sysrootDirStage1 ~ build.relativeSysrootPrefix).toString();
193     buildVariables["TARGET_GCC"] = build.target ~ "-gcc";
194 
195     // overwrite from build.json
196     static void addConstants(string[string] cst)
197     {
198         foreach (key, val; cst)
199         {
200             buildVariables[key] = val;
201         }
202     }
203 
204     addConstants(build.constants);
205     final switch (build.type)
206     {
207     case ToolchainType.cross:
208         addConstants(build.constantsCross);
209         break;
210     case ToolchainType.native:
211         addConstants(build.constantsNative);
212         break;
213     case ToolchainType.cross_native:
214         addConstants(build.constantsCrossNative);
215         break;
216     case ToolchainType.canadian:
217         addConstants(build.constantsCanadian);
218         break;
219     }
220 
221     // overwrites from cmd
222     foreach (key, val; cmdVariables)
223     {
224         buildVariables[key] = val;
225     }
226 
227     foreach (key, val; buildVariables)
228     {
229         yap(key, "=", val);
230     }
231     endSectionLog();
232 
233     // Setup patch directories
234     startSectionLog("Setting patch directories");
235 
236     static void addConfPatchDirs(string[] dirs)
237     {
238         foreach (dir; dirs)
239         {
240             // Make sure relative Paths are relative to the build config specifying them
241             auto path = Path(dir);
242             if (!path.isAbsolute)
243                 path = path.absolutePath(buildConfig.dirName);
244             patchDirectories ~= path;
245         }
246     }
247 
248     patchDirectories ~= mainPatchDir;
249     addConfPatchDirs(build.localPatchDirs);
250     final switch (build.type)
251     {
252     case ToolchainType.cross:
253         addConfPatchDirs(build.localPatchDirsCross);
254         break;
255     case ToolchainType.native:
256         addConfPatchDirs(build.localPatchDirsNative);
257         break;
258     case ToolchainType.cross_native:
259         addConfPatchDirs(build.localPatchDirsCrossNative);
260         break;
261     case ToolchainType.canadian:
262         addConfPatchDirs(build.localPatchDirsCanadian);
263         break;
264     }
265     foreach (dir; patchDirsCMD)
266         patchDirectories ~= Path(dir);
267 
268     endSectionLog();
269 }
270 
271 void dumpConfiguration()
272 {
273     startSection("Dumping configuration");
274     writeBulletPoint(mixin(interp!"Type: ${build.type}     Target type: ${build.targetType}"));
275     writeBulletPoint(mixin(interp!"Build: ${buildTriplet}"));
276     writeBulletPoint(mixin(interp!"Host: ${build.host}"));
277     writeBulletPoint(mixin(interp!"Target: ${build.target}"));
278     if (build.targetType == HostType.linux)
279         writeBulletPoint(mixin(interp!"Kernel ARCH: ${build.arch}"));
280     endSection();
281 }
282 
283 auto namedFields(T, A...)(ref T instance)
284 {
285     static string generateMixin(string[] fields)
286     {
287         string result = "return only(";
288         foreach (i, entry; fields)
289         {
290             if (i != 0)
291                 result ~= ", ";
292             result ~= "tuple!(\"name\", \"value\")(\"" ~ entry ~ "\", cast(Component)instance." ~ entry ~ ")";
293         }
294         result ~= ");";
295         return result;
296     }
297 
298     mixin(generateMixin([A]));
299 }
300 
301 struct BuildConfig
302 {
303     string host, target, arch;
304     string[string] constants;
305     @SerializedName("constants_native") string[string] constantsNative;
306     @SerializedName("constants_cross") string[string] constantsCross;
307     @SerializedName("constants_cross_native") string[string] constantsCrossNative;
308     @SerializedName("constants_canadian") string[string] constantsCanadian;
309 
310     @SerializedName("sysroot_prefix") string sysrootPrefix = "/";
311     @SerializedName("patch_dirs") string[] localPatchDirs;
312     @SerializedName("patch_dirs_native") string[] localPatchDirsNative;
313     @SerializedName("patch_dirs_cross") string[] localPatchDirsCross;
314     @SerializedName("patch_dirs_cross_native") string[] localPatchDirsCrossNative;
315     @SerializedName("patch_dirs_canadian") string[] localPatchDirsCanadian;
316 
317     string type = "";
318 
319     BuildCommand gmp, mpfr, mpc, linux, binutils, w32api, gcc;
320     GlibcBuildCommand glibc;
321     CleanupCommand cleanup;
322     @SerializedName("cleanup_cross") CleanupCommand cleanupCross;
323     @SerializedName("cleanup_cross_native") CleanupCommand cleanupCrossNative;
324     @SerializedName("cleanup_native") CleanupCommand cleanupNative;
325     @SerializedName("cleanup_canadian") CleanupCommand cleanupCanadian;
326     @SerializedName("gcc_stage1") BuildCommand gccStage1;
327 }
328 
329 struct MainConfig
330 {
331     @SerializeIgnore string host;
332     @SerializeIgnore string target;
333     @SerializeIgnore string arch;
334     @SerializeIgnore string[string] constants;
335     @SerializeIgnore string[string] constantsNative;
336     @SerializeIgnore string[string] constantsCross;
337     @SerializeIgnore string[string] constantsCrossNative;
338     @SerializeIgnore string[string] constantsCanadian;
339 
340     @SerializeIgnore MultilibEntry[] multilibs;
341     @SerializeIgnore string sysrootPrefix;
342     @property string relativeSysrootPrefix()
343     {
344         return sysrootPrefix.relativePath("/");
345     }
346 
347     @SerializeIgnore ToolchainType type;
348     @SerializeIgnore string[] localPatchDirs;
349     @SerializeIgnore string[] localPatchDirsNative;
350     @SerializeIgnore string[] localPatchDirsCross;
351     @SerializeIgnore string[] localPatchDirsCrossNative;
352     @SerializeIgnore string[] localPatchDirsCanadian;
353 
354     Component mpc, mpfr, gmp, binutils, linux, w32api;
355     GCCComponent gcc;
356     GlibcComponent glibc;
357     // This is a special component: only a addon for glibc, don't build individually
358     @SerializedName("glibc_ports") Component glibcPorts;
359 
360     @SerializeIgnore CleanupCommand cleanup;
361     @SerializeIgnore CleanupCommand cleanupCross;
362     @SerializeIgnore CleanupCommand cleanupCrossNative;
363     @SerializeIgnore CleanupCommand cleanupNative;
364     @SerializeIgnore CleanupCommand cleanupCanadian;
365     @property CleanupCommand variantCleanup()
366     {
367         final switch (type)
368         {
369         case ToolchainType.native:
370             return cleanupNative;
371         case ToolchainType.canadian:
372             return cleanupCanadian;
373         case ToolchainType.cross:
374             return cleanupCross;
375         case ToolchainType.cross_native:
376             return cleanupCrossNative;
377         }
378     }
379 
380     @property HostType targetType()
381     {
382         if (target.toLower().canFind("mingw"))
383             return HostType.mingw;
384         else
385             return HostType.linux;
386     }
387 
388     @property componentRange()
389     {
390         return this.namedFields!(MainConfig, "mpc", "mpfr", "gmp", "glibc",
391             "binutils", "linux", "w32api", "gcc");
392     }
393 
394     @property configuredComponents()
395     {
396         return this.componentRange.filter!(a => a.value.isInConfig);
397     }
398 
399     void include(BuildConfig config)
400     {
401         host = config.host;
402         target = config.target;
403         arch = config.arch;
404         constants = config.constants;
405         constantsNative = config.constantsNative;
406         constantsCross = config.constantsCross;
407         constantsCrossNative = config.constantsCrossNative;
408         constantsCanadian = config.constantsCanadian;
409         sysrootPrefix = config.sysrootPrefix;
410         localPatchDirs = config.localPatchDirs;
411         localPatchDirsNative = config.localPatchDirsNative;
412         localPatchDirsCross = config.localPatchDirsCross;
413         localPatchDirsCrossNative = config.localPatchDirsCrossNative;
414         localPatchDirsCanadian = config.localPatchDirsCanadian;
415         cleanup = config.cleanup;
416         cleanupNative = config.cleanupNative;
417         cleanupCanadian = config.cleanupCanadian;
418         cleanupCross = config.cleanupCross;
419         cleanupCrossNative = config.cleanupCrossNative;
420 
421         void trySet(Component comp, BuildCommand com)
422         {
423             if (comp)
424                 comp._mainBuildCommand = com;
425         }
426 
427         trySet(mpc, config.mpc);
428         trySet(mpfr, config.mpfr);
429         trySet(gmp, config.gmp);
430         trySet(glibc, config.glibc);
431         trySet(binutils, config.binutils);
432         trySet(linux, config.linux);
433         trySet(w32api, config.w32api);
434         if (gcc)
435         {
436             gcc._mainBuildCommand = config.gcc;
437             gcc._stage1BuildCommand = config.gccStage1;
438         }
439     }
440 
441     void include(CMDBuildOverwrites overwrite)
442     {
443         void overwriteIfSet(ref string oldVal, string newVal)
444         {
445             if (!newVal.empty)
446                 oldVal = newVal;
447         }
448 
449         overwriteIfSet(host, cmdOverwrites.host);
450         overwriteIfSet(target, cmdOverwrites.target);
451         overwriteIfSet(gcc.file, cmdOverwrites.gccFile);
452         overwriteIfSet(gcc.suburl, cmdOverwrites.gccSuburl);
453         overwriteIfSet(gcc.md5, cmdOverwrites.gccMD5);
454 
455         if (build.target == build.host)
456         {
457             if (build.host == buildTriplet)
458                 build.type = ToolchainType.native;
459             else
460                 build.type = ToolchainType.cross_native;
461         }
462         else
463         {
464             if (build.host == buildTriplet)
465                 build.type = ToolchainType.cross;
466             else
467                 build.type = ToolchainType.canadian;
468         }
469 
470         if (!overwrite.type.isNull)
471             type = overwrite.type;
472     }
473 }
474 
475 class Component
476 {
477 private:
478     BuildCommand _mainBuildCommand;
479 
480 public:
481 
482     string file, url, md5;
483     // Total url is url | mirror ~ suburl | mirror ~ filename
484     string suburl;
485 
486     @property BuildCommand mainBuildCommand()
487     {
488         return _mainBuildCommand;
489     }
490 
491     @SerializeIgnore bool wasExtracted = false;
492 
493     @property Path localFile()
494     {
495         return downloadDir ~this.file;
496     }
497 
498     @property Path baseDirName()
499     {
500         return Path(file.stripExt.stripExt);
501     }
502 
503     Path getSourceFile(Path relFile)
504     {
505         return sourceFolder ~ relFile;
506     }
507 
508     @property Path configureFile()
509     {
510         return getSourceFile(Path("configure"));
511     }
512 
513     @property Path sourceFolder()
514     {
515         return extractDir ~ baseDirName;
516     }
517 
518     @property Path buildFolder()
519     {
520         return buildDir ~ baseDirName;
521     }
522 }
523 
524 // Whether this component is specified in config file
525 @property bool isInConfig(Component c)
526 {
527     return c !is null && c.mainBuildCommand !is null;
528 }
529 
530 @property bool hasBuildCommands(Component c)
531 {
532     return c.isInConfig && c.mainBuildCommand.matchesBuildType;
533 }
534 
535 class GlibcComponent : Component
536 {
537 public:
538     override @property GlibcBuildCommand mainBuildCommand()
539     {
540         return cast(GlibcBuildCommand) _mainBuildCommand;
541     }
542 }
543 
544 class GCCComponent : Component
545 {
546 private:
547     BuildCommand _stage1BuildCommand;
548 
549 public:
550     @property BuildCommand stage1BuildCommand()
551     {
552         return _stage1BuildCommand;
553     }
554 }
555 
556 enum ToolchainType
557 {
558     native,
559     cross,
560     cross_native,
561     canadian
562 }
563 
564 enum HostType
565 {
566     mingw,
567     linux
568 }
569 
570 enum BuildMode
571 {
572     all,
573     download,
574     extract,
575     patch
576 }
577 
578 class BuildCommand
579 {
580     string[] commands;
581     string[] variants;
582 }
583 
584 @property bool matchesBuildType(BuildCommand cmd)
585 {
586     if (!cmd)
587         return false;
588 
589     if (cmd.variants.empty)
590         return true;
591 
592     foreach (variant; cmd.variants)
593     {
594         if (variant == to!string(build.type))
595             return true;
596     }
597     return false;
598 }
599 
600 class GlibcBuildCommand : BuildCommand
601 {
602     @SerializedName("multi_commands") string[][] multiCommands;
603 }
604 
605 class CleanupCommand : BuildCommand
606 {
607     @SerializedName("strip_target") string[] stripTarget;
608     @SerializedName("strip_host") string[] stripHost;
609     string[] remove;
610 }
611 
612 struct MultilibEntry
613 {
614     string gccFolder;
615     string args;
616     string osFolder;
617 
618     @property bool isDefaultLib()
619     {
620         return gccFolder == ".";
621     }
622 }