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