1 /**
2 Copyright: Copyright (c) 2015-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module packager;
7 
8 import std.file;
9 import std.string;
10 import std.digest.crc;
11 import std.stdio;
12 import std.zip;
13 import std.path;
14 import std.process;
15 import std.datetime;
16 
17 enum ROOT_PATH = "../..";
18 enum TEST_DIR_NAME = "test";
19 string testDir;
20 
21 version(Windows)
22 	enum bool is_Windows = true;
23 else
24 	enum bool is_Windows = false;
25 
26 void makePackage(ReleasePackage* pack)
27 {
28 	string archStr = archToString[pack.arch];
29 	pack.addFiles("builds/default", "*.exe");
30 	pack.addFiles("res", "*.ply");
31 	pack.addFiles("config", "*.sdl");
32 	pack.addFile("config/servers.txt");
33 	pack.addFiles("lib/"~archStr, "*.dll");
34 	pack.addFile("saves/test"~archStr~".db");
35 	pack.addFile("README.md");
36 	pack.addFile("CHANGELOG.md");
37 	pack.addFile("LICENSE.md");
38 	pack.addFile("pluginpacks/default.txt");
39 	pack.addFile("launcher.exe");
40 
41 	pack.addFile("tools/minecraft_import/minecraft_import.exe");
42 	pack.addFile("tools/minecraft_import/readme.md");
43 }
44 
45 enum Compiler
46 {
47 	dmd,
48 	ldc,
49 	gdc
50 }
51 string[] compilerExeNames = ["dmd", "ldc2", "gdc"];
52 
53 string semver = "0.8.0";
54 Compiler compiler = Compiler.ldc;
55 string buildType = "release-debug";
56 
57 void main(string[] args)
58 {
59 	testDir = buildNormalizedPath(absolutePath(buildPath(ROOT_PATH, TEST_DIR_NAME)));
60 	if (exists(testDir) && isDir(testDir))
61 	{
62 		writefln("Deleting test dir %s", testDir);
63 		rmdirRecurse(testDir);
64 	}
65 
66 	StopWatch sw;
67 	sw.start();
68 	doWork();
69 	sw.stop();
70 	writefln("Finished in %.1fs", sw.peek().to!("seconds", real));
71 }
72 
73 void doWork()
74 {
75 	completeBuild(Arch.x32, semver, compiler, buildType);
76 	writeln;
77 	completeBuild(Arch.x64, semver, compiler, buildType);
78 }
79 
80 void completeBuild(Arch arch, string semver, Compiler compiler, string buildType)
81 {
82 	buildApp(arch, semver, compiler, buildType, "tools/minecraft_import");
83 
84 	string launcher_arch;
85 	if (compiler == Compiler.dmd && is_Windows)
86 		launcher_arch = arch == Arch.x32 ? "x86_mscoff" : "x86_64";
87 	else
88 		launcher_arch = arch == Arch.x32 ? "x86" : "x86_64";
89 
90 	string voxelman_arch = arch == Arch.x32 ? "32" : "64";
91 
92 	string dubCom() {
93 		return format(`dub run --root="tools/launcher" -q --nodeps --compiler=ldc2 --arch=%s --build=debug -- --arch=%s --compiler=%s --build=%s`,
94 			launcher_arch, voxelman_arch, compiler, buildType);
95 	}
96 
97 	string com = dubCom();
98 	writefln("Executing '%s'", com); stdout.flush();
99 
100 	auto dub = executeShell(com, null, Config.none, size_t.max, ROOT_PATH);
101 
102 	dub.output.write;
103 	if (dub.status != 0) {
104 		writeln("Failed to run dub or launcher");
105 		return;
106 	}
107 
108 	writefln("Packing %sbit", voxelman_arch); stdout.flush();
109 	pack(semver, arch, Platform.windows);
110 }
111 
112 void buildApp(Arch arch, string semver, Compiler compiler, string buildType, string root = "./")
113 {
114 	string app_arch;
115 	if (compiler == Compiler.dmd && is_Windows)
116 		app_arch = arch == Arch.x32 ? "x86_mscoff" : "x86_64";
117 	else
118 		app_arch = arch == Arch.x32 ? "x86" : "x86_64";
119 
120 	string dubCom() {
121 		return format(`dub build --root="%s" -q --nodeps --compiler=%s --arch=%s --build=%s`,
122 			root, compilerExeNames[compiler], app_arch, buildType);
123 	}
124 
125 	string com = dubCom();
126 	writefln("Executing '%s'", com); stdout.flush();
127 
128 	auto dub = executeShell(com, null, Config.none, size_t.max, ROOT_PATH);
129 
130 	dub.output.write;
131 	if (dub.status != 0) {
132 		writeln("Failed to run dub");
133 	}
134 }
135 
136 struct ReleasePackage
137 {
138 	string semver;
139 	Arch arch;
140 	Platform platform;
141 	ZipArchive zip;
142 	string fileRoot;
143 	string archRoot;
144 }
145 
146 void pack(string semver, Arch arch, Platform pl)
147 {
148 	ReleasePackage pack = ReleasePackage(
149 		semver,
150 		arch,
151 		pl,
152 		new ZipArchive,
153 		ROOT_PATH);
154 	string archName = archName(&pack, "voxelman");
155 	pack.archRoot = archName;
156 
157 	makePackage(&pack);
158 	string archiveName = buildNormalizedPath(ROOT_PATH, archName) ~ ".zip";
159 	writePackage(&pack, archiveName);
160 
161 	extractArchive(archiveName, testDir);
162 }
163 
164 enum Arch { x64, x32 }
165 enum Platform { windows, linux, macos }
166 string[Platform] platformToString;
167 string[Arch] archToString;
168 static this()
169 {
170 	platformToString = [Platform.windows : "win", Platform.linux : "linux", Platform.macos : "mac"];
171 	archToString = [Arch.x64 : "64", Arch.x32 : "32"];
172 }
173 
174 void writePackage(ReleasePackage* pack, string path)
175 {
176 	writefln("Writing archive into %s", path);
177 	std.file.write(path, pack.zip.build());
178 }
179 
180 string archName(R)(ReleasePackage* pack, R baseName)
181 {
182 	string arch = archToString[pack.arch];
183 	string platform = platformToString[pack.platform];
184 	return format("%s-v%s-%s%s", baseName, pack.semver, platform, arch);
185 }
186 
187 alias normPath = buildNormalizedPath;
188 alias absPath = absolutePath;
189 void addFiles(ReleasePackage* pack, string path, string pattern)
190 {
191 	import std.file : dirEntries, SpanMode;
192 
193 	string absRoot = pack.fileRoot.absPath.normPath;
194 	foreach (entry; dirEntries(buildPath(pack.fileRoot, path), pattern, SpanMode.depth))
195 	if (entry.isFile) {
196 		string absPath = entry.name.absPath.normPath;
197 		addFile(pack, absPath, relativePath(absPath, absRoot));
198 	}
199 }
200 
201 void addFile(ReleasePackage* pack, string archive_name)
202 {
203 	addFile(pack.zip, buildPath(pack.fileRoot, archive_name).absPath.normPath, buildPath(pack.archRoot, archive_name));
204 }
205 
206 void addFile(ReleasePackage* pack, string fs_name, string archive_name)
207 {
208 	addFile(pack.zip, fs_name.absPath.normPath, buildPath(pack.archRoot, archive_name));
209 }
210 
211 void addFile(ZipArchive archive, string fs_name, string archive_name)
212 {
213 	string norm = archive_name.normPath;
214 	if (!exists(fs_name)) {
215 		writefln("Cannot find %s at %s", fs_name, norm);
216 		return;
217 	}
218 	writefln("Add %s as %s", fs_name, norm);
219 	void[] data = std.file.read(fs_name);
220 	ArchiveMember am = new ArchiveMember();
221 	am.name = norm;
222 	am.compressionMethod = CompressionMethod.deflate;
223 	am.expandedData(cast(ubyte[])data);
224 	archive.addMember(am);
225 }
226 
227 void extractArchive(string archive, string pathTo)
228 {
229 	writefln("Extracting %s into %s", archive, pathTo);
230 	extractArchive(new ZipArchive(std.file.read(archive)), pathTo);
231 }
232 
233 void extractArchive(ZipArchive archive, string pathTo)
234 {
235 	foreach (ArchiveMember am; archive.directory)
236 	{
237 		string targetPath = buildPath(pathTo, am.name);
238 		mkdirRecurse(dirName(targetPath));
239 		archive.expand(am);
240 		std.file.write(targetPath, am.expandedData);
241 	}
242 }