1 /**
2 	Compiler settings and abstraction.
3 
4 	Copyright: © 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.compiler;
9 
10 import dub.compilers.dmd;
11 import dub.compilers.gdc;
12 import dub.compilers.ldc;
13 import dub.internal.vibecompat.core.log;
14 import dub.internal.vibecompat.data.json;
15 import dub.internal.vibecompat.inet.path;
16 
17 import std.algorithm;
18 import std.array;
19 import std.conv;
20 import std.exception;
21 import std.process;
22 import std.path : globMatch;
23 
24 
25 static this()
26 {
27 	registerCompiler(new DmdCompiler);
28 	registerCompiler(new GdcCompiler);
29 	registerCompiler(new LdcCompiler);
30 }
31 
32 
33 Compiler getCompiler(string name)
34 {
35 	foreach (c; s_compilers)
36 		if (c.name == name)
37 			return c;
38 
39 	// try to match names like gdmd or gdc-2.61
40 	if (name.canFind("dmd")) return getCompiler("dmd");
41 	if (name.canFind("gdc")) return getCompiler("gdc");
42 	if (name.canFind("ldc")) return getCompiler("ldc");
43 			
44 	throw new Exception("Unknown compiler: "~name);
45 }
46 
47 void registerCompiler(Compiler c)
48 {
49 	s_compilers ~= c;
50 }
51 
52 void warnOnSpecialCompilerFlags(string[] compiler_flags, string package_name, string config_name)
53 {
54 	struct SpecialFlag {
55 		string[] flags;
56 		string alternative;
57 	}
58 	static immutable SpecialFlag[] s_specialFlags = [
59 		{["-c", "-o-"], "Automatically issued by DUB, do not specify in package.json"},
60 		{["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`},
61 		{["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"},
62 		{["-wi"], `Use the "buildRequirements" field to control warning behavior`},
63 		{["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`},
64 		{["-of"], `Use "targetPath" and "targetName" to customize the output file`},
65 		{["-debug", "-fdebug", "-g"], "Call dub with --build=debug"},
66 		{["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"},
67 		{["-unittest", "-funittest"], "Call dub with --build=unittest"},
68 		{["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`},
69 		{["-D"], "Call dub with --build=docs or --build=ddox"},
70 		{["-X"], "Call dub with --build=ddox"},
71 		{["-cov"], "Call dub with --build=cov or --build=unittest-cox"},
72 		{["-profile"], "Call dub with --build=profile"},
73 		{["-version="], `Use "versions" to specify version constants in a compiler independent way`},
74 		{["-debug=", `Use "debugVersions" to specify version constants in a compiler independent way`]},
75 		{["-I"], `Use "importPaths" to specify import paths in a compiler independent way`},
76 		{["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`},
77 	];
78 
79 	bool got_preamble = false;
80 	void outputPreamble()
81 	{
82 		if (got_preamble) return;
83 		got_preamble = true;
84 		logWarn("");
85 		if (config_name.empty) logWarn("## Warning for package %s ##", package_name);
86 		else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name);
87 		logWarn("");
88 		logWarn("The following compiler flags have been specified in the package description");
89 		logWarn("file. They are handled by DUB and direct use in packages is discouraged.");
90 		logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags");
91 		logWarn("to the compiler, or use one of the suggestions below:");
92 		logWarn("");
93 	}
94 
95 	foreach (f; compiler_flags) {
96 		foreach (sf; s_specialFlags) {
97 			if (sf.flags.canFind!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) {
98 				outputPreamble();
99 				logWarn("%s: %s", f, sf.alternative);
100 				break;
101 			}
102 		}
103 	}
104 
105 	if (got_preamble) logWarn("");
106 }
107 
108 
109 /**
110 	Alters the build options to comply with the specified build requirements.
111 */
112 void enforceBuildRequirements(ref BuildSettings settings)
113 {
114 	settings.addOptions(BuildOptions.warningsAsErrors);
115 	if (settings.requirements & BuildRequirements.allowWarnings) { settings.options &= ~BuildOptions.warningsAsErrors; settings.options |= BuildOptions.warnings; }
116 	if (settings.requirements & BuildRequirements.silenceWarnings) settings.options &= ~(BuildOptions.warningsAsErrors|BuildOptions.warnings);
117 	if (settings.requirements & BuildRequirements.disallowDeprecations) { settings.options &= ~(BuildOptions.ignoreDeprecations|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.deprecationErrors; }
118 	if (settings.requirements & BuildRequirements.silenceDeprecations) { settings.options &= ~(BuildOptions.deprecationErrors|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.ignoreDeprecations; }
119 	if (settings.requirements & BuildRequirements.disallowInlining) settings.options &= BuildOptions.inline;
120 	if (settings.requirements & BuildRequirements.disallowOptimization) settings.options &= ~BuildOptions.optimize;
121 	if (settings.requirements & BuildRequirements.requireBoundsCheck) settings.options &= ~BuildOptions.noBoundsCheck;
122 	if (settings.requirements & BuildRequirements.requireContracts) settings.options &= ~BuildOptions.releaseMode;
123 	if (settings.requirements & BuildRequirements.relaxProperties) settings.options &= ~BuildOptions.property;
124 }
125 
126 
127 /**
128 	Replaces each referenced import library by the appropriate linker flags.
129 
130 	This function tries to invoke "pkg-config" if possible and falls back to
131 	direct flag translation if that fails.
132 */
133 void resolveLibs(ref BuildSettings settings)
134 {
135 	if (settings.libs.length == 0) return;
136 
137 	if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) {
138 		logDiagnostic("Ignoring all import libraries for static library build.");
139 		settings.libs = null;
140 		version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array;
141 	}
142 	
143 	try {
144 		logDiagnostic("Trying to use pkg-config to resolve library flags for %s.", settings.libs);
145 		auto libflags = execute(["pkg-config", "--libs"] ~ settings.libs.map!(l => "lib"~l)().array());
146 		enforce(libflags.status == 0, "pkg-config exited with error code "~to!string(libflags.status));
147 		foreach (f; libflags.output.split()) {
148 			if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(","));
149 			else settings.addLFlags(f);
150 		}
151 		settings.libs = null;
152 	} catch (Exception e) {
153 		logDiagnostic("pkg-config failed: %s", e.msg);
154 		logDiagnostic("Falling back to direct -lxyz flags.");
155 	}
156 }
157 
158 
159 interface Compiler {
160 	@property string name() const;
161 
162 	BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null);
163 
164 	/// Replaces high level fields with low level fields and converts
165 	/// dmd flags to compiler-specific flags
166 	void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all);
167 
168 	/// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field.
169 	void extractBuildOptions(ref BuildSettings settings);
170 
171 	/// Adds the appropriate flag to set a target path
172 	void setTarget(ref BuildSettings settings, in BuildPlatform platform);
173 
174 	/// Invokes the compiler using the given flags
175 	void invoke(in BuildSettings settings, in BuildPlatform platform);
176 
177 	/// Invokes the underlying linker directly
178 	void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects);
179 }
180 
181 /// BuildPlatform specific settings, like needed libraries or additional
182 /// include paths.
183 struct BuildSettings {
184 	TargetType targetType;
185 	string targetPath;
186 	string targetName;
187 	string workingDirectory;
188 	string mainSourceFile;
189 	string[] dflags;
190 	string[] lflags;
191 	string[] libs;
192 	string[] sourceFiles;
193 	string[] copyFiles;
194 	string[] versions;
195 	string[] debugVersions;
196 	string[] importPaths;
197 	string[] stringImportPaths;
198 	string[] importFiles;
199 	string[] stringImportFiles;
200 	string[] preGenerateCommands;
201 	string[] postGenerateCommands;
202 	string[] preBuildCommands;
203 	string[] postBuildCommands;
204 	BuildRequirements requirements;
205 	BuildOptions options;
206 
207 	BuildSettings dup()
208 	const {
209 		BuildSettings ret;
210 		foreach (m; __traits(allMembers, BuildSettings)) {
211 			static if (is(typeof(__traits(getMember, ret, m) = __traits(getMember, this, m).dup)))
212 				__traits(getMember, ret, m) = __traits(getMember, this, m).dup;
213 			else static if (is(typeof(__traits(getMember, ret, m) = __traits(getMember, this, m))))
214 				__traits(getMember, ret, m) = __traits(getMember, this, m);
215 		}
216 		assert(ret.targetType == targetType);
217 		assert(ret.targetName == targetName);
218 		assert(ret.importPaths == importPaths);
219 		return ret;
220 	}
221 
222 	void add(in BuildSettings bs)
223 	{
224 		addDFlags(bs.dflags);
225 		addLFlags(bs.lflags);
226 		addLibs(bs.libs);
227 		addSourceFiles(bs.sourceFiles);
228 		addCopyFiles(bs.copyFiles);
229 		addVersions(bs.versions);
230 		addDebugVersions(bs.debugVersions);
231 		addImportPaths(bs.importPaths);
232 		addStringImportPaths(bs.stringImportPaths);
233 		addImportFiles(bs.importFiles);
234 		addStringImportFiles(bs.stringImportFiles);
235 		addPreGenerateCommands(bs.preGenerateCommands);
236 		addPostGenerateCommands(bs.postGenerateCommands);
237 		addPreBuildCommands(bs.preBuildCommands);
238 		addPostBuildCommands(bs.postBuildCommands);
239 	}
240 
241 	void addDFlags(in string[] value...) { dflags ~= value; }
242 	void removeDFlags(in string[] value...) { remove(dflags, value); }
243 	void addLFlags(in string[] value...) { lflags ~= value; }
244 	void addLibs(in string[] value...) { add(libs, value); }
245 	void addSourceFiles(in string[] value...) { add(sourceFiles, value); }
246 	void prependSourceFiles(in string[] value...) { prepend(sourceFiles, value); }
247 	void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); }
248 	void addCopyFiles(in string[] value...) { add(copyFiles, value); }
249 	void addVersions(in string[] value...) { add(versions, value); }
250 	void addDebugVersions(in string[] value...) { add(debugVersions, value); }
251 	void addImportPaths(in string[] value...) { add(importPaths, value); }
252 	void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); }
253 	void prependStringImportPaths(in string[] value...) { prepend(stringImportPaths, value); }
254 	void addImportFiles(in string[] value...) { add(importFiles, value); }
255 	void removeImportFiles(in string[] value...) { removePaths(importFiles, value); }
256 	void addStringImportFiles(in string[] value...) { add(stringImportFiles, value); }
257 	void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); }
258 	void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); }
259 	void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); }
260 	void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); }
261 	void addRequirements(in BuildRequirements[] value...) { foreach (v; value) this.requirements |= v; }
262 	void addOptions(in BuildOptions[] value...) { foreach (v; value) this.options |= v; }
263 	void removeOptions(in BuildOptions[] value...) { foreach (v; value) this.options &= ~v; }
264 
265 	// Adds vals to arr without adding duplicates.
266 	private void add(ref string[] arr, in string[] vals, bool no_duplicates = true)
267 	{
268 		if (!no_duplicates) {
269 			arr ~= vals;
270 			return;
271 		}
272 
273 		foreach (v; vals) {
274 			bool found = false;
275 			foreach (i; 0 .. arr.length)
276 				if (arr[i] == v) {
277 					found = true;
278 					break;
279 				}
280 			if (!found) arr ~= v;
281 		}
282 	}
283 
284 	private void prepend(ref string[] arr, in string[] vals, bool no_duplicates = true)
285 	{
286 		if (!no_duplicates) {
287 			arr = vals ~ arr;
288 			return;
289 		}
290 
291 		foreach_reverse (v; vals) {
292 			bool found = false;
293 			foreach (i; 0 .. arr.length)
294 				if (arr[i] == v) {
295 					found = true;
296 					break;
297 				}
298 			if (!found) arr = v ~ arr;
299 		}
300 	}
301 
302 	private void removePaths(ref string[] arr, in string[] vals)
303 	{
304 		bool matches(string s)
305 		{
306 			foreach (p; vals)
307 				if (Path(s) == Path(p) || globMatch(s, p))
308 					return true;
309 			return false;
310 		}
311 		arr = arr.filter!(s => !matches(s))().array();
312 	}
313 
314 	private void remove(ref string[] arr, in string[] vals)
315 	{
316 		bool matches(string s)
317 		{
318 			foreach (p; vals)
319 				if (s == p)
320 					return true;
321 			return false;
322 		}
323 		arr = arr.filter!(s => !matches(s))().array();
324 	}
325 }
326 
327 /// Represents a platform a package can be build upon.
328 struct BuildPlatform {
329 	/// e.g. ["posix", "windows"]
330 	string[] platform;
331 	/// e.g. ["x86", "x86_64"]
332 	string[] architecture;
333 	/// Canonical compiler name e.g. "dmd"
334 	string compiler;
335 	/// Compiler binary name e.g. "ldmd2"
336 	string compilerBinary;
337 
338 	/// Build platforms can be specified via a string specification.
339 	///
340 	/// Specifications are build upon the following scheme, where each component
341 	/// is optional (indicated by []), but the order is obligatory.
342 	/// "[-platform][-architecture][-compiler]"
343 	///
344 	/// So the following strings are valid specifications:
345 	/// "-windows-x86-dmd"
346 	/// "-dmd"
347 	/// "-arm"
348 	/// "-arm-dmd"
349 	/// "-windows-dmd"
350 	///
351 	/// Params:
352 	///     specification = The specification being matched. It must be the empty string or start with a dash.  
353 	///
354 	/// Returns: 
355 	///     true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches)
356 	///
357 	bool matchesSpecification(const(char)[] specification) const {
358 		if (specification.empty)
359 			return true;
360 		auto splitted=specification.splitter('-');
361 		assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!");
362 		splitted.popFront(); // Drop leading empty match.
363 		enforce(!splitted.empty, "Platform specification if present, must not be empty!");
364 		if (platform.canFind(splitted.front)) {
365 			splitted.popFront();
366 			if(splitted.empty)
367 			    return true;
368 		}
369 		if (architecture.canFind(splitted.front)) {
370 			splitted.popFront();
371 			if(splitted.empty)
372 			    return true;
373 		}
374 		if (compiler == splitted.front) {
375 			splitted.popFront();
376 			enforce(splitted.empty, "No valid specification! The compiler has to be the last element!");
377 			return true;
378 		}
379 		return false;
380 	}
381 	unittest {
382 		auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd");
383 		assert(platform.matchesSpecification("-posix"));
384 		assert(platform.matchesSpecification("-linux"));
385 		assert(platform.matchesSpecification("-linux-dmd"));
386 		assert(platform.matchesSpecification("-linux-x86_64-dmd"));
387 		assert(platform.matchesSpecification("-x86_64"));
388 		assert(!platform.matchesSpecification("-windows"));
389 		assert(!platform.matchesSpecification("-ldc"));
390 		assert(!platform.matchesSpecification("-windows-dmd"));
391 	}
392 }
393 
394 enum BuildSetting {
395 	dflags            = 1<<0,
396 	lflags            = 1<<1,
397 	libs              = 1<<2,
398 	sourceFiles       = 1<<3,
399 	copyFiles         = 1<<4,
400 	versions          = 1<<5,
401 	debugVersions     = 1<<6,
402 	importPaths       = 1<<7,
403 	stringImportPaths = 1<<8,
404 	options           = 1<<9,
405 	none = 0,
406 	commandLine = dflags|copyFiles,
407 	commandLineSeparate = commandLine|lflags,
408 	all = dflags|lflags|libs|sourceFiles|copyFiles|versions|debugVersions|importPaths|stringImportPaths|options,
409 	noOptions = all & ~options
410 }
411 
412 enum TargetType {
413 	autodetect,
414 	none,
415 	executable,
416 	library,
417 	sourceLibrary,
418 	dynamicLibrary,
419 	staticLibrary
420 }
421 
422 enum BuildRequirements {
423 	none = 0,                     /// No special requirements
424 	allowWarnings        = 1<<0,  /// Warnings do not abort compilation
425 	silenceWarnings      = 1<<1,  /// Don't show warnings
426 	disallowDeprecations = 1<<2,  /// Using deprecated features aborts compilation
427 	silenceDeprecations  = 1<<3,  /// Don't show deprecation warnings
428 	disallowInlining     = 1<<4,  /// Avoid function inlining, even in release builds
429 	disallowOptimization = 1<<5,  /// Avoid optimizations, even in release builds
430 	requireBoundsCheck   = 1<<6,  /// Always perform bounds checks
431 	requireContracts     = 1<<7,  /// Leave assertions and contracts enabled in release builds
432 	relaxProperties      = 1<<8,  /// DEPRECATED: Do not enforce strict property handling (-property)
433 	noDefaultFlags       = 1<<9,  /// Do not issue any of the default build flags (e.g. -debug, -w, -property etc.) - use only for development purposes
434 }
435 
436 enum BuildOptions {
437 	none = 0,                     /// Use compiler defaults
438 	debugMode = 1<<0,             /// Compile in debug mode (enables contracts, -debug)
439 	releaseMode = 1<<1,           /// Compile in release mode (disables assertions and bounds checks, -release)
440 	coverage = 1<<2,              /// Enable code coverage analysis (-cov)
441 	debugInfo = 1<<3,             /// Enable symbolic debug information (-g)
442 	debugInfoC = 1<<4,            /// Enable symbolic debug information in C compatible form (-gc)
443 	alwaysStackFrame = 1<<5,      /// Always generate a stack frame (-gs)
444 	stackStomping = 1<<6,         /// Perform stack stomping (-gx)
445 	inline = 1<<7,                /// Perform function inlining (-inline)
446 	noBoundsCheck = 1<<8,         /// Disable all bounds checking (-noboundscheck)
447 	optimize = 1<<9,              /// Enable optimizations (-O)
448 	profile = 1<<10,              /// Emit profiling code (-profile)
449 	unittests = 1<<11,            /// Compile unit tests (-unittest)
450 	verbose = 1<<12,              /// Verbose compiler output (-v)
451 	ignoreUnknownPragmas = 1<<13, /// Ignores unknown pragmas during compilation (-ignore)
452 	syntaxOnly = 1<<14,           /// Don't generate object files (-o-)
453 	warnings = 1<<15,             /// Enable warnings (-wi)
454 	warningsAsErrors = 1<<16,     /// Treat warnings as errors (-w)
455 	ignoreDeprecations = 1<<17,   /// Do not warn about using deprecated features (-d)
456 	deprecationWarnings = 1<<18,  /// Warn about using deprecated features (-dw)
457 	deprecationErrors = 1<<19,    /// Stop compilation upon usage of deprecated features (-de)
458 	property = 1<<20,             /// DEPRECATED: Enforce property syntax (-property)
459 }
460 
461 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform)
462 {
463 	assert(settings.targetName.length > 0, "No target name set.");
464 	final switch (settings.targetType) {
465 		case TargetType.autodetect: assert(false, "Configurations must have a concrete target type.");
466 		case TargetType.none: return null;
467 		case TargetType.sourceLibrary: return null;
468 		case TargetType.executable:
469 			if( platform.platform.canFind("windows") )
470 				return settings.targetName ~ ".exe";
471 			else return settings.targetName;
472 		case TargetType.library:
473 		case TargetType.staticLibrary:
474 			if (platform.platform.canFind("windows") && platform.compiler == "dmd")
475 				return settings.targetName ~ ".lib";
476 			else return "lib" ~ settings.targetName ~ ".a";
477 		case TargetType.dynamicLibrary:
478 			if( platform.platform.canFind("windows") )
479 				return settings.targetName ~ ".dll";
480 			else return "lib" ~ settings.targetName ~ ".so";
481 	}
482 } 
483 
484 
485 bool isLinkerFile(string f)
486 {
487 	import std.path;
488 	switch (extension(f)) {
489 		default:
490 			return false;
491 		version (Windows) {
492 			case ".lib", ".obj", ".res":
493 				return true;
494 		} else {
495 			case ".a", ".o", ".so", ".dylib":
496 				return true;
497 		}
498 	}
499 }
500 
501 private {
502 	Compiler[] s_compilers;
503 }