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