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