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