1 /**
2 	A package manager.
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
7 */
8 module dub.dub;
9 
10 import dub.compilers.compiler;
11 import dub.dependency;
12 import dub.dependencyresolver;
13 import dub.internal.utils;
14 import dub.internal.vibecompat.core.file;
15 import dub.internal.vibecompat.data.json;
16 import dub.internal.vibecompat.inet.url;
17 import dub.internal.logging;
18 import dub.package_;
19 import dub.packagemanager;
20 import dub.packagesuppliers;
21 import dub.project;
22 import dub.generators.generator;
23 import dub.init;
24 
25 import std.algorithm;
26 import std.array : array, replace;
27 import std.conv : text, to;
28 import std.encoding : sanitize;
29 import std.exception : enforce;
30 import std.file;
31 import std.process : environment;
32 import std.range : assumeSorted, empty;
33 import std.string;
34 
35 // Set output path and options for coverage reports
36 version (DigitalMars) version (D_Coverage)
37 {
38 	shared static this()
39 	{
40 		import core.runtime, std.file, std.path, std.stdio;
41 		dmd_coverSetMerge(true);
42 		auto path = buildPath(dirName(thisExePath()), "../cov");
43 		if (!path.exists)
44 			mkdir(path);
45 		dmd_coverDestPath(path);
46 	}
47 }
48 
49 static this()
50 {
51 	import dub.compilers.dmd : DMDCompiler;
52 	import dub.compilers.gdc : GDCCompiler;
53 	import dub.compilers.ldc : LDCCompiler;
54 	registerCompiler(new DMDCompiler);
55 	registerCompiler(new GDCCompiler);
56 	registerCompiler(new LDCCompiler);
57 }
58 
59 deprecated("use defaultRegistryURLs") enum defaultRegistryURL = defaultRegistryURLs[0];
60 
61 /// The URL to the official package registry and it's default fallback registries.
62 static immutable string[] defaultRegistryURLs = [
63 	"https://code.dlang.org/",
64 	"https://codemirror.dlang.org/",
65 	"https://dub.bytecraft.nl/",
66 	"https://code-mirror.dlang.io/",
67 ];
68 
69 /** Returns a default list of package suppliers.
70 
71 	This will contain a single package supplier that points to the official
72 	package registry.
73 
74 	See_Also: `defaultRegistryURLs`
75 */
76 PackageSupplier[] defaultPackageSuppliers()
77 {
78 	logDiagnostic("Using dub registry url '%s'", defaultRegistryURLs[0]);
79 	return [new FallbackPackageSupplier(defaultRegistryURLs.map!getRegistryPackageSupplier.array)];
80 }
81 
82 /** Returns a registry package supplier according to protocol.
83 
84 	Allowed protocols are dub+http(s):// and maven+http(s)://.
85 */
86 PackageSupplier getRegistryPackageSupplier(string url)
87 {
88 	switch (url.startsWith("dub+", "mvn+", "file://"))
89 	{
90 		case 1:
91 			return new RegistryPackageSupplier(URL(url[4..$]));
92 		case 2:
93 			return new MavenRegistryPackageSupplier(URL(url[4..$]));
94 		case 3:
95 			return new FileSystemPackageSupplier(NativePath(url[7..$]));
96 		default:
97 			return new RegistryPackageSupplier(URL(url));
98 	}
99 }
100 
101 unittest
102 {
103 	auto dubRegistryPackageSupplier = getRegistryPackageSupplier("dub+https://code.dlang.org");
104 	assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org"));
105 
106 	dubRegistryPackageSupplier = getRegistryPackageSupplier("https://code.dlang.org");
107 	assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org"));
108 
109 	auto mavenRegistryPackageSupplier = getRegistryPackageSupplier("mvn+http://localhost:8040/maven/libs-release/dubpackages");
110 	assert(mavenRegistryPackageSupplier.description.canFind(" http://localhost:8040/maven/libs-release/dubpackages"));
111 
112 	auto fileSystemPackageSupplier = getRegistryPackageSupplier("file:///etc/dubpackages");
113 	assert(fileSystemPackageSupplier.description.canFind(" " ~ NativePath("/etc/dubpackages").toNativeString));
114 }
115 
116 /** Provides a high-level entry point for DUB's functionality.
117 
118 	This class provides means to load a certain project (a root package with
119 	all of its dependencies) and to perform high-level operations as found in
120 	the command line interface.
121 */
122 class Dub {
123 	private {
124 		bool m_dryRun = false;
125 		PackageManager m_packageManager;
126 		PackageSupplier[] m_packageSuppliers;
127 		NativePath m_rootPath;
128 		SpecialDirs m_dirs;
129 		UserConfiguration m_config;
130 		Project m_project;
131 		string m_defaultCompiler;
132 	}
133 
134 	/** The default placement location of fetched packages.
135 
136 		This property can be altered, so that packages which are downloaded as part
137 		of the normal upgrade process are stored in a certain location. This is
138 		how the "--local" and "--system" command line switches operate.
139 	*/
140 	PlacementLocation defaultPlacementLocation = PlacementLocation.user;
141 
142 
143 	/** Initializes the instance for use with a specific root package.
144 
145 		Note that a package still has to be loaded using one of the
146 		`loadPackage` overloads.
147 
148 		Params:
149 			root_path = Path to the root package
150 			additional_package_suppliers = A list of package suppliers to try
151 				before the suppliers found in the configurations files and the
152 				`defaultPackageSuppliers`.
153 			skip_registry = Can be used to skip using the configured package
154 				suppliers, as well as the default suppliers.
155 	*/
156 	this(string root_path = ".", PackageSupplier[] additional_package_suppliers = null,
157 			SkipPackageSuppliers skip_registry = SkipPackageSuppliers.none)
158 	{
159 		m_rootPath = NativePath(root_path);
160 		if (!m_rootPath.absolute) m_rootPath = NativePath(getcwd()) ~ m_rootPath;
161 
162 		init();
163 
164 		m_packageSuppliers = this.computePkgSuppliers(additional_package_suppliers,
165 			skip_registry, environment.get("DUB_REGISTRY", null));
166 		m_packageManager = new PackageManager(m_rootPath, m_dirs.localRepository, m_dirs.systemSettings);
167 
168 		auto ccps = m_config.customCachePaths;
169 		if (ccps.length)
170 			m_packageManager.customCachePaths = ccps;
171 
172 		// TODO: Move this environment read out of the ctor
173 		if (auto p = environment.get("DUBPATH")) {
174 			version(Windows) enum pathsep = ";";
175 			else enum pathsep = ":";
176 			NativePath[] paths = p.split(pathsep)
177 				.map!(p => NativePath(p))().array();
178 			m_packageManager.searchPath = paths;
179 		}
180 	}
181 
182 	/** Initializes the instance with a single package search path, without
183 		loading a package.
184 
185 		This constructor corresponds to the "--bare" option of the command line
186 		interface.
187 
188 		Params:
189 		  root = The root path of the Dub instance itself.
190 		  pkg_root = The root of the location where packages are located
191 					 Only packages under this location will be accessible.
192 					 Note that packages at the top levels will be ignored.
193 	*/
194 	this(NativePath root, NativePath pkg_root)
195 	{
196 		// Note: We're doing `init()` before setting the `rootPath`,
197 		// to prevent `init` from reading the project's settings.
198 		init();
199 		this.m_rootPath = root;
200 		m_packageManager = new PackageManager(pkg_root);
201 	}
202 
203 	deprecated("Use the overload that takes `(NativePath pkg_root, NativePath root)`")
204 	this(NativePath pkg_root)
205 	{
206 		this(pkg_root, pkg_root);
207 	}
208 
209 	private void init()
210 	{
211 		this.m_dirs = SpecialDirs.make();
212 		this.loadConfig();
213 		this.determineDefaultCompiler();
214 	}
215 
216 	/**
217 	 * Load user configuration for this instance
218 	 *
219 	 * This can be overloaded in child classes to prevent library / unittest
220 	 * dub from doing any kind of file IO.
221 	 */
222 	protected void loadConfig()
223 	{
224 		import configy.Read;
225 
226 		void readSettingsFile (NativePath path_)
227 		{
228 			// TODO: Remove `StrictMode.Warn` after v1.40 release
229 			// The default is to error, but as the previous parser wasn't
230 			// complaining, we should first warn the user.
231 			const path = path_.toNativeString();
232 			if (path.exists) {
233 				auto newConf = parseConfigFileSimple!UserConfiguration(path, StrictMode.Warn);
234 				if (!newConf.isNull())
235 					this.m_config = this.m_config.merge(newConf.get());
236 			}
237 		}
238 
239 		const dubFolderPath = NativePath(thisExePath).parentPath;
240 
241 		// override default userSettings + localRepository if a $DPATH or
242 		// $DUB_HOME environment variable is set.
243 		bool overrideDubHomeFromEnv;
244 		{
245 			string dubHome = environment.get("DUB_HOME");
246 			if (!dubHome.length) {
247 				auto dpath = environment.get("DPATH");
248 				if (dpath.length)
249 					dubHome = (NativePath(dpath) ~ "dub/").toNativeString();
250 
251 			}
252 			if (dubHome.length) {
253 				overrideDubHomeFromEnv = true;
254 
255 				m_dirs.userSettings = NativePath(dubHome);
256 				m_dirs.localRepository = m_dirs.userSettings;
257 			}
258 		}
259 
260 		readSettingsFile(m_dirs.systemSettings ~ "settings.json");
261 		readSettingsFile(dubFolderPath ~ "../etc/dub/settings.json");
262 		version (Posix) {
263 			if (dubFolderPath.absolute && dubFolderPath.startsWith(NativePath("usr")))
264 				readSettingsFile(NativePath("/etc/dub/settings.json"));
265 		}
266 
267 		// Override user + local package path from system / binary settings
268 		// Then continues loading local settings from these folders. (keeping
269 		// global /etc/dub/settings.json settings intact)
270 		//
271 		// Don't use it if either $DPATH or $DUB_HOME are set, as environment
272 		// variables usually take precedence over configuration.
273 		if (!overrideDubHomeFromEnv && this.m_config.dubHome.set) {
274 			m_dirs.userSettings = NativePath(this.m_config.dubHome.expandEnvironmentVariables);
275 		}
276 
277 		// load user config:
278 		readSettingsFile(m_dirs.userSettings ~ "settings.json");
279 
280 		// load per-package config:
281 		if (!this.m_rootPath.empty)
282 			readSettingsFile(this.m_rootPath ~ "dub.settings.json");
283 
284 		// same as userSettings above, but taking into account the
285 		// config loaded from user settings and per-package config as well.
286 		if (!overrideDubHomeFromEnv && this.m_config.dubHome.set) {
287 			m_dirs.localRepository = NativePath(this.m_config.dubHome.expandEnvironmentVariables);
288 		}
289 	}
290 
291 	unittest
292 	{
293 		scope (exit) environment.remove("DUB_REGISTRY");
294 		auto dub = new TestDub(".", null, SkipPackageSuppliers.configured);
295 		assert(dub.m_packageSuppliers.length == 0);
296 		environment["DUB_REGISTRY"] = "http://example.com/";
297 		dub = new TestDub(".", null, SkipPackageSuppliers.configured);
298 		assert(dub.m_packageSuppliers.length == 1);
299 		environment["DUB_REGISTRY"] = "http://example.com/;http://foo.com/";
300 		dub = new TestDub(".", null, SkipPackageSuppliers.configured);
301 		assert(dub.m_packageSuppliers.length == 2);
302 		dub = new TestDub(".", [new RegistryPackageSupplier(URL("http://bar.com/"))], SkipPackageSuppliers.configured);
303 		assert(dub.m_packageSuppliers.length == 3);
304 	}
305 
306 	/** Get the list of package suppliers.
307 
308 		Params:
309 			additional_package_suppliers = A list of package suppliers to try
310 				before the suppliers found in the configurations files and the
311 				`defaultPackageSuppliers`.
312 			skip_registry = Can be used to skip using the configured package
313 				suppliers, as well as the default suppliers.
314 	*/
315 	deprecated("This is an implementation detail. " ~
316 		"Use `packageSuppliers` to get the computed list of package " ~
317 		"suppliers once a `Dub` instance has been constructed.")
318 	public PackageSupplier[] getPackageSuppliers(PackageSupplier[] additional_package_suppliers, SkipPackageSuppliers skip_registry)
319 	{
320 		return this.computePkgSuppliers(additional_package_suppliers, skip_registry, environment.get("DUB_REGISTRY", null));
321 	}
322 
323 	/// Ditto
324 	private PackageSupplier[] computePkgSuppliers(
325 		PackageSupplier[] additional_package_suppliers, SkipPackageSuppliers skip_registry,
326 		string dub_registry_var)
327 	{
328 		PackageSupplier[] ps = additional_package_suppliers;
329 
330 		if (skip_registry < SkipPackageSuppliers.all)
331 		{
332 			ps ~= dub_registry_var
333 				.splitter(";")
334 				.map!(url => getRegistryPackageSupplier(url))
335 				.array;
336 		}
337 
338 		if (skip_registry < SkipPackageSuppliers.configured)
339 		{
340 			ps ~= m_config.registryUrls
341 				.map!(url => getRegistryPackageSupplier(url))
342 				.array;
343 		}
344 
345 		if (skip_registry < SkipPackageSuppliers.standard)
346 			ps ~= defaultPackageSuppliers();
347 
348 		return ps;
349 	}
350 
351 	/// ditto
352 	deprecated("This is an implementation detail. " ~
353 		"Use `packageSuppliers` to get the computed list of package " ~
354 		"suppliers once a `Dub` instance has been constructed.")
355 	public PackageSupplier[] getPackageSuppliers(PackageSupplier[] additional_package_suppliers)
356 	{
357 		return getPackageSuppliers(additional_package_suppliers, m_config.skipRegistry);
358 	}
359 
360 	unittest
361 	{
362 		auto dub = new TestDub();
363 
364 		assert(dub.computePkgSuppliers(null, SkipPackageSuppliers.none, null).length == 1);
365 		assert(dub.computePkgSuppliers(null, SkipPackageSuppliers.configured, null).length == 0);
366 		assert(dub.computePkgSuppliers(null, SkipPackageSuppliers.standard, null).length == 0);
367 
368 		assert(dub.computePkgSuppliers(null, SkipPackageSuppliers.standard, "http://example.com/")
369 			.length == 1);
370 	}
371 
372 	@property bool dryRun() const { return m_dryRun; }
373 	@property void dryRun(bool v) { m_dryRun = v; }
374 
375 	/** Returns the root path (usually the current working directory).
376 	*/
377 	@property NativePath rootPath() const { return m_rootPath; }
378 	/// ditto
379 	deprecated("Changing the root path is deprecated as it has non-obvious pitfalls " ~
380 			   "(e.g. settings aren't reloaded). Instantiate a new `Dub` instead")
381 	@property void rootPath(NativePath root_path)
382 	{
383 		m_rootPath = root_path;
384 		if (!m_rootPath.absolute) m_rootPath = NativePath(getcwd()) ~ m_rootPath;
385 	}
386 
387 	/// Returns the name listed in the dub.json of the current
388 	/// application.
389 	@property string projectName() const { return m_project.name; }
390 
391 	@property NativePath projectPath() const { return this.m_project.rootPackage.path; }
392 
393 	@property string[] configurations() const { return m_project.configurations; }
394 
395 	@property inout(PackageManager) packageManager() inout { return m_packageManager; }
396 
397 	@property inout(Project) project() inout { return m_project; }
398 
399 	@property inout(PackageSupplier)[] packageSuppliers() inout { return m_packageSuppliers; }
400 
401 	/** Returns the default compiler binary to use for building D code.
402 
403 		If set, the "defaultCompiler" field of the DUB user or system
404 		configuration file will be used. Otherwise the PATH environment variable
405 		will be searched for files named "dmd", "gdc", "gdmd", "ldc2", "ldmd2"
406 		(in that order, taking into account operating system specific file
407 		extensions) and the first match is returned. If no match is found, "dmd"
408 		will be used.
409 	*/
410 	@property string defaultCompiler() const { return m_defaultCompiler; }
411 
412 	/** Returns the default architecture to use for building D code.
413 
414 		If set, the "defaultArchitecture" field of the DUB user or system
415 		configuration file will be used. Otherwise null will be returned.
416 	*/
417 	@property string defaultArchitecture() const { return this.m_config.defaultArchitecture; }
418 
419 	/** Returns the default low memory option to use for building D code.
420 
421 		If set, the "defaultLowMemory" field of the DUB user or system
422 		configuration file will be used. Otherwise false will be returned.
423 	*/
424 	@property bool defaultLowMemory() const { return this.m_config.defaultLowMemory; }
425 
426 	@property const(string[string]) defaultEnvironments() const { return this.m_config.defaultEnvironments; }
427 	@property const(string[string]) defaultBuildEnvironments() const { return this.m_config.defaultBuildEnvironments; }
428 	@property const(string[string]) defaultRunEnvironments() const { return this.m_config.defaultRunEnvironments; }
429 	@property const(string[string]) defaultPreGenerateEnvironments() const { return this.m_config.defaultPreGenerateEnvironments; }
430 	@property const(string[string]) defaultPostGenerateEnvironments() const { return this.m_config.defaultPostGenerateEnvironments; }
431 	@property const(string[string]) defaultPreBuildEnvironments() const { return this.m_config.defaultPreBuildEnvironments; }
432 	@property const(string[string]) defaultPostBuildEnvironments() const { return this.m_config.defaultPostBuildEnvironments; }
433 	@property const(string[string]) defaultPreRunEnvironments() const { return this.m_config.defaultPreRunEnvironments; }
434 	@property const(string[string]) defaultPostRunEnvironments() const { return this.m_config.defaultPostRunEnvironments; }
435 
436 	/** Loads the package that resides within the configured `rootPath`.
437 	*/
438 	void loadPackage()
439 	{
440 		loadPackage(m_rootPath);
441 	}
442 
443 	/// Loads the package from the specified path as the main project package.
444 	void loadPackage(NativePath path)
445 	{
446 		m_project = new Project(m_packageManager, path);
447 	}
448 
449 	/// Loads a specific package as the main project package (can be a sub package)
450 	void loadPackage(Package pack)
451 	{
452 		m_project = new Project(m_packageManager, pack);
453 	}
454 
455 	/** Loads a single file package.
456 
457 		Single-file packages are D files that contain a package receipe comment
458 		at their top. A recipe comment must be a nested `/+ ... +/` style
459 		comment, containing the virtual recipe file name and a colon, followed by the
460 		recipe contents (what would normally be in dub.sdl/dub.json).
461 
462 		Example:
463 		---
464 		/+ dub.sdl:
465 		   name "test"
466 		   dependency "vibe-d" version="~>0.7.29"
467 		+/
468 		import vibe.http.server;
469 
470 		void main()
471 		{
472 			auto settings = new HTTPServerSettings;
473 			settings.port = 8080;
474 			listenHTTP(settings, &hello);
475 		}
476 
477 		void hello(HTTPServerRequest req, HTTPServerResponse res)
478 		{
479 			res.writeBody("Hello, World!");
480 		}
481 		---
482 
483 		The script above can be invoked with "dub --single test.d".
484 	*/
485 	void loadSingleFilePackage(NativePath path)
486 	{
487 		import dub.recipe.io : parsePackageRecipe;
488 		import std.file : mkdirRecurse, readText;
489 		import std.path : baseName, stripExtension;
490 
491 		path = makeAbsolute(path);
492 
493 		string file_content = readText(path.toNativeString());
494 
495 		if (file_content.startsWith("#!")) {
496 			auto idx = file_content.indexOf('\n');
497 			enforce(idx > 0, "The source fine doesn't contain anything but a shebang line.");
498 			file_content = file_content[idx+1 .. $];
499 		}
500 
501 		file_content = file_content.strip();
502 
503 		string recipe_content;
504 
505 		if (file_content.startsWith("/+")) {
506 			file_content = file_content[2 .. $];
507 			auto idx = file_content.indexOf("+/");
508 			enforce(idx >= 0, "Missing \"+/\" to close comment.");
509 			recipe_content = file_content[0 .. idx].strip();
510 		} else throw new Exception("The source file must start with a recipe comment.");
511 
512 		auto nidx = recipe_content.indexOf('\n');
513 
514 		auto idx = recipe_content.indexOf(':');
515 		enforce(idx > 0 && (nidx < 0 || nidx > idx),
516 			"The first line of the recipe comment must list the recipe file name followed by a colon (e.g. \"/+ dub.sdl:\").");
517 		auto recipe_filename = recipe_content[0 .. idx];
518 		recipe_content = recipe_content[idx+1 .. $];
519 		auto recipe_default_package_name = path.toString.baseName.stripExtension.strip;
520 
521 		auto recipe = parsePackageRecipe(recipe_content, recipe_filename, null, recipe_default_package_name);
522 		enforce(recipe.buildSettings.sourceFiles.length == 0, "Single-file packages are not allowed to specify source files.");
523 		enforce(recipe.buildSettings.sourcePaths.length == 0, "Single-file packages are not allowed to specify source paths.");
524 		enforce(recipe.buildSettings.importPaths.length == 0, "Single-file packages are not allowed to specify import paths.");
525 		recipe.buildSettings.sourceFiles[""] = [path.toNativeString()];
526 		recipe.buildSettings.sourcePaths[""] = [];
527 		recipe.buildSettings.importPaths[""] = [];
528 		recipe.buildSettings.mainSourceFile = path.toNativeString();
529 		if (recipe.buildSettings.targetType == TargetType.autodetect)
530 			recipe.buildSettings.targetType = TargetType.executable;
531 
532 		auto pack = new Package(recipe, path.parentPath, null, "~master");
533 		loadPackage(pack);
534 	}
535 	/// ditto
536 	void loadSingleFilePackage(string path)
537 	{
538 		loadSingleFilePackage(NativePath(path));
539 	}
540 
541 	/** Gets the default configuration for a particular build platform.
542 
543 		This forwards to `Project.getDefaultConfiguration` and requires a
544 		project to be loaded.
545 	*/
546 	string getDefaultConfiguration(in BuildPlatform platform, bool allow_non_library_configs = true) const { return m_project.getDefaultConfiguration(platform, allow_non_library_configs); }
547 
548 	/** Attempts to upgrade the dependency selection of the loaded project.
549 
550 		Params:
551 			options = Flags that control how the upgrade is carried out
552 			packages_to_upgrade = Optional list of packages. If this list
553 				contains one or more packages, only those packages will
554 				be upgraded. Otherwise, all packages will be upgraded at
555 				once.
556 	*/
557 	void upgrade(UpgradeOptions options, string[] packages_to_upgrade = null)
558 	{
559 		// clear non-existent version selections
560 		if (!(options & UpgradeOptions.upgrade)) {
561 			next_pack:
562 			foreach (p; m_project.selections.selectedPackages) {
563 				auto dep = m_project.selections.getSelectedVersion(p);
564 				if (!dep.path.empty) {
565 					auto path = dep.path;
566 					if (!path.absolute) path = this.rootPath ~ path;
567 					try if (m_packageManager.getOrLoadPackage(path)) continue;
568 					catch (Exception e) { logDebug("Failed to load path based selection: %s", e.toString().sanitize); }
569 				} else if (!dep.repository.empty) {
570 					if (m_packageManager.loadSCMPackage(getBasePackageName(p), dep.repository))
571 						continue;
572 				} else {
573 					if (m_packageManager.getPackage(p, dep.version_)) continue;
574 					foreach (ps; m_packageSuppliers) {
575 						try {
576 							auto versions = ps.getVersions(p);
577 							if (versions.canFind!(v => dep.matches(v, VersionMatchMode.strict)))
578 								continue next_pack;
579 						} catch (Exception e) {
580 							logWarn("Error querying versions for %s, %s: %s", p, ps.description, e.msg);
581 							logDebug("Full error: %s", e.toString().sanitize());
582 						}
583 					}
584 				}
585 
586 				logWarn("Selected package %s %s doesn't exist. Using latest matching version instead.", p, dep);
587 				m_project.selections.deselectVersion(p);
588 			}
589 		}
590 
591 		auto resolver = new DependencyVersionResolver(
592 			this, options, m_project.rootPackage, m_project.selections);
593 		Dependency[string] versions = resolver.resolve(packages_to_upgrade);
594 
595 		if (options & UpgradeOptions.dryRun) {
596 			bool any = false;
597 			string rootbasename = getBasePackageName(m_project.rootPackage.name);
598 
599 			foreach (p, ver; versions) {
600 				if (!ver.path.empty || !ver.repository.empty) continue;
601 
602 				auto basename = getBasePackageName(p);
603 				if (basename == rootbasename) continue;
604 
605 				if (!m_project.selections.hasSelectedVersion(basename)) {
606 					logInfo("Upgrade", Color.cyan,
607 						"Package %s would be selected with version %s", basename, ver);
608 					any = true;
609 					continue;
610 				}
611 				auto sver = m_project.selections.getSelectedVersion(basename);
612 				if (!sver.path.empty || !sver.repository.empty) continue;
613 				if (ver.version_ <= sver.version_) continue;
614 				logInfo("Upgrade", Color.cyan,
615 					"%s would be upgraded from %s to %s.",
616 					basename.color(Mode.bold), sver, ver);
617 				any = true;
618 			}
619 			if (any) logInfo("Use \"%s\" to perform those changes", "dub upgrade".color(Mode.bold));
620 			return;
621 		}
622 
623 		foreach (p, ver; versions) {
624 			assert(!p.canFind(":"), "Resolved packages contain a sub package!?: "~p);
625 			Package pack;
626 			if (!ver.path.empty) {
627 				try pack = m_packageManager.getOrLoadPackage(ver.path);
628 				catch (Exception e) {
629 					logDebug("Failed to load path based selection: %s", e.toString().sanitize);
630 					continue;
631 				}
632 			} else if (!ver.repository.empty) {
633 				pack = m_packageManager.loadSCMPackage(p, ver.repository);
634 			} else {
635 				assert(ver.isExactVersion, "Resolved dependency is neither path, nor repository, nor exact version based!?");
636 				pack = m_packageManager.getPackage(p, ver.version_);
637 				if (pack && m_packageManager.isManagedPackage(pack)
638 					&& ver.version_.isBranch && (options & UpgradeOptions.upgrade) != 0)
639 				{
640 					// TODO: only re-install if there is actually a new commit available
641 					logInfo("Re-installing branch based dependency %s %s", p, ver.toString());
642 					m_packageManager.remove(pack);
643 					pack = null;
644 				}
645 			}
646 
647 			FetchOptions fetchOpts;
648 			fetchOpts |= (options & UpgradeOptions.preRelease) != 0 ? FetchOptions.usePrerelease : FetchOptions.none;
649 			if (!pack) fetch(p, ver.version_, defaultPlacementLocation, fetchOpts, "getting selected version");
650 			if ((options & UpgradeOptions.select) && p != m_project.rootPackage.name) {
651 				if (!ver.repository.empty) {
652 					m_project.selections.selectVersion(p, ver.repository);
653 				} else if (ver.path.empty) {
654 					m_project.selections.selectVersion(p, ver.version_);
655 				} else {
656 					NativePath relpath = ver.path;
657 					if (relpath.absolute) relpath = relpath.relativeTo(m_project.rootPackage.path);
658 					m_project.selections.selectVersion(p, relpath);
659 				}
660 			}
661 		}
662 
663 		string[] missingDependenciesBeforeReinit = m_project.missingDependencies;
664 		m_project.reinit();
665 
666 		if (!m_project.hasAllDependencies) {
667 			auto resolvedDependencies = setDifference(
668 					assumeSorted(missingDependenciesBeforeReinit),
669 					assumeSorted(m_project.missingDependencies)
670 				);
671 			if (!resolvedDependencies.empty)
672 				upgrade(options, m_project.missingDependencies);
673 		}
674 
675 		if ((options & UpgradeOptions.select) && !(options & (UpgradeOptions.noSaveSelections | UpgradeOptions.dryRun)))
676 			m_project.saveSelections();
677 	}
678 
679 	/** Generate project files for a specified generator.
680 
681 		Any existing project files will be overridden.
682 	*/
683 	void generateProject(string ide, GeneratorSettings settings)
684 	{
685 		// With a requested `unittest` config, switch to the special test runner
686 		// config (which doesn't require an existing `unittest` configuration).
687 		if (settings.config == "unittest") {
688 			const test_config = m_project.addTestRunnerConfiguration(settings, !m_dryRun);
689 			if (test_config) settings.config = test_config;
690 		}
691 
692 		auto generator = createProjectGenerator(ide, m_project);
693 		if (m_dryRun) return; // TODO: pass m_dryRun to the generator
694 		generator.generate(settings);
695 	}
696 
697 	/** Generate project files using the special test runner (`dub test`) configuration.
698 
699 		Any existing project files will be overridden.
700 	*/
701 	void testProject(GeneratorSettings settings, string config, NativePath custom_main_file)
702 	{
703 		if (!custom_main_file.empty && !custom_main_file.absolute) custom_main_file = getWorkingDirectory() ~ custom_main_file;
704 
705 		const test_config = m_project.addTestRunnerConfiguration(settings, !m_dryRun, config, custom_main_file);
706 		if (!test_config) return; // target type "none"
707 
708 		settings.config = test_config;
709 
710 		auto generator = createProjectGenerator("build", m_project);
711 		generator.generate(settings);
712 	}
713 
714 	/** Executes D-Scanner tests on the current project. **/
715 	void lintProject(string[] args)
716 	{
717 		import std.path : buildPath, buildNormalizedPath;
718 
719 		if (m_dryRun) return;
720 
721 		auto tool = "dscanner";
722 
723 		auto tool_pack = m_packageManager.getBestPackage(tool);
724 		if (!tool_pack) {
725 			logInfo("Hint", Color.light_blue, "%s is not present, getting and storing it user wide", tool);
726 			tool_pack = fetch(tool, VersionRange.Any, defaultPlacementLocation, FetchOptions.none);
727 		}
728 
729 		auto dscanner_dub = new Dub(null, m_packageSuppliers);
730 		dscanner_dub.loadPackage(tool_pack);
731 		dscanner_dub.upgrade(UpgradeOptions.select);
732 
733 		GeneratorSettings settings = this.makeAppSettings();
734 		foreach (dependencyPackage; m_project.dependencies)
735 		{
736 			auto cfgs = m_project.getPackageConfigs(settings.platform, null, true);
737 			auto buildSettings = dependencyPackage.getBuildSettings(settings.platform, cfgs[dependencyPackage.name]);
738 			foreach (importPath; buildSettings.importPaths) {
739 				settings.runArgs ~= ["-I", buildNormalizedPath(dependencyPackage.path.toNativeString(), importPath.idup)];
740 			}
741 		}
742 
743 		string configFilePath = buildPath(m_project.rootPackage.path.toNativeString(), "dscanner.ini");
744 		if (!args.canFind("--config") && exists(configFilePath)) {
745 			settings.runArgs ~= ["--config", configFilePath];
746 		}
747 
748 		settings.runArgs ~= args ~ [m_project.rootPackage.path.toNativeString()];
749 		dscanner_dub.generateProject("build", settings);
750 	}
751 
752 	/** Prints the specified build settings necessary for building the root package.
753 	*/
754 	void listProjectData(GeneratorSettings settings, string[] requestedData, ListBuildSettingsFormat list_type)
755 	{
756 		import std.stdio;
757 		import std.ascii : newline;
758 
759 		// Split comma-separated lists
760 		string[] requestedDataSplit =
761 			requestedData
762 			.map!(a => a.splitter(",").map!strip)
763 			.joiner()
764 			.array();
765 
766 		auto data = m_project.listBuildSettings(settings, requestedDataSplit, list_type);
767 
768 		string delimiter;
769 		final switch (list_type) with (ListBuildSettingsFormat) {
770 			case list: delimiter = newline ~ newline; break;
771 			case listNul: delimiter = "\0\0"; break;
772 			case commandLine: delimiter = " "; break;
773 			case commandLineNul: delimiter = "\0\0"; break;
774 		}
775 
776 		write(data.joiner(delimiter));
777 		if (delimiter != "\0\0") writeln();
778 	}
779 
780 	/// Cleans intermediate/cache files of the given package
781 	void cleanPackage(NativePath path)
782 	{
783 		logInfo("Cleaning", Color.green, "package at %s", path.toNativeString().color(Mode.bold));
784 		enforce(!Package.findPackageFile(path).empty, "No package found.", path.toNativeString());
785 
786 		// TODO: clear target files and copy files
787 
788 		if (existsFile(path ~ ".dub/build")) rmdirRecurse((path ~ ".dub/build").toNativeString());
789 		if (existsFile(path ~ ".dub/metadata_cache.json")) std.file.remove((path ~ ".dub/metadata_cache.json").toNativeString());
790 
791 		auto p = Package.load(path);
792 		if (p.getBuildSettings().targetType == TargetType.none) {
793 			foreach (sp; p.subPackages.filter!(sp => !sp.path.empty)) {
794 				cleanPackage(path ~ sp.path);
795 			}
796 		}
797 	}
798 
799 	/// Fetches the package matching the dependency and places it in the specified location.
800 	deprecated("Use the overload that accepts either a `Version` or a `VersionRange` as second argument")
801 	Package fetch(string packageId, const Dependency dep, PlacementLocation location, FetchOptions options, string reason = "")
802 	{
803 		const vrange = dep.visit!(
804 			(VersionRange range) => range,
805 			function VersionRange (any) { throw new Exception("Cannot call `dub.fetch` with a " ~ typeof(any).stringof ~ " dependency"); }
806 		);
807 		return this.fetch(packageId, vrange, location, options, reason);
808 	}
809 
810 	/// Ditto
811 	Package fetch(string packageId, in Version vers, PlacementLocation location, FetchOptions options, string reason = "")
812 	{
813 		return this.fetch(packageId, VersionRange(vers, vers), location, options, reason);
814 	}
815 
816 	/// Ditto
817 	Package fetch(string packageId, in VersionRange range, PlacementLocation location, FetchOptions options, string reason = "")
818 	{
819 		auto basePackageName = getBasePackageName(packageId);
820 		Json pinfo;
821 		PackageSupplier supplier;
822 		foreach(ps; m_packageSuppliers){
823 			try {
824 				pinfo = ps.fetchPackageRecipe(basePackageName, Dependency(range), (options & FetchOptions.usePrerelease) != 0);
825 				if (pinfo.type == Json.Type.null_)
826 					continue;
827 				supplier = ps;
828 				break;
829 			} catch(Exception e) {
830 				logWarn("Package %s not found for %s: %s", packageId, ps.description, e.msg);
831 				logDebug("Full error: %s", e.toString().sanitize());
832 			}
833 		}
834 		enforce(pinfo.type != Json.Type.undefined, "No package "~packageId~" was found matching the dependency " ~ range.toString());
835 		Version ver = Version(pinfo["version"].get!string);
836 
837 		// always upgrade branch based versions - TODO: actually check if there is a new commit available
838 		Package existing = m_packageManager.getPackage(packageId, ver, location);
839 		if (options & FetchOptions.printOnly) {
840 			if (existing && existing.version_ != ver)
841 				logInfo("A new version for %s is available (%s -> %s). Run \"%s\" to switch.",
842 					packageId.color(Mode.bold), existing.version_, ver,
843 					text("dub upgrade ", packageId).color(Mode.bold));
844 			return null;
845 		}
846 
847 		if (existing) {
848 			if (!ver.isBranch() || !(options & FetchOptions.forceBranchUpgrade) || location == PlacementLocation.local) {
849 				// TODO: support git working trees by performing a "git pull" instead of this
850 				logDiagnostic("Package %s %s (in %s packages) is already present with the latest version, skipping upgrade.",
851 					packageId, ver, location.toString);
852 				return existing;
853 			} else {
854 				logInfo("Removing", Color.yellow, "%s %s to prepare replacement with a new version", packageId.color(Mode.bold), ver);
855 				if (!m_dryRun) m_packageManager.remove(existing);
856 			}
857 		}
858 
859 		if (reason.length) logInfo("Fetching", Color.yellow, "%s %s (%s)", packageId.color(Mode.bold), ver, reason);
860 		else logInfo("Fetching", Color.yellow, "%s %s", packageId.color(Mode.bold), ver);
861 		if (m_dryRun) return null;
862 
863 		logDebug("Acquiring package zip file");
864 
865 		// repeat download on corrupted zips, see #1336
866 		foreach_reverse (i; 0..3)
867 		{
868 			import std.zip : ZipException;
869 
870 			auto path = getTempFile(basePackageName, ".zip");
871 			supplier.fetchPackage(path, basePackageName, Dependency(range), (options & FetchOptions.usePrerelease) != 0); // Q: continue on fail?
872 			scope(exit) std.file.remove(path.toNativeString());
873 			logDiagnostic("Placing to %s...", location.toString());
874 
875 			try {
876 				return m_packageManager.store(path, location, basePackageName, ver);
877 			} catch (ZipException e) {
878 				logInfo("Failed to extract zip archive for %s %s...", packageId, ver);
879 				// rethrow the exception at the end of the loop
880 				if (i == 0)
881 					throw e;
882 			}
883 		}
884 		assert(0, "Should throw a ZipException instead.");
885 	}
886 
887 	/** Removes a specific locally cached package.
888 
889 		This will delete the package files from disk and removes the
890 		corresponding entry from the list of known packages.
891 
892 		Params:
893 			pack = Package instance to remove
894 	*/
895 	void remove(in Package pack)
896 	{
897 		logInfo("Removing", Color.yellow, "%s (in %s)", pack.name.color(Mode.bold), pack.path.toNativeString());
898 		if (!m_dryRun) m_packageManager.remove(pack);
899 	}
900 
901 	/// Compatibility overload. Use the version without a `force_remove` argument instead.
902 	deprecated("Use `remove(pack)` directly instead, the boolean has no effect")
903 	void remove(in Package pack, bool force_remove)
904 	{
905 		remove(pack);
906 	}
907 
908 	/// @see remove(string, string, RemoveLocation)
909 	enum RemoveVersionWildcard = "*";
910 
911 	/** Removes one or more versions of a locally cached package.
912 
913 		This will remove a given package with a specified version from the
914 		given location. It will remove at most one package, unless `version_`
915 		is set to `RemoveVersionWildcard`.
916 
917 		Params:
918 			package_id = Name of the package to be removed
919 			location_ = Specifies the location to look for the given package
920 				name/version.
921 			resolve_version = Callback to select package version.
922 	*/
923 	void remove(string package_id, PlacementLocation location,
924 				scope size_t delegate(in Package[] packages) resolve_version)
925 	{
926 		enforce(!package_id.empty);
927 		if (location == PlacementLocation.local) {
928 			logInfo("To remove a locally placed package, make sure you don't have any data"
929 					~ "\nleft in it's directory and then simply remove the whole directory.");
930 			throw new Exception("dub cannot remove locally installed packages.");
931 		}
932 
933 		Package[] packages;
934 
935 		// Retrieve packages to be removed.
936 		foreach(pack; m_packageManager.getPackageIterator(package_id))
937 			if (m_packageManager.isManagedPackage(pack))
938 				packages ~= pack;
939 
940 		// Check validity of packages to be removed.
941 		if(packages.empty) {
942 			throw new Exception("Cannot find package to remove. ("
943 				~ "id: '" ~ package_id ~ "', location: '" ~ to!string(location) ~ "'"
944 				~ ")");
945 		}
946 
947 		// Sort package list in ascending version order
948 		packages.sort!((a, b) => a.version_ < b.version_);
949 
950 		immutable idx = resolve_version(packages);
951 		if (idx == size_t.max)
952 			return;
953 		else if (idx != packages.length)
954 			packages = packages[idx .. idx + 1];
955 
956 		logDebug("Removing %s packages.", packages.length);
957 		foreach(pack; packages) {
958 			try {
959 				remove(pack);
960 			} catch (Exception e) {
961 				logError("Failed to remove %s %s: %s", package_id, pack.version_, e.msg);
962 				logInfo("Continuing with other packages (if any).");
963 			}
964 		}
965 	}
966 
967 	/// Compatibility overload. Use the version without a `force_remove` argument instead.
968 	void remove(string package_id, PlacementLocation location, bool force_remove,
969 				scope size_t delegate(in Package[] packages) resolve_version)
970 	{
971 		remove(package_id, location, resolve_version);
972 	}
973 
974 	/** Removes a specific version of a package.
975 
976 		Params:
977 			package_id = Name of the package to be removed
978 			version_ = Identifying a version or a wild card. If an empty string
979 				is passed, the package will be removed from the location, if
980 				there is only one version retrieved. This will throw an
981 				exception, if there are multiple versions retrieved.
982 			location_ = Specifies the location to look for the given package
983 				name/version.
984 	 */
985 	void remove(string package_id, string version_, PlacementLocation location)
986 	{
987 		remove(package_id, location, (in packages) {
988 			if (version_ == RemoveVersionWildcard || version_.empty)
989 				return packages.length;
990 
991 			foreach (i, p; packages) {
992 				if (p.version_ == Version(version_))
993 					return i;
994 			}
995 			throw new Exception("Cannot find package to remove. ("
996 				~ "id: '" ~ package_id ~ "', version: '" ~ version_ ~ "', location: '" ~ to!string(location) ~ "'"
997 				~ ")");
998 		});
999 	}
1000 
1001 	/// Compatibility overload. Use the version without a `force_remove` argument instead.
1002 	deprecated("Use the overload without force_remove instead")
1003 	void remove(string package_id, string version_, PlacementLocation location, bool force_remove)
1004 	{
1005 		remove(package_id, version_, location);
1006 	}
1007 
1008 	/** Adds a directory to the list of locally known packages.
1009 
1010 		Forwards to `PackageManager.addLocalPackage`.
1011 
1012 		Params:
1013 			path = Path to the package
1014 			ver = Optional version to associate with the package (can be left
1015 				empty)
1016 			system = Make the package known system wide instead of user wide
1017 				(requires administrator privileges).
1018 
1019 		See_Also: `removeLocalPackage`
1020 	*/
1021 	void addLocalPackage(string path, string ver, bool system)
1022 	{
1023 		if (m_dryRun) return;
1024 		m_packageManager.addLocalPackage(makeAbsolute(path), ver, system ? PlacementLocation.system : PlacementLocation.user);
1025 	}
1026 
1027 	/** Removes a directory from the list of locally known packages.
1028 
1029 		Forwards to `PackageManager.removeLocalPackage`.
1030 
1031 		Params:
1032 			path = Path to the package
1033 			system = Make the package known system wide instead of user wide
1034 				(requires administrator privileges).
1035 
1036 		See_Also: `addLocalPackage`
1037 	*/
1038 	void removeLocalPackage(string path, bool system)
1039 	{
1040 		if (m_dryRun) return;
1041 		m_packageManager.removeLocalPackage(makeAbsolute(path), system ? PlacementLocation.system : PlacementLocation.user);
1042 	}
1043 
1044 	/** Registers a local directory to search for packages to use for satisfying
1045 		dependencies.
1046 
1047 		Params:
1048 			path = Path to a directory containing package directories
1049 			system = Make the package known system wide instead of user wide
1050 				(requires administrator privileges).
1051 
1052 		See_Also: `removeSearchPath`
1053 	*/
1054 	void addSearchPath(string path, bool system)
1055 	{
1056 		if (m_dryRun) return;
1057 		m_packageManager.addSearchPath(makeAbsolute(path), system ? PlacementLocation.system : PlacementLocation.user);
1058 	}
1059 
1060 	/** Unregisters a local directory search path.
1061 
1062 		Params:
1063 			path = Path to a directory containing package directories
1064 			system = Make the package known system wide instead of user wide
1065 				(requires administrator privileges).
1066 
1067 		See_Also: `addSearchPath`
1068 	*/
1069 	void removeSearchPath(string path, bool system)
1070 	{
1071 		if (m_dryRun) return;
1072 		m_packageManager.removeSearchPath(makeAbsolute(path), system ? PlacementLocation.system : PlacementLocation.user);
1073 	}
1074 
1075 	/** Queries all package suppliers with the given query string.
1076 
1077 		Returns a list of tuples, where the first entry is the human readable
1078 		name of the package supplier and the second entry is the list of
1079 		matched packages.
1080 
1081 		Params:
1082 		  query = the search term to match packages on
1083 
1084 		See_Also: `PackageSupplier.searchPackages`
1085 	*/
1086 	auto searchPackages(string query)
1087 	{
1088 		import std.typecons : Tuple, tuple;
1089 		Tuple!(string, PackageSupplier.SearchResult[])[] results;
1090 		foreach (ps; this.m_packageSuppliers) {
1091 			try
1092 				results ~= tuple(ps.description, ps.searchPackages(query));
1093 			catch (Exception e) {
1094 				logWarn("Searching %s for '%s' failed: %s", ps.description, query, e.msg);
1095 			}
1096 		}
1097 		return results.filter!(tup => tup[1].length);
1098 	}
1099 
1100 	/** Returns a list of all available versions (including branches) for a
1101 		particular package.
1102 
1103 		The list returned is based on the registered package suppliers. Local
1104 		packages are not queried in the search for versions.
1105 
1106 		See_also: `getLatestVersion`
1107 	*/
1108 	Version[] listPackageVersions(string name)
1109 	{
1110 		Version[] versions;
1111 		auto basePackageName = getBasePackageName(name);
1112 		foreach (ps; this.m_packageSuppliers) {
1113 			try versions ~= ps.getVersions(basePackageName);
1114 			catch (Exception e) {
1115 				logWarn("Failed to get versions for package %s on provider %s: %s", name, ps.description, e.msg);
1116 			}
1117 		}
1118 		return versions.sort().uniq.array;
1119 	}
1120 
1121 	/** Returns the latest available version for a particular package.
1122 
1123 		This function returns the latest numbered version of a package. If no
1124 		numbered versions are available, it will return an available branch,
1125 		preferring "~master".
1126 
1127 		Params:
1128 			package_name: The name of the package in question.
1129 			prefer_stable: If set to `true` (the default), returns the latest
1130 				stable version, even if there are newer pre-release versions.
1131 
1132 		See_also: `listPackageVersions`
1133 	*/
1134 	Version getLatestVersion(string package_name, bool prefer_stable = true)
1135 	{
1136 		auto vers = listPackageVersions(package_name);
1137 		enforce(!vers.empty, "Failed to find any valid versions for a package name of '"~package_name~"'.");
1138 		auto final_versions = vers.filter!(v => !v.isBranch && !v.isPreRelease).array;
1139 		if (prefer_stable && final_versions.length) return final_versions[$-1];
1140 		else return vers[$-1];
1141 	}
1142 
1143 	/** Initializes a directory with a package skeleton.
1144 
1145 		Params:
1146 			path = Path of the directory to create the new package in. The
1147 				directory will be created if it doesn't exist.
1148 			deps = List of dependencies to add to the package recipe.
1149 			type = Specifies the type of the application skeleton to use.
1150 			format = Determines the package recipe format to use.
1151 			recipe_callback = Optional callback that can be used to
1152 				customize the recipe before it gets written.
1153 	*/
1154 	void createEmptyPackage(NativePath path, string[] deps, string type,
1155 		PackageFormat format = PackageFormat.sdl,
1156 		scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null,
1157 		string[] app_args = [])
1158 	{
1159 		if (!path.absolute) path = m_rootPath ~ path;
1160 		path.normalize();
1161 
1162 		VersionRange[string] depVers;
1163 		string[] notFound; // keep track of any failed packages in here
1164 		foreach (dep; deps) {
1165 			try {
1166 				Version ver = getLatestVersion(dep);
1167 				if (ver.isBranch())
1168 					depVers[dep] = VersionRange(ver);
1169 				else
1170 					depVers[dep] = VersionRange.fromString("~>" ~ ver.toString());
1171 			} catch (Exception e) {
1172 				notFound ~= dep;
1173 			}
1174 		}
1175 
1176 		if(notFound.length > 1){
1177 			throw new Exception(.format("Couldn't find packages: %-(%s, %).", notFound));
1178 		}
1179 		else if(notFound.length == 1){
1180 			throw new Exception(.format("Couldn't find package: %-(%s, %).", notFound));
1181 		}
1182 
1183 		if (m_dryRun) return;
1184 
1185 		initPackage(path, depVers, type, format, recipe_callback);
1186 
1187 		if (!["vibe.d", "deimos", "minimal"].canFind(type)) {
1188 			runCustomInitialization(path, type, app_args);
1189 		}
1190 
1191 		//Act smug to the user.
1192 		logInfo("Success", Color.green, "created empty project in %s", path.toNativeString().color(Mode.bold));
1193 	}
1194 
1195 	private void runCustomInitialization(NativePath path, string type, string[] runArgs)
1196 	{
1197 		string packageName = type;
1198 		auto template_pack = m_packageManager.getBestPackage(packageName);
1199 		if (!template_pack) {
1200 			logInfo("%s is not present, getting and storing it user wide", packageName);
1201 			template_pack = fetch(packageName, VersionRange.Any, defaultPlacementLocation, FetchOptions.none);
1202 		}
1203 
1204 		Package initSubPackage = m_packageManager.getSubPackage(template_pack, "init-exec", false);
1205 		auto template_dub = new Dub(null, m_packageSuppliers);
1206 		template_dub.loadPackage(initSubPackage);
1207 
1208 		GeneratorSettings settings = this.makeAppSettings();
1209 		settings.runArgs = runArgs;
1210 
1211 		initSubPackage.recipe.buildSettings.workingDirectory = path.toNativeString();
1212 		template_dub.generateProject("build", settings);
1213 	}
1214 
1215 	/** Converts the package recipe of the loaded root package to the given format.
1216 
1217 		Params:
1218 			destination_file_ext = The file extension matching the desired
1219 				format. Possible values are "json" or "sdl".
1220 			print_only = Print the converted recipe instead of writing to disk
1221 	*/
1222 	void convertRecipe(string destination_file_ext, bool print_only = false)
1223 	{
1224 		import std.path : extension;
1225 		import std.stdio : stdout;
1226 		import dub.recipe.io : serializePackageRecipe, writePackageRecipe;
1227 
1228 		if (print_only) {
1229 			auto dst = stdout.lockingTextWriter;
1230 			serializePackageRecipe(dst, m_project.rootPackage.rawRecipe, "dub."~destination_file_ext);
1231 			return;
1232 		}
1233 
1234 		auto srcfile = m_project.rootPackage.recipePath;
1235 		auto srcext = srcfile.head.name.extension;
1236 		if (srcext == "."~destination_file_ext) {
1237 			// no logging before this point
1238 			tagWidth.push(5);
1239 			logError("Package format is already %s.", destination_file_ext);
1240 			return;
1241 		}
1242 
1243 		writePackageRecipe(srcfile.parentPath ~ ("dub."~destination_file_ext), m_project.rootPackage.rawRecipe);
1244 		removeFile(srcfile);
1245 	}
1246 
1247 	/** Runs DDOX to generate or serve documentation.
1248 
1249 		Params:
1250 			run = If set to true, serves documentation on a local web server.
1251 				Otherwise generates actual HTML files.
1252 			generate_args = Additional command line arguments to pass to
1253 				"ddox generate-html" or "ddox serve-html".
1254 	*/
1255 	void runDdox(bool run, string[] generate_args = null)
1256 	{
1257 		import std.process : browse;
1258 
1259 		if (m_dryRun) return;
1260 
1261 		// allow to choose a custom ddox tool
1262 		auto tool = m_project.rootPackage.recipe.ddoxTool;
1263 		if (tool.empty) tool = "ddox";
1264 
1265 		auto tool_pack = m_packageManager.getBestPackage(tool);
1266 		if (!tool_pack) {
1267 			logInfo("%s is not present, getting and storing it user wide", tool);
1268 			tool_pack = fetch(tool, VersionRange.Any, defaultPlacementLocation, FetchOptions.none);
1269 		}
1270 
1271 		auto ddox_dub = new Dub(null, m_packageSuppliers);
1272 		ddox_dub.loadPackage(tool_pack);
1273 		ddox_dub.upgrade(UpgradeOptions.select);
1274 
1275 		GeneratorSettings settings = this.makeAppSettings();
1276 
1277 		auto filterargs = m_project.rootPackage.recipe.ddoxFilterArgs.dup;
1278 		if (filterargs.empty) filterargs = ["--min-protection=Protected", "--only-documented"];
1279 
1280 		settings.runArgs = "filter" ~ filterargs ~ "docs.json";
1281 		ddox_dub.generateProject("build", settings);
1282 
1283 		auto p = tool_pack.path;
1284 		p.endsWithSlash = true;
1285 		auto tool_path = p.toNativeString();
1286 
1287 		if (run) {
1288 			settings.runArgs = ["serve-html", "--navigation-type=ModuleTree", "docs.json", "--web-file-dir="~tool_path~"public"] ~ generate_args;
1289 			browse("http://127.0.0.1:8080/");
1290 		} else {
1291 			settings.runArgs = ["generate-html", "--navigation-type=ModuleTree", "docs.json", "docs"] ~ generate_args;
1292 		}
1293 		ddox_dub.generateProject("build", settings);
1294 
1295 		if (!run) {
1296 			// TODO: ddox should copy those files itself
1297 			version(Windows) runCommand(`xcopy /S /D "`~tool_path~`public\*" docs\`);
1298 			else runCommand("rsync -ru '"~tool_path~"public/' docs/");
1299 		}
1300 	}
1301 
1302 	/// Make a `GeneratorSettings` suitable to generate tools (DDOC, DScanner, etc...)
1303 	private GeneratorSettings makeAppSettings () const
1304 	{
1305 		GeneratorSettings settings;
1306 		auto compiler_binary = this.defaultCompiler;
1307 
1308 		settings.config = "application";
1309 		settings.buildType = "debug";
1310 		settings.compiler = getCompiler(compiler_binary);
1311 		settings.platform = settings.compiler.determinePlatform(
1312 			settings.buildSettings, compiler_binary, this.defaultArchitecture);
1313 		if (this.defaultLowMemory)
1314 			settings.buildSettings.options |= BuildOption.lowmem;
1315 		if (this.defaultEnvironments)
1316 			settings.buildSettings.addEnvironments(this.defaultEnvironments);
1317 		if (this.defaultBuildEnvironments)
1318 			settings.buildSettings.addBuildEnvironments(this.defaultBuildEnvironments);
1319 		if (this.defaultRunEnvironments)
1320 			settings.buildSettings.addRunEnvironments(this.defaultRunEnvironments);
1321 		if (this.defaultPreGenerateEnvironments)
1322 			settings.buildSettings.addPreGenerateEnvironments(this.defaultPreGenerateEnvironments);
1323 		if (this.defaultPostGenerateEnvironments)
1324 			settings.buildSettings.addPostGenerateEnvironments(this.defaultPostGenerateEnvironments);
1325 		if (this.defaultPreBuildEnvironments)
1326 			settings.buildSettings.addPreBuildEnvironments(this.defaultPreBuildEnvironments);
1327 		if (this.defaultPostBuildEnvironments)
1328 			settings.buildSettings.addPostBuildEnvironments(this.defaultPostBuildEnvironments);
1329 		if (this.defaultPreRunEnvironments)
1330 			settings.buildSettings.addPreRunEnvironments(this.defaultPreRunEnvironments);
1331 		if (this.defaultPostRunEnvironments)
1332 			settings.buildSettings.addPostRunEnvironments(this.defaultPostRunEnvironments);
1333 		settings.run = true;
1334 
1335 		return settings;
1336 	}
1337 
1338 	private void determineDefaultCompiler()
1339 	{
1340 		import std.file : thisExePath;
1341 		import std.path : buildPath, dirName, expandTilde, isAbsolute, isDirSeparator;
1342 		import std.range : front;
1343 
1344 		// Env takes precedence
1345 		if (auto envCompiler = environment.get("DC"))
1346 			m_defaultCompiler = envCompiler;
1347 		else
1348 			m_defaultCompiler = m_config.defaultCompiler.expandTilde;
1349 		if (m_defaultCompiler.length && m_defaultCompiler.isAbsolute)
1350 			return;
1351 
1352 		static immutable BinaryPrefix = `$DUB_BINARY_PATH`;
1353 		if(m_defaultCompiler.startsWith(BinaryPrefix))
1354 		{
1355 			m_defaultCompiler = thisExePath().dirName() ~ m_defaultCompiler[BinaryPrefix.length .. $];
1356 			return;
1357 		}
1358 
1359 		if (!find!isDirSeparator(m_defaultCompiler).empty)
1360 			throw new Exception("defaultCompiler specified in a DUB config file cannot use an unqualified relative path:\n\n" ~ m_defaultCompiler ~
1361 			"\n\nUse \"$DUB_BINARY_PATH/../path/you/want\" instead.");
1362 
1363 		version (Windows) enum sep = ";", exe = ".exe";
1364 		version (Posix) enum sep = ":", exe = "";
1365 
1366 		auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"];
1367 		// If a compiler name is specified, look for it next to dub.
1368 		// Otherwise, look for any of the common compilers adjacent to dub.
1369 		if (m_defaultCompiler.length)
1370 		{
1371 			string compilerPath = buildPath(thisExePath().dirName(), m_defaultCompiler ~ exe);
1372 			if (existsFile(compilerPath))
1373 			{
1374 				m_defaultCompiler = compilerPath;
1375 				return;
1376 			}
1377 		}
1378 		else
1379 		{
1380 			auto nextFound = compilers.find!(bin => existsFile(buildPath(thisExePath().dirName(), bin ~ exe)));
1381 			if (!nextFound.empty)
1382 			{
1383 				m_defaultCompiler = buildPath(thisExePath().dirName(),  nextFound.front ~ exe);
1384 				return;
1385 			}
1386 		}
1387 
1388 		// If nothing found next to dub, search the user's PATH, starting
1389 		// with the compiler name from their DUB config file, if specified.
1390 		auto paths = environment.get("PATH", "").splitter(sep).map!NativePath;
1391 		if (m_defaultCompiler.length && paths.canFind!(p => existsFile(p ~ (m_defaultCompiler~exe))))
1392 			return;
1393 		foreach (p; paths) {
1394 			auto res = compilers.find!(bin => existsFile(p ~ (bin~exe)));
1395 			if (!res.empty) {
1396 				m_defaultCompiler = res.front;
1397 				return;
1398 			}
1399 		}
1400 		m_defaultCompiler = compilers[0];
1401 	}
1402 
1403 	unittest
1404 	{
1405 		import std.path: buildPath, absolutePath;
1406 		auto dub = new TestDub(".", null, SkipPackageSuppliers.configured);
1407 		immutable olddc = environment.get("DC", null);
1408 		immutable oldpath = environment.get("PATH", null);
1409 		immutable testdir = "test-determineDefaultCompiler";
1410 		void repairenv(string name, string var)
1411 		{
1412 			if (var !is null)
1413 				environment[name] = var;
1414 			else if (name in environment)
1415 				environment.remove(name);
1416 		}
1417 		scope (exit) repairenv("DC", olddc);
1418 		scope (exit) repairenv("PATH", oldpath);
1419 		scope (exit) rmdirRecurse(testdir);
1420 
1421 		version (Windows) enum sep = ";", exe = ".exe";
1422 		version (Posix) enum sep = ":", exe = "";
1423 
1424 		immutable dmdpath = testdir.buildPath("dmd", "bin");
1425 		immutable ldcpath = testdir.buildPath("ldc", "bin");
1426 		mkdirRecurse(dmdpath);
1427 		mkdirRecurse(ldcpath);
1428 		immutable dmdbin = dmdpath.buildPath("dmd"~exe);
1429 		immutable ldcbin = ldcpath.buildPath("ldc2"~exe);
1430 		std.file.write(dmdbin, null);
1431 		std.file.write(ldcbin, null);
1432 
1433 		environment["DC"] = dmdbin.absolutePath();
1434 		dub.determineDefaultCompiler();
1435 		assert(dub.m_defaultCompiler == dmdbin.absolutePath());
1436 
1437 		environment["DC"] = "dmd";
1438 		environment["PATH"] = dmdpath ~ sep ~ ldcpath;
1439 		dub.determineDefaultCompiler();
1440 		assert(dub.m_defaultCompiler == "dmd");
1441 
1442 		environment["DC"] = "ldc2";
1443 		environment["PATH"] = dmdpath ~ sep ~ ldcpath;
1444 		dub.determineDefaultCompiler();
1445 		assert(dub.m_defaultCompiler == "ldc2");
1446 
1447 		environment.remove("DC");
1448 		environment["PATH"] = ldcpath ~ sep ~ dmdpath;
1449 		dub.determineDefaultCompiler();
1450 		assert(dub.m_defaultCompiler == "ldc2");
1451 	}
1452 
1453 	private NativePath makeAbsolute(NativePath p) const { return p.absolute ? p : m_rootPath ~ p; }
1454 	private NativePath makeAbsolute(string p) const { return makeAbsolute(NativePath(p)); }
1455 }
1456 
1457 
1458 /// Option flags for `Dub.fetch`
1459 enum FetchOptions
1460 {
1461 	none = 0,
1462 	forceBranchUpgrade = 1<<0,
1463 	usePrerelease = 1<<1,
1464 	forceRemove = 1<<2, /// Deprecated, does nothing.
1465 	printOnly = 1<<3,
1466 }
1467 
1468 /// Option flags for `Dub.upgrade`
1469 enum UpgradeOptions
1470 {
1471 	none = 0,
1472 	upgrade = 1<<1, /// Upgrade existing packages
1473 	preRelease = 1<<2, /// inclde pre-release versions in upgrade
1474 	forceRemove = 1<<3, /// Deprecated, does nothing.
1475 	select = 1<<4, /// Update the dub.selections.json file with the upgraded versions
1476 	dryRun = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence
1477 	/*deprecated*/ printUpgradesOnly = dryRun, /// deprecated, use dryRun instead
1478 	/*deprecated*/ useCachedResult = 1<<6, /// deprecated, has no effect
1479 	noSaveSelections = 1<<7, /// Don't store updated selections on disk
1480 }
1481 
1482 /// Determines which of the default package suppliers are queried for packages.
1483 enum SkipPackageSuppliers {
1484 	none,       /// Uses all configured package suppliers.
1485 	standard,   /// Does not use the default package suppliers (`defaultPackageSuppliers`).
1486 	configured, /// Does not use default suppliers or suppliers configured in DUB's configuration file
1487 	all         /// Uses only manually specified package suppliers.
1488 }
1489 
1490 private class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) {
1491 	protected {
1492 		Dub m_dub;
1493 		UpgradeOptions m_options;
1494 		Dependency[][string] m_packageVersions;
1495 		Package[string] m_remotePackages;
1496 		SelectedVersions m_selectedVersions;
1497 		Package m_rootPackage;
1498 		bool[string] m_packagesToUpgrade;
1499 		Package[PackageDependency] m_packages;
1500 		TreeNodes[][TreeNode] m_children;
1501 	}
1502 
1503 
1504 	this(Dub dub, UpgradeOptions options, Package root, SelectedVersions selected_versions)
1505 	{
1506 		assert(dub !is null);
1507 		assert(root !is null);
1508 		assert(selected_versions !is null);
1509 
1510 		if (environment.get("DUB_NO_RESOLVE_LIMIT") !is null)
1511 			super(ulong.max);
1512 		else
1513 		    super(1_000_000);
1514 
1515 		m_dub = dub;
1516 		m_options = options;
1517 		m_rootPackage = root;
1518 		m_selectedVersions = selected_versions;
1519 	}
1520 
1521 	Dependency[string] resolve(string[] filter)
1522 	{
1523 		foreach (name; filter)
1524 			m_packagesToUpgrade[name] = true;
1525 		return super.resolve(TreeNode(m_rootPackage.name, Dependency(m_rootPackage.version_)),
1526 			(m_options & UpgradeOptions.dryRun) == 0);
1527 	}
1528 
1529 	protected bool isFixedPackage(string pack)
1530 	{
1531 		return m_packagesToUpgrade !is null && pack !in m_packagesToUpgrade;
1532 	}
1533 
1534 	protected override Dependency[] getAllConfigs(string pack)
1535 	{
1536 		if (auto pvers = pack in m_packageVersions)
1537 			return *pvers;
1538 
1539 		if ((!(m_options & UpgradeOptions.upgrade) || isFixedPackage(pack)) && m_selectedVersions.hasSelectedVersion(pack)) {
1540 			auto ret = [m_selectedVersions.getSelectedVersion(pack)];
1541 			logDiagnostic("Using fixed selection %s %s", pack, ret[0]);
1542 			m_packageVersions[pack] = ret;
1543 			return ret;
1544 		}
1545 
1546 		logDiagnostic("Search for versions of %s (%s package suppliers)", pack, m_dub.m_packageSuppliers.length);
1547 		Version[] versions;
1548 		foreach (p; m_dub.packageManager.getPackageIterator(pack))
1549 			versions ~= p.version_;
1550 
1551 		foreach (ps; m_dub.m_packageSuppliers) {
1552 			try {
1553 				auto vers = ps.getVersions(pack);
1554 				vers.reverse();
1555 				if (!vers.length) {
1556 					logDiagnostic("No versions for %s for %s", pack, ps.description);
1557 					continue;
1558 				}
1559 
1560 				versions ~= vers;
1561 				break;
1562 			} catch (Exception e) {
1563 				logWarn("Package %s not found in %s: %s", pack, ps.description, e.msg);
1564 				logDebug("Full error: %s", e.toString().sanitize);
1565 			}
1566 		}
1567 
1568 		// sort by version, descending, and remove duplicates
1569 		versions = versions.sort!"a>b".uniq.array;
1570 
1571 		// move pre-release versions to the back of the list if no preRelease flag is given
1572 		if (!(m_options & UpgradeOptions.preRelease))
1573 			versions = versions.filter!(v => !v.isPreRelease).array ~ versions.filter!(v => v.isPreRelease).array;
1574 
1575 		// filter out invalid/unreachable dependency specs
1576 		versions = versions.filter!((v) {
1577 				bool valid = getPackage(pack, Dependency(v)) !is null;
1578 				if (!valid) logDiagnostic("Excluding invalid dependency specification %s %s from dependency resolution process.", pack, v);
1579 				return valid;
1580 			}).array;
1581 
1582 		if (!versions.length) logDiagnostic("Nothing found for %s", pack);
1583 		else logDiagnostic("Return for %s: %s", pack, versions);
1584 
1585 		auto ret = versions.map!(v => Dependency(v)).array;
1586 		m_packageVersions[pack] = ret;
1587 		return ret;
1588 	}
1589 
1590 	protected override Dependency[] getSpecificConfigs(string pack, TreeNodes nodes)
1591 	{
1592 		if (!nodes.configs.path.empty || !nodes.configs.repository.empty) {
1593 			if (getPackage(pack, nodes.configs)) return [nodes.configs];
1594 			else return null;
1595 		}
1596 		else return null;
1597 	}
1598 
1599 
1600 	protected override TreeNodes[] getChildren(TreeNode node)
1601 	{
1602 		if (auto pc = node in m_children)
1603 			return *pc;
1604 		auto ret = getChildrenRaw(node);
1605 		m_children[node] = ret;
1606 		return ret;
1607 	}
1608 
1609 	private final TreeNodes[] getChildrenRaw(TreeNode node)
1610 	{
1611 		import std.array : appender;
1612 		auto ret = appender!(TreeNodes[]);
1613 		auto pack = getPackage(node.pack, node.config);
1614 		if (!pack) {
1615 			// this can hapen when the package description contains syntax errors
1616 			logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config);
1617 			return null;
1618 		}
1619 		auto basepack = pack.basePackage;
1620 
1621 		foreach (d; pack.getAllDependenciesRange()) {
1622 			auto dbasename = getBasePackageName(d.name);
1623 
1624 			// detect dependencies to the root package (or sub packages thereof)
1625 			if (dbasename == basepack.name) {
1626 				auto absdeppath = d.spec.mapToPath(pack.path).path;
1627 				absdeppath.endsWithSlash = true;
1628 				auto subpack = m_dub.m_packageManager.getSubPackage(basepack, getSubPackageName(d.name), true);
1629 				if (subpack) {
1630 					auto desireddeppath = basepack.path;
1631 					desireddeppath.endsWithSlash = true;
1632 
1633 					auto altdeppath = d.name == dbasename ? basepack.path : subpack.path;
1634 					altdeppath.endsWithSlash = true;
1635 
1636 					if (!d.spec.path.empty && absdeppath != desireddeppath)
1637 						logWarn("Sub package %s, referenced by %s %s must be referenced using the path to its base package",
1638 							subpack.name, pack.name, pack.version_);
1639 
1640 					enforce(d.spec.path.empty || absdeppath == desireddeppath || absdeppath == altdeppath,
1641 						format("Dependency from %s to %s uses wrong path: %s vs. %s",
1642 							node.pack, subpack.name, absdeppath.toNativeString(), desireddeppath.toNativeString()));
1643 				}
1644 				ret ~= TreeNodes(d.name, node.config);
1645 				continue;
1646 			}
1647 
1648 			DependencyType dt;
1649 			if (d.spec.optional) {
1650 				if (d.spec.default_) dt = DependencyType.optionalDefault;
1651 				else dt = DependencyType.optional;
1652 			} else dt = DependencyType.required;
1653 
1654 			Dependency dspec = d.spec.mapToPath(pack.path);
1655 
1656 			// if not upgrading, use the selected version
1657 			if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions.hasSelectedVersion(dbasename))
1658 				dspec = m_selectedVersions.getSelectedVersion(dbasename);
1659 
1660 			// keep selected optional dependencies and avoid non-selected optional-default dependencies by default
1661 			if (!m_selectedVersions.bare) {
1662 				if (dt == DependencyType.optionalDefault && !m_selectedVersions.hasSelectedVersion(dbasename))
1663 					dt = DependencyType.optional;
1664 				else if (dt == DependencyType.optional && m_selectedVersions.hasSelectedVersion(dbasename))
1665 					dt = DependencyType.optionalDefault;
1666 			}
1667 
1668 			ret ~= TreeNodes(d.name, dspec, dt);
1669 		}
1670 		return ret.data;
1671 	}
1672 
1673 	protected override bool matches(Dependency configs, Dependency config)
1674 	{
1675 		if (!configs.path.empty) return configs.path == config.path;
1676 		return configs.merge(config).valid;
1677 	}
1678 
1679 	private Package getPackage(string name, Dependency dep)
1680 	{
1681 		auto key = PackageDependency(name, dep);
1682 		if (auto pp = key in m_packages)
1683 			return *pp;
1684 		auto p = getPackageRaw(name, dep);
1685 		m_packages[key] = p;
1686 		return p;
1687 	}
1688 
1689 	private Package getPackageRaw(string name, Dependency dep)
1690 	{
1691 		auto basename = getBasePackageName(name);
1692 
1693 		// for sub packages, first try to get them from the base package
1694 		if (basename != name) {
1695 			auto subname = getSubPackageName(name);
1696 			auto basepack = getPackage(basename, dep);
1697 			if (!basepack) return null;
1698 			if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true)) {
1699 				return sp;
1700 			} else if (!basepack.subPackages.canFind!(p => p.path.length)) {
1701 				// note: external sub packages are handled further below
1702 				auto spr = basepack.getInternalSubPackage(subname);
1703 				if (!spr.isNull) {
1704 					auto sp = new Package(spr.get, basepack.path, basepack);
1705 					m_remotePackages[sp.name] = sp;
1706 					return sp;
1707 				} else {
1708 					logDiagnostic("Sub package %s doesn't exist in %s %s.", name, basename, dep.version_);
1709 					return null;
1710 				}
1711 			} else if (auto ret = m_dub.m_packageManager.getBestPackage(name, dep)) {
1712 				return ret;
1713 			} else {
1714 				logDiagnostic("External sub package %s %s not found.", name, dep.version_);
1715 				return null;
1716 			}
1717 		}
1718 
1719 		// shortcut if the referenced package is the root package
1720 		if (basename == m_rootPackage.basePackage.name)
1721 			return m_rootPackage.basePackage;
1722 
1723 		if (!dep.repository.empty) {
1724 			auto ret = m_dub.packageManager.loadSCMPackage(name, dep.repository);
1725 			return ret !is null && dep.matches(ret.version_) ? ret : null;
1726 		} else if (!dep.path.empty) {
1727 			try {
1728 				return m_dub.packageManager.getOrLoadPackage(dep.path);
1729 			} catch (Exception e) {
1730 				logDiagnostic("Failed to load path based dependency %s: %s", name, e.msg);
1731 				logDebug("Full error: %s", e.toString().sanitize);
1732 				return null;
1733 			}
1734 		}
1735 		const vers = dep.version_;
1736 
1737 		if (auto ret = m_dub.m_packageManager.getBestPackage(name, dep))
1738 			return ret;
1739 
1740 		auto key = name ~ ":" ~ vers.toString();
1741 		if (auto ret = key in m_remotePackages)
1742 			return *ret;
1743 
1744 		auto prerelease = (m_options & UpgradeOptions.preRelease) != 0;
1745 
1746 		auto rootpack = name.split(":")[0];
1747 
1748 		foreach (ps; m_dub.m_packageSuppliers) {
1749 			if (rootpack == name) {
1750 				try {
1751 					auto desc = ps.fetchPackageRecipe(name, dep, prerelease);
1752 					if (desc.type == Json.Type.null_)
1753 						continue;
1754 					auto ret = new Package(desc);
1755 					m_remotePackages[key] = ret;
1756 					return ret;
1757 				} catch (Exception e) {
1758 					logDiagnostic("Metadata for %s %s could not be downloaded from %s: %s", name, vers, ps.description, e.msg);
1759 					logDebug("Full error: %s", e.toString().sanitize);
1760 				}
1761 			} else {
1762 				logDiagnostic("Package %s not found in base package description (%s). Downloading whole package.", name, vers.toString());
1763 				try {
1764 					FetchOptions fetchOpts;
1765 					fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none;
1766 					m_dub.fetch(rootpack, vers, m_dub.defaultPlacementLocation, fetchOpts, "need sub package description");
1767 					auto ret = m_dub.m_packageManager.getBestPackage(name, dep);
1768 					if (!ret) {
1769 						logWarn("Package %s %s doesn't have a sub package %s", rootpack, dep.version_, name);
1770 						return null;
1771 					}
1772 					m_remotePackages[key] = ret;
1773 					return ret;
1774 				} catch (Exception e) {
1775 					logDiagnostic("Package %s could not be downloaded from %s: %s", rootpack, ps.description, e.msg);
1776 					logDebug("Full error: %s", e.toString().sanitize);
1777 				}
1778 			}
1779 		}
1780 
1781 		m_remotePackages[key] = null;
1782 
1783 		logWarn("Package %s %s could not be loaded either locally, or from the configured package registries.", name, dep);
1784 		return null;
1785 	}
1786 }
1787 
1788 /**
1789  * An instance of Dub that does not rely on the environment
1790  *
1791  * This instance of dub should not read any environment variables,
1792  * nor should it do any file IO, to make it usable and reliable in unittests.
1793  * Currently it reads environment variables but does not read the configuration.
1794  */
1795 package final class TestDub : Dub
1796 {
1797     /// Forward to base constructor
1798     public this (string root = ".", PackageSupplier[] extras = null,
1799                  SkipPackageSuppliers skip = SkipPackageSuppliers.none)
1800     {
1801         super(root, extras, skip);
1802     }
1803 
1804     /// Avoid loading user configuration
1805     protected override void loadConfig() { /* No-op */ }
1806 }
1807 
1808 private struct SpecialDirs {
1809 	/// The path where to store temporary files and directory
1810 	NativePath temp;
1811 	/// The system-wide dub-specific folder
1812 	NativePath systemSettings;
1813 	/// The dub-specific folder in the user home directory
1814 	NativePath userSettings;
1815 	/**
1816 	 * Windows-only: the local, user-specific folder
1817 	 *
1818 	 * This folder, unlike `userSettings`, does not roam, IOW an account
1819 	 * on a company network will not save the content of this data,
1820 	 * unlike `userSettings`.
1821 	 * On Posix, this is equivalent to `userSettings`.
1822 	 *
1823 	 * See_Also: https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid
1824 	 */
1825 	NativePath localRepository;
1826 
1827 	/// Returns: An instance of `SpecialDirs` initialized from the environment
1828 	public static SpecialDirs make () {
1829 		import std.file : tempDir;
1830 
1831 		SpecialDirs result;
1832 		result.temp = NativePath(tempDir);
1833 
1834 		version(Windows) {
1835 			result.systemSettings = NativePath(environment.get("ProgramData")) ~ "dub/";
1836 			immutable appDataDir = environment.get("APPDATA");
1837 			result.userSettings = NativePath(appDataDir) ~ "dub/";
1838 			// LOCALAPPDATA is not defined before Windows Vista
1839 			result.localRepository = NativePath(environment.get("LOCALAPPDATA", appDataDir)) ~ "dub";
1840 		} else version(Posix) {
1841 			result.systemSettings = NativePath("/var/lib/dub/");
1842 			result.userSettings = NativePath(environment.get("HOME")) ~ ".dub/";
1843 			if (!result.userSettings.absolute)
1844 				result.userSettings = NativePath(getcwd()) ~ result.userSettings;
1845 			result.localRepository = result.userSettings;
1846 		}
1847 		return result;
1848 	}
1849 }
1850 
1851 /**
1852  * User-provided configuration
1853  *
1854  * All fields in this struct should be optional.
1855  * Fields that are *not* optional should be mandatory from the POV
1856  * of the application, not the POV of file parsing.
1857  * For example, git's `core.author` and `core.email` are required to commit,
1858  * but the error happens on the commit, not when the gitconfig is parsed.
1859  *
1860  * We have multiple configuration locations, and two kinds of fields:
1861  * additive and non-additive. Additive fields are fields which are the union
1862  * of all configuration files (e.g. `registryURLs`). Non-additive fields
1863  * will ignore values set in lower priorities configuration, although parsing
1864  * must still succeed. Additive fields are marked as `@Optional`,
1865  * non-additive are marked as `SetInfo`.
1866  */
1867 private struct UserConfiguration {
1868 	import configy.Attributes;
1869 
1870 	@Optional string[] registryUrls;
1871 	@Optional NativePath[] customCachePaths;
1872 
1873 	SetInfo!(SkipPackageSuppliers) skipRegistry;
1874 	SetInfo!(string) defaultCompiler;
1875 	SetInfo!(string) defaultArchitecture;
1876 	SetInfo!(bool) defaultLowMemory;
1877 
1878 	SetInfo!(string[string]) defaultEnvironments;
1879 	SetInfo!(string[string]) defaultBuildEnvironments;
1880 	SetInfo!(string[string]) defaultRunEnvironments;
1881 	SetInfo!(string[string]) defaultPreGenerateEnvironments;
1882 	SetInfo!(string[string]) defaultPostGenerateEnvironments;
1883 	SetInfo!(string[string]) defaultPreBuildEnvironments;
1884 	SetInfo!(string[string]) defaultPostBuildEnvironments;
1885 	SetInfo!(string[string]) defaultPreRunEnvironments;
1886 	SetInfo!(string[string]) defaultPostRunEnvironments;
1887 	SetInfo!(string) dubHome;
1888 
1889 	/// Merge a lower priority config (`this`) with a `higher` priority config
1890 	public UserConfiguration merge(UserConfiguration higher)
1891 		return @safe pure nothrow
1892 	{
1893 		import std.traits : hasUDA;
1894 		UserConfiguration result;
1895 
1896 		static foreach (idx, _; UserConfiguration.tupleof) {
1897 			static if (hasUDA!(UserConfiguration.tupleof[idx], Optional))
1898 				result.tupleof[idx] = higher.tupleof[idx] ~ this.tupleof[idx];
1899 			else static if (IsSetInfo!(typeof(this.tupleof[idx]))) {
1900 				if (higher.tupleof[idx].set)
1901 					result.tupleof[idx] = higher.tupleof[idx];
1902 				else
1903 					result.tupleof[idx] = this.tupleof[idx];
1904 			} else
1905 				static assert(false,
1906 							  "Expect `@Optional` or `SetInfo` on: `" ~
1907 							  __traits(identifier, this.tupleof[idx]) ~
1908 							  "` of type : `" ~
1909 							  typeof(this.tupleof[idx]).stringof ~ "`");
1910 		}
1911 
1912 		return result;
1913 	}
1914 
1915 	/// Workaround multiple `E` declaration in `static foreach` when inline
1916 	private template IsSetInfo(T) { enum bool IsSetInfo = is(T : SetInfo!E, E); }
1917 }
1918 
1919 unittest {
1920 	import configy.Read;
1921 
1922     const str1 = `{
1923   "registryUrls": [ "http://foo.bar\/optional\/escape" ],
1924   "customCachePaths": [ "foo/bar", "foo/foo" ],
1925 
1926   "skipRegistry": "all",
1927   "defaultCompiler": "dmd",
1928   "defaultArchitecture": "fooarch",
1929   "defaultLowMemory": false,
1930 
1931   "defaultEnvironments": {
1932     "VAR2": "settings.VAR2",
1933     "VAR3": "settings.VAR3",
1934     "VAR4": "settings.VAR4"
1935   }
1936 }`;
1937 
1938 	const str2 = `{
1939   "registryUrls": [ "http://bar.foo" ],
1940   "customCachePaths": [ "bar/foo", "bar/bar" ],
1941 
1942   "skipRegistry": "none",
1943   "defaultCompiler": "ldc",
1944   "defaultArchitecture": "bararch",
1945   "defaultLowMemory": true,
1946 
1947   "defaultEnvironments": {
1948     "VAR": "Hi",
1949   }
1950 }`;
1951 
1952 	 auto c1 = parseConfigString!UserConfiguration(str1, "/dev/null");
1953 	 assert(c1.registryUrls == [ "http://foo.bar/optional/escape" ]);
1954 	 assert(c1.customCachePaths == [ NativePath("foo/bar"), NativePath("foo/foo") ]);
1955 	 assert(c1.skipRegistry == SkipPackageSuppliers.all);
1956 	 assert(c1.defaultCompiler == "dmd");
1957 	 assert(c1.defaultArchitecture == "fooarch");
1958 	 assert(c1.defaultLowMemory == false);
1959 	 assert(c1.defaultEnvironments.length == 3);
1960 	 assert(c1.defaultEnvironments["VAR2"] == "settings.VAR2");
1961 	 assert(c1.defaultEnvironments["VAR3"] == "settings.VAR3");
1962 	 assert(c1.defaultEnvironments["VAR4"] == "settings.VAR4");
1963 
1964 	 auto c2 = parseConfigString!UserConfiguration(str2, "/dev/null");
1965 	 assert(c2.registryUrls == [ "http://bar.foo" ]);
1966 	 assert(c2.customCachePaths == [ NativePath("bar/foo"), NativePath("bar/bar") ]);
1967 	 assert(c2.skipRegistry == SkipPackageSuppliers.none);
1968 	 assert(c2.defaultCompiler == "ldc");
1969 	 assert(c2.defaultArchitecture == "bararch");
1970 	 assert(c2.defaultLowMemory == true);
1971 	 assert(c2.defaultEnvironments.length == 1);
1972 	 assert(c2.defaultEnvironments["VAR"] == "Hi");
1973 
1974 	 auto m1 = c2.merge(c1);
1975 	 // c1 takes priority, so its registryUrls is first
1976 	 assert(m1.registryUrls == [ "http://foo.bar/optional/escape", "http://bar.foo" ]);
1977 	 // Same with CCP
1978 	 assert(m1.customCachePaths == [
1979 		 NativePath("foo/bar"), NativePath("foo/foo"),
1980 		 NativePath("bar/foo"), NativePath("bar/bar"),
1981 	 ]);
1982 
1983 	 // c1 fields only
1984 	 assert(m1.skipRegistry == c1.skipRegistry);
1985 	 assert(m1.defaultCompiler == c1.defaultCompiler);
1986 	 assert(m1.defaultArchitecture == c1.defaultArchitecture);
1987 	 assert(m1.defaultLowMemory == c1.defaultLowMemory);
1988 	 assert(m1.defaultEnvironments == c1.defaultEnvironments);
1989 
1990 	 auto m2 = c1.merge(c2);
1991 	 assert(m2.registryUrls == [ "http://bar.foo", "http://foo.bar/optional/escape" ]);
1992 	 assert(m2.customCachePaths == [
1993 		 NativePath("bar/foo"), NativePath("bar/bar"),
1994 		 NativePath("foo/bar"), NativePath("foo/foo"),
1995 	 ]);
1996 	 assert(m2.skipRegistry == c2.skipRegistry);
1997 	 assert(m2.defaultCompiler == c2.defaultCompiler);
1998 	 assert(m2.defaultArchitecture == c2.defaultArchitecture);
1999 	 assert(m2.defaultLowMemory == c2.defaultLowMemory);
2000 	 assert(m2.defaultEnvironments == c2.defaultEnvironments);
2001 
2002 	 auto m3 = UserConfiguration.init.merge(c1);
2003 	 assert(m3 == c1);
2004 }