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