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 		ret.environments = bs.environments;
610 		ret.buildEnvironments = bs.buildEnvironments;
611 		ret.runEnvironments = bs.runEnvironments;
612 		ret.preGenerateEnvironments = bs.preGenerateEnvironments;
613 		ret.postGenerateEnvironments = bs.postGenerateEnvironments;
614 		ret.preBuildEnvironments = bs.preBuildEnvironments;
615 		ret.postBuildEnvironments = bs.postBuildEnvironments;
616 		ret.preRunEnvironments = bs.preRunEnvironments;
617 		ret.postRunEnvironments = bs.postRunEnvironments;
618 
619 		// prettify build requirements output
620 		for (int i = 1; i <= BuildRequirement.max; i <<= 1)
621 			if (bs.requirements & cast(BuildRequirement)i)
622 				ret.buildRequirements ~= cast(BuildRequirement)i;
623 
624 		// prettify options output
625 		for (int i = 1; i <= BuildOption.max; i <<= 1)
626 			if (bs.options & cast(BuildOption)i)
627 				ret.options ~= cast(BuildOption)i;
628 
629 		// collect all possible source files and determine their types
630 		SourceFileRole[string] sourceFileTypes;
631 		foreach (f; allbs.stringImportFiles) sourceFileTypes[f] = SourceFileRole.unusedStringImport;
632 		foreach (f; allbs.importFiles) sourceFileTypes[f] = SourceFileRole.unusedImport;
633 		foreach (f; allbs.sourceFiles) sourceFileTypes[f] = SourceFileRole.unusedSource;
634 		foreach (f; bs.stringImportFiles) sourceFileTypes[f] = SourceFileRole.stringImport;
635 		foreach (f; bs.importFiles) sourceFileTypes[f] = SourceFileRole.import_;
636 		foreach (f; bs.sourceFiles) sourceFileTypes[f] = SourceFileRole.source;
637 		foreach (f; sourceFileTypes.byKey.array.sort()) {
638 			SourceFileDescription sf;
639 			sf.path = f;
640 			sf.role = sourceFileTypes[f];
641 			ret.files ~= sf;
642 		}
643 
644 		return ret;
645 	}
646 
647 	private void checkDubRequirements()
648 	{
649 		import dub.dependency : Dependency;
650 		import dub.semver : isValidVersion;
651 		import dub.version_ : dubVersion;
652 		import std.exception : enforce;
653 
654 		const dep = m_info.toolchainRequirements.dub;
655 
656 		static assert(dubVersion.length);
657 		static if (dubVersion[0] == 'v') {
658 			enum dv = dubVersion[1 .. $];
659 		}
660 		else {
661 			enum dv = dubVersion;
662 		}
663 		static assert(isValidVersion(dv));
664 
665 		enforce(dep.matches(dv),
666 			"dub-" ~ dv ~ " does not comply with toolchainRequirements.dub "
667 			~ "specification: " ~ m_info.toolchainRequirements.dub.toString()
668 			~ "\nPlease consider upgrading your DUB installation");
669 	}
670 
671 	private void fillWithDefaults()
672 	{
673 		auto bs = &m_info.buildSettings;
674 
675 		// check for default string import folders
676 		if ("" !in bs.stringImportPaths) {
677 			foreach(defvf; ["views"]){
678 				if( existsFile(m_path ~ defvf) )
679 					bs.stringImportPaths[""] ~= defvf;
680 			}
681 		}
682 
683 		// check for default source folders
684 		immutable hasSP = ("" in bs.sourcePaths) !is null;
685 		immutable hasIP = ("" in bs.importPaths) !is null;
686 		if (!hasSP || !hasIP) {
687 			foreach (defsf; ["source/", "src/"]) {
688 				if (existsFile(m_path ~ defsf)) {
689 					if (!hasSP) bs.sourcePaths[""] ~= defsf;
690 					if (!hasIP) bs.importPaths[""] ~= defsf;
691 				}
692 			}
693 		}
694 
695 		// check for default app_main
696 		string app_main_file;
697 		auto pkg_name = m_info.name.length ? m_info.name : "unknown";
698 		foreach(sf; bs.sourcePaths.get("", null)){
699 			auto p = m_path ~ sf;
700 			if( !existsFile(p) ) continue;
701 			foreach(fil; ["app.d", "main.d", pkg_name ~ "/main.d", pkg_name ~ "/" ~ "app.d"]){
702 				if( existsFile(p ~ fil) ) {
703 					app_main_file = (NativePath(sf) ~ fil).toNativeString();
704 					break;
705 				}
706 			}
707 		}
708 
709 		// generate default configurations if none are defined
710 		if (m_info.configurations.length == 0) {
711 			if (bs.targetType == TargetType.executable) {
712 				BuildSettingsTemplate app_settings;
713 				app_settings.targetType = TargetType.executable;
714 				if (bs.mainSourceFile.empty) app_settings.mainSourceFile = app_main_file;
715 				m_info.configurations ~= ConfigurationInfo("application", app_settings);
716 			} else if (bs.targetType != TargetType.none) {
717 				BuildSettingsTemplate lib_settings;
718 				lib_settings.targetType = bs.targetType == TargetType.autodetect ? TargetType.library : bs.targetType;
719 
720 				if (bs.targetType == TargetType.autodetect) {
721 					if (app_main_file.length) {
722 						lib_settings.excludedSourceFiles[""] ~= app_main_file;
723 
724 						BuildSettingsTemplate app_settings;
725 						app_settings.targetType = TargetType.executable;
726 						app_settings.mainSourceFile = app_main_file;
727 						m_info.configurations ~= ConfigurationInfo("application", app_settings);
728 					}
729 				}
730 
731 				m_info.configurations ~= ConfigurationInfo("library", lib_settings);
732 			}
733 		}
734 	}
735 
736 	package void simpleLint()
737 	const {
738 		if (m_parentPackage) {
739 			if (m_parentPackage.path != path) {
740 				if (this.recipe.license.length && this.recipe.license != m_parentPackage.recipe.license)
741 					logWarn("Warning: License in subpackage %s is different than it's parent package, this is discouraged.", name);
742 			}
743 		}
744 		if (name.empty) logWarn("Warning: The package in %s has no name.", path);
745 		bool[string] cnames;
746 		foreach (ref c; this.recipe.configurations) {
747 			if (c.name in cnames)
748 				logWarn("Warning: Multiple configurations with the name \"%s\" are defined in package \"%s\". This will most likely cause configuration resolution issues.",
749 					c.name, this.name);
750 			cnames[c.name] = true;
751 		}
752 	}
753 
754 	/// Exclude files listed in mainSourceFile for other configurations unless they are listed in sourceFiles
755 	private void mutuallyExcludeMainFiles()
756 	{
757 		string[] allMainFiles;
758 		foreach (ref config; m_info.configurations)
759 			if (!config.buildSettings.mainSourceFile.empty())
760 				allMainFiles ~= config.buildSettings.mainSourceFile;
761 
762 		if (allMainFiles.length == 0)
763 			return;
764 
765 		foreach (ref config; m_info.configurations) {
766 			import std.algorithm.searching : canFind;
767 			auto bs = &config.buildSettings;
768 			auto otherMainFiles = allMainFiles.filter!(elem => (elem != bs.mainSourceFile)).array;
769 
770 			if (bs.sourceFiles.length == 0)
771 				bs.excludedSourceFiles[""] ~= otherMainFiles;
772 			else
773 				foreach (suffix, arr; bs.sourceFiles)
774 					bs.excludedSourceFiles[suffix] ~= otherMainFiles.filter!(elem => !canFind(arr, elem)).array;
775 		}
776 	}
777 }
778 
779 private string determineVersionFromSCM(NativePath path)
780 {
781 	if (existsFile(path ~ ".git"))
782 	{
783 		import dub.internal.git : determineVersionWithGit;
784 
785 		return determineVersionWithGit(path);
786 	}
787 	return null;
788 }