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 = NativePath(getcwd()) ~ 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 = NativePath(getcwd()) ~ 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 receipe 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 		// With a requested `unittest` config, switch to the special test runner
690 		// config (which doesn't require an existing `unittest` configuration).
691 		if (settings.config == "unittest") {
692 			const test_config = m_project.addTestRunnerConfiguration(settings, !m_dryRun);
693 			if (test_config) settings.config = test_config;
694 		}
695 
696 		auto generator = createProjectGenerator(ide, m_project);
697 		if (m_dryRun) return; // TODO: pass m_dryRun to the generator
698 		generator.generate(settings);
699 	}
700 
701 	/** Generate project files using the special test runner (`dub test`) configuration.
702 
703 		Any existing project files will be overridden.
704 	*/
705 	void testProject(GeneratorSettings settings, string config, NativePath custom_main_file)
706 	{
707 		settings.cache = this.m_dirs.cache;
708 		if (!custom_main_file.empty && !custom_main_file.absolute) custom_main_file = getWorkingDirectory() ~ custom_main_file;
709 
710 		const test_config = m_project.addTestRunnerConfiguration(settings, !m_dryRun, config, custom_main_file);
711 		if (!test_config) return; // target type "none"
712 
713 		settings.config = test_config;
714 
715 		auto generator = createProjectGenerator("build", m_project);
716 		generator.generate(settings);
717 	}
718 
719 	/** Executes D-Scanner tests on the current project. **/
720 	void lintProject(string[] args)
721 	{
722 		import std.path : buildPath, buildNormalizedPath;
723 
724 		if (m_dryRun) return;
725 
726 		auto tool = "dscanner";
727 
728 		auto tool_pack = m_packageManager.getBestPackage(tool);
729 		if (!tool_pack) {
730 			logInfo("Hint", Color.light_blue, "%s is not present, getting and storing it user wide", tool);
731 			tool_pack = fetch(tool, VersionRange.Any, defaultPlacementLocation, FetchOptions.none);
732 		}
733 
734 		auto dscanner_dub = new Dub(null, m_packageSuppliers);
735 		dscanner_dub.loadPackage(tool_pack);
736 		dscanner_dub.upgrade(UpgradeOptions.select);
737 
738 		GeneratorSettings settings = this.makeAppSettings();
739 		foreach (dependencyPackage; m_project.dependencies)
740 		{
741 			auto cfgs = m_project.getPackageConfigs(settings.platform, null, true);
742 			auto buildSettings = dependencyPackage.getBuildSettings(settings.platform, cfgs[dependencyPackage.name]);
743 			foreach (importPath; buildSettings.importPaths) {
744 				settings.runArgs ~= ["-I", buildNormalizedPath(dependencyPackage.path.toNativeString(), importPath.idup)];
745 			}
746 		}
747 
748 		string configFilePath = buildPath(m_project.rootPackage.path.toNativeString(), "dscanner.ini");
749 		if (!args.canFind("--config") && exists(configFilePath)) {
750 			settings.runArgs ~= ["--config", configFilePath];
751 		}
752 
753 		settings.runArgs ~= args ~ [m_project.rootPackage.path.toNativeString()];
754 		dscanner_dub.generateProject("build", settings);
755 	}
756 
757 	/** Prints the specified build settings necessary for building the root package.
758 	*/
759 	void listProjectData(GeneratorSettings settings, string[] requestedData, ListBuildSettingsFormat list_type)
760 	{
761 		import std.stdio;
762 		import std.ascii : newline;
763 
764 		// Split comma-separated lists
765 		string[] requestedDataSplit =
766 			requestedData
767 			.map!(a => a.splitter(",").map!strip)
768 			.joiner()
769 			.array();
770 
771 		auto data = m_project.listBuildSettings(settings, requestedDataSplit, list_type);
772 
773 		string delimiter;
774 		final switch (list_type) with (ListBuildSettingsFormat) {
775 			case list: delimiter = newline ~ newline; break;
776 			case listNul: delimiter = "\0\0"; break;
777 			case commandLine: delimiter = " "; break;
778 			case commandLineNul: delimiter = "\0\0"; break;
779 		}
780 
781 		write(data.joiner(delimiter));
782 		if (delimiter != "\0\0") writeln();
783 	}
784 
785 	/// Cleans intermediate/cache files of the given package (or all packages)
786 	deprecated("Use `clean(Package)` instead")
787 	void cleanPackage(NativePath path)
788 	{
789 		auto ppack = Package.findPackageFile(path);
790 		enforce(!ppack.empty, "No package found.", path.toNativeString());
791 		this.clean(Package.load(path, ppack));
792 	}
793 
794 	/// Ditto
795 	void clean()
796 	{
797 		const cache = this.m_dirs.cache;
798 		logInfo("Cleaning", Color.green, "all artifacts at %s",
799 			cache.toNativeString().color(Mode.bold));
800 		if (existsFile(cache))
801 			rmdirRecurse(cache.toNativeString());
802 	}
803 
804 	/// Ditto
805 	void clean(Package pack)
806 	{
807 		const cache = this.packageCache(pack);
808 		logInfo("Cleaning", Color.green, "artifacts for package %s at %s",
809 			pack.name.color(Mode.bold),
810 			cache.toNativeString().color(Mode.bold));
811 
812 		// TODO: clear target files and copy files
813 		if (existsFile(cache))
814 			rmdirRecurse(cache.toNativeString());
815 	}
816 
817 	/// Fetches the package matching the dependency and places it in the specified location.
818 	deprecated("Use the overload that accepts either a `Version` or a `VersionRange` as second argument")
819 	Package fetch(string packageId, const Dependency dep, PlacementLocation location, FetchOptions options, string reason = "")
820 	{
821 		const vrange = dep.visit!(
822 			(VersionRange range) => range,
823 			function VersionRange (any) { throw new Exception("Cannot call `dub.fetch` with a " ~ typeof(any).stringof ~ " dependency"); }
824 		);
825 		return this.fetch(packageId, vrange, location, options, reason);
826 	}
827 
828 	/// Ditto
829 	Package fetch(string packageId, in Version vers, PlacementLocation location, FetchOptions options, string reason = "")
830 	{
831 		return this.fetch(packageId, VersionRange(vers, vers), location, options, reason);
832 	}
833 
834 	/// Ditto
835 	Package fetch(string packageId, in VersionRange range, PlacementLocation location, FetchOptions options, string reason = "")
836 	{
837 		auto basePackageName = getBasePackageName(packageId);
838 		Json pinfo;
839 		PackageSupplier supplier;
840 		foreach(ps; m_packageSuppliers){
841 			try {
842 				pinfo = ps.fetchPackageRecipe(basePackageName, Dependency(range), (options & FetchOptions.usePrerelease) != 0);
843 				if (pinfo.type == Json.Type.null_)
844 					continue;
845 				supplier = ps;
846 				break;
847 			} catch(Exception e) {
848 				logWarn("Package %s not found for %s: %s", packageId, ps.description, e.msg);
849 				logDebug("Full error: %s", e.toString().sanitize());
850 			}
851 		}
852 		enforce(pinfo.type != Json.Type.undefined, "No package "~packageId~" was found matching the dependency " ~ range.toString());
853 		Version ver = Version(pinfo["version"].get!string);
854 
855 		// always upgrade branch based versions - TODO: actually check if there is a new commit available
856 		Package existing = m_packageManager.getPackage(packageId, ver, location);
857 		if (options & FetchOptions.printOnly) {
858 			if (existing && existing.version_ != ver)
859 				logInfo("A new version for %s is available (%s -> %s). Run \"%s\" to switch.",
860 					packageId.color(Mode.bold), existing.version_, ver,
861 					text("dub upgrade ", packageId).color(Mode.bold));
862 			return null;
863 		}
864 
865 		if (existing) {
866 			if (!ver.isBranch() || !(options & FetchOptions.forceBranchUpgrade) || location == PlacementLocation.local) {
867 				// TODO: support git working trees by performing a "git pull" instead of this
868 				logDiagnostic("Package %s %s (in %s packages) is already present with the latest version, skipping upgrade.",
869 					packageId, ver, location.toString);
870 				return existing;
871 			} else {
872 				logInfo("Removing", Color.yellow, "%s %s to prepare replacement with a new version", packageId.color(Mode.bold), ver);
873 				if (!m_dryRun) m_packageManager.remove(existing);
874 			}
875 		}
876 
877 		if (reason.length) logInfo("Fetching", Color.yellow, "%s %s (%s)", packageId.color(Mode.bold), ver, reason);
878 		else logInfo("Fetching", Color.yellow, "%s %s", packageId.color(Mode.bold), ver);
879 		if (m_dryRun) return null;
880 
881 		logDebug("Acquiring package zip file");
882 
883 		// repeat download on corrupted zips, see #1336
884 		foreach_reverse (i; 0..3)
885 		{
886 			import std.zip : ZipException;
887 
888 			auto path = getTempFile(basePackageName, ".zip");
889 			supplier.fetchPackage(path, basePackageName, Dependency(range), (options & FetchOptions.usePrerelease) != 0); // Q: continue on fail?
890 			scope(exit) removeFile(path);
891 			logDiagnostic("Placing to %s...", location.toString());
892 
893 			try {
894 				return m_packageManager.store(path, location, basePackageName, ver);
895 			} catch (ZipException e) {
896 				logInfo("Failed to extract zip archive for %s %s...", packageId, ver);
897 				// rethrow the exception at the end of the loop
898 				if (i == 0)
899 					throw e;
900 			}
901 		}
902 		assert(0, "Should throw a ZipException instead.");
903 	}
904 
905 	/** Removes a specific locally cached package.
906 
907 		This will delete the package files from disk and removes the
908 		corresponding entry from the list of known packages.
909 
910 		Params:
911 			pack = Package instance to remove
912 	*/
913 	void remove(in Package pack)
914 	{
915 		logInfo("Removing", Color.yellow, "%s (in %s)", pack.name.color(Mode.bold), pack.path.toNativeString());
916 		if (!m_dryRun) m_packageManager.remove(pack);
917 	}
918 
919 	/// Compatibility overload. Use the version without a `force_remove` argument instead.
920 	deprecated("Use `remove(pack)` directly instead, the boolean has no effect")
921 	void remove(in Package pack, bool force_remove)
922 	{
923 		remove(pack);
924 	}
925 
926 	/// @see remove(string, string, RemoveLocation)
927 	enum RemoveVersionWildcard = "*";
928 
929 	/** Removes one or more versions of a locally cached package.
930 
931 		This will remove a given package with a specified version from the
932 		given location. It will remove at most one package, unless `version_`
933 		is set to `RemoveVersionWildcard`.
934 
935 		Params:
936 			package_id = Name of the package to be removed
937 			location_ = Specifies the location to look for the given package
938 				name/version.
939 			resolve_version = Callback to select package version.
940 	*/
941 	void remove(string package_id, PlacementLocation location,
942 				scope size_t delegate(in Package[] packages) resolve_version)
943 	{
944 		enforce(!package_id.empty);
945 		if (location == PlacementLocation.local) {
946 			logInfo("To remove a locally placed package, make sure you don't have any data"
947 					~ "\nleft in it's directory and then simply remove the whole directory.");
948 			throw new Exception("dub cannot remove locally installed packages.");
949 		}
950 
951 		Package[] packages;
952 
953 		// Retrieve packages to be removed.
954 		foreach(pack; m_packageManager.getPackageIterator(package_id))
955 			if (m_packageManager.isManagedPackage(pack))
956 				packages ~= pack;
957 
958 		// Check validity of packages to be removed.
959 		if(packages.empty) {
960 			throw new Exception("Cannot find package to remove. ("
961 				~ "id: '" ~ package_id ~ "', location: '" ~ to!string(location) ~ "'"
962 				~ ")");
963 		}
964 
965 		// Sort package list in ascending version order
966 		packages.sort!((a, b) => a.version_ < b.version_);
967 
968 		immutable idx = resolve_version(packages);
969 		if (idx == size_t.max)
970 			return;
971 		else if (idx != packages.length)
972 			packages = packages[idx .. idx + 1];
973 
974 		logDebug("Removing %s packages.", packages.length);
975 		foreach(pack; packages) {
976 			try {
977 				remove(pack);
978 			} catch (Exception e) {
979 				logError("Failed to remove %s %s: %s", package_id, pack.version_, e.msg);
980 				logInfo("Continuing with other packages (if any).");
981 			}
982 		}
983 	}
984 
985 	/// Compatibility overload. Use the version without a `force_remove` argument instead.
986 	deprecated("Use the overload without the 3rd argument (`force_remove`) instead")
987 	void remove(string package_id, PlacementLocation location, bool force_remove,
988 				scope size_t delegate(in Package[] packages) resolve_version)
989 	{
990 		remove(package_id, location, resolve_version);
991 	}
992 
993 	/** Removes a specific version of a package.
994 
995 		Params:
996 			package_id = Name of the package to be removed
997 			version_ = Identifying a version or a wild card. If an empty string
998 				is passed, the package will be removed from the location, if
999 				there is only one version retrieved. This will throw an
1000 				exception, if there are multiple versions retrieved.
1001 			location_ = Specifies the location to look for the given package
1002 				name/version.
1003 	 */
1004 	void remove(string package_id, string version_, PlacementLocation location)
1005 	{
1006 		remove(package_id, location, (in packages) {
1007 			if (version_ == RemoveVersionWildcard || version_.empty)
1008 				return packages.length;
1009 
1010 			foreach (i, p; packages) {
1011 				if (p.version_ == Version(version_))
1012 					return i;
1013 			}
1014 			throw new Exception("Cannot find package to remove. ("
1015 				~ "id: '" ~ package_id ~ "', version: '" ~ version_ ~ "', location: '" ~ to!string(location) ~ "'"
1016 				~ ")");
1017 		});
1018 	}
1019 
1020 	/// Compatibility overload. Use the version without a `force_remove` argument instead.
1021 	deprecated("Use the overload without force_remove instead")
1022 	void remove(string package_id, string version_, PlacementLocation location, bool force_remove)
1023 	{
1024 		remove(package_id, version_, location);
1025 	}
1026 
1027 	/** Adds a directory to the list of locally known packages.
1028 
1029 		Forwards to `PackageManager.addLocalPackage`.
1030 
1031 		Params:
1032 			path = Path to the package
1033 			ver = Optional version to associate with the package (can be left
1034 				empty)
1035 			system = Make the package known system wide instead of user wide
1036 				(requires administrator privileges).
1037 
1038 		See_Also: `removeLocalPackage`
1039 	*/
1040 	void addLocalPackage(string path, string ver, bool system)
1041 	{
1042 		if (m_dryRun) return;
1043 		m_packageManager.addLocalPackage(makeAbsolute(path), ver, system ? PlacementLocation.system : PlacementLocation.user);
1044 	}
1045 
1046 	/** Removes a directory from the list of locally known packages.
1047 
1048 		Forwards to `PackageManager.removeLocalPackage`.
1049 
1050 		Params:
1051 			path = Path to the package
1052 			system = Make the package known system wide instead of user wide
1053 				(requires administrator privileges).
1054 
1055 		See_Also: `addLocalPackage`
1056 	*/
1057 	void removeLocalPackage(string path, bool system)
1058 	{
1059 		if (m_dryRun) return;
1060 		m_packageManager.removeLocalPackage(makeAbsolute(path), system ? PlacementLocation.system : PlacementLocation.user);
1061 	}
1062 
1063 	/** Registers a local directory to search for packages to use for satisfying
1064 		dependencies.
1065 
1066 		Params:
1067 			path = Path to a directory containing package directories
1068 			system = Make the package known system wide instead of user wide
1069 				(requires administrator privileges).
1070 
1071 		See_Also: `removeSearchPath`
1072 	*/
1073 	void addSearchPath(string path, bool system)
1074 	{
1075 		if (m_dryRun) return;
1076 		m_packageManager.addSearchPath(makeAbsolute(path), system ? PlacementLocation.system : PlacementLocation.user);
1077 	}
1078 
1079 	/** Unregisters a local directory search path.
1080 
1081 		Params:
1082 			path = Path to a directory containing package directories
1083 			system = Make the package known system wide instead of user wide
1084 				(requires administrator privileges).
1085 
1086 		See_Also: `addSearchPath`
1087 	*/
1088 	void removeSearchPath(string path, bool system)
1089 	{
1090 		if (m_dryRun) return;
1091 		m_packageManager.removeSearchPath(makeAbsolute(path), system ? PlacementLocation.system : PlacementLocation.user);
1092 	}
1093 
1094 	/** Queries all package suppliers with the given query string.
1095 
1096 		Returns a list of tuples, where the first entry is the human readable
1097 		name of the package supplier and the second entry is the list of
1098 		matched packages.
1099 
1100 		Params:
1101 		  query = the search term to match packages on
1102 
1103 		See_Also: `PackageSupplier.searchPackages`
1104 	*/
1105 	auto searchPackages(string query)
1106 	{
1107 		import std.typecons : Tuple, tuple;
1108 		Tuple!(string, PackageSupplier.SearchResult[])[] results;
1109 		foreach (ps; this.m_packageSuppliers) {
1110 			try
1111 				results ~= tuple(ps.description, ps.searchPackages(query));
1112 			catch (Exception e) {
1113 				logWarn("Searching %s for '%s' failed: %s", ps.description, query, e.msg);
1114 			}
1115 		}
1116 		return results.filter!(tup => tup[1].length);
1117 	}
1118 
1119 	/** Returns a list of all available versions (including branches) for a
1120 		particular package.
1121 
1122 		The list returned is based on the registered package suppliers. Local
1123 		packages are not queried in the search for versions.
1124 
1125 		See_also: `getLatestVersion`
1126 	*/
1127 	Version[] listPackageVersions(string name)
1128 	{
1129 		Version[] versions;
1130 		auto basePackageName = getBasePackageName(name);
1131 		foreach (ps; this.m_packageSuppliers) {
1132 			try versions ~= ps.getVersions(basePackageName);
1133 			catch (Exception e) {
1134 				logWarn("Failed to get versions for package %s on provider %s: %s", name, ps.description, e.msg);
1135 			}
1136 		}
1137 		return versions.sort().uniq.array;
1138 	}
1139 
1140 	/** Returns the latest available version for a particular package.
1141 
1142 		This function returns the latest numbered version of a package. If no
1143 		numbered versions are available, it will return an available branch,
1144 		preferring "~master".
1145 
1146 		Params:
1147 			package_name: The name of the package in question.
1148 			prefer_stable: If set to `true` (the default), returns the latest
1149 				stable version, even if there are newer pre-release versions.
1150 
1151 		See_also: `listPackageVersions`
1152 	*/
1153 	Version getLatestVersion(string package_name, bool prefer_stable = true)
1154 	{
1155 		auto vers = listPackageVersions(package_name);
1156 		enforce(!vers.empty, "Failed to find any valid versions for a package name of '"~package_name~"'.");
1157 		auto final_versions = vers.filter!(v => !v.isBranch && !v.isPreRelease).array;
1158 		if (prefer_stable && final_versions.length) return final_versions[$-1];
1159 		else return vers[$-1];
1160 	}
1161 
1162 	/** Initializes a directory with a package skeleton.
1163 
1164 		Params:
1165 			path = Path of the directory to create the new package in. The
1166 				directory will be created if it doesn't exist.
1167 			deps = List of dependencies to add to the package recipe.
1168 			type = Specifies the type of the application skeleton to use.
1169 			format = Determines the package recipe format to use.
1170 			recipe_callback = Optional callback that can be used to
1171 				customize the recipe before it gets written.
1172 	*/
1173 	void createEmptyPackage(NativePath path, string[] deps, string type,
1174 		PackageFormat format = PackageFormat.sdl,
1175 		scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null,
1176 		string[] app_args = [])
1177 	{
1178 		if (!path.absolute) path = m_rootPath ~ path;
1179 		path.normalize();
1180 
1181 		VersionRange[string] depVers;
1182 		string[] notFound; // keep track of any failed packages in here
1183 		foreach (dep; deps) {
1184 			try {
1185 				Version ver = getLatestVersion(dep);
1186 				if (ver.isBranch())
1187 					depVers[dep] = VersionRange(ver);
1188 				else
1189 					depVers[dep] = VersionRange.fromString("~>" ~ ver.toString());
1190 			} catch (Exception e) {
1191 				notFound ~= dep;
1192 			}
1193 		}
1194 
1195 		if(notFound.length > 1){
1196 			throw new Exception(.format("Couldn't find packages: %-(%s, %).", notFound));
1197 		}
1198 		else if(notFound.length == 1){
1199 			throw new Exception(.format("Couldn't find package: %-(%s, %).", notFound));
1200 		}
1201 
1202 		if (m_dryRun) return;
1203 
1204 		initPackage(path, depVers, type, format, recipe_callback);
1205 
1206 		if (!["vibe.d", "deimos", "minimal"].canFind(type)) {
1207 			runCustomInitialization(path, type, app_args);
1208 		}
1209 
1210 		//Act smug to the user.
1211 		logInfo("Success", Color.green, "created empty project in %s", path.toNativeString().color(Mode.bold));
1212 	}
1213 
1214 	private void runCustomInitialization(NativePath path, string type, string[] runArgs)
1215 	{
1216 		string packageName = type;
1217 		auto template_pack = m_packageManager.getBestPackage(packageName);
1218 		if (!template_pack) {
1219 			logInfo("%s is not present, getting and storing it user wide", packageName);
1220 			template_pack = fetch(packageName, VersionRange.Any, defaultPlacementLocation, FetchOptions.none);
1221 		}
1222 
1223 		Package initSubPackage = m_packageManager.getSubPackage(template_pack, "init-exec", false);
1224 		auto template_dub = new Dub(null, m_packageSuppliers);
1225 		template_dub.loadPackage(initSubPackage);
1226 
1227 		GeneratorSettings settings = this.makeAppSettings();
1228 		settings.runArgs = runArgs;
1229 
1230 		initSubPackage.recipe.buildSettings.workingDirectory = path.toNativeString();
1231 		template_dub.generateProject("build", settings);
1232 	}
1233 
1234 	/** Converts the package recipe of the loaded root package to the given format.
1235 
1236 		Params:
1237 			destination_file_ext = The file extension matching the desired
1238 				format. Possible values are "json" or "sdl".
1239 			print_only = Print the converted recipe instead of writing to disk
1240 	*/
1241 	void convertRecipe(string destination_file_ext, bool print_only = false)
1242 	{
1243 		import std.path : extension;
1244 		import std.stdio : stdout;
1245 		import dub.recipe.io : serializePackageRecipe, writePackageRecipe;
1246 
1247 		if (print_only) {
1248 			auto dst = stdout.lockingTextWriter;
1249 			serializePackageRecipe(dst, m_project.rootPackage.rawRecipe, "dub."~destination_file_ext);
1250 			return;
1251 		}
1252 
1253 		auto srcfile = m_project.rootPackage.recipePath;
1254 		auto srcext = srcfile.head.name.extension;
1255 		if (srcext == "."~destination_file_ext) {
1256 			// no logging before this point
1257 			tagWidth.push(5);
1258 			logError("Package format is already %s.", destination_file_ext);
1259 			return;
1260 		}
1261 
1262 		writePackageRecipe(srcfile.parentPath ~ ("dub."~destination_file_ext), m_project.rootPackage.rawRecipe);
1263 		removeFile(srcfile);
1264 	}
1265 
1266 	/** Runs DDOX to generate or serve documentation.
1267 
1268 		Params:
1269 			run = If set to true, serves documentation on a local web server.
1270 				Otherwise generates actual HTML files.
1271 			generate_args = Additional command line arguments to pass to
1272 				"ddox generate-html" or "ddox serve-html".
1273 	*/
1274 	void runDdox(bool run, string[] generate_args = null)
1275 	{
1276 		import std.process : browse;
1277 
1278 		if (m_dryRun) return;
1279 
1280 		// allow to choose a custom ddox tool
1281 		auto tool = m_project.rootPackage.recipe.ddoxTool;
1282 		if (tool.empty) tool = "ddox";
1283 
1284 		auto tool_pack = m_packageManager.getBestPackage(tool);
1285 		if (!tool_pack) {
1286 			logInfo("%s is not present, getting and storing it user wide", tool);
1287 			tool_pack = fetch(tool, VersionRange.Any, defaultPlacementLocation, FetchOptions.none);
1288 		}
1289 
1290 		auto ddox_dub = new Dub(null, m_packageSuppliers);
1291 		ddox_dub.loadPackage(tool_pack);
1292 		ddox_dub.upgrade(UpgradeOptions.select);
1293 
1294 		GeneratorSettings settings = this.makeAppSettings();
1295 
1296 		auto filterargs = m_project.rootPackage.recipe.ddoxFilterArgs.dup;
1297 		if (filterargs.empty) filterargs = ["--min-protection=Protected", "--only-documented"];
1298 
1299 		settings.runArgs = "filter" ~ filterargs ~ "docs.json";
1300 		ddox_dub.generateProject("build", settings);
1301 
1302 		auto p = tool_pack.path;
1303 		p.endsWithSlash = true;
1304 		auto tool_path = p.toNativeString();
1305 
1306 		if (run) {
1307 			settings.runArgs = ["serve-html", "--navigation-type=ModuleTree", "docs.json", "--web-file-dir="~tool_path~"public"] ~ generate_args;
1308 			browse("http://127.0.0.1:8080/");
1309 		} else {
1310 			settings.runArgs = ["generate-html", "--navigation-type=ModuleTree", "docs.json", "docs"] ~ generate_args;
1311 		}
1312 		ddox_dub.generateProject("build", settings);
1313 
1314 		if (!run) {
1315 			// TODO: ddox should copy those files itself
1316 			version(Windows) runCommand(`xcopy /S /D "`~tool_path~`public\*" docs\`);
1317 			else runCommand("rsync -ru '"~tool_path~"public/' docs/");
1318 		}
1319 	}
1320 
1321 	/**
1322 	 * Compute and returns the path were artifacts are stored
1323 	 *
1324 	 * Expose `dub.generator.generator : packageCache` with this instance's
1325 	 * configured cache.
1326 	 */
1327 	protected NativePath packageCache (Package pkg) const
1328 	{
1329 		return .packageCache(this.m_dirs.cache, pkg);
1330 	}
1331 
1332 	/// Exposed because `commandLine` replicates `generateProject` for `dub describe`
1333 	/// instead of treating it like a regular generator... Remove this once the
1334 	/// flaw is fixed, and don't add more calls to this function!
1335 	package(dub) NativePath cachePathDontUse () const @safe pure nothrow @nogc
1336 	{
1337 		return this.m_dirs.cache;
1338 	}
1339 
1340 	/// Make a `GeneratorSettings` suitable to generate tools (DDOC, DScanner, etc...)
1341 	private GeneratorSettings makeAppSettings () const
1342 	{
1343 		GeneratorSettings settings;
1344 		auto compiler_binary = this.defaultCompiler;
1345 
1346 		settings.config = "application";
1347 		settings.buildType = "debug";
1348 		settings.compiler = getCompiler(compiler_binary);
1349 		settings.platform = settings.compiler.determinePlatform(
1350 			settings.buildSettings, compiler_binary, this.defaultArchitecture);
1351 		if (this.defaultLowMemory)
1352 			settings.buildSettings.options |= BuildOption.lowmem;
1353 		if (this.defaultEnvironments)
1354 			settings.buildSettings.addEnvironments(this.defaultEnvironments);
1355 		if (this.defaultBuildEnvironments)
1356 			settings.buildSettings.addBuildEnvironments(this.defaultBuildEnvironments);
1357 		if (this.defaultRunEnvironments)
1358 			settings.buildSettings.addRunEnvironments(this.defaultRunEnvironments);
1359 		if (this.defaultPreGenerateEnvironments)
1360 			settings.buildSettings.addPreGenerateEnvironments(this.defaultPreGenerateEnvironments);
1361 		if (this.defaultPostGenerateEnvironments)
1362 			settings.buildSettings.addPostGenerateEnvironments(this.defaultPostGenerateEnvironments);
1363 		if (this.defaultPreBuildEnvironments)
1364 			settings.buildSettings.addPreBuildEnvironments(this.defaultPreBuildEnvironments);
1365 		if (this.defaultPostBuildEnvironments)
1366 			settings.buildSettings.addPostBuildEnvironments(this.defaultPostBuildEnvironments);
1367 		if (this.defaultPreRunEnvironments)
1368 			settings.buildSettings.addPreRunEnvironments(this.defaultPreRunEnvironments);
1369 		if (this.defaultPostRunEnvironments)
1370 			settings.buildSettings.addPostRunEnvironments(this.defaultPostRunEnvironments);
1371 		settings.run = true;
1372 
1373 		return settings;
1374 	}
1375 
1376 	private void determineDefaultCompiler()
1377 	{
1378 		import std.file : thisExePath;
1379 		import std.path : buildPath, dirName, expandTilde, isAbsolute, isDirSeparator;
1380 		import std.range : front;
1381 
1382 		// Env takes precedence
1383 		if (auto envCompiler = environment.get("DC"))
1384 			m_defaultCompiler = envCompiler;
1385 		else
1386 			m_defaultCompiler = m_config.defaultCompiler.expandTilde;
1387 		if (m_defaultCompiler.length && m_defaultCompiler.isAbsolute)
1388 			return;
1389 
1390 		static immutable BinaryPrefix = `$DUB_BINARY_PATH`;
1391 		if(m_defaultCompiler.startsWith(BinaryPrefix))
1392 		{
1393 			m_defaultCompiler = thisExePath().dirName() ~ m_defaultCompiler[BinaryPrefix.length .. $];
1394 			return;
1395 		}
1396 
1397 		if (!find!isDirSeparator(m_defaultCompiler).empty)
1398 			throw new Exception("defaultCompiler specified in a DUB config file cannot use an unqualified relative path:\n\n" ~ m_defaultCompiler ~
1399 			"\n\nUse \"$DUB_BINARY_PATH/../path/you/want\" instead.");
1400 
1401 		version (Windows) enum sep = ";", exe = ".exe";
1402 		version (Posix) enum sep = ":", exe = "";
1403 
1404 		auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"];
1405 		// If a compiler name is specified, look for it next to dub.
1406 		// Otherwise, look for any of the common compilers adjacent to dub.
1407 		if (m_defaultCompiler.length)
1408 		{
1409 			string compilerPath = buildPath(thisExePath().dirName(), m_defaultCompiler ~ exe);
1410 			if (existsFile(compilerPath))
1411 			{
1412 				m_defaultCompiler = compilerPath;
1413 				return;
1414 			}
1415 		}
1416 		else
1417 		{
1418 			auto nextFound = compilers.find!(bin => existsFile(buildPath(thisExePath().dirName(), bin ~ exe)));
1419 			if (!nextFound.empty)
1420 			{
1421 				m_defaultCompiler = buildPath(thisExePath().dirName(),  nextFound.front ~ exe);
1422 				return;
1423 			}
1424 		}
1425 
1426 		// If nothing found next to dub, search the user's PATH, starting
1427 		// with the compiler name from their DUB config file, if specified.
1428 		auto paths = environment.get("PATH", "").splitter(sep).map!NativePath;
1429 		if (m_defaultCompiler.length && paths.canFind!(p => existsFile(p ~ (m_defaultCompiler~exe))))
1430 			return;
1431 		foreach (p; paths) {
1432 			auto res = compilers.find!(bin => existsFile(p ~ (bin~exe)));
1433 			if (!res.empty) {
1434 				m_defaultCompiler = res.front;
1435 				return;
1436 			}
1437 		}
1438 		m_defaultCompiler = compilers[0];
1439 	}
1440 
1441 	unittest
1442 	{
1443 		import std.path: buildPath, absolutePath;
1444 		auto dub = new TestDub(".", null, SkipPackageSuppliers.configured);
1445 		immutable olddc = environment.get("DC", null);
1446 		immutable oldpath = environment.get("PATH", null);
1447 		immutable testdir = "test-determineDefaultCompiler";
1448 		void repairenv(string name, string var)
1449 		{
1450 			if (var !is null)
1451 				environment[name] = var;
1452 			else if (name in environment)
1453 				environment.remove(name);
1454 		}
1455 		scope (exit) repairenv("DC", olddc);
1456 		scope (exit) repairenv("PATH", oldpath);
1457 		scope (exit) rmdirRecurse(testdir);
1458 
1459 		version (Windows) enum sep = ";", exe = ".exe";
1460 		version (Posix) enum sep = ":", exe = "";
1461 
1462 		immutable dmdpath = testdir.buildPath("dmd", "bin");
1463 		immutable ldcpath = testdir.buildPath("ldc", "bin");
1464 		mkdirRecurse(dmdpath);
1465 		mkdirRecurse(ldcpath);
1466 		immutable dmdbin = dmdpath.buildPath("dmd"~exe);
1467 		immutable ldcbin = ldcpath.buildPath("ldc2"~exe);
1468 		std.file.write(dmdbin, null);
1469 		std.file.write(ldcbin, null);
1470 
1471 		environment["DC"] = dmdbin.absolutePath();
1472 		dub.determineDefaultCompiler();
1473 		assert(dub.m_defaultCompiler == dmdbin.absolutePath());
1474 
1475 		environment["DC"] = "dmd";
1476 		environment["PATH"] = dmdpath ~ sep ~ ldcpath;
1477 		dub.determineDefaultCompiler();
1478 		assert(dub.m_defaultCompiler == "dmd");
1479 
1480 		environment["DC"] = "ldc2";
1481 		environment["PATH"] = dmdpath ~ sep ~ ldcpath;
1482 		dub.determineDefaultCompiler();
1483 		assert(dub.m_defaultCompiler == "ldc2");
1484 
1485 		environment.remove("DC");
1486 		environment["PATH"] = ldcpath ~ sep ~ dmdpath;
1487 		dub.determineDefaultCompiler();
1488 		assert(dub.m_defaultCompiler == "ldc2");
1489 	}
1490 
1491 	private NativePath makeAbsolute(NativePath p) const { return p.absolute ? p : m_rootPath ~ p; }
1492 	private NativePath makeAbsolute(string p) const { return makeAbsolute(NativePath(p)); }
1493 }
1494 
1495 
1496 /// Option flags for `Dub.fetch`
1497 enum FetchOptions
1498 {
1499 	none = 0,
1500 	forceBranchUpgrade = 1<<0,
1501 	usePrerelease = 1<<1,
1502 	forceRemove = 1<<2, /// Deprecated, does nothing.
1503 	printOnly = 1<<3,
1504 }
1505 
1506 /// Option flags for `Dub.upgrade`
1507 enum UpgradeOptions
1508 {
1509 	none = 0,
1510 	upgrade = 1<<1, /// Upgrade existing packages
1511 	preRelease = 1<<2, /// inclde pre-release versions in upgrade
1512 	forceRemove = 1<<3, /// Deprecated, does nothing.
1513 	select = 1<<4, /// Update the dub.selections.json file with the upgraded versions
1514 	dryRun = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence
1515 	/*deprecated*/ printUpgradesOnly = dryRun, /// deprecated, use dryRun instead
1516 	/*deprecated*/ useCachedResult = 1<<6, /// deprecated, has no effect
1517 	noSaveSelections = 1<<7, /// Don't store updated selections on disk
1518 }
1519 
1520 /// Determines which of the default package suppliers are queried for packages.
1521 public alias SkipPackageSuppliers = SPS;
1522 
1523 private class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) {
1524 	protected {
1525 		Dub m_dub;
1526 		UpgradeOptions m_options;
1527 		Dependency[][string] m_packageVersions;
1528 		Package[string] m_remotePackages;
1529 		SelectedVersions m_selectedVersions;
1530 		Package m_rootPackage;
1531 		bool[string] m_packagesToUpgrade;
1532 		Package[PackageDependency] m_packages;
1533 		TreeNodes[][TreeNode] m_children;
1534 	}
1535 
1536 
1537 	this(Dub dub, UpgradeOptions options, Package root, SelectedVersions selected_versions)
1538 	{
1539 		assert(dub !is null);
1540 		assert(root !is null);
1541 		assert(selected_versions !is null);
1542 
1543 		if (environment.get("DUB_NO_RESOLVE_LIMIT") !is null)
1544 			super(ulong.max);
1545 		else
1546 		    super(1_000_000);
1547 
1548 		m_dub = dub;
1549 		m_options = options;
1550 		m_rootPackage = root;
1551 		m_selectedVersions = selected_versions;
1552 	}
1553 
1554 	Dependency[string] resolve(string[] filter)
1555 	{
1556 		foreach (name; filter)
1557 			m_packagesToUpgrade[name] = true;
1558 		return super.resolve(TreeNode(m_rootPackage.name, Dependency(m_rootPackage.version_)),
1559 			(m_options & UpgradeOptions.dryRun) == 0);
1560 	}
1561 
1562 	protected bool isFixedPackage(string pack)
1563 	{
1564 		return m_packagesToUpgrade !is null && pack !in m_packagesToUpgrade;
1565 	}
1566 
1567 	protected override Dependency[] getAllConfigs(string pack)
1568 	{
1569 		if (auto pvers = pack in m_packageVersions)
1570 			return *pvers;
1571 
1572 		if ((!(m_options & UpgradeOptions.upgrade) || isFixedPackage(pack)) && m_selectedVersions.hasSelectedVersion(pack)) {
1573 			auto ret = [m_selectedVersions.getSelectedVersion(pack)];
1574 			logDiagnostic("Using fixed selection %s %s", pack, ret[0]);
1575 			m_packageVersions[pack] = ret;
1576 			return ret;
1577 		}
1578 
1579 		logDiagnostic("Search for versions of %s (%s package suppliers)", pack, m_dub.m_packageSuppliers.length);
1580 		Version[] versions;
1581 		foreach (p; m_dub.packageManager.getPackageIterator(pack))
1582 			versions ~= p.version_;
1583 
1584 		foreach (ps; m_dub.m_packageSuppliers) {
1585 			try {
1586 				auto vers = ps.getVersions(pack);
1587 				vers.reverse();
1588 				if (!vers.length) {
1589 					logDiagnostic("No versions for %s for %s", pack, ps.description);
1590 					continue;
1591 				}
1592 
1593 				versions ~= vers;
1594 				break;
1595 			} catch (Exception e) {
1596 				logWarn("Package %s not found in %s: %s", pack, ps.description, e.msg);
1597 				logDebug("Full error: %s", e.toString().sanitize);
1598 			}
1599 		}
1600 
1601 		// sort by version, descending, and remove duplicates
1602 		versions = versions.sort!"a>b".uniq.array;
1603 
1604 		// move pre-release versions to the back of the list if no preRelease flag is given
1605 		if (!(m_options & UpgradeOptions.preRelease))
1606 			versions = versions.filter!(v => !v.isPreRelease).array ~ versions.filter!(v => v.isPreRelease).array;
1607 
1608 		// filter out invalid/unreachable dependency specs
1609 		versions = versions.filter!((v) {
1610 				bool valid = getPackage(pack, Dependency(v)) !is null;
1611 				if (!valid) logDiagnostic("Excluding invalid dependency specification %s %s from dependency resolution process.", pack, v);
1612 				return valid;
1613 			}).array;
1614 
1615 		if (!versions.length) logDiagnostic("Nothing found for %s", pack);
1616 		else logDiagnostic("Return for %s: %s", pack, versions);
1617 
1618 		auto ret = versions.map!(v => Dependency(v)).array;
1619 		m_packageVersions[pack] = ret;
1620 		return ret;
1621 	}
1622 
1623 	protected override Dependency[] getSpecificConfigs(string pack, TreeNodes nodes)
1624 	{
1625 		if (!nodes.configs.path.empty || !nodes.configs.repository.empty) {
1626 			if (getPackage(pack, nodes.configs)) return [nodes.configs];
1627 			else return null;
1628 		}
1629 		else return null;
1630 	}
1631 
1632 
1633 	protected override TreeNodes[] getChildren(TreeNode node)
1634 	{
1635 		if (auto pc = node in m_children)
1636 			return *pc;
1637 		auto ret = getChildrenRaw(node);
1638 		m_children[node] = ret;
1639 		return ret;
1640 	}
1641 
1642 	private final TreeNodes[] getChildrenRaw(TreeNode node)
1643 	{
1644 		import std.array : appender;
1645 		auto ret = appender!(TreeNodes[]);
1646 		auto pack = getPackage(node.pack, node.config);
1647 		if (!pack) {
1648 			// this can hapen when the package description contains syntax errors
1649 			logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config);
1650 			return null;
1651 		}
1652 		auto basepack = pack.basePackage;
1653 
1654 		foreach (d; pack.getAllDependenciesRange()) {
1655 			auto dbasename = getBasePackageName(d.name);
1656 
1657 			// detect dependencies to the root package (or sub packages thereof)
1658 			if (dbasename == basepack.name) {
1659 				auto absdeppath = d.spec.mapToPath(pack.path).path;
1660 				absdeppath.endsWithSlash = true;
1661 				auto subpack = m_dub.m_packageManager.getSubPackage(basepack, getSubPackageName(d.name), true);
1662 				if (subpack) {
1663 					auto desireddeppath = basepack.path;
1664 					desireddeppath.endsWithSlash = true;
1665 
1666 					auto altdeppath = d.name == dbasename ? basepack.path : subpack.path;
1667 					altdeppath.endsWithSlash = true;
1668 
1669 					if (!d.spec.path.empty && absdeppath != desireddeppath)
1670 						logWarn("Sub package %s, referenced by %s %s must be referenced using the path to its base package",
1671 							subpack.name, pack.name, pack.version_);
1672 
1673 					enforce(d.spec.path.empty || absdeppath == desireddeppath || absdeppath == altdeppath,
1674 						format("Dependency from %s to %s uses wrong path: %s vs. %s",
1675 							node.pack, subpack.name, absdeppath.toNativeString(), desireddeppath.toNativeString()));
1676 				}
1677 				ret ~= TreeNodes(d.name, node.config);
1678 				continue;
1679 			}
1680 
1681 			DependencyType dt;
1682 			if (d.spec.optional) {
1683 				if (d.spec.default_) dt = DependencyType.optionalDefault;
1684 				else dt = DependencyType.optional;
1685 			} else dt = DependencyType.required;
1686 
1687 			Dependency dspec = d.spec.mapToPath(pack.path);
1688 
1689 			// if not upgrading, use the selected version
1690 			if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions.hasSelectedVersion(dbasename))
1691 				dspec = m_selectedVersions.getSelectedVersion(dbasename);
1692 
1693 			// keep selected optional dependencies and avoid non-selected optional-default dependencies by default
1694 			if (!m_selectedVersions.bare) {
1695 				if (dt == DependencyType.optionalDefault && !m_selectedVersions.hasSelectedVersion(dbasename))
1696 					dt = DependencyType.optional;
1697 				else if (dt == DependencyType.optional && m_selectedVersions.hasSelectedVersion(dbasename))
1698 					dt = DependencyType.optionalDefault;
1699 			}
1700 
1701 			ret ~= TreeNodes(d.name, dspec, dt);
1702 		}
1703 		return ret.data;
1704 	}
1705 
1706 	protected override bool matches(Dependency configs, Dependency config)
1707 	{
1708 		if (!configs.path.empty) return configs.path == config.path;
1709 		return configs.merge(config).valid;
1710 	}
1711 
1712 	private Package getPackage(string name, Dependency dep)
1713 	{
1714 		auto key = PackageDependency(name, dep);
1715 		if (auto pp = key in m_packages)
1716 			return *pp;
1717 		auto p = getPackageRaw(name, dep);
1718 		m_packages[key] = p;
1719 		return p;
1720 	}
1721 
1722 	private Package getPackageRaw(string name, Dependency dep)
1723 	{
1724 		auto basename = getBasePackageName(name);
1725 
1726 		// for sub packages, first try to get them from the base package
1727 		if (basename != name) {
1728 			auto subname = getSubPackageName(name);
1729 			auto basepack = getPackage(basename, dep);
1730 			if (!basepack) return null;
1731 			if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true)) {
1732 				return sp;
1733 			} else if (!basepack.subPackages.canFind!(p => p.path.length)) {
1734 				// note: external sub packages are handled further below
1735 				auto spr = basepack.getInternalSubPackage(subname);
1736 				if (!spr.isNull) {
1737 					auto sp = new Package(spr.get, basepack.path, basepack);
1738 					m_remotePackages[sp.name] = sp;
1739 					return sp;
1740 				} else {
1741 					logDiagnostic("Sub package %s doesn't exist in %s %s.", name, basename, dep.version_);
1742 					return null;
1743 				}
1744 			} else {
1745 				logDiagnostic("External sub package %s %s not found.", name, dep.version_);
1746 				return null;
1747 			}
1748 		}
1749 
1750 		// shortcut if the referenced package is the root package
1751 		if (basename == m_rootPackage.basePackage.name)
1752 			return m_rootPackage.basePackage;
1753 
1754 		if (!dep.repository.empty) {
1755 			auto ret = m_dub.packageManager.loadSCMPackage(name, dep.repository);
1756 			return ret !is null && dep.matches(ret.version_) ? ret : null;
1757 		} else if (!dep.path.empty) {
1758 			try {
1759 				return m_dub.packageManager.getOrLoadPackage(dep.path);
1760 			} catch (Exception e) {
1761 				logDiagnostic("Failed to load path based dependency %s: %s", name, e.msg);
1762 				logDebug("Full error: %s", e.toString().sanitize);
1763 				return null;
1764 			}
1765 		}
1766 		const vers = dep.version_;
1767 
1768 		if (auto ret = m_dub.m_packageManager.getBestPackage(name, vers))
1769 			return ret;
1770 
1771 		auto key = name ~ ":" ~ vers.toString();
1772 		if (auto ret = key in m_remotePackages)
1773 			return *ret;
1774 
1775 		auto prerelease = (m_options & UpgradeOptions.preRelease) != 0;
1776 
1777 		auto rootpack = name.split(":")[0];
1778 
1779 		foreach (ps; m_dub.m_packageSuppliers) {
1780 			if (rootpack == name) {
1781 				try {
1782 					auto desc = ps.fetchPackageRecipe(name, dep, prerelease);
1783 					if (desc.type == Json.Type.null_)
1784 						continue;
1785 					auto ret = new Package(desc);
1786 					m_remotePackages[key] = ret;
1787 					return ret;
1788 				} catch (Exception e) {
1789 					logDiagnostic("Metadata for %s %s could not be downloaded from %s: %s", name, vers, ps.description, e.msg);
1790 					logDebug("Full error: %s", e.toString().sanitize);
1791 				}
1792 			} else {
1793 				logDiagnostic("Package %s not found in base package description (%s). Downloading whole package.", name, vers.toString());
1794 				try {
1795 					FetchOptions fetchOpts;
1796 					fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none;
1797 					m_dub.fetch(rootpack, vers, m_dub.defaultPlacementLocation, fetchOpts, "need sub package description");
1798 					auto ret = m_dub.m_packageManager.getBestPackage(name, vers);
1799 					if (!ret) {
1800 						logWarn("Package %s %s doesn't have a sub package %s", rootpack, dep.version_, name);
1801 						return null;
1802 					}
1803 					m_remotePackages[key] = ret;
1804 					return ret;
1805 				} catch (Exception e) {
1806 					logDiagnostic("Package %s could not be downloaded from %s: %s", rootpack, ps.description, e.msg);
1807 					logDebug("Full error: %s", e.toString().sanitize);
1808 				}
1809 			}
1810 		}
1811 
1812 		m_remotePackages[key] = null;
1813 
1814 		logWarn("Package %s %s could not be loaded either locally, or from the configured package registries.", name, dep);
1815 		return null;
1816 	}
1817 }
1818 
1819 /**
1820  * An instance of Dub that does not rely on the environment
1821  *
1822  * This instance of dub should not read any environment variables,
1823  * nor should it do any file IO, to make it usable and reliable in unittests.
1824  * Currently it reads environment variables but does not read the configuration.
1825  */
1826 package final class TestDub : Dub
1827 {
1828     /// Forward to base constructor
1829     public this (string root = ".", PackageSupplier[] extras = null,
1830                  SkipPackageSuppliers skip = SkipPackageSuppliers.none)
1831     {
1832         super(root, extras, skip);
1833     }
1834 
1835     /// Avoid loading user configuration
1836     protected override void loadConfig() { /* No-op */ }
1837 }
1838 
1839 private struct SpecialDirs {
1840 	/// The path where to store temporary files and directory
1841 	NativePath temp;
1842 	/// The system-wide dub-specific folder
1843 	NativePath systemSettings;
1844 	/// The dub-specific folder in the user home directory
1845 	NativePath userSettings;
1846 	/**
1847 	 * User location where to install packages
1848 	 *
1849 	 * On Windows, this folder, unlike `userSettings`, does not roam,
1850 	 * so an account on a company network will not save the content of this data,
1851 	 * unlike `userSettings`.
1852 	 *
1853 	 * On Posix, this is currently equivalent to `userSettings`.
1854 	 *
1855 	 * See_Also: https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid
1856 	 */
1857 	NativePath userPackages;
1858 
1859 	/**
1860 	 * Location at which build/generation artifact will be written
1861 	 *
1862 	 * All build artifacts are stored under a single build cache,
1863 	 * which is usually located under `$HOME/.dub/cache/` on POSIX,
1864 	 * and `%LOCALAPPDATA%/dub/cache` on Windows.
1865 	 *
1866 	 * Versions of dub prior to v1.31.0 used to store  artifact under the
1867 	 * project directory, but this led to issues with packages stored on
1868 	 * read-only filesystem / location, and lingering artifacts scattered
1869 	 * through the filesystem.
1870 	 */
1871 	NativePath cache;
1872 
1873 	/// Returns: An instance of `SpecialDirs` initialized from the environment
1874 	public static SpecialDirs make () {
1875 		import std.file : tempDir;
1876 
1877 		SpecialDirs result;
1878 		result.temp = NativePath(tempDir);
1879 
1880 		version(Windows) {
1881 			result.systemSettings = NativePath(environment.get("ProgramData")) ~ "dub/";
1882 			immutable appDataDir = environment.get("APPDATA");
1883 			result.userSettings = NativePath(appDataDir) ~ "dub/";
1884 			// LOCALAPPDATA is not defined before Windows Vista
1885 			result.userPackages = NativePath(environment.get("LOCALAPPDATA", appDataDir)) ~ "dub";
1886 		} else version(Posix) {
1887 			result.systemSettings = NativePath("/var/lib/dub/");
1888 			result.userSettings = NativePath(environment.get("HOME")) ~ ".dub/";
1889 			if (!result.userSettings.absolute)
1890 				result.userSettings = NativePath(getcwd()) ~ result.userSettings;
1891 			result.userPackages = result.userSettings;
1892 		}
1893 		result.cache = result.userPackages ~ "cache";
1894 		return result;
1895 	}
1896 }