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