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 deprecated("Open an issue if this is needed")
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 	// `package` visibility as it is set from the PackageManager
84 	package NativePath m_infoFile;
85 	private {
86 		NativePath m_path;
87 		PackageRecipe m_info;
88 		PackageRecipe m_rawRecipe;
89 		Package m_parentPackage;
90 	}
91 
92 	/** Constructs a `Package` using an in-memory package recipe.
93 
94 		Params:
95 			json_recipe = The package recipe in JSON format
96 			recipe = The package recipe in generic format
97 			root = The directory in which the package resides (if any).
98 			parent = Reference to the parent package, if the new package is a
99 				sub package.
100 			version_override = Optional version to associate to the package
101 				instead of the one declared in the package recipe, or the one
102 				determined by invoking the VCS (GIT currently).
103 	*/
104 	deprecated("Provide an already parsed PackageRecipe instead of a JSON object")
105 	this(Json json_recipe, NativePath root = NativePath(), Package parent = null, string version_override = "")
106 	{
107 		import dub.recipe.json;
108 
109 		PackageRecipe recipe;
110 		parseJson(recipe, json_recipe, parent ? parent.name : null);
111 		this(recipe, root, parent, version_override);
112 	}
113 	/// ditto
114 	this(PackageRecipe recipe, NativePath root = NativePath(), Package parent = null, string version_override = "")
115 	{
116 		// save the original recipe
117 		m_rawRecipe = recipe.clone;
118 
119 		if (!version_override.empty)
120 			recipe.version_ = version_override;
121 
122 		// try to run git to determine the version of the package if no explicit version was given
123 		if (recipe.version_.length == 0 && !parent) {
124 			try recipe.version_ = determineVersionFromSCM(root);
125 			catch (Exception e) logDebug("Failed to determine version by SCM: %s", e.msg);
126 
127 			if (recipe.version_.length == 0) {
128 				logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", recipe.name, this.path.toNativeString());
129 				// TODO: Assume unknown version here?
130 				// recipe.version_ = Version.unknown.toString();
131 				recipe.version_ = Version.masterBranch.toString();
132 			} else logDiagnostic("Determined package version using GIT: %s %s", recipe.name, recipe.version_);
133 		}
134 
135 		m_parentPackage = parent;
136 		m_path = root;
137 		m_path.endsWithSlash = true;
138 
139 		// use the given recipe as the basis
140 		m_info = recipe;
141 
142 		checkDubRequirements();
143 		fillWithDefaults();
144 		mutuallyExcludeMainFiles();
145 	}
146 
147 	/** Searches the given directory for package recipe files.
148 
149 		Params:
150 			directory = The directory to search
151 
152 		Returns:
153 			Returns the full path to the package file, if any was found.
154 			Otherwise returns an empty path.
155 	*/
156 	deprecated("Use `PackageManager.findPackageFile`")
157 	static NativePath findPackageFile(NativePath directory)
158 	{
159 		foreach (file; packageInfoFiles) {
160 			auto filename = directory ~ file.filename;
161 			if (existsFile(filename)) return filename;
162 		}
163 		return NativePath.init;
164 	}
165 
166 	/** Constructs a `Package` using a package that is physically present on the local file system.
167 
168 		Params:
169 			root = The directory in which the package resides.
170 			recipe_file = Optional path to the package recipe file. If left
171 				empty, the `root` directory will be searched for a recipe file.
172 			parent = Reference to the parent package, if the new package is a
173 				sub package.
174 			version_override = Optional version to associate to the package
175 				instead of the one declared in the package recipe, or the one
176 				determined by invoking the VCS (GIT currently).
177 			mode = Whether to issue errors, warning, or ignore unknown keys in dub.json
178 	*/
179 	deprecated("Use `PackageManager.getOrLoadPackage` instead of loading packages directly")
180 	static Package load(NativePath root, NativePath recipe_file = NativePath.init,
181 		Package parent = null, string version_override = "",
182 		StrictMode mode = StrictMode.Ignore)
183 	{
184 		import dub.recipe.io;
185 
186 		if (recipe_file.empty) recipe_file = findPackageFile(root);
187 
188 		enforce(!recipe_file.empty,
189 			"No package file found in %s, expected one of %s"
190 				.format(root.toNativeString(),
191 					packageInfoFiles.map!(f => cast(string)f.filename).join("/")));
192 
193 		auto recipe = readPackageRecipe(recipe_file, parent ? parent.name : null, mode);
194 
195 		auto ret = new Package(recipe, root, parent, version_override);
196 		ret.m_infoFile = recipe_file;
197 		return ret;
198 	}
199 
200 	/** Returns the qualified name of the package.
201 
202 		The qualified name includes any possible parent package if this package
203 		is a sub package.
204 	*/
205 	@property string name()
206 	const {
207 		if (m_parentPackage) return m_parentPackage.name ~ ":" ~ m_info.name;
208 		else return m_info.name;
209 	}
210 
211 	/** Returns the directory in which the package resides.
212 
213 		Note that this can be empty for packages that are not stored in the
214 		local file system.
215 	*/
216 	@property NativePath path() const { return m_path; }
217 
218 
219 	/** Accesses the version associated with this package.
220 
221 		Note that this is a shortcut to `this.recipe.version_`.
222 	*/
223 	@property Version version_() const { return m_parentPackage ? m_parentPackage.version_ : Version(m_info.version_); }
224 	/// ditto
225 	@property void version_(Version value) { assert(m_parentPackage is null); m_info.version_ = value.toString(); }
226 
227 	/** Accesses the recipe contents of this package.
228 
229 		The recipe contains any default values and configurations added by DUB.
230 		To access the raw user recipe, use the `rawRecipe` property.
231 
232 		See_Also: `rawRecipe`
233 	*/
234 	@property ref inout(PackageRecipe) recipe() inout { return m_info; }
235 
236 	/** Accesses the original package recipe.
237 
238 		The returned recipe matches exactly the contents of the original package
239 		recipe. For the effective package recipe, augmented with DUB generated
240 		default settings and configurations, use the `recipe` property.
241 
242 		See_Also: `recipe`
243 	*/
244 	@property ref const(PackageRecipe) rawRecipe() const { return m_rawRecipe; }
245 
246 	/** Returns the path to the package recipe file.
247 
248 		Note that this can be empty for packages that are not stored in the
249 		local file system.
250 	*/
251 	@property NativePath recipePath() const { return m_infoFile; }
252 
253 
254 	/** Returns the base package of this package.
255 
256 		The base package is the root of the sub package hierarchy (i.e. the
257 		topmost parent). This will be `null` for packages that are not sub
258 		packages.
259 	*/
260 	@property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; }
261 
262 	/** Returns the parent of this package.
263 
264 		The parent package is the package that contains a sub package. This will
265 		be `null` for packages that are not sub packages.
266 	*/
267 	@property inout(Package) parentPackage() inout { return m_parentPackage; }
268 
269 	/** Returns the list of all sub packages.
270 
271 		Note that this is a shortcut for `this.recipe.subPackages`.
272 	*/
273 	@property inout(SubPackage)[] subPackages() inout { return m_info.subPackages; }
274 
275 	/** Returns the list of all build configuration names.
276 
277 		Configuration contents can be accessed using `this.recipe.configurations`.
278 	*/
279 	@property string[] configurations()
280 	const {
281 		auto ret = appender!(string[])();
282 		foreach (ref config; m_info.configurations)
283 			ret.put(config.name);
284 		return ret.data;
285 	}
286 
287 	/** Returns the list of all custom build type names.
288 
289 		Build type contents can be accessed using `this.recipe.buildTypes`.
290 	*/
291 	@property string[] customBuildTypes()
292 	const {
293 		auto ret = appender!(string[])();
294 		foreach (name; m_info.buildTypes.byKey)
295 			ret.put(name);
296 		return ret.data;
297 	}
298 
299 	/** Writes the current recipe contents to a recipe file.
300 
301 		The parameter-less overload writes to `this.path`, which must not be
302 		empty. The default recipe file name will be used in this case.
303 	*/
304 	void storeInfo()
305 	{
306 		storeInfo(m_path);
307 		m_infoFile = m_path ~ defaultPackageFilename;
308 	}
309 	/// ditto
310 	void storeInfo(NativePath path)
311 	const {
312 		auto filename = path ~ defaultPackageFilename;
313 		writeJsonFile(filename, m_info.toJson());
314 	}
315 
316 	deprecated("Use `PackageManager.getSubPackage` instead")
317 	Nullable!PackageRecipe getInternalSubPackage(string name)
318 	{
319 		foreach (ref p; m_info.subPackages)
320 			if (p.path.empty && p.recipe.name == name)
321 				return Nullable!PackageRecipe(p.recipe);
322 		return Nullable!PackageRecipe();
323 	}
324 
325 	/** Searches for use of compiler-specific flags that have generic
326 		alternatives.
327 
328 		This will output a warning message for each such flag to the console.
329 	*/
330 	void warnOnSpecialCompilerFlags()
331 	{
332 		// warn about use of special flags
333 		m_info.buildSettings.warnOnSpecialCompilerFlags(m_info.name, null);
334 		foreach (ref config; m_info.configurations)
335 			config.buildSettings.warnOnSpecialCompilerFlags(m_info.name, config.name);
336 	}
337 
338 	/** Retrieves a build settings template.
339 
340 		If no `config` is given, this returns the build settings declared at the
341 		root level of the package recipe. Otherwise returns the settings
342 		declared within the given configuration (excluding those at the root
343 		level).
344 
345 		Note that this is a shortcut to accessing `this.recipe.buildSettings` or
346 		`this.recipe.configurations[].buildSettings`.
347 	*/
348 	const(BuildSettingsTemplate) getBuildSettings(string config = null)
349 	const {
350 		if (config.length) {
351 			foreach (ref conf; m_info.configurations)
352 				if (conf.name == config)
353 					return conf.buildSettings;
354 			assert(false, "Unknown configuration: "~config);
355 		} else {
356 			return m_info.buildSettings;
357 		}
358 	}
359 
360 	/** Returns all BuildSettings for the given platform and configuration.
361 
362 		This will gather the effective build settings declared in the package
363 		recipe for when building on a particular platform and configuration.
364 		Root build settings and configuration specific settings will be
365 		merged.
366 	*/
367 	BuildSettings getBuildSettings(in BuildPlatform platform, string config)
368 	const {
369 		BuildSettings ret;
370 		m_info.buildSettings.getPlatformSettings(ret, platform, this.path);
371 		bool found = false;
372 		foreach(ref conf; m_info.configurations){
373 			if( conf.name != config ) continue;
374 			conf.buildSettings.getPlatformSettings(ret, platform, this.path);
375 			found = true;
376 			break;
377 		}
378 		assert(found || config is null, "Unknown configuration for "~m_info.name~": "~config);
379 
380 		// construct default target name based on package name
381 		if( ret.targetName.empty ) ret.targetName = this.name.replace(":", "_");
382 
383 		// special support for DMD style flags
384 		getCompiler("dmd").extractBuildOptions(ret);
385 
386 		return ret;
387 	}
388 
389 	/** Returns the combination of all build settings for all configurations
390 		and platforms.
391 
392 		This can be useful for IDEs to gather a list of all potentially used
393 		files or settings.
394 	*/
395 	BuildSettings getCombinedBuildSettings()
396 	const {
397 		BuildSettings ret;
398 		m_info.buildSettings.getPlatformSettings(ret, BuildPlatform.any, this.path);
399 		foreach(ref conf; m_info.configurations)
400 			conf.buildSettings.getPlatformSettings(ret, BuildPlatform.any, this.path);
401 
402 		// construct default target name based on package name
403 		if (ret.targetName.empty) ret.targetName = this.name.replace(":", "_");
404 
405 		// special support for DMD style flags
406 		getCompiler("dmd").extractBuildOptions(ret);
407 
408 		return ret;
409 	}
410 
411 	/** Adds build type specific settings to an existing set of build settings.
412 
413 		This function searches the package recipe for overridden build types. If
414 		none is found, the default build settings will be applied, if
415 		`build_type` matches a default build type name. An exception is thrown
416 		otherwise.
417 	*/
418 	void addBuildTypeSettings(ref BuildSettings settings, in BuildPlatform platform, string build_type)
419 	const {
420 		if (auto pbt = build_type in m_info.buildTypes) {
421 			logDiagnostic("Using custom build type '%s'.", build_type);
422 			pbt.getPlatformSettings(settings, platform, this.path);
423 		} else {
424 			with(BuildOption) switch (build_type) {
425 				default: throw new Exception(format("Unknown build type for %s: '%s'", this.name, build_type));
426 				case "$DFLAGS": break;
427 				case "plain": break;
428 				case "debug": settings.addOptions(debugMode, debugInfo); break;
429 				case "release": settings.addOptions(releaseMode, optimize, inline); break;
430 				case "release-debug": settings.addOptions(releaseMode, optimize, inline, debugInfo); break;
431 				case "release-nobounds": settings.addOptions(releaseMode, optimize, inline, noBoundsCheck); break;
432 				case "unittest": settings.addOptions(unittests, debugMode, debugInfo); break;
433 				case "docs": settings.addOptions(syntaxOnly, _docs); break;
434 				case "ddox": settings.addOptions(syntaxOnly,  _ddox); break;
435 				case "profile": settings.addOptions(profile, optimize, inline, debugInfo); break;
436 				case "profile-gc": settings.addOptions(profileGC, debugInfo); break;
437 				case "cov": settings.addOptions(coverage, debugInfo); break;
438 				case "cov-ctfe": settings.addOptions(coverageCTFE, debugInfo); break;
439 				case "unittest-cov": settings.addOptions(unittests, coverage, debugMode, debugInfo); break;
440 				case "unittest-cov-ctfe": settings.addOptions(unittests, coverageCTFE, debugMode, debugInfo); break;
441 				case "syntax": settings.addOptions(syntaxOnly); break;
442 			}
443 		}
444 
445 		// Add environment DFLAGS last so that user specified values are not overriden by us.
446 		import std.process : environment;
447 		string dflags = environment.get("DFLAGS", "");
448 		settings.addDFlags(dflags.split());
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 		import std.algorithm: sort, uniq;
577 		import std.array: array;
578 		return
579 			chain(
580 				only(this.recipe.buildSettings.dependencies.byKeyValue),
581 				this.recipe.configurations.map!(c => c.buildSettings.dependencies.byKeyValue)
582 			)
583 			.joiner()
584 			.map!(d => PackageDependency(PackageName(d.key), d.value))
585 			.array
586 			.sort
587 			.uniq;
588 	}
589 
590 
591 	/** Returns a description of the package for use in IDEs or build tools.
592 	*/
593 	PackageDescription describe(BuildPlatform platform, string config)
594 	const {
595 		return describe(platform, getCompiler(platform.compilerBinary), config);
596 	}
597 	/// ditto
598 	PackageDescription describe(BuildPlatform platform, Compiler compiler, string config)
599 	const {
600 		PackageDescription ret;
601 		ret.configuration = config;
602 		ret.path = m_path.toNativeString();
603 		ret.name = this.name;
604 		ret.version_ = this.version_;
605 		ret.description = m_info.description;
606 		ret.homepage = m_info.homepage;
607 		ret.authors = m_info.authors.dup;
608 		ret.copyright = m_info.copyright;
609 		ret.license = m_info.license;
610 		ret.dependencies = getDependencies(config).keys;
611 
612 		// save build settings
613 		BuildSettings bs = getBuildSettings(platform, config);
614 		BuildSettings allbs = getCombinedBuildSettings();
615 
616 		ret.targetType = bs.targetType;
617 		ret.targetPath = bs.targetPath;
618 		ret.targetName = bs.targetName;
619 		if (ret.targetType != TargetType.none && compiler)
620 			ret.targetFileName = compiler.getTargetFileName(bs, platform);
621 		ret.workingDirectory = bs.workingDirectory;
622 		ret.mainSourceFile = bs.mainSourceFile;
623 		ret.dflags = bs.dflags;
624 		ret.lflags = bs.lflags;
625 		ret.libs = bs.libs;
626 		ret.injectSourceFiles = bs.injectSourceFiles;
627 		ret.copyFiles = bs.copyFiles;
628 		ret.versions = bs.versions;
629 		ret.debugVersions = bs.debugVersions;
630 		ret.importPaths = bs.importPaths;
631 		ret.cImportPaths = bs.cImportPaths;
632 		ret.stringImportPaths = bs.stringImportPaths;
633 		ret.preGenerateCommands = bs.preGenerateCommands;
634 		ret.postGenerateCommands = bs.postGenerateCommands;
635 		ret.preBuildCommands = bs.preBuildCommands;
636 		ret.postBuildCommands = bs.postBuildCommands;
637 		ret.environments = bs.environments;
638 		ret.buildEnvironments = bs.buildEnvironments;
639 		ret.runEnvironments = bs.runEnvironments;
640 		ret.preGenerateEnvironments = bs.preGenerateEnvironments;
641 		ret.postGenerateEnvironments = bs.postGenerateEnvironments;
642 		ret.preBuildEnvironments = bs.preBuildEnvironments;
643 		ret.postBuildEnvironments = bs.postBuildEnvironments;
644 		ret.preRunEnvironments = bs.preRunEnvironments;
645 		ret.postRunEnvironments = bs.postRunEnvironments;
646 
647 		// prettify build requirements output
648 		for (int i = 1; i <= BuildRequirement.max; i <<= 1)
649 			if (bs.requirements & cast(BuildRequirement)i)
650 				ret.buildRequirements ~= cast(BuildRequirement)i;
651 
652 		// prettify options output
653 		for (int i = 1; i <= BuildOption.max; i <<= 1)
654 			if (bs.options & cast(BuildOption)i)
655 				ret.options ~= cast(BuildOption)i;
656 
657 		// collect all possible source files and determine their types
658 		SourceFileRole[string] sourceFileTypes;
659 		foreach (f; allbs.stringImportFiles) sourceFileTypes[f] = SourceFileRole.unusedStringImport;
660 		foreach (f; allbs.importFiles) sourceFileTypes[f] = SourceFileRole.unusedImport;
661 		foreach (f; allbs.sourceFiles) sourceFileTypes[f] = SourceFileRole.unusedSource;
662 		foreach (f; bs.stringImportFiles) sourceFileTypes[f] = SourceFileRole.stringImport;
663 		foreach (f; bs.importFiles) sourceFileTypes[f] = SourceFileRole.import_;
664 		foreach (f; bs.sourceFiles) sourceFileTypes[f] = SourceFileRole.source;
665 		foreach (f; sourceFileTypes.byKey.array.sort()) {
666 			SourceFileDescription sf;
667 			sf.path = f;
668 			sf.role = sourceFileTypes[f];
669 			ret.files ~= sf;
670 		}
671 
672 		return ret;
673 	}
674 
675 	private void checkDubRequirements()
676 	{
677 		import dub.semver : isValidVersion;
678 		import dub.version_ : dubVersion;
679 		import std.exception : enforce;
680 
681 		const dep = m_info.toolchainRequirements.dub;
682 
683 		static assert(dubVersion.length);
684 		immutable dv = Version(dubVersion[(dubVersion[0] == 'v') .. $]);
685 
686 		enforce(dep.matches(dv),
687 			"dub-" ~ dv.toString() ~ " does not comply with toolchainRequirements.dub "
688 			~ "specification: " ~ m_info.toolchainRequirements.dub.toString()
689 			~ "\nPlease consider upgrading your DUB installation");
690 	}
691 
692 	private void fillWithDefaults()
693 	{
694 		auto bs = &m_info.buildSettings;
695 
696 		// check for default string import folders
697 		if ("" !in bs.stringImportPaths) {
698 			foreach(defvf; ["views"]){
699 				if( existsFile(m_path ~ defvf) )
700 					bs.stringImportPaths[""] ~= defvf;
701 			}
702 		}
703 
704 		// check for default source folders
705 		immutable hasSP = ("" in bs.sourcePaths) !is null;
706 		immutable hasIP = ("" in bs.importPaths) !is null;
707 		if (!hasSP || !hasIP) {
708 			foreach (defsf; ["source/", "src/"]) {
709 				if (existsFile(m_path ~ defsf)) {
710 					if (!hasSP) bs.sourcePaths[""] ~= defsf;
711 					if (!hasIP) bs.importPaths[""] ~= defsf;
712 				}
713 			}
714 		}
715 
716 		// generate default configurations if none are defined
717 		if (m_info.configurations.length == 0) {
718 			// check for default app_main
719 			string app_main_file;
720 			auto pkg_name = m_info.name.length ? m_info.name : "unknown";
721 			MainFileSearch: foreach_reverse(sf; bs.sourcePaths.get("", null)){
722 				auto p = m_path ~ sf;
723 				if( !existsFile(p) ) continue;
724 				foreach(fil; ["app.d", "main.d", pkg_name ~ "/main.d", pkg_name ~ "/" ~ "app.d"]){
725 					if( existsFile(p ~ fil) ) {
726 						app_main_file = (NativePath(sf) ~ fil).toNativeString();
727 						break MainFileSearch;
728 					}
729 				}
730 			}
731 
732 			if (bs.targetType == TargetType.executable) {
733 				BuildSettingsTemplate app_settings;
734 				app_settings.targetType = TargetType.executable;
735 				if (bs.mainSourceFile.empty) app_settings.mainSourceFile = app_main_file;
736 				m_info.configurations ~= ConfigurationInfo("application", app_settings);
737 			} else if (bs.targetType != TargetType.none) {
738 				BuildSettingsTemplate lib_settings;
739 				lib_settings.targetType = bs.targetType == TargetType.autodetect ? TargetType.library : bs.targetType;
740 
741 				if (bs.targetType == TargetType.autodetect) {
742 					if (app_main_file.length) {
743 						lib_settings.excludedSourceFiles[""] ~= app_main_file;
744 
745 						BuildSettingsTemplate app_settings;
746 						app_settings.targetType = TargetType.executable;
747 						app_settings.mainSourceFile = app_main_file;
748 						m_info.configurations ~= ConfigurationInfo("application", app_settings);
749 					}
750 				}
751 
752 				m_info.configurations ~= ConfigurationInfo("library", lib_settings);
753 			}
754 		}
755 	}
756 
757 	package void simpleLint()
758 	const {
759 		if (m_parentPackage) {
760 			if (m_parentPackage.path != path) {
761 				if (this.recipe.license.length && this.recipe.license != m_parentPackage.recipe.license)
762 					logWarn("Warning: License in sub-package %s is different than its parent package, this is discouraged.", name);
763 			}
764 		}
765 		if (name.empty) logWarn("Warning: The package in %s has no name.", path);
766 		bool[string] cnames;
767 		foreach (ref c; this.recipe.configurations) {
768 			if (c.name in cnames)
769 				logWarn("Warning: Multiple configurations with the name \"%s\" are defined in package \"%s\". This will most likely cause configuration resolution issues.",
770 					c.name, this.name);
771 			cnames[c.name] = true;
772 		}
773 	}
774 
775 	/// Exclude files listed in mainSourceFile for other configurations unless they are listed in sourceFiles
776 	private void mutuallyExcludeMainFiles()
777 	{
778 		string[] allMainFiles;
779 		foreach (ref config; m_info.configurations)
780 			if (!config.buildSettings.mainSourceFile.empty())
781 				allMainFiles ~= config.buildSettings.mainSourceFile;
782 
783 		if (allMainFiles.length == 0)
784 			return;
785 
786 		foreach (ref config; m_info.configurations) {
787 			import std.algorithm.searching : canFind;
788 			auto bs = &config.buildSettings;
789 			auto otherMainFiles = allMainFiles.filter!(elem => (elem != bs.mainSourceFile)).array;
790 
791 			if (bs.sourceFiles.length == 0)
792 				bs.excludedSourceFiles[""] ~= otherMainFiles;
793 			else
794 				foreach (suffix, arr; bs.sourceFiles)
795 					bs.excludedSourceFiles[suffix] ~= otherMainFiles.filter!(elem => !canFind(arr, elem)).array;
796 		}
797 	}
798 }
799 
800 private string determineVersionFromSCM(NativePath path)
801 {
802 	if (existsFile(path ~ ".git"))
803 	{
804 		import dub.internal.git : determineVersionWithGit;
805 
806 		return determineVersionWithGit(path);
807 	}
808 	return null;
809 }