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