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