Newer
Older
dub_jkp / source / dub / compilers / dmd.d
  1. /**
  2. DMD compiler support.
  3.  
  4. Copyright: © 2013-2013 rejectedsoftware e.K.
  5. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
  6. Authors: Sönke Ludwig
  7. */
  8. module dub.compilers.dmd;
  9.  
  10. import dub.compilers.compiler;
  11. import dub.compilers.utils;
  12. import dub.internal.utils;
  13. import dub.internal.vibecompat.core.file;
  14. import dub.internal.vibecompat.inet.path;
  15. import dub.internal.logging;
  16.  
  17. import std.algorithm;
  18. import std.array;
  19. import std.exception;
  20. import std.typecons;
  21.  
  22. // Determines whether the specified process is running under WOW64 or an Intel64 of x64 processor.
  23. version (Windows)
  24. private Nullable!bool isWow64() {
  25. // See also: https://docs.microsoft.com/de-de/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo
  26. import core.sys.windows.windows : GetNativeSystemInfo, SYSTEM_INFO, PROCESSOR_ARCHITECTURE_AMD64;
  27.  
  28. static Nullable!bool result;
  29.  
  30. // A process's architecture won't change over while the process is in memory
  31. // Return the cached result
  32. if (!result.isNull)
  33. return result;
  34.  
  35. SYSTEM_INFO systemInfo;
  36. GetNativeSystemInfo(&systemInfo);
  37. result = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64;
  38. return result;
  39. }
  40.  
  41. class DMDCompiler : Compiler {
  42. private static immutable s_options = [
  43. tuple(BuildOption.debugMode, ["-debug"]),
  44. tuple(BuildOption.releaseMode, ["-release"]),
  45. tuple(BuildOption.coverage, ["-cov"]),
  46. tuple(BuildOption.coverageCTFE, ["-cov=ctfe"]),
  47. tuple(BuildOption.debugInfo, ["-g"]),
  48. tuple(BuildOption.debugInfoC, ["-g"]),
  49. tuple(BuildOption.alwaysStackFrame, ["-gs"]),
  50. tuple(BuildOption.stackStomping, ["-gx"]),
  51. tuple(BuildOption.inline, ["-inline"]),
  52. tuple(BuildOption.noBoundsCheck, ["-noboundscheck"]),
  53. tuple(BuildOption.optimize, ["-O"]),
  54. tuple(BuildOption.profile, ["-profile"]),
  55. tuple(BuildOption.unittests, ["-unittest"]),
  56. tuple(BuildOption.verbose, ["-v"]),
  57. tuple(BuildOption.ignoreUnknownPragmas, ["-ignore"]),
  58. tuple(BuildOption.syntaxOnly, ["-o-"]),
  59. tuple(BuildOption.warnings, ["-wi"]),
  60. tuple(BuildOption.warningsAsErrors, ["-w"]),
  61. tuple(BuildOption.ignoreDeprecations, ["-d"]),
  62. tuple(BuildOption.deprecationWarnings, ["-dw"]),
  63. tuple(BuildOption.deprecationErrors, ["-de"]),
  64. tuple(BuildOption.property, ["-property"]),
  65. tuple(BuildOption.profileGC, ["-profile=gc"]),
  66. tuple(BuildOption.betterC, ["-betterC"]),
  67. tuple(BuildOption.lowmem, ["-lowmem"]),
  68. tuple(BuildOption.color, ["-color"]),
  69.  
  70. tuple(BuildOption._docs, ["-Dddocs"]),
  71. tuple(BuildOption._ddox, ["-Xfdocs.json", "-Df__dummy.html"]),
  72. ];
  73.  
  74. @property string name() const { return "dmd"; }
  75.  
  76. enum dmdVersionRe = `^version\s+v?(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`;
  77.  
  78. unittest {
  79. import std.regex : matchFirst, regex;
  80. auto probe = `
  81. binary dmd
  82. version v2.082.0
  83. config /etc/dmd.conf
  84. `;
  85. auto re = regex(dmdVersionRe, "m");
  86. auto c = matchFirst(probe, re);
  87. assert(c && c.length > 1 && c[1] == "2.082.0");
  88. }
  89.  
  90. unittest {
  91. import std.regex : matchFirst, regex;
  92. auto probe = `
  93. binary dmd
  94. version v2.084.0-beta.1
  95. config /etc/dmd.conf
  96. `;
  97. auto re = regex(dmdVersionRe, "m");
  98. auto c = matchFirst(probe, re);
  99. assert(c && c.length > 1 && c[1] == "2.084.0-beta.1");
  100. }
  101.  
  102. string determineVersion(string compiler_binary, string verboseOutput)
  103. {
  104. import std.regex : matchFirst, regex;
  105. auto ver = matchFirst(verboseOutput, regex(dmdVersionRe, "m"));
  106. return ver && ver.length > 1 ? ver[1] : null;
  107. }
  108.  
  109. BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override)
  110. {
  111. // Set basic arch flags for the probe - might be revised based on the exact value + compiler version
  112. string[] arch_flags;
  113. if (arch_override.length)
  114. arch_flags = [ arch_override != "x86_64" ? "-m32" : "-m64" ];
  115. else
  116. {
  117. // Don't use Optlink by default on Windows
  118. version (Windows) {
  119. const is64bit = isWow64();
  120. if (!is64bit.isNull)
  121. arch_flags = [ is64bit.get ? "-m64" : "-m32" ];
  122. }
  123. }
  124.  
  125. BuildPlatform bp = probePlatform(
  126. compiler_binary,
  127. arch_flags ~ ["-quiet", "-c", "-o-", "-v"],
  128. arch_override
  129. );
  130.  
  131. /// Replace architecture string in `bp.architecture`
  132. void replaceArch(const string from, const string to)
  133. {
  134. const idx = bp.architecture.countUntil(from);
  135. if (idx != -1)
  136. bp.architecture[idx] = to;
  137. }
  138.  
  139. // DMD 2.099 changed the default for -m32 from OMF to MsCOFF
  140. const m32IsCoff = bp.frontendVersion >= 2_099;
  141.  
  142. switch (arch_override) {
  143. default: throw new UnsupportedArchitectureException(arch_override);
  144. case "": break;
  145. case "x86": arch_flags = ["-m32"]; break;
  146. case "x86_64": arch_flags = ["-m64"]; break;
  147.  
  148. case "x86_omf":
  149. if (m32IsCoff)
  150. {
  151. arch_flags = [ "-m32omf" ];
  152. replaceArch("x86_mscoff", "x86_omf"); // Probe used the wrong default
  153. }
  154. else // -m32 is OMF
  155. {
  156. arch_flags = [ "-m32" ];
  157. }
  158. break;
  159.  
  160. case "x86_mscoff":
  161. if (m32IsCoff)
  162. {
  163. arch_flags = [ "-m32" ];
  164. }
  165. else // -m32 is OMF
  166. {
  167. arch_flags = [ "-m32mscoff" ];
  168. replaceArch("x86_omf", "x86_mscoff"); // Probe used the wrong default
  169. }
  170. break;
  171. }
  172. settings.addDFlags(arch_flags);
  173.  
  174. return bp;
  175. }
  176.  
  177. version (Windows) version (DigitalMars) unittest
  178. {
  179. BuildSettings settings;
  180. auto compiler = new DMDCompiler;
  181. auto bp = compiler.determinePlatform(settings, "dmd", "x86");
  182. assert(bp.isWindows());
  183. assert(bp.architecture.canFind("x86"));
  184. const defaultOMF = (bp.frontendVersion < 2_099);
  185. assert(bp.architecture.canFind("x86_omf") == defaultOMF);
  186. assert(bp.architecture.canFind("x86_mscoff") != defaultOMF);
  187. settings = BuildSettings.init;
  188. bp = compiler.determinePlatform(settings, "dmd", "x86_omf");
  189. assert(bp.isWindows());
  190. assert(bp.architecture.canFind("x86"));
  191. assert(bp.architecture.canFind("x86_omf"));
  192. assert(!bp.architecture.canFind("x86_mscoff"));
  193. settings = BuildSettings.init;
  194. bp = compiler.determinePlatform(settings, "dmd", "x86_mscoff");
  195. assert(bp.isWindows());
  196. assert(bp.architecture.canFind("x86"));
  197. assert(!bp.architecture.canFind("x86_omf"));
  198. assert(bp.architecture.canFind("x86_mscoff"));
  199. settings = BuildSettings.init;
  200. bp = compiler.determinePlatform(settings, "dmd", "x86_64");
  201. assert(bp.isWindows());
  202. assert(bp.architecture.canFind("x86_64"));
  203. assert(!bp.architecture.canFind("x86"));
  204. assert(!bp.architecture.canFind("x86_omf"));
  205. assert(!bp.architecture.canFind("x86_mscoff"));
  206. settings = BuildSettings.init;
  207. bp = compiler.determinePlatform(settings, "dmd", "");
  208. if (!isWow64.isNull && !isWow64.get) assert(bp.architecture.canFind("x86"));
  209. if (!isWow64.isNull && !isWow64.get) assert(bp.architecture.canFind("x86_mscoff"));
  210. if (!isWow64.isNull && !isWow64.get) assert(!bp.architecture.canFind("x86_omf"));
  211. if (!isWow64.isNull && isWow64.get) assert(bp.architecture.canFind("x86_64"));
  212. }
  213.  
  214. version (LDC) unittest {
  215. import std.conv : to;
  216.  
  217. BuildSettings settings;
  218. auto compiler = new DMDCompiler;
  219. auto bp = compiler.determinePlatform(settings, "ldmd2", "x86");
  220. assert(bp.architecture.canFind("x86"), bp.architecture.to!string);
  221. assert(!bp.architecture.canFind("x86_omf"), bp.architecture.to!string);
  222. bp = compiler.determinePlatform(settings, "ldmd2", "");
  223. version (X86) assert(bp.architecture.canFind("x86"), bp.architecture.to!string);
  224. version (X86_64) assert(bp.architecture.canFind("x86_64"), bp.architecture.to!string);
  225. assert(!bp.architecture.canFind("x86_omf"), bp.architecture.to!string);
  226. }
  227.  
  228. void prepareBuildSettings(ref BuildSettings settings, const scope ref BuildPlatform platform,
  229. BuildSetting fields = BuildSetting.all) const
  230. {
  231. enforceBuildRequirements(settings);
  232.  
  233. if (!(fields & BuildSetting.options)) {
  234. foreach (t; s_options)
  235. if (settings.options & t[0])
  236. settings.addDFlags(t[1]);
  237. }
  238.  
  239. if (!(fields & BuildSetting.versions)) {
  240. settings.addDFlags(settings.versions.map!(s => "-version="~s)().array());
  241. settings.versions = null;
  242. }
  243.  
  244. if (!(fields & BuildSetting.debugVersions)) {
  245. settings.addDFlags(settings.debugVersions.map!(s => "-debug="~s)().array());
  246. settings.debugVersions = null;
  247. }
  248.  
  249. if (!(fields & BuildSetting.importPaths)) {
  250. settings.addDFlags(settings.importPaths.map!(s => "-I"~s)().array());
  251. settings.importPaths = null;
  252. }
  253.  
  254. if (!(fields & BuildSetting.cImportPaths)) {
  255. settings.addDFlags(settings.cImportPaths.map!(s => "-I"~s)().array());
  256. settings.cImportPaths = null;
  257. }
  258.  
  259. if (!(fields & BuildSetting.stringImportPaths)) {
  260. settings.addDFlags(settings.stringImportPaths.map!(s => "-J"~s)().array());
  261. settings.stringImportPaths = null;
  262. }
  263.  
  264. if (!(fields & BuildSetting.libs)) {
  265. resolveLibs(settings, platform);
  266. if (platform.isWindows())
  267. settings.addSourceFiles(settings.libs.map!(l => l~".lib")().array());
  268. else
  269. settings.addLFlags(settings.libs.map!(l => "-l"~l)().array());
  270. }
  271.  
  272. if (!(fields & BuildSetting.sourceFiles)) {
  273. settings.addDFlags(settings.sourceFiles);
  274. settings.sourceFiles = null;
  275. }
  276.  
  277. if (!(fields & BuildSetting.lflags)) {
  278. settings.addDFlags(lflagsToDFlags(settings.lflags));
  279. settings.lflags = null;
  280. }
  281.  
  282. if (platform.platform.canFind("posix") && (settings.options & BuildOption.pic))
  283. settings.addDFlags("-fPIC");
  284.  
  285. assert(fields & BuildSetting.dflags);
  286. assert(fields & BuildSetting.copyFiles);
  287. }
  288.  
  289. void extractBuildOptions(ref BuildSettings settings) const
  290. {
  291. Appender!(string[]) newflags;
  292. next_flag: foreach (f; settings.dflags) {
  293. foreach (t; s_options)
  294. if (t[1].canFind(f)) {
  295. settings.options |= t[0];
  296. continue next_flag;
  297. }
  298. if (f.startsWith("-version=")) settings.addVersions(f[9 .. $]);
  299. else if (f.startsWith("-debug=")) settings.addDebugVersions(f[7 .. $]);
  300. else newflags ~= f;
  301. }
  302. settings.dflags = newflags.data;
  303. }
  304.  
  305. string getTargetFileName(in BuildSettings settings, in BuildPlatform platform)
  306. const {
  307. import std.conv: text;
  308. assert(settings.targetName.length > 0, "No target name set.");
  309. final switch (settings.targetType) {
  310. case TargetType.autodetect:
  311. assert(false,
  312. text("Configurations must have a concrete target type, ", settings.targetName,
  313. " has ", settings.targetType));
  314. case TargetType.none: return null;
  315. case TargetType.sourceLibrary: return null;
  316. case TargetType.executable:
  317. if (platform.isWindows())
  318. return settings.targetName ~ ".exe";
  319. else return settings.targetName.idup;
  320. case TargetType.library:
  321. case TargetType.staticLibrary:
  322. if (platform.isWindows())
  323. return settings.targetName ~ ".lib";
  324. else return "lib" ~ settings.targetName ~ ".a";
  325. case TargetType.dynamicLibrary:
  326. if (platform.isWindows())
  327. return settings.targetName ~ ".dll";
  328. else if (platform.platform.canFind("darwin"))
  329. return "lib" ~ settings.targetName ~ ".dylib";
  330. else return "lib" ~ settings.targetName ~ ".so";
  331. case TargetType.object:
  332. if (platform.isWindows())
  333. return settings.targetName ~ ".obj";
  334. else return settings.targetName ~ ".o";
  335. }
  336. }
  337.  
  338. void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const
  339. {
  340. const targetFileName = getTargetFileName(settings, platform);
  341.  
  342. final switch (settings.targetType) {
  343. case TargetType.autodetect: assert(false, "Invalid target type: autodetect");
  344. case TargetType.none: assert(false, "Invalid target type: none");
  345. case TargetType.sourceLibrary: assert(false, "Invalid target type: sourceLibrary");
  346. case TargetType.executable: break;
  347. case TargetType.library:
  348. case TargetType.staticLibrary:
  349. settings.addDFlags("-lib");
  350. break;
  351. case TargetType.dynamicLibrary:
  352. if (platform.compiler != "dmd" || platform.isWindows() || platform.platform.canFind("osx"))
  353. settings.addDFlags("-shared");
  354. else
  355. settings.prependDFlags("-shared", "-defaultlib=libphobos2.so");
  356. addDynamicLibName(settings, platform, targetFileName);
  357. break;
  358. case TargetType.object:
  359. settings.addDFlags("-c");
  360. break;
  361. }
  362.  
  363. if (tpath is null)
  364. tpath = (NativePath(settings.targetPath) ~ targetFileName).toNativeString();
  365. settings.addDFlags("-of"~tpath);
  366. }
  367.  
  368. void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd)
  369. {
  370. auto res_file = getTempFile("dub-build", ".rsp");
  371. // clean-up early to avoid build-up of temporaries when invoke is called
  372. // many times in one DUB session. (e.g. when using DUB as a library)
  373. scope (exit)
  374. removeFile(res_file);
  375. const(string)[] args = settings.dflags;
  376. if (platform.frontendVersion >= 2066) args ~= "-vcolumns";
  377. writeFile(res_file, escapeArgs(args).join("\n"));
  378.  
  379. logDiagnostic("[cwd=%s] %s %s", cwd, platform.compilerBinary, escapeArgs(args).join(" "));
  380. string[string] env;
  381. foreach (aa; [settings.environments, settings.buildEnvironments])
  382. foreach (k, v; aa)
  383. env[k] = v;
  384. invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback, cwd, env);
  385. }
  386.  
  387. void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd)
  388. {
  389. import std.string;
  390. auto tpath = NativePath(settings.targetPath) ~ getTargetFileName(settings, platform);
  391. auto args = ["-of"~tpath.toNativeString()];
  392. args ~= objects;
  393. args ~= settings.sourceFiles;
  394. if (platform.platform.canFind("linux"))
  395. args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being specified in the wrong order by DMD
  396. args ~= lflagsToDFlags(settings.lflags);
  397. if (platform.compiler == "ldc") {
  398. // ldmd2: support the full LDC-specific list + extra "-m32mscoff", a superset of the DMD list
  399. import dub.compilers.ldc : LDCCompiler;
  400. args ~= settings.dflags.filter!(f => f == "-m32mscoff" || LDCCompiler.isLinkerDFlag(f)).array;
  401. } else {
  402. args ~= settings.dflags.filter!(f => isLinkerDFlag(f)).array;
  403. }
  404.  
  405. auto res_file = getTempFile("dub-build", ".lnk");
  406. writeFile(res_file, escapeArgs(args).join("\n"));
  407.  
  408. logDiagnostic("[cwd=%s] %s %s", cwd, platform.compilerBinary, escapeArgs(args).join(" "));
  409. string[string] env;
  410. foreach (aa; [settings.environments, settings.buildEnvironments])
  411. foreach (k, v; aa)
  412. env[k] = v;
  413. invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback, cwd, env);
  414. }
  415.  
  416. string[] lflagsToDFlags(const string[] lflags) const
  417. {
  418. return map!(f => "-L"~f)(lflags.filter!(f => f != "")()).array();
  419. }
  420.  
  421. private auto escapeArgs(in string[] args)
  422. {
  423. return args.map!(s => s.canFind(' ') ? "\""~s~"\"" : s);
  424. }
  425.  
  426. static bool isLinkerDFlag(string arg)
  427. {
  428. switch (arg) {
  429. case "-g", "-gc", "-m32", "-m64", "-shared", "-lib", "-m32omf", "-m32mscoff", "-betterC":
  430. return true;
  431. default:
  432. return arg.startsWith("-L")
  433. || arg.startsWith("-Xcc=")
  434. || arg.startsWith("-defaultlib=");
  435. }
  436. }
  437. }