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