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