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