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