app.d 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import std.stdio;
  2. import std.file;
  3. import std.path;
  4. import std.compiler;
  5. import std.system;
  6. import std.format;
  7. import std.conv;
  8. import std.process;
  9. import std.string;
  10. import iopipe.json.serialize;
  11. import iopipe.json.parser;
  12. import std.exception;
  13. import iopipe.traits;
  14. // copied from arsd.archive
  15. /++
  16. A header of a file in the archive. This represents the
  17. binary format of the header block.
  18. +/
  19. align(512)
  20. struct TarFileHeader {
  21. align(1):
  22. char[100] fileName_ = 0;
  23. char[8] fileMode_ = 0;
  24. char[8] ownerUid_ = 0;
  25. char[8] ownerGid_ = 0;
  26. char[12] size_ = 0; // in octal
  27. char[12] mtime_ = 0; // octal unix timestamp
  28. char[8] checksum_ = 0; // right?????
  29. char[1] fileType_ = 0; // hard link, soft link, etc
  30. char[100] linkFileName_ = 0;
  31. char[6] ustarMagic_ = 0; // if "ustar\0", remaining fields are set
  32. char[2] ustarVersion_ = 0;
  33. char[32] ownerName_ = 0;
  34. char[32] groupName_ = 0;
  35. char[8] deviceMajorNumber_ = 0;
  36. char[8] deviceMinorNumber_ = 0;
  37. char[155] filenamePrefix_ = 0;
  38. /// Returns the filename. You should cache the return value as long as TarFileHeader is in scope (it returns a slice after calling strlen)
  39. const(char)[] filename() {
  40. import core.stdc.string;
  41. if(filenamePrefix_[0])
  42. return upToZero(filenamePrefix_[]) ~ upToZero(fileName_[]);
  43. return upToZero(fileName_[]);
  44. }
  45. ///
  46. ulong size() {
  47. import core.stdc.stdlib;
  48. return strtoul(size_.ptr, null, 8);
  49. }
  50. ///
  51. TarFileType type() {
  52. if(fileType_[0] == 0)
  53. return TarFileType.normal;
  54. else
  55. return cast(TarFileType) (fileType_[0] - '0');
  56. }
  57. uint mode() {
  58. import std.conv : to;
  59. return fileMode_.upToZero.to!int(8);
  60. }
  61. }
  62. /// There's other types but this is all I care about. You can still detect the char by `((cast(char) type) + '0')`
  63. enum TarFileType {
  64. normal = 0, ///
  65. hardLink = 1, ///
  66. symLink = 2, ///
  67. characterSpecial = 3, ///
  68. blockSpecial = 4, ///
  69. directory = 5, ///
  70. fifo = 6 ///
  71. }
  72. /++
  73. Low level tar file processor. You must pass it a
  74. TarFileHeader buffer as well as a size_t for context.
  75. Both must be initialized to all zeroes on first call,
  76. then not modified in between calls.
  77. Each call must populate the dataBuffer with 512 bytes.
  78. returns true if still work to do.
  79. +/
  80. bool processTar(
  81. TarFileHeader* header,
  82. long* bytesRemainingOnCurrentFile,
  83. ubyte[] dataBuffer,
  84. scope void delegate(TarFileHeader* header, bool isNewFile, bool fileFinished, ubyte[] data) handleData
  85. )
  86. {
  87. assert(dataBuffer.length == 512);
  88. assert(bytesRemainingOnCurrentFile !is null);
  89. assert(header !is null);
  90. if(*bytesRemainingOnCurrentFile) {
  91. bool isNew = *bytesRemainingOnCurrentFile == header.size();
  92. if(*bytesRemainingOnCurrentFile <= 512) {
  93. handleData(header, isNew, true, dataBuffer[0 .. cast(size_t) *bytesRemainingOnCurrentFile]);
  94. *bytesRemainingOnCurrentFile = 0;
  95. } else {
  96. handleData(header, isNew, false, dataBuffer[]);
  97. *bytesRemainingOnCurrentFile -= 512;
  98. }
  99. } else {
  100. *header = *(cast(TarFileHeader*) dataBuffer.ptr);
  101. auto s = header.size();
  102. *bytesRemainingOnCurrentFile = s;
  103. if(header.type() == TarFileType.symLink)
  104. handleData(header, true, true, cast(ubyte[])header.linkFileName_.upToZero);
  105. if(header.type() == TarFileType.directory)
  106. handleData(header, true, false, null);
  107. if(s == 0 && header.type == TarFileType.normal)
  108. return false;
  109. }
  110. return true;
  111. }
  112. T[] upToZero(T)(T[] input)
  113. {
  114. foreach(i, v; input)
  115. if(v == 0)
  116. return input[0 .. i];
  117. return input;
  118. }
  119. version(X86)
  120. enum arch="x86";
  121. else version(X86_64)
  122. enum arch="x86_64";
  123. else version(ARM)
  124. enum arch="arm";
  125. else version(AArch64)
  126. enum arch="arm64";
  127. else
  128. static assert(false, "Unsupported architecture");
  129. // establish the runtime
  130. version(CRuntime_Microsoft)
  131. enum CRT="MSVC";
  132. else version(CRuntime_Glibc)
  133. enum CRT="glibc";
  134. else version(CppRuntime_Clang)
  135. enum CRT="llvm";
  136. else
  137. static assert(false, "Unsupported runtime");
  138. enum osStr = os.to!string;
  139. enum baseDir = buildPath("install", "lib", osStr, arch, CRT);
  140. void extractArchive(char[] path)
  141. {
  142. import std.io : File, mode;
  143. import iopipe.bufpipe;
  144. import iopipe.refc;
  145. import iopipe.zip;
  146. import iopipe.valve;
  147. auto archivePath = buildPath(path, "install", "lib.tgz");
  148. auto expectedPrefix = "lib/" ~ osStr ~ "/";
  149. enforce(exists(archivePath), "No lib archive found while attempting to install raylib libraries!");
  150. // the input file
  151. auto inputFile = File(archivePath, mode!"rb").refCounted.bufd.unzip;
  152. // for tar
  153. TarFileHeader tfh;
  154. long size;
  155. bool doOutput;
  156. // open a file using iopipe
  157. auto openOutputFile(string fname)
  158. {
  159. return bufd.push!(c => c.outputPipe(File(fname, mode!"wb").refCounted));
  160. }
  161. typeof(openOutputFile("")) currentFile;
  162. string currentSymlinkText;
  163. void handleTar(TarFileHeader *header, bool isNewFile, bool fileFinished, ubyte[] data)
  164. {
  165. auto ft = header.type;
  166. if(isNewFile)
  167. {
  168. // check that the name matches
  169. auto fn = header.filename;
  170. if(!fn.startsWith(expectedPrefix))
  171. return;
  172. version(Posix)
  173. {
  174. // handle symlinks on posix
  175. if(ft == TarFileType.symLink)
  176. {
  177. doOutput = true;
  178. currentSymlinkText = "";
  179. }
  180. }
  181. if(ft == TarFileType.normal)
  182. {
  183. doOutput = true;
  184. auto newFilePath = buildPath(path, "install", fn);
  185. mkdirRecurse(dirName(newFilePath));
  186. currentFile = openOutputFile(newFilePath);
  187. }
  188. }
  189. if(doOutput)
  190. {
  191. if(ft == TarFileType.symLink)
  192. {
  193. currentSymlinkText ~= cast(char[])data;
  194. }
  195. else
  196. {
  197. currentFile.ensureElems(data.length);
  198. assert(currentFile.window.length >= data.length);
  199. currentFile.window[0 .. data.length] = data[];
  200. currentFile.release(data.length);
  201. }
  202. }
  203. if(fileFinished)
  204. {
  205. if(doOutput)
  206. {
  207. auto fn = header.filename;
  208. auto fp = buildPath(path, "install", fn);
  209. version(Posix)
  210. {
  211. if(ft == TarFileType.symLink)
  212. {
  213. mkdirRecurse(dirName(fp));
  214. symlink(currentSymlinkText, fp);
  215. }
  216. }
  217. if(ft == TarFileType.normal)
  218. {
  219. // close the file
  220. destroy(currentFile);
  221. // Effect the correct file permissions
  222. version(Posix)
  223. {
  224. setAttributes(fp, header.mode);
  225. }
  226. }
  227. doOutput = false;
  228. }
  229. }
  230. }
  231. while(inputFile.extend(0) > 0)
  232. {
  233. while(inputFile.window.length >= 512)
  234. {
  235. // big enough to process another tar chunk
  236. processTar(&tfh, &size, inputFile.window[0 .. 512], &handleTar);
  237. inputFile.release(512);
  238. }
  239. }
  240. }
  241. int main()
  242. {
  243. writeln("raylib-d library installation");
  244. // look at the dub.selections.json file
  245. auto dubConfig = execute(["dub", "describe"], null, Config.stderrPassThrough);
  246. string raylibdPath;
  247. if(dubConfig.status != 0)
  248. {
  249. stderr.writeln("Error executing dub describe");
  250. return dubConfig.status;
  251. }
  252. char[] getRaylibPath(char[] jsonStr)
  253. {
  254. auto tokens = jsonTokenizer(jsonStr);
  255. enforce(tokens.parseTo("packages"), "Could not find packages in dub json output!");
  256. auto nt = tokens.next.token;
  257. enforce(nt == JSONToken.ArrayStart, "Expected array start in packages");
  258. while(nt != JSONToken.ArrayEnd)
  259. {
  260. tokens.releaseParsed();
  261. tokens.startCache;
  262. enforce(tokens.parseTo("name"), "Could not find package name in json file");
  263. auto n = tokens.next;
  264. jsonExpect(n, JSONToken.String, "Expected string for package name");
  265. if(n.data(tokens.chain) == "raylib-d")
  266. {
  267. tokens.rewind;
  268. tokens.parseTo("path");
  269. auto p = tokens.next;
  270. jsonExpect(p, JSONToken.String, "Expected string for path");
  271. return p.data(tokens.chain);
  272. }
  273. tokens.rewind;
  274. tokens.endCache;
  275. nt = tokens.skipItem.token;
  276. }
  277. throw new Exception("Could not find raylib-d dependency for current project!");
  278. }
  279. try {
  280. auto path = getRaylibPath(dubConfig.output.dup);
  281. // check to see if the `lib` directory exists, and if not, see if we can extract it from a tarball
  282. writeln("Detected raylib dependency path as ", path);
  283. auto libpath = buildPath(path, baseDir);
  284. if(!exists(libpath))
  285. {
  286. // extract the data, but only for the detected OS
  287. writeln("Extracting archive");
  288. extractArchive(path);
  289. }
  290. writeln("Copying library files from ", libpath);
  291. foreach(ent; dirEntries(libpath, SpanMode.shallow))
  292. {
  293. auto newLoc = buildPath(".", ent.name.baseName(".lnk"));
  294. version(Posix)
  295. {
  296. if(ent.isSymlink)
  297. {
  298. // recreate the symlink
  299. auto origln = readLink(ent.name);
  300. writefln("Creating symlink %s -> %s", newLoc, origln);
  301. symlink(origln, newLoc);
  302. continue;
  303. }
  304. else if(ent.name.endsWith(".lnk"))
  305. {
  306. // dub workaround. This is really a symlink but wasn't
  307. // properly downloaded by dub.
  308. auto origln = cast(char[])read(ent.name);
  309. writefln("Creating symlink %s -> %s", newLoc, origln);
  310. symlink(origln, newLoc);
  311. continue;
  312. }
  313. }
  314. writeln("Installing library file ", newLoc);
  315. copy(ent.name, newLoc, PreserveAttributes.yes);
  316. }
  317. } catch(Exception ex) {
  318. stderr.writeln("Error: ", ex.msg);
  319. return 1;
  320. }
  321. return 0;
  322. }