1 /** 2 Compiler settings and abstraction. 3 4 Copyright: © 2013-2016 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.compiler; 9 10 public import dub.compilers.buildsettings; 11 deprecated("Please `import dub.dependency : Dependency` instead") public import dub.dependency : Dependency; 12 public import dub.platform : BuildPlatform, matchesSpecification; 13 14 import dub.internal.vibecompat.inet.path; 15 import dub.internal.vibecompat.core.file; 16 17 import dub.internal.logging; 18 19 import std.algorithm; 20 import std.array; 21 import std.exception; 22 import std.process; 23 24 /// Exception thrown in Compiler.determinePlatform if the given architecture is 25 /// not supported. 26 class UnsupportedArchitectureException : Exception 27 { 28 this(string architecture, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) pure nothrow @safe 29 { 30 super("Unsupported architecture: "~architecture, file, line, nextInChain); 31 } 32 } 33 34 /// Exception thrown in getCompiler if no compiler matches the given name. 35 class UnknownCompilerException : Exception 36 { 37 this(string compilerName, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) pure nothrow @safe 38 { 39 super("Unknown compiler: "~compilerName, file, line, nextInChain); 40 } 41 } 42 43 /// Exception thrown in invokeTool and probePlatform if running the compiler 44 /// returned non-zero exit code. 45 class CompilerInvocationException : Exception 46 { 47 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) pure nothrow @safe 48 { 49 super(msg, file, line, nextInChain); 50 } 51 } 52 53 /** Returns a compiler handler for a given binary name. 54 55 The name will be compared against the canonical name of each registered 56 compiler handler. If no match is found, the sub strings "dmd", "gdc" and 57 "ldc", in this order, will be searched within the name. If this doesn't 58 yield a match either, an $(LREF UnknownCompilerException) will be thrown. 59 */ 60 Compiler getCompiler(string name) 61 { 62 foreach (c; s_compilers) 63 if (c.name == name) 64 return c; 65 66 // try to match names like gdmd or gdc-2.61 67 if (name.canFind("dmd")) return getCompiler("dmd"); 68 if (name.canFind("gdc")) return getCompiler("gdc"); 69 if (name.canFind("ldc")) return getCompiler("ldc"); 70 71 throw new UnknownCompilerException(name); 72 } 73 74 /** Registers a new compiler handler. 75 76 Note that by default `DMDCompiler`, `GDCCompiler` and `LDCCompiler` are 77 already registered at startup. 78 */ 79 void registerCompiler(Compiler c) 80 { 81 s_compilers ~= c; 82 } 83 84 85 interface Compiler { 86 /// Returns the canonical name of the compiler (e.g. "dmd"). 87 @property string name() const; 88 89 /** Determines the build platform properties given a set of build settings. 90 91 This will invoke the compiler to build a platform probe file, which 92 determines the target build platform's properties during compile-time. 93 94 See_Also: `dub.compilers.utils.generatePlatformProbeFile` 95 */ 96 BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null); 97 98 /// Replaces high level fields with low level fields and converts 99 /// dmd flags to compiler-specific flags 100 void prepareBuildSettings(ref BuildSettings settings, const scope ref BuildPlatform platform, BuildSetting supported_fields = BuildSetting.all) const; 101 102 /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field. 103 void extractBuildOptions(ref BuildSettings settings) const; 104 105 /// Computes the full file name of the generated binary. 106 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) const; 107 108 /// Adds the appropriate flag to set a target path 109 void setTarget(ref BuildSettings settings, in BuildPlatform platform, string targetPath = null) const; 110 111 /// Invokes the compiler using the given flags 112 deprecated("specify the working directory") 113 final void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) 114 { 115 invoke(settings, platform, output_callback, getWorkingDirectory()); 116 } 117 118 /// ditto 119 void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd); 120 121 /// Invokes the underlying linker directly 122 deprecated("specify the working directory") 123 final void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) 124 { 125 invokeLinker(settings, platform, objects, output_callback, getWorkingDirectory()); 126 } 127 128 /// ditto 129 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd); 130 131 /// Convert linker flags to compiler format 132 string[] lflagsToDFlags(const string[] lflags) const; 133 134 /// Determines compiler version 135 string determineVersion(string compiler_binary, string verboseOutput); 136 137 /** Runs a tool and provides common boilerplate code. 138 139 This method should be used by `Compiler` implementations to invoke the 140 compiler or linker binary. 141 */ 142 deprecated("specify the working directory") 143 protected final void invokeTool(string[] args, void delegate(int, string) output_callback, string[string] env = null) 144 { 145 invokeTool(args, output_callback, getWorkingDirectory(), env); 146 } 147 148 /// ditto 149 protected final void invokeTool(string[] args, void delegate(int, string) output_callback, NativePath cwd, string[string] env = null) 150 { 151 import std.string; 152 153 int status; 154 if (output_callback) { 155 auto result = executeShell(escapeShellCommand(args), 156 env, Config.none, size_t.max, cwd.toNativeString()); 157 output_callback(result.status, result.output); 158 status = result.status; 159 } else { 160 auto compiler_pid = spawnShell(escapeShellCommand(args), 161 env, Config.none, cwd.toNativeString()); 162 status = compiler_pid.wait(); 163 } 164 165 version (Posix) if (status == -9) { 166 throw new CompilerInvocationException( 167 format("%s failed with exit code %s. This may indicate that the process has run out of memory.", 168 args[0], status)); 169 } 170 enforce!CompilerInvocationException(status == 0, 171 format("%s failed with exit code %s.", args[0], status)); 172 } 173 174 /** Compiles platform probe file with the specified compiler and parses its output. 175 Params: 176 compiler_binary = binary to invoke compiler with 177 args = arguments for the probe compilation 178 arch_override = special handler for x86_mscoff 179 */ 180 protected final BuildPlatform probePlatform(string compiler_binary, string[] args, 181 string arch_override) 182 { 183 import dub.compilers.utils : generatePlatformProbeFile, readPlatformJsonProbe; 184 import std.string : format, strip; 185 186 auto fil = generatePlatformProbeFile(); 187 188 auto result = executeShell(escapeShellCommand(compiler_binary ~ args ~ fil.toNativeString())); 189 enforce!CompilerInvocationException(result.status == 0, 190 format("Failed to invoke the compiler %s to determine the build platform: %s", 191 compiler_binary, result.output)); 192 193 auto build_platform = readPlatformJsonProbe(result.output); 194 build_platform.compilerBinary = compiler_binary; 195 196 auto ver = determineVersion(compiler_binary, result.output) 197 .strip; 198 if (ver.empty) { 199 logWarn(`Could not probe the compiler version for "%s". ` ~ 200 `Toolchain requirements might be ineffective`, build_platform.compiler); 201 } 202 else { 203 build_platform.compilerVersion = ver; 204 } 205 206 // Skip the following check for LDC, emitting a warning if the specified `-arch` 207 // cmdline option does not lead to the same string being found among 208 // `build_platform.architecture`, as it's brittle and doesn't work with triples. 209 if (build_platform.compiler != "ldc") { 210 if (arch_override.length && !build_platform.architecture.canFind(arch_override) && 211 !(build_platform.compiler == "dmd" && arch_override.among("x86_omf", "x86_mscoff")) // Will be fixed in determinePlatform 212 ) { 213 logWarn(`Failed to apply the selected architecture %s. Got %s.`, 214 arch_override, build_platform.architecture); 215 } 216 } 217 218 return build_platform; 219 } 220 } 221 222 private { 223 Compiler[] s_compilers; 224 }