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