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 	{
1173 		if (!path.absolute) path = m_rootPath ~ path;
1174 		path.normalize();
1175 
1176 		string[string] depVers;
1177 		string[] notFound; // keep track of any failed packages in here
1178 		foreach (dep; deps) {
1179 			Version ver;
1180 			try {
1181 				ver = getLatestVersion(dep);
1182 				depVers[dep] = ver.isBranch ? ver.toString() : "~>" ~ ver.toString();
1183 			} catch (Exception e) {
1184 				notFound ~= dep;
1185 			}
1186 		}
1187 
1188 		if(notFound.length > 1){
1189 			throw new Exception(.format("Couldn't find packages: %-(%s, %).", notFound));
1190 		}
1191 		else if(notFound.length == 1){
1192 			throw new Exception(.format("Couldn't find package: %-(%s, %).", notFound));
1193 		}
1194 
1195 		if (m_dryRun) return;
1196 
1197 		initPackage(path, depVers, type, format, recipe_callback);
1198 
1199 		//Act smug to the user.
1200 		logInfo("Successfully created an empty project in '%s'.", path.toNativeString());
1201 	}
1202 
1203 	/** Converts the package recipe of the loaded root package to the given format.
1204 
1205 		Params:
1206 			destination_file_ext = The file extension matching the desired
1207 				format. Possible values are "json" or "sdl".
1208 			print_only = Print the converted recipe instead of writing to disk
1209 	*/
1210 	void convertRecipe(string destination_file_ext, bool print_only = false)
1211 	{
1212 		import std.path : extension;
1213 		import std.stdio : stdout;
1214 		import dub.recipe.io : serializePackageRecipe, writePackageRecipe;
1215 
1216 		if (print_only) {
1217 			auto dst = stdout.lockingTextWriter;
1218 			serializePackageRecipe(dst, m_project.rootPackage.rawRecipe, "dub."~destination_file_ext);
1219 			return;
1220 		}
1221 
1222 		auto srcfile = m_project.rootPackage.recipePath;
1223 		auto srcext = srcfile.head.toString().extension;
1224 		if (srcext == "."~destination_file_ext) {
1225 			logInfo("Package format is already %s.", destination_file_ext);
1226 			return;
1227 		}
1228 
1229 		writePackageRecipe(srcfile.parentPath ~ ("dub."~destination_file_ext), m_project.rootPackage.rawRecipe);
1230 		removeFile(srcfile);
1231 	}
1232 
1233 	/** Runs DDOX to generate or serve documentation.
1234 
1235 		Params:
1236 			run = If set to true, serves documentation on a local web server.
1237 				Otherwise generates actual HTML files.
1238 			generate_args = Additional command line arguments to pass to
1239 				"ddox generate-html" or "ddox serve-html".
1240 	*/
1241 	void runDdox(bool run, string[] generate_args = null)
1242 	{
1243 		import std.process : browse;
1244 
1245 		if (m_dryRun) return;
1246 
1247 		// allow to choose a custom ddox tool
1248 		auto tool = m_project.rootPackage.recipe.ddoxTool;
1249 		if (tool.empty) tool = "ddox";
1250 
1251 		auto tool_pack = m_packageManager.getBestPackage(tool, ">=0.0.0");
1252 		if (!tool_pack) tool_pack = m_packageManager.getBestPackage(tool, "~master");
1253 		if (!tool_pack) {
1254 			logInfo("%s is not present, getting and storing it user wide", tool);
1255 			tool_pack = fetch(tool, Dependency(">=0.0.0"), defaultPlacementLocation, FetchOptions.none);
1256 		}
1257 
1258 		auto ddox_dub = new Dub(null, m_packageSuppliers);
1259 		ddox_dub.loadPackage(tool_pack.path);
1260 		ddox_dub.upgrade(UpgradeOptions.select);
1261 
1262 		auto compiler_binary = this.defaultCompiler;
1263 
1264 		GeneratorSettings settings;
1265 		settings.config = "application";
1266 		settings.compiler = getCompiler(compiler_binary); // TODO: not using --compiler ???
1267 		settings.platform = settings.compiler.determinePlatform(settings.buildSettings, compiler_binary, m_defaultArchitecture);
1268 		settings.buildType = "debug";
1269 		settings.run = true;
1270 
1271 		auto filterargs = m_project.rootPackage.recipe.ddoxFilterArgs.dup;
1272 		if (filterargs.empty) filterargs = ["--min-protection=Protected", "--only-documented"];
1273 
1274 		settings.runArgs = "filter" ~ filterargs ~ "docs.json";
1275 		ddox_dub.generateProject("build", settings);
1276 
1277 		auto p = tool_pack.path;
1278 		p.endsWithSlash = true;
1279 		auto tool_path = p.toNativeString();
1280 
1281 		if (run) {
1282 			settings.runArgs = ["serve-html", "--navigation-type=ModuleTree", "docs.json", "--web-file-dir="~tool_path~"public"] ~ generate_args;
1283 			browse("http://127.0.0.1:8080/");
1284 		} else {
1285 			settings.runArgs = ["generate-html", "--navigation-type=ModuleTree", "docs.json", "docs"] ~ generate_args;
1286 		}
1287 		ddox_dub.generateProject("build", settings);
1288 
1289 		if (!run) {
1290 			// TODO: ddox should copy those files itself
1291 			version(Windows) runCommand(`xcopy /S /D "`~tool_path~`public\*" docs\`);
1292 			else runCommand("rsync -ru '"~tool_path~"public/' docs/");
1293 		}
1294 	}
1295 
1296 	private void updatePackageSearchPath()
1297 	{
1298 		if (!m_overrideSearchPath.empty) {
1299 			m_packageManager.disableDefaultSearchPaths = true;
1300 			m_packageManager.searchPath = [m_overrideSearchPath];
1301 		} else {
1302 			auto p = environment.get("DUBPATH");
1303 			NativePath[] paths;
1304 
1305 			version(Windows) enum pathsep = ";";
1306 			else enum pathsep = ":";
1307 			if (p.length) paths ~= p.split(pathsep).map!(p => NativePath(p))().array();
1308 			m_packageManager.disableDefaultSearchPaths = false;
1309 			m_packageManager.searchPath = paths;
1310 		}
1311 	}
1312 
1313 	private void determineDefaultCompiler()
1314 	{
1315 		import std.file : thisExePath;
1316 		import std.path : buildPath, dirName, expandTilde, isAbsolute, isDirSeparator;
1317 		import std.process : environment;
1318 		import std.range : front;
1319 
1320 		m_defaultCompiler = m_config.defaultCompiler.expandTilde;
1321 		if (m_defaultCompiler.length && m_defaultCompiler.isAbsolute)
1322 			return;
1323 
1324 		static immutable BinaryPrefix = `$DUB_BINARY_PATH`;
1325 		if(m_defaultCompiler.startsWith(BinaryPrefix))
1326 		{
1327 			m_defaultCompiler = thisExePath().dirName() ~ m_defaultCompiler[BinaryPrefix.length .. $];
1328 			return;
1329 		}
1330 
1331 		if (!find!isDirSeparator(m_defaultCompiler).empty)
1332 			throw new Exception("defaultCompiler specified in a DUB config file cannot use an unqualified relative path:\n\n" ~ m_defaultCompiler ~
1333 			"\n\nUse \"$DUB_BINARY_PATH/../path/you/want\" instead.");
1334 
1335 		version (Windows) enum sep = ";", exe = ".exe";
1336 		version (Posix) enum sep = ":", exe = "";
1337 
1338 		auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"];
1339 		// If a compiler name is specified, look for it next to dub.
1340 		// Otherwise, look for any of the common compilers adjacent to dub.
1341 		if (m_defaultCompiler.length)
1342 		{
1343 			string compilerPath = buildPath(thisExePath().dirName(), m_defaultCompiler ~ exe);
1344 			if (existsFile(compilerPath))
1345 			{
1346 				m_defaultCompiler = compilerPath;
1347 				return;
1348 			}
1349 		}
1350 		else
1351 		{
1352 			auto nextFound = compilers.find!(bin => existsFile(buildPath(thisExePath().dirName(), bin ~ exe)));
1353 			if (!nextFound.empty)
1354 			{
1355 				m_defaultCompiler = buildPath(thisExePath().dirName(),  nextFound.front ~ exe);
1356 				return;
1357 			}
1358 		}
1359 
1360 		// If nothing found next to dub, search the user's PATH, starting
1361 		// with the compiler name from their DUB config file, if specified.
1362 		if (m_defaultCompiler.length)
1363 			compilers = m_defaultCompiler ~ compilers;
1364 		auto paths = environment.get("PATH", "").splitter(sep).map!NativePath;
1365 		auto res = compilers.find!(bin => paths.canFind!(p => existsFile(p ~ (bin~exe))));
1366 		m_defaultCompiler = res.empty ? compilers[0] : res.front;
1367 	}
1368 
1369 	private NativePath makeAbsolute(NativePath p) const { return p.absolute ? p : m_rootPath ~ p; }
1370 	private NativePath makeAbsolute(string p) const { return makeAbsolute(NativePath(p)); }
1371 }
1372 
1373 
1374 /// Option flags for `Dub.fetch`
1375 enum FetchOptions
1376 {
1377 	none = 0,
1378 	forceBranchUpgrade = 1<<0,
1379 	usePrerelease = 1<<1,
1380 	forceRemove = 1<<2, /// Deprecated, does nothing.
1381 	printOnly = 1<<3,
1382 }
1383 
1384 /// Option flags for `Dub.upgrade`
1385 enum UpgradeOptions
1386 {
1387 	none = 0,
1388 	upgrade = 1<<1, /// Upgrade existing packages
1389 	preRelease = 1<<2, /// inclde pre-release versions in upgrade
1390 	forceRemove = 1<<3, /// Deprecated, does nothing.
1391 	select = 1<<4, /// Update the dub.selections.json file with the upgraded versions
1392 	dryRun = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence
1393 	/*deprecated*/ printUpgradesOnly = dryRun, /// deprecated, use dryRun instead
1394 	/*deprecated*/ useCachedResult = 1<<6, /// deprecated, has no effect
1395 	noSaveSelections = 1<<7, /// Don't store updated selections on disk
1396 }
1397 
1398 /// Determines which of the default package suppliers are queried for packages.
1399 enum SkipPackageSuppliers {
1400 	none,       /// Uses all configured package suppliers.
1401 	standard,   /// Does not use the default package suppliers (`defaultPackageSuppliers`).
1402 	configured, /// Does not use default suppliers or suppliers configured in DUB's configuration file
1403 	all         /// Uses only manually specified package suppliers.
1404 }
1405 
1406 private class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) {
1407 	protected {
1408 		Dub m_dub;
1409 		UpgradeOptions m_options;
1410 		Dependency[][string] m_packageVersions;
1411 		Package[string] m_remotePackages;
1412 		SelectedVersions m_selectedVersions;
1413 		Package m_rootPackage;
1414 		bool[string] m_packagesToUpgrade;
1415 		Package[PackageDependency] m_packages;
1416 		TreeNodes[][TreeNode] m_children;
1417 	}
1418 
1419 
1420 	this(Dub dub, UpgradeOptions options)
1421 	{
1422 		m_dub = dub;
1423 		m_options = options;
1424 	}
1425 
1426 	void addPackageToUpgrade(string name)
1427 	{
1428 		m_packagesToUpgrade[name] = true;
1429 	}
1430 
1431 	Dependency[string] resolve(Package root, SelectedVersions selected_versions)
1432 	{
1433 		m_rootPackage = root;
1434 		m_selectedVersions = selected_versions;
1435 		return super.resolve(TreeNode(root.name, Dependency(root.version_)), (m_options & UpgradeOptions.printUpgradesOnly) == 0);
1436 	}
1437 
1438 	protected bool isFixedPackage(string pack)
1439 	{
1440 		return m_packagesToUpgrade !is null && pack !in m_packagesToUpgrade;
1441 	}
1442 
1443 	protected override Dependency[] getAllConfigs(string pack)
1444 	{
1445 		if (auto pvers = pack in m_packageVersions)
1446 			return *pvers;
1447 
1448 		if ((!(m_options & UpgradeOptions.upgrade) || isFixedPackage(pack)) && m_selectedVersions.hasSelectedVersion(pack)) {
1449 			auto ret = [m_selectedVersions.getSelectedVersion(pack)];
1450 			logDiagnostic("Using fixed selection %s %s", pack, ret[0]);
1451 			m_packageVersions[pack] = ret;
1452 			return ret;
1453 		}
1454 
1455 		logDiagnostic("Search for versions of %s (%s package suppliers)", pack, m_dub.m_packageSuppliers.length);
1456 		Version[] versions;
1457 		foreach (p; m_dub.packageManager.getPackageIterator(pack))
1458 			versions ~= p.version_;
1459 
1460 		foreach (ps; m_dub.m_packageSuppliers) {
1461 			try {
1462 				auto vers = ps.getVersions(pack);
1463 				vers.reverse();
1464 				if (!vers.length) {
1465 					logDiagnostic("No versions for %s for %s", pack, ps.description);
1466 					continue;
1467 				}
1468 
1469 				versions ~= vers;
1470 				break;
1471 			} catch (Exception e) {
1472 				logWarn("Package %s not found in %s: %s", pack, ps.description, e.msg);
1473 				logDebug("Full error: %s", e.toString().sanitize);
1474 			}
1475 		}
1476 
1477 		// sort by version, descending, and remove duplicates
1478 		versions = versions.sort!"a>b".uniq.array;
1479 
1480 		// move pre-release versions to the back of the list if no preRelease flag is given
1481 		if (!(m_options & UpgradeOptions.preRelease))
1482 			versions = versions.filter!(v => !v.isPreRelease).array ~ versions.filter!(v => v.isPreRelease).array;
1483 
1484 		// filter out invalid/unreachable dependency specs
1485 		versions = versions.filter!((v) {
1486 				bool valid = getPackage(pack, Dependency(v)) !is null;
1487 				if (!valid) logDiagnostic("Excluding invalid dependency specification %s %s from dependency resolution process.", pack, v);
1488 				return valid;
1489 			}).array;
1490 
1491 		if (!versions.length) logDiagnostic("Nothing found for %s", pack);
1492 		else logDiagnostic("Return for %s: %s", pack, versions);
1493 
1494 		auto ret = versions.map!(v => Dependency(v)).array;
1495 		m_packageVersions[pack] = ret;
1496 		return ret;
1497 	}
1498 
1499 	protected override Dependency[] getSpecificConfigs(string pack, TreeNodes nodes)
1500 	{
1501 		if (!nodes.configs.path.empty && getPackage(pack, nodes.configs)) return [nodes.configs];
1502 		else return null;
1503 	}
1504 
1505 
1506 	protected override TreeNodes[] getChildren(TreeNode node)
1507 	{
1508 		if (auto pc = node in m_children)
1509 			return *pc;
1510 		auto ret = getChildrenRaw(node);
1511 		m_children[node] = ret;
1512 		return ret;
1513 	}
1514 
1515 	private final TreeNodes[] getChildrenRaw(TreeNode node)
1516 	{
1517 		import std.array : appender;
1518 		auto ret = appender!(TreeNodes[]);
1519 		auto pack = getPackage(node.pack, node.config);
1520 		if (!pack) {
1521 			// this can hapen when the package description contains syntax errors
1522 			logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config);
1523 			return null;
1524 		}
1525 		auto basepack = pack.basePackage;
1526 
1527 		foreach (d; pack.getAllDependenciesRange()) {
1528 			auto dbasename = getBasePackageName(d.name);
1529 
1530 			// detect dependencies to the root package (or sub packages thereof)
1531 			if (dbasename == basepack.name) {
1532 				auto absdeppath = d.spec.mapToPath(pack.path).path;
1533 				absdeppath.endsWithSlash = true;
1534 				auto subpack = m_dub.m_packageManager.getSubPackage(basepack, getSubPackageName(d.name), true);
1535 				if (subpack) {
1536 					auto desireddeppath = d.name == dbasename ? basepack.path : subpack.path;
1537 					desireddeppath.endsWithSlash = true;
1538 					enforce(d.spec.path.empty || absdeppath == desireddeppath,
1539 						format("Dependency from %s to root package references wrong path: %s vs. %s",
1540 							node.pack, absdeppath.toNativeString(), desireddeppath.toNativeString()));
1541 				}
1542 				ret ~= TreeNodes(d.name, node.config);
1543 				continue;
1544 			}
1545 
1546 			DependencyType dt;
1547 			if (d.spec.optional) {
1548 				if (d.spec.default_) dt = DependencyType.optionalDefault;
1549 				else dt = DependencyType.optional;
1550 			} else dt = DependencyType.required;
1551 
1552 			Dependency dspec = d.spec.mapToPath(pack.path);
1553 
1554 			// if not upgrading, use the selected version
1555 			if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions && m_selectedVersions.hasSelectedVersion(dbasename))
1556 				dspec = m_selectedVersions.getSelectedVersion(dbasename);
1557 
1558 			// keep selected optional dependencies and avoid non-selected optional-default dependencies by default
1559 			if (m_selectedVersions && !m_selectedVersions.bare) {
1560 				if (dt == DependencyType.optionalDefault && !m_selectedVersions.hasSelectedVersion(dbasename))
1561 					dt = DependencyType.optional;
1562 				else if (dt == DependencyType.optional && m_selectedVersions.hasSelectedVersion(dbasename))
1563 					dt = DependencyType.optionalDefault;
1564 			}
1565 
1566 			ret ~= TreeNodes(d.name, dspec, dt);
1567 		}
1568 		return ret.data;
1569 	}
1570 
1571 	protected override bool matches(Dependency configs, Dependency config)
1572 	{
1573 		if (!configs.path.empty) return configs.path == config.path;
1574 		return configs.merge(config).valid;
1575 	}
1576 
1577 	private Package getPackage(string name, Dependency dep)
1578 	{
1579 		auto key = PackageDependency(name, dep);
1580 		if (auto pp = key in m_packages)
1581 			return *pp;
1582 		auto p = getPackageRaw(name, dep);
1583 		m_packages[key] = p;
1584 		return p;
1585 	}
1586 
1587 	private Package getPackageRaw(string name, Dependency dep)
1588 	{
1589 		auto basename = getBasePackageName(name);
1590 
1591 		// for sub packages, first try to get them from the base package
1592 		if (basename != name) {
1593 			auto subname = getSubPackageName(name);
1594 			auto basepack = getPackage(basename, dep);
1595 			if (!basepack) return null;
1596 			if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true)) {
1597 				return sp;
1598 			} else if (!basepack.subPackages.canFind!(p => p.path.length)) {
1599 				// note: external sub packages are handled further below
1600 				auto spr = basepack.getInternalSubPackage(subname);
1601 				if (!spr.isNull) {
1602 					auto sp = new Package(spr, basepack.path, basepack);
1603 					m_remotePackages[sp.name] = sp;
1604 					return sp;
1605 				} else {
1606 					logDiagnostic("Sub package %s doesn't exist in %s %s.", name, basename, dep.version_);
1607 					return null;
1608 				}
1609 			} else if (auto ret = m_dub.m_packageManager.getBestPackage(name, dep)) {
1610 				return ret;
1611 			} else {
1612 				logDiagnostic("External sub package %s %s not found.", name, dep.version_);
1613 				return null;
1614 			}
1615 		}
1616 
1617 		// shortcut if the referenced package is the root package
1618 		if (basename == m_rootPackage.basePackage.name)
1619 			return m_rootPackage.basePackage;
1620 
1621 		if (!dep.path.empty) {
1622 			try {
1623 				auto ret = m_dub.packageManager.getOrLoadPackage(dep.path);
1624 				if (dep.matches(ret.version_)) return ret;
1625 			} catch (Exception e) {
1626 				logDiagnostic("Failed to load path based dependency %s: %s", name, e.msg);
1627 				logDebug("Full error: %s", e.toString().sanitize);
1628 				return null;
1629 			}
1630 		}
1631 
1632 		if (auto ret = m_dub.m_packageManager.getBestPackage(name, dep))
1633 			return ret;
1634 
1635 		auto key = name ~ ":" ~ dep.version_.toString();
1636 		if (auto ret = key in m_remotePackages)
1637 			return *ret;
1638 
1639 		auto prerelease = (m_options & UpgradeOptions.preRelease) != 0;
1640 
1641 		auto rootpack = name.split(":")[0];
1642 
1643 		foreach (ps; m_dub.m_packageSuppliers) {
1644 			if (rootpack == name) {
1645 				try {
1646 					auto desc = ps.fetchPackageRecipe(name, dep, prerelease);
1647 					if (desc.type == Json.Type.null_)
1648 						continue;
1649 					auto ret = new Package(desc);
1650 					m_remotePackages[key] = ret;
1651 					return ret;
1652 				} catch (Exception e) {
1653 					logDiagnostic("Metadata for %s %s could not be downloaded from %s: %s", name, dep, ps.description, e.msg);
1654 					logDebug("Full error: %s", e.toString().sanitize);
1655 				}
1656 			} else {
1657 				logDiagnostic("Package %s not found in base package description (%s). Downloading whole package.", name, dep.version_.toString());
1658 				try {
1659 					FetchOptions fetchOpts;
1660 					fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none;
1661 					m_dub.fetch(rootpack, dep, m_dub.defaultPlacementLocation, fetchOpts, "need sub package description");
1662 					auto ret = m_dub.m_packageManager.getBestPackage(name, dep);
1663 					if (!ret) {
1664 						logWarn("Package %s %s doesn't have a sub package %s", rootpack, dep.version_, name);
1665 						return null;
1666 					}
1667 					m_remotePackages[key] = ret;
1668 					return ret;
1669 				} catch (Exception e) {
1670 					logDiagnostic("Package %s could not be downloaded from %s: %s", rootpack, ps.description, e.msg);
1671 					logDebug("Full error: %s", e.toString().sanitize);
1672 				}
1673 			}
1674 		}
1675 
1676 		m_remotePackages[key] = null;
1677 
1678 		logWarn("Package %s %s could not be loaded either locally, or from the configured package registries.", name, dep);
1679 		return null;
1680 	}
1681 }
1682 
1683 private struct SpecialDirs {
1684 	NativePath temp;
1685 	NativePath userSettings;
1686 	NativePath systemSettings;
1687 	NativePath localRepository;
1688 }
1689 
1690 private class DubConfig {
1691 	private {
1692 		DubConfig m_parentConfig;
1693 		Json m_data;
1694 	}
1695 
1696 	this(Json data, DubConfig parent_config)
1697 	{
1698 		m_data = data;
1699 		m_parentConfig = parent_config;
1700 	}
1701 
1702 	@property string[] registryURLs()
1703 	{
1704 		string[] ret;
1705 		if (auto pv = "registryUrls" in m_data)
1706 			ret = (*pv).deserializeJson!(string[]);
1707 		if (m_parentConfig) ret ~= m_parentConfig.registryURLs;
1708 		return ret;
1709 	}
1710 
1711 	@property SkipPackageSuppliers skipRegistry()
1712 	{
1713 		if(auto pv = "skipRegistry" in m_data)
1714 			return to!SkipPackageSuppliers((*pv).get!string);
1715 
1716 		if (m_parentConfig)
1717 			return m_parentConfig.skipRegistry;
1718 
1719 		return SkipPackageSuppliers.none;
1720 	}
1721 
1722 	@property NativePath[] customCachePaths()
1723 	{
1724 		import std.algorithm.iteration : map;
1725 		import std.array : array;
1726 
1727 		NativePath[] ret;
1728 		if (auto pv = "customCachePaths" in m_data)
1729 			ret = (*pv).deserializeJson!(string[])
1730 				.map!(s => NativePath(s))
1731 				.array;
1732 		if (m_parentConfig)
1733 			ret ~= m_parentConfig.customCachePaths;
1734 		return ret;
1735 	}
1736 
1737 	@property string defaultCompiler()
1738 	const {
1739 		if (auto pv = "defaultCompiler" in m_data)
1740 			return pv.get!string;
1741 		if (m_parentConfig) return m_parentConfig.defaultCompiler;
1742 		return null;
1743 	}
1744 
1745 	@property string defaultArchitecture()
1746 	const {
1747 		if(auto pv = "defaultArchitecture" in m_data)
1748 			return (*pv).get!string;
1749 		if (m_parentConfig) return m_parentConfig.defaultArchitecture;
1750 		return null;
1751 	}
1752 }