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, in ref BuildPlatform platform, 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 	/// Determines compiler version
96 	string determineVersion(string compiler_binary, string verboseOutput);
97 
98 	/** Runs a tool and provides common boilerplate code.
99 
100 		This method should be used by `Compiler` implementations to invoke the
101 		compiler or linker binary.
102 	*/
103 	protected final void invokeTool(string[] args, void delegate(int, string) output_callback)
104 	{
105 		import std.string;
106 
107 		int status;
108 		if (output_callback) {
109 			auto result = executeShell(escapeShellCommand(args));
110 			output_callback(result.status, result.output);
111 			status = result.status;
112 		} else {
113 			auto compiler_pid = spawnShell(escapeShellCommand(args));
114 			status = compiler_pid.wait();
115 		}
116 
117 		version (Posix) if (status == -9) {
118 			throw new Exception(format("%s failed with exit code %s. This may indicate that the process has run out of memory.",
119 				args[0], status));
120 		}
121 		enforce(status == 0, format("%s failed with exit code %s.", args[0], status));
122 	}
123 
124 	/** Compiles platform probe file with the specified compiler and parses its output.
125 		Params:
126 			compiler_binary =	binary to invoke compiler with
127 			args			=	arguments for the probe compilation
128 			arch_override	=	special handler for x86_mscoff
129 	*/
130 	protected final BuildPlatform probePlatform(string compiler_binary, string[] args,
131 		string arch_override)
132 	{
133 		import dub.compilers.utils : generatePlatformProbeFile, readPlatformJsonProbe;
134 		import std.string : format, strip;
135 
136 		auto fil = generatePlatformProbeFile();
137 
138 		auto result = executeShell(escapeShellCommand(compiler_binary ~ args ~ fil.toNativeString()));
139 		enforce(result.status == 0, format("Failed to invoke the compiler %s to determine the build platform: %s",
140 				compiler_binary, result.output));
141 
142 		auto build_platform = readPlatformJsonProbe(result.output);
143 		build_platform.compilerBinary = compiler_binary;
144 
145 		auto ver = determineVersion(compiler_binary, result.output)
146 			.strip;
147 		if (ver.empty) {
148 			logWarn(`Could not probe the compiler version for "%s". ` ~
149 				`Toolchain requirements might be ineffective`, build_platform.compiler);
150 		}
151 		else {
152 			build_platform.compilerVersion = ver;
153 		}
154 
155 		// Hack: see #1059
156 		// When compiling with --arch=x86_mscoff build_platform.architecture is equal to ["x86"] and canFind below is false.
157 		// This hack prevents unnesessary warning 'Failed to apply the selected architecture x86_mscoff. Got ["x86"]'.
158 		// And also makes "x86_mscoff" available as a platform specifier in the package recipe
159 		if (arch_override == "x86_mscoff")
160 			build_platform.architecture ~= arch_override;
161 		if (arch_override.length && !build_platform.architecture.canFind(arch_override)) {
162 			logWarn(`Failed to apply the selected architecture %s. Got %s.`,
163 				arch_override, build_platform.architecture);
164 		}
165 
166 		return build_platform;
167 	}
168 }
169 
170 private {
171 	Compiler[] s_compilers;
172 }