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