1 /**
2 	Contains high-level functionality for working with packages.
3 
4 	Copyright: © 2012-2013 Matthias Dondorff, © 2012-2016 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Matthias Dondorff, Sönke Ludwig, Martin Nowak, Nick Sabalausky
7 */
8 module dub.package_;
9 
10 public import dub.recipe.packagerecipe;
11 
12 import dub.compilers.compiler;
13 import dub.dependency;
14 import dub.description;
15 import dub.recipe.json;
16 import dub.recipe.sdl;
17 
18 import dub.internal.logging;
19 import dub.internal.utils;
20 import dub.internal.vibecompat.core.file;
21 import dub.internal.vibecompat.data.json;
22 import dub.internal.vibecompat.inet.path;
23 
24 import configy.Read : StrictMode;
25 
26 import std.algorithm;
27 import std.array;
28 import std.conv;
29 import std.exception;
30 import std.file;
31 import std.range;
32 import std.string;
33 import std.typecons : Nullable;
34 
35 
36 /// Lists the supported package recipe formats.
37 enum PackageFormat {
38 	json, /// JSON based, using the ".json" file extension
39 	sdl   /// SDLang based, using the ".sdl" file extension
40 }
41 
42 struct FilenameAndFormat {
43 	string filename;
44 	PackageFormat format;
45 }
46 
47 /// Supported package descriptions in decreasing order of preference.
48 static immutable FilenameAndFormat[] packageInfoFiles = [
49 	{"dub.json", PackageFormat.json},
50 	{"dub.sdl", PackageFormat.sdl},
51 	{"package.json", PackageFormat.json}
52 ];
53 
54 /// Returns a list of all recognized package recipe file names in descending order of precedence.
55 @property string[] packageInfoFilenames() { return packageInfoFiles.map!(f => cast(string)f.filename).array; }
56 
57 /// Returns the default package recile file name.
58 @property string defaultPackageFilename() { return packageInfoFiles[0].filename; }
59 
60 /// All built-in build type names except for the special `$DFLAGS` build type.
61 /// Has the default build type (`debug`) as first index.
62 static immutable string[] builtinBuildTypes = [
63 	"debug",
64 	"plain",
65 	"release",
66 	"release-debug",
67 	"release-nobounds",
68 	"unittest",
69 	"profile",
70 	"profile-gc",
71 	"docs",
72 	"ddox",
73 	"cov",
74 	"cov-ctfe",
75 	"unittest-cov",
76 	"unittest-cov-ctfe",
77 	"syntax"
78 ];
79 
80 /**	Represents a package, including its sub packages.
81 */
82 class Package {
83 	private {
84 		NativePath m_path;
85 		NativePath m_infoFile;
86 		PackageRecipe m_info;
87 		PackageRecipe m_rawRecipe;
88 		Package m_parentPackage;
89 	}
90 
91 	/** Constructs a `Package` using an in-memory package recipe.
92 
93 		Params:
94 			json_recipe = The package recipe in JSON format
95 			recipe = The package recipe in generic format
96 			root = The directory in which the package resides (if any).
97 			parent = Reference to the parent package, if the new package is a
98 				sub package.
99 			version_override = Optional version to associate to the package
100 				instead of the one declared in the package recipe, or the one
101 				determined by invoking the VCS (GIT currently).
102 	*/
103 	this(Json json_recipe, NativePath root = NativePath(), Package parent = null, string version_override = "")
104 	{
105 		import dub.recipe.json;
106 
107 		PackageRecipe recipe;
108 		parseJson(recipe, json_recipe, parent ? parent.name : null);
109 		this(recipe, root, parent, version_override);
110 	}
111 	/// ditto
112 	this(PackageRecipe recipe, NativePath root = NativePath(), Package parent = null, string version_override = "")
113 	{
114 		// save the original recipe
115 		m_rawRecipe = recipe.clone;
116 
117 		if (!version_override.empty)
118 			recipe.version_ = version_override;
119 
120 		// try to run git to determine the version of the package if no explicit version was given
121 		if (recipe.version_.length == 0 && !parent) {
122 			try recipe.version_ = determineVersionFromSCM(root);
123 			catch (Exception e) logDebug("Failed to determine version by SCM: %s", e.msg);
124 
125 			if (recipe.version_.length == 0) {
126 				logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", recipe.name, this.path.toNativeString());
127 				// TODO: Assume unknown version here?
128 				// recipe.version_ = Version.unknown.toString();
129 				recipe.version_ = Version.masterBranch.toString();
130 			} else logDiagnostic("Determined package version using GIT: %s %s", recipe.name, recipe.version_);
131 		}
132 
133 		m_parentPackage = parent;
134 		m_path = root;
135 		m_path.endsWithSlash = true;
136 
137 		// use the given recipe as the basis
138 		m_info = recipe;
139 
140 		checkDubRequirements();
141 		fillWithDefaults();
142 		mutuallyExcludeMainFiles();
143 	}
144 
145 	/** Searches the given directory for package recipe files.
146 
147 		Params:
148 			directory = The directory to search
149 
150 		Returns:
151 			Returns the full path to the package file, if any was found.
152 			Otherwise returns an empty path.
153 	*/
154 	static NativePath findPackageFile(NativePath directory)
155 	{
156 		foreach (file; packageInfoFiles) {
157 			auto filename = directory ~ file.filename;
158 			if (existsFile(filename)) return filename;
159 		}
160 		return NativePath.init;
161 	}
162 
163 	/** Constructs a `Package` using a package that is physically present on the local file system.
164 
165 		Params:
166 			root = The directory in which the package resides.
167 			recipe_file = Optional path to the package recipe file. If left
168 				empty, the `root` directory will be searched for a recipe file.
169 			parent = Reference to the parent package, if the new package is a
170 				sub package.
171 			version_override = Optional version to associate to the package
172 				instead of the one declared in the package recipe, or the one
173 				determined by invoking the VCS (GIT currently).
174 			mode = Whether to issue errors, warning, or ignore unknown keys in dub.json
175 	*/
176 	static Package load(NativePath root, NativePath recipe_file = NativePath.init,
177 		Package parent = null, string version_override = "",
178 		StrictMode mode = StrictMode.Ignore)
179 	{
180 		import dub.recipe.io;
181 
182 		if (recipe_file.empty) recipe_file = findPackageFile(root);
183 
184 		enforce(!recipe_file.empty,
185 			"No package file found in %s, expected one of %s"
186 				.format(root.toNativeString(),
187 					packageInfoFiles.map!(f => cast(string)f.filename).join("/")));
188 
189 		auto recipe = readPackageRecipe(recipe_file, parent ? parent.name : null, mode);
190 
191 		auto ret = new Package(recipe, root, parent, version_override);
192 		ret.m_infoFile = recipe_file;
193 		return ret;
194 	}
195 
196 	/** Returns the qualified name of the package.
197 
198 		The qualified name includes any possible parent package if this package
199 		is a sub package.
200 	*/
201 	@property string name()
202 	const {
203 		if (m_parentPackage) return m_parentPackage.name ~ ":" ~ m_info.name;
204 		else return m_info.name;
205 	}
206 
207 	/** Returns the directory in which the package resides.
208 
209 		Note that this can be empty for packages that are not stored in the
210 		local file system.
211 	*/
212 	@property NativePath path() const { return m_path; }
213 
214 
215 	/** Accesses the version associated with this package.
216 
217 		Note that this is a shortcut to `this.recipe.version_`.
218 	*/
219 	@property Version version_() const { return m_parentPackage ? m_parentPackage.version_ : Version(m_info.version_); }
220 	/// ditto
221 	@property void version_(Version value) { assert(m_parentPackage is null); m_info.version_ = value.toString(); }
222 
223 	/** Accesses the recipe contents of this package.
224 
225 		The recipe contains any default values and configurations added by DUB.
226 		To access the raw user recipe, use the `rawRecipe` property.
227 
228 		See_Also: `rawRecipe`
229 	*/
230 	@property ref inout(PackageRecipe) recipe() inout { return m_info; }
231 
232 	/** Accesses the original package recipe.
233 
234 		The returned recipe matches exactly the contents of the original package
235 		recipe. For the effective package recipe, augmented with DUB generated
236 		default settings and configurations, use the `recipe` property.
237 
238 		See_Also: `recipe`
239 	*/
240 	@property ref const(PackageRecipe) rawRecipe() const { return m_rawRecipe; }
241 
242 	/** Returns the path to the package recipe file.
243 
244 		Note that this can be empty for packages that are not stored in the
245 		local file system.
246 	*/
247 	@property NativePath recipePath() const { return m_infoFile; }
248 
249 
250 	/** Returns the base package of this package.
251 
252 		The base package is the root of the sub package hierarchy (i.e. the
253 		topmost parent). This will be `null` for packages that are not sub
254 		packages.
255 	*/
256 	@property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; }
257 
258 	/** Returns the parent of this package.
259 
260 		The parent package is the package that contains a sub package. This will
261 		be `null` for packages that are not sub packages.
262 	*/
263 	@property inout(Package) parentPackage() inout { return m_parentPackage; }
264 
265 	/** Returns the list of all sub packages.
266 
267 		Note that this is a shortcut for `this.recipe.subPackages`.
268 	*/
269 	@property inout(SubPackage)[] subPackages() inout { return m_info.subPackages; }
270 
271 	/** Returns the list of all build configuration names.
272 
273 		Configuration contents can be accessed using `this.recipe.configurations`.
274 	*/
275 	@property string[] configurations()
276 	const {
277 		auto ret = appender!(string[])();
278 		foreach (ref config; m_info.configurations)
279 			ret.put(config.name);
280 		return ret.data;
281 	}
282 
283 	/** Returns the list of all custom build type names.
284 
285 		Build type contents can be accessed using `this.recipe.buildTypes`.
286 	*/
287 	@property string[] customBuildTypes()
288 	const {
289 		auto ret = appender!(string[])();
290 		foreach (name; m_info.buildTypes.byKey)
291 			ret.put(name);
292 		return ret.data;
293 	}
294 
295 	/** Writes the current recipe contents to a recipe file.
296 
297 		The parameter-less overload writes to `this.path`, which must not be
298 		empty. The default recipe file name will be used in this case.
299 	*/
300 	void storeInfo()
301 	{
302 		storeInfo(m_path);
303 		m_infoFile = m_path ~ defaultPackageFilename;
304 	}
305 	/// ditto
306 	void storeInfo(NativePath path)
307 	const {
308 		auto filename = path ~ defaultPackageFilename;
309 		auto dstFile = openFile(filename.toNativeString(), FileMode.createTrunc);
310 		scope(exit) dstFile.close();
311 		dstFile.writePrettyJsonString(m_info.toJson());
312 	}
313 
314 	/// Get the metadata cache for this package
315 	@property Json metadataCache()
316 	{
317 		enum silent_fail = true;
318 		return jsonFromFile(m_path ~ ".dub/metadata_cache.json", silent_fail);
319 	}
320 
321 	/// Write metadata cache for this package
322 	@property void metadataCache(Json json)
323 	{
324 		enum create_if_missing = true;
325 		if (isWritableDir(m_path ~ ".dub", create_if_missing))
326 			writeJsonFile(m_path ~ ".dub/metadata_cache.json", json);
327 		// TODO: store elsewhere
328 	}
329 
330 	/** Returns the package recipe of a non-path-based sub package.
331 
332 		For sub packages that are declared within the package recipe of the
333 		parent package, this function will return the corresponding recipe. Sub
334 		packages declared using a path must be loaded manually (or using the
335 		`PackageManager`).
336 	*/
337 	Nullable!PackageRecipe getInternalSubPackage(string name)
338 	{
339 		foreach (ref p; m_info.subPackages)
340 			if (p.path.empty && p.recipe.name == name)
341 				return Nullable!PackageRecipe(p.recipe);
342 		return Nullable!PackageRecipe();
343 	}
344 
345 	/** Searches for use of compiler-specific flags that have generic
346 		alternatives.
347 
348 		This will output a warning message for each such flag to the console.
349 	*/
350 	void warnOnSpecialCompilerFlags()
351 	{
352 		// warn about use of special flags
353 		m_info.buildSettings.warnOnSpecialCompilerFlags(m_info.name, null);
354 		foreach (ref config; m_info.configurations)
355 			config.buildSettings.warnOnSpecialCompilerFlags(m_info.name, config.name);
356 	}
357 
358 	/** Retrieves a build settings template.
359 
360 		If no `config` is given, this returns the build settings declared at the
361 		root level of the package recipe. Otherwise returns the settings
362 		declared within the given configuration (excluding those at the root
363 		level).
364 
365 		Note that this is a shortcut to accessing `this.recipe.buildSettings` or
366 		`this.recipe.configurations[].buildSettings`.
367 	*/
368 	const(BuildSettingsTemplate) getBuildSettings(string config = null)
369 	const {
370 		if (config.length) {
371 			foreach (ref conf; m_info.configurations)
372 				if (conf.name == config)
373 					return conf.buildSettings;
374 			assert(false, "Unknown configuration: "~config);
375 		} else {
376 			return m_info.buildSettings;
377 		}
378 	}
379 
380 	/** Returns all BuildSettings for the given platform and configuration.
381 
382 		This will gather the effective build settings declared in tha package
383 		recipe for when building on a particular platform and configuration.
384 		Root build settings and configuration specific settings will be
385 		merged.
386 	*/
387 	BuildSettings getBuildSettings(in BuildPlatform platform, string config)
388 	const {
389 		BuildSettings ret;
390 		m_info.buildSettings.getPlatformSettings(ret, platform, this.path);
391 		bool found = false;
392 		foreach(ref conf; m_info.configurations){
393 			if( conf.name != config ) continue;
394 			conf.buildSettings.getPlatformSettings(ret, platform, this.path);
395 			found = true;
396 			break;
397 		}
398 		assert(found || config is null, "Unknown configuration for "~m_info.name~": "~config);
399 
400 		// construct default target name based on package name
401 		if( ret.targetName.empty ) ret.targetName = this.name.replace(":", "_");
402 
403 		// special support for DMD style flags
404 		getCompiler("dmd").extractBuildOptions(ret);
405 
406 		return ret;
407 	}
408 
409 	/** Returns the combination of all build settings for all configurations
410 		and platforms.
411 
412 		This can be useful for IDEs to gather a list of all potentially used
413 		files or settings.
414 	*/
415 	BuildSettings getCombinedBuildSettings()
416 	const {
417 		BuildSettings ret;
418 		m_info.buildSettings.getPlatformSettings(ret, BuildPlatform.any, this.path);
419 		foreach(ref conf; m_info.configurations)
420 			conf.buildSettings.getPlatformSettings(ret, BuildPlatform.any, this.path);
421 
422 		// construct default target name based on package name
423 		if (ret.targetName.empty) ret.targetName = this.name.replace(":", "_");
424 
425 		// special support for DMD style flags
426 		getCompiler("dmd").extractBuildOptions(ret);
427 
428 		return ret;
429 	}
430 
431 	/** Adds build type specific settings to an existing set of build settings.
432 
433 		This function searches the package recipe for overridden build types. If
434 		none is found, the default build settings will be applied, if
435 		`build_type` matches a default build type name. An exception is thrown
436 		otherwise.
437 	*/
438 	void addBuildTypeSettings(ref BuildSettings settings, in BuildPlatform platform, string build_type)
439 	const {
440 		import std.process;
441 		string dflags = environment.get("DFLAGS", "");
442 		settings.addDFlags(dflags.split());
443 
444 		if (auto pbt = build_type in m_info.buildTypes) {
445 			logDiagnostic("Using custom build type '%s'.", build_type);
446 			pbt.getPlatformSettings(settings, platform, this.path);
447 		} else {
448 			with(BuildOption) switch (build_type) {
449 				default: throw new Exception(format("Unknown build type for %s: '%s'", this.name, build_type));
450 				case "$DFLAGS": break;
451 				case "plain": break;
452 				case "debug": settings.addOptions(debugMode, debugInfo); break;
453 				case "release": settings.addOptions(releaseMode, optimize, inline); break;
454 				case "release-debug": settings.addOptions(releaseMode, optimize, inline, debugInfo); break;
455 				case "release-nobounds": settings.addOptions(releaseMode, optimize, inline, noBoundsCheck); break;
456 				case "unittest": settings.addOptions(unittests, debugMode, debugInfo); break;
457 				case "docs": settings.addOptions(syntaxOnly, _docs); break;
458 				case "ddox": settings.addOptions(syntaxOnly,  _ddox); break;
459 				case "profile": settings.addOptions(profile, optimize, inline, debugInfo); break;
460 				case "profile-gc": settings.addOptions(profileGC, debugInfo); break;
461 				case "cov": settings.addOptions(coverage, debugInfo); break;
462 				case "cov-ctfe": settings.addOptions(coverageCTFE, debugInfo); break;
463 				case "unittest-cov": settings.addOptions(unittests, coverage, debugMode, debugInfo); break;
464 				case "unittest-cov-ctfe": settings.addOptions(unittests, coverageCTFE, debugMode, debugInfo); break;
465 				case "syntax": settings.addOptions(syntaxOnly); break;
466 			}
467 		}
468 	}
469 
470 	/** Returns the selected configuration for a certain dependency.
471 
472 		If no configuration is specified in the package recipe, null will be
473 		returned instead.
474 
475 		FIXME: The `platform` parameter is currently ignored, as the
476 			`"subConfigurations"` field doesn't support platform suffixes.
477 	*/
478 	string getSubConfiguration(string config, in Package dependency, in BuildPlatform platform)
479 	const {
480 		bool found = false;
481 		foreach(ref c; m_info.configurations){
482 			if( c.name == config ){
483 				if( auto pv = dependency.name in c.buildSettings.subConfigurations ) return *pv;
484 				found = true;
485 				break;
486 			}
487 		}
488 		assert(found || config is null, "Invalid configuration \""~config~"\" for "~this.name);
489 		if( auto pv = dependency.name in m_info.buildSettings.subConfigurations ) return *pv;
490 		return null;
491 	}
492 
493 	/** Returns the default configuration to build for the given platform.
494 
495 		This will return the first configuration that is applicable to the given
496 		platform, or `null` if none is applicable. By default, only library
497 		configurations will be returned. Setting `allow_non_library` to `true`
498 		will also return executable configurations.
499 
500 		See_Also: `getPlatformConfigurations`
501 	*/
502 	string getDefaultConfiguration(in BuildPlatform platform, bool allow_non_library = false)
503 	const {
504 		foreach (ref conf; m_info.configurations) {
505 			if (!conf.matchesPlatform(platform)) continue;
506 			if (!allow_non_library && conf.buildSettings.targetType == TargetType.executable) continue;
507 			return conf.name;
508 		}
509 		return null;
510 	}
511 
512 	/** Returns a list of configurations suitable for the given platform.
513 
514 		Params:
515 			platform = The platform against which to match configurations
516 			allow_non_library = If set to true, executable configurations will
517 				also be included.
518 
519 		See_Also: `getDefaultConfiguration`
520 	*/
521 	string[] getPlatformConfigurations(in BuildPlatform platform, bool allow_non_library = false)
522 	const {
523 		auto ret = appender!(string[]);
524 		foreach(ref conf; m_info.configurations){
525 			if (!conf.matchesPlatform(platform)) continue;
526 			if (!allow_non_library && conf.buildSettings.targetType == TargetType.executable) continue;
527 			ret ~= conf.name;
528 		}
529 		if (ret.data.length == 0) ret.put(null);
530 		return ret.data;
531 	}
532 
533 	/** Determines if the package has a dependency to a certain package.
534 
535 		Params:
536 			dependency_name = The name of the package to search for
537 			config = Name of the configuration to use when searching
538 				for dependencies
539 
540 		See_Also: `getDependencies`
541 	*/
542 	bool hasDependency(string dependency_name, string config)
543 	const {
544 		if (dependency_name in m_info.buildSettings.dependencies) return true;
545 		foreach (ref c; m_info.configurations)
546 			if ((config.empty || c.name == config) && dependency_name in c.buildSettings.dependencies)
547 				return true;
548 		return false;
549 	}
550 
551 	/** Retrieves all dependencies for a particular configuration.
552 
553 		This includes dependencies that are declared at the root level of the
554 		package recipe, as well as those declared within the specified
555 		configuration. If no configuration with the given name exists, only
556 		dependencies declared at the root level will be returned.
557 
558 		See_Also: `hasDependency`
559 	*/
560 	const(Dependency[string]) getDependencies(string config)
561 	const {
562 		Dependency[string] ret;
563 		foreach (k, v; m_info.buildSettings.dependencies) {
564 			// DMD bug: Not giving `Dependency` here leads to RangeError
565 			Dependency dep = v;
566 			ret[k] = dep;
567 		}
568 		foreach (ref conf; m_info.configurations)
569 			if (conf.name == config) {
570 				foreach (k, v; conf.buildSettings.dependencies) {
571 					Dependency dep = v;
572 					ret[k] = dep;
573 				}
574 				break;
575 			}
576 		return ret;
577 	}
578 
579 	/** Returns a list of all possible dependencies of the package.
580 
581 		This list includes all dependencies of all configurations. The same
582 		package may occur multiple times with possibly different `Dependency`
583 		values.
584 	*/
585 	PackageDependency[] getAllDependencies()
586 	const {
587 		auto ret = appender!(PackageDependency[]);
588 		getAllDependenciesRange().copy(ret);
589 		return ret.data;
590 	}
591 
592 	// Left as package until the final API for this has been found
593 	package auto getAllDependenciesRange()
594 	const {
595 		return
596 			chain(
597 				only(this.recipe.buildSettings.dependencies.byKeyValue),
598 				this.recipe.configurations.map!(c => c.buildSettings.dependencies.byKeyValue)
599 			)
600 			.joiner()
601 			.map!(d => PackageDependency(d.key, d.value));
602 	}
603 
604 
605 	/** Returns a description of the package for use in IDEs or build tools.
606 	*/
607 	PackageDescription describe(BuildPlatform platform, string config)
608 	const {
609 		return describe(platform, getCompiler(platform.compilerBinary), config);
610 	}
611 	/// ditto
612 	PackageDescription describe(BuildPlatform platform, Compiler compiler, string config)
613 	const {
614 		PackageDescription ret;
615 		ret.configuration = config;
616 		ret.path = m_path.toNativeString();
617 		ret.name = this.name;
618 		ret.version_ = this.version_;
619 		ret.description = m_info.description;
620 		ret.homepage = m_info.homepage;
621 		ret.authors = m_info.authors.dup;
622 		ret.copyright = m_info.copyright;
623 		ret.license = m_info.license;
624 		ret.dependencies = getDependencies(config).keys;
625 
626 		// save build settings
627 		BuildSettings bs = getBuildSettings(platform, config);
628 		BuildSettings allbs = getCombinedBuildSettings();
629 
630 		ret.targetType = bs.targetType;
631 		ret.targetPath = bs.targetPath;
632 		ret.targetName = bs.targetName;
633 		if (ret.targetType != TargetType.none && compiler)
634 			ret.targetFileName = compiler.getTargetFileName(bs, platform);
635 		ret.workingDirectory = bs.workingDirectory;
636 		ret.mainSourceFile = bs.mainSourceFile;
637 		ret.dflags = bs.dflags;
638 		ret.lflags = bs.lflags;
639 		ret.libs = bs.libs;
640 		ret.injectSourceFiles = bs.injectSourceFiles;
641 		ret.copyFiles = bs.copyFiles;
642 		ret.versions = bs.versions;
643 		ret.debugVersions = bs.debugVersions;
644 		ret.importPaths = bs.importPaths;
645 		ret.stringImportPaths = bs.stringImportPaths;
646 		ret.preGenerateCommands = bs.preGenerateCommands;
647 		ret.postGenerateCommands = bs.postGenerateCommands;
648 		ret.preBuildCommands = bs.preBuildCommands;
649 		ret.postBuildCommands = bs.postBuildCommands;
650 		ret.environments = bs.environments;
651 		ret.buildEnvironments = bs.buildEnvironments;
652 		ret.runEnvironments = bs.runEnvironments;
653 		ret.preGenerateEnvironments = bs.preGenerateEnvironments;
654 		ret.postGenerateEnvironments = bs.postGenerateEnvironments;
655 		ret.preBuildEnvironments = bs.preBuildEnvironments;
656 		ret.postBuildEnvironments = bs.postBuildEnvironments;
657 		ret.preRunEnvironments = bs.preRunEnvironments;
658 		ret.postRunEnvironments = bs.postRunEnvironments;
659 
660 		// prettify build requirements output
661 		for (int i = 1; i <= BuildRequirement.max; i <<= 1)
662 			if (bs.requirements & cast(BuildRequirement)i)
663 				ret.buildRequirements ~= cast(BuildRequirement)i;
664 
665 		// prettify options output
666 		for (int i = 1; i <= BuildOption.max; i <<= 1)
667 			if (bs.options & cast(BuildOption)i)
668 				ret.options ~= cast(BuildOption)i;
669 
670 		// collect all possible source files and determine their types
671 		SourceFileRole[string] sourceFileTypes;
672 		foreach (f; allbs.stringImportFiles) sourceFileTypes[f] = SourceFileRole.unusedStringImport;
673 		foreach (f; allbs.importFiles) sourceFileTypes[f] = SourceFileRole.unusedImport;
674 		foreach (f; allbs.sourceFiles) sourceFileTypes[f] = SourceFileRole.unusedSource;
675 		foreach (f; bs.stringImportFiles) sourceFileTypes[f] = SourceFileRole.stringImport;
676 		foreach (f; bs.importFiles) sourceFileTypes[f] = SourceFileRole.import_;
677 		foreach (f; bs.sourceFiles) sourceFileTypes[f] = SourceFileRole.source;
678 		foreach (f; sourceFileTypes.byKey.array.sort()) {
679 			SourceFileDescription sf;
680 			sf.path = f;
681 			sf.role = sourceFileTypes[f];
682 			ret.files ~= sf;
683 		}
684 
685 		return ret;
686 	}
687 
688 	private void checkDubRequirements()
689 	{
690 		import dub.dependency : Dependency;
691 		import dub.semver : isValidVersion;
692 		import dub.version_ : dubVersion;
693 		import std.exception : enforce;
694 
695 		const dep = m_info.toolchainRequirements.dub;
696 
697 		static assert(dubVersion.length);
698 		static if (dubVersion[0] == 'v') {
699 			enum dv = dubVersion[1 .. $];
700 		}
701 		else {
702 			enum dv = dubVersion;
703 		}
704 		static assert(isValidVersion(dv));
705 
706 		enforce(dep.matches(dv),
707 			"dub-" ~ dv ~ " does not comply with toolchainRequirements.dub "
708 			~ "specification: " ~ m_info.toolchainRequirements.dub.toString()
709 			~ "\nPlease consider upgrading your DUB installation");
710 	}
711 
712 	private void fillWithDefaults()
713 	{
714 		auto bs = &m_info.buildSettings;
715 
716 		// check for default string import folders
717 		if ("" !in bs.stringImportPaths) {
718 			foreach(defvf; ["views"]){
719 				if( existsFile(m_path ~ defvf) )
720 					bs.stringImportPaths[""] ~= defvf;
721 			}
722 		}
723 
724 		// check for default source folders
725 		immutable hasSP = ("" in bs.sourcePaths) !is null;
726 		immutable hasIP = ("" in bs.importPaths) !is null;
727 		if (!hasSP || !hasIP) {
728 			foreach (defsf; ["source/", "src/"]) {
729 				if (existsFile(m_path ~ defsf)) {
730 					if (!hasSP) bs.sourcePaths[""] ~= defsf;
731 					if (!hasIP) bs.importPaths[""] ~= defsf;
732 				}
733 			}
734 		}
735 
736 		// generate default configurations if none are defined
737 		if (m_info.configurations.length == 0) {
738 			// check for default app_main
739 			string app_main_file;
740 			auto pkg_name = m_info.name.length ? m_info.name : "unknown";
741 			MainFileSearch: foreach_reverse(sf; bs.sourcePaths.get("", null)){
742 				auto p = m_path ~ sf;
743 				if( !existsFile(p) ) continue;
744 				foreach(fil; ["app.d", "main.d", pkg_name ~ "/main.d", pkg_name ~ "/" ~ "app.d"]){
745 					if( existsFile(p ~ fil) ) {
746 						app_main_file = (NativePath(sf) ~ fil).toNativeString();
747 						break MainFileSearch;
748 					}
749 				}
750 			}
751 
752 			if (bs.targetType == TargetType.executable) {
753 				BuildSettingsTemplate app_settings;
754 				app_settings.targetType = TargetType.executable;
755 				if (bs.mainSourceFile.empty) app_settings.mainSourceFile = app_main_file;
756 				m_info.configurations ~= ConfigurationInfo("application", app_settings);
757 			} else if (bs.targetType != TargetType.none) {
758 				BuildSettingsTemplate lib_settings;
759 				lib_settings.targetType = bs.targetType == TargetType.autodetect ? TargetType.library : bs.targetType;
760 
761 				if (bs.targetType == TargetType.autodetect) {
762 					if (app_main_file.length) {
763 						lib_settings.excludedSourceFiles[""] ~= app_main_file;
764 
765 						BuildSettingsTemplate app_settings;
766 						app_settings.targetType = TargetType.executable;
767 						app_settings.mainSourceFile = app_main_file;
768 						m_info.configurations ~= ConfigurationInfo("application", app_settings);
769 					}
770 				}
771 
772 				m_info.configurations ~= ConfigurationInfo("library", lib_settings);
773 			}
774 		}
775 	}
776 
777 	package void simpleLint()
778 	const {
779 		if (m_parentPackage) {
780 			if (m_parentPackage.path != path) {
781 				if (this.recipe.license.length && this.recipe.license != m_parentPackage.recipe.license)
782 					logWarn("Warning: License in subpackage %s is different than it's parent package, this is discouraged.", name);
783 			}
784 		}
785 		if (name.empty) logWarn("Warning: The package in %s has no name.", path);
786 		bool[string] cnames;
787 		foreach (ref c; this.recipe.configurations) {
788 			if (c.name in cnames)
789 				logWarn("Warning: Multiple configurations with the name \"%s\" are defined in package \"%s\". This will most likely cause configuration resolution issues.",
790 					c.name, this.name);
791 			cnames[c.name] = true;
792 		}
793 	}
794 
795 	/// Exclude files listed in mainSourceFile for other configurations unless they are listed in sourceFiles
796 	private void mutuallyExcludeMainFiles()
797 	{
798 		string[] allMainFiles;
799 		foreach (ref config; m_info.configurations)
800 			if (!config.buildSettings.mainSourceFile.empty())
801 				allMainFiles ~= config.buildSettings.mainSourceFile;
802 
803 		if (allMainFiles.length == 0)
804 			return;
805 
806 		foreach (ref config; m_info.configurations) {
807 			import std.algorithm.searching : canFind;
808 			auto bs = &config.buildSettings;
809 			auto otherMainFiles = allMainFiles.filter!(elem => (elem != bs.mainSourceFile)).array;
810 
811 			if (bs.sourceFiles.length == 0)
812 				bs.excludedSourceFiles[""] ~= otherMainFiles;
813 			else
814 				foreach (suffix, arr; bs.sourceFiles)
815 					bs.excludedSourceFiles[suffix] ~= otherMainFiles.filter!(elem => !canFind(arr, elem)).array;
816 		}
817 	}
818 }
819 
820 private string determineVersionFromSCM(NativePath path)
821 {
822 	if (existsFile(path ~ ".git"))
823 	{
824 		import dub.internal.git : determineVersionWithGit;
825 
826 		return determineVersionWithGit(path);
827 	}
828 	return null;
829 }