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(in BuildPlatform platform, string verboseOutput) 103 { 104 // Find the backend version of the compiler, not the dmd FE. 105 // Specificically, for gdmd-14 this function should return 106 // 14.X.Y not 2.108.Z 107 switch (platform.compiler) { 108 case "dmd": 109 case "ldc": 110 { 111 import std.regex : matchFirst, regex; 112 auto ver = matchFirst(verboseOutput, regex(dmdVersionRe, "m")); 113 return ver && ver.length > 1 ? ver[1] : null; 114 } 115 case "gdc": 116 { 117 import std.process; 118 const result = execute([platform.compilerBinary, "-q,-dumpfullversion", "--version"]); 119 return result.status == 0 ? result.output : null; 120 } 121 default: 122 throw new UnknownCompilerException(platform.compiler); 123 } 124 125 } 126 127 BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override) 128 { 129 // Set basic arch flags for the probe - might be revised based on the exact value + compiler version 130 string[] arch_flags; 131 switch (arch_override) { 132 default: throw new UnsupportedArchitectureException(arch_override); 133 case "": 134 // Don't use Optlink by default on Windows 135 version (Windows) { 136 const is64bit = isWow64(); 137 if (!is64bit.isNull) 138 arch_flags = [ is64bit.get ? "-m64" : "-m32" ]; 139 } 140 break; 141 // DMD 2.099 made MsCOFF the default, and DMD v2.109 removed OMF 142 // support. Default everything to MsCOFF, people wanting to use OMF 143 // should use an older DMD / dub. 144 case "x86", "x86_omf", "x86_mscoff": arch_flags = ["-m32"]; break; 145 case "x86_64": arch_flags = ["-m64"]; break; 146 } 147 148 auto bp = probePlatform(compiler_binary, arch_flags); 149 150 bool keep_arch; 151 if (arch_flags.length) 152 keep_arch = bp.architecture != probePlatform(compiler_binary, []).architecture; 153 settings.maybeAddArchFlags(keep_arch, arch_flags, arch_override); 154 155 if (arch_override.length 156 && !bp.architecture.canFind(arch_override) 157 && !arch_override.among("x86_omf", "x86_mscoff") 158 ) { 159 logWarn(`Failed to apply the selected architecture %s. Got %s.`, 160 arch_override, bp.architecture); 161 } 162 163 return bp; 164 } 165 166 version (Windows) version (DigitalMars) unittest 167 { 168 BuildSettings settings; 169 auto compiler = new DMDCompiler; 170 auto bp = compiler.determinePlatform(settings, "dmd", "x86"); 171 assert(bp.isWindows()); 172 assert(bp.architecture.canFind("x86")); 173 settings = BuildSettings.init; 174 bp = compiler.determinePlatform(settings, "dmd", "x86_omf"); 175 assert(bp.isWindows()); 176 assert(bp.architecture.canFind("x86")); 177 settings = BuildSettings.init; 178 bp = compiler.determinePlatform(settings, "dmd", "x86_mscoff"); 179 assert(bp.isWindows()); 180 assert(bp.architecture.canFind("x86")); 181 settings = BuildSettings.init; 182 bp = compiler.determinePlatform(settings, "dmd", "x86_64"); 183 assert(bp.isWindows()); 184 assert(bp.architecture.canFind("x86_64")); 185 assert(!bp.architecture.canFind("x86")); 186 settings = BuildSettings.init; 187 bp = compiler.determinePlatform(settings, "dmd", ""); 188 if (!isWow64.isNull && !isWow64.get) assert(bp.architecture.canFind("x86")); 189 if (!isWow64.isNull && isWow64.get) assert(bp.architecture.canFind("x86_64")); 190 } 191 192 version (LDC) unittest { 193 import std.conv : to; 194 195 version (ARM) 196 enum isARM = true; 197 version (AArch64) 198 enum isARM = true; 199 else 200 enum isARM = false; 201 202 BuildSettings settings; 203 auto compiler = new DMDCompiler; 204 auto bp = compiler.determinePlatform(settings, "ldmd2", "x86"); 205 static if (isARM) 206 assert(bp.architecture.canFind("arm"), bp.architecture.to!string); 207 else 208 assert(bp.architecture.canFind("x86"), bp.architecture.to!string); 209 bp = compiler.determinePlatform(settings, "ldmd2", ""); 210 version (X86) assert(bp.architecture.canFind("x86"), bp.architecture.to!string); 211 version (X86_64) assert(bp.architecture.canFind("x86_64"), bp.architecture.to!string); 212 } 213 214 void prepareBuildSettings(ref BuildSettings settings, const scope ref BuildPlatform platform, 215 BuildSetting fields = BuildSetting.all) const 216 { 217 enforceBuildRequirements(settings); 218 219 // Keep the current dflags at the end of the array so that they will overwrite other flags. 220 // This allows user $DFLAGS to modify flags added by us. 221 const dflagsTail = settings.dflags; 222 settings.dflags = []; 223 224 if (!(fields & BuildSetting.options)) { 225 foreach (t; s_options) 226 if (settings.options & t[0]) 227 settings.addDFlags(t[1]); 228 } 229 230 if (!(fields & BuildSetting.versions)) { 231 settings.addDFlags(settings.versions.map!(s => "-version="~s)().array()); 232 settings.versions = null; 233 } 234 235 if (!(fields & BuildSetting.debugVersions)) { 236 settings.addDFlags(settings.debugVersions.map!(s => "-debug="~s)().array()); 237 settings.debugVersions = null; 238 } 239 240 if (!(fields & BuildSetting.importPaths)) { 241 settings.addDFlags(settings.importPaths.map!(s => "-I"~s)().array()); 242 settings.importPaths = null; 243 } 244 245 if (!(fields & BuildSetting.cImportPaths)) { 246 settings.addDFlags(settings.cImportPaths.map!(s => "-P-I"~s)().array()); 247 settings.cImportPaths = 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.isWindows()) 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.frameworks)) { 264 if (platform.isDarwin()) 265 settings.addLFlags(settings.frameworks.map!(l => ["-framework", l])().joiner.array()); 266 else 267 logDiagnostic("Not a darwin-derived platform, skipping frameworks..."); 268 } 269 270 if (!(fields & BuildSetting.sourceFiles)) { 271 settings.addDFlags(settings.sourceFiles); 272 settings.sourceFiles = null; 273 } 274 275 if (!(fields & BuildSetting.lflags)) { 276 settings.addDFlags(lflagsToDFlags(settings.lflags)); 277 settings.lflags = null; 278 } 279 280 if (platform.platform.canFind("posix") && (settings.options & BuildOption.pic)) 281 settings.addDFlags("-fPIC"); 282 283 settings.addDFlags(dflagsTail); 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", "-betterC": 430 return true; 431 default: 432 return arg.startsWith("-L") 433 || arg.startsWith("-Xcc=") 434 || arg.startsWith("-defaultlib="); 435 } 436 } 437 438 protected string[] defaultProbeArgs () const { 439 return ["-quiet", "-c", "-o-", "-v"]; 440 } 441 }