1 /**
2 	Build settings definitions.
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.buildsettings;
9 
10 import dub.internal.vibecompat.inet.path;
11 
12 import dub.internal.configy.Attributes;
13 
14 import std.array : array;
15 import std.algorithm : filter, any;
16 import std.path : globMatch;
17 import std.typecons : BitFlags;
18 import std.algorithm.iteration : uniq;
19 import std.range : chain;
20 
21 /// BuildPlatform specific settings, like needed libraries or additional
22 /// include paths.
23 struct BuildSettings {
24 	import dub.internal.vibecompat.data.serialization : byName;
25 
26 	TargetType targetType;
27 	string targetPath;
28 	string targetName;
29 	string workingDirectory;
30 	string mainSourceFile;
31 	string[] dflags;
32 	string[] lflags;
33 	string[] libs;
34 	string[] linkerFiles;
35 	string[] sourceFiles;
36 	string[] injectSourceFiles;
37 	string[] copyFiles;
38 	string[] extraDependencyFiles;
39 	string[] versions;
40 	string[] debugVersions;
41 	string[] versionFilters;
42 	string[] debugVersionFilters;
43 	string[] importPaths;
44 	string[] stringImportPaths;
45 	string[] importFiles;
46 	string[] stringImportFiles;
47 	string[] preGenerateCommands;
48 	string[] postGenerateCommands;
49 	string[] preBuildCommands;
50 	string[] postBuildCommands;
51 	string[] preRunCommands;
52 	string[] postRunCommands;
53 	string[string] environments;
54 	string[string] buildEnvironments;
55 	string[string] runEnvironments;
56 	string[string] preGenerateEnvironments;
57 	string[string] postGenerateEnvironments;
58 	string[string] preBuildEnvironments;
59 	string[string] postBuildEnvironments;
60 	string[string] preRunEnvironments;
61 	string[string] postRunEnvironments;
62 	@byName Flags!BuildRequirement requirements;
63 	@byName Flags!BuildOption options;
64 
65 	BuildSettings dup()
66 	const {
67 		import std.traits: FieldNameTuple;
68 		import std.algorithm: map;
69 		import std.typecons: tuple;
70 		import std.array: assocArray;
71 		BuildSettings ret;
72 		foreach (m; FieldNameTuple!BuildSettings) {
73 			static if (is(typeof(__traits(getMember, ret, m) = __traits(getMember, this, m).dup)))
74 				__traits(getMember, ret, m) = __traits(getMember, this, m).dup;
75 			else static if (is(typeof(add(__traits(getMember, ret, m), __traits(getMember, this, m)))))
76 				add(__traits(getMember, ret, m), __traits(getMember, this, m));
77 			else static if (is(typeof(__traits(getMember, ret, m) = __traits(getMember, this, m))))
78 				__traits(getMember, ret, m) = __traits(getMember, this, m);
79 			else static assert(0, "Cannot duplicate BuildSettings." ~ m);
80 		}
81 		assert(ret.targetType == targetType);
82 		assert(ret.targetName == targetName);
83 		assert(ret.importPaths == importPaths);
84 		return ret;
85 	}
86 
87 	/**
88 	 * Merges $(LREF bs) onto `this` BuildSettings instance. This is called for
89 	 * sourceLibrary dependencies when they are included in the build to be
90 	 * merged into the root package build settings as well as configuring
91 	 * targets for different build types such as release or unittest-cov.
92 	 */
93 	void add(in BuildSettings bs)
94 	{
95 		addDFlags(bs.dflags);
96 		addLFlags(bs.lflags);
97 		addLibs(bs.libs);
98 		addLinkerFiles(bs.linkerFiles);
99 		addSourceFiles(bs.sourceFiles);
100 		addInjectSourceFiles(bs.injectSourceFiles);
101 		addCopyFiles(bs.copyFiles);
102 		addExtraDependencyFiles(bs.extraDependencyFiles);
103 		addVersions(bs.versions);
104 		addDebugVersions(bs.debugVersions);
105 		addVersionFilters(bs.versionFilters);
106 		addDebugVersionFilters(bs.debugVersionFilters);
107 		addImportPaths(bs.importPaths);
108 		addStringImportPaths(bs.stringImportPaths);
109 		addImportFiles(bs.importFiles);
110 		addStringImportFiles(bs.stringImportFiles);
111 		addPreGenerateCommands(bs.preGenerateCommands);
112 		addPostGenerateCommands(bs.postGenerateCommands);
113 		addPreBuildCommands(bs.preBuildCommands);
114 		addPostBuildCommands(bs.postBuildCommands);
115 		addPreRunCommands(bs.preRunCommands);
116 		addPostRunCommands(bs.postRunCommands);
117 		addEnvironments(bs.environments);
118 		addBuildEnvironments(bs.buildEnvironments);
119 		addRunEnvironments(bs.runEnvironments);
120 		addPreGenerateEnvironments(bs.preGenerateEnvironments);
121 		addPostGenerateEnvironments(bs.postGenerateEnvironments);
122 		addPreBuildEnvironments(bs.preBuildEnvironments);
123 		addPostBuildEnvironments(bs.postBuildEnvironments);
124 		addPreRunEnvironments(bs.preRunEnvironments);
125 		addPostRunEnvironments(bs.postRunEnvironments);
126 		addRequirements(bs.requirements);
127 		addOptions(bs.options);
128 	}
129 
130 	void addDFlags(in string[] value...) { dflags = chain(dflags, value.dup).uniq.array; }
131 	void prependDFlags(in string[] value...) { prepend(dflags, value); }
132 	void removeDFlags(in string[] value...) { remove(dflags, value); }
133 	void addLFlags(in string[] value...) { lflags ~= value; }
134 	void prependLFlags(in string[] value...) { prepend(lflags, value, false); }
135 	void addLibs(in string[] value...) { add(libs, value); }
136 	void addLinkerFiles(in string[] value...) { add(linkerFiles, value); }
137 	void addSourceFiles(in string[] value...) { add(sourceFiles, value); }
138 	void prependSourceFiles(in string[] value...) { prepend(sourceFiles, value); }
139 	void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); }
140 	void addInjectSourceFiles(in string[] value...) { add(injectSourceFiles, value); }
141 	void addCopyFiles(in string[] value...) { add(copyFiles, value); }
142 	void addExtraDependencyFiles(in string[] value...) { add(extraDependencyFiles, value); }
143 	void addVersions(in string[] value...) { add(versions, value); }
144 	void addDebugVersions(in string[] value...) { add(debugVersions, value); }
145 	void addVersionFilters(in string[] value...) { add(versionFilters, value); }
146 	void addDebugVersionFilters(in string[] value...) { add(debugVersionFilters, value); }
147 	void addImportPaths(in string[] value...) { add(importPaths, value); }
148 	void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); }
149 	void prependStringImportPaths(in string[] value...) { prepend(stringImportPaths, value); }
150 	void addImportFiles(in string[] value...) { add(importFiles, value); }
151 	void addStringImportFiles(in string[] value...) { addSI(stringImportFiles, value); }
152 	void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); }
153 	void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); }
154 	void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); }
155 	void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); }
156 	void addPreRunCommands(in string[] value...) { add(preRunCommands, value, false); }
157 	void addPostRunCommands(in string[] value...) { add(postRunCommands, value, false); }
158 	void addEnvironments(in string[string] value) { add(environments, value); }
159 	void updateEnvironments(in string[string] value) { update(environments, value); }
160 	void addBuildEnvironments(in string[string] value) { add(buildEnvironments, value); }
161 	void updateBuildEnvironments(in string[string] value) { update(buildEnvironments, value); }
162 	void addRunEnvironments(in string[string] value) { add(runEnvironments, value); }
163 	void updateRunEnvironments(in string[string] value) { update(runEnvironments, value); }
164 	void addPreGenerateEnvironments(in string[string] value) { add(preGenerateEnvironments, value); }
165 	void updatePreGenerateEnvironments(in string[string] value) { update(preGenerateEnvironments, value); }
166 	void addPostGenerateEnvironments(in string[string] value) { add(postGenerateEnvironments, value); }
167 	void updatePostGenerateEnvironments(in string[string] value) { update(postGenerateEnvironments, value); }
168 	void addPreBuildEnvironments(in string[string] value) { add(preBuildEnvironments, value); }
169 	void updatePreBuildEnvironments(in string[string] value) { update(preBuildEnvironments, value); }
170 	void addPostBuildEnvironments(in string[string] value) { add(postBuildEnvironments, value); }
171 	void updatePostBuildEnvironments(in string[string] value) { update(postBuildEnvironments, value); }
172 	void addPreRunEnvironments(in string[string] value) { add(preRunEnvironments, value); }
173 	void updatePreRunEnvironments(in string[string] value) { update(preRunEnvironments, value); }
174 	void addPostRunEnvironments(in string[string] value) { add(postRunEnvironments, value); }
175 	void updatePostRunEnvironments(in string[string] value) { update(postRunEnvironments, value); }
176 	void addRequirements(in BuildRequirement[] value...) { foreach (v; value) this.requirements |= v; }
177 	void addRequirements(in Flags!BuildRequirement value) { this.requirements |= value; }
178 	void addOptions(in BuildOption[] value...) { foreach (v; value) this.options |= v; }
179 	void addOptions(in Flags!BuildOption value) { this.options |= value; }
180 	void removeOptions(in BuildOption[] value...) { foreach (v; value) this.options &= ~v; }
181 	void removeOptions(in Flags!BuildOption value) { this.options &= ~value; }
182 
183 private:
184 	static auto filterDuplicates(T)(ref string[] arr, in T vals, bool noDuplicates = true)
185 	{
186 		return noDuplicates
187 			? vals.filter!(filtered => !arr.any!(item => item == filtered)).array
188 			: vals;
189 	}
190 
191 	// Append vals to arr without adding duplicates.
192 	static void add(ref string[] arr, in string[] vals, bool noDuplicates = true)
193 	{
194 		// vals might contain duplicates, add each val individually
195 		foreach (val; vals)
196 			arr ~= filterDuplicates(arr, [val], noDuplicates);
197 	}
198 	// Append vals to AA
199 	static void add(ref string[string] aa, in string[string] vals)
200 	{
201 		// vals might contain duplicated keys, add each val individually
202 		foreach (key, val; vals)
203 			if (key !in aa)
204 				aa[key] = val;
205 	}
206 	// Update vals to AA
207 	static void update(ref string[string] aa, in string[string] vals)
208 	{
209 		// If there are duplicate keys, they will be ignored and overwritten.
210 		foreach (key, val; vals)
211 			aa[key] = val;
212 	}
213 
214 	unittest
215 	{
216 		auto ary = ["-dip1000", "-vgc"];
217 		BuildSettings.add(ary, ["-dip1000", "-vgc"]);
218 		assert(ary == ["-dip1000", "-vgc"]);
219 		BuildSettings.add(ary, ["-dip1001", "-vgc"], false);
220 		assert(ary == ["-dip1000", "-vgc", "-dip1001", "-vgc"]);
221 		BuildSettings.add(ary, ["-dupflag", "-notdupflag", "-dupflag"]);
222 		assert(ary == ["-dip1000", "-vgc", "-dip1001", "-vgc", "-dupflag", "-notdupflag"]);
223 	}
224 
225 	// Prepend arr by vals without adding duplicates.
226 	static void prepend(ref string[] arr, in string[] vals, bool noDuplicates = true)
227 	{
228 		import std.range : retro;
229 		// vals might contain duplicates, add each val individually
230 		foreach (val; vals.retro)
231 			arr = filterDuplicates(arr, [val], noDuplicates) ~ arr;
232 	}
233 
234 	unittest
235 	{
236 		auto ary = ["-dip1000", "-vgc"];
237 		BuildSettings.prepend(ary, ["-dip1000", "-vgc"]);
238 		assert(ary == ["-dip1000", "-vgc"]);
239 		BuildSettings.prepend(ary, ["-dip1001", "-vgc"], false);
240 		assert(ary == ["-dip1001", "-vgc", "-dip1000", "-vgc"]);
241 		BuildSettings.prepend(ary, ["-dupflag", "-notdupflag", "-dupflag"]);
242 		assert(ary == ["-notdupflag", "-dupflag", "-dip1001", "-vgc", "-dip1000", "-vgc"]);
243 	}
244 
245 	// add string import files (avoids file name duplicates in addition to path duplicates)
246 	static void addSI(ref string[] arr, in string[] vals)
247 	{
248 		bool[string] existing;
249 		foreach (v; arr) existing[NativePath(v).head.name] = true;
250 		foreach (v; vals) {
251 			auto s = NativePath(v).head.name;
252 			if (s !in existing) {
253 				existing[s] = true;
254 				arr ~= v;
255 			}
256 		}
257 	}
258 
259 	unittest
260 	{
261 		auto ary = ["path/foo.txt"];
262 		BuildSettings.addSI(ary, ["path2/foo2.txt"]);
263 		assert(ary == ["path/foo.txt", "path2/foo2.txt"]);
264 		BuildSettings.addSI(ary, ["path2/foo.txt"]); // no duplicate basenames
265 		assert(ary == ["path/foo.txt", "path2/foo2.txt"]);
266 	}
267 
268 	static bool pathMatch(string path, string pattern)
269 	{
270 		import std.functional : memoize;
271 
272 		alias nativePath = memoize!((string stringPath) => NativePath(stringPath));
273 
274 		return nativePath(path) == nativePath(pattern) || globMatch(path, pattern);
275 	}
276 
277 	static void removeValuesFromArray(alias Match)(ref string[] arr, in string[] vals)
278 	{
279 		bool matches(string s)
280 		{
281 			return vals.any!(item => Match(s, item));
282 		}
283 		arr = arr.filter!(s => !matches(s)).array;
284 	}
285 
286 	static void removePaths(ref string[] arr, in string[] vals)
287 	{
288 		removeValuesFromArray!(pathMatch)(arr, vals);
289 	}
290 
291 	unittest
292 	{
293 		auto ary = ["path1", "root/path1", "root/path2", "root2/path1"];
294 		BuildSettings.removePaths(ary, ["path1"]);
295 		assert(ary == ["root/path1", "root/path2", "root2/path1"]);
296 		BuildSettings.removePaths(ary, ["*/path1"]);
297 		assert(ary == ["root/path2"]);
298 		BuildSettings.removePaths(ary, ["foo", "bar", "root/path2"]);
299 		assert(ary == []);
300 	}
301 
302 	static void remove(ref string[] arr, in string[] vals)
303 	{
304 		removeValuesFromArray!((a, b) => a == b)(arr, vals);
305 	}
306 
307 	unittest
308 	{
309 		import std.string : join;
310 
311 		auto ary = ["path1", "root/path1", "root/path2", "root2/path1"];
312 		BuildSettings.remove(ary, ["path1"]);
313 		assert(ary == ["root/path1", "root/path2", "root2/path1"]);
314 		BuildSettings.remove(ary, ["root/path*"]);
315 		assert(ary == ["root/path1", "root/path2", "root2/path1"]);
316 		BuildSettings.removePaths(ary, ["foo", "root/path2", "bar", "root2/path1"]);
317 		assert(ary == ["root/path1"]);
318 		BuildSettings.remove(ary, ["root/path1", "foo"]);
319 		assert(ary == []);
320 	}
321 }
322 
323 enum BuildSetting {
324 	dflags            = 1<<0,
325 	lflags            = 1<<1,
326 	libs              = 1<<2,
327 	sourceFiles       = 1<<3,
328 	copyFiles         = 1<<4,
329 	versions          = 1<<5,
330 	debugVersions     = 1<<6,
331 	importPaths       = 1<<7,
332 	stringImportPaths = 1<<8,
333 	options           = 1<<9,
334 	none = 0,
335 	commandLine = dflags|copyFiles,
336 	commandLineSeparate = commandLine|lflags,
337 	all = dflags|lflags|libs|sourceFiles|copyFiles|versions|debugVersions|importPaths|stringImportPaths|options,
338 	noOptions = all & ~options
339 }
340 
341 enum TargetType {
342 	autodetect,
343 	none,
344 	executable,
345 	library,
346 	sourceLibrary,
347 	dynamicLibrary,
348 	staticLibrary,
349 	object
350 }
351 
352 enum BuildRequirement {
353 	none = 0,                     /// No special requirements
354 	allowWarnings        = 1<<0,  /// Warnings do not abort compilation
355 	silenceWarnings      = 1<<1,  /// Don't show warnings
356 	disallowDeprecations = 1<<2,  /// Using deprecated features aborts compilation
357 	silenceDeprecations  = 1<<3,  /// Don't show deprecation warnings
358 	disallowInlining     = 1<<4,  /// Avoid function inlining, even in release builds
359 	disallowOptimization = 1<<5,  /// Avoid optimizations, even in release builds
360 	requireBoundsCheck   = 1<<6,  /// Always perform bounds checks
361 	requireContracts     = 1<<7,  /// Leave assertions and contracts enabled in release builds
362 	relaxProperties      = 1<<8,  /// DEPRECATED: Do not enforce strict property handling (-property)
363 	noDefaultFlags       = 1<<9,  /// Do not issue any of the default build flags (e.g. -debug, -w, -property etc.) - use only for development purposes
364 }
365 
366 enum BuildOption {
367 	none = 0,                     /// Use compiler defaults
368 	debugMode = 1<<0,             /// Compile in debug mode (enables contracts, -debug)
369 	releaseMode = 1<<1,           /// Compile in release mode (disables assertions and bounds checks, -release)
370 	coverage = 1<<2,              /// Enable code coverage analysis (-cov)
371 	debugInfo = 1<<3,             /// Enable symbolic debug information (-g)
372 	debugInfoC = 1<<4,            /// Enable symbolic debug information in C compatible form (-gc)
373 	alwaysStackFrame = 1<<5,      /// Always generate a stack frame (-gs)
374 	stackStomping = 1<<6,         /// Perform stack stomping (-gx)
375 	inline = 1<<7,                /// Perform function inlining (-inline)
376 	noBoundsCheck = 1<<8,         /// Disable all bounds checking (-noboundscheck)
377 	optimize = 1<<9,              /// Enable optimizations (-O)
378 	profile = 1<<10,              /// Emit profiling code (-profile)
379 	unittests = 1<<11,            /// Compile unit tests (-unittest)
380 	verbose = 1<<12,              /// Verbose compiler output (-v)
381 	ignoreUnknownPragmas = 1<<13, /// Ignores unknown pragmas during compilation (-ignore)
382 	syntaxOnly = 1<<14,           /// Don't generate object files (-o-)
383 	warnings = 1<<15,             /// Enable warnings (-wi)
384 	warningsAsErrors = 1<<16,     /// Treat warnings as errors (-w)
385 	ignoreDeprecations = 1<<17,   /// Do not warn about using deprecated features (-d)
386 	deprecationWarnings = 1<<18,  /// Warn about using deprecated features (-dw)
387 	deprecationErrors = 1<<19,    /// Stop compilation upon usage of deprecated features (-de)
388 	property = 1<<20,             /// DEPRECATED: Enforce property syntax (-property)
389 	profileGC = 1<<21,            /// Profile runtime allocations
390 	pic = 1<<22,                  /// Generate position independent code
391 	betterC = 1<<23,              /// Compile in betterC mode (-betterC)
392 	lowmem = 1<<24,               /// Compile in lowmem mode (-lowmem)
393 	coverageCTFE = 1<<25,         /// Enable code coverage analysis including at compile-time (-cov=ctfe)
394 	color = 1<<26,                /// Colorize output (-color)
395 
396 	// for internal usage
397 	_docs = 1<<27,                // Write ddoc to docs
398 	_ddox = 1<<28,                // Compile docs.json
399 }
400 
401 struct Flags (T) {
402 	import dub.internal.vibecompat.data.serialization : ignore;
403 
404 	@ignore BitFlags!T values;
405 
406 	public this(T opt) @safe pure nothrow @nogc
407 	{
408 		this.values = opt;
409 	}
410 
411 	public this(BitFlags!T v) @safe pure nothrow @nogc
412 	{
413 		this.values = v;
414 	}
415 
416 	alias values this;
417 
418 	/**
419 	 * Reads a list of flags from a JSON/YAML document and converts them
420 	 * to our internal representation.
421 	 *
422 	 * Flags inside of dub code are stored as a `BitFlags`,
423 	 * but they are specified in the recipe using an array of their name.
424 	 * This routine handles the conversion from `string[]` to `BitFlags!T`.
425 	 */
426 	public static Flags!T fromYAML (scope ConfigParser!(Flags!T) p)
427 	{
428 		import dub.internal.dyaml.node;
429 		import std.exception;
430 		import std.conv;
431 
432 		enforce(p.node.nodeID == NodeID.sequence, "Should be a sequence");
433 		typeof(return) res;
434 		foreach (str; p.node.sequence)
435 			res |= str.as!string.to!T;
436 		return res;
437 	}
438 }
439 
440 unittest
441 {
442 	import dub.internal.configy.Read;
443 
444 	static struct Config
445 	{
446 		Flags!BuildRequirement flags;
447 	}
448 
449 	auto c = parseConfigString!Config(`
450 {
451 	"flags": [ "allowWarnings", "noDefaultFlags", "disallowInlining" ]
452 }
453 `, __FILE__);
454 	assert(c.flags.allowWarnings);
455 	c.flags.allowWarnings = false;
456 	assert(c.flags.noDefaultFlags);
457 	c.flags.noDefaultFlags = false;
458 	assert(c.flags.disallowInlining);
459 	c.flags.disallowInlining = false;
460 	assert(c.flags == c.flags.init);
461 }
462 
463 /**
464 	All build options that will be inherited upwards in the dependency graph
465 
466 	Build options in this category fulfill one of the following properties:
467 	$(UL
468 		$(LI The option affects the semantics of the generated code)
469 		$(LI The option affects if a certain piece of code is valid or not)
470 		$(LI The option enabled meta information in dependent projects that are useful for the dependee (e.g. debug information))
471 	)
472 */
473 enum Flags!BuildOption inheritedBuildOptions =
474 	BuildOption.debugMode | BuildOption.releaseMode
475 	| BuildOption.coverage | BuildOption.coverageCTFE | BuildOption.debugInfo | BuildOption.debugInfoC
476 	| BuildOption.alwaysStackFrame | BuildOption.stackStomping | BuildOption.inline
477 	| BuildOption.noBoundsCheck | BuildOption.profile | BuildOption.ignoreUnknownPragmas
478 	| BuildOption.syntaxOnly | BuildOption.warnings	| BuildOption.warningsAsErrors
479 	| BuildOption.ignoreDeprecations | BuildOption.deprecationWarnings
480 	| BuildOption.deprecationErrors | BuildOption.property | BuildOption.profileGC
481 	| BuildOption.pic;
482 
483 deprecated("Use `Flags!BuildOption` instead")
484 public alias BuildOptions = Flags!BuildOption;
485 
486 deprecated("Use `Flags!BuildRequirement` instead")
487 public alias BuildRequirements = Flags!BuildRequirement;