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 }