1 /**
2 	Utility functionality for compiler class implementations.
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.utils;
9 
10 import dub.compilers.buildsettings;
11 import dub.platform;
12 import dub.internal.vibecompat.core.log;
13 import dub.internal.vibecompat.inet.path;
14 import std.algorithm : canFind, endsWith, filter;
15 
16 
17 /**
18 	Alters the build options to comply with the specified build requirements.
19 
20 	And enabled options that do not comply will get disabled.
21 */
22 void enforceBuildRequirements(ref BuildSettings settings)
23 {
24 	settings.addOptions(BuildOption.warningsAsErrors);
25 	if (settings.requirements & BuildRequirement.allowWarnings) { settings.options &= ~BuildOption.warningsAsErrors; settings.options |= BuildOption.warnings; }
26 	if (settings.requirements & BuildRequirement.silenceWarnings) settings.options &= ~(BuildOption.warningsAsErrors|BuildOption.warnings);
27 	if (settings.requirements & BuildRequirement.disallowDeprecations) { settings.options &= ~(BuildOption.ignoreDeprecations|BuildOption.deprecationWarnings); settings.options |= BuildOption.deprecationErrors; }
28 	if (settings.requirements & BuildRequirement.silenceDeprecations) { settings.options &= ~(BuildOption.deprecationErrors|BuildOption.deprecationWarnings); settings.options |= BuildOption.ignoreDeprecations; }
29 	if (settings.requirements & BuildRequirement.disallowInlining) settings.options &= ~BuildOption.inline;
30 	if (settings.requirements & BuildRequirement.disallowOptimization) settings.options &= ~BuildOption.optimize;
31 	if (settings.requirements & BuildRequirement.requireBoundsCheck) settings.options &= ~BuildOption.noBoundsCheck;
32 	if (settings.requirements & BuildRequirement.requireContracts) settings.options &= ~BuildOption.releaseMode;
33 	if (settings.requirements & BuildRequirement.relaxProperties) settings.options &= ~BuildOption.property;
34 }
35 
36 
37 /**
38 	Determines if a specific file name has the extension of a linker file.
39 
40 	Linker files include static/dynamic libraries, resource files, object files
41 	and DLL definition files.
42 */
43 bool isLinkerFile(string f)
44 {
45 	import std.path;
46 	switch (extension(f)) {
47 		default:
48 			return false;
49 		version (Windows) {
50 			case ".lib", ".obj", ".res", ".def":
51 				return true;
52 		} else {
53 			case ".a", ".o", ".so", ".dylib":
54 				return true;
55 		}
56 	}
57 }
58 
59 unittest {
60 	version (Windows) {
61 		assert(isLinkerFile("test.obj"));
62 		assert(isLinkerFile("test.lib"));
63 		assert(isLinkerFile("test.res"));
64 		assert(!isLinkerFile("test.o"));
65 		assert(!isLinkerFile("test.d"));
66 	} else {
67 		assert(isLinkerFile("test.o"));
68 		assert(isLinkerFile("test.a"));
69 		assert(isLinkerFile("test.so"));
70 		assert(isLinkerFile("test.dylib"));
71 		assert(!isLinkerFile("test.obj"));
72 		assert(!isLinkerFile("test.d"));
73 	}
74 }
75 
76 
77 /**
78 	Replaces each referenced import library by the appropriate linker flags.
79 
80 	This function tries to invoke "pkg-config" if possible and falls back to
81 	direct flag translation if that fails.
82 */
83 void resolveLibs(ref BuildSettings settings)
84 {
85 	import std.string : format;
86 
87 	if (settings.libs.length == 0) return;
88 
89 	if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) {
90 		logDiagnostic("Ignoring all import libraries for static library build.");
91 		settings.libs = null;
92 		version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array;
93 	}
94 
95 	version (Posix) {
96 		import std.algorithm : any, map, partition, startsWith;
97 		import std.array : array, join, split;
98 		import std.exception : enforce;
99 		import std.process : execute;
100 
101 		try {
102 			enum pkgconfig_bin = "pkg-config";
103 
104 			bool exists(string lib) {
105 				return execute([pkgconfig_bin, "--exists", lib]).status == 0;
106 			}
107 
108 			auto pkgconfig_libs = settings.libs.partition!(l => !exists(l));
109 			pkgconfig_libs ~= settings.libs[0 .. $ - pkgconfig_libs.length]
110 				.partition!(l => !exists("lib"~l)).map!(l => "lib"~l).array;
111 			settings.libs = settings.libs[0 .. $ - pkgconfig_libs.length];
112 
113 			if (pkgconfig_libs.length) {
114 				logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.join(", "));
115 				auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs);
116 				enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output));
117 				foreach (f; libflags.output.split()) {
118 					if (f.startsWith("-L-L")) {
119 						settings.addLFlags(f[2 .. $]);
120 					} else if (f.startsWith("-defaultlib")) {
121 						settings.addDFlags(f);
122 					} else if (f.startsWith("-L-defaultlib")) {
123 						settings.addDFlags(f[2 .. $]);
124 					} else if (f.startsWith("-pthread")) {
125 						settings.addLFlags("-lpthread");
126 					} else if (f.startsWith("-L-l")) {
127 						settings.addLFlags(f[2 .. $].split(","));
128 					} else if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(","));
129 					else settings.addLFlags(f);
130 				}
131 			}
132 			if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", "));
133 		} catch (Exception e) {
134 			logDiagnostic("pkg-config failed: %s", e.msg);
135 			logDiagnostic("Falling back to direct -l... flags.");
136 		}
137 	}
138 }
139 
140 
141 /** Searches the given list of compiler flags for ones that have a generic
142 	equivalent.
143 
144 	Certain compiler flags should, instead of using compiler-specfic syntax,
145 	be specified as build options (`BuildOptions`) or built requirements
146 	(`BuildRequirements`). This function will output warning messages to
147 	assist the user in making the best choice.
148 */
149 void warnOnSpecialCompilerFlags(string[] compiler_flags, BuildOptions options, string package_name, string config_name)
150 {
151 	import std.algorithm : any, endsWith, startsWith;
152 	import std.range : empty;
153 
154 	struct SpecialFlag {
155 		string[] flags;
156 		string alternative;
157 	}
158 	static immutable SpecialFlag[] s_specialFlags = [
159 		{["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"},
160 		{["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`},
161 		{["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"},
162 		{["-wi"], `Use the "buildRequirements" field to control warning behavior`},
163 		{["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`},
164 		{["-of"], `Use "targetPath" and "targetName" to customize the output file`},
165 		{["-debug", "-fdebug", "-g"], "Call dub with --build=debug"},
166 		{["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"},
167 		{["-unittest", "-funittest"], "Call dub with --build=unittest"},
168 		{["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`},
169 		{["-D"], "Call dub with --build=docs or --build=ddox"},
170 		{["-X"], "Call dub with --build=ddox"},
171 		{["-cov"], "Call dub with --build=cov or --build=unittest-cov"},
172 		{["-profile"], "Call dub with --build=profile"},
173 		{["-version="], `Use "versions" to specify version constants in a compiler independent way`},
174 		{["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`},
175 		{["-I"], `Use "importPaths" to specify import paths in a compiler independent way`},
176 		{["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`},
177 		{["-m32", "-m64"], `Use --arch=x86/--arch=x86_64 to specify the target architecture`}
178 	];
179 
180 	struct SpecialOption {
181 		BuildOption[] flags;
182 		string alternative;
183 	}
184 	static immutable SpecialOption[] s_specialOptions = [
185 		{[BuildOption.debugMode], "Call DUB with --build=debug"},
186 		{[BuildOption.releaseMode], "Call DUB with --build=release"},
187 		{[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"},
188 		{[BuildOption.debugInfo], "Call DUB with --build=debug"},
189 		{[BuildOption.inline], "Call DUB with --build=release"},
190 		{[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"},
191 		{[BuildOption.optimize], "Call DUB with --build=release"},
192 		{[BuildOption.profile], "Call DUB with --build=profile"},
193 		{[BuildOption.unittests], "Call DUB with --build=unittest"},
194 		{[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"},
195 		{[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"},
196 		{[BuildOption.property], "This flag is deprecated and has no effect"}
197 	];
198 
199 	bool got_preamble = false;
200 	void outputPreamble()
201 	{
202 		if (got_preamble) return;
203 		got_preamble = true;
204 		logWarn("");
205 		if (config_name.empty) logWarn("## Warning for package %s ##", package_name);
206 		else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name);
207 		logWarn("");
208 		logWarn("The following compiler flags have been specified in the package description");
209 		logWarn("file. They are handled by DUB and direct use in packages is discouraged.");
210 		logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags");
211 		logWarn("to the compiler, or use one of the suggestions below:");
212 		logWarn("");
213 	}
214 
215 	foreach (f; compiler_flags) {
216 		foreach (sf; s_specialFlags) {
217 			if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) {
218 				outputPreamble();
219 				logWarn("%s: %s", f, sf.alternative);
220 				break;
221 			}
222 		}
223 	}
224 
225 	foreach (sf; s_specialOptions) {
226 		foreach (f; sf.flags) {
227 			if (options & f) {
228 				outputPreamble();
229 				logWarn("%s: %s", f, sf.alternative);
230 				break;
231 			}
232 		}
233 	}
234 
235 	if (got_preamble) logWarn("");
236 }
237 
238 
239 /**
240 	Generate a file that will give, at compile time, informations about the compiler (architecture, frontend version...)
241 
242 	See_Also: `readPlatformProbe`
243 */
244 Path generatePlatformProbeFile()
245 {
246 	import dub.internal.vibecompat.core.file;
247 	import dub.internal.vibecompat.data.json;
248 	import dub.internal.utils;
249 
250 	auto path = getTempFile("dub_platform_probe", ".d");
251 
252 	auto fil = openFile(path, FileMode.createTrunc);
253 	scope (failure) {
254 		fil.close();
255 	}
256 
257 	// NOTE: This must be kept in sync with the dub.platform module
258 	fil.write(q{
259 		module dub_platform_probe;
260 
261 		template toString(int v) { enum toString = v.stringof; }
262 
263 		pragma(msg, `{`);
264 		pragma(msg,`  "compiler": "`~ determineCompiler() ~ `",`);
265 		pragma(msg, `  "frontendVersion": ` ~ toString!__VERSION__ ~ `,`);
266 		pragma(msg, `  "compilerVendor": "` ~ __VENDOR__ ~ `",`);
267 		pragma(msg, `  "platform": [`);
268 		pragma(msg, `    ` ~ determinePlatform());
269 		pragma(msg, `  ],`);
270 		pragma(msg, `  "architecture": [`);
271 		pragma(msg, `    ` ~ determineArchitecture());
272 		pragma(msg, `   ],`);
273 		pragma(msg, `}`);
274 
275 		string determinePlatform()
276 		{
277 			string ret;
278 			version(Windows) ret ~= `"windows", `;
279 			version(linux) ret ~= `"linux", `;
280 			version(Posix) ret ~= `"posix", `;
281 			version(OSX) ret ~= `"osx", `;
282 			version(FreeBSD) ret ~= `"freebsd", `;
283 			version(OpenBSD) ret ~= `"openbsd", `;
284 			version(NetBSD) ret ~= `"netbsd", `;
285 			version(DragonFlyBSD) ret ~= `"dragonflybsd", `;
286 			version(BSD) ret ~= `"bsd", `;
287 			version(Solaris) ret ~= `"solaris", `;
288 			version(AIX) ret ~= `"aix", `;
289 			version(Haiku) ret ~= `"haiku", `;
290 			version(SkyOS) ret ~= `"skyos", `;
291 			version(SysV3) ret ~= `"sysv3", `;
292 			version(SysV4) ret ~= `"sysv4", `;
293 			version(Hurd) ret ~= `"hurd", `;
294 			version(Android) ret ~= `"android", `;
295 			version(Cygwin) ret ~= `"cygwin", `;
296 			version(MinGW) ret ~= `"mingw", `;
297 			return ret;
298 		}
299 
300 		string determineArchitecture()
301 		{
302 			string ret;
303 			version(X86) ret ~= `"x86", `;
304 			version(X86_64) ret ~= `"x86_64", `;
305 			version(ARM) ret ~= `"arm", `;
306 			version(ARM_Thumb) ret ~= `"arm_thumb", `;
307 			version(ARM_SoftFloat) ret ~= `"arm_softfloat", `;
308 			version(ARM_HardFloat) ret ~= `"arm_hardfloat", `;
309 			version(ARM64) ret ~= `"arm64", `;
310 			version(PPC) ret ~= `"ppc", `;
311 			version(PPC_SoftFP) ret ~= `"ppc_softfp", `;
312 			version(PPC_HardFP) ret ~= `"ppc_hardfp", `;
313 			version(PPC64) ret ~= `"ppc64", `;
314 			version(IA64) ret ~= `"ia64", `;
315 			version(MIPS) ret ~= `"mips", `;
316 			version(MIPS32) ret ~= `"mips32", `;
317 			version(MIPS64) ret ~= `"mips64", `;
318 			version(MIPS_O32) ret ~= `"mips_o32", `;
319 			version(MIPS_N32) ret ~= `"mips_n32", `;
320 			version(MIPS_O64) ret ~= `"mips_o64", `;
321 			version(MIPS_N64) ret ~= `"mips_n64", `;
322 			version(MIPS_EABI) ret ~= `"mips_eabi", `;
323 			version(MIPS_NoFloat) ret ~= `"mips_nofloat", `;
324 			version(MIPS_SoftFloat) ret ~= `"mips_softfloat", `;
325 			version(MIPS_HardFloat) ret ~= `"mips_hardfloat", `;
326 			version(SPARC) ret ~= `"sparc", `;
327 			version(SPARC_V8Plus) ret ~= `"sparc_v8plus", `;
328 			version(SPARC_SoftFP) ret ~= `"sparc_softfp", `;
329 			version(SPARC_HardFP) ret ~= `"sparc_hardfp", `;
330 			version(SPARC64) ret ~= `"sparc64", `;
331 			version(S390) ret ~= `"s390", `;
332 			version(S390X) ret ~= `"s390x", `;
333 			version(HPPA) ret ~= `"hppa", `;
334 			version(HPPA64) ret ~= `"hppa64", `;
335 			version(SH) ret ~= `"sh", `;
336 			version(SH64) ret ~= `"sh64", `;
337 			version(Alpha) ret ~= `"alpha", `;
338 			version(Alpha_SoftFP) ret ~= `"alpha_softfp", `;
339 			version(Alpha_HardFP) ret ~= `"alpha_hardfp", `;
340 			return ret;
341 		}
342 
343 		string determineCompiler()
344 		{
345 			version(DigitalMars) return "dmd";
346 			else version(GNU) return "gdc";
347 			else version(LDC) return "ldc";
348 			else version(SDC) return "sdc";
349 			else return null;
350 		}
351 	});
352 
353 	fil.close();
354 
355 	return path;
356 }
357 
358 /**
359 	Processes the output generated by compiling the platform probe file.
360 
361 	See_Also: `generatePlatformProbeFile`.
362 */
363 BuildPlatform readPlatformProbe(string output)
364 {
365 	import std.algorithm : map;
366 	import std.array : array;
367 	import std.exception : enforce;
368 	import std.string;
369 
370 	// work around possible additional output of the compiler
371 	auto idx1 = output.indexOf("{");
372 	auto idx2 = output.lastIndexOf("}");
373 	enforce(idx1 >= 0 && idx1 < idx2,
374 		"Unexpected platform information output - does not contain a JSON object.");
375 	output = output[idx1 .. idx2+1];
376 
377 	import dub.internal.vibecompat.data.json;
378 	auto json = parseJsonString(output);
379 
380 	BuildPlatform build_platform;
381 	build_platform.platform = json["platform"].get!(Json[]).map!(e => e.get!string()).array();
382 	build_platform.architecture = json["architecture"].get!(Json[]).map!(e => e.get!string()).array();
383 	build_platform.compiler = json["compiler"].get!string;
384 	build_platform.frontendVersion = json["frontendVersion"].get!int;
385 	return build_platform;
386 }