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.data.settings : SPS = SkipPackageSuppliers, Settings;
12 import dub.dependency;
13 import dub.dependencyresolver;
14 import dub.internal.utils;
15 import dub.internal.vibecompat.core.file;
16 import dub.internal.vibecompat.data.json;
17 import dub.internal.vibecompat.inet.url;
18 import dub.internal.logging;
19 import dub.package_;
20 import dub.packagemanager;
21 import dub.packagesuppliers;
22 import dub.project;
23 import dub.generators.generator;
24 import dub.init;
25 
26 import std.algorithm;
27 import std.array : array, replace;
28 import std.conv : text, to;
29 import std.encoding : sanitize;
30 import std.exception : enforce;
31 import std.file;
32 import std.process : environment;
33 import std.range : assumeSorted, empty;
34 import std.string;
35 
36 static this()
37 {
38 	import dub.compilers.dmd : DMDCompiler;
39 	import dub.compilers.gdc : GDCCompiler;
40 	import dub.compilers.ldc : LDCCompiler;
41 	registerCompiler(new DMDCompiler);
42 	registerCompiler(new GDCCompiler);
43 	registerCompiler(new LDCCompiler);
44 }
45 
46 deprecated("use defaultRegistryURLs") enum defaultRegistryURL = defaultRegistryURLs[0];
47 
48 /// The URL to the official package registry and it's default fallback registries.
49 static immutable string[] defaultRegistryURLs = [
50 	"https://code.dlang.org/",
51 	"https://codemirror.dlang.org/",
52 	"https://dub.bytecraft.nl/",
53 	"https://code-mirror.dlang.io/",
54 ];
55 
56 /** Returns a default list of package suppliers.
57 
58 	This will contain a single package supplier that points to the official
59 	package registry.
60 
61 	See_Also: `defaultRegistryURLs`
62 */
63 deprecated("This function wasn't intended for public use - open an issue with Dub if you need it")
64 PackageSupplier[] defaultPackageSuppliers()
65 {
66 	logDiagnostic("Using dub registry url '%s'", defaultRegistryURLs[0]);
67 	return [new FallbackPackageSupplier(defaultRegistryURLs.map!_getRegistryPackageSupplier.array)];
68 }
69 
70 /** Returns a registry package supplier according to protocol.
71 
72 	Allowed protocols are dub+http(s):// and maven+http(s)://.
73 */
74 deprecated("This function wasn't intended for public use - open an issue with Dub if you need it")
75 PackageSupplier getRegistryPackageSupplier(string url)
76 {
77     return _getRegistryPackageSupplier(url);
78 }
79 
80 // Private to avoid a bug in `defaultPackageSuppliers` with `map` triggering a deprecation
81 // even though the context is deprecated.
82 private PackageSupplier _getRegistryPackageSupplier(string url)
83 {
84 	switch (url.startsWith("dub+", "mvn+", "file://"))
85 	{
86 		case 1:
87 			return new RegistryPackageSupplier(URL(url[4..$]));
88 		case 2:
89 			return new MavenRegistryPackageSupplier(URL(url[4..$]));
90 		case 3:
91 			return new FileSystemPackageSupplier(NativePath(url[7..$]));
92 		default:
93 			return new RegistryPackageSupplier(URL(url));
94 	}
95 }
96 
97 deprecated unittest
98 {
99 	auto dubRegistryPackageSupplier = getRegistryPackageSupplier("dub+https://code.dlang.org");
100 	assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org"));
101 
102 	dubRegistryPackageSupplier = getRegistryPackageSupplier("https://code.dlang.org");
103 	assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org"));
104 
105 	auto mavenRegistryPackageSupplier = getRegistryPackageSupplier("mvn+http://localhost:8040/maven/libs-release/dubpackages");
106 	assert(mavenRegistryPackageSupplier.description.canFind(" http://localhost:8040/maven/libs-release/dubpackages"));
107 
108 	auto fileSystemPackageSupplier = getRegistryPackageSupplier("file:///etc/dubpackages");
109 	assert(fileSystemPackageSupplier.description.canFind(" " ~ NativePath("/etc/dubpackages").toNativeString));
110 }
111 
112 /** Provides a high-level entry point for DUB's functionality.
113 
114 	This class provides means to load a certain project (a root package with
115 	all of its dependencies) and to perform high-level operations as found in
116 	the command line interface.
117 */
118 class Dub {
119 	protected {
120 		bool m_dryRun = false;
121 		PackageManager m_packageManager;
122 		PackageSupplier[] m_packageSuppliers;
123 		NativePath m_rootPath;
124 		string m_mainRecipePath;
125 		SpecialDirs m_dirs;
126 		Settings m_config;
127 		Project m_project;
128 		string m_defaultCompiler;
129 	}
130 
131 	/** The default placement location of fetched packages.
132 
133 		This property can be altered, so that packages which are downloaded as part
134 		of the normal upgrade process are stored in a certain location. This is
135 		how the "--local" and "--system" command line switches operate.
136 	*/
137 	PlacementLocation defaultPlacementLocation = PlacementLocation.user;
138 
139 
140 	/** Initializes the instance for use with a specific root package.
141 
142 		Note that a package still has to be loaded using one of the
143 		`loadPackage` overloads.
144 
145 		Params:
146 			root_path = Path to the root package
147 			base = A list of package suppliers that are always present
148 				   (regardless of `skip`) and take precedence over the default
149 				   and configured `PackageSupplier`. This setting is currently
150 				   not used by the dub application but useful for libraries.
151 			skip = Can be used to skip using the configured package	suppliers,
152 					as well as the default suppliers.
153 	*/
154 	this(string root_path = ".", PackageSupplier[] base = null,
155 			SkipPackageSuppliers skip = SkipPackageSuppliers.none)
156 	{
157 		m_rootPath = NativePath(root_path);
158 		if (!m_rootPath.absolute) m_rootPath = getWorkingDirectory() ~ m_rootPath;
159 
160 		init();
161 
162 		const registry_var = environment.get("DUB_REGISTRY", null);
163 		m_packageSuppliers = this.makePackageSuppliers(base, skip, registry_var);
164 		m_packageManager = this.makePackageManager();
165 
166 		auto ccps = m_config.customCachePaths;
167 		if (ccps.length)
168 			m_packageManager.customCachePaths = ccps;
169 
170 		// TODO: Move this environment read out of the ctor
171 		if (auto p = environment.get("DUBPATH")) {
172 			version(Windows) enum pathsep = ";";
173 			else enum pathsep = ":";
174 			NativePath[] paths = p.split(pathsep)
175 				.map!(p => NativePath(p))().array();
176 			m_packageManager.searchPath = paths;
177 		}
178 	}
179 
180 	/** Initializes the instance with a single package search path, without
181 		loading a package.
182 
183 		This constructor corresponds to the "--bare" option of the command line
184 		interface.
185 
186 		Params:
187 		  root = The root path of the Dub instance itself.
188 		  pkg_root = The root of the location where packages are located
189 					 Only packages under this location will be accessible.
190 					 Note that packages at the top levels will be ignored.
191 	*/
192 	this(NativePath root, NativePath pkg_root)
193 	{
194 		// Note: We're doing `init()` before setting the `rootPath`,
195 		// to prevent `init` from reading the project's settings.
196 		init();
197 		this.m_rootPath = root;
198 		m_packageManager = new PackageManager(pkg_root);
199 	}
200 
201 	deprecated("Use the overload that takes `(NativePath pkg_root, NativePath root)`")
202 	this(NativePath pkg_root)
203 	{
204 		this(pkg_root, pkg_root);
205 	}
206 
207 	/**
208 	 * Get the `PackageManager` instance to use for this `Dub` instance
209 	 *
210 	 * The `PackageManager` is a central component of `Dub` as it allows to
211 	 * store and retrieve packages from the file system. In unittests, or more
212 	 * generally in a library setup, one may wish to provide a custom
213 	 * implementation, which can be done by overriding this method.
214 	 */
215 	protected PackageManager makePackageManager()
216 	{
217 		return new PackageManager(m_rootPath, m_dirs.userPackages, m_dirs.systemSettings, false);
218 	}
219 
220 	protected void init()
221 	{
222 		this.m_dirs = SpecialDirs.make();
223 		this.m_config = this.loadConfig(this.m_dirs);
224 		this.m_defaultCompiler = this.determineDefaultCompiler();
225 	}
226 
227 	/**
228 	 * Load user configuration for this instance
229 	 *
230 	 * This can be overloaded in child classes to prevent library / unittest
231 	 * dub from doing any kind of file IO.
232 	 * As this routine is used during initialization, the only assumption made
233 	 * in the base implementation is that `m_rootPath` has been populated.
234 	 * Child implementation should not rely on any other field in the base
235 	 * class having been populated.
236 	 *
237 	 * Params:
238 	 *	 dirs = An instance of `SpecialDirs` to read from and write to,
239 	 *			as the configurations being read might set a `dubHome`.
240 	 *
241 	 * Returns:
242 	 *	 A populated `Settings` instance.
243 	 */
244 	protected Settings loadConfig(ref SpecialDirs dirs) const
245 	{
246 		import dub.internal.configy.Read;
247 
248 		static void readSettingsFile (NativePath path_, ref Settings current)
249 		{
250 			// TODO: Remove `StrictMode.Warn` after v1.40 release
251 			// The default is to error, but as the previous parser wasn't
252 			// complaining, we should first warn the user.
253 			const path = path_.toNativeString();
254 			if (path.exists) {
255 				auto newConf = parseConfigFileSimple!Settings(path, StrictMode.Warn);
256 				if (!newConf.isNull())
257 					current = current.merge(newConf.get());
258 			}
259 		}
260 
261 		Settings result;
262 		const dubFolderPath = NativePath(thisExePath).parentPath;
263 
264 		// override default userSettings + userPackages if a $DPATH or
265 		// $DUB_HOME environment variable is set.
266 		bool overrideDubHomeFromEnv;
267 		{
268 			string dubHome = environment.get("DUB_HOME");
269 			if (!dubHome.length) {
270 				auto dpath = environment.get("DPATH");
271 				if (dpath.length)
272 					dubHome = (NativePath(dpath) ~ "dub/").toNativeString();
273 
274 			}
275 			if (dubHome.length) {
276 				overrideDubHomeFromEnv = true;
277 
278 				dirs.userSettings = NativePath(dubHome);
279 				dirs.userPackages = dirs.userSettings;
280 				dirs.cache = dirs.userPackages ~ "cache";
281 			}
282 		}
283 
284 		readSettingsFile(dirs.systemSettings ~ "settings.json", result);
285 		readSettingsFile(dubFolderPath ~ "../etc/dub/settings.json", result);
286 		version (Posix) {
287 			if (dubFolderPath.absolute && dubFolderPath.startsWith(NativePath("usr")))
288 				readSettingsFile(NativePath("/etc/dub/settings.json"), result);
289 		}
290 
291 		// Override user + local package path from system / binary settings
292 		// Then continues loading local settings from these folders. (keeping
293 		// global /etc/dub/settings.json settings intact)
294 		//
295 		// Don't use it if either $DPATH or $DUB_HOME are set, as environment
296 		// variables usually take precedence over configuration.
297 		if (!overrideDubHomeFromEnv && result.dubHome.set) {
298 			dirs.userSettings = NativePath(result.dubHome.expandEnvironmentVariables);
299 		}
300 
301 		// load user config:
302 		readSettingsFile(dirs.userSettings ~ "settings.json", result);
303 
304 		// load per-package config:
305 		if (!this.m_rootPath.empty)
306 			readSettingsFile(this.m_rootPath ~ "dub.settings.json", result);
307 
308 		// same as userSettings above, but taking into account the
309 		// config loaded from user settings and per-package config as well.
310 		if (!overrideDubHomeFromEnv && result.dubHome.set) {
311 			dirs.userPackages = NativePath(result.dubHome.expandEnvironmentVariables);
312 			dirs.cache = dirs.userPackages ~ "cache";
313 		}
314 
315         return result;
316     }
317 
318 	/** Get the list of package suppliers.
319 
320 		Params:
321 			additional_package_suppliers = A list of package suppliers to try
322 				before the suppliers found in the configurations files and the
323 				`defaultPackageSuppliers`.
324 			skip = Can be used to skip using the configured package suppliers,
325 				   as well as the default suppliers.
326 	*/
327 	deprecated("This is an implementation detail. " ~
328 		"Use `packageSuppliers` to get the computed list of package " ~
329 		"suppliers once a `Dub` instance has been constructed.")
330 	public PackageSupplier[] getPackageSuppliers(PackageSupplier[] base, SkipPackageSuppliers skip)
331 	{
332 		return this.makePackageSuppliers(base, skip, environment.get("DUB_REGISTRY", null));
333 	}
334 
335 	/// Ditto
336 	protected PackageSupplier[] makePackageSuppliers(PackageSupplier[] base,
337 		SkipPackageSuppliers skip, string registry_var)
338 	{
339 		PackageSupplier[] ps = base;
340 
341 		if (skip < SkipPackageSuppliers.all)
342 		{
343 			ps ~= registry_var
344 				.splitter(";")
345 				.map!(url => this.makePackageSupplier(url))
346 				.array;
347 		}
348 
349 		if (skip < SkipPackageSuppliers.configured)
350 		{
351 			ps ~= m_config.registryUrls
352 				.map!(url => this.makePackageSupplier(url))
353 				.array;
354 		}
355 
356 		if (skip < SkipPackageSuppliers.standard)
357 			ps ~= new FallbackPackageSupplier(
358 				defaultRegistryURLs.map!(url => this.makePackageSupplier(url))
359 				.array);
360 
361 		return ps;
362 	}
363 
364 	// Note: This test rely on the environment, which is not how unittests should work.
365 	// This should be removed / refactored to keep coverage without affecting the env.
366 	unittest
367 	{
368 		import dub.test.base : TestDub;
369 
370 		scope (exit) environment.remove("DUB_REGISTRY");
371 		auto dub = new TestDub(null, ".", null, SkipPackageSuppliers.configured);
372 		assert(dub.packageSuppliers.length == 0);
373 		environment["DUB_REGISTRY"] = "http://example.com/";
374 		dub = new TestDub(null, ".", null, SkipPackageSuppliers.configured);
375 		assert(dub.packageSuppliers.length == 1);
376 		environment["DUB_REGISTRY"] = "http://example.com/;http://foo.com/";
377 		dub = new TestDub(null, ".", null, SkipPackageSuppliers.configured);
378 		assert(dub.packageSuppliers.length == 2);
379 		dub = new TestDub(null, ".", [new RegistryPackageSupplier(URL("http://bar.com/"))], SkipPackageSuppliers.configured);
380 		assert(dub.packageSuppliers.length == 3);
381 
382 		dub = new TestDub();
383 		assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.none, null).length == 1);
384 		assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.configured, null).length == 0);
385 		assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.standard, null).length == 0);
386 		assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.standard, "http://example.com/")
387 			.length == 1);
388 	}
389 
390 	/**
391 	 * Instantiate a `PackageSupplier` according to a given URL
392 	 *
393 	 * This is a factory function for `PackageSupplier`. Child classes may
394 	 * wish to override this to implement their own `PackageSupplier` logic,
395 	 * be it by extending this method's ability or replacing it.
396 	 *
397 	 * Params:
398 	 *	 url = The URL of the `PackageSupplier`.
399 	 *
400 	 * Returns:
401 	 *	 A new instance of a `PackageSupplier`.
402 	 */
403 	protected PackageSupplier makePackageSupplier(string url) const
404 	{
405 		switch (url.startsWith("dub+", "mvn+", "file://"))
406 		{
407 		case 1:
408 			return new RegistryPackageSupplier(URL(url[4..$]));
409 		case 2:
410 			return new MavenRegistryPackageSupplier(URL(url[4..$]));
411 		case 3:
412 			return new FileSystemPackageSupplier(NativePath(url[7..$]));
413 		default:
414 			return new RegistryPackageSupplier(URL(url));
415 		}
416 	}
417 
418 	/// ditto
419 	deprecated("This is an implementation detail. " ~
420 		"Use `packageSuppliers` to get the computed list of package " ~
421 		"suppliers once a `Dub` instance has been constructed.")
422 	public PackageSupplier[] getPackageSuppliers(PackageSupplier[] additional_package_suppliers)
423 	{
424 		return getPackageSuppliers(additional_package_suppliers, m_config.skipRegistry);
425 	}
426 
427 	@property bool dryRun() const { return m_dryRun; }
428 	@property void dryRun(bool v) { m_dryRun = v; }
429 
430 	/** Returns the root path (usually the current working directory).
431 	*/
432 	@property NativePath rootPath() const { return m_rootPath; }
433 	/// ditto
434 	deprecated("Changing the root path is deprecated as it has non-obvious pitfalls " ~
435 			   "(e.g. settings aren't reloaded). Instantiate a new `Dub` instead")
436 	@property void rootPath(NativePath root_path)
437 	{
438 		m_rootPath = root_path;
439 		if (!m_rootPath.absolute) m_rootPath = getWorkingDirectory() ~ m_rootPath;
440 	}
441 
442 	/// Returns the name listed in the dub.json of the current
443 	/// application.
444 	@property string projectName() const { return m_project.name; }
445 
446 	@property string mainRecipePath() const { return m_mainRecipePath; }
447 	/// Whenever the switch --recipe= is supplied, this member will be populated.
448 	@property string mainRecipePath(string recipePath)
449 	{
450 		return m_mainRecipePath = recipePath;
451 	}
452 
453 
454 	@property NativePath projectPath() const { return this.m_project.rootPackage.path; }
455 
456 	@property string[] configurations() const { return m_project.configurations; }
457 
458 	@property inout(PackageManager) packageManager() inout { return m_packageManager; }
459 
460 	@property inout(Project) project() inout { return m_project; }
461 
462 	@property inout(PackageSupplier)[] packageSuppliers() inout { return m_packageSuppliers; }
463 
464 	/** Returns the default compiler binary to use for building D code.
465 
466 		If set, the "defaultCompiler" field of the DUB user or system
467 		configuration file will be used. Otherwise the PATH environment variable
468 		will be searched for files named "dmd", "gdc", "gdmd", "ldc2", "ldmd2"
469 		(in that order, taking into account operating system specific file
470 		extensions) and the first match is returned. If no match is found, "dmd"
471 		will be used.
472 	*/
473 	@property string defaultCompiler() const { return m_defaultCompiler; }
474 
475 	/** Returns the default architecture to use for building D code.
476 
477 		If set, the "defaultArchitecture" field of the DUB user or system
478 		configuration file will be used. Otherwise null will be returned.
479 	*/
480 	@property string defaultArchitecture() const { return this.m_config.defaultArchitecture; }
481 
482 	/** Returns the default low memory option to use for building D code.
483 
484 		If set, the "defaultLowMemory" field of the DUB user or system
485 		configuration file will be used. Otherwise false will be returned.
486 	*/
487 	@property bool defaultLowMemory() const { return this.m_config.defaultLowMemory; }
488 
489 	@property const(string[string]) defaultEnvironments() const { return this.m_config.defaultEnvironments; }
490 	@property const(string[string]) defaultBuildEnvironments() const { return this.m_config.defaultBuildEnvironments; }
491 	@property const(string[string]) defaultRunEnvironments() const { return this.m_config.defaultRunEnvironments; }
492 	@property const(string[string]) defaultPreGenerateEnvironments() const { return this.m_config.defaultPreGenerateEnvironments; }
493 	@property const(string[string]) defaultPostGenerateEnvironments() const { return this.m_config.defaultPostGenerateEnvironments; }
494 	@property const(string[string]) defaultPreBuildEnvironments() const { return this.m_config.defaultPreBuildEnvironments; }
495 	@property const(string[string]) defaultPostBuildEnvironments() const { return this.m_config.defaultPostBuildEnvironments; }
496 	@property const(string[string]) defaultPreRunEnvironments() const { return this.m_config.defaultPreRunEnvironments; }
497 	@property const(string[string]) defaultPostRunEnvironments() const { return this.m_config.defaultPostRunEnvironments; }
498 
499 	/** Loads the package that resides within the configured `rootPath`.
500 	*/
501 	void loadPackage()
502 	{
503 		loadPackage(m_rootPath);
504 	}
505 
506 	/// Loads the package from the specified path as the main project package.
507 	void loadPackage(NativePath path)
508 	{
509 		auto pack = this.m_packageManager.getOrLoadPackage(
510 			path, NativePath.init, false, StrictMode.Warn);
511 		this.loadPackage(pack);
512 	}
513 
514 	/// Loads a specific package as the main project package (can be a sub package)
515 	void loadPackage(Package pack)
516 	{
517 		auto selections = Project.loadSelections(pack);
518 		m_project = new Project(m_packageManager, pack, selections);
519 	}
520 
521 	/** Loads a single file package.
522 
523 		Single-file packages are D files that contain a package recipe comment
524 		at their top. A recipe comment must be a nested `/+ ... +/` style
525 		comment, containing the virtual recipe file name and a colon, followed by the
526 		recipe contents (what would normally be in dub.sdl/dub.json).
527 
528 		Example:
529 		---
530 		/+ dub.sdl:
531 		   name "test"
532 		   dependency "vibe-d" version="~>0.7.29"
533 		+/
534 		import vibe.http.server;
535 
536 		void main()
537 		{
538 			auto settings = new HTTPServerSettings;
539 			settings.port = 8080;
540 			listenHTTP(settings, &hello);
541 		}
542 
543 		void hello(HTTPServerRequest req, HTTPServerResponse res)
544 		{
545 			res.writeBody("Hello, World!");
546 		}
547 		---
548 
549 		The script above can be invoked with "dub --single test.d".
550 	*/
551 	void loadSingleFilePackage(NativePath path)
552 	{
553 		import dub.recipe.io : parsePackageRecipe;
554 		import std.file : readText;
555 		import std.path : baseName, stripExtension;
556 
557 		path = makeAbsolute(path);
558 
559 		string file_content = readText(path.toNativeString());
560 
561 		if (file_content.startsWith("#!")) {
562 			auto idx = file_content.indexOf('\n');
563 			enforce(idx > 0, "The source fine doesn't contain anything but a shebang line.");
564 			file_content = file_content[idx+1 .. $];
565 		}
566 
567 		file_content = file_content.strip();
568 
569 		string recipe_content;
570 
571 		if (file_content.startsWith("/+")) {
572 			file_content = file_content[2 .. $];
573 			auto idx = file_content.indexOf("+/");
574 			enforce(idx >= 0, "Missing \"+/\" to close comment.");
575 			recipe_content = file_content[0 .. idx].strip();
576 		} else throw new Exception("The source file must start with a recipe comment.");
577 
578 		auto nidx = recipe_content.indexOf('\n');
579 
580 		auto idx = recipe_content.indexOf(':');
581 		enforce(idx > 0 && (nidx < 0 || nidx > idx),
582 			"The first line of the recipe comment must list the recipe file name followed by a colon (e.g. \"/+ dub.sdl:\").");
583 		auto recipe_filename = recipe_content[0 .. idx];
584 		recipe_content = recipe_content[idx+1 .. $];
585 		auto recipe_default_package_name = path.toString.baseName.stripExtension.strip;
586 
587 		const PackageName empty;
588 		auto recipe = parsePackageRecipe(recipe_content, recipe_filename, empty, recipe_default_package_name);
589 		enforce(recipe.buildSettings.sourceFiles.length == 0, "Single-file packages are not allowed to specify source files.");
590 		enforce(recipe.buildSettings.sourcePaths.length == 0, "Single-file packages are not allowed to specify source paths.");
591 		enforce(recipe.buildSettings.cSourcePaths.length == 0, "Single-file packages are not allowed to specify C source paths.");
592 		enforce(recipe.buildSettings.importPaths.length == 0, "Single-file packages are not allowed to specify import paths.");
593 		enforce(recipe.buildSettings.cImportPaths.length == 0, "Single-file packages are not allowed to specify C import paths.");
594 		recipe.buildSettings.sourceFiles[""] = [path.toNativeString()];
595 		recipe.buildSettings.sourcePaths[""] = [];
596 		recipe.buildSettings.cSourcePaths[""] = [];
597 		recipe.buildSettings.importPaths[""] = [];
598 		recipe.buildSettings.cImportPaths[""] = [];
599 		recipe.buildSettings.mainSourceFile = path.toNativeString();
600 		if (recipe.buildSettings.targetType == TargetType.autodetect)
601 			recipe.buildSettings.targetType = TargetType.executable;
602 
603 		auto pack = new Package(recipe, path.parentPath, null, "~master");
604 		loadPackage(pack);
605 	}
606 	/// ditto
607 	void loadSingleFilePackage(string path)
608 	{
609 		loadSingleFilePackage(NativePath(path));
610 	}
611 
612 	/** Gets the default configuration for a particular build platform.
613 
614 		This forwards to `Project.getDefaultConfiguration` and requires a
615 		project to be loaded.
616 	*/
617 	string getDefaultConfiguration(in BuildPlatform platform, bool allow_non_library_configs = true) const { return m_project.getDefaultConfiguration(platform, allow_non_library_configs); }
618 
619 	/** Attempts to upgrade the dependency selection of the loaded project.
620 
621 		Params:
622 			options = Flags that control how the upgrade is carried out
623 			packages_to_upgrade = Optional list of packages. If this list
624 				contains one or more packages, only those packages will
625 				be upgraded. Otherwise, all packages will be upgraded at
626 				once.
627 	*/
628 	void upgrade(UpgradeOptions options, string[] packages_to_upgrade = null)
629 	{
630 		// clear non-existent version selections
631 		if (!(options & UpgradeOptions.upgrade)) {
632 			next_pack:
633 			foreach (p; m_project.selections.selectedPackages) {
634 				const name = PackageName(p); // Always a main package name
635 				auto dep = m_project.selections.getSelectedVersion(name);
636 				if (!dep.path.empty) {
637 					auto path = dep.path;
638 					if (!path.absolute) path = this.rootPath ~ path;
639 					try if (m_packageManager.getOrLoadPackage(path)) continue;
640 					catch (Exception e) { logDebug("Failed to load path based selection: %s", e.toString().sanitize); }
641 				} else if (!dep.repository.empty) {
642 					if (m_packageManager.loadSCMPackage(name, dep.repository))
643 						continue;
644 				} else {
645 					if (m_packageManager.getPackage(name, dep.version_)) continue;
646 					foreach (ps; m_packageSuppliers) {
647 						try {
648 							auto versions = ps.getVersions(name);
649 							if (versions.canFind!(v => dep.matches(v, VersionMatchMode.strict)))
650 								continue next_pack;
651 						} catch (Exception e) {
652 							logWarn("Error querying versions for %s, %s: %s", p, ps.description, e.msg);
653 							logDebug("Full error: %s", e.toString().sanitize());
654 						}
655 					}
656 				}
657 
658 				logWarn("Selected package %s %s doesn't exist. Using latest matching version instead.", p, dep);
659 				m_project.selections.deselectVersion(name);
660 			}
661 		}
662 
663 		auto resolver = new DependencyVersionResolver(
664 			this, options, m_project.rootPackage, m_project.selections);
665 		Dependency[PackageName] versions = resolver.resolve(packages_to_upgrade);
666 
667 		if (options & UpgradeOptions.dryRun) {
668 			bool any = false;
669 			string rootbasename = PackageName(m_project.rootPackage.name).main.toString();
670 
671 			foreach (p, ver; versions) {
672 				if (!ver.path.empty || !ver.repository.empty) continue;
673 
674 				auto basename = p.main;
675 				if (basename.toString() == rootbasename) continue;
676 
677 				if (!m_project.selections.hasSelectedVersion(basename)) {
678 					logInfo("Upgrade", Color.cyan,
679 						"Package %s would be selected with version %s", basename, ver);
680 					any = true;
681 					continue;
682 				}
683 				auto sver = m_project.selections.getSelectedVersion(basename);
684 				if (!sver.path.empty || !sver.repository.empty) continue;
685 				if (ver.version_ <= sver.version_) continue;
686 				logInfo("Upgrade", Color.cyan,
687 					"%s would be upgraded from %s to %s.",
688 					basename.toString().color(Mode.bold), sver, ver);
689 				any = true;
690 			}
691 			if (any) logInfo("Use \"%s\" to perform those changes", "dub upgrade".color(Mode.bold));
692 			return;
693 		}
694 
695 		foreach (name, ver; versions) {
696 			assert(!name.sub, "Resolved packages contain a sub package!?: " ~ name.toString());
697 			Package pack;
698 			if (!ver.path.empty) {
699 				try pack = m_packageManager.getOrLoadPackage(ver.path);
700 				catch (Exception e) {
701 					logDebug("Failed to load path based selection: %s", e.toString().sanitize);
702 					continue;
703 				}
704 			} else if (!ver.repository.empty) {
705 				pack = m_packageManager.loadSCMPackage(name, ver.repository);
706 			} else {
707 				assert(ver.isExactVersion, "Resolved dependency is neither path, nor repository, nor exact version based!?");
708 				pack = m_packageManager.getPackage(name, ver.version_);
709 				if (pack && m_packageManager.isManagedPackage(pack)
710 					&& ver.version_.isBranch && (options & UpgradeOptions.upgrade) != 0)
711 				{
712 					// TODO: only re-install if there is actually a new commit available
713 					logInfo("Re-installing branch based dependency %s %s", name, ver.toString());
714 					m_packageManager.remove(pack);
715 					pack = null;
716 				}
717 			}
718 
719 			FetchOptions fetchOpts;
720 			fetchOpts |= (options & UpgradeOptions.preRelease) != 0 ? FetchOptions.usePrerelease : FetchOptions.none;
721 			if (!pack) this.fetch(name, ver.version_, fetchOpts, defaultPlacementLocation, "getting selected version");
722 			if ((options & UpgradeOptions.select) && name.toString() != m_project.rootPackage.name) {
723 				if (!ver.repository.empty) {
724 					m_project.selections.selectVersion(name, ver.repository);
725 				} else if (ver.path.empty) {
726 					m_project.selections.selectVersion(name, ver.version_);
727 				} else {
728 					NativePath relpath = ver.path;
729 					if (relpath.absolute) relpath = relpath.relativeTo(m_project.rootPackage.path);
730 					m_project.selections.selectVersion(name, relpath);
731 				}
732 			}
733 		}
734 
735 		string[] missingDependenciesBeforeReinit = m_project.missingDependencies;
736 		m_project.reinit();
737 
738 		if (!m_project.hasAllDependencies) {
739 			auto resolvedDependencies = setDifference(
740 					assumeSorted(missingDependenciesBeforeReinit),
741 					assumeSorted(m_project.missingDependencies)
742 				);
743 			if (!resolvedDependencies.empty)
744 				upgrade(options, m_project.missingDependencies);
745 		}
746 
747 		if ((options & UpgradeOptions.select) && !(options & (UpgradeOptions.noSaveSelections | UpgradeOptions.dryRun)))
748 			m_project.saveSelections();
749 	}
750 
751 	/** Generate project files for a specified generator.
752 
753 		Any existing project files will be overridden.
754 	*/
755 	void generateProject(string ide, GeneratorSettings settings)
756 	{
757 		settings.cache = this.m_dirs.cache;
758 		if (settings.overrideToolWorkingDirectory is NativePath.init)
759 			settings.overrideToolWorkingDirectory = m_rootPath;
760 		// With a requested `unittest` config, switch to the special test runner
761 		// config (which doesn't require an existing `unittest` configuration).
762 		if (settings.config == "unittest") {
763 			const test_config = m_project.addTestRunnerConfiguration(settings, !m_dryRun);
764 			if (test_config) settings.config = test_config;
765 		}
766 
767 		auto generator = createProjectGenerator(ide, m_project);
768 		if (m_dryRun) return; // TODO: pass m_dryRun to the generator
769 		generator.generate(settings);
770 	}
771 
772 	/** Generate project files using the special test runner (`dub test`) configuration.
773 
774 		Any existing project files will be overridden.
775 	*/
776 	void testProject(GeneratorSettings settings, string config, NativePath custom_main_file)
777 	{
778 		settings.cache = this.m_dirs.cache;
779 		if (settings.overrideToolWorkingDirectory is NativePath.init)
780 			settings.overrideToolWorkingDirectory = m_rootPath;
781 		if (!custom_main_file.empty && !custom_main_file.absolute) custom_main_file = m_rootPath ~ custom_main_file;
782 
783 		const test_config = m_project.addTestRunnerConfiguration(settings, !m_dryRun, config, custom_main_file);
784 		if (!test_config) return; // target type "none"
785 
786 		settings.config = test_config;
787 
788 		auto generator = createProjectGenerator("build", m_project);
789 		generator.generate(settings);
790 	}
791 
792 	/** Executes D-Scanner tests on the current project. **/
793 	void lintProject(string[] args)
794 	{
795 		if (m_dryRun) return;
796 
797 		auto tool = PackageName("dscanner");
798 
799 		auto tool_pack = m_packageManager.getBestPackage(tool);
800 		if (!tool_pack) {
801 			logInfo("Hint", Color.light_blue, "%s is not present, getting and storing it locally", tool);
802 			tool_pack = this.fetch(tool);
803 		}
804 
805 		auto dscanner_dub = new Dub(null, m_packageSuppliers);
806 		dscanner_dub.loadPackage(tool_pack);
807 		dscanner_dub.upgrade(UpgradeOptions.select);
808 
809 		GeneratorSettings settings = this.makeAppSettings();
810 		foreach (dependencyPackage; m_project.dependencies)
811 		{
812 			auto cfgs = m_project.getPackageConfigs(settings.platform, null, true);
813 			auto buildSettings = dependencyPackage.getBuildSettings(settings.platform, cfgs[dependencyPackage.name]);
814 			foreach (importPath; buildSettings.importPaths) {
815 				settings.runArgs ~= ["-I", (dependencyPackage.path ~ importPath).toNativeString()];
816 			}
817 			foreach (cimportPath; buildSettings.cImportPaths) {
818 				settings.runArgs ~= ["-I", (dependencyPackage.path ~ cimportPath).toNativeString()];
819 			}
820 		}
821 
822 		string configFilePath = (m_project.rootPackage.path ~ "dscanner.ini").toNativeString();
823 		if (!args.canFind("--config") && exists(configFilePath)) {
824 			settings.runArgs ~= ["--config", configFilePath];
825 		}
826 
827 		settings.runArgs ~= args ~ [m_project.rootPackage.path.toNativeString()];
828 		dscanner_dub.generateProject("build", settings);
829 	}
830 
831 	/** Prints the specified build settings necessary for building the root package.
832 	*/
833 	void listProjectData(GeneratorSettings settings, string[] requestedData, ListBuildSettingsFormat list_type)
834 	{
835 		import std.stdio;
836 		import std.ascii : newline;
837 
838 		if (settings.overrideToolWorkingDirectory is NativePath.init)
839 			settings.overrideToolWorkingDirectory = m_rootPath;
840 
841 		// Split comma-separated lists
842 		string[] requestedDataSplit =
843 			requestedData
844 			.map!(a => a.splitter(",").map!strip)
845 			.joiner()
846 			.array();
847 
848 		auto data = m_project.listBuildSettings(settings, requestedDataSplit, list_type);
849 
850 		string delimiter;
851 		final switch (list_type) with (ListBuildSettingsFormat) {
852 			case list: delimiter = newline ~ newline; break;
853 			case listNul: delimiter = "\0\0"; break;
854 			case commandLine: delimiter = " "; break;
855 			case commandLineNul: delimiter = "\0\0"; break;
856 		}
857 
858 		write(data.joiner(delimiter));
859 		if (delimiter != "\0\0") writeln();
860 	}
861 
862 	/// Cleans intermediate/cache files of the given package (or all packages)
863 	deprecated("Use `clean(Package)` instead")
864 	void cleanPackage(NativePath path)
865 	{
866 		this.clean(Package.load(path));
867 	}
868 
869 	/// Ditto
870 	void clean()
871 	{
872 		const cache = this.m_dirs.cache;
873 		logInfo("Cleaning", Color.green, "all artifacts at %s",
874 			cache.toNativeString().color(Mode.bold));
875 		if (existsFile(cache))
876 			rmdirRecurse(cache.toNativeString());
877 	}
878 
879 	/// Ditto
880 	void clean(Package pack)
881 	{
882 		const cache = this.packageCache(pack);
883 		logInfo("Cleaning", Color.green, "artifacts for package %s at %s",
884 			pack.name.color(Mode.bold),
885 			cache.toNativeString().color(Mode.bold));
886 
887 		// TODO: clear target files and copy files
888 		if (existsFile(cache))
889 			rmdirRecurse(cache.toNativeString());
890 	}
891 
892 	deprecated("Use the overload that accepts either a `Version` or a `VersionRange` as second argument")
893 	Package fetch(string packageId, const Dependency dep, PlacementLocation location, FetchOptions options, string reason = "")
894 	{
895 		const vrange = dep.visit!(
896 			(VersionRange range) => range,
897 			function VersionRange (any) { throw new Exception("Cannot call `dub.fetch` with a " ~ typeof(any).stringof ~ " dependency"); }
898 		);
899 		return this.fetch(packageId, vrange, location, options, reason);
900 	}
901 
902 	deprecated("Use `fetch(PackageName, Version, [FetchOptions, PlacementLocation, string])`")
903 	Package fetch(string name, in Version vers, PlacementLocation location, FetchOptions options, string reason = "")
904 	{
905 		const n = PackageName(name);
906 		return this.fetch(n, VersionRange(vers, vers), options, location, reason);
907 	}
908 
909 	deprecated("Use `fetch(PackageName, VersionRange, [FetchOptions, PlacementLocation, string])`")
910 	Package fetch(string name, in VersionRange range, PlacementLocation location, FetchOptions options, string reason = "")
911 	{
912 		const n = PackageName(name);
913 		return this.fetch(n, range, options, location, reason);
914 	}
915 
916 	/**
917 	 * Fetches a missing package and stores it locally
918 	 *
919 	 * This will query the configured PackageSuppliers for a package
920 	 * matching the `range` specification, store it locally, and load
921 	 * it in the `PackageManager`. Note that unlike the command line
922 	 * version, this function is not idempotent and will remove an
923 	 * existing package and re-download it.
924 	 *
925 	 * Params:
926 	 *   name = Name of the package to retrieve. Subpackages will lead
927 	 *          to the main package being retrieved and the subpackage
928 	 *          being returned (if it exists).
929 	 *   vers = For `Version` overloads, the exact version to return.
930 	 *   range = The `VersionRange` to match. Default to `Any` to fetch
931 	 *           the latest version.
932 	 *   options = A set of options used for fetching / matching versions.
933 	 *   location = Where to store the retrieved package. Default to the
934 	 *              configured `defaultPlacementLocation`.
935 	 *   reason = Optionally, the reason for retriving this package.
936 	 *            This is used only for logging.
937 	 *
938 	 * Returns:
939 	 *	 The fetched or loaded `Package`, or `null` in dry-run mode.
940 	 *
941 	 * Throws:
942 	 *	 If the package cannot be fetched or loaded.
943 	 */
944 	Package fetch(in PackageName name, in Version vers,
945 		FetchOptions options = FetchOptions.none, string reason = "")
946 	{
947 		return this.fetch(name, VersionRange(vers, vers), options,
948 			this.defaultPlacementLocation, reason);
949 	}
950 
951 	/// Ditto
952 	Package fetch(in PackageName name, in Version vers, FetchOptions options,
953 		PlacementLocation location, string reason = "")
954 	{
955 		return this.fetch(name, VersionRange(vers, vers), options,
956 			this.defaultPlacementLocation, reason);
957 	}
958 
959 	/// Ditto
960 	Package fetch(in PackageName name, in VersionRange range = VersionRange.Any,
961 		FetchOptions options = FetchOptions.none, string reason = "")
962 	{
963 		return this.fetch(name, range, options, this.defaultPlacementLocation, reason);
964 	}
965 
966 	/// Ditto
967 	Package fetch(in PackageName name, in VersionRange range, FetchOptions options,
968 		PlacementLocation location, string reason = "")
969 	{
970 		Json pinfo;
971 		PackageSupplier supplier;
972 		foreach(ps; m_packageSuppliers){
973 			try {
974 				pinfo = ps.fetchPackageRecipe(name.main, range, (options & FetchOptions.usePrerelease) != 0);
975 				if (pinfo.type == Json.Type.null_)
976 					continue;
977 				supplier = ps;
978 				break;
979 			} catch(Exception e) {
980 				logWarn("Package %s not found for %s: %s", name, ps.description, e.msg);
981 				logDebug("Full error: %s", e.toString().sanitize());
982 			}
983 		}
984 		enforce(!pinfo.type.among(Json.Type.undefined, Json.Type.null_),
985 			"No package %s was found matching the dependency %s"
986 				.format(name, range));
987 		Version ver = Version(pinfo["version"].get!string);
988 
989 		// always upgrade branch based versions - TODO: actually check if there is a new commit available
990 		Package existing = m_packageManager.getPackage(name, ver, location);
991 		if (options & FetchOptions.printOnly) {
992 			if (existing && existing.version_ != ver)
993 				logInfo("A new version for %s is available (%s -> %s). Run \"%s\" to switch.",
994                     name.toString().color(Mode.bold), existing, ver,
995 					text("dub upgrade ", name.main).color(Mode.bold));
996 			return null;
997 		}
998 
999 		if (existing) {
1000 			if (!ver.isBranch() || !(options & FetchOptions.forceBranchUpgrade) || location == PlacementLocation.local) {
1001 				// TODO: support git working trees by performing a "git pull" instead of this
1002 				logDiagnostic("Package %s %s (in %s packages) is already present with the latest version, skipping upgrade.",
1003 					name, ver, location.toString);
1004 				return existing;
1005 			} else {
1006 				logInfo("Removing", Color.yellow, "%s %s to prepare " ~
1007 					"replacement with a new version", name.toString().color(Mode.bold),
1008 					ver);
1009 				if (!m_dryRun) m_packageManager.remove(existing);
1010 			}
1011 		}
1012 
1013 		if (reason.length) logInfo("Fetching", Color.yellow, "%s %s (%s)", name.toString().color(Mode.bold), ver, reason);
1014 		else logInfo("Fetching", Color.yellow, "%s %s", name.toString().color(Mode.bold), ver);
1015 		if (m_dryRun) return null;
1016 
1017 		logDebug("Acquiring package zip file");
1018 
1019 		// repeat download on corrupted zips, see #1336
1020 		foreach_reverse (i; 0..3)
1021 		{
1022 			import std.zip : ZipException;
1023 
1024 			auto data = supplier.fetchPackage(name.main, range, (options & FetchOptions.usePrerelease) != 0); // Q: continue on fail?
1025 			logDiagnostic("Placing to %s...", location.toString());
1026 
1027 			try {
1028 				return m_packageManager.store(data, location, name.main, ver);
1029 			} catch (ZipException e) {
1030 				logInfo("Failed to extract zip archive for %s@%s...", name, ver);
1031 				// re-throw the exception at the end of the loop
1032 				if (i == 0)
1033 					throw e;
1034 			}
1035 		}
1036 		assert(0, "Should throw a ZipException instead.");
1037 	}
1038 
1039 	/** Removes a specific locally cached package.
1040 
1041 		This will delete the package files from disk and removes the
1042 		corresponding entry from the list of known packages.
1043 
1044 		Params:
1045 			pack = Package instance to remove
1046 	*/
1047 	void remove(in Package pack)
1048 	{
1049 		logInfo("Removing", Color.yellow, "%s (in %s)", pack.name.color(Mode.bold), pack.path.toNativeString());
1050 		if (!m_dryRun) m_packageManager.remove(pack);
1051 	}
1052 
1053 	/// Compatibility overload. Use the version without a `force_remove` argument instead.
1054 	deprecated("Use `remove(pack)` directly instead, the boolean has no effect")
1055 	void remove(in Package pack, bool force_remove)
1056 	{
1057 		remove(pack);
1058 	}
1059 
1060 	/// @see remove(string, string, RemoveLocation)
1061 	enum RemoveVersionWildcard = "*";
1062 
1063 	/** Removes one or more versions of a locally cached package.
1064 
1065 		This will remove a given package with a specified version from the
1066 		given location. It will remove at most one package, unless `version_`
1067 		is set to `RemoveVersionWildcard`.
1068 
1069 		Params:
1070 			name = Name of the package to be removed
1071 			location = Specifies the location to look for the given package
1072 				name/version.
1073 			resolve_version = Callback to select package version.
1074 	*/
1075 	void remove(in PackageName name, PlacementLocation location,
1076 		scope size_t delegate(in Package[] packages) resolve_version)
1077 	{
1078 		enforce(name.main.toString().length);
1079 		enforce(!name.sub.length, "Cannot remove subpackage %s, remove %s instead"
1080 			.format(name, name.main));
1081 		if (location == PlacementLocation.local) {
1082 			logInfo("To remove a locally placed package, make sure you don't have any data"
1083 					~ "\nleft in it's directory and then simply remove the whole directory.");
1084 			throw new Exception("dub cannot remove locally installed packages.");
1085 		}
1086 
1087 		Package[] packages;
1088 
1089 		// Retrieve packages to be removed.
1090 		foreach(pack; m_packageManager.getPackageIterator(name.toString()))
1091 			if (m_packageManager.isManagedPackage(pack))
1092 				packages ~= pack;
1093 
1094 		// Check validity of packages to be removed.
1095 		enforce(!packages.empty, "Cannot find package '%s' to remove at %s location"
1096 			.format(name, location.toString()));
1097 
1098 		// Sort package list in ascending version order
1099 		packages.sort!((a, b) => a.version_ < b.version_);
1100 
1101 		immutable idx = resolve_version(packages);
1102 		if (idx == size_t.max)
1103 			return;
1104 		else if (idx != packages.length)
1105 			packages = packages[idx .. idx + 1];
1106 
1107 		logDebug("Removing %s packages.", packages.length);
1108 		foreach(pack; packages) {
1109 			try {
1110 				remove(pack);
1111 			} catch (Exception e) {
1112 				logError("Failed to remove %s %s: %s", name, pack, e.msg);
1113 				logInfo("Continuing with other packages (if any).");
1114 			}
1115 		}
1116 	}
1117 
1118 	deprecated("Use `remove(PackageName, PlacementLocation, delegate)`")
1119 	void remove(string name, PlacementLocation location,
1120 		scope size_t delegate(in Package[] packages) resolve_version)
1121 	{
1122 		this.remove(PackageName(name), location, resolve_version);
1123 	}
1124 
1125 	/// Compatibility overload. Use the version without a `force_remove` argument instead.
1126 	deprecated("Use the overload without the 3rd argument (`force_remove`) instead")
1127 	void remove(string package_id, PlacementLocation location, bool force_remove,
1128 				scope size_t delegate(in Package[] packages) resolve_version)
1129 	{
1130 		remove(package_id, location, resolve_version);
1131 	}
1132 
1133 	/** Removes a specific version of a package.
1134 
1135 		Params:
1136 			name = Name of the package to be removed
1137 			version_ = Identifying a version or a wild card. If an empty string
1138 				is passed, the package will be removed from the location, if
1139 				there is only one version retrieved. This will throw an
1140 				exception, if there are multiple versions retrieved.
1141 			location = Specifies the location to look for the given package
1142 				name/version.
1143 	 */
1144 	void remove(in PackageName name, string version_, PlacementLocation location)
1145 	{
1146 		remove(name, location, (in packages) {
1147 			if (version_ == RemoveVersionWildcard || version_.empty)
1148 				return packages.length;
1149 
1150 			foreach (i, p; packages) {
1151 				if (p.version_ == Version(version_))
1152 					return i;
1153 			}
1154 			throw new Exception("Cannot find package '%s@%s' to remove at %s location"
1155 				.format(name, version_, location.toString()));
1156 		});
1157 	}
1158 
1159 	deprecated("Use `remove(PackageName, string, PlacementLocation)`")
1160 	void remove(string name, string version_, PlacementLocation location)
1161 	{
1162 		this.remove(PackageName(name), version_, location);
1163 	}
1164 
1165 	/// Compatibility overload. Use the version without a `force_remove` argument instead.
1166 	deprecated("Use the overload without force_remove instead")
1167 	void remove(string package_id, string version_, PlacementLocation location, bool force_remove)
1168 	{
1169 		remove(package_id, version_, location);
1170 	}
1171 
1172 	/** Adds a directory to the list of locally known packages.
1173 
1174 		Forwards to `PackageManager.addLocalPackage`.
1175 
1176 		Params:
1177 			path = Path to the package
1178 			ver = Optional version to associate with the package (can be left
1179 				empty)
1180 			system = Make the package known system wide instead of user wide
1181 				(requires administrator privileges).
1182 
1183 		See_Also: `removeLocalPackage`
1184 	*/
1185 	deprecated("Use `addLocalPackage(string, string, PlacementLocation)` instead")
1186 	void addLocalPackage(string path, string ver, bool system)
1187 	{
1188 		this.addLocalPackage(path, ver, system ? PlacementLocation.system : PlacementLocation.user);
1189 	}
1190 
1191 	/// Ditto
1192 	void addLocalPackage(string path, string ver, PlacementLocation loc)
1193 	{
1194 		if (m_dryRun) return;
1195 		this.m_packageManager.addLocalPackage(makeAbsolute(path), ver, loc);
1196 	}
1197 
1198 	/** Removes a directory from the list of locally known packages.
1199 
1200 		Forwards to `PackageManager.removeLocalPackage`.
1201 
1202 		Params:
1203 			path = Path to the package
1204 			system = Make the package known system wide instead of user wide
1205 				(requires administrator privileges).
1206 
1207 		See_Also: `addLocalPackage`
1208 	*/
1209 	deprecated("Use `removeLocalPackage(string, string, PlacementLocation)` instead")
1210 	void removeLocalPackage(string path, bool system)
1211 	{
1212 		this.removeLocalPackage(path, system ? PlacementLocation.system : PlacementLocation.user);
1213 	}
1214 
1215 	/// Ditto
1216 	void removeLocalPackage(string path, PlacementLocation loc)
1217 	{
1218 		if (m_dryRun) return;
1219 		this.m_packageManager.removeLocalPackage(makeAbsolute(path), loc);
1220 	}
1221 
1222 	/** Registers a local directory to search for packages to use for satisfying
1223 		dependencies.
1224 
1225 		Params:
1226 			path = Path to a directory containing package directories
1227 			system = Make the package known system wide instead of user wide
1228 				(requires administrator privileges).
1229 
1230 		See_Also: `removeSearchPath`
1231 	*/
1232 	deprecated("Use `addSearchPath(string, PlacementLocation)` instead")
1233 	void addSearchPath(string path, bool system)
1234 	{
1235 		this.addSearchPath(path, system ? PlacementLocation.system : PlacementLocation.user);
1236 	}
1237 
1238 	/// Ditto
1239 	void addSearchPath(string path, PlacementLocation loc)
1240 	{
1241 		if (m_dryRun) return;
1242 		this.m_packageManager.addSearchPath(makeAbsolute(path), loc);
1243 	}
1244 
1245 	/** Deregisters a local directory search path.
1246 
1247 		Params:
1248 			path = Path to a directory containing package directories
1249 			system = Make the package known system wide instead of user wide
1250 				(requires administrator privileges).
1251 
1252 		See_Also: `addSearchPath`
1253 	*/
1254 	deprecated("Use `removeSearchPath(string, PlacementLocation)` instead")
1255 	void removeSearchPath(string path, bool system)
1256 	{
1257 		this.removeSearchPath(path, system ? PlacementLocation.system : PlacementLocation.user);
1258 	}
1259 
1260 	/// Ditto
1261 	void removeSearchPath(string path, PlacementLocation loc)
1262 	{
1263 		if (m_dryRun) return;
1264 		this.m_packageManager.removeSearchPath(makeAbsolute(path), loc);
1265 	}
1266 
1267 	/** Queries all package suppliers with the given query string.
1268 
1269 		Returns a list of tuples, where the first entry is the human readable
1270 		name of the package supplier and the second entry is the list of
1271 		matched packages.
1272 
1273 		Params:
1274 		  query = the search term to match packages on
1275 
1276 		See_Also: `PackageSupplier.searchPackages`
1277 	*/
1278 	auto searchPackages(string query)
1279 	{
1280 		import std.typecons : Tuple, tuple;
1281 		Tuple!(string, PackageSupplier.SearchResult[])[] results;
1282 		foreach (ps; this.m_packageSuppliers) {
1283 			try
1284 				results ~= tuple(ps.description, ps.searchPackages(query));
1285 			catch (Exception e) {
1286 				logWarn("Searching %s for '%s' failed: %s", ps.description, query, e.msg);
1287 			}
1288 		}
1289 		return results.filter!(tup => tup[1].length);
1290 	}
1291 
1292 	/** Returns a list of all available versions (including branches) for a
1293 		particular package.
1294 
1295 		The list returned is based on the registered package suppliers. Local
1296 		packages are not queried in the search for versions.
1297 
1298 		See_also: `getLatestVersion`
1299 	*/
1300 	Version[] listPackageVersions(in PackageName name)
1301 	{
1302 		Version[] versions;
1303 		foreach (ps; this.m_packageSuppliers) {
1304 			try versions ~= ps.getVersions(name);
1305 			catch (Exception e) {
1306 				logWarn("Failed to get versions for package %s on provider %s: %s", name, ps.description, e.msg);
1307 			}
1308 		}
1309 		return versions.sort().uniq.array;
1310 	}
1311 
1312 	deprecated("Use `listPackageVersions(PackageName)`")
1313 	Version[] listPackageVersions(string name)
1314 	{
1315 		const n = PackageName(name);
1316 		return this.listPackageVersions(n);
1317 	}
1318 
1319 	/** Returns the latest available version for a particular package.
1320 
1321 		This function returns the latest numbered version of a package. If no
1322 		numbered versions are available, it will return an available branch,
1323 		preferring "~master".
1324 
1325 		Params:
1326 			name = The name of the package in question.
1327 			prefer_stable = If set to `true` (the default), returns the latest
1328 				stable version, even if there are newer pre-release versions.
1329 
1330 		See_also: `listPackageVersions`
1331 	*/
1332 	Version getLatestVersion(in PackageName name, bool prefer_stable = true)
1333 	{
1334 		auto vers = this.listPackageVersions(name);
1335 		enforce(!vers.empty,
1336 			"Failed to find any valid versions for a package name of '%s'."
1337 			.format(name));
1338 		auto final_versions = vers.filter!(v => !v.isBranch && !v.isPreRelease).array;
1339 		if (prefer_stable && final_versions.length) return final_versions[$-1];
1340 		else return vers[$-1];
1341 	}
1342 
1343 	deprecated("Use `getLatestVersion(PackageName, bool)`")
1344 	Version getLatestVersion(string name, bool prefer_stable = true)
1345 	{
1346 		const n = PackageName(name);
1347 		return this.getLatestVersion(n, prefer_stable);
1348 	}
1349 
1350 	/** Initializes a directory with a package skeleton.
1351 
1352 		Params:
1353 			path = Path of the directory to create the new package in. The
1354 				directory will be created if it doesn't exist.
1355 			deps = List of dependencies to add to the package recipe.
1356 			type = Specifies the type of the application skeleton to use.
1357 			format = Determines the package recipe format to use.
1358 			recipe_callback = Optional callback that can be used to
1359 				customize the recipe before it gets written.
1360 			app_args = Arguments to provide to the custom initialization routine.
1361 	*/
1362 	void createEmptyPackage(NativePath path, string[] deps, string type,
1363 		PackageFormat format = PackageFormat.sdl,
1364 		scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null,
1365 		string[] app_args = [])
1366 	{
1367 		if (!path.absolute) path = m_rootPath ~ path;
1368 		path.normalize();
1369 
1370 		VersionRange[string] depVers;
1371 		string[] notFound; // keep track of any failed packages in here
1372 		foreach (dep; deps) {
1373 			const name = PackageName(dep);
1374 			try {
1375 				Version ver = this.getLatestVersion(name);
1376 				if (ver.isBranch())
1377 					depVers[dep] = VersionRange(ver);
1378 				else
1379 					depVers[dep] = VersionRange.fromString("~>" ~ ver.toString());
1380 			} catch (Exception e) {
1381 				notFound ~= dep;
1382 			}
1383 		}
1384 
1385 		if(notFound.length > 1){
1386 			throw new Exception(.format("Couldn't find packages: %-(%s, %).", notFound));
1387 		}
1388 		else if(notFound.length == 1){
1389 			throw new Exception(.format("Couldn't find package: %-(%s, %).", notFound));
1390 		}
1391 
1392 		if (m_dryRun) return;
1393 
1394 		initPackage(path, depVers, type, format, recipe_callback);
1395 
1396 		if (!["vibe.d", "deimos", "minimal"].canFind(type)) {
1397 			runCustomInitialization(path, type, app_args);
1398 		}
1399 
1400 		//Act smug to the user.
1401 		logInfo("Success", Color.green, "created empty project in %s", path.toNativeString().color(Mode.bold));
1402 	}
1403 
1404 	/**
1405 	 * Run initialization code from a template project
1406 	 *
1407 	 * Looks up a project, then get its `init-exec` subpackage,
1408 	 * and run this to initialize the repository with a default structure.
1409 	 */
1410 	private void runCustomInitialization(NativePath path, string name, string[] runArgs)
1411 	{
1412 		auto name_ = PackageName(name);
1413 		auto template_pack = m_packageManager.getBestPackage(name_);
1414 		if (!template_pack) {
1415 			logInfo("%s is not present, getting and storing it locally", name);
1416 			template_pack = fetch(name_);
1417 		}
1418 
1419 		Package initSubPackage = m_packageManager.getSubPackage(template_pack, "init-exec", false);
1420 		auto template_dub = new Dub(null, m_packageSuppliers);
1421 		template_dub.loadPackage(initSubPackage);
1422 
1423 		GeneratorSettings settings = this.makeAppSettings();
1424 		settings.runArgs = runArgs;
1425 
1426 		initSubPackage.recipe.buildSettings.workingDirectory = path.toNativeString();
1427 		template_dub.generateProject("build", settings);
1428 	}
1429 
1430 	/** Converts the package recipe of the loaded root package to the given format.
1431 
1432 		Params:
1433 			destination_file_ext = The file extension matching the desired
1434 				format. Possible values are "json" or "sdl".
1435 			print_only = Print the converted recipe instead of writing to disk
1436 	*/
1437 	void convertRecipe(string destination_file_ext, bool print_only = false)
1438 	{
1439 		import std.path : extension;
1440 		import std.stdio : stdout;
1441 		import dub.recipe.io : serializePackageRecipe, writePackageRecipe;
1442 
1443 		if (print_only) {
1444 			auto dst = stdout.lockingTextWriter;
1445 			serializePackageRecipe(dst, m_project.rootPackage.rawRecipe, "dub."~destination_file_ext);
1446 			return;
1447 		}
1448 
1449 		auto srcfile = m_project.rootPackage.recipePath;
1450 		auto srcext = srcfile.head.name.extension;
1451 		if (srcext == "."~destination_file_ext) {
1452 			// no logging before this point
1453 			tagWidth.push(5);
1454 			logError("Package format is already %s.", destination_file_ext);
1455 			return;
1456 		}
1457 
1458 		writePackageRecipe(srcfile.parentPath ~ ("dub."~destination_file_ext), m_project.rootPackage.rawRecipe);
1459 		removeFile(srcfile);
1460 	}
1461 
1462 	/** Runs DDOX to generate or serve documentation.
1463 
1464 		Params:
1465 			run = If set to true, serves documentation on a local web server.
1466 				Otherwise generates actual HTML files.
1467 			generate_args = Additional command line arguments to pass to
1468 				"ddox generate-html" or "ddox serve-html".
1469 	*/
1470 	void runDdox(bool run, string[] generate_args = null)
1471 	{
1472 		import std.process : browse;
1473 
1474 		if (m_dryRun) return;
1475 
1476 		// allow to choose a custom ddox tool
1477 		auto tool = m_project.rootPackage.recipe.ddoxTool.empty
1478 			? PackageName("ddox")
1479 			: PackageName(m_project.rootPackage.recipe.ddoxTool);
1480 
1481 		auto tool_pack = m_packageManager.getBestPackage(tool);
1482 		if (!tool_pack) {
1483 			logInfo("%s is not present, getting and storing it user wide", tool);
1484 			tool_pack = this.fetch(tool);
1485 		}
1486 
1487 		auto ddox_dub = new Dub(null, m_packageSuppliers);
1488 		ddox_dub.loadPackage(tool_pack);
1489 		ddox_dub.upgrade(UpgradeOptions.select);
1490 
1491 		GeneratorSettings settings = this.makeAppSettings();
1492 
1493 		auto filterargs = m_project.rootPackage.recipe.ddoxFilterArgs.dup;
1494 		if (filterargs.empty) filterargs = ["--min-protection=Protected", "--only-documented"];
1495 
1496 		settings.runArgs = "filter" ~ filterargs ~ "docs.json";
1497 		ddox_dub.generateProject("build", settings);
1498 
1499 		auto p = tool_pack.path;
1500 		p.endsWithSlash = true;
1501 		auto tool_path = p.toNativeString();
1502 
1503 		if (run) {
1504 			settings.runArgs = ["serve-html", "--navigation-type=ModuleTree", "docs.json", "--web-file-dir="~tool_path~"public"] ~ generate_args;
1505 			browse("http://127.0.0.1:8080/");
1506 		} else {
1507 			settings.runArgs = ["generate-html", "--navigation-type=ModuleTree", "docs.json", "docs"] ~ generate_args;
1508 		}
1509 		ddox_dub.generateProject("build", settings);
1510 
1511 		if (!run) {
1512 			// TODO: ddox should copy those files itself
1513 			version(Windows) runCommand(`xcopy /S /D "`~tool_path~`public\*" docs\`, null, m_rootPath.toNativeString());
1514 			else runCommand("rsync -ru '"~tool_path~"public/' docs/", null, m_rootPath.toNativeString());
1515 		}
1516 	}
1517 
1518 	/**
1519 	 * Compute and returns the path were artifacts are stored
1520 	 *
1521 	 * Expose `dub.generator.generator : packageCache` with this instance's
1522 	 * configured cache.
1523 	 */
1524 	protected NativePath packageCache (Package pkg) const
1525 	{
1526 		return .packageCache(this.m_dirs.cache, pkg);
1527 	}
1528 
1529 	/// Exposed because `commandLine` replicates `generateProject` for `dub describe`
1530 	/// instead of treating it like a regular generator... Remove this once the
1531 	/// flaw is fixed, and don't add more calls to this function!
1532 	package(dub) NativePath cachePathDontUse () const @safe pure nothrow @nogc
1533 	{
1534 		return this.m_dirs.cache;
1535 	}
1536 
1537 	/// Make a `GeneratorSettings` suitable to generate tools (DDOC, DScanner, etc...)
1538 	private GeneratorSettings makeAppSettings () const
1539 	{
1540 		GeneratorSettings settings;
1541 		auto compiler_binary = this.defaultCompiler;
1542 
1543 		settings.config = "application";
1544 		settings.buildType = "debug";
1545 		settings.compiler = getCompiler(compiler_binary);
1546 		settings.platform = settings.compiler.determinePlatform(
1547 			settings.buildSettings, compiler_binary, this.defaultArchitecture);
1548 		if (this.defaultLowMemory)
1549 			settings.buildSettings.options |= BuildOption.lowmem;
1550 		if (this.defaultEnvironments)
1551 			settings.buildSettings.addEnvironments(this.defaultEnvironments);
1552 		if (this.defaultBuildEnvironments)
1553 			settings.buildSettings.addBuildEnvironments(this.defaultBuildEnvironments);
1554 		if (this.defaultRunEnvironments)
1555 			settings.buildSettings.addRunEnvironments(this.defaultRunEnvironments);
1556 		if (this.defaultPreGenerateEnvironments)
1557 			settings.buildSettings.addPreGenerateEnvironments(this.defaultPreGenerateEnvironments);
1558 		if (this.defaultPostGenerateEnvironments)
1559 			settings.buildSettings.addPostGenerateEnvironments(this.defaultPostGenerateEnvironments);
1560 		if (this.defaultPreBuildEnvironments)
1561 			settings.buildSettings.addPreBuildEnvironments(this.defaultPreBuildEnvironments);
1562 		if (this.defaultPostBuildEnvironments)
1563 			settings.buildSettings.addPostBuildEnvironments(this.defaultPostBuildEnvironments);
1564 		if (this.defaultPreRunEnvironments)
1565 			settings.buildSettings.addPreRunEnvironments(this.defaultPreRunEnvironments);
1566 		if (this.defaultPostRunEnvironments)
1567 			settings.buildSettings.addPostRunEnvironments(this.defaultPostRunEnvironments);
1568 		settings.run = true;
1569 		settings.overrideToolWorkingDirectory = m_rootPath;
1570 
1571 		return settings;
1572 	}
1573 
1574 	/**
1575 	 * Determine the default compiler to use for this instance
1576 	 *
1577 	 * The default compiler will be used unless --compiler is specified.
1578 	 * The environment variable `DC` will take precedence over anything,
1579 	 * followed by the configuration. If nothing is found, the folder in
1580 	 * which `dub` is installed will be searched, and if nothing is found,
1581 	 * the $PATH will be searched.
1582 	 * In the majority of cases, as we distribute `dub` alongside the compiler,
1583 	 * it will be found once the directory in which dub reside is searched.
1584 	 *
1585 	 * Returns: The value to use for the default compiler.
1586 	 */
1587 	protected string determineDefaultCompiler() const
1588 	{
1589 		import std.file : thisExePath;
1590 		import std.path : buildPath, dirName, expandTilde, isAbsolute, isDirSeparator;
1591 		import std.range : front;
1592 
1593 		// Env takes precedence
1594 		string result;
1595 		if (auto envCompiler = environment.get("DC"))
1596 			result = envCompiler;
1597 		else
1598 			result = this.m_config.defaultCompiler.expandTilde;
1599 		if (result.length && result.isAbsolute)
1600 			return result;
1601 
1602 		static immutable BinaryPrefix = `$DUB_BINARY_PATH`;
1603 		if (result.startsWith(BinaryPrefix))
1604 			return thisExePath().dirName() ~ result[BinaryPrefix.length .. $];
1605 
1606 		if (!find!isDirSeparator(result).empty)
1607 			throw new Exception("defaultCompiler specified in a DUB config file cannot use an unqualified relative path:\n\n" ~ result ~
1608 			"\n\nUse \"$DUB_BINARY_PATH/../path/you/want\" instead.");
1609 
1610 		version (Windows) enum sep = ";", exe = ".exe";
1611 		version (Posix) enum sep = ":", exe = "";
1612 
1613 		auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"];
1614 		// If a compiler name is specified, look for it next to dub.
1615 		// Otherwise, look for any of the common compilers adjacent to dub.
1616 		if (result.length)
1617 		{
1618 			string compilerPath = buildPath(thisExePath().dirName(), result ~ exe);
1619 			if (existsFile(compilerPath))
1620 				return compilerPath;
1621 		}
1622 		else
1623 		{
1624 			auto nextFound = compilers.find!(bin => existsFile(buildPath(thisExePath().dirName(), bin ~ exe)));
1625 			if (!nextFound.empty)
1626 				return buildPath(thisExePath().dirName(),  nextFound.front ~ exe);
1627 		}
1628 
1629 		// If nothing found next to dub, search the user's PATH, starting
1630 		// with the compiler name from their DUB config file, if specified.
1631 		auto paths = environment.get("PATH", "").splitter(sep).map!NativePath;
1632 		if (result.length && paths.canFind!(p => existsFile(p ~ (result ~ exe))))
1633 			return result;
1634 		foreach (p; paths) {
1635 			auto res = compilers.find!(bin => existsFile(p ~ (bin~exe)));
1636 			if (!res.empty)
1637 				return res.front;
1638 		}
1639 		return compilers[0];
1640 	}
1641 
1642 	// This test also relies on the environment and the filesystem,
1643 	// as the `makePackageSuppliers` does, and should be refactored.
1644 	unittest
1645 	{
1646 		import dub.test.base : TestDub;
1647 		import std.path: buildPath, absolutePath;
1648 
1649 		auto dub = new TestDub(null, ".", null, SkipPackageSuppliers.configured);
1650 		immutable olddc = environment.get("DC", null);
1651 		immutable oldpath = environment.get("PATH", null);
1652 		immutable testdir = "test-determineDefaultCompiler";
1653 		void repairenv(string name, string var)
1654 		{
1655 			if (var !is null)
1656 				environment[name] = var;
1657 			else if (name in environment)
1658 				environment.remove(name);
1659 		}
1660 		scope (exit) repairenv("DC", olddc);
1661 		scope (exit) repairenv("PATH", oldpath);
1662 		scope (exit) rmdirRecurse(testdir);
1663 
1664 		version (Windows) enum sep = ";", exe = ".exe";
1665 		version (Posix) enum sep = ":", exe = "";
1666 
1667 		immutable dmdpath = testdir.buildPath("dmd", "bin");
1668 		immutable ldcpath = testdir.buildPath("ldc", "bin");
1669 		mkdirRecurse(dmdpath);
1670 		mkdirRecurse(ldcpath);
1671 		immutable dmdbin = dmdpath.buildPath("dmd"~exe);
1672 		immutable ldcbin = ldcpath.buildPath("ldc2"~exe);
1673 		std.file.write(dmdbin, null);
1674 		std.file.write(ldcbin, null);
1675 
1676 		environment["DC"] = dmdbin.absolutePath();
1677 		assert(dub.determineDefaultCompiler() == dmdbin.absolutePath());
1678 
1679 		environment["DC"] = "dmd";
1680 		environment["PATH"] = dmdpath ~ sep ~ ldcpath;
1681 		assert(dub.determineDefaultCompiler() == "dmd");
1682 
1683 		environment["DC"] = "ldc2";
1684 		environment["PATH"] = dmdpath ~ sep ~ ldcpath;
1685 		assert(dub.determineDefaultCompiler() == "ldc2");
1686 
1687 		environment.remove("DC");
1688 		environment["PATH"] = ldcpath ~ sep ~ dmdpath;
1689 		assert(dub.determineDefaultCompiler() == "ldc2");
1690 	}
1691 
1692 	private NativePath makeAbsolute(NativePath p) const { return p.absolute ? p : m_rootPath ~ p; }
1693 	private NativePath makeAbsolute(string p) const { return makeAbsolute(NativePath(p)); }
1694 }
1695 
1696 
1697 /// Option flags for `Dub.fetch`
1698 enum FetchOptions
1699 {
1700 	none = 0,
1701 	forceBranchUpgrade = 1<<0,
1702 	usePrerelease = 1<<1,
1703 	forceRemove = 1<<2, /// Deprecated, does nothing.
1704 	printOnly = 1<<3,
1705 }
1706 
1707 /// Option flags for `Dub.upgrade`
1708 enum UpgradeOptions
1709 {
1710 	none = 0,
1711 	upgrade = 1<<1, /// Upgrade existing packages
1712 	preRelease = 1<<2, /// include pre-release versions in upgrade
1713 	forceRemove = 1<<3, /// Deprecated, does nothing.
1714 	select = 1<<4, /// Update the dub.selections.json file with the upgraded versions
1715 	dryRun = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence
1716 	/*deprecated*/ printUpgradesOnly = dryRun, /// deprecated, use dryRun instead
1717 	/*deprecated*/ useCachedResult = 1<<6, /// deprecated, has no effect
1718 	noSaveSelections = 1<<7, /// Don't store updated selections on disk
1719 }
1720 
1721 /// Determines which of the default package suppliers are queried for packages.
1722 public alias SkipPackageSuppliers = SPS;
1723 
1724 private class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) {
1725 	protected {
1726 		Dub m_dub;
1727 		UpgradeOptions m_options;
1728 		Dependency[][PackageName] m_packageVersions;
1729 		Package[string] m_remotePackages;
1730 		SelectedVersions m_selectedVersions;
1731 		Package m_rootPackage;
1732 		bool[PackageName] m_packagesToUpgrade;
1733 		Package[PackageDependency] m_packages;
1734 		TreeNodes[][TreeNode] m_children;
1735 	}
1736 
1737 
1738 	this(Dub dub, UpgradeOptions options, Package root, SelectedVersions selected_versions)
1739 	{
1740 		assert(dub !is null);
1741 		assert(root !is null);
1742 		assert(selected_versions !is null);
1743 
1744 		if (environment.get("DUB_NO_RESOLVE_LIMIT") !is null)
1745 			super(ulong.max);
1746 		else
1747 		    super(1_000_000);
1748 
1749 		m_dub = dub;
1750 		m_options = options;
1751 		m_rootPackage = root;
1752 		m_selectedVersions = selected_versions;
1753 	}
1754 
1755 	Dependency[PackageName] resolve(string[] filter)
1756 	{
1757 		foreach (name; filter)
1758 			m_packagesToUpgrade[PackageName(name)] = true;
1759 		return super.resolve(TreeNode(PackageName(m_rootPackage.name), Dependency(m_rootPackage.version_)),
1760 			(m_options & UpgradeOptions.dryRun) == 0);
1761 	}
1762 
1763 	protected bool isFixedPackage(in PackageName pack)
1764 	{
1765 		return m_packagesToUpgrade !is null && pack !in m_packagesToUpgrade;
1766 	}
1767 
1768 	protected override Dependency[] getAllConfigs(in PackageName pack)
1769 	{
1770 		if (auto pvers = pack in m_packageVersions)
1771 			return *pvers;
1772 
1773 		if ((!(m_options & UpgradeOptions.upgrade) || isFixedPackage(pack)) && m_selectedVersions.hasSelectedVersion(pack)) {
1774 			auto ret = [m_selectedVersions.getSelectedVersion(pack)];
1775 			logDiagnostic("Using fixed selection %s %s", pack, ret[0]);
1776 			m_packageVersions[pack] = ret;
1777 			return ret;
1778 		}
1779 
1780 		logDiagnostic("Search for versions of %s (%s package suppliers)", pack, m_dub.m_packageSuppliers.length);
1781 		Version[] versions;
1782 		foreach (p; m_dub.packageManager.getPackageIterator(pack.toString()))
1783 			versions ~= p.version_;
1784 
1785 		foreach (ps; m_dub.m_packageSuppliers) {
1786 			try {
1787 				auto vers = ps.getVersions(pack);
1788 				vers.reverse();
1789 				if (!vers.length) {
1790 					logDiagnostic("No versions for %s for %s", pack, ps.description);
1791 					continue;
1792 				}
1793 
1794 				versions ~= vers;
1795 				break;
1796 			} catch (Exception e) {
1797 				logWarn("Package %s not found in %s: %s", pack, ps.description, e.msg);
1798 				logDebug("Full error: %s", e.toString().sanitize);
1799 			}
1800 		}
1801 
1802 		// sort by version, descending, and remove duplicates
1803 		versions = versions.sort!"a>b".uniq.array;
1804 
1805 		// move pre-release versions to the back of the list if no preRelease flag is given
1806 		if (!(m_options & UpgradeOptions.preRelease))
1807 			versions = versions.filter!(v => !v.isPreRelease).array ~ versions.filter!(v => v.isPreRelease).array;
1808 
1809 		// filter out invalid/unreachable dependency specs
1810 		versions = versions.filter!((v) {
1811 				bool valid = getPackage(pack, Dependency(v)) !is null;
1812 				if (!valid) logDiagnostic("Excluding invalid dependency specification %s %s from dependency resolution process.", pack, v);
1813 				return valid;
1814 			}).array;
1815 
1816 		if (!versions.length) logDiagnostic("Nothing found for %s", pack);
1817 		else logDiagnostic("Return for %s: %s", pack, versions);
1818 
1819 		auto ret = versions.map!(v => Dependency(v)).array;
1820 		m_packageVersions[pack] = ret;
1821 		return ret;
1822 	}
1823 
1824 	protected override Dependency[] getSpecificConfigs(in PackageName pack, TreeNodes nodes)
1825 	{
1826 		if (!nodes.configs.path.empty || !nodes.configs.repository.empty) {
1827 			if (getPackage(nodes.pack, nodes.configs)) return [nodes.configs];
1828 			else return null;
1829 		}
1830 		else return null;
1831 	}
1832 
1833 
1834 	protected override TreeNodes[] getChildren(TreeNode node)
1835 	{
1836 		if (auto pc = node in m_children)
1837 			return *pc;
1838 		auto ret = getChildrenRaw(node);
1839 		m_children[node] = ret;
1840 		return ret;
1841 	}
1842 
1843 	private final TreeNodes[] getChildrenRaw(TreeNode node)
1844 	{
1845 		import std.array : appender;
1846 		auto ret = appender!(TreeNodes[]);
1847 		auto pack = getPackage(node.pack, node.config);
1848 		if (!pack) {
1849 			// this can happen when the package description contains syntax errors
1850 			logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config);
1851 			return null;
1852 		}
1853 		auto basepack = pack.basePackage;
1854 
1855 		foreach (d; pack.getAllDependenciesRange()) {
1856 			auto dbasename = d.name.main.toString();
1857 
1858 			// detect dependencies to the root package (or sub packages thereof)
1859 			if (dbasename == basepack.name) {
1860 				auto absdeppath = d.spec.mapToPath(pack.path).path;
1861 				absdeppath.endsWithSlash = true;
1862 				auto subpack = m_dub.m_packageManager.getSubPackage(basepack, d.name.sub, true);
1863 				if (subpack) {
1864 					auto desireddeppath = basepack.path;
1865 					desireddeppath.endsWithSlash = true;
1866 
1867 					auto altdeppath = d.name == d.name.main ? basepack.path : subpack.path;
1868 					altdeppath.endsWithSlash = true;
1869 
1870 					if (!d.spec.path.empty && absdeppath != desireddeppath)
1871 						logWarn("Sub package %s, referenced by %s %s must be referenced using the path to its base package",
1872 							subpack.name, pack.name, pack);
1873 
1874 					enforce(d.spec.path.empty || absdeppath == desireddeppath || absdeppath == altdeppath,
1875 						format("Dependency from %s to %s uses wrong path: %s vs. %s",
1876 							node.pack, subpack.name, absdeppath.toNativeString(), desireddeppath.toNativeString()));
1877 				}
1878 				ret ~= TreeNodes(d.name, node.config);
1879 				continue;
1880 			}
1881 
1882 			DependencyType dt;
1883 			if (d.spec.optional) {
1884 				if (d.spec.default_) dt = DependencyType.optionalDefault;
1885 				else dt = DependencyType.optional;
1886 			} else dt = DependencyType.required;
1887 
1888 			Dependency dspec = d.spec.mapToPath(pack.path);
1889 
1890 			// if not upgrading, use the selected version
1891 			if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions.hasSelectedVersion(d.name.main))
1892 				dspec = m_selectedVersions.getSelectedVersion(d.name.main);
1893 
1894 			// keep selected optional dependencies and avoid non-selected optional-default dependencies by default
1895 			if (!m_selectedVersions.bare) {
1896 				if (dt == DependencyType.optionalDefault && !m_selectedVersions.hasSelectedVersion(d.name.main))
1897 					dt = DependencyType.optional;
1898 				else if (dt == DependencyType.optional && m_selectedVersions.hasSelectedVersion(d.name.main))
1899 					dt = DependencyType.optionalDefault;
1900 			}
1901 
1902 			ret ~= TreeNodes(d.name, dspec, dt);
1903 		}
1904 		return ret.data;
1905 	}
1906 
1907 	protected override bool matches(Dependency configs, Dependency config)
1908 	{
1909 		if (!configs.path.empty) return configs.path == config.path;
1910 		return configs.merge(config).valid;
1911 	}
1912 
1913 	private Package getPackage(PackageName name, Dependency dep)
1914 	{
1915 		auto key = PackageDependency(name, dep);
1916 		if (auto pp = key in m_packages)
1917 			return *pp;
1918 		auto p = getPackageRaw(name, dep);
1919 		m_packages[key] = p;
1920 		return p;
1921 	}
1922 
1923 	private Package getPackageRaw(in PackageName name, Dependency dep)
1924 	{
1925 		import dub.recipe.json;
1926 
1927 		// for sub packages, first try to get them from the base package
1928 		if (name.main != name) {
1929 			auto subname = name.sub;
1930 			auto basepack = getPackage(name.main, dep);
1931 			if (!basepack) return null;
1932 			if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true))
1933 				return sp;
1934 			logDiagnostic("Subpackage %s@%s not found.", name, dep);
1935 			return null;
1936 		}
1937 
1938 		// shortcut if the referenced package is the root package
1939 		if (name.main.toString() == m_rootPackage.basePackage.name)
1940 			return m_rootPackage.basePackage;
1941 
1942 		if (!dep.repository.empty) {
1943 			auto ret = m_dub.packageManager.loadSCMPackage(name, dep.repository);
1944 			return ret !is null && dep.matches(ret.version_) ? ret : null;
1945 		}
1946 		if (!dep.path.empty) {
1947 			try {
1948 				return m_dub.packageManager.getOrLoadPackage(dep.path);
1949 			} catch (Exception e) {
1950 				logDiagnostic("Failed to load path based dependency %s: %s", name, e.msg);
1951 				logDebug("Full error: %s", e.toString().sanitize);
1952 				return null;
1953 			}
1954 		}
1955 		const vers = dep.version_;
1956 
1957 		if (auto ret = m_dub.m_packageManager.getBestPackage(name, vers))
1958 			return ret;
1959 
1960 		auto key = name.toString() ~ ":" ~ vers.toString();
1961 		if (auto ret = key in m_remotePackages)
1962 			return *ret;
1963 
1964 		auto prerelease = (m_options & UpgradeOptions.preRelease) != 0;
1965 
1966 		foreach (ps; m_dub.m_packageSuppliers) {
1967 			if (name.main == name) {
1968 				try {
1969 					auto desc = ps.fetchPackageRecipe(name, VersionRange(vers, vers), prerelease);
1970 					if (desc.type == Json.Type.null_)
1971 						continue;
1972 					PackageRecipe recipe;
1973 					parseJson(recipe, desc);
1974 					auto ret = new Package(recipe);
1975 					m_remotePackages[key] = ret;
1976 					return ret;
1977 				} catch (Exception e) {
1978 					logDiagnostic("Metadata for %s %s could not be downloaded from %s: %s", name, vers, ps.description, e.msg);
1979 					logDebug("Full error: %s", e.toString().sanitize);
1980 				}
1981 			} else {
1982 				logDiagnostic("Package %s not found in base package description (%s). Downloading whole package.", name, vers.toString());
1983 				try {
1984 					FetchOptions fetchOpts;
1985 					fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none;
1986 					m_dub.fetch(name.main, vers, fetchOpts, m_dub.defaultPlacementLocation, "need sub package description");
1987 					auto ret = m_dub.m_packageManager.getBestPackage(name, vers);
1988 					if (!ret) {
1989 						logWarn("Package %s %s doesn't have a sub package %s", name.main, dep, name);
1990 						return null;
1991 					}
1992 					m_remotePackages[key] = ret;
1993 					return ret;
1994 				} catch (Exception e) {
1995 					logDiagnostic("Package %s could not be downloaded from %s: %s", name.main, ps.description, e.msg);
1996 					logDebug("Full error: %s", e.toString().sanitize);
1997 				}
1998 			}
1999 		}
2000 
2001 		m_remotePackages[key] = null;
2002 
2003 		logWarn("Package %s %s could not be loaded either locally, or from the configured package registries.", name, dep);
2004 		return null;
2005 	}
2006 }
2007 
2008 package struct SpecialDirs {
2009 	/// The path where to store temporary files and directory
2010 	NativePath temp;
2011 	/// The system-wide dub-specific folder
2012 	NativePath systemSettings;
2013 	/// The dub-specific folder in the user home directory
2014 	NativePath userSettings;
2015 	/**
2016 	 * User location where to install packages
2017 	 *
2018 	 * On Windows, this folder, unlike `userSettings`, does not roam,
2019 	 * so an account on a company network will not save the content of this data,
2020 	 * unlike `userSettings`.
2021 	 *
2022 	 * On Posix, this is currently equivalent to `userSettings`.
2023 	 *
2024 	 * See_Also: https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid
2025 	 */
2026 	NativePath userPackages;
2027 
2028 	/**
2029 	 * Location at which build/generation artifact will be written
2030 	 *
2031 	 * All build artifacts are stored under a single build cache,
2032 	 * which is usually located under `$HOME/.dub/cache/` on POSIX,
2033 	 * and `%LOCALAPPDATA%/dub/cache` on Windows.
2034 	 *
2035 	 * Versions of dub prior to v1.31.0 used to store  artifact under the
2036 	 * project directory, but this led to issues with packages stored on
2037 	 * read-only file system / location, and lingering artifacts scattered
2038 	 * through the file system.
2039 	 *
2040 	 * Dub writes in the cache directory some Json description files
2041 	 * of the available artifacts. These files are intended to be read by
2042 	 * 3rd party software (e.g. Meson). The default cache location specified
2043 	 * in this function should therefore not change across future Dub versions.
2044 	 */
2045 	NativePath cache;
2046 
2047 	/// Returns: An instance of `SpecialDirs` initialized from the environment
2048 	public static SpecialDirs make () {
2049 		import std.file : tempDir;
2050 
2051 		SpecialDirs result;
2052 		result.temp = NativePath(tempDir);
2053 
2054 		version(Windows) {
2055 			result.systemSettings = NativePath(environment.get("ProgramData")) ~ "dub/";
2056 			immutable appDataDir = environment.get("APPDATA");
2057 			result.userSettings = NativePath(appDataDir) ~ "dub/";
2058 			// LOCALAPPDATA is not defined before Windows Vista
2059 			result.userPackages = NativePath(environment.get("LOCALAPPDATA", appDataDir)) ~ "dub";
2060 		} else version(Posix) {
2061 			result.systemSettings = NativePath("/var/lib/dub/");
2062 			result.userSettings = NativePath(environment.get("HOME")) ~ ".dub/";
2063 			if (!result.userSettings.absolute)
2064 				result.userSettings = getWorkingDirectory() ~ result.userSettings;
2065 			result.userPackages = result.userSettings;
2066 		}
2067 		result.cache = result.userPackages ~ "cache";
2068 		return result;
2069 	}
2070 }