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.configy.attributes;
11 import dub.internal.logging;
12 import dub.internal.vibecompat.core.file;
13 import dub.internal.vibecompat.inet.path;
14 import dub.platform : BuildPlatform, matchesSpecification;
15 import dub.recipe.packagerecipe;
16 
17 import std.array : appender, array;
18 import std.algorithm : filter, any, sort;
19 import std.path : globMatch;
20 import std.typecons : BitFlags;
21 import std.algorithm.iteration : uniq;
22 import std.exception : enforce;
23 import std.file;
24 import std.process : environment;
25 import std.range : empty, chain;
26 
27 /// BuildPlatform specific settings, like needed libraries or additional
28 /// include paths.
29 struct BuildSettings {
30 	import dub.internal.vibecompat.data.serialization : byName;
31 
32 	TargetType targetType;
33 	string targetPath;
34 	string targetName;
35 	string workingDirectory;
36 	string mainSourceFile;
37 	string[] dflags;
38 	string[] lflags;
39 	string[] libs;
40 	string[] frameworks;
41 	string[] linkerFiles;
42 	string[] sourceFiles;
43 	string[] injectSourceFiles;
44 	string[] copyFiles;
45 	string[] extraDependencyFiles;
46 	string[] versions;
47 	string[] debugVersions;
48 	string[] versionFilters;
49 	string[] debugVersionFilters;
50 	string[] importPaths;
51 	string[] cImportPaths;
52 	string[] stringImportPaths;
53 	string[] importFiles;
54 	string[] stringImportFiles;
55 	string[] preGenerateCommands;
56 	string[] postGenerateCommands;
57 	string[] preBuildCommands;
58 	string[] postBuildCommands;
59 	string[] preRunCommands;
60 	string[] postRunCommands;
61 	string[string] environments;
62 	string[string] buildEnvironments;
63 	string[string] runEnvironments;
64 	string[string] preGenerateEnvironments;
65 	string[string] postGenerateEnvironments;
66 	string[string] preBuildEnvironments;
67 	string[string] postBuildEnvironments;
68 	string[string] preRunEnvironments;
69 	string[string] postRunEnvironments;
70 	@byName Flags!BuildRequirement requirements;
71 	@byName Flags!BuildOption options;
72 
73 	BuildSettings dup() const {
74 		// Forwards to `add`, but `add` doesn't handle the first 5 fields
75 		// as they are not additive, hence the `tupleof` call.
76 		return typeof(return)(this.tupleof[0 .. /* dflags, not included */ 5])
77 			.add(this);
78 	}
79 
80 	/**
81 	 * Merges $(LREF bs) onto `this` BuildSettings instance. This is called for
82 	 * sourceLibrary dependencies when they are included in the build to be
83 	 * merged into the root package build settings as well as configuring
84 	 * targets for different build types such as `release` or `unittest-cov`.
85 	 */
86 	ref BuildSettings add(in BuildSettings bs)
87 	{
88 		addDFlags(bs.dflags);
89 		addLFlags(bs.lflags);
90 		addLibs(bs.libs);
91 		addFrameworks(bs.frameworks);
92 		addLinkerFiles(bs.linkerFiles);
93 		addSourceFiles(bs.sourceFiles);
94 		addInjectSourceFiles(bs.injectSourceFiles);
95 		addCopyFiles(bs.copyFiles);
96 		addExtraDependencyFiles(bs.extraDependencyFiles);
97 		addVersions(bs.versions);
98 		addDebugVersions(bs.debugVersions);
99 		addVersionFilters(bs.versionFilters);
100 		addDebugVersionFilters(bs.debugVersionFilters);
101 		addImportPaths(bs.importPaths);
102 		addCImportPaths(bs.cImportPaths);
103 		addStringImportPaths(bs.stringImportPaths);
104 		addImportFiles(bs.importFiles);
105 		addStringImportFiles(bs.stringImportFiles);
106 		addPreGenerateCommands(bs.preGenerateCommands);
107 		addPostGenerateCommands(bs.postGenerateCommands);
108 		addPreBuildCommands(bs.preBuildCommands);
109 		addPostBuildCommands(bs.postBuildCommands);
110 		addPreRunCommands(bs.preRunCommands);
111 		addPostRunCommands(bs.postRunCommands);
112 		addEnvironments(bs.environments);
113 		addBuildEnvironments(bs.buildEnvironments);
114 		addRunEnvironments(bs.runEnvironments);
115 		addPreGenerateEnvironments(bs.preGenerateEnvironments);
116 		addPostGenerateEnvironments(bs.postGenerateEnvironments);
117 		addPreBuildEnvironments(bs.preBuildEnvironments);
118 		addPostBuildEnvironments(bs.postBuildEnvironments);
119 		addPreRunEnvironments(bs.preRunEnvironments);
120 		addPostRunEnvironments(bs.postRunEnvironments);
121 		addRequirements(bs.requirements);
122 		addOptions(bs.options);
123 		return this;
124 	}
125 
126 	void addDFlags(in string[] value...) { dflags = chain(dflags, value.dup).uniq.array; }
127 	void prependDFlags(in string[] value...) { prepend(dflags, value); }
128 	void removeDFlags(in string[] value...) { remove(dflags, value); }
129 	void addLFlags(in string[] value...) { lflags ~= value; }
130 	void prependLFlags(in string[] value...) { prepend(lflags, value, false); }
131 	void addLibs(in string[] value...) { add(libs, value); }
132 	void addFrameworks(in string[] value...) { add(frameworks, value); }
133 	void addLinkerFiles(in string[] value...) { add(linkerFiles, value); }
134 	void addSourceFiles(in string[] value...) { add(sourceFiles, value); }
135 	void prependSourceFiles(in string[] value...) { prepend(sourceFiles, value); }
136 	void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); }
137 	void addInjectSourceFiles(in string[] value...) { add(injectSourceFiles, value); }
138 	void addCopyFiles(in string[] value...) { add(copyFiles, value); }
139 	void addExtraDependencyFiles(in string[] value...) { add(extraDependencyFiles, value); }
140 	void addVersions(in string[] value...) { add(versions, value); }
141 	void addDebugVersions(in string[] value...) { add(debugVersions, value); }
142 	void addVersionFilters(in string[] value...) { add(versionFilters, value); }
143 	void addDebugVersionFilters(in string[] value...) { add(debugVersionFilters, value); }
144 	void addImportPaths(in string[] value...) { add(importPaths, value); }
145 	void addCImportPaths(in string[] value...) { add(cImportPaths, value); }
146 	void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); }
147 	void prependStringImportPaths(in string[] value...) { prepend(stringImportPaths, value); }
148 	void addImportFiles(in string[] value...) { add(importFiles, value); }
149 	void addStringImportFiles(in string[] value...) { addSI(stringImportFiles, value); }
150 	void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); }
151 	void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); }
152 	void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); }
153 	void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); }
154 	void addPreRunCommands(in string[] value...) { add(preRunCommands, value, false); }
155 	void addPostRunCommands(in string[] value...) { add(postRunCommands, value, false); }
156 	void addEnvironments(in string[string] value) { add(environments, value); }
157 	void updateEnvironments(in string[string] value) { update(environments, value); }
158 	void addBuildEnvironments(in string[string] value) { add(buildEnvironments, value); }
159 	void updateBuildEnvironments(in string[string] value) { update(buildEnvironments, value); }
160 	void addRunEnvironments(in string[string] value) { add(runEnvironments, value); }
161 	void updateRunEnvironments(in string[string] value) { update(runEnvironments, value); }
162 	void addPreGenerateEnvironments(in string[string] value) { add(preGenerateEnvironments, value); }
163 	void updatePreGenerateEnvironments(in string[string] value) { update(preGenerateEnvironments, value); }
164 	void addPostGenerateEnvironments(in string[string] value) { add(postGenerateEnvironments, value); }
165 	void updatePostGenerateEnvironments(in string[string] value) { update(postGenerateEnvironments, value); }
166 	void addPreBuildEnvironments(in string[string] value) { add(preBuildEnvironments, value); }
167 	void updatePreBuildEnvironments(in string[string] value) { update(preBuildEnvironments, value); }
168 	void addPostBuildEnvironments(in string[string] value) { add(postBuildEnvironments, value); }
169 	void updatePostBuildEnvironments(in string[string] value) { update(postBuildEnvironments, value); }
170 	void addPreRunEnvironments(in string[string] value) { add(preRunEnvironments, value); }
171 	void updatePreRunEnvironments(in string[string] value) { update(preRunEnvironments, value); }
172 	void addPostRunEnvironments(in string[string] value) { add(postRunEnvironments, value); }
173 	void updatePostRunEnvironments(in string[string] value) { update(postRunEnvironments, value); }
174 	void addRequirements(in BuildRequirement[] value...) { foreach (v; value) this.requirements |= v; }
175 	void addRequirements(in Flags!BuildRequirement value) { this.requirements |= value; }
176 	void addOptions(in BuildOption[] value...) { foreach (v; value) this.options |= v; }
177 	void addOptions(in Flags!BuildOption value) { this.options |= value; }
178 	void removeOptions(in BuildOption[] value...) { foreach (v; value) this.options &= ~v; }
179 	void removeOptions(in Flags!BuildOption value) { this.options &= ~value; }
180 
181 private:
182 	static auto filterDuplicates(T)(ref string[] arr, in T vals, bool noDuplicates = true)
183 	{
184 		return noDuplicates
185 			? vals.filter!(filtered => !arr.any!(item => item == filtered)).array
186 			: vals;
187 	}
188 
189 	// Append `vals` to `arr` without adding duplicates.
190 	static void add(ref string[] arr, in string[] vals, bool noDuplicates = true)
191 	{
192 		// vals might contain duplicates, add each val individually
193 		foreach (val; vals)
194 			arr ~= filterDuplicates(arr, [val], noDuplicates);
195 	}
196 	// Append `vals` to `aa`
197 	static void add(ref string[string] aa, in string[string] vals)
198 	{
199 		// vals might contain duplicated keys, add each val individually
200 		foreach (key, val; vals)
201 			if (key !in aa)
202 				aa[key] = val;
203 	}
204 	// Update `vals` to `aa`
205 	static void update(ref string[string] aa, in string[string] vals)
206 	{
207 		// If there are duplicate keys, they will be ignored and overwritten.
208 		foreach (key, val; vals)
209 			aa[key] = val;
210 	}
211 
212 	unittest
213 	{
214 		auto ary = ["-dip1000", "-vgc"];
215 		BuildSettings.add(ary, ["-dip1000", "-vgc"]);
216 		assert(ary == ["-dip1000", "-vgc"]);
217 		BuildSettings.add(ary, ["-dip1001", "-vgc"], false);
218 		assert(ary == ["-dip1000", "-vgc", "-dip1001", "-vgc"]);
219 		BuildSettings.add(ary, ["-dupflag", "-notdupflag", "-dupflag"]);
220 		assert(ary == ["-dip1000", "-vgc", "-dip1001", "-vgc", "-dupflag", "-notdupflag"]);
221 	}
222 
223 	// Prepend `arr` by `vals` without adding duplicates.
224 	static void prepend(ref string[] arr, in string[] vals, bool noDuplicates = true)
225 	{
226 		import std.range : retro;
227 		// vals might contain duplicates, add each val individually
228 		foreach (val; vals.retro)
229 			arr = filterDuplicates(arr, [val], noDuplicates) ~ arr;
230 	}
231 
232 	unittest
233 	{
234 		auto ary = ["-dip1000", "-vgc"];
235 		BuildSettings.prepend(ary, ["-dip1000", "-vgc"]);
236 		assert(ary == ["-dip1000", "-vgc"]);
237 		BuildSettings.prepend(ary, ["-dip1001", "-vgc"], false);
238 		assert(ary == ["-dip1001", "-vgc", "-dip1000", "-vgc"]);
239 		BuildSettings.prepend(ary, ["-dupflag", "-notdupflag", "-dupflag"]);
240 		assert(ary == ["-notdupflag", "-dupflag", "-dip1001", "-vgc", "-dip1000", "-vgc"]);
241 	}
242 
243 	// add string import files (avoids file name duplicates in addition to path duplicates)
244 	static void addSI(ref string[] arr, in string[] vals)
245 	{
246 		bool[string] existing;
247 		foreach (v; arr) existing[NativePath(v).head.name] = true;
248 		foreach (v; vals) {
249 			auto s = NativePath(v).head.name;
250 			if (s !in existing) {
251 				existing[s] = true;
252 				arr ~= v;
253 			}
254 		}
255 	}
256 
257 	unittest
258 	{
259 		auto ary = ["path/foo.txt"];
260 		BuildSettings.addSI(ary, ["path2/foo2.txt"]);
261 		assert(ary == ["path/foo.txt", "path2/foo2.txt"]);
262 		BuildSettings.addSI(ary, ["path2/foo.txt"]); // no duplicate basenames
263 		assert(ary == ["path/foo.txt", "path2/foo2.txt"]);
264 	}
265 
266 	static bool pathMatch(string path, string pattern)
267 	{
268 		import std.functional : memoize;
269 
270 		alias nativePath = memoize!((string stringPath) => NativePath(stringPath));
271 
272 		return nativePath(path) == nativePath(pattern) || globMatch(path, pattern);
273 	}
274 
275 	static void removeValuesFromArray(alias Match)(ref string[] arr, in string[] vals)
276 	{
277 		bool matches(string s)
278 		{
279 			return vals.any!(item => Match(s, item));
280 		}
281 		arr = arr.filter!(s => !matches(s)).array;
282 	}
283 
284 	static void removePaths(ref string[] arr, in string[] vals)
285 	{
286 		removeValuesFromArray!(pathMatch)(arr, vals);
287 	}
288 
289 	unittest
290 	{
291 		auto ary = ["path1", "root/path1", "root/path2", "root2/path1"];
292 		BuildSettings.removePaths(ary, ["path1"]);
293 		assert(ary == ["root/path1", "root/path2", "root2/path1"]);
294 		BuildSettings.removePaths(ary, ["*/path1"]);
295 		assert(ary == ["root/path2"]);
296 		BuildSettings.removePaths(ary, ["foo", "bar", "root/path2"]);
297 		assert(ary == []);
298 	}
299 
300 	static void remove(ref string[] arr, in string[] vals)
301 	{
302 		removeValuesFromArray!((a, b) => a == b)(arr, vals);
303 	}
304 
305 	unittest
306 	{
307 		import std.string : join;
308 
309 		auto ary = ["path1", "root/path1", "root/path2", "root2/path1"];
310 		BuildSettings.remove(ary, ["path1"]);
311 		assert(ary == ["root/path1", "root/path2", "root2/path1"]);
312 		BuildSettings.remove(ary, ["root/path*"]);
313 		assert(ary == ["root/path1", "root/path2", "root2/path1"]);
314 		BuildSettings.removePaths(ary, ["foo", "root/path2", "bar", "root2/path1"]);
315 		assert(ary == ["root/path1"]);
316 		BuildSettings.remove(ary, ["root/path1", "foo"]);
317 		assert(ary == []);
318 	}
319 }
320 
321 enum BuildSetting {
322 	dflags            = 1<<0,
323 	lflags            = 1<<1,
324 	libs              = 1<<2,
325 	sourceFiles       = 1<<3,
326 	copyFiles         = 1<<4,
327 	versions          = 1<<5,
328 	debugVersions     = 1<<6,
329 	importPaths       = 1<<7,
330 	cImportPaths      = 1<<8,
331 	stringImportPaths = 1<<9,
332 	options           = 1<<10,
333 	frameworks        = 1<<11,
334 	none = 0,
335 	commandLine = dflags|copyFiles,
336 	commandLineSeparate = commandLine|lflags,
337 	all = dflags|lflags|libs|sourceFiles|copyFiles|versions|debugVersions|importPaths|cImportPaths|stringImportPaths|options|frameworks,
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,             /// Enables -debug flag (debug statements and identifiers)
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 low-memory 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 	import dub.internal.vibecompat.data.json : Json;
404 
405 	@ignore BitFlags!T values;
406 
407 	public this(T opt) @safe pure nothrow @nogc
408 	{
409 		this.values = opt;
410 	}
411 
412 	public this(BitFlags!T v) @safe pure nothrow @nogc
413 	{
414 		this.values = v;
415 	}
416 
417 	alias values this;
418 
419 	public Json toJson() const
420 	{
421 		import std.conv : to;
422 		import std.traits : EnumMembers;
423 
424 		auto json = Json.emptyArray;
425 
426 		static foreach (em; EnumMembers!T) {
427 			static if (em != 0) {
428 				if (values & em) {
429 					json ~= em.to!string;
430 				}
431 			}
432 		}
433 
434 		return json;
435 	}
436 
437 	public static Flags!T fromJson(Json json)
438 	{
439 		import std.conv : to;
440 		import std.exception : enforce;
441 
442 		BitFlags!T flags;
443 
444 		enforce(json.type == Json.Type.array, "Should be an array");
445 		foreach (jval; json) {
446 			flags |= jval.get!string.to!T;
447 		}
448 
449 		return Flags!T(flags);
450 	}
451 
452 	/**
453 	 * Reads a list of flags from a JSON/YAML document and converts them
454 	 * to our internal representation.
455 	 *
456 	 * Flags inside of dub code are stored as a `BitFlags`,
457 	 * but they are specified in the recipe using an array of their name.
458 	 * This routine handles the conversion from `string[]` to `BitFlags!T`.
459 	 */
460 	public static Flags!T fromConfig (scope ConfigParser p)
461 	{
462 		import dub.internal.configy.backend.node;
463 		import std.exception;
464 		import std.conv;
465 
466 		auto seq = p.node.asSequence();
467         enforce(seq !is null, "Should be a sequence");
468 		typeof(return) res;
469 		foreach (idx, entry; seq) {
470             if (scope scalar = entry.asScalar())
471                 res |= scalar.str.to!T;
472         }
473 		return res;
474 	}
475 }
476 
477 /// Constructs a BuildSettings object from this template.
478 void getPlatformSettings(in BuildSettingsTemplate* this_, ref BuildSettings dst,
479 	in BuildPlatform platform, NativePath base_path) {
480     getPlatformSettings(*this_, dst, platform, base_path);
481 }
482 
483 /// Ditto
484 void getPlatformSettings(in BuildSettingsTemplate this_, ref BuildSettings dst,
485 	in BuildPlatform platform, NativePath base_path) {
486 	dst.targetType = this_.targetType;
487 	if (!this_.targetPath.empty) dst.targetPath = this_.targetPath;
488 	if (!this_.targetName.empty) dst.targetName = this_.targetName;
489 	if (!this_.workingDirectory.empty) dst.workingDirectory = this_.workingDirectory;
490 	if (!this_.mainSourceFile.empty) {
491 		auto p = NativePath(this_.mainSourceFile);
492 		p.normalize();
493 		dst.mainSourceFile = p.toNativeString();
494 		dst.addSourceFiles(dst.mainSourceFile);
495 	}
496 
497 	string[] collectFiles(in string[][string] paths_map, string pattern)
498 	{
499 		auto files = appender!(string[]);
500 
501 		import dub.project : buildSettingsVars;
502 		import std.typecons : Nullable;
503 
504 		static Nullable!(string[string]) envVarCache;
505 
506 		if (envVarCache.isNull) envVarCache = environment.toAA();
507 
508 		foreach (suffix, paths; paths_map) {
509 			if (!platform.matchesSpecification(suffix))
510 				continue;
511 
512 			foreach (spath; paths) {
513 				enforce(!spath.empty, "Paths must not be empty strings.");
514 				auto path = NativePath(spath);
515 				if (!path.absolute) path = base_path ~ path;
516 				if (!existsDirectory(path)) {
517 					import std.algorithm : any, find;
518 					const hasVar = chain(buildSettingsVars, envVarCache.get.byKey).any!((string var) {
519 						return spath.find("$"~var).length > 0 || spath.find("${"~var~"}").length > 0;
520 					});
521 					if (!hasVar)
522 						logWarn("Invalid source/import path: %s", path.toNativeString());
523 					continue;
524 				}
525 
526 				auto pstr = path.toNativeString();
527 				foreach (d; dirEntries(pstr, pattern, SpanMode.depth)) {
528 					import std.path : baseName, pathSplitter;
529 					import std.algorithm.searching : canFind;
530 					// eliminate any hidden files, or files in hidden directories. But always include
531 					// files that are listed inside hidden directories that are specifically added to
532 					// the project.
533 					if (d.isDir || pathSplitter(d.name[pstr.length .. $])
534 						.canFind!(name => name.length && name[0] == '.'))
535 						continue;
536 					auto src = NativePath(d.name).relativeTo(base_path);
537 					files ~= src.toNativeString();
538 				}
539 			}
540 		}
541 
542 		return files.data;
543 	}
544 
545 	// collect source files. Note: D source from 'sourcePaths' and C sources from 'cSourcePaths' are joint into 'sourceFiles'
546 	dst.addSourceFiles(collectFiles(this_.sourcePaths, "*.d"));
547 	dst.addSourceFiles(collectFiles(this_.cSourcePaths, "*.{c,i}"));
548 	auto sourceFiles = dst.sourceFiles.sort();
549 
550 	// collect import files and remove sources
551 	import std.algorithm : copy, setDifference;
552 
553 	auto importFiles =
554 		chain(collectFiles(this_.importPaths, "*.{d,di}"), collectFiles(this_.cImportPaths, "*.h"))
555 		.array()
556 		.sort();
557 	immutable nremoved = importFiles.setDifference(sourceFiles).copy(importFiles.release).length;
558 	importFiles = importFiles[0 .. $ - nremoved];
559 	dst.addImportFiles(importFiles.release);
560 
561 	dst.addStringImportFiles(collectFiles(this_.stringImportPaths, "*"));
562 
563 	this_.getPlatformSetting_!("dflags", "addDFlags")(dst, platform);
564 	this_.getPlatformSetting_!("lflags", "addLFlags")(dst, platform);
565 	this_.getPlatformSetting_!("libs", "addLibs")(dst, platform);
566 	this_.getPlatformSetting_!("frameworks", "addFrameworks")(dst, platform);
567 	this_.getPlatformSetting_!("sourceFiles", "addSourceFiles")(dst, platform);
568 	this_.getPlatformSetting_!("excludedSourceFiles", "removeSourceFiles")(dst, platform);
569 	this_.getPlatformSetting_!("injectSourceFiles", "addInjectSourceFiles")(dst, platform);
570 	this_.getPlatformSetting_!("copyFiles", "addCopyFiles")(dst, platform);
571 	this_.getPlatformSetting_!("extraDependencyFiles", "addExtraDependencyFiles")(dst, platform);
572 	this_.getPlatformSetting_!("versions", "addVersions")(dst, platform);
573 	this_.getPlatformSetting_!("debugVersions", "addDebugVersions")(dst, platform);
574 	this_.getPlatformSetting_!("versionFilters", "addVersionFilters")(dst, platform);
575 	this_.getPlatformSetting_!("debugVersionFilters", "addDebugVersionFilters")(dst, platform);
576 	this_.getPlatformSetting_!("importPaths", "addImportPaths")(dst, platform);
577 	this_.getPlatformSetting_!("cImportPaths", "addCImportPaths")(dst, platform);
578 	this_.getPlatformSetting_!("stringImportPaths", "addStringImportPaths")(dst, platform);
579 	this_.getPlatformSetting_!("preGenerateCommands", "addPreGenerateCommands")(dst, platform);
580 	this_.getPlatformSetting_!("postGenerateCommands", "addPostGenerateCommands")(dst, platform);
581 	this_.getPlatformSetting_!("preBuildCommands", "addPreBuildCommands")(dst, platform);
582 	this_.getPlatformSetting_!("postBuildCommands", "addPostBuildCommands")(dst, platform);
583 	this_.getPlatformSetting_!("preRunCommands", "addPreRunCommands")(dst, platform);
584 	this_.getPlatformSetting_!("postRunCommands", "addPostRunCommands")(dst, platform);
585 	this_.getPlatformSetting_!("environments", "addEnvironments")(dst, platform);
586 	this_.getPlatformSetting_!("buildEnvironments", "addBuildEnvironments")(dst, platform);
587 	this_.getPlatformSetting_!("runEnvironments", "addRunEnvironments")(dst, platform);
588 	this_.getPlatformSetting_!("preGenerateEnvironments", "addPreGenerateEnvironments")(dst, platform);
589 	this_.getPlatformSetting_!("postGenerateEnvironments", "addPostGenerateEnvironments")(dst, platform);
590 	this_.getPlatformSetting_!("preBuildEnvironments", "addPreBuildEnvironments")(dst, platform);
591 	this_.getPlatformSetting_!("postBuildEnvironments", "addPostBuildEnvironments")(dst, platform);
592 	this_.getPlatformSetting_!("preRunEnvironments", "addPreRunEnvironments")(dst, platform);
593 	this_.getPlatformSetting_!("postRunEnvironments", "addPostRunEnvironments")(dst, platform);
594 	this_.getPlatformSetting_!("buildRequirements", "addRequirements")(dst, platform);
595 	this_.getPlatformSetting_!("buildOptions", "addOptions")(dst, platform);
596 }
597 
598 unittest { // issue #1407 - duplicate main source file
599 	{
600 		BuildSettingsTemplate t;
601 		t.mainSourceFile = "./foo.d";
602 		t.sourceFiles[""] = ["foo.d"];
603 		BuildSettings bs;
604 		t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/"));
605 		assert(bs.sourceFiles == ["foo.d"]);
606 	}
607 
608 	version (Windows) {{
609 		BuildSettingsTemplate t;
610 		t.mainSourceFile = "src/foo.d";
611 		t.sourceFiles[""] = ["src\\foo.d"];
612 		BuildSettings bs;
613 		t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/"));
614 		assert(bs.sourceFiles == ["src\\foo.d"]);
615 	}}
616 }
617 
618 unittest
619 {
620 	import dub.internal.vibecompat.data.json;
621 
622 	auto opts = Flags!BuildOption(BuildOption.debugMode | BuildOption.debugInfo | BuildOption.warningsAsErrors);
623 	const str = serializeToJsonString(opts);
624 	assert(str == `["debugMode","debugInfo","warningsAsErrors"]`);
625 	assert(deserializeJson!(typeof(opts))(str) == opts);
626 }
627 
628 unittest
629 {
630 	import dub.internal.configy.easy;
631 
632 	static struct Config
633 	{
634 		Flags!BuildRequirement flags;
635 	}
636 
637 	auto c = parseConfigString!Config(`
638 {
639 	"flags": [ "allowWarnings", "noDefaultFlags", "disallowInlining" ]
640 }
641 `, __FILE__);
642 	assert(c.flags.allowWarnings);
643 	c.flags.allowWarnings = false;
644 	assert(c.flags.noDefaultFlags);
645 	c.flags.noDefaultFlags = false;
646 	assert(c.flags.disallowInlining);
647 	c.flags.disallowInlining = false;
648 	assert(c.flags == c.flags.init);
649 }
650 
651 /**
652 	All build options that will be inherited upwards in the dependency graph
653 
654 	Build options in this category fulfill one of the following properties:
655 	$(UL
656 		$(LI The option affects the semantics of the generated code)
657 		$(LI The option affects if a certain piece of code is valid or not)
658 		$(LI The option enabled meta information in dependent projects that are useful for the dependee (e.g. debug information))
659 	)
660 */
661 enum Flags!BuildOption inheritedBuildOptions =
662 	BuildOption.debugMode | BuildOption.releaseMode
663 	| BuildOption.coverage | BuildOption.coverageCTFE | BuildOption.debugInfo | BuildOption.debugInfoC
664 	| BuildOption.alwaysStackFrame | BuildOption.stackStomping | BuildOption.inline
665 	| BuildOption.noBoundsCheck | BuildOption.profile | BuildOption.ignoreUnknownPragmas
666 	| BuildOption.syntaxOnly | BuildOption.warnings	| BuildOption.warningsAsErrors
667 	| BuildOption.ignoreDeprecations | BuildOption.deprecationWarnings
668 	| BuildOption.deprecationErrors | BuildOption.property | BuildOption.profileGC
669 	| BuildOption.pic;
670 
671 deprecated("Use `Flags!BuildOption` instead")
672 public alias BuildOptions = Flags!BuildOption;
673 
674 deprecated("Use `Flags!BuildRequirement` instead")
675 public alias BuildRequirements = Flags!BuildRequirement;