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.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 23 class LDCCompiler : Compiler { 24 private static immutable s_options = [ 25 tuple(BuildOption.debugMode, ["-d-debug"]), 26 tuple(BuildOption.releaseMode, ["-release"]), 27 tuple(BuildOption.coverage, ["-cov"]), 28 tuple(BuildOption.coverageCTFE, ["-cov=ctfe"]), 29 tuple(BuildOption.debugInfo, ["-g"]), 30 tuple(BuildOption.debugInfoC, ["-gc"]), 31 tuple(BuildOption.alwaysStackFrame, ["-disable-fp-elim"]), 32 //tuple(BuildOption.stackStomping, ["-?"]), 33 tuple(BuildOption.inline, ["-enable-inlining", "-Hkeep-all-bodies"]), 34 tuple(BuildOption.noBoundsCheck, ["-boundscheck=off"]), 35 tuple(BuildOption.optimize, ["-O3"]), 36 tuple(BuildOption.profile, ["-fdmd-trace-functions"]), 37 tuple(BuildOption.unittests, ["-unittest"]), 38 tuple(BuildOption.verbose, ["-v"]), 39 tuple(BuildOption.ignoreUnknownPragmas, ["-ignore"]), 40 tuple(BuildOption.syntaxOnly, ["-o-"]), 41 tuple(BuildOption.warnings, ["-wi"]), 42 tuple(BuildOption.warningsAsErrors, ["-w"]), 43 tuple(BuildOption.ignoreDeprecations, ["-d"]), 44 tuple(BuildOption.deprecationWarnings, ["-dw"]), 45 tuple(BuildOption.deprecationErrors, ["-de"]), 46 tuple(BuildOption.property, ["-property"]), 47 //tuple(BuildOption.profileGC, ["-?"]), 48 tuple(BuildOption.betterC, ["-betterC"]), 49 tuple(BuildOption.lowmem, ["-lowmem"]), 50 tuple(BuildOption.color, ["-enable-color"]), 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_mscoff": arch_flags = ["-march=x86"]; break; 86 case "x86_64": arch_flags = ["-march=x86-64"]; break; 87 case "aarch64": arch_flags = ["-march=aarch64"]; break; 88 case "powerpc64": arch_flags = ["-march=powerpc64"]; break; 89 default: 90 if (arch_override.canFind('-')) 91 arch_flags = ["-mtriple="~arch_override]; 92 else 93 throw new UnsupportedArchitectureException(arch_override); 94 break; 95 } 96 settings.addDFlags(arch_flags); 97 98 return probePlatform( 99 compiler_binary, 100 arch_flags ~ ["-c", "-o-", "-v"], 101 arch_override 102 ); 103 } 104 105 void prepareBuildSettings(ref BuildSettings settings, const scope ref BuildPlatform platform, BuildSetting fields = BuildSetting.all) const 106 { 107 enforceBuildRequirements(settings); 108 109 // Keep the current dflags at the end of the array so that they will overwrite other flags. 110 // This allows user $DFLAGS to modify flags added by us. 111 const dflagsTail = settings.dflags; 112 settings.dflags = []; 113 114 if (!(fields & BuildSetting.options)) { 115 foreach (t; s_options) 116 if (settings.options & t[0]) 117 settings.addDFlags(t[1]); 118 } 119 120 if (!(fields & BuildSetting.versions)) { 121 settings.addDFlags(settings.versions.map!(s => "-d-version="~s)().array()); 122 settings.versions = null; 123 } 124 125 if (!(fields & BuildSetting.debugVersions)) { 126 settings.addDFlags(settings.debugVersions.map!(s => "-d-debug="~s)().array()); 127 settings.debugVersions = null; 128 } 129 130 if (!(fields & BuildSetting.importPaths)) { 131 settings.addDFlags(settings.importPaths.map!(s => "-I"~s)().array()); 132 settings.importPaths = null; 133 } 134 135 if (!(fields & BuildSetting.cImportPaths)) { 136 settings.addDFlags(settings.cImportPaths.map!(s => "-I"~s)().array()); 137 settings.cImportPaths = null; 138 } 139 140 if (!(fields & BuildSetting.stringImportPaths)) { 141 settings.addDFlags(settings.stringImportPaths.map!(s => "-J"~s)().array()); 142 settings.stringImportPaths = null; 143 } 144 145 if (!(fields & BuildSetting.sourceFiles)) { 146 settings.addDFlags(settings.sourceFiles); 147 settings.sourceFiles = null; 148 } 149 150 if (!(fields & BuildSetting.libs)) { 151 resolveLibs(settings, platform); 152 settings.addLFlags(settings.libs.map!(l => "-l"~l)().array()); 153 } 154 155 if (!(fields & BuildSetting.lflags)) { 156 settings.addDFlags(lflagsToDFlags(settings.lflags)); 157 settings.lflags = null; 158 } 159 160 if (settings.options & BuildOption.pic) { 161 if (platform.isWindows()) { 162 /* This has nothing to do with PIC, but as the PIC option is exclusively 163 * set internally for code that ends up in a dynamic library, explicitly 164 * specify what `-shared` defaults to (`-shared` can't be used when 165 * compiling only, without linking). 166 * *Pre*pending the flags enables the user to override them. 167 */ 168 settings.prependDFlags("-fvisibility=public", "-dllimport=all"); 169 } else { 170 settings.addDFlags("-relocation-model=pic"); 171 } 172 } 173 174 settings.addDFlags(dflagsTail); 175 176 assert(fields & BuildSetting.dflags); 177 assert(fields & BuildSetting.copyFiles); 178 } 179 180 void extractBuildOptions(ref BuildSettings settings) const 181 { 182 Appender!(string[]) newflags; 183 next_flag: foreach (f; settings.dflags) { 184 foreach (t; s_options) 185 if (t[1].canFind(f)) { 186 settings.options |= t[0]; 187 continue next_flag; 188 } 189 if (f.startsWith("-d-version=")) settings.addVersions(f[11 .. $]); 190 else if (f.startsWith("-d-debug=")) settings.addDebugVersions(f[9 .. $]); 191 else newflags ~= f; 192 } 193 settings.dflags = newflags.data; 194 } 195 196 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) 197 const { 198 assert(settings.targetName.length > 0, "No target name set."); 199 200 const p = platform.platform; 201 final switch (settings.targetType) { 202 case TargetType.autodetect: assert(false, "Configurations must have a concrete target type."); 203 case TargetType.none: return null; 204 case TargetType.sourceLibrary: return null; 205 case TargetType.executable: 206 if (p.canFind("windows")) 207 return settings.targetName ~ ".exe"; 208 else if (p.canFind("wasm")) 209 return settings.targetName ~ ".wasm"; 210 else return settings.targetName.idup; 211 case TargetType.library: 212 case TargetType.staticLibrary: 213 if (p.canFind("windows") && !p.canFind("mingw")) 214 return settings.targetName ~ ".lib"; 215 else return "lib" ~ settings.targetName ~ ".a"; 216 case TargetType.dynamicLibrary: 217 if (p.canFind("windows")) 218 return settings.targetName ~ ".dll"; 219 else if (p.canFind("darwin")) 220 return "lib" ~ settings.targetName ~ ".dylib"; 221 else return "lib" ~ settings.targetName ~ ".so"; 222 case TargetType.object: 223 if (p.canFind("windows")) 224 return settings.targetName ~ ".obj"; 225 else return settings.targetName ~ ".o"; 226 } 227 } 228 229 void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const 230 { 231 const targetFileName = getTargetFileName(settings, platform); 232 233 final switch (settings.targetType) { 234 case TargetType.autodetect: assert(false, "Invalid target type: autodetect"); 235 case TargetType.none: assert(false, "Invalid target type: none"); 236 case TargetType.sourceLibrary: assert(false, "Invalid target type: sourceLibrary"); 237 case TargetType.executable: break; 238 case TargetType.library: 239 case TargetType.staticLibrary: 240 // -oq: name object files uniquely (so the files don't collide) 241 settings.addDFlags("-lib", "-oq"); 242 // -cleanup-obj (supported since LDC v1.1): remove object files after archiving to static lib 243 if (platform.frontendVersion >= 2071) { 244 settings.addDFlags("-cleanup-obj"); 245 } 246 if (platform.frontendVersion < 2095) { 247 // Since LDC v1.25, -cleanup-obj defaults to a unique temp -od directory 248 // We need to resort to a unique-ish -od directory before that 249 settings.addDFlags("-od=" ~ settings.targetPath ~ "/obj"); 250 } 251 break; 252 case TargetType.dynamicLibrary: 253 settings.addDFlags("-shared"); 254 addDynamicLibName(settings, platform, targetFileName); 255 break; 256 case TargetType.object: 257 settings.addDFlags("-c"); 258 break; 259 } 260 261 if (tpath is null) 262 tpath = (NativePath(settings.targetPath) ~ targetFileName).toNativeString(); 263 settings.addDFlags("-of"~tpath); 264 } 265 266 void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd) 267 { 268 auto res_file = getTempFile("dub-build", ".rsp"); 269 const(string)[] args = settings.dflags; 270 if (platform.frontendVersion >= 2066) args ~= "-vcolumns"; 271 writeFile(res_file, escapeArgs(args).join("\n")); 272 273 logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" ")); 274 string[string] env; 275 foreach (aa; [settings.environments, settings.buildEnvironments]) 276 foreach (k, v; aa) 277 env[k] = v; 278 invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback, cwd, env); 279 } 280 281 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd) 282 { 283 import std.string; 284 auto tpath = NativePath(settings.targetPath) ~ getTargetFileName(settings, platform); 285 auto args = ["-of"~tpath.toNativeString()]; 286 args ~= objects; 287 args ~= settings.sourceFiles; 288 if (platform.platform.canFind("linux")) 289 args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being specified in the wrong order 290 args ~= lflagsToDFlags(settings.lflags); 291 args ~= settings.dflags.filter!(f => isLinkerDFlag(f)).array; 292 293 auto res_file = getTempFile("dub-build", ".lnk"); 294 writeFile(res_file, escapeArgs(args).join("\n")); 295 296 logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" ")); 297 string[string] env; 298 foreach (aa; [settings.environments, settings.buildEnvironments]) 299 foreach (k, v; aa) 300 env[k] = v; 301 invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback, cwd, env); 302 } 303 304 string[] lflagsToDFlags(const string[] lflags) const 305 { 306 return map!(f => "-L"~f)(lflags.filter!(f => f != "")()).array(); 307 } 308 309 private auto escapeArgs(in string[] args) 310 { 311 return args.map!(s => s.canFind(' ') ? "\""~s~"\"" : s); 312 } 313 314 static bool isLinkerDFlag(string arg) 315 { 316 if (arg.length > 2 && arg.startsWith("--")) 317 arg = arg[1 .. $]; // normalize to 1 leading hyphen 318 319 switch (arg) { 320 case "-g", "-gc", "-m32", "-m64", "-shared", "-lib", 321 "-betterC", "-disable-linker-strip-dead", "-static": 322 return true; 323 default: 324 return arg.startsWith("-L") 325 || arg.startsWith("-Xcc=") 326 || arg.startsWith("-defaultlib=") 327 || arg.startsWith("-platformlib=") 328 || arg.startsWith("-flto") 329 || arg.startsWith("-fsanitize=") 330 || arg.startsWith("-gcc=") 331 || arg.startsWith("-link-") 332 || arg.startsWith("-linker=") 333 || arg.startsWith("-march=") 334 || arg.startsWith("-mscrtlib=") 335 || arg.startsWith("-mtriple="); 336 } 337 } 338 }