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