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