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 26 class DMDCompiler : Compiler { 27 private static immutable s_options = [ 28 tuple(BuildOption.debugMode, ["-debug"]), 29 tuple(BuildOption.releaseMode, ["-release"]), 30 tuple(BuildOption.coverage, ["-cov"]), 31 tuple(BuildOption.debugInfo, ["-g"]), 32 tuple(BuildOption.debugInfoC, ["-g"]), 33 tuple(BuildOption.alwaysStackFrame, ["-gs"]), 34 tuple(BuildOption.stackStomping, ["-gx"]), 35 tuple(BuildOption.inline, ["-inline"]), 36 tuple(BuildOption.noBoundsCheck, ["-noboundscheck"]), 37 tuple(BuildOption.optimize, ["-O"]), 38 tuple(BuildOption.profile, ["-profile"]), 39 tuple(BuildOption.unittests, ["-unittest"]), 40 tuple(BuildOption.verbose, ["-v"]), 41 tuple(BuildOption.ignoreUnknownPragmas, ["-ignore"]), 42 tuple(BuildOption.syntaxOnly, ["-o-"]), 43 tuple(BuildOption.warnings, ["-wi"]), 44 tuple(BuildOption.warningsAsErrors, ["-w"]), 45 tuple(BuildOption.ignoreDeprecations, ["-d"]), 46 tuple(BuildOption.deprecationWarnings, ["-dw"]), 47 tuple(BuildOption.deprecationErrors, ["-de"]), 48 tuple(BuildOption.property, ["-property"]), 49 tuple(BuildOption.profileGC, ["-profile=gc"]), 50 tuple(BuildOption.betterC, ["-betterC"]), 51 52 tuple(BuildOption._docs, ["-Dddocs"]), 53 tuple(BuildOption._ddox, ["-Xfdocs.json", "-Df__dummy.html"]), 54 ]; 55 56 @property string name() const { return "dmd"; } 57 58 enum dmdVersionRe = `^version\s+v?(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; 59 60 unittest { 61 import std.regex : matchFirst, regex; 62 auto probe = ` 63 binary dmd 64 version v2.082.0 65 config /etc/dmd.conf 66 `; 67 auto re = regex(dmdVersionRe, "m"); 68 auto c = matchFirst(probe, re); 69 assert(c && c.length > 1 && c[1] == "2.082.0"); 70 } 71 72 unittest { 73 import std.regex : matchFirst, regex; 74 auto probe = ` 75 binary dmd 76 version v2.084.0-beta.1 77 config /etc/dmd.conf 78 `; 79 auto re = regex(dmdVersionRe, "m"); 80 auto c = matchFirst(probe, re); 81 assert(c && c.length > 1 && c[1] == "2.084.0-beta.1"); 82 } 83 84 BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override) 85 { 86 string[] arch_flags; 87 switch (arch_override) { 88 default: throw new Exception("Unsupported architecture: "~arch_override); 89 case "": break; 90 case "x86": arch_flags = ["-m32"]; break; 91 case "x86_64": arch_flags = ["-m64"]; break; 92 case "x86_mscoff": arch_flags = ["-m32mscoff"]; break; 93 } 94 settings.addDFlags(arch_flags); 95 96 return probePlatform( 97 compiler_binary, 98 arch_flags ~ ["-quiet", "-c", "-o-", "-v"], 99 arch_override, 100 [ dmdVersionRe ] 101 ); 102 } 103 104 void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const 105 { 106 enforceBuildRequirements(settings); 107 108 if (!(fields & BuildSetting.options)) { 109 foreach (t; s_options) 110 if (settings.options & t[0]) 111 settings.addDFlags(t[1]); 112 } 113 114 if (!(fields & BuildSetting.versions)) { 115 settings.addDFlags(settings.versions.map!(s => "-version="~s)().array()); 116 settings.versions = null; 117 } 118 119 if (!(fields & BuildSetting.debugVersions)) { 120 settings.addDFlags(settings.debugVersions.map!(s => "-debug="~s)().array()); 121 settings.debugVersions = null; 122 } 123 124 if (!(fields & BuildSetting.importPaths)) { 125 settings.addDFlags(settings.importPaths.map!(s => "-I"~s)().array()); 126 settings.importPaths = null; 127 } 128 129 if (!(fields & BuildSetting.stringImportPaths)) { 130 settings.addDFlags(settings.stringImportPaths.map!(s => "-J"~s)().array()); 131 settings.stringImportPaths = null; 132 } 133 134 if (!(fields & BuildSetting.libs)) { 135 resolveLibs(settings); 136 version(Windows) settings.addSourceFiles(settings.libs.map!(l => l~".lib")().array()); 137 else settings.addLFlags(settings.libs.map!(l => "-l"~l)().array()); 138 } 139 140 if (!(fields & BuildSetting.sourceFiles)) { 141 settings.addDFlags(settings.sourceFiles); 142 settings.sourceFiles = null; 143 } 144 145 if (!(fields & BuildSetting.lflags)) { 146 settings.addDFlags(lflagsToDFlags(settings.lflags)); 147 settings.lflags = null; 148 } 149 150 version (Posix) { 151 if (settings.options & BuildOption.pic) 152 settings.addDFlags("-fPIC"); 153 } 154 155 assert(fields & BuildSetting.dflags); 156 assert(fields & BuildSetting.copyFiles); 157 } 158 159 void extractBuildOptions(ref BuildSettings settings) const 160 { 161 Appender!(string[]) newflags; 162 next_flag: foreach (f; settings.dflags) { 163 foreach (t; s_options) 164 if (t[1].canFind(f)) { 165 settings.options |= t[0]; 166 continue next_flag; 167 } 168 if (f.startsWith("-version=")) settings.addVersions(f[9 .. $]); 169 else if (f.startsWith("-debug=")) settings.addDebugVersions(f[7 .. $]); 170 else newflags ~= f; 171 } 172 settings.dflags = newflags.data; 173 } 174 175 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) 176 const { 177 import std.conv: text; 178 assert(settings.targetName.length > 0, "No target name set."); 179 final switch (settings.targetType) { 180 case TargetType.autodetect: 181 assert(false, 182 text("Configurations must have a concrete target type, ", settings.targetName, 183 " has ", settings.targetType)); 184 case TargetType.none: return null; 185 case TargetType.sourceLibrary: return null; 186 case TargetType.executable: 187 if (platform.platform.canFind("windows")) 188 return settings.targetName ~ ".exe"; 189 else return settings.targetName; 190 case TargetType.library: 191 case TargetType.staticLibrary: 192 if (platform.platform.canFind("windows")) 193 return settings.targetName ~ ".lib"; 194 else return "lib" ~ settings.targetName ~ ".a"; 195 case TargetType.dynamicLibrary: 196 if (platform.platform.canFind("windows")) 197 return settings.targetName ~ ".dll"; 198 else if (platform.platform.canFind("osx")) 199 return "lib" ~ settings.targetName ~ ".dylib"; 200 else return "lib" ~ settings.targetName ~ ".so"; 201 case TargetType.object: 202 if (platform.platform.canFind("windows")) 203 return settings.targetName ~ ".obj"; 204 else return settings.targetName ~ ".o"; 205 } 206 } 207 208 void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const 209 { 210 final switch (settings.targetType) { 211 case TargetType.autodetect: assert(false, "Invalid target type: autodetect"); 212 case TargetType.none: assert(false, "Invalid target type: none"); 213 case TargetType.sourceLibrary: assert(false, "Invalid target type: sourceLibrary"); 214 case TargetType.executable: break; 215 case TargetType.library: 216 case TargetType.staticLibrary: 217 settings.addDFlags("-lib"); 218 break; 219 case TargetType.dynamicLibrary: 220 version (Windows) settings.addDFlags("-shared"); 221 else version (OSX) settings.addDFlags("-shared"); 222 else settings.prependDFlags("-shared", "-defaultlib=libphobos2.so"); 223 break; 224 case TargetType.object: 225 settings.addDFlags("-c"); 226 break; 227 } 228 229 if (tpath is null) 230 tpath = (NativePath(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); 231 settings.addDFlags("-of"~tpath); 232 } 233 234 void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) 235 { 236 auto res_file = getTempFile("dub-build", ".rsp"); 237 const(string)[] args = settings.dflags; 238 if (platform.frontendVersion >= 2066) args ~= "-vcolumns"; 239 std.file.write(res_file.toNativeString(), escapeArgs(args).join("\n")); 240 241 logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" ")); 242 invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback); 243 } 244 245 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) 246 { 247 import std..string; 248 auto tpath = NativePath(settings.targetPath) ~ getTargetFileName(settings, platform); 249 auto args = ["-of"~tpath.toNativeString()]; 250 args ~= objects; 251 args ~= settings.sourceFiles; 252 version(linux) args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being specified in the wrong order by DMD 253 args ~= lflagsToDFlags(settings.lflags); 254 args ~= settings.dflags.filter!(f => isLinkerDFlag(f)).array; 255 256 auto res_file = getTempFile("dub-build", ".lnk"); 257 std.file.write(res_file.toNativeString(), escapeArgs(args).join("\n")); 258 259 logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" ")); 260 invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback); 261 } 262 263 string[] lflagsToDFlags(in string[] lflags) const 264 { 265 return lflags.map!(f => "-L"~f)().array(); 266 } 267 268 private auto escapeArgs(in string[] args) 269 { 270 return args.map!(s => s.canFind(' ') ? "\""~s~"\"" : s); 271 } 272 273 private static bool isLinkerDFlag(string arg) 274 { 275 switch (arg) { 276 default: 277 if (arg.startsWith("-defaultlib=")) return true; 278 return false; 279 case "-g", "-gc", "-m32", "-m64", "-shared", "-lib", "-m32mscoff": 280 return true; 281 } 282 } 283 }