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.log;
  14. import dub.internal.vibecompat.inet.path;
  15. import dub.recipe.packagerecipe : ToolchainRequirements;
  16.  
  17. import std.algorithm;
  18. import std.array;
  19. import std.conv;
  20. import std.exception;
  21. import std.file;
  22. import std.process;
  23. import std.typecons;
  24.  
  25. // Determines whether the specified process is running under WOW64 or an Intel64 of x64 processor.
  26. version (Windows)
  27. private Nullable!bool isWow64() {
  28. // See also: https://docs.microsoft.com/de-de/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo
  29. import core.sys.windows.windows : GetNativeSystemInfo, SYSTEM_INFO, PROCESSOR_ARCHITECTURE_AMD64;
  30.  
  31. static Nullable!bool result;
  32.  
  33. // A process's architecture won't change over while the process is in memory
  34. // Return the cached result
  35. if (!result.isNull)
  36. return result;
  37.  
  38. SYSTEM_INFO systemInfo;
  39. GetNativeSystemInfo(&systemInfo);
  40. result = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64;
  41. return result;
  42. }
  43.  
  44. class DMDCompiler : Compiler {
  45. private static immutable s_options = [
  46. tuple(BuildOption.debugMode, ["-debug"]),
  47. tuple(BuildOption.releaseMode, ["-release"]),
  48. tuple(BuildOption.coverage, ["-cov"]),
  49. tuple(BuildOption.debugInfo, ["-g"]),
  50. tuple(BuildOption.debugInfoC, ["-g"]),
  51. tuple(BuildOption.alwaysStackFrame, ["-gs"]),
  52. tuple(BuildOption.stackStomping, ["-gx"]),
  53. tuple(BuildOption.inline, ["-inline"]),
  54. tuple(BuildOption.noBoundsCheck, ["-noboundscheck"]),
  55. tuple(BuildOption.optimize, ["-O"]),
  56. tuple(BuildOption.profile, ["-profile"]),
  57. tuple(BuildOption.unittests, ["-unittest"]),
  58. tuple(BuildOption.verbose, ["-v"]),
  59. tuple(BuildOption.ignoreUnknownPragmas, ["-ignore"]),
  60. tuple(BuildOption.syntaxOnly, ["-o-"]),
  61. tuple(BuildOption.warnings, ["-wi"]),
  62. tuple(BuildOption.warningsAsErrors, ["-w"]),
  63. tuple(BuildOption.ignoreDeprecations, ["-d"]),
  64. tuple(BuildOption.deprecationWarnings, ["-dw"]),
  65. tuple(BuildOption.deprecationErrors, ["-de"]),
  66. tuple(BuildOption.property, ["-property"]),
  67. tuple(BuildOption.profileGC, ["-profile=gc"]),
  68. tuple(BuildOption.betterC, ["-betterC"]),
  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. string[] arch_flags;
  112. switch (arch_override) {
  113. default: throw new Exception("Unsupported architecture: "~arch_override);
  114. case "":
  115. // Don't use Optlink by default on Windows
  116. version (Windows) {
  117. const is64bit = isWow64();
  118. if (!is64bit.isNull)
  119. arch_flags = [is64bit.get ? "-m64" : "-m32mscoff"];
  120. }
  121. break;
  122. case "x86": arch_flags = ["-m32"]; break;
  123. case "x86_omf": arch_flags = ["-m32"]; break;
  124. case "x86_64": arch_flags = ["-m64"]; break;
  125. case "x86_mscoff": arch_flags = ["-m32mscoff"]; break;
  126. }
  127. settings.addDFlags(arch_flags);
  128.  
  129. return probePlatform(
  130. compiler_binary,
  131. arch_flags ~ ["-quiet", "-c", "-o-", "-v"],
  132. arch_override
  133. );
  134. }
  135. version (Windows) version (DigitalMars) unittest
  136. {
  137. BuildSettings settings;
  138. auto compiler = new DMDCompiler;
  139. auto bp = compiler.determinePlatform(settings, "dmd", "x86");
  140. assert(bp.platform.canFind("windows"));
  141. assert(bp.architecture.canFind("x86"));
  142. assert(bp.architecture.canFind("x86_omf"));
  143. assert(!bp.architecture.canFind("x86_mscoff"));
  144. settings = BuildSettings.init;
  145. bp = compiler.determinePlatform(settings, "dmd", "x86_omf");
  146. assert(bp.platform.canFind("windows"));
  147. assert(bp.architecture.canFind("x86"));
  148. assert(bp.architecture.canFind("x86_omf"));
  149. assert(!bp.architecture.canFind("x86_mscoff"));
  150. settings = BuildSettings.init;
  151. bp = compiler.determinePlatform(settings, "dmd", "x86_mscoff");
  152. assert(bp.platform.canFind("windows"));
  153. assert(bp.architecture.canFind("x86"));
  154. assert(!bp.architecture.canFind("x86_omf"));
  155. assert(bp.architecture.canFind("x86_mscoff"));
  156. settings = BuildSettings.init;
  157. bp = compiler.determinePlatform(settings, "dmd", "x86_64");
  158. assert(bp.platform.canFind("windows"));
  159. assert(bp.architecture.canFind("x86_64"));
  160. assert(!bp.architecture.canFind("x86"));
  161. assert(!bp.architecture.canFind("x86_omf"));
  162. assert(!bp.architecture.canFind("x86_mscoff"));
  163. settings = BuildSettings.init;
  164. bp = compiler.determinePlatform(settings, "dmd", "");
  165. if (!isWow64.isNull && !isWow64.get) assert(bp.architecture.canFind("x86"));
  166. if (!isWow64.isNull && !isWow64.get) assert(bp.architecture.canFind("x86_mscoff"));
  167. if (!isWow64.isNull && !isWow64.get) assert(!bp.architecture.canFind("x86_omf"));
  168. if (!isWow64.isNull && isWow64.get) assert(bp.architecture.canFind("x86_64"));
  169. }
  170.  
  171. version (LDC) unittest {
  172. BuildSettings settings;
  173. auto compiler = new DMDCompiler;
  174. auto bp = compiler.determinePlatform(settings, "ldmd2", "x86");
  175. assert(bp.architecture.canFind("x86"));
  176. assert(!bp.architecture.canFind("x86_omf"));
  177. bp = compiler.determinePlatform(settings, "ldmd2", "");
  178. version (X86) assert(bp.architecture.canFind("x86"));
  179. version (X86_64) assert(bp.architecture.canFind("x86_64"));
  180. assert(!bp.architecture.canFind("x86_omf"));
  181. }
  182.  
  183. void prepareBuildSettings(ref BuildSettings settings, const scope ref BuildPlatform platform,
  184. BuildSetting fields = BuildSetting.all) const
  185. {
  186. enforceBuildRequirements(settings);
  187.  
  188. if (!(fields & BuildSetting.options)) {
  189. foreach (t; s_options)
  190. if (settings.options & t[0])
  191. settings.addDFlags(t[1]);
  192. }
  193.  
  194. if (!(fields & BuildSetting.versions)) {
  195. settings.addDFlags(settings.versions.map!(s => "-version="~s)().array());
  196. settings.versions = null;
  197. }
  198.  
  199. if (!(fields & BuildSetting.debugVersions)) {
  200. settings.addDFlags(settings.debugVersions.map!(s => "-debug="~s)().array());
  201. settings.debugVersions = null;
  202. }
  203.  
  204. if (!(fields & BuildSetting.importPaths)) {
  205. settings.addDFlags(settings.importPaths.map!(s => "-I"~s)().array());
  206. settings.importPaths = null;
  207. }
  208.  
  209. if (!(fields & BuildSetting.stringImportPaths)) {
  210. settings.addDFlags(settings.stringImportPaths.map!(s => "-J"~s)().array());
  211. settings.stringImportPaths = null;
  212. }
  213.  
  214. if (!(fields & BuildSetting.libs)) {
  215. resolveLibs(settings, platform);
  216. if (platform.platform.canFind("windows"))
  217. settings.addSourceFiles(settings.libs.map!(l => l~".lib")().array());
  218. else
  219. settings.addLFlags(settings.libs.map!(l => "-l"~l)().array());
  220. }
  221.  
  222. if (!(fields & BuildSetting.sourceFiles)) {
  223. settings.addDFlags(settings.sourceFiles);
  224. settings.sourceFiles = null;
  225. }
  226.  
  227. if (!(fields & BuildSetting.lflags)) {
  228. settings.addDFlags(lflagsToDFlags(settings.lflags));
  229. settings.lflags = null;
  230. }
  231.  
  232. if (platform.platform.canFind("posix") && (settings.options & BuildOption.pic))
  233. settings.addDFlags("-fPIC");
  234.  
  235. assert(fields & BuildSetting.dflags);
  236. assert(fields & BuildSetting.copyFiles);
  237. }
  238.  
  239. void extractBuildOptions(ref BuildSettings settings) const
  240. {
  241. Appender!(string[]) newflags;
  242. next_flag: foreach (f; settings.dflags) {
  243. foreach (t; s_options)
  244. if (t[1].canFind(f)) {
  245. settings.options |= t[0];
  246. continue next_flag;
  247. }
  248. if (f.startsWith("-version=")) settings.addVersions(f[9 .. $]);
  249. else if (f.startsWith("-debug=")) settings.addDebugVersions(f[7 .. $]);
  250. else newflags ~= f;
  251. }
  252. settings.dflags = newflags.data;
  253. }
  254.  
  255. string getTargetFileName(in BuildSettings settings, in BuildPlatform platform)
  256. const {
  257. import std.conv: text;
  258. assert(settings.targetName.length > 0, "No target name set.");
  259. final switch (settings.targetType) {
  260. case TargetType.autodetect:
  261. assert(false,
  262. text("Configurations must have a concrete target type, ", settings.targetName,
  263. " has ", settings.targetType));
  264. case TargetType.none: return null;
  265. case TargetType.sourceLibrary: return null;
  266. case TargetType.executable:
  267. if (platform.platform.canFind("windows"))
  268. return settings.targetName ~ ".exe";
  269. else return settings.targetName.idup;
  270. case TargetType.library:
  271. case TargetType.staticLibrary:
  272. if (platform.platform.canFind("windows"))
  273. return settings.targetName ~ ".lib";
  274. else return "lib" ~ settings.targetName ~ ".a";
  275. case TargetType.dynamicLibrary:
  276. if (platform.platform.canFind("windows"))
  277. return settings.targetName ~ ".dll";
  278. else if (platform.platform.canFind("darwin"))
  279. return "lib" ~ settings.targetName ~ ".dylib";
  280. else return "lib" ~ settings.targetName ~ ".so";
  281. case TargetType.object:
  282. if (platform.platform.canFind("windows"))
  283. return settings.targetName ~ ".obj";
  284. else return settings.targetName ~ ".o";
  285. }
  286. }
  287.  
  288. void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const
  289. {
  290. final switch (settings.targetType) {
  291. case TargetType.autodetect: assert(false, "Invalid target type: autodetect");
  292. case TargetType.none: assert(false, "Invalid target type: none");
  293. case TargetType.sourceLibrary: assert(false, "Invalid target type: sourceLibrary");
  294. case TargetType.executable: break;
  295. case TargetType.library:
  296. case TargetType.staticLibrary:
  297. settings.addDFlags("-lib");
  298. break;
  299. case TargetType.dynamicLibrary:
  300. if (platform.compiler != "dmd" || platform.platform.canFind("windows") || platform.platform.canFind("osx"))
  301. settings.addDFlags("-shared");
  302. else
  303. settings.prependDFlags("-shared", "-defaultlib=libphobos2.so");
  304. break;
  305. case TargetType.object:
  306. settings.addDFlags("-c");
  307. break;
  308. }
  309.  
  310. if (tpath is null)
  311. tpath = (NativePath(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString();
  312. settings.addDFlags("-of"~tpath);
  313. }
  314.  
  315. void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback)
  316. {
  317. auto res_file = getTempFile("dub-build", ".rsp");
  318. const(string)[] args = settings.dflags;
  319. if (platform.frontendVersion >= 2066) args ~= "-vcolumns";
  320. std.file.write(res_file.toNativeString(), escapeArgs(args).join("\n"));
  321.  
  322. logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" "));
  323. invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback);
  324. }
  325.  
  326. void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback)
  327. {
  328. import std.string;
  329. auto tpath = NativePath(settings.targetPath) ~ getTargetFileName(settings, platform);
  330. auto args = ["-of"~tpath.toNativeString()];
  331. args ~= objects;
  332. args ~= settings.sourceFiles;
  333. if (platform.platform.canFind("linux"))
  334. args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being specified in the wrong order by DMD
  335. args ~= lflagsToDFlags(settings.lflags);
  336. args ~= settings.dflags.filter!(f => isLinkerDFlag(f)).array;
  337.  
  338. auto res_file = getTempFile("dub-build", ".lnk");
  339. std.file.write(res_file.toNativeString(), escapeArgs(args).join("\n"));
  340.  
  341. logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" "));
  342. invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback);
  343. }
  344.  
  345. string[] lflagsToDFlags(in string[] lflags) const
  346. {
  347. return map!(f => "-L"~f)(lflags.filter!(f => f != "")()).array();
  348. }
  349.  
  350. private auto escapeArgs(in string[] args)
  351. {
  352. return args.map!(s => s.canFind(' ') ? "\""~s~"\"" : s);
  353. }
  354.  
  355. private static bool isLinkerDFlag(string arg)
  356. {
  357. switch (arg) {
  358. default:
  359. if (arg.startsWith("-defaultlib=")) return true;
  360. return false;
  361. case "-g", "-gc", "-m32", "-m64", "-shared", "-lib", "-m32mscoff":
  362. return true;
  363. }
  364. }
  365. }