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