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