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 }