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 public import dub.dependency : Dependency; 12 public import dub.platform : BuildPlatform, matchesSpecification; 13 14 import dub.internal.vibecompat.core.file; 15 import dub.internal.vibecompat.core.log; 16 import dub.internal.vibecompat.data.json; 17 import dub.internal.vibecompat.inet.path; 18 import dub.recipe.packagerecipe : ToolchainRequirements; 19 20 import std.algorithm; 21 import std.array; 22 import std.conv; 23 import std.exception; 24 import std.process; 25 import std.typecons : Flag; 26 27 28 /** Returns a compiler handler for a given binary name. 29 30 The name will be compared against the canonical name of each registered 31 compiler handler. If no match is found, the sub strings "dmd", "gdc" and 32 "ldc", in this order, will be searched within the name. If this doesn't 33 yield a match either, an exception will be thrown. 34 */ 35 Compiler getCompiler(string name) 36 { 37 foreach (c; s_compilers) 38 if (c.name == name) 39 return c; 40 41 // try to match names like gdmd or gdc-2.61 42 if (name.canFind("dmd")) return getCompiler("dmd"); 43 if (name.canFind("gdc")) return getCompiler("gdc"); 44 if (name.canFind("ldc")) return getCompiler("ldc"); 45 46 throw new Exception("Unknown compiler: "~name); 47 } 48 49 /** Registers a new compiler handler. 50 51 Note that by default `DMDCompiler`, `GDCCompiler` and `LDCCompiler` are 52 already registered at startup. 53 */ 54 void registerCompiler(Compiler c) 55 { 56 s_compilers ~= c; 57 } 58 59 60 interface Compiler { 61 /// Returns the canonical name of the compiler (e.g. "dmd"). 62 @property string name() const; 63 64 /** Determines the build platform properties given a set of build settings. 65 66 This will invoke the compiler to build a platform probe file, which 67 determines the target build platform's properties during compile-time. 68 69 See_Also: `dub.compilers.utils.generatePlatformProbeFile` 70 */ 71 BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null); 72 73 /// Replaces high level fields with low level fields and converts 74 /// dmd flags to compiler-specific flags 75 void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all) const; 76 77 /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field. 78 void extractBuildOptions(ref BuildSettings settings) const; 79 80 /// Computes the full file name of the generated binary. 81 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) const; 82 83 /// Adds the appropriate flag to set a target path 84 void setTarget(ref BuildSettings settings, in BuildPlatform platform, string targetPath = null) const; 85 86 /// Invokes the compiler using the given flags 87 void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback); 88 89 /// Invokes the underlying linker directly 90 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback); 91 92 /// Convert linker flags to compiler format 93 string[] lflagsToDFlags(in string[] lflags) const; 94 95 /** Runs a tool and provides common boilerplate code. 96 97 This method should be used by `Compiler` implementations to invoke the 98 compiler or linker binary. 99 */ 100 protected final void invokeTool(string[] args, void delegate(int, string) output_callback) 101 { 102 import std..string; 103 104 int status; 105 if (output_callback) { 106 auto result = executeShell(escapeShellCommand(args)); 107 output_callback(result.status, result.output); 108 status = result.status; 109 } else { 110 auto compiler_pid = spawnShell(escapeShellCommand(args)); 111 status = compiler_pid.wait(); 112 } 113 114 version (Posix) if (status == -9) { 115 throw new Exception(format("%s failed with exit code %s. This may indicate that the process has run out of memory.", 116 args[0], status)); 117 } 118 enforce(status == 0, format("%s failed with exit code %s.", args[0], status)); 119 } 120 121 /** Compiles platform probe file with the specified compiler and parses its output. 122 Params: 123 compiler_binary = binary to invoke compiler with 124 args = arguments for the probe compilation 125 arch_override = special handler for x86_mscoff 126 versionRes = array of regular expressions to scan the output 127 and find the compiler version. For each, the 128 version must be in capture index 1. The output 129 is scanned in multi-line mode (i.e. ^ will match any line start) 130 */ 131 protected final BuildPlatform probePlatform(string compiler_binary, string[] args, 132 string arch_override, string[] versionRes) 133 { 134 import dub.compilers.utils : generatePlatformProbeFile, readPlatformJsonProbe; 135 import std.algorithm : filter, map; 136 import std.range : takeOne; 137 import std.regex : matchFirst, regex; 138 import std..string : format; 139 140 auto fil = generatePlatformProbeFile(); 141 142 auto result = executeShell(escapeShellCommand(compiler_binary ~ args ~ fil.toNativeString())); 143 enforce(result.status == 0, format("Failed to invoke the compiler %s to determine the build platform: %s", 144 compiler_binary, result.output)); 145 146 auto build_platform = readPlatformJsonProbe(result.output); 147 build_platform.compilerBinary = compiler_binary; 148 149 if (build_platform.compiler != this.name) { 150 logWarn(`The determined compiler type "%s" doesn't match the expected type "%s". `~ 151 `This will probably result in build errors.`, build_platform.compiler, this.name); 152 } 153 154 auto ver = versionRes 155 .map!(re => matchFirst(result.output, regex(re, "m"))) 156 .filter!(c => c.length > 1) 157 .map!(c => c[1]) 158 .takeOne(); 159 if (ver.empty) { 160 logWarn(`Could not probe the compiler version for "%s". ` ~ 161 `Toolchain requirements might be ineffective`, build_platform.compiler); 162 } 163 else { 164 build_platform.compilerVersion = ver.front; 165 } 166 167 // Hack: see #1059 168 // When compiling with --arch=x86_mscoff build_platform.architecture is equal to ["x86"] and canFind below is false. 169 // This hack prevents unnesessary warning 'Failed to apply the selected architecture x86_mscoff. Got ["x86"]'. 170 // And also makes "x86_mscoff" available as a platform specifier in the package recipe 171 if (arch_override == "x86_mscoff") 172 build_platform.architecture ~= arch_override; 173 if (arch_override.length && !build_platform.architecture.canFind(arch_override)) { 174 logWarn(`Failed to apply the selected architecture %s. Got %s.`, 175 arch_override, build_platform.architecture); 176 } 177 178 return build_platform; 179 } 180 } 181 182 private { 183 Compiler[] s_compilers; 184 }