1 /** 2 GDC 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.gdc; 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 GDCCompiler : Compiler { 27 private static immutable s_options = [ 28 tuple(BuildOption.debugMode, ["-fdebug"]), 29 tuple(BuildOption.releaseMode, ["-frelease"]), 30 tuple(BuildOption.coverage, ["-fprofile-arcs", "-ftest-coverage"]), 31 tuple(BuildOption.debugInfo, ["-g"]), 32 tuple(BuildOption.debugInfoC, ["-g", "-fdebug-c"]), 33 //tuple(BuildOption.alwaysStackFrame, ["-X"]), 34 //tuple(BuildOption.stackStomping, ["-X"]), 35 tuple(BuildOption.inline, ["-finline-functions"]), 36 tuple(BuildOption.noBoundsCheck, ["-fno-bounds-check"]), 37 tuple(BuildOption.optimize, ["-O3"]), 38 tuple(BuildOption.profile, ["-pg"]), 39 tuple(BuildOption.unittests, ["-funittest"]), 40 tuple(BuildOption.verbose, ["-fd-verbose"]), 41 tuple(BuildOption.ignoreUnknownPragmas, ["-fignore-unknown-pragmas"]), 42 tuple(BuildOption.syntaxOnly, ["-fsyntax-only"]), 43 tuple(BuildOption.warnings, ["-Wall"]), 44 tuple(BuildOption.warningsAsErrors, ["-Werror", "-Wall"]), 45 tuple(BuildOption.ignoreDeprecations, ["-Wno-deprecated"]), 46 tuple(BuildOption.deprecationWarnings, ["-Wdeprecated"]), 47 tuple(BuildOption.deprecationErrors, ["-Werror", "-Wdeprecated"]), 48 tuple(BuildOption.property, ["-fproperty"]), 49 //tuple(BuildOption.profileGC, ["-?"]), 50 51 tuple(BuildOption._docs, ["-fdoc-dir=docs"]), 52 tuple(BuildOption._ddox, ["-fXf=docs.json", "-fdoc-file=__dummy.html"]), 53 ]; 54 55 @property string name() const { return "gdc"; } 56 57 enum gdcVersionRe1 = `GDC\s+(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; 58 enum gdcVersionRe2 = `^GNU D \(.*?\) version (\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; 59 enum gdcVersionRe3 = `^gcc version (\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; 60 61 unittest { 62 import std.algorithm : equal, map; 63 import std.range : only; 64 import std.regex : matchFirst, regex; 65 auto probe = ` 66 Target: x86_64-pc-linux-gnu 67 Thread model: posix 68 gcc version 8.2.1 20180831 (GDC 8.2.1 based on D v2.068.2 built with ISL 0.20 for Arch Linux) 69 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 70 GNU D (GDC 8.2.1 based on D v2.068.2 built with ISL 0.20 for Arch Linux) version 8.2.1 20180831 (x86_64-pc-linux-gnu) 71 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 72 binary /usr/lib/gcc/x86_64-pc-linux-gnu/8.2.1/cc1d 73 version v2.068.2 74 predefs GNU D_Version2 LittleEndian GNU_DWARF2_Exceptions GNU_StackGrowsDown GNU_InlineAsm D_LP64 D_PIC assert all X86_64 D_HardFloat Posix linux CRuntime_Glibc 75 `; 76 auto vers = only(gdcVersionRe1, gdcVersionRe2, gdcVersionRe3) 77 .map!(re => matchFirst(probe, regex(re, "m"))) 78 .filter!(c => c && c.length > 1) 79 .map!(c => c[1]); 80 81 assert(vers.equal([ "8.2.1", "8.2.1", "8.2.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 "arm": arch_flags = ["-marm"]; break; 91 case "arm_thumb": arch_flags = ["-mthumb"]; break; 92 case "x86": arch_flags = ["-m32"]; break; 93 case "x86_64": arch_flags = ["-m64"]; break; 94 } 95 settings.addDFlags(arch_flags); 96 97 return probePlatform( 98 compiler_binary, 99 arch_flags ~ ["-S", "-v"], 100 arch_override, 101 [ gdcVersionRe1, gdcVersionRe2, gdcVersionRe3 ] 102 ); 103 } 104 105 void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const 106 { 107 enforceBuildRequirements(settings); 108 109 if (!(fields & BuildSetting.options)) { 110 foreach (t; s_options) 111 if (settings.options & t[0]) 112 settings.addDFlags(t[1]); 113 } 114 115 if (!(fields & BuildSetting.versions)) { 116 settings.addDFlags(settings.versions.map!(s => "-fversion="~s)().array()); 117 settings.versions = null; 118 } 119 120 if (!(fields & BuildSetting.debugVersions)) { 121 settings.addDFlags(settings.debugVersions.map!(s => "-fdebug="~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); 142 settings.addDFlags(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("-fPIC"); 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("-fversion=")) settings.addVersions(f[10 .. $]); 167 else if (f.startsWith("-fdebug=")) settings.addDebugVersions(f[8 .. $]); 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 final switch (settings.targetType) { 177 case TargetType.autodetect: assert(false, "Configurations must have a concrete target type."); 178 case TargetType.none: return null; 179 case TargetType.sourceLibrary: return null; 180 case TargetType.executable: 181 if (platform.platform.canFind("windows")) 182 return settings.targetName ~ ".exe"; 183 else return settings.targetName; 184 case TargetType.library: 185 case TargetType.staticLibrary: 186 return "lib" ~ settings.targetName ~ ".a"; 187 case TargetType.dynamicLibrary: 188 if (platform.platform.canFind("windows")) 189 return settings.targetName ~ ".dll"; 190 else if (platform.platform.canFind("osx")) 191 return "lib" ~ settings.targetName ~ ".dylib"; 192 else return "lib" ~ settings.targetName ~ ".so"; 193 case TargetType.object: 194 if (platform.platform.canFind("windows")) 195 return settings.targetName ~ ".obj"; 196 else return settings.targetName ~ ".o"; 197 } 198 } 199 200 void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const 201 { 202 final switch (settings.targetType) { 203 case TargetType.autodetect: assert(false, "Invalid target type: autodetect"); 204 case TargetType.none: assert(false, "Invalid target type: none"); 205 case TargetType.sourceLibrary: assert(false, "Invalid target type: sourceLibrary"); 206 case TargetType.executable: break; 207 case TargetType.library: 208 case TargetType.staticLibrary: 209 case TargetType.object: 210 settings.addDFlags("-c"); 211 break; 212 case TargetType.dynamicLibrary: 213 settings.addDFlags("-shared", "-fPIC"); 214 break; 215 } 216 217 if (tpath is null) 218 tpath = (NativePath(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); 219 settings.addDFlags("-o", tpath); 220 } 221 222 void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) 223 { 224 auto res_file = getTempFile("dub-build", ".rsp"); 225 std.file.write(res_file.toNativeString(), join(settings.dflags.map!(s => escape(s)), "\n")); 226 227 logDiagnostic("%s %s", platform.compilerBinary, join(cast(string[])settings.dflags, " ")); 228 invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback); 229 } 230 231 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) 232 { 233 import std..string; 234 string[] args; 235 // As the user is supposed to call setTarget prior to invoke, -o target is already set. 236 if (settings.targetType == TargetType.staticLibrary || settings.targetType == TargetType.staticLibrary) { 237 auto tpath = extractTarget(settings.dflags); 238 assert(tpath !is null, "setTarget should be called before invoke"); 239 args = [ "ar", "rcs", tpath ] ~ objects; 240 } else { 241 args = platform.compilerBinary ~ objects ~ settings.sourceFiles ~ settings.lflags ~ settings.dflags.filter!(f => isLinkageFlag(f)).array; 242 version(linux) args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being specified in the wrong order by DMD 243 } 244 logDiagnostic("%s", args.join(" ")); 245 invokeTool(args, output_callback); 246 } 247 248 string[] lflagsToDFlags(in string[] lflags) const 249 { 250 string[] dflags; 251 foreach( f; lflags ) 252 { 253 dflags ~= "-Xlinker"; 254 dflags ~= f; 255 } 256 257 return dflags; 258 } 259 } 260 261 private string extractTarget(const string[] args) { auto i = args.countUntil("-o"); return i >= 0 ? args[i+1] : null; } 262 263 private bool isLinkageFlag(string flag) { 264 switch (flag) { 265 case "-c": 266 return false; 267 default: 268 return true; 269 } 270 } 271 272 private string escape(string str) 273 { 274 auto ret = appender!string(); 275 foreach (char ch; str) { 276 switch (ch) { 277 default: ret.put(ch); break; 278 case '\\': ret.put(`\\`); break; 279 case ' ': ret.put(`\ `); break; 280 } 281 } 282 return ret.data; 283 }