1 /**
2 	Compiler settings and abstraction.
3 
4 	Copyright: © 2013-2014 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 
12 import dub.compilers.dmd;
13 import dub.compilers.gdc;
14 import dub.compilers.ldc;
15 import dub.internal.vibecompat.core.file;
16 import dub.internal.vibecompat.core.log;
17 import dub.internal.vibecompat.data.json;
18 import dub.internal.vibecompat.inet.path;
19 
20 import std.algorithm;
21 import std.array;
22 import std.conv;
23 import std.exception;
24 import std.process;
25 
26 
27 static this()
28 {
29 	registerCompiler(new DmdCompiler);
30 	registerCompiler(new GdcCompiler);
31 	registerCompiler(new LdcCompiler);
32 }
33 
34 Compiler getCompiler(string name)
35 {
36 	foreach (c; s_compilers)
37 		if (c.name == name)
38 			return c;
39 
40 	// try to match names like gdmd or gdc-2.61
41 	if (name.canFind("dmd")) return getCompiler("dmd");
42 	if (name.canFind("gdc")) return getCompiler("gdc");
43 	if (name.canFind("ldc")) return getCompiler("ldc");
44 
45 	throw new Exception("Unknown compiler: "~name);
46 }
47 
48 string defaultCompiler()
49 {
50 	static string name;
51 	if (!name.length) name = findCompiler();
52 	return name;
53 }
54 
55 private string findCompiler()
56 {
57 	import std.process : env=environment;
58 	import dub.version_ : initialCompilerBinary;
59 	version (Windows) enum sep = ";", exe = ".exe";
60 	version (Posix) enum sep = ":", exe = "";
61 
62 	auto def = Path(initialCompilerBinary);
63 	if (def.absolute && existsFile(def))
64 		return initialCompilerBinary;
65 
66 	auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"];
67 	if (!def.absolute)
68 		compilers = initialCompilerBinary ~ compilers;
69 
70 	auto paths = env.get("PATH", "").splitter(sep).map!Path;
71 	auto res = compilers.find!(bin => paths.canFind!(p => existsFile(p ~ (bin~exe))));
72 	return res.empty ? initialCompilerBinary : res.front;
73 }
74 
75 void registerCompiler(Compiler c)
76 {
77 	s_compilers ~= c;
78 }
79 
80 void warnOnSpecialCompilerFlags(string[] compiler_flags, BuildOptions options, string package_name, string config_name)
81 {
82 	struct SpecialFlag {
83 		string[] flags;
84 		string alternative;
85 	}
86 	static immutable SpecialFlag[] s_specialFlags = [
87 		{["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"},
88 		{["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`},
89 		{["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"},
90 		{["-wi"], `Use the "buildRequirements" field to control warning behavior`},
91 		{["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`},
92 		{["-of"], `Use "targetPath" and "targetName" to customize the output file`},
93 		{["-debug", "-fdebug", "-g"], "Call dub with --build=debug"},
94 		{["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"},
95 		{["-unittest", "-funittest"], "Call dub with --build=unittest"},
96 		{["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`},
97 		{["-D"], "Call dub with --build=docs or --build=ddox"},
98 		{["-X"], "Call dub with --build=ddox"},
99 		{["-cov"], "Call dub with --build=cov or --build=unittest-cov"},
100 		{["-profile"], "Call dub with --build=profile"},
101 		{["-version="], `Use "versions" to specify version constants in a compiler independent way`},
102 		{["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`},
103 		{["-I"], `Use "importPaths" to specify import paths in a compiler independent way`},
104 		{["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`},
105 		{["-m32", "-m64"], `Use --arch=x86/--arch=x86_64 to specify the target architecture`}
106 	];
107 
108 	struct SpecialOption {
109 		BuildOption[] flags;
110 		string alternative;
111 	}
112 	static immutable SpecialOption[] s_specialOptions = [
113 		{[BuildOption.debugMode], "Call DUB with --build=debug"},
114 		{[BuildOption.releaseMode], "Call DUB with --build=release"},
115 		{[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"},
116 		{[BuildOption.debugInfo], "Call DUB with --build=debug"},
117 		{[BuildOption.inline], "Call DUB with --build=release"},
118 		{[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"},
119 		{[BuildOption.optimize], "Call DUB with --build=release"},
120 		{[BuildOption.profile], "Call DUB with --build=profile"},
121 		{[BuildOption.unittests], "Call DUB with --build=unittest"},
122 		{[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"},
123 		{[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"},
124 		{[BuildOption.property], "This flag is deprecated and has no effect"}
125 	];
126 
127 	bool got_preamble = false;
128 	void outputPreamble()
129 	{
130 		if (got_preamble) return;
131 		got_preamble = true;
132 		logWarn("");
133 		if (config_name.empty) logWarn("## Warning for package %s ##", package_name);
134 		else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name);
135 		logWarn("");
136 		logWarn("The following compiler flags have been specified in the package description");
137 		logWarn("file. They are handled by DUB and direct use in packages is discouraged.");
138 		logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags");
139 		logWarn("to the compiler, or use one of the suggestions below:");
140 		logWarn("");
141 	}
142 
143 	foreach (f; compiler_flags) {
144 		foreach (sf; s_specialFlags) {
145 			if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) {
146 				outputPreamble();
147 				logWarn("%s: %s", f, sf.alternative);
148 				break;
149 			}
150 		}
151 	}
152 
153 	foreach (sf; s_specialOptions) {
154 		foreach (f; sf.flags) {
155 			if (options & f) {
156 				outputPreamble();
157 				logWarn("%s: %s", f, sf.alternative);
158 				break;
159 			}
160 		}
161 	}
162 
163 	if (got_preamble) logWarn("");
164 }
165 
166 
167 /**
168 	Alters the build options to comply with the specified build requirements.
169 */
170 void enforceBuildRequirements(ref BuildSettings settings)
171 {
172 	settings.addOptions(BuildOption.warningsAsErrors);
173 	if (settings.requirements & BuildRequirement.allowWarnings) { settings.options &= ~BuildOption.warningsAsErrors; settings.options |= BuildOption.warnings; }
174 	if (settings.requirements & BuildRequirement.silenceWarnings) settings.options &= ~(BuildOption.warningsAsErrors|BuildOption.warnings);
175 	if (settings.requirements & BuildRequirement.disallowDeprecations) { settings.options &= ~(BuildOption.ignoreDeprecations|BuildOption.deprecationWarnings); settings.options |= BuildOption.deprecationErrors; }
176 	if (settings.requirements & BuildRequirement.silenceDeprecations) { settings.options &= ~(BuildOption.deprecationErrors|BuildOption.deprecationWarnings); settings.options |= BuildOption.ignoreDeprecations; }
177 	if (settings.requirements & BuildRequirement.disallowInlining) settings.options &= ~BuildOption.inline;
178 	if (settings.requirements & BuildRequirement.disallowOptimization) settings.options &= ~BuildOption.optimize;
179 	if (settings.requirements & BuildRequirement.requireBoundsCheck) settings.options &= ~BuildOption.noBoundsCheck;
180 	if (settings.requirements & BuildRequirement.requireContracts) settings.options &= ~BuildOption.releaseMode;
181 	if (settings.requirements & BuildRequirement.relaxProperties) settings.options &= ~BuildOption.property;
182 }
183 
184 
185 /**
186 	Replaces each referenced import library by the appropriate linker flags.
187 
188 	This function tries to invoke "pkg-config" if possible and falls back to
189 	direct flag translation if that fails.
190 */
191 void resolveLibs(ref BuildSettings settings)
192 {
193 	import std..string : format;
194 
195 	if (settings.libs.length == 0) return;
196 
197 	if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) {
198 		logDiagnostic("Ignoring all import libraries for static library build.");
199 		settings.libs = null;
200 		version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array;
201 	}
202 
203 	version (Posix) {
204 		try {
205 			enum pkgconfig_bin = "pkg-config";
206 
207 			bool exists(string lib) {
208 				return execute([pkgconfig_bin, "--exists", lib]).status == 0;
209 			}
210 
211 			auto pkgconfig_libs = settings.libs.partition!(l => !exists(l));
212 			pkgconfig_libs ~= settings.libs[0 .. $ - pkgconfig_libs.length]
213 				.partition!(l => !exists("lib"~l)).map!(l => "lib"~l).array;
214 			settings.libs = settings.libs[0 .. $ - pkgconfig_libs.length];
215 
216 			if (pkgconfig_libs.length) {
217 				logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.join(", "));
218 				auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs);
219 				enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output));
220 				foreach (f; libflags.output.split()) {
221 					if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(","));
222 					else settings.addLFlags(f);
223 				}
224 			}
225 			if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", "));
226 		} catch (Exception e) {
227 			logDiagnostic("pkg-config failed: %s", e.msg);
228 			logDiagnostic("Falling back to direct -l... flags.");
229 		}
230 	}
231 }
232 
233 
234 interface Compiler {
235 	@property string name() const;
236 
237 	BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null);
238 
239 	/// Replaces high level fields with low level fields and converts
240 	/// dmd flags to compiler-specific flags
241 	void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all) const;
242 
243 	/// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field.
244 	void extractBuildOptions(ref BuildSettings settings) const;
245 
246 	/// Adds the appropriate flag to set a target path
247 	void setTarget(ref BuildSettings settings, in BuildPlatform platform, string targetPath = null) const;
248 
249 	/// Invokes the compiler using the given flags
250 	void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback);
251 
252 	/// Invokes the underlying linker directly
253 	void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback);
254 
255 	protected final void invokeTool(string[] args, void delegate(int, string) output_callback)
256 	{
257 		import std..string;
258 
259 		int status;
260 		if (output_callback) {
261 			auto result = executeShell(escapeShellCommand(args));
262 			output_callback(result.status, result.output);
263 			status = result.status;
264 		} else {
265 			auto compiler_pid = spawnShell(escapeShellCommand(args));
266 			status = compiler_pid.wait();
267 		}
268 
269 		version (Posix) if (status == -9) {
270 			throw new Exception(format("%s failed with exit code %s. This may indicate that the process has run out of memory.",
271 				args[0], status));
272 		}
273 		enforce(status == 0, format("%s failed with exit code %s.", args[0], status));
274 	}
275 }
276 
277 
278 /// Represents a platform a package can be build upon.
279 struct BuildPlatform {
280 	/// e.g. ["posix", "windows"]
281 	string[] platform;
282 	/// e.g. ["x86", "x86_64"]
283 	string[] architecture;
284 	/// Canonical compiler name e.g. "dmd"
285 	string compiler;
286 	/// Compiler binary name e.g. "ldmd2"
287 	string compilerBinary;
288 	/// Compiled frontend version (e.g. 2065)
289 	int frontendVersion;
290 
291 	enum any = BuildPlatform(null, null, null, null, -1);
292 
293 	/// Build platforms can be specified via a string specification.
294 	///
295 	/// Specifications are build upon the following scheme, where each component
296 	/// is optional (indicated by []), but the order is obligatory.
297 	/// "[-platform][-architecture][-compiler]"
298 	///
299 	/// So the following strings are valid specifications:
300 	/// "-windows-x86-dmd"
301 	/// "-dmd"
302 	/// "-arm"
303 	/// "-arm-dmd"
304 	/// "-windows-dmd"
305 	///
306 	/// Params:
307 	///     specification = The specification being matched. It must be the empty string or start with a dash.
308 	///
309 	/// Returns:
310 	///     true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches)
311 	///
312 	bool matchesSpecification(const(char)[] specification)
313 	const {
314 		if (specification.empty) return true;
315 		if (this == any) return true;
316 
317 		auto splitted=specification.splitter('-');
318 		assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!");
319 		splitted.popFront(); // Drop leading empty match.
320 		enforce(!splitted.empty, "Platform specification if present, must not be empty!");
321 		if (platform.canFind(splitted.front)) {
322 			splitted.popFront();
323 			if(splitted.empty)
324 				return true;
325 		}
326 		if (architecture.canFind(splitted.front)) {
327 			splitted.popFront();
328 			if(splitted.empty)
329 				return true;
330 		}
331 		if (compiler == splitted.front) {
332 			splitted.popFront();
333 			enforce(splitted.empty, "No valid specification! The compiler has to be the last element!");
334 			return true;
335 		}
336 		return false;
337 	}
338 	unittest {
339 		auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd");
340 		assert(platform.matchesSpecification("-posix"));
341 		assert(platform.matchesSpecification("-linux"));
342 		assert(platform.matchesSpecification("-linux-dmd"));
343 		assert(platform.matchesSpecification("-linux-x86_64-dmd"));
344 		assert(platform.matchesSpecification("-x86_64"));
345 		assert(!platform.matchesSpecification("-windows"));
346 		assert(!platform.matchesSpecification("-ldc"));
347 		assert(!platform.matchesSpecification("-windows-dmd"));
348 	}
349 }
350 
351 
352 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform)
353 {
354 	assert(settings.targetName.length > 0, "No target name set.");
355 	final switch (settings.targetType) {
356 		case TargetType.autodetect: assert(false, "Configurations must have a concrete target type.");
357 		case TargetType.none: return null;
358 		case TargetType.sourceLibrary: return null;
359 		case TargetType.executable:
360 			if( platform.platform.canFind("windows") )
361 				return settings.targetName ~ ".exe";
362 			else return settings.targetName;
363 		case TargetType.library:
364 		case TargetType.staticLibrary:
365 			if (platform.platform.canFind("windows") && platform.compiler == "dmd")
366 				return settings.targetName ~ ".lib";
367 			else return "lib" ~ settings.targetName ~ ".a";
368 		case TargetType.dynamicLibrary:
369 			if( platform.platform.canFind("windows") )
370 				return settings.targetName ~ ".dll";
371 			else return "lib" ~ settings.targetName ~ ".so";
372 		case TargetType.object:
373 			if (platform.platform.canFind("windows"))
374 				return settings.targetName ~ ".obj";
375 			else return settings.targetName ~ ".o";
376 	}
377 }
378 
379 
380 bool isLinkerFile(string f)
381 {
382 	import std.path;
383 	switch (extension(f)) {
384 		default:
385 			return false;
386 		version (Windows) {
387 			case ".lib", ".obj", ".res", ".def":
388 				return true;
389 		} else {
390 			case ".a", ".o", ".so", ".dylib":
391 				return true;
392 		}
393 	}
394 }
395 
396 /// Generate a file that will give, at compile time, informations about the compiler (architecture, frontend version...)
397 Path generatePlatformProbeFile()
398 {
399 	import dub.internal.vibecompat.core.file;
400 	import dub.internal.vibecompat.data.json;
401 	import dub.internal.utils;
402 
403 	auto path = getTempFile("dub_platform_probe", ".d");
404 
405 	auto fil = openFile(path, FileMode.CreateTrunc);
406 	scope (failure) {
407 		fil.close();
408 	}
409 
410 	fil.write(q{
411 		module dub_platform_probe;
412 
413 		template toString(int v) { enum toString = v.stringof; }
414 
415 		pragma(msg, `{`);
416 		pragma(msg,`  "compiler": "`~ determineCompiler() ~ `",`);
417 		pragma(msg, `  "frontendVersion": ` ~ toString!__VERSION__ ~ `,`);
418 		pragma(msg, `  "compilerVendor": "` ~ __VENDOR__ ~ `",`);
419 		pragma(msg, `  "platform": [`);
420 		pragma(msg, `    ` ~ determinePlatform());
421 		pragma(msg, `  ],`);
422 		pragma(msg, `  "architecture": [`);
423 		pragma(msg, `    ` ~ determineArchitecture());
424 		pragma(msg, `   ],`);
425 		pragma(msg, `}`);
426 
427 		string determinePlatform()
428 		{
429 			string ret;
430 			version(Windows) ret ~= `"windows", `;
431 			version(linux) ret ~= `"linux", `;
432 			version(Posix) ret ~= `"posix", `;
433 			version(OSX) ret ~= `"osx", `;
434 			version(FreeBSD) ret ~= `"freebsd", `;
435 			version(OpenBSD) ret ~= `"openbsd", `;
436 			version(NetBSD) ret ~= `"netbsd", `;
437 			version(DragonFlyBSD) ret ~= `"dragonflybsd", `;
438 			version(BSD) ret ~= `"bsd", `;
439 			version(Solaris) ret ~= `"solaris", `;
440 			version(AIX) ret ~= `"aix", `;
441 			version(Haiku) ret ~= `"haiku", `;
442 			version(SkyOS) ret ~= `"skyos", `;
443 			version(SysV3) ret ~= `"sysv3", `;
444 			version(SysV4) ret ~= `"sysv4", `;
445 			version(Hurd) ret ~= `"hurd", `;
446 			version(Android) ret ~= `"android", `;
447 			version(Cygwin) ret ~= `"cygwin", `;
448 			version(MinGW) ret ~= `"mingw", `;
449 			return ret;
450 		}
451 
452 		string determineArchitecture()
453 		{
454 			string ret;
455 			version(X86) ret ~= `"x86", `;
456 			version(X86_64) ret ~= `"x86_64", `;
457 			version(ARM) ret ~= `"arm", `;
458 			version(ARM_Thumb) ret ~= `"arm_thumb", `;
459 			version(ARM_SoftFloat) ret ~= `"arm_softfloat", `;
460 			version(ARM_HardFloat) ret ~= `"arm_hardfloat", `;
461 			version(ARM64) ret ~= `"arm64", `;
462 			version(PPC) ret ~= `"ppc", `;
463 			version(PPC_SoftFP) ret ~= `"ppc_softfp", `;
464 			version(PPC_HardFP) ret ~= `"ppc_hardfp", `;
465 			version(PPC64) ret ~= `"ppc64", `;
466 			version(IA64) ret ~= `"ia64", `;
467 			version(MIPS) ret ~= `"mips", `;
468 			version(MIPS32) ret ~= `"mips32", `;
469 			version(MIPS64) ret ~= `"mips64", `;
470 			version(MIPS_O32) ret ~= `"mips_o32", `;
471 			version(MIPS_N32) ret ~= `"mips_n32", `;
472 			version(MIPS_O64) ret ~= `"mips_o64", `;
473 			version(MIPS_N64) ret ~= `"mips_n64", `;
474 			version(MIPS_EABI) ret ~= `"mips_eabi", `;
475 			version(MIPS_NoFloat) ret ~= `"mips_nofloat", `;
476 			version(MIPS_SoftFloat) ret ~= `"mips_softfloat", `;
477 			version(MIPS_HardFloat) ret ~= `"mips_hardfloat", `;
478 			version(SPARC) ret ~= `"sparc", `;
479 			version(SPARC_V8Plus) ret ~= `"sparc_v8plus", `;
480 			version(SPARC_SoftFP) ret ~= `"sparc_softfp", `;
481 			version(SPARC_HardFP) ret ~= `"sparc_hardfp", `;
482 			version(SPARC64) ret ~= `"sparc64", `;
483 			version(S390) ret ~= `"s390", `;
484 			version(S390X) ret ~= `"s390x", `;
485 			version(HPPA) ret ~= `"hppa", `;
486 			version(HPPA64) ret ~= `"hppa64", `;
487 			version(SH) ret ~= `"sh", `;
488 			version(SH64) ret ~= `"sh64", `;
489 			version(Alpha) ret ~= `"alpha", `;
490 			version(Alpha_SoftFP) ret ~= `"alpha_softfp", `;
491 			version(Alpha_HardFP) ret ~= `"alpha_hardfp", `;
492 			return ret;
493 		}
494 
495 		string determineCompiler()
496 		{
497 			version(DigitalMars) return "dmd";
498 			else version(GNU) return "gdc";
499 			else version(LDC) return "ldc";
500 			else version(SDC) return "sdc";
501 			else return null;
502 		}
503 	});
504 
505 	fil.close();
506 
507 	return path;
508 }
509 
510 BuildPlatform readPlatformProbe(string output)
511 {
512 	import std..string;
513 
514 	// work around possible additional output of the compiler
515 	auto idx1 = output.indexOf("{");
516 	auto idx2 = output.lastIndexOf("}");
517 	enforce(idx1 >= 0 && idx1 < idx2,
518 		"Unexpected platform information output - does not contain a JSON object.");
519 	output = output[idx1 .. idx2+1];
520 
521 	import dub.internal.vibecompat.data.json;
522 	auto json = parseJsonString(output);
523 
524 	BuildPlatform build_platform;
525 	build_platform.platform = json.platform.get!(Json[]).map!(e => e.get!string()).array();
526 	build_platform.architecture = json.architecture.get!(Json[]).map!(e => e.get!string()).array();
527 	build_platform.compiler = json.compiler.get!string;
528 	build_platform.frontendVersion = json.frontendVersion.get!int;
529 	return build_platform;
530 }
531 
532 private {
533 	Compiler[] s_compilers;
534 }