PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/build.d

http://github.com/AndrejMitrovic/DWinProgramming
D | 576 lines | 469 code | 86 blank | 21 comment | 103 complexity | a1cbd7d3af58c719fa7c90ed05775dce MD5 | raw file
  1. module build;
  2. /*
  3. Build tool to build all samples or individual samples.
  4. */
  5. import core.thread : Thread, dur;
  6. import std.algorithm;
  7. import std.array;
  8. import std.exception;
  9. import std.stdio;
  10. import std.string;
  11. import std.path;
  12. import std.file;
  13. import std.process;
  14. import std.parallelism;
  15. enum curdir = ".";
  16. alias std.path.absolutePath rel2abs;
  17. string[] RCINCLUDES;
  18. extern(C) int kbhit();
  19. extern(C) int getch();
  20. class ForcedExitException : Exception
  21. {
  22. this()
  23. {
  24. super("");
  25. }
  26. }
  27. class FailedBuildException : Exception
  28. {
  29. string[] failedMods;
  30. string[] errorMsgs;
  31. this(string[] failedModules, string[] errorMsgs)
  32. {
  33. this.failedMods = failedModules;
  34. this.errorMsgs = errorMsgs;
  35. super("");
  36. }
  37. }
  38. bool allExist(string[] paths)
  39. {
  40. foreach (path; paths)
  41. {
  42. if (!path.exists)
  43. return false;
  44. }
  45. return true;
  46. }
  47. void checkWinLib()
  48. {
  49. win32lib = (compiler == Compiler.DMD)
  50. ? "dmd_win32.lib"
  51. : "gdc_win32.lib";
  52. string buildScript = (compiler == Compiler.DMD)
  53. ? "dmd_build.bat"
  54. : "gdc_build.bat";
  55. enforce(win32lib.exists, format("Not found: %s. You have to compile the WindowsAPI bindings first. Use the %s script in the win32 folder.", win32lib, buildScript));
  56. }
  57. void checkTools()
  58. {
  59. executeShell("echo int x; > test.h");
  60. auto res = executeShell("cmd /c htod test.h").status;
  61. if (res == -1 || res == 1)
  62. {
  63. skipHeaderCompile = true;
  64. writeln("Warning: HTOD missing, won't retranslate .h headers.");
  65. }
  66. collectException(std.file.remove("test.h"));
  67. collectException(std.file.remove("test.d"));
  68. if (compiler == Compiler.DMD)
  69. {
  70. executeShell("echo //void > test.rc");
  71. string cmd = (compiler == Compiler.DMD)
  72. ? "cmd /c rc test.rc > nul"
  73. : "cmd /c windres test.rc > nul";
  74. res = executeShell(cmd).status;
  75. if (res == -1 || res == 1)
  76. {
  77. skipResCompile = true;
  78. writeln("Warning: RC Compiler not found. Builder will use precompiled resources. See README for more details.");
  79. }
  80. collectException(std.file.remove("test.rc"));
  81. collectException(std.file.remove("test.res"));
  82. }
  83. if (!skipResCompile)
  84. {
  85. auto includes = environment.get("RCINCLUDES", `C:\Program Files\Microsoft SDKs\Windows\v7.1\Include;C:\Program Files\Microsoft Visual Studio 10.0\VC\include;C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\include`).split(";");
  86. if (includes.allExist)
  87. {
  88. RCINCLUDES = includes;
  89. skipResCompile = false;
  90. }
  91. else
  92. writeln("Won't compile resources.");
  93. }
  94. if (skipResCompile)
  95. {
  96. writeln("Warning: RC Compiler Include dirs not found. Builder will will use precompiled resources.");
  97. }
  98. writeln();
  99. Thread.sleep(dur!"seconds"(1));
  100. }
  101. string[] getFilesByExt(string dir, string ext, string ext2 = null)
  102. {
  103. string[] result;
  104. foreach (string file; dirEntries(dir, SpanMode.shallow))
  105. {
  106. if (file.isFile && (file.extension.toLower == ext || file.extension.toLower == ext2))
  107. {
  108. result ~= file;
  109. }
  110. }
  111. return result;
  112. }
  113. __gshared bool Debug;
  114. __gshared bool cleanOnly;
  115. __gshared bool skipHeaderCompile;
  116. __gshared bool skipResCompile;
  117. __gshared bool silent;
  118. __gshared bool doRun;
  119. __gshared string win32lib;
  120. enum Compiler { DMD, GDC }
  121. __gshared Compiler compiler = Compiler.DMD;
  122. string soloProject;
  123. alias reduce!("a ~ ' ' ~ b") flatten;
  124. string[] getProjectDirs(string root)
  125. {
  126. string[] result;
  127. if (!isValidPath(root) || !exists(root))
  128. {
  129. assert(0, format("Path doesn't exist: %s", root));
  130. }
  131. // direntries is not a range in 2.053
  132. foreach (string dir; dirEntries(root, SpanMode.shallow))
  133. {
  134. if (dir.isDir && dir.baseName != "MSDN" && dir.baseName != "Extra2")
  135. {
  136. foreach (string subdir; dirEntries(dir, SpanMode.shallow))
  137. {
  138. if (subdir.isDir && subdir.baseName != "todo")
  139. result ~= subdir;
  140. }
  141. }
  142. }
  143. return result;
  144. }
  145. bool buildProject(string dir, out string errorMsg)
  146. {
  147. string appName = rel2abs(dir).baseName;
  148. string exeName = rel2abs(dir) ~ `\` ~ appName ~ ".exe";
  149. string LIBPATH = ".";
  150. string debugFlags = "-IWindowsAPI -I. -version=Unicode -version=WindowsXP -d -g -w -wi";
  151. string releaseFlags = (compiler == Compiler.DMD)
  152. ? "-IWindowsAPI -I. -version=Unicode -version=WindowsXP -d -L-Subsystem:Windows:4"
  153. : "-IWindowsAPI -I. -version=Unicode -version=WindowsXP -d -L--subsystem -Lwindows";
  154. string FLAGS = Debug ? debugFlags : releaseFlags;
  155. // there's only one resource and header file for each example
  156. string[] resources;
  157. string[] headers;
  158. if (!skipResCompile)
  159. resources = dir.getFilesByExt(".rc");
  160. if (!skipHeaderCompile)
  161. headers = dir.getFilesByExt(".h");
  162. // have to clean .o files for GCC
  163. if (compiler == Compiler.GDC)
  164. {
  165. executeShell("cmd /c del " ~ rel2abs(dir) ~ `\*.o > nul`);
  166. }
  167. if (resources.length)
  168. {
  169. string res_cmd;
  170. final switch (compiler)
  171. {
  172. case Compiler.DMD:
  173. {
  174. res_cmd = "rc " ~ resources[0].stripExtension ~ ".rc";
  175. break;
  176. }
  177. case Compiler.GDC:
  178. {
  179. res_cmd = "windres -i " ~
  180. resources[0].stripExtension ~ ".rc" ~
  181. " -o " ~
  182. resources[0].stripExtension ~ "_res.o";
  183. break;
  184. }
  185. }
  186. auto pc = executeShell(res_cmd);
  187. auto output = pc.output;
  188. auto res = pc.status;
  189. if (res == -1 || res == 1)
  190. {
  191. errorMsg = format("Compiling resource file failed.\nCommand was:\n%s\n\nError was:\n%s", res_cmd, output);
  192. return false;
  193. }
  194. }
  195. // @BUG@ htod can't output via -of or -od, causes multithreading issues.
  196. // We're distributing precompiled .d files now.
  197. //~ headers.length && executeShell("htod " ~ headers[0] ~ " " ~ `-IC:\dm\include`);
  198. //~ headers.length && executeShell("copy resource.d " ~ rel2abs(dir) ~ `\resource.d > nul`);
  199. // get sources after any .h header files were converted to .d header files
  200. //~ auto sources = dir.getFilesByExt(".d", "res");
  201. auto sources = dir.getFilesByExt(".d", (compiler == Compiler.DMD)
  202. ? ".res"
  203. : ".o");
  204. if (sources.length)
  205. {
  206. string cmd;
  207. final switch (compiler)
  208. {
  209. case Compiler.DMD:
  210. {
  211. cmd = "dmd -of" ~ exeName ~
  212. " -od" ~ rel2abs(dir) ~ `\` ~
  213. " -I" ~ LIBPATH ~ `\` ~
  214. " " ~ LIBPATH ~ `\` ~ win32lib ~
  215. " " ~ FLAGS ~
  216. " " ~ sources.flatten;
  217. break;
  218. }
  219. case Compiler.GDC:
  220. {
  221. version(LP_64)
  222. enum bitSwitch = "-m64";
  223. else
  224. enum bitSwitch = "-m32";
  225. cmd = "gdmd.bat " ~ bitSwitch ~ " " ~ "-fignore-unknown-pragmas -mwindows -of" ~ exeName ~
  226. " -od" ~ rel2abs(dir) ~ `\` ~
  227. " -Llibwinmm.a -Llibuxtheme.a -Llibcomctl32.a -Llibwinspool.a -Llibws2_32.a -Llibgdi32.a -I" ~ LIBPATH ~ `\` ~
  228. " " ~ LIBPATH ~ `\` ~ win32lib ~
  229. " " ~ FLAGS ~
  230. " " ~ sources.flatten;
  231. break;
  232. }
  233. }
  234. auto res = executeShell(cmd);
  235. if (res.status != 0)
  236. {
  237. errorMsg = res.output;
  238. return false;
  239. }
  240. }
  241. return true;
  242. }
  243. void runApp(string dir)
  244. {
  245. string appName = rel2abs(dir).baseName;
  246. string exeName = rel2abs(dir) ~ `\` ~ appName ~ ".exe";
  247. executeShell(exeName);
  248. }
  249. void buildProjectDirs(string[] dirs, bool cleanOnly = false)
  250. {
  251. __gshared string[] failedBuilds;
  252. __gshared string[] serialBuilds;
  253. __gshared string[] errorMsgs;
  254. if (cleanOnly)
  255. writeln("Cleaning.. ");
  256. void buildDir(string dir)
  257. {
  258. if (!cleanOnly && kbhit())
  259. {
  260. auto key = cast(dchar)getch();
  261. stdin.flush();
  262. enforce(key != 'q', new ForcedExitException);
  263. }
  264. // @BUG@ Using chdir in parallel builds wreaks havoc on other threads.
  265. if (dir.baseName == "EdrTest" ||
  266. dir.baseName == "ShowBit" ||
  267. dir.baseName == "StrProg")
  268. {
  269. serialBuilds ~= dir;
  270. }
  271. else
  272. {
  273. if (cleanOnly)
  274. {
  275. executeShell("cmd /c del " ~ dir ~ `\` ~ "*.obj > nul");
  276. executeShell("cmd /c del " ~ dir ~ `\` ~ "*.exe > nul");
  277. }
  278. else
  279. {
  280. string errorMsg;
  281. if (!buildProject(dir, /* out */ errorMsg))
  282. {
  283. writefln("\nfail: %s\n%s", dir.relativePath(), errorMsg);
  284. errorMsgs ~= errorMsg;
  285. failedBuilds ~= dir.relativePath() ~ `\` ~ dir.baseName ~ ".exe";
  286. }
  287. else
  288. {
  289. if (!silent)
  290. writeln("ok: " ~ dir.relativePath());
  291. }
  292. }
  293. }
  294. }
  295. // GDC has issues when called in parallel (something seems to lock gdc files)
  296. if (compiler == Compiler.GDC)
  297. {
  298. foreach (dir; dirs)
  299. buildDir(dir);
  300. }
  301. else
  302. {
  303. foreach (dir; parallel(dirs, 1))
  304. buildDir(dir);
  305. }
  306. foreach (dir; serialBuilds)
  307. {
  308. chdir(rel2abs(dir) ~ `\`);
  309. if (cleanOnly)
  310. {
  311. executeShell("cmd /c del *.obj > nul");
  312. executeShell("cmd /c del *.o > nul");
  313. executeShell("cmd /c del *.exe > nul");
  314. executeShell("cmd /c del *.di > nul");
  315. executeShell("cmd /c del *.dll > nul");
  316. executeShell("cmd /c del *.lib > nul");
  317. }
  318. else
  319. {
  320. string projScript = (compiler == Compiler.DMD)
  321. ? "dmd_build.bat"
  322. : "gdc_build.bat";
  323. string debugFlags = "-I. -version=Unicode -version=WindowsXP -g -w -wi";
  324. string releaseFlags = (compiler == Compiler.DMD)
  325. ? "-I. -version=Unicode -version=WindowsXP -L-Subsystem:Windows:4"
  326. : "-I. -version=Unicode -version=WindowsXP -L--subsystem -Lwindows";
  327. if (projScript.exists)
  328. {
  329. auto pc = executeShell(projScript ~ " " ~ (Debug ? "-g" : "-L-Subsystem:Windows"));
  330. auto output = pc.output;
  331. auto res = pc.status;
  332. if (res == 1 || res == -1)
  333. {
  334. failedBuilds ~= rel2abs(curdir) ~ `\.exe`;
  335. errorMsgs ~= output;
  336. }
  337. }
  338. }
  339. }
  340. enforce(!failedBuilds.length, new FailedBuildException(failedBuilds, errorMsgs));
  341. }
  342. import std.exception;
  343. class BuildException : Exception
  344. {
  345. string errorMsg;
  346. this(string msg)
  347. {
  348. errorMsg = msg;
  349. super(msg);
  350. }
  351. this(string msg, string file, size_t line, Exception next = null)
  352. {
  353. errorMsg = msg;
  354. super(msg, file, line, next);
  355. }
  356. }
  357. string ExceptionImpl(string name)
  358. {
  359. return(`
  360. class ` ~ name ~ ` : BuildException
  361. {
  362. this(string msg)
  363. {
  364. super(msg);
  365. }
  366. this(string msg, string file, size_t line, Exception next = null)
  367. {
  368. super(msg, file, line, next);
  369. }
  370. }`);
  371. }
  372. string findAbsolutePath(string path, string input)
  373. {
  374. string result;
  375. foreach (name; path.absolutePath.pathSplitter)
  376. {
  377. result = buildPath(result, name);
  378. if (name == input)
  379. break;
  380. }
  381. return result;
  382. }
  383. mixin(ExceptionImpl("CompilerError"));
  384. mixin(ExceptionImpl("ModuleException"));
  385. mixin(ExceptionImpl("ParseException"));
  386. mixin(ExceptionImpl("ProcessExecutionException"));
  387. int main(string[] args)
  388. {
  389. args.popFront;
  390. foreach (arg; args)
  391. {
  392. if (arg.toLower == "clean") cleanOnly = true;
  393. else if (arg.toLower == "debug") Debug = true;
  394. else if (arg.toLower == "gdc") compiler = Compiler.GDC;
  395. else if (arg.toLower == "dmd") compiler = Compiler.DMD;
  396. else if (arg.toLower == "run") doRun = true;
  397. else
  398. if (arg.isFile && arg.extension == ".d")
  399. {
  400. soloProject = dirName(arg);
  401. }
  402. else
  403. {
  404. if (arg.driveName.length)
  405. {
  406. if (arg.exists && arg.isDir)
  407. {
  408. soloProject = arg;
  409. }
  410. else
  411. enforce(0, "Cannot build project in path: \"" ~ arg ~
  412. "\". Try wrapping %CD% with quotes when calling build: \"%CD%\"");
  413. }
  414. }
  415. }
  416. if (compiler == Compiler.GDC)
  417. {
  418. auto status = executeShell("perl.exe --help > nul 2>&1 ");
  419. if (status.status != 0)
  420. {
  421. writefln("Error: Couldn't invoke perl.exe: %s. Perl is required to run the GDMD script, try installing Strawberry Perl: http://strawberryperl.com", status.output);
  422. return 0;
  423. }
  424. }
  425. string[] dirs;
  426. if (soloProject.length)
  427. {
  428. silent = true;
  429. dirs ~= getSafePath(rel2abs(soloProject));
  430. string dWinPath = findAbsolutePath(".", "DWinProgramming");
  431. chdir(dWinPath);
  432. }
  433. else
  434. {
  435. dirs = getProjectDirs(rel2abs(curdir ~ `\Samples`));
  436. }
  437. if (!cleanOnly)
  438. {
  439. checkTools();
  440. checkWinLib();
  441. if (!silent)
  442. {
  443. //~ writeln("About to build.");
  444. // @BUG@ The RDMD bundled with DMD 2.053 has input handling bugs,
  445. // wait for 2.054 to print this out. If you have RDMD from github,
  446. // you can press 'q' during the build process to force exit.
  447. //~ writeln("About to build. Press 'q' to stop the build process.");
  448. //~ Thread.sleep(dur!("seconds")(2));
  449. }
  450. }
  451. try
  452. {
  453. buildProjectDirs(dirs, cleanOnly);
  454. if (soloProject.length && doRun)
  455. runApp(dirs.front);
  456. }
  457. catch (ForcedExitException)
  458. {
  459. writeln("\nBuild process halted, about to clean..\n");
  460. Thread.sleep(dur!("seconds")(1));
  461. cleanOnly = true;
  462. buildProjectDirs(dirs, cleanOnly);
  463. }
  464. catch (FailedBuildException exc)
  465. {
  466. if (soloProject.length)
  467. {
  468. writefln("%s failed to build.\n%s", exc.failedMods[0], exc.errorMsgs[0]);
  469. }
  470. else
  471. {
  472. writefln("\n\n%s projects failed to build:", exc.failedMods.length);
  473. foreach (i, mod; exc.failedMods)
  474. {
  475. writeln(mod, exc.errorMsgs[i]);
  476. }
  477. }
  478. return 1;
  479. }
  480. if (!cleanOnly && !silent)
  481. {
  482. writeln("\nAll examples succesfully built.");
  483. }
  484. return 0;
  485. }
  486. /** Remove std.path shenanigans */
  487. string getSafePath(string input)
  488. {
  489. return input.chomp(`\.`);
  490. }