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