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 public import dub.dependency : Dependency;
12 public import dub.platform : BuildPlatform, matchesSpecification;
13 
14 import dub.internal.vibecompat.core.file;
15 import dub.internal.vibecompat.core.log;
16 import dub.internal.vibecompat.data.json;
17 import dub.internal.vibecompat.inet.path;
18 import dub.recipe.packagerecipe : ToolchainRequirements;
19 
20 import std.algorithm;
21 import std.array;
22 import std.conv;
23 import std.exception;
24 import std.process;
25 import std.typecons : Flag;
26 
27 
28 /** Returns a compiler handler for a given binary name.
29 
30 	The name will be compared against the canonical name of each registered
31 	compiler handler. If no match is found, the sub strings "dmd", "gdc" and
32 	"ldc", in this order, will be searched within the name. If this doesn't
33 	yield a match either, an exception will be thrown.
34 */
35 Compiler getCompiler(string name)
36 {
37 	foreach (c; s_compilers)
38 		if (c.name == name)
39 			return c;
40 
41 	// try to match names like gdmd or gdc-2.61
42 	if (name.canFind("dmd")) return getCompiler("dmd");
43 	if (name.canFind("gdc")) return getCompiler("gdc");
44 	if (name.canFind("ldc")) return getCompiler("ldc");
45 
46 	throw new Exception("Unknown compiler: "~name);
47 }
48 
49 /** Registers a new compiler handler.
50 
51 	Note that by default `DMDCompiler`, `GDCCompiler` and `LDCCompiler` are
52 	already registered at startup.
53 */
54 void registerCompiler(Compiler c)
55 {
56 	s_compilers ~= c;
57 }
58 
59 
60 interface Compiler {
61 	/// Returns the canonical name of the compiler (e.g. "dmd").
62 	@property string name() const;
63 
64 	/** Determines the build platform properties given a set of build settings.
65 
66 		This will invoke the compiler to build a platform probe file, which
67 		determines the target build platform's properties during compile-time.
68 
69 		See_Also: `dub.compilers.utils.generatePlatformProbeFile`
70 	*/
71 	BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null);
72 
73 	/// Replaces high level fields with low level fields and converts
74 	/// dmd flags to compiler-specific flags
75 	void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all) const;
76 
77 	/// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field.
78 	void extractBuildOptions(ref BuildSettings settings) const;
79 
80 	/// Computes the full file name of the generated binary.
81 	string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) const;
82 
83 	/// Adds the appropriate flag to set a target path
84 	void setTarget(ref BuildSettings settings, in BuildPlatform platform, string targetPath = null) const;
85 
86 	/// Invokes the compiler using the given flags
87 	void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback);
88 
89 	/// Invokes the underlying linker directly
90 	void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback);
91 
92 	/// Convert linker flags to compiler format
93 	string[] lflagsToDFlags(in string[] lflags) const;
94 
95 	/** Runs a tool and provides common boilerplate code.
96 
97 		This method should be used by `Compiler` implementations to invoke the
98 		compiler or linker binary.
99 	*/
100 	protected final void invokeTool(string[] args, void delegate(int, string) output_callback)
101 	{
102 		import std..string;
103 
104 		int status;
105 		if (output_callback) {
106 			auto result = executeShell(escapeShellCommand(args));
107 			output_callback(result.status, result.output);
108 			status = result.status;
109 		} else {
110 			auto compiler_pid = spawnShell(escapeShellCommand(args));
111 			status = compiler_pid.wait();
112 		}
113 
114 		version (Posix) if (status == -9) {
115 			throw new Exception(format("%s failed with exit code %s. This may indicate that the process has run out of memory.",
116 				args[0], status));
117 		}
118 		enforce(status == 0, format("%s failed with exit code %s.", args[0], status));
119 	}
120 
121 	/** Compiles platform probe file with the specified compiler and parses its output.
122 		Params:
123 			compiler_binary =	binary to invoke compiler with
124 			args			=	arguments for the probe compilation
125 			arch_override	=	special handler for x86_mscoff
126 			versionRes		=	array of regular expressions to scan the output
127 								and find the compiler version. For each, the
128 								version must be in capture index 1. The output
129 								is scanned in multi-line mode (i.e. ^ will match any line start)
130 	*/
131 	protected final BuildPlatform probePlatform(string compiler_binary, string[] args,
132 		string arch_override, string[] versionRes)
133 	{
134 		import dub.compilers.utils : generatePlatformProbeFile, readPlatformJsonProbe;
135 		import std.algorithm : filter, map;
136 		import std.range : takeOne;
137 		import std.regex : matchFirst, regex;
138 		import std..string : format;
139 
140 		auto fil = generatePlatformProbeFile();
141 
142 		auto result = executeShell(escapeShellCommand(compiler_binary ~ args ~ fil.toNativeString()));
143 		enforce(result.status == 0, format("Failed to invoke the compiler %s to determine the build platform: %s",
144 				compiler_binary, result.output));
145 
146 		auto build_platform = readPlatformJsonProbe(result.output);
147 		build_platform.compilerBinary = compiler_binary;
148 
149 		if (build_platform.compiler != this.name) {
150 			logWarn(`The determined compiler type "%s" doesn't match the expected type "%s". `~
151 				`This will probably result in build errors.`, build_platform.compiler, this.name);
152 		}
153 
154 		auto ver = versionRes
155 			.map!(re => matchFirst(result.output, regex(re, "m")))
156 			.filter!(c => c.length > 1)
157 			.map!(c => c[1])
158 			.takeOne();
159 		if (ver.empty) {
160 			logWarn(`Could not probe the compiler version for "%s". ` ~
161 				`Toolchain requirements might be ineffective`, build_platform.compiler);
162 		}
163 		else {
164 			build_platform.compilerVersion = ver.front;
165 		}
166 
167 		// Hack: see #1059
168 		// When compiling with --arch=x86_mscoff build_platform.architecture is equal to ["x86"] and canFind below is false.
169 		// This hack prevents unnesessary warning 'Failed to apply the selected architecture x86_mscoff. Got ["x86"]'.
170 		// And also makes "x86_mscoff" available as a platform specifier in the package recipe
171 		if (arch_override == "x86_mscoff")
172 			build_platform.architecture ~= arch_override;
173 		if (arch_override.length && !build_platform.architecture.canFind(arch_override)) {
174 			logWarn(`Failed to apply the selected architecture %s. Got %s.`,
175 				arch_override, build_platform.architecture);
176 		}
177 
178 		return build_platform;
179 	}
180 }
181 
182 private {
183 	Compiler[] s_compilers;
184 }