1 /*
2  *******************************************************************************************
3  * Dgame (a D game framework) - Copyright (c) Randy Schütt
4  *
5  * This software is provided 'as-is', without any express or implied warranty.
6  * In no event will the authors be held liable for any damages arising from
7  * the use of this software.
8  *
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  *
13  * 1. The origin of this software must not be misrepresented; you must not claim
14  *    that you wrote the original software. If you use this software in a product,
15  *    an acknowledgment in the product documentation would be appreciated but is
16  *    not required.
17  *
18  * 2. Altered source versions must be plainly marked as such, and must not be
19  *    misrepresented as being the original software.
20  *
21  * 3. This notice may not be removed or altered from any source distribution.
22  *******************************************************************************************
23  */
24 module build;
25 
26 import std.stdio;
27 import std.path : dirName, buildNormalizedPath, absolutePath;
28 import std.process : system, ErrnoException;
29 import std.file : mkdir, exists, read, dirEntries, SpanMode;
30 import std.array : endsWith;
31 import std..string : format, toUpper, chop, splitLines, strip;
32 import std.exception : enforce;
33 
34 version(DigitalMars) {
35 	enum DMD = true;
36 	enum GDC = false;
37 	enum LDC = false;
38 } else version(GNU) {
39 	enum DMD = false;
40 	enum GDC = true;
41 	enum LDC = false;
42 } else version(LDC) {
43 	enum DMD = false;
44 	enum GDC = false;
45 	enum LDC = true;
46 }
47 
48 version(Windows) {
49 	enum Windows = true;
50 	enum Posix = false;
51 } else version(Posix) {
52 	enum Windows = false;
53 	enum Posix = true;
54 } else
55 	static assert(false, "Unknown operating system.");
56 
57 static immutable string Project = "Dgame";
58 static immutable string SrcDgame = "../";
59 static immutable string LibDir  = SrcDgame ~ "lib";
60 
61 // Compiler configuration
62 version(DigitalMars) {
63 	pragma(msg, "Using the Digital Mars DMD compiler.");
64 
65 	debug {
66 		static immutable string Release = "-debug";
67 	} else {
68 		static immutable string Release = "-release -inline";
69 	}
70 
71 	static immutable string CompilerOptions = "-lib -O " ~ Release ~ " -wi";
72 
73 	string buildCompileString(string files, string libName) {
74 		return format("dmd %s -of%s/%s %s -I%s -I../../", CompilerOptions, outdir, libName, files, derelictImportDir);
75 	}
76 } else version(GNU) {
77 	pragma(msg, "Using the GNU GDC compiler.");
78 
79 	static immutable string CompilerOptions = "-c -s -O3 -Wall";
80 
81 	string buildCompileString(string files, string libName) {
82 		return format("gdc %s -o %s/%s %s -I%s -I../../", CompilerOptions, outdir, libName, files, derelictImportDir);
83 	}
84 } else version(LDC) {
85 	pragma(msg, "Using the LDC compiler.");
86 
87 	static immutable string CompilerOptions = "-lib -O -release -enable-inlining -w -wi";
88 
89 	string buildCompileString(string files, string libName) {
90 		return format("ldc2 %s -of%s/%s %s -I%s", CompilerOptions, outdir, libName, files, derelictImportDir);
91 	}
92 } else
93 	static assert(false, "Unknown compiler.");
94 
95 static if (Windows && DMD) {
96 	static immutable string Prefix = "";
97 	static immutable string Extension = ".lib";
98 } else static if (Posix || GDC || LDC) {
99 	static immutable string Prefix = "lib";
100 	static immutable string Extension = ".a";
101 } else
102 	static assert(false, "Unknown operating system and compiler.");
103 
104 struct Package {
105 	const string name;
106 	string path;
107 }
108 
109 final abstract class Pack {
110 public:
111 	static const Internal = Package("Internal",	SrcDgame ~ "Internal/");
112 	static const Audio	= Package("Audio",	SrcDgame ~ "Audio/");
113 	static const Graphics = Package("Graphics", SrcDgame ~ "Graphics/");
114 	static const Math 	= Package("Math",	SrcDgame ~ "Math/");
115 	static const System = Package("System",	SrcDgame ~ "System/");
116 	static const Window = Package("Window", SrcDgame ~ "Window/");
117 }
118 
119 // Map package names to source paths.
120 Package[string] pathMap;
121 
122 string buildPath;
123 string derelictImportDir;
124 
125 debug {
126 	string outdir = LibDir ~ "/Debug";
127 } else {
128 	string outdir = LibDir ~ "/Release";
129 }
130 
131 static this() {
132 	if (!LibDir.exists())
133 		mkdir(LibDir);
134 
135 	if (!outdir.exists())
136 		mkdir(outdir);
137 
138 	// Initializes the source path map.
139 	pathMap = [
140 		Pack.Internal.name.toUpper() : Pack.Internal,
141 		Pack.Audio.name.toUpper() : Pack.Audio,
142 		Pack.Graphics.name.toUpper() : Pack.Graphics,
143 		Pack.Math.name.toUpper() : Pack.Math,
144 		Pack.System.name.toUpper() : Pack.System,
145 		Pack.Window.name.toUpper() : Pack.Window,
146 	];
147 }
148 
149 enum DerelictDirname = "derelict";
150 
151 void main(string[] args) {
152 	// Determine the path to this executable so that imports and source files can be found
153 	// no matter what the working directory.
154 	buildPath = args[0].dirName();
155 
156 	string derelictPath = args[0].absolutePath().dirName() ~ "/../../" ~ DerelictDirname;
157 	derelictPath = derelictPath.buildNormalizedPath();
158 
159 	writeln("Assume '", derelictPath, "' as derelict path.");
160 	writeln("Verify...\n");
161 
162 	if (.exists(derelictPath))
163 		derelictImportDir = derelictPath;
164 	else {
165 		writeln("Assume, that the derelict path is in 'external.txt'.");
166 		writeln("Verify...\n");
167 
168 		if (.exists(buildPath ~ "/external.txt")) {
169             foreach(derelictLine; (cast(string) .read(buildPath ~ "/external.txt")).splitLines()) {
170                 derelictLine = derelictLine.strip();
171                 if(derelictLine.length > 0 && derelictLine[0] != '#' && .exists(derelictLine)) {
172                     derelictImportDir = derelictLine;
173                     break;
174                 }
175             }
176         }
177 
178 		if (derelictImportDir.length == 0 || !.exists(derelictImportDir)) {
179 			do {
180 				writeln("Derelict import path not found.");
181 				writeln("You can enter the full path in 'external.txt'.");
182 				writeln("But for now, please enter the full path here or press q for quit:");
183 
184 				derelictImportDir = readln().chop();
185 
186 				if (derelictImportDir[0] == 'q')
187 					return;
188 
189 				if (.exists(derelictImportDir)) {
190 					if (derelictImportDir.endsWith(DerelictDirname))
191 						break;
192 
193 					derelictImportDir ~= DerelictDirname;
194 					if (.exists(derelictImportDir))
195 						break;
196 				}
197 			} while (true);
198 		}
199 	}
200 
201 	if (derelictImportDir.endsWith(DerelictDirname))
202 		derelictImportDir = derelictImportDir.dirName();
203 
204 	if (buildPath != "./") {
205 		outdir = buildNormalizedPath(buildPath, outdir);
206 
207 		// fix up the package paths
208 		foreach (ref Package pack; pathMap) {
209 			pack.path = buildNormalizedPath(buildPath, pack.path);
210 		}
211 	}
212 
213 	if (args.length == 1)
214 		buildAll();
215 	else
216 		buildSome(args[1 .. $]);
217 }
218 
219 // Build all of the Derelict libraries.
220 void buildAll() {
221 	writeln("Building all packages.");
222 	try {
223 		foreach (ref Package pack; pathMap) {
224 			buildPackage(pack);
225 		}
226 
227 		writeln("\nAll builds complete\n");
228 	}
229 	// Eat any ErrnoException. The compiler will print the right thing on a failed build, no need
230 	// to clutter the output with exception info.
231 	catch (ErrnoException e) {
232 		writeln("\nBuild Failed!\n");
233 	}
234 }
235 
236 // Build only the packages specified on the command line.
237 void buildSome(string[] args) {
238 	try {
239 		// If any of the args matches a key in the pathMap, build
240 		// that package.
241 		foreach (s; args) {
242 			auto key = s.toUpper();
243 
244 			Package* p = key in pathMap;
245 			if (!p)
246 				writefln("Unknown package '%s'", s);
247 			else
248 				buildPackage(*p);
249 		}
250 
251 		writeln("\nSelected builds complete\n");
252 	} catch (ErrnoException e) {
253 		writeln("\nBuild Failed!\n");
254 	}
255 }
256 
257 void buildPackage(ref const Package pack) {
258 	writeln();
259 	writefln("Building %s/%s", Project, pack.name);
260 	writeln();
261 
262 	// Build up a string of all .d files in the package directory.
263 	string joined;
264 	foreach (string s; dirEntries(pack.path, SpanMode.breadth)) {
265 		if (s.endsWith(".d")) {
266 			writeln(s);
267 			joined ~= " " ~ s;
268 		}
269 	}
270 
271 	writeln();
272 
273 	string libName = format("%s%s%s%s", Prefix, Project, pack.name, Extension);
274 	string arg = buildCompileString(joined, libName);
275 	writeln(arg);
276 
277 	(system(arg) == 0).enforce(new ErrnoException("Build failure"));
278 
279 	writeln("Build succeeded.");
280 }