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 : BuildPlatform, archCheck, compilerCheck, platformCheck;
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 	import std.array : array;
87 
88 	if (settings.libs.length == 0) return;
89 
90 	if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) {
91 		logDiagnostic("Ignoring all import libraries for static library build.");
92 		settings.libs = null;
93 		version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array;
94 	}
95 
96 	version (Posix) {
97 		import std.algorithm : any, map, partition, startsWith;
98 		import std.array : array, join, split;
99 		import std.exception : enforce;
100 		import std.process : execute;
101 
102 		try {
103 			enum pkgconfig_bin = "pkg-config";
104 
105 			bool exists(string lib) {
106 				return execute([pkgconfig_bin, "--exists", lib]).status == 0;
107 			}
108 
109 			auto pkgconfig_libs = settings.libs.partition!(l => !exists(l));
110 			pkgconfig_libs ~= settings.libs[0 .. $ - pkgconfig_libs.length]
111 				.partition!(l => !exists("lib"~l)).map!(l => "lib"~l).array;
112 			settings.libs = settings.libs[0 .. $ - pkgconfig_libs.length];
113 
114 			if (pkgconfig_libs.length) {
115 				logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.join(", "));
116 				auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs);
117 				enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output));
118 				foreach (f; libflags.output.split()) {
119 					if (f.startsWith("-L-L")) {
120 						settings.addLFlags(f[2 .. $]);
121 					} else if (f.startsWith("-defaultlib")) {
122 						settings.addDFlags(f);
123 					} else if (f.startsWith("-L-defaultlib")) {
124 						settings.addDFlags(f[2 .. $]);
125 					} else if (f.startsWith("-pthread")) {
126 						settings.addLFlags("-lpthread");
127 					} else if (f.startsWith("-L-l")) {
128 						settings.addLFlags(f[2 .. $].split(","));
129 					} else if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(","));
130 					else settings.addLFlags(f);
131 				}
132 			}
133 			if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", "));
134 		} catch (Exception e) {
135 			logDiagnostic("pkg-config failed: %s", e.msg);
136 			logDiagnostic("Falling back to direct -l... flags.");
137 		}
138 	}
139 }
140 
141 
142 /** Searches the given list of compiler flags for ones that have a generic
143 	equivalent.
144 
145 	Certain compiler flags should, instead of using compiler-specific syntax,
146 	be specified as build options (`BuildOptions`) or built requirements
147 	(`BuildRequirements`). This function will output warning messages to
148 	assist the user in making the best choice.
149 */
150 void warnOnSpecialCompilerFlags(string[] compiler_flags, BuildOptions options, string package_name, string config_name)
151 {
152 	import std.algorithm : any, endsWith, startsWith;
153 	import std.range : empty;
154 
155 	struct SpecialFlag {
156 		string[] flags;
157 		string alternative;
158 	}
159 	static immutable SpecialFlag[] s_specialFlags = [
160 		{["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"},
161 		{["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`},
162 		{["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"},
163 		{["-wi"], `Use the "buildRequirements" field to control warning behavior`},
164 		{["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`},
165 		{["-of"], `Use "targetPath" and "targetName" to customize the output file`},
166 		{["-debug", "-fdebug", "-g"], "Call dub with --build=debug"},
167 		{["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"},
168 		{["-unittest", "-funittest"], "Call dub with --build=unittest"},
169 		{["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`},
170 		{["-D"], "Call dub with --build=docs or --build=ddox"},
171 		{["-X"], "Call dub with --build=ddox"},
172 		{["-cov"], "Call dub with --build=cov or --build=unittest-cov"},
173 		{["-profile"], "Call dub with --build=profile"},
174 		{["-version="], `Use "versions" to specify version constants in a compiler independent way`},
175 		{["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`},
176 		{["-I"], `Use "importPaths" to specify import paths in a compiler independent way`},
177 		{["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`},
178 		{["-m32", "-m64", "-m32mscoff"], `Use --arch=x86/--arch=x86_64/--arch=x86_mscoff to specify the target architecture, e.g. 'dub build --arch=x86_64'`}
179 	];
180 
181 	struct SpecialOption {
182 		BuildOption[] flags;
183 		string alternative;
184 	}
185 	static immutable SpecialOption[] s_specialOptions = [
186 		{[BuildOption.debugMode], "Call DUB with --build=debug"},
187 		{[BuildOption.releaseMode], "Call DUB with --build=release"},
188 		{[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"},
189 		{[BuildOption.debugInfo], "Call DUB with --build=debug"},
190 		{[BuildOption.inline], "Call DUB with --build=release"},
191 		{[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"},
192 		{[BuildOption.optimize], "Call DUB with --build=release"},
193 		{[BuildOption.profile], "Call DUB with --build=profile"},
194 		{[BuildOption.unittests], "Call DUB with --build=unittest"},
195 		{[BuildOption.syntaxOnly], "Call DUB with --build=syntax"},
196 		{[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"},
197 		{[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"},
198 		{[BuildOption.property], "This flag is deprecated and has no effect"}
199 	];
200 
201 	bool got_preamble = false;
202 	void outputPreamble()
203 	{
204 		if (got_preamble) return;
205 		got_preamble = true;
206 		logWarn("");
207 		if (config_name.empty) logWarn("## Warning for package %s ##", package_name);
208 		else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name);
209 		logWarn("");
210 		logWarn("The following compiler flags have been specified in the package description");
211 		logWarn("file. They are handled by DUB and direct use in packages is discouraged.");
212 		logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags");
213 		logWarn("to the compiler, or use one of the suggestions below:");
214 		logWarn("");
215 	}
216 
217 	foreach (f; compiler_flags) {
218 		foreach (sf; s_specialFlags) {
219 			if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) {
220 				outputPreamble();
221 				logWarn("%s: %s", f, sf.alternative);
222 				break;
223 			}
224 		}
225 	}
226 
227 	foreach (sf; s_specialOptions) {
228 		foreach (f; sf.flags) {
229 			if (options & f) {
230 				outputPreamble();
231 				logWarn("%s: %s", f, sf.alternative);
232 				break;
233 			}
234 		}
235 	}
236 
237 	if (got_preamble) logWarn("");
238 }
239 
240 /**
241 	Turn a DMD-like version (e.g. 2.082.1) into a SemVer-like version (e.g. 2.82.1).
242     The function accepts a dependency operator prefix and some text postfix.
243     Prefix and postfix are returned verbatim.
244 	Params:
245 		ver	=	version string, possibly with a dependency operator prefix and some
246 				test postfix.
247 	Returns:
248 		A Semver compliant string
249 */
250 package(dub) string dmdLikeVersionToSemverLike(string ver)
251 {
252 	import std.algorithm : countUntil, joiner, map, skipOver, splitter;
253 	import std.array : join, split;
254 	import std.ascii : isDigit;
255 	import std.conv : text;
256 	import std.exception : enforce;
257 	import std.functional : not;
258 	import std.range : padRight;
259 
260 	const start = ver.countUntil!isDigit;
261 	enforce(start != -1, "Invalid semver: "~ver);
262 	const prefix = ver[0 .. start];
263 	ver = ver[start .. $];
264 
265 	const end = ver.countUntil!(c => !c.isDigit && c != '.');
266 	const postfix = end == -1 ? null : ver[end .. $];
267 	auto verStr = ver[0 .. $-postfix.length];
268 
269 	auto comps = verStr
270 		.splitter(".")
271 		.map!((a) { if (a.length > 1) a.skipOver("0"); return a;})
272 		.padRight("0", 3);
273 
274 	return text(prefix, comps.joiner("."), postfix);
275 }
276 
277 ///
278 unittest {
279 	assert(dmdLikeVersionToSemverLike("2.082.1") == "2.82.1");
280 	assert(dmdLikeVersionToSemverLike("2.082.0") == "2.82.0");
281 	assert(dmdLikeVersionToSemverLike("2.082") == "2.82.0");
282 	assert(dmdLikeVersionToSemverLike("~>2.082") == "~>2.82.0");
283 	assert(dmdLikeVersionToSemverLike("~>2.082-beta1") == "~>2.82.0-beta1");
284 	assert(dmdLikeVersionToSemverLike("2.4.6") == "2.4.6");
285 	assert(dmdLikeVersionToSemverLike("2.4.6-alpha12") == "2.4.6-alpha12");
286 }
287 
288 private enum probeBeginMark = "__dub_probe_begin__";
289 private enum probeEndMark = "__dub_probe_end__";
290 
291 /**
292 	Generate a file that will give, at compile time, information about the compiler (architecture, frontend version...)
293 
294 	See_Also: `readPlatformProbe`
295 */
296 NativePath generatePlatformProbeFile()
297 {
298 	import dub.internal.vibecompat.core.file;
299 	import dub.internal.vibecompat.data.json;
300 	import dub.internal.utils;
301 
302 	// try to not use phobos in the probe to avoid long import times
303 	enum probe = q{
304 		module dub_platform_probe;
305 
306 		template toString(int v) { enum toString = v.stringof; }
307 		string stringArray(string[] ary) {
308 			string res;
309 			foreach (i, e; ary) {
310 				if (i)
311 					res ~= ", ";
312 				res ~= '"' ~ e ~ '"';
313 			}
314 			return res;
315 		}
316 
317 		pragma(msg, } ~ `"` ~ probeBeginMark ~ `"` ~q{ );
318 		pragma(msg, `{`);
319 		pragma(msg,`  "compiler": "`~ determineCompiler() ~ `",`);
320 		pragma(msg, `  "frontendVersion": ` ~ toString!__VERSION__ ~ `,`);
321 		pragma(msg, `  "compilerVendor": "` ~ __VENDOR__ ~ `",`);
322 		pragma(msg, `  "platform": [`);
323 		pragma(msg, `    ` ~ determinePlatform().stringArray);
324 		pragma(msg, `  ],`);
325 		pragma(msg, `  "architecture": [`);
326 		pragma(msg, `    ` ~ determineArchitecture().stringArray);
327 		pragma(msg, `   ],`);
328 		pragma(msg, `}`);
329 		pragma(msg, } ~ `"` ~ probeEndMark ~ `"` ~ q{ );
330 
331 		string[] determinePlatform() } ~ '{' ~ platformCheck ~ '}' ~ q{
332 		string[] determineArchitecture() } ~ '{' ~ archCheck ~ '}' ~ q{
333 		string determineCompiler() } ~ '{' ~ compilerCheck ~ '}';
334 
335 	auto path = getTempFile("dub_platform_probe", ".d");
336 	auto fil = openFile(path, FileMode.createTrunc);
337 	fil.write(probe);
338 
339 	return path;
340 }
341 
342 /**
343 	Processes the JSON output generated by compiling the platform probe file.
344 
345 	See_Also: `generatePlatformProbeFile`.
346 */
347 BuildPlatform readPlatformJsonProbe(string output)
348 {
349 	import std.algorithm : map;
350 	import std.array : array;
351 	import std.exception : enforce;
352 	import std..string;
353 
354 	// work around possible additional output of the compiler
355 	auto idx1 = output.indexOf(probeBeginMark);
356 	auto idx2 = output.lastIndexOf(probeEndMark);
357 	enforce(idx1 >= 0 && idx1 < idx2,
358 		"Unexpected platform information output - does not contain a JSON object.");
359 	output = output[idx1+probeBeginMark.length .. idx2];
360 
361 	import dub.internal.vibecompat.data.json;
362 	auto json = parseJsonString(output);
363 
364 	BuildPlatform build_platform;
365 	build_platform.platform = json["platform"].get!(Json[]).map!(e => e.get!string()).array();
366 	build_platform.architecture = json["architecture"].get!(Json[]).map!(e => e.get!string()).array();
367 	build_platform.compiler = json["compiler"].get!string;
368 	build_platform.frontendVersion = json["frontendVersion"].get!int;
369 	return build_platform;
370 }