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_64": arch_flags = ["-m64"]; break; 124 case "x86_mscoff": arch_flags = ["-m32mscoff"]; break; 125 } 126 settings.addDFlags(arch_flags); 127 128 return probePlatform( 129 compiler_binary, 130 arch_flags ~ ["-quiet", "-c", "-o-", "-v"], 131 arch_override 132 ); 133 } 134 135 void prepareBuildSettings(ref BuildSettings settings, in ref BuildPlatform platform, BuildSetting fields = BuildSetting.all) const 136 { 137 enforceBuildRequirements(settings); 138 139 if (!(fields & BuildSetting.options)) { 140 foreach (t; s_options) 141 if (settings.options & t[0]) 142 settings.addDFlags(t[1]); 143 } 144 145 if (!(fields & BuildSetting.versions)) { 146 settings.addDFlags(settings.versions.map!(s => "-version="~s)().array()); 147 settings.versions = null; 148 } 149 150 if (!(fields & BuildSetting.debugVersions)) { 151 settings.addDFlags(settings.debugVersions.map!(s => "-debug="~s)().array()); 152 settings.debugVersions = null; 153 } 154 155 if (!(fields & BuildSetting.importPaths)) { 156 settings.addDFlags(settings.importPaths.map!(s => "-I"~s)().array()); 157 settings.importPaths = null; 158 } 159 160 if (!(fields & BuildSetting.stringImportPaths)) { 161 settings.addDFlags(settings.stringImportPaths.map!(s => "-J"~s)().array()); 162 settings.stringImportPaths = null; 163 } 164 165 if (!(fields & BuildSetting.libs)) { 166 resolveLibs(settings, platform); 167 if (platform.platform.canFind("windows")) 168 settings.addSourceFiles(settings.libs.map!(l => l~".lib")().array()); 169 else 170 settings.addLFlags(settings.libs.map!(l => "-l"~l)().array()); 171 } 172 173 if (!(fields & BuildSetting.sourceFiles)) { 174 settings.addDFlags(settings.sourceFiles); 175 settings.sourceFiles = null; 176 } 177 178 if (!(fields & BuildSetting.lflags)) { 179 settings.addDFlags(lflagsToDFlags(settings.lflags)); 180 settings.lflags = null; 181 } 182 183 if (platform.platform.canFind("posix") && (settings.options & BuildOption.pic)) 184 settings.addDFlags("-fPIC"); 185 186 assert(fields & BuildSetting.dflags); 187 assert(fields & BuildSetting.copyFiles); 188 } 189 190 void extractBuildOptions(ref BuildSettings settings) const 191 { 192 Appender!(string[]) newflags; 193 next_flag: foreach (f; settings.dflags) { 194 foreach (t; s_options) 195 if (t[1].canFind(f)) { 196 settings.options |= t[0]; 197 continue next_flag; 198 } 199 if (f.startsWith("-version=")) settings.addVersions(f[9 .. $]); 200 else if (f.startsWith("-debug=")) settings.addDebugVersions(f[7 .. $]); 201 else newflags ~= f; 202 } 203 settings.dflags = newflags.data; 204 } 205 206 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) 207 const { 208 import std.conv: text; 209 assert(settings.targetName.length > 0, "No target name set."); 210 final switch (settings.targetType) { 211 case TargetType.autodetect: 212 assert(false, 213 text("Configurations must have a concrete target type, ", settings.targetName, 214 " has ", settings.targetType)); 215 case TargetType.none: return null; 216 case TargetType.sourceLibrary: return null; 217 case TargetType.executable: 218 if (platform.platform.canFind("windows")) 219 return settings.targetName ~ ".exe"; 220 else return settings.targetName.idup; 221 case TargetType.library: 222 case TargetType.staticLibrary: 223 if (platform.platform.canFind("windows")) 224 return settings.targetName ~ ".lib"; 225 else return "lib" ~ settings.targetName ~ ".a"; 226 case TargetType.dynamicLibrary: 227 if (platform.platform.canFind("windows")) 228 return settings.targetName ~ ".dll"; 229 else if (platform.platform.canFind("osx")) 230 return "lib" ~ settings.targetName ~ ".dylib"; 231 else return "lib" ~ settings.targetName ~ ".so"; 232 case TargetType.object: 233 if (platform.platform.canFind("windows")) 234 return settings.targetName ~ ".obj"; 235 else return settings.targetName ~ ".o"; 236 } 237 } 238 239 void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const 240 { 241 final switch (settings.targetType) { 242 case TargetType.autodetect: assert(false, "Invalid target type: autodetect"); 243 case TargetType.none: assert(false, "Invalid target type: none"); 244 case TargetType.sourceLibrary: assert(false, "Invalid target type: sourceLibrary"); 245 case TargetType.executable: break; 246 case TargetType.library: 247 case TargetType.staticLibrary: 248 settings.addDFlags("-lib"); 249 break; 250 case TargetType.dynamicLibrary: 251 if (platform.compiler != "dmd" || platform.platform.canFind("windows") || platform.platform.canFind("osx")) 252 settings.addDFlags("-shared"); 253 else 254 settings.prependDFlags("-shared", "-defaultlib=libphobos2.so"); 255 break; 256 case TargetType.object: 257 settings.addDFlags("-c"); 258 break; 259 } 260 261 if (tpath is null) 262 tpath = (NativePath(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); 263 settings.addDFlags("-of"~tpath); 264 } 265 266 void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) 267 { 268 auto res_file = getTempFile("dub-build", ".rsp"); 269 const(string)[] args = settings.dflags; 270 if (platform.frontendVersion >= 2066) args ~= "-vcolumns"; 271 std.file.write(res_file.toNativeString(), escapeArgs(args).join("\n")); 272 273 logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" ")); 274 invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback); 275 } 276 277 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) 278 { 279 import std.string; 280 auto tpath = NativePath(settings.targetPath) ~ getTargetFileName(settings, platform); 281 auto args = ["-of"~tpath.toNativeString()]; 282 args ~= objects; 283 args ~= settings.sourceFiles; 284 if (platform.platform.canFind("linux")) 285 args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being specified in the wrong order by DMD 286 args ~= lflagsToDFlags(settings.lflags); 287 args ~= settings.dflags.filter!(f => isLinkerDFlag(f)).array; 288 289 auto res_file = getTempFile("dub-build", ".lnk"); 290 std.file.write(res_file.toNativeString(), escapeArgs(args).join("\n")); 291 292 logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" ")); 293 invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback); 294 } 295 296 string[] lflagsToDFlags(in string[] lflags) const 297 { 298 return map!(f => "-L"~f)(lflags.filter!(f => f != "")()).array(); 299 } 300 301 private auto escapeArgs(in string[] args) 302 { 303 return args.map!(s => s.canFind(' ') ? "\""~s~"\"" : s); 304 } 305 306 private static bool isLinkerDFlag(string arg) 307 { 308 switch (arg) { 309 default: 310 if (arg.startsWith("-defaultlib=")) return true; 311 return false; 312 case "-g", "-gc", "-m32", "-m64", "-shared", "-lib", "-m32mscoff": 313 return true; 314 } 315 } 316 }