1 /**
2 	DMD compiler support.
3 
4 	Copyright: © 2013-2013 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.dmd;
9 
10 import dub.compilers.compiler;
11 import dub.compilers.utils;
12 import dub.internal.utils;
13 import dub.internal.vibecompat.core.log;
14 import dub.internal.vibecompat.inet.path;
15 import dub.recipe.packagerecipe : ToolchainRequirements;
16 
17 import std.algorithm;
18 import std.array;
19 import std.conv;
20 import std.exception;
21 import std.file;
22 import std.process;
23 import std.typecons;
24 
25 // Determines whether the specified process is running under WOW64 or an Intel64 of x64 processor.
26 version (Windows)
27 private Nullable!bool isWow64() {
28 	// See also: https://docs.microsoft.com/de-de/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo
29 	import core.sys.windows.windows : GetNativeSystemInfo, SYSTEM_INFO, PROCESSOR_ARCHITECTURE_AMD64;
30 
31 	static Nullable!bool result;
32 
33 	// A process's architecture won't change over while the process is in memory
34 	// Return the cached result
35 	if (!result.isNull)
36 		return result;
37 
38 	SYSTEM_INFO systemInfo;
39 	GetNativeSystemInfo(&systemInfo);
40 	result = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64;
41 	return result;
42 }
43 
44 class DMDCompiler : Compiler {
45 	private static immutable s_options = [
46 		tuple(BuildOption.debugMode, ["-debug"]),
47 		tuple(BuildOption.releaseMode, ["-release"]),
48 		tuple(BuildOption.coverage, ["-cov"]),
49 		tuple(BuildOption.debugInfo, ["-g"]),
50 		tuple(BuildOption.debugInfoC, ["-g"]),
51 		tuple(BuildOption.alwaysStackFrame, ["-gs"]),
52 		tuple(BuildOption.stackStomping, ["-gx"]),
53 		tuple(BuildOption.inline, ["-inline"]),
54 		tuple(BuildOption.noBoundsCheck, ["-noboundscheck"]),
55 		tuple(BuildOption.optimize, ["-O"]),
56 		tuple(BuildOption.profile, ["-profile"]),
57 		tuple(BuildOption.unittests, ["-unittest"]),
58 		tuple(BuildOption.verbose, ["-v"]),
59 		tuple(BuildOption.ignoreUnknownPragmas, ["-ignore"]),
60 		tuple(BuildOption.syntaxOnly, ["-o-"]),
61 		tuple(BuildOption.warnings, ["-wi"]),
62 		tuple(BuildOption.warningsAsErrors, ["-w"]),
63 		tuple(BuildOption.ignoreDeprecations, ["-d"]),
64 		tuple(BuildOption.deprecationWarnings, ["-dw"]),
65 		tuple(BuildOption.deprecationErrors, ["-de"]),
66 		tuple(BuildOption.property, ["-property"]),
67 		tuple(BuildOption.profileGC, ["-profile=gc"]),
68 		tuple(BuildOption.betterC, ["-betterC"]),
69 
70 		tuple(BuildOption._docs, ["-Dddocs"]),
71 		tuple(BuildOption._ddox, ["-Xfdocs.json", "-Df__dummy.html"]),
72 	];
73 
74 	@property string name() const { return "dmd"; }
75 
76 	enum dmdVersionRe = `^version\s+v?(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`;
77 
78 	unittest {
79 		import std.regex : matchFirst, regex;
80 		auto probe = `
81 binary    dmd
82 version   v2.082.0
83 config    /etc/dmd.conf
84 `;
85 		auto re = regex(dmdVersionRe, "m");
86 		auto c = matchFirst(probe, re);
87 		assert(c && c.length > 1 && c[1] == "2.082.0");
88 	}
89 
90 	unittest {
91 		import std.regex : matchFirst, regex;
92 		auto probe = `
93 binary    dmd
94 version   v2.084.0-beta.1
95 config    /etc/dmd.conf
96 `;
97 		auto re = regex(dmdVersionRe, "m");
98 		auto c = matchFirst(probe, re);
99 		assert(c && c.length > 1 && c[1] == "2.084.0-beta.1");
100 	}
101 
102 	BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override)
103 	{
104 		string[] arch_flags;
105 		switch (arch_override) {
106 			default: throw new Exception("Unsupported architecture: "~arch_override);
107 			case "":
108 				// Don't use Optlink by default on Windows
109 				version (Windows) {
110 					const is64bit = isWow64();
111 					if (!is64bit.isNull)
112 						arch_flags = [is64bit ? "-m64" : "-m32mscoff"];
113 				}
114 				break;
115 			case "x86": arch_flags = ["-m32"]; break;
116 			case "x86_64": arch_flags = ["-m64"]; break;
117 			case "x86_mscoff": arch_flags = ["-m32mscoff"]; break;
118 		}
119 		settings.addDFlags(arch_flags);
120 
121 		return probePlatform(
122 			compiler_binary,
123 			arch_flags ~ ["-quiet", "-c", "-o-", "-v"],
124 			arch_override,
125 			[ dmdVersionRe ]
126 		);
127 	}
128 
129 	void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const
130 	{
131 		enforceBuildRequirements(settings);
132 
133 		if (!(fields & BuildSetting.options)) {
134 			foreach (t; s_options)
135 				if (settings.options & t[0])
136 					settings.addDFlags(t[1]);
137 		}
138 
139 		if (!(fields & BuildSetting.versions)) {
140 			settings.addDFlags(settings.versions.map!(s => "-version="~s)().array());
141 			settings.versions = null;
142 		}
143 
144 		if (!(fields & BuildSetting.debugVersions)) {
145 			settings.addDFlags(settings.debugVersions.map!(s => "-debug="~s)().array());
146 			settings.debugVersions = null;
147 		}
148 
149 		if (!(fields & BuildSetting.importPaths)) {
150 			settings.addDFlags(settings.importPaths.map!(s => "-I"~s)().array());
151 			settings.importPaths = null;
152 		}
153 
154 		if (!(fields & BuildSetting.stringImportPaths)) {
155 			settings.addDFlags(settings.stringImportPaths.map!(s => "-J"~s)().array());
156 			settings.stringImportPaths = null;
157 		}
158 
159 		if (!(fields & BuildSetting.libs)) {
160 			resolveLibs(settings);
161 			version(Windows) settings.addSourceFiles(settings.libs.map!(l => l~".lib")().array());
162 			else settings.addLFlags(settings.libs.map!(l => "-l"~l)().array());
163 		}
164 
165 		if (!(fields & BuildSetting.sourceFiles)) {
166 			settings.addDFlags(settings.sourceFiles);
167 			settings.sourceFiles = null;
168 		}
169 
170 		if (!(fields & BuildSetting.lflags)) {
171 			settings.addDFlags(lflagsToDFlags(settings.lflags));
172 			settings.lflags = null;
173 		}
174 
175 		version (Posix) {
176 			if (settings.options & BuildOption.pic)
177 				settings.addDFlags("-fPIC");
178 		}
179 
180 		assert(fields & BuildSetting.dflags);
181 		assert(fields & BuildSetting.copyFiles);
182 	}
183 
184 	void extractBuildOptions(ref BuildSettings settings) const
185 	{
186 		Appender!(string[]) newflags;
187 		next_flag: foreach (f; settings.dflags) {
188 			foreach (t; s_options)
189 				if (t[1].canFind(f)) {
190 					settings.options |= t[0];
191 					continue next_flag;
192 				}
193 			if (f.startsWith("-version=")) settings.addVersions(f[9 .. $]);
194 			else if (f.startsWith("-debug=")) settings.addDebugVersions(f[7 .. $]);
195 			else newflags ~= f;
196 		}
197 		settings.dflags = newflags.data;
198 	}
199 
200 	string getTargetFileName(in BuildSettings settings, in BuildPlatform platform)
201 	const {
202 		import std.conv: text;
203 		assert(settings.targetName.length > 0, "No target name set.");
204 		final switch (settings.targetType) {
205 			case TargetType.autodetect:
206 				assert(false,
207 					   text("Configurations must have a concrete target type, ", settings.targetName,
208 							" has ", settings.targetType));
209 			case TargetType.none: return null;
210 			case TargetType.sourceLibrary: return null;
211 			case TargetType.executable:
212 				if (platform.platform.canFind("windows"))
213 					return settings.targetName ~ ".exe";
214 				else return settings.targetName;
215 			case TargetType.library:
216 			case TargetType.staticLibrary:
217 				if (platform.platform.canFind("windows"))
218 					return settings.targetName ~ ".lib";
219 				else return "lib" ~ settings.targetName ~ ".a";
220 			case TargetType.dynamicLibrary:
221 				if (platform.platform.canFind("windows"))
222 					return settings.targetName ~ ".dll";
223 				else if (platform.platform.canFind("osx"))
224 					return "lib" ~ settings.targetName ~ ".dylib";
225 				else return "lib" ~ settings.targetName ~ ".so";
226 			case TargetType.object:
227 				if (platform.platform.canFind("windows"))
228 					return settings.targetName ~ ".obj";
229 				else return settings.targetName ~ ".o";
230 		}
231 	}
232 
233 	void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const
234 	{
235 		final switch (settings.targetType) {
236 			case TargetType.autodetect: assert(false, "Invalid target type: autodetect");
237 			case TargetType.none: assert(false, "Invalid target type: none");
238 			case TargetType.sourceLibrary: assert(false, "Invalid target type: sourceLibrary");
239 			case TargetType.executable: break;
240 			case TargetType.library:
241 			case TargetType.staticLibrary:
242 				settings.addDFlags("-lib");
243 				break;
244 			case TargetType.dynamicLibrary:
245 				version (Windows) settings.addDFlags("-shared");
246 				else version (OSX) settings.addDFlags("-shared");
247 				else settings.prependDFlags("-shared", "-defaultlib=libphobos2.so");
248 				break;
249 			case TargetType.object:
250 				settings.addDFlags("-c");
251 				break;
252 		}
253 
254 		if (tpath is null)
255 			tpath = (NativePath(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString();
256 		settings.addDFlags("-of"~tpath);
257 	}
258 
259 	void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback)
260 	{
261 		auto res_file = getTempFile("dub-build", ".rsp");
262 		const(string)[] args = settings.dflags;
263 		if (platform.frontendVersion >= 2066) args ~= "-vcolumns";
264 		std.file.write(res_file.toNativeString(), escapeArgs(args).join("\n"));
265 
266 		logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" "));
267 		invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback);
268 	}
269 
270 	void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback)
271 	{
272 		import std..string;
273 		auto tpath = NativePath(settings.targetPath) ~ getTargetFileName(settings, platform);
274 		auto args = ["-of"~tpath.toNativeString()];
275 		args ~= objects;
276 		args ~= settings.sourceFiles;
277 		version(linux) args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being specified in the wrong order by DMD
278 		args ~= lflagsToDFlags(settings.lflags);
279 		args ~= settings.dflags.filter!(f => isLinkerDFlag(f)).array;
280 
281 		auto res_file = getTempFile("dub-build", ".lnk");
282 		std.file.write(res_file.toNativeString(), escapeArgs(args).join("\n"));
283 
284 		logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" "));
285 		invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback);
286 	}
287 
288 	string[] lflagsToDFlags(in string[] lflags) const
289 	{
290 		return  lflags.map!(f => "-L"~f)().array();
291 	}
292 
293 	private auto escapeArgs(in string[] args)
294 	{
295 		return args.map!(s => s.canFind(' ') ? "\""~s~"\"" : s);
296 	}
297 
298 	private static bool isLinkerDFlag(string arg)
299 	{
300 		switch (arg) {
301 			default:
302 				if (arg.startsWith("-defaultlib=")) return true;
303 				return false;
304 			case "-g", "-gc", "-m32", "-m64", "-shared", "-lib", "-m32mscoff":
305 				return true;
306 		}
307 	}
308 }