1 /**
2 	LDC 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.ldc;
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 
26 class LDCCompiler : Compiler {
27 	private static immutable s_options = [
28 		tuple(BuildOption.debugMode, ["-d-debug"]),
29 		tuple(BuildOption.releaseMode, ["-release"]),
30 		//tuple(BuildOption.coverage, ["-?"]),
31 		tuple(BuildOption.debugInfo, ["-g"]),
32 		tuple(BuildOption.debugInfoC, ["-gc"]),
33 		//tuple(BuildOption.alwaysStackFrame, ["-?"]),
34 		//tuple(BuildOption.stackStomping, ["-?"]),
35 		tuple(BuildOption.inline, ["-enable-inlining", "-Hkeep-all-bodies"]),
36 		tuple(BuildOption.noBoundsCheck, ["-boundscheck=off"]),
37 		tuple(BuildOption.optimize, ["-O3"]),
38 		//tuple(BuildOption.profile, ["-?"]),
39 		tuple(BuildOption.unittests, ["-unittest"]),
40 		tuple(BuildOption.verbose, ["-v"]),
41 		tuple(BuildOption.ignoreUnknownPragmas, ["-ignore"]),
42 		tuple(BuildOption.syntaxOnly, ["-o-"]),
43 		tuple(BuildOption.warnings, ["-wi"]),
44 		tuple(BuildOption.warningsAsErrors, ["-w"]),
45 		tuple(BuildOption.ignoreDeprecations, ["-d"]),
46 		tuple(BuildOption.deprecationWarnings, ["-dw"]),
47 		tuple(BuildOption.deprecationErrors, ["-de"]),
48 		tuple(BuildOption.property, ["-property"]),
49 		//tuple(BuildOption.profileGC, ["-?"]),
50 		tuple(BuildOption.betterC, ["-betterC"]),
51 
52 		tuple(BuildOption._docs, ["-Dd=docs"]),
53 		tuple(BuildOption._ddox, ["-Xf=docs.json", "-Dd=__dummy_docs"]),
54 	];
55 
56 	@property string name() const { return "ldc"; }
57 
58 	enum ldcVersionRe = `^version\s+v?(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`;
59 
60 	unittest {
61 		import std.regex : matchFirst, regex;
62 		auto probe = `
63 binary    /usr/bin/ldc2
64 version   1.11.0 (DMD v2.081.2, LLVM 6.0.1)
65 config    /etc/ldc2.conf (x86_64-pc-linux-gnu)
66 `;
67 		auto re = regex(ldcVersionRe, "m");
68 		auto c = matchFirst(probe, re);
69 		assert(c && c.length > 1 && c[1] == "1.11.0");
70 	}
71 
72 	BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override)
73 	{
74 		string[] arch_flags;
75 		switch (arch_override) {
76 			default: throw new Exception("Unsupported architecture: "~arch_override);
77 			case "": break;
78 			case "x86": arch_flags = ["-march=x86"]; break;
79 			case "x86_64": arch_flags = ["-march=x86-64"]; break;
80 		}
81 		settings.addDFlags(arch_flags);
82 
83 		return probePlatform(
84 			compiler_binary,
85 			arch_flags ~ ["-c", "-o-", "-v"],
86 			arch_override,
87 			[ ldcVersionRe ]
88 		);
89 	}
90 
91 	void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const
92 	{
93 		enforceBuildRequirements(settings);
94 
95 		if (!(fields & BuildSetting.options)) {
96 			foreach (t; s_options)
97 				if (settings.options & t[0])
98 					settings.addDFlags(t[1]);
99 		}
100 
101 		// since LDC always outputs multiple object files, avoid conflicts by default
102 		settings.addDFlags("-oq", "-od=.dub/obj");
103 
104 		if (!(fields & BuildSetting.versions)) {
105 			settings.addDFlags(settings.versions.map!(s => "-d-version="~s)().array());
106 			settings.versions = null;
107 		}
108 
109 		if (!(fields & BuildSetting.debugVersions)) {
110 			settings.addDFlags(settings.debugVersions.map!(s => "-d-debug="~s)().array());
111 			settings.debugVersions = null;
112 		}
113 
114 		if (!(fields & BuildSetting.importPaths)) {
115 			settings.addDFlags(settings.importPaths.map!(s => "-I"~s)().array());
116 			settings.importPaths = null;
117 		}
118 
119 		if (!(fields & BuildSetting.stringImportPaths)) {
120 			settings.addDFlags(settings.stringImportPaths.map!(s => "-J"~s)().array());
121 			settings.stringImportPaths = null;
122 		}
123 
124 		if (!(fields & BuildSetting.sourceFiles)) {
125 			settings.addDFlags(settings.sourceFiles);
126 			settings.sourceFiles = null;
127 		}
128 
129 		if (!(fields & BuildSetting.libs)) {
130 			resolveLibs(settings);
131 			settings.addLFlags(settings.libs.map!(l => "-l"~l)().array());
132 		}
133 
134 		if (!(fields & BuildSetting.lflags)) {
135 			settings.addDFlags(lflagsToDFlags(settings.lflags));
136 			settings.lflags = null;
137 		}
138 
139 		if (settings.options & BuildOption.pic)
140 			settings.addDFlags("-relocation-model=pic");
141 
142 		assert(fields & BuildSetting.dflags);
143 		assert(fields & BuildSetting.copyFiles);
144 	}
145 
146 	void extractBuildOptions(ref BuildSettings settings) const
147 	{
148 		Appender!(string[]) newflags;
149 		next_flag: foreach (f; settings.dflags) {
150 			foreach (t; s_options)
151 				if (t[1].canFind(f)) {
152 					settings.options |= t[0];
153 					continue next_flag;
154 				}
155 			if (f.startsWith("-d-version=")) settings.addVersions(f[11 .. $]);
156 			else if (f.startsWith("-d-debug=")) settings.addDebugVersions(f[9 .. $]);
157 			else newflags ~= f;
158 		}
159 		settings.dflags = newflags.data;
160 	}
161 
162 	string getTargetFileName(in BuildSettings settings, in BuildPlatform platform)
163 	const {
164 		assert(settings.targetName.length > 0, "No target name set.");
165 
166 		final switch (settings.targetType) {
167 			case TargetType.autodetect: assert(false, "Configurations must have a concrete target type.");
168 			case TargetType.none: return null;
169 			case TargetType.sourceLibrary: return null;
170 			case TargetType.executable:
171 				if (platform.platform.canFind("windows"))
172 					return settings.targetName ~ ".exe";
173 				else return settings.targetName;
174 			case TargetType.library:
175 			case TargetType.staticLibrary:
176 				if (generatesCOFF(platform)) return settings.targetName ~ ".lib";
177 				else return "lib" ~ settings.targetName ~ ".a";
178 			case TargetType.dynamicLibrary:
179 				if (platform.platform.canFind("windows"))
180 					return settings.targetName ~ ".dll";
181 				else if (platform.platform.canFind("osx"))
182 					return "lib" ~ settings.targetName ~ ".dylib";
183 				else return "lib" ~ settings.targetName ~ ".so";
184 			case TargetType.object:
185 				if (platform.platform.canFind("windows"))
186 					return settings.targetName ~ ".obj";
187 				else return settings.targetName ~ ".o";
188 		}
189 	}
190 
191 	void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const
192 	{
193 		final switch (settings.targetType) {
194 			case TargetType.autodetect: assert(false, "Invalid target type: autodetect");
195 			case TargetType.none: assert(false, "Invalid target type: none");
196 			case TargetType.sourceLibrary: assert(false, "Invalid target type: sourceLibrary");
197 			case TargetType.executable: break;
198 			case TargetType.library:
199 			case TargetType.staticLibrary:
200 				settings.addDFlags("-lib");
201 				break;
202 			case TargetType.dynamicLibrary:
203 				settings.addDFlags("-shared");
204 				break;
205 			case TargetType.object:
206 				settings.addDFlags("-c");
207 				break;
208 		}
209 
210 		if (tpath is null)
211 			tpath = (NativePath(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString();
212 		settings.addDFlags("-of"~tpath);
213 	}
214 
215 	void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback)
216 	{
217 		auto res_file = getTempFile("dub-build", ".rsp");
218 		const(string)[] args = settings.dflags;
219 		if (platform.frontendVersion >= 2066) args ~= "-vcolumns";
220 		std.file.write(res_file.toNativeString(), escapeArgs(args).join("\n"));
221 
222 		logDiagnostic("%s %s", platform.compilerBinary, escapeArgs(args).join(" "));
223 		invokeTool([platform.compilerBinary, "@"~res_file.toNativeString()], output_callback);
224 	}
225 
226 	void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback)
227 	{
228 		assert(false, "Separate linking not implemented for LDC");
229 	}
230 
231 	string[] lflagsToDFlags(in string[] lflags) const
232 	{
233 		return  lflags.map!(s => "-L="~s)().array();
234 	}
235 
236 	private auto escapeArgs(in string[] args)
237 	{
238 		return args.map!(s => s.canFind(' ') ? "\""~s~"\"" : s);
239 	}
240 
241 	private static bool generatesCOFF(in BuildPlatform platform)
242 	{
243 		import std..string : splitLines, strip;
244 		import std.uni : toLower;
245 
246 		static bool[string] compiler_coff_map;
247 
248 		if (auto pret = platform.compilerBinary in compiler_coff_map)
249 			return *pret;
250 
251 		auto result = executeShell(escapeShellCommand([platform.compilerBinary, "-version"]));
252 		enforce (result.status == 0, "Failed to determine linker used by LDC. \""
253 			~platform.compilerBinary~" -version\" failed with exit code "
254 			~result.status.to!string()~".");
255 
256 		bool ret = result.output
257 			.splitLines
258 			.find!(l => l.strip.toLower.startsWith("default target:"))
259 			.front
260 			.canFind("msvc");
261 
262 		compiler_coff_map[platform.compilerBinary] = ret;
263 		return ret;
264 	}
265 }