1 /**
2 	Management of packages on the local computer.
3 
4 	Copyright: © 2012-2016 rejectedsoftware e.K.
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig, Matthias Dondorff
7 */
8 module dub.packagemanager;
9 
10 import dub.dependency;
11 import dub.internal.utils;
12 import dub.internal.vibecompat.core.file;
13 import dub.internal.vibecompat.data.json;
14 import dub.internal.vibecompat.inet.path;
15 import dub.internal.logging;
16 import dub.package_;
17 import dub.recipe.io;
18 import configy.Exceptions;
19 public import configy.Read : StrictMode;
20 
21 import std.algorithm : countUntil, filter, map, sort, canFind, remove;
22 import std.array;
23 import std.conv;
24 import std.digest.sha;
25 import std.encoding : sanitize;
26 import std.exception;
27 import std.file;
28 import std.range;
29 import std.string;
30 import std.sumtype;
31 import std.zip;
32 
33 
34 /// Indicates where a package has been or should be placed to.
35 public enum PlacementLocation {
36 	/// Packages retrieved with 'local' will be placed in the current folder
37 	/// using the package name as destination.
38 	local,
39 	/// Packages with 'userWide' will be placed in a folder accessible by
40 	/// all of the applications from the current user.
41 	user,
42 	/// Packages retrieved with 'systemWide' will be placed in a shared folder,
43 	/// which can be accessed by all users of the system.
44 	system,
45 }
46 
47 /// Converts a `PlacementLocation` to a string
48 public string toString (PlacementLocation loc) @safe pure nothrow @nogc
49 {
50     final switch (loc) {
51     case PlacementLocation.local:
52         return "Local";
53     case PlacementLocation.user:
54         return "User";
55     case PlacementLocation.system:
56         return "System";
57     }
58 }
59 
60 /// The PackageManager can retrieve present packages and get / remove
61 /// packages.
62 class PackageManager {
63 	private {
64 		/**
65 		 * The 'internal' location, for packages not attributable to a location.
66 		 *
67 		 * There are two uses for this:
68 		 * - In `bare` mode, the search paths are set at this scope,
69 		 *	 and packages gathered are stored in `localPackage`;
70 		 * - In the general case, any path-based or SCM-based dependency
71 		 *	 is loaded in `fromPath`;
72 		 */
73 		Location m_internal;
74 		/**
75 		 * List of locations that are managed by this `PackageManager`
76 		 *
77 		 * The `PackageManager` can be instantiated either in 'bare' mode,
78 		 * in which case this array will be empty, or in the normal mode,
79 		 * this array will have 3 entries, matching values
80 		 * in the `PlacementLocation` enum.
81 		 *
82 		 * See_Also: `Location`, `PlacementLocation`
83 		 */
84 		Location[] m_repositories;
85 	}
86 
87 	/**
88 	   Instantiate an instance with a single search path
89 
90 	   This constructor is used when dub is invoked with the '--bar' CLI switch.
91 	   The instance will not look up the default repositories
92 	   (e.g. ~/.dub/packages), using only `path` instead.
93 
94 	   Params:
95 		 path = Path of the single repository
96 	 */
97 	this(NativePath path)
98 	{
99 		this.m_internal.searchPath = [ path ];
100 		this.refresh(true);
101 	}
102 
103 	this(NativePath package_path, NativePath user_path, NativePath system_path, bool refresh_packages = true)
104 	{
105 		m_repositories = [
106 			Location(package_path ~ ".dub/packages/"),
107 			Location(user_path ~ "packages/"),
108 			Location(system_path ~ "packages/")];
109 
110 		if (refresh_packages) refresh(true);
111 	}
112 
113 	/** Gets/sets the list of paths to search for local packages.
114 	*/
115 	@property void searchPath(NativePath[] paths)
116 	{
117 		if (paths == this.m_internal.searchPath) return;
118 		this.m_internal.searchPath = paths.dup;
119 		refresh(false);
120 	}
121 	/// ditto
122 	@property const(NativePath)[] searchPath() const { return this.m_internal.searchPath; }
123 
124 	/** Returns the effective list of search paths, including default ones.
125 	*/
126 	deprecated("Use the `PackageManager` facilities instead")
127 	@property const(NativePath)[] completeSearchPath()
128 	const {
129 		auto ret = appender!(const(NativePath)[])();
130 		ret.put(this.m_internal.searchPath);
131 		foreach (ref repo; m_repositories) {
132 			ret.put(repo.searchPath);
133 			ret.put(repo.packagePath);
134 		}
135 		return ret.data;
136 	}
137 
138 	/** Sets additional (read-only) package cache paths to search for packages.
139 
140 		Cache paths have the same structure as the default cache paths, such as
141 		".dub/packages/".
142 
143 		Note that previously set custom paths will be removed when setting this
144 		property.
145 	*/
146 	@property void customCachePaths(NativePath[] custom_cache_paths)
147 	{
148 		import std.algorithm.iteration : map;
149 		import std.array : array;
150 
151 		m_repositories.length = PlacementLocation.max+1;
152 		m_repositories ~= custom_cache_paths.map!(p => Location(p)).array;
153 
154 		refresh(false);
155 	}
156 
157 
158 	/** Looks up a specific package.
159 
160 		Looks up a package matching the given version/path in the set of
161 		registered packages. The lookup order is done according the the
162 		usual rules (see getPackageIterator).
163 
164 		Params:
165 			name = The name of the package
166 			ver = The exact version of the package to query
167 			path = An exact path that the package must reside in. Note that
168 				the package must still be registered in the package manager.
169 			enable_overrides = Apply the local package override list before
170 				returning a package (enabled by default)
171 
172 		Returns:
173 			The matching package or null if no match was found.
174 	*/
175 	Package getPackage(string name, Version ver, bool enable_overrides = true)
176 	{
177 		if (enable_overrides) {
178 			foreach (ref repo; m_repositories)
179 				foreach (ovr; repo.overrides)
180 					if (ovr.package_ == name && ovr.source.matches(ver)) {
181 						Package pack = ovr.target.match!(
182 							(NativePath path) => getOrLoadPackage(path),
183 							(Version	vers) => getPackage(name, vers, false),
184 						);
185 						if (pack) return pack;
186 
187 						ovr.target.match!(
188 							(any) {
189 								logWarn("Package override %s %s -> '%s' doesn't reference an existing package.",
190 										ovr.package_, ovr.version_, any);
191 							},
192 						);
193 					}
194 		}
195 
196 		foreach (p; getPackageIterator(name))
197 			if (p.version_.matches(ver, isManagedPackage(p) ? VersionMatchMode.strict : VersionMatchMode.standard))
198 				return p;
199 
200 		return null;
201 	}
202 
203 	/// ditto
204 	deprecated("Use the overload that accepts a `Version` as second argument")
205 	Package getPackage(string name, string ver, bool enable_overrides = true)
206 	{
207 		return getPackage(name, Version(ver), enable_overrides);
208 	}
209 
210 	/// ditto
211 	deprecated("Use the overload that takes a `PlacementLocation`")
212 	Package getPackage(string name, Version ver, NativePath path)
213 	{
214 		foreach (p; getPackageIterator(name)) {
215 			auto pvm = isManagedPackage(p) ? VersionMatchMode.strict : VersionMatchMode.standard;
216 			if (p.version_.matches(ver, pvm) && p.path.startsWith(path))
217 				return p;
218 		}
219 		return null;
220 	}
221 
222 	/// Ditto
223 	Package getPackage(string name, Version ver, PlacementLocation loc)
224 	{
225 		// Bare mode
226 		if (loc >= this.m_repositories.length)
227 			return null;
228 		return this.m_repositories[loc].lookup(name, ver);
229 	}
230 
231 	/// ditto
232 	deprecated("Use the overload that accepts a `Version` as second argument")
233 	Package getPackage(string name, string ver, NativePath path)
234 	{
235 		return getPackage(name, Version(ver), path);
236 	}
237 
238 	/// ditto
239 	deprecated("Use another `PackageManager` API, open an issue if none suits you")
240 	Package getPackage(string name, NativePath path)
241 	{
242 		foreach( p; getPackageIterator(name) )
243 			if (p.path.startsWith(path))
244 				return p;
245 		return null;
246 	}
247 
248 
249 	/** Looks up the first package matching the given name.
250 	*/
251 	deprecated("Use `getBestPackage` instead")
252 	Package getFirstPackage(string name)
253 	{
254 		foreach (ep; getPackageIterator(name))
255 			return ep;
256 		return null;
257 	}
258 
259 	/** Looks up the latest package matching the given name.
260 	*/
261 	deprecated("Use `getBestPackage` with `name, Dependency.any` instead")
262 	Package getLatestPackage(string name)
263 	{
264 		Package pkg;
265 		foreach (ep; getPackageIterator(name))
266 			if (pkg is null || pkg.version_ < ep.version_)
267 				pkg = ep;
268 		return pkg;
269 	}
270 
271 	/** For a given package path, returns the corresponding package.
272 
273 		If the package is already loaded, a reference is returned. Otherwise
274 		the package gets loaded and cached for the next call to this function.
275 
276 		Params:
277 			path = NativePath to the root directory of the package
278 			recipe_path = Optional path to the recipe file of the package
279 			allow_sub_packages = Also return a sub package if it resides in the given folder
280 			mode = Whether to issue errors, warning, or ignore unknown keys in dub.json
281 
282 		Returns: The packages loaded from the given path
283 		Throws: Throws an exception if no package can be loaded
284 	*/
285 	Package getOrLoadPackage(NativePath path, NativePath recipe_path = NativePath.init,
286 		bool allow_sub_packages = false, StrictMode mode = StrictMode.Ignore)
287 	{
288 		path.endsWithSlash = true;
289 		foreach (p; this.m_internal.fromPath)
290 			if (p.path == path && (!p.parentPackage || (allow_sub_packages && p.parentPackage.path != p.path)))
291 				return p;
292 		auto pack = Package.load(path, recipe_path, null, null, mode);
293 		addPackages(this.m_internal.fromPath, pack);
294 		return pack;
295 	}
296 
297 	/** For a given SCM repository, returns the corresponding package.
298 
299 		An SCM repository is provided as its remote URL, the repository is cloned
300 		and in the dependency speicfied commit is checked out.
301 
302 		If the target directory already exists, just returns the package
303 		without cloning.
304 
305 		Params:
306 			name = Package name
307 			dependency = Dependency that contains the repository URL and a specific commit
308 
309 		Returns:
310 			The package loaded from the given SCM repository or null if the
311 			package couldn't be loaded.
312 	*/
313 	deprecated("Use the overload that accepts a `dub.dependency : Repository`")
314 	Package loadSCMPackage(string name, Dependency dependency)
315 	in { assert(!dependency.repository.empty); }
316 	do { return this.loadSCMPackage(name, dependency.repository); }
317 
318 	/// Ditto
319 	Package loadSCMPackage(string name, Repository repo)
320 	in { assert(!repo.empty); }
321 	do {
322 		Package pack;
323 
324 		final switch (repo.kind)
325 		{
326 			case repo.Kind.git:
327 				pack = loadGitPackage(name, repo);
328 		}
329 		if (pack !is null) {
330 			addPackages(this.m_internal.fromPath, pack);
331 		}
332 		return pack;
333 	}
334 
335 	private Package loadGitPackage(string name, in Repository repo)
336 	{
337 		import dub.internal.git : cloneRepository;
338 
339 		if (!repo.ref_.startsWith("~") && !repo.ref_.isGitHash) {
340 			return null;
341 		}
342 
343 		string gitReference = repo.ref_.chompPrefix("~");
344 		NativePath destination = this.getPackagePath(PlacementLocation.user, name, repo.ref_);
345 		// For libraries leaking their import path
346 		destination ~= name;
347 		destination.endsWithSlash = true;
348 
349 		foreach (p; getPackageIterator(name)) {
350 			if (p.path == destination) {
351 				return p;
352 			}
353 		}
354 
355 		if (!cloneRepository(repo.remote, gitReference, destination.toNativeString())) {
356 			return null;
357 		}
358 
359 		return Package.load(destination);
360 	}
361 
362 	/**
363 	 * Get the final destination a specific package needs to be stored in.
364 	 *
365 	 * See `Location.getPackagePath`.
366 	 */
367 	package(dub) NativePath getPackagePath (PlacementLocation base, string name, string vers)
368 	{
369 		assert(this.m_repositories.length == 3, "getPackagePath called in bare mode");
370 		return this.m_repositories[base].getPackagePath(name, vers);
371 	}
372 
373 	/** Searches for the latest version of a package matching the given dependency.
374 	*/
375 	Package getBestPackage(string name, VersionRange range = VersionRange.Any)
376 	{
377 		return this.getBestPackage(name, Dependency(range));
378 	}
379 
380 	/// Ditto
381 	Package getBestPackage(string name, Version vers)
382 	{
383 		return this.getBestPackage(name, VersionRange(vers, vers));
384 	}
385 
386 	/// Ditto
387 	Package getBestPackage(string name, Dependency version_spec, bool enable_overrides = true)
388 	{
389 		Package ret;
390 		foreach (p; getPackageIterator(name)) {
391 			auto vmm = isManagedPackage(p) ? VersionMatchMode.strict : VersionMatchMode.standard;
392 			if (version_spec.matches(p.version_, vmm) && (!ret || p.version_ > ret.version_))
393 				ret = p;
394 		}
395 
396 		if (enable_overrides && ret) {
397 			if (auto ovr = getPackage(name, ret.version_))
398 				return ovr;
399 		}
400 		return ret;
401 	}
402 
403 	/// ditto
404 	Package getBestPackage(string name, string version_spec)
405 	{
406 		return getBestPackage(name, Dependency(version_spec));
407 	}
408 
409 	/** Gets the a specific sub package.
410 
411 		In contrast to `Package.getSubPackage`, this function supports path
412 		based sub packages.
413 
414 		Params:
415 			base_package = The package from which to get a sub package
416 			sub_name = Name of the sub package (not prefixed with the base
417 				package name)
418 			silent_fail = If set to true, the function will return `null` if no
419 				package is found. Otherwise will throw an exception.
420 
421 	*/
422 	Package getSubPackage(Package base_package, string sub_name, bool silent_fail)
423 	{
424 		foreach (p; getPackageIterator(base_package.name~":"~sub_name))
425 			if (p.parentPackage is base_package)
426 				return p;
427 		enforce(silent_fail, "Sub package \""~base_package.name~":"~sub_name~"\" doesn't exist.");
428 		return null;
429 	}
430 
431 
432 	/** Determines if a package is managed by DUB.
433 
434 		Managed packages can be upgraded and removed.
435 	*/
436 	bool isManagedPackage(Package pack)
437 	const {
438 		auto ppath = pack.basePackage.path;
439 		return isManagedPath(ppath);
440 	}
441 
442 	/** Determines if a specific path is within a DUB managed package folder.
443 
444 		By default, managed folders are "~/.dub/packages" and
445 		"/var/lib/dub/packages".
446 	*/
447 	bool isManagedPath(NativePath path)
448 	const {
449 		foreach (rep; m_repositories) {
450 			NativePath rpath = rep.packagePath;
451 			if (path.startsWith(rpath))
452 				return true;
453 		}
454 		return false;
455 	}
456 
457 	/** Enables iteration over all known local packages.
458 
459 		Returns: A delegate suitable for use with `foreach` is returned.
460 	*/
461 	int delegate(int delegate(ref Package)) getPackageIterator()
462 	{
463 		int iterator(int delegate(ref Package) del)
464 		{
465 			// Search scope by priority, internal has the highest
466 			foreach (p; this.m_internal.fromPath)
467 				if (auto ret = del(p)) return ret;
468 			foreach (p; this.m_internal.localPackages)
469 				if (auto ret = del(p)) return ret;
470 
471 			foreach (ref repo; m_repositories) {
472 				foreach (p; repo.localPackages)
473 					if (auto ret = del(p)) return ret;
474 				foreach (p; repo.fromPath)
475 					if (auto ret = del(p)) return ret;
476 			}
477 			return 0;
478 		}
479 
480 		return &iterator;
481 	}
482 
483 	/** Enables iteration over all known local packages with a certain name.
484 
485 		Returns: A delegate suitable for use with `foreach` is returned.
486 	*/
487 	int delegate(int delegate(ref Package)) getPackageIterator(string name)
488 	{
489 		int iterator(int delegate(ref Package) del)
490 		{
491 			foreach (p; getPackageIterator())
492 				if (p.name == name)
493 					if (auto ret = del(p)) return ret;
494 			return 0;
495 		}
496 
497 		return &iterator;
498 	}
499 
500 
501 	/** Returns a list of all package overrides for the given scope.
502 	*/
503 	deprecated(OverrideDepMsg)
504 	const(PackageOverride)[] getOverrides(PlacementLocation scope_)
505 	const {
506 		return cast(typeof(return)) this.getOverrides_(scope_);
507 	}
508 
509 	package(dub) const(PackageOverride_)[] getOverrides_(PlacementLocation scope_)
510 	const {
511 		return m_repositories[scope_].overrides;
512 	}
513 
514 	/** Adds a new override for the given package.
515 	*/
516 	deprecated("Use the overload that accepts a `VersionRange` as 3rd argument")
517 	void addOverride(PlacementLocation scope_, string package_, Dependency version_spec, Version target)
518 	{
519 		m_repositories[scope_].overrides ~= PackageOverride(package_, version_spec, target);
520 		m_repositories[scope_].writeOverrides();
521 	}
522 	/// ditto
523 	deprecated("Use the overload that accepts a `VersionRange` as 3rd argument")
524 	void addOverride(PlacementLocation scope_, string package_, Dependency version_spec, NativePath target)
525 	{
526 		m_repositories[scope_].overrides ~= PackageOverride(package_, version_spec, target);
527 		m_repositories[scope_].writeOverrides();
528 	}
529 
530 	/// Ditto
531 	deprecated(OverrideDepMsg)
532 	void addOverride(PlacementLocation scope_, string package_, VersionRange source, Version target)
533 	{
534 		this.addOverride_(scope_, package_, source, target);
535 	}
536 	/// ditto
537 	deprecated(OverrideDepMsg)
538 	void addOverride(PlacementLocation scope_, string package_, VersionRange source, NativePath target)
539 	{
540 		this.addOverride_(scope_, package_, source, target);
541 	}
542 
543 	// Non deprecated version that is used by `commandline`. Do not use!
544 	package(dub) void addOverride_(PlacementLocation scope_, string package_, VersionRange source, Version target)
545 	{
546 		m_repositories[scope_].overrides ~= PackageOverride_(package_, source, target);
547 		m_repositories[scope_].writeOverrides();
548 	}
549 	// Non deprecated version that is used by `commandline`. Do not use!
550 	package(dub) void addOverride_(PlacementLocation scope_, string package_, VersionRange source, NativePath target)
551 	{
552 		m_repositories[scope_].overrides ~= PackageOverride_(package_, source, target);
553 		m_repositories[scope_].writeOverrides();
554 	}
555 
556 	/** Removes an existing package override.
557 	*/
558 	deprecated("Use the overload that accepts a `VersionRange` as 3rd argument")
559 	void removeOverride(PlacementLocation scope_, string package_, Dependency version_spec)
560 	{
561 		version_spec.visit!(
562 			(VersionRange src) => this.removeOverride(scope_, package_, src),
563 			(any) { throw new Exception(format("No override exists for %s %s", package_, version_spec)); },
564 		);
565 	}
566 
567 	deprecated(OverrideDepMsg)
568 	void removeOverride(PlacementLocation scope_, string package_, VersionRange src)
569 	{
570 		this.removeOverride_(scope_, package_, src);
571 	}
572 
573 	package(dub) void removeOverride_(PlacementLocation scope_, string package_, VersionRange src)
574 	{
575 		Location* rep = &m_repositories[scope_];
576 		foreach (i, ovr; rep.overrides) {
577 			if (ovr.package_ != package_ || ovr.source != src)
578 				continue;
579 			rep.overrides = rep.overrides[0 .. i] ~ rep.overrides[i+1 .. $];
580 			(*rep).writeOverrides();
581 			return;
582 		}
583 		throw new Exception(format("No override exists for %s %s", package_, src));
584 	}
585 
586 	deprecated("Use `store(NativePath source, PlacementLocation dest, string name, Version vers)`")
587 	Package storeFetchedPackage(NativePath zip_file_path, Json package_info, NativePath destination)
588 	{
589 		return this.store_(zip_file_path, destination, package_info["name"].get!string,
590 			Version(package_info["version"].get!string));
591 	}
592 
593 	/**
594 	 * Store a zip file stored at `src` into a managed location `destination`
595 	 *
596 	 * This will extracts the package supplied as (as a zip file) to the
597 	 * `destination` and sets a version field in the package description.
598 	 * In the future, we should aim not to alter the package description,
599 	 * but this is done for backward compatibility.
600 	 *
601 	 * Params:
602 	 *   src = The path to the zip file containing the package
603 	 *   dest = At which `PlacementLocation`  the package should be stored
604 	 *   name = Name of the package being stored
605 	 *   vers = Version of the package
606 	 *
607 	 * Returns:
608 	 *   The `Package` after it has been loaded.
609 	 *
610 	 * Throws:
611 	 *   If the package cannot be loaded / the zip is corrupted / the package
612 	 *   already exists, etc...
613 	 */
614 	Package store(NativePath src, PlacementLocation dest, string name, Version vers)
615 	{
616 		NativePath dstpath = this.getPackagePath(dest, name, vers.toString());
617 		if (!dstpath.existsFile())
618 			mkdirRecurse(dstpath.toNativeString());
619 		// For libraries leaking their import path
620 		dstpath = dstpath ~ name;
621 
622 		// possibly wait for other dub instance
623 		import core.time : seconds;
624 		auto lock = lockFile(dstpath.toNativeString() ~ ".lock", 30.seconds);
625 		if (dstpath.existsFile()) {
626 			this.refresh(false);
627 			return this.getPackage(name, vers, dest);
628 		}
629 		return this.store_(src, dstpath, name, vers);
630 	}
631 
632 	/// Backward-compatibility for deprecated overload, simplify once `storeFetchedPatch`
633 	/// is removed
634 	private Package store_(NativePath src, NativePath destination, string name, Version vers)
635 	{
636 		import std.range : walkLength;
637 
638 		logDebug("Placing package '%s' version '%s' to location '%s' from file '%s'",
639 			name, vers, destination.toNativeString(), src.toNativeString());
640 
641 		if( existsFile(destination) ){
642 			throw new Exception(format("%s (%s) needs to be removed from '%s' prior placement.",
643 				name, vers, destination));
644 		}
645 
646 		// open zip file
647 		ZipArchive archive;
648 		{
649 			logDebug("Opening file %s", src);
650 			auto f = openFile(src, FileMode.read);
651 			scope(exit) f.close();
652 			archive = new ZipArchive(f.readAll());
653 		}
654 
655 		logDebug("Extracting from zip.");
656 
657 		// In a github zip, the actual contents are in a subfolder
658 		alias PSegment = typeof(NativePath.init.head);
659 		PSegment[] zip_prefix;
660 		outer: foreach(ArchiveMember am; archive.directory) {
661 			auto path = NativePath(am.name).bySegment.array;
662 			foreach (fil; packageInfoFiles)
663 				if (path.length == 2 && path[$-1].name == fil.filename) {
664 					zip_prefix = path[0 .. $-1];
665 					break outer;
666 				}
667 		}
668 
669 		logDebug("zip root folder: %s", zip_prefix);
670 
671 		NativePath getCleanedPath(string fileName) {
672 			auto path = NativePath(fileName);
673 			if (zip_prefix.length && !path.bySegment.startsWith(zip_prefix)) return NativePath.init;
674 			static if (is(typeof(path[0 .. 1]))) return path[zip_prefix.length .. $];
675 			else return NativePath(path.bySegment.array[zip_prefix.length .. $]);
676 		}
677 
678 		static void setAttributes(string path, ArchiveMember am)
679 		{
680 			import std.datetime : DosFileTimeToSysTime;
681 
682 			auto mtime = DosFileTimeToSysTime(am.time);
683 			setTimes(path, mtime, mtime);
684 			if (auto attrs = am.fileAttributes)
685 				std.file.setAttributes(path, attrs);
686 		}
687 
688 		// extract & place
689 		mkdirRecurse(destination.toNativeString());
690 		logDebug("Copying all files...");
691 		int countFiles = 0;
692 		foreach(ArchiveMember a; archive.directory) {
693 			auto cleanedPath = getCleanedPath(a.name);
694 			if(cleanedPath.empty) continue;
695 			auto dst_path = destination ~ cleanedPath;
696 
697 			logDebug("Creating %s", cleanedPath);
698 			if( dst_path.endsWithSlash ){
699 				if( !existsDirectory(dst_path) )
700 					mkdirRecurse(dst_path.toNativeString());
701 			} else {
702 				if( !existsDirectory(dst_path.parentPath) )
703 					mkdirRecurse(dst_path.parentPath.toNativeString());
704 				// for symlinks on posix systems, use the symlink function to
705 				// create them. Windows default unzip doesn't handle symlinks,
706 				// so we don't need to worry about it for Windows.
707 				version(Posix) {
708 					import core.sys.posix.sys.stat;
709 					if( S_ISLNK(cast(mode_t)a.fileAttributes) ){
710 						import core.sys.posix.unistd;
711 						// need to convert name and target to zero-terminated string
712 						auto target = toStringz(cast(const(char)[])archive.expand(a));
713 						auto dstFile = toStringz(dst_path.toNativeString());
714 						enforce(symlink(target, dstFile) == 0, "Error creating symlink: " ~ dst_path.toNativeString());
715 						goto symlink_exit;
716 					}
717 				}
718 
719 				{
720 					auto dstFile = openFile(dst_path, FileMode.createTrunc);
721 					scope(exit) dstFile.close();
722 					dstFile.put(archive.expand(a));
723 				}
724 				setAttributes(dst_path.toNativeString(), a);
725 symlink_exit:
726 				++countFiles;
727 			}
728 		}
729 		logDebug("%s file(s) copied.", to!string(countFiles));
730 
731 		// overwrite dub.json (this one includes a version field)
732 		auto pack = Package.load(destination, NativePath.init, null, vers.toString());
733 
734 		if (pack.recipePath.head != defaultPackageFilename)
735 			// Storeinfo saved a default file, this could be different to the file from the zip.
736 			removeFile(pack.recipePath);
737 		pack.storeInfo();
738 		addPackages(this.m_internal.localPackages, pack);
739 		return pack;
740 	}
741 
742 	/// Removes the given the package.
743 	void remove(in Package pack)
744 	{
745 		logDebug("Remove %s, version %s, path '%s'", pack.name, pack.version_, pack.path);
746 		enforce(!pack.path.empty, "Cannot remove package "~pack.name~" without a path.");
747 
748 		// remove package from repositories' list
749 		bool found = false;
750 		bool removeFrom(Package[] packs, in Package pack) {
751 			auto packPos = countUntil!("a.path == b.path")(packs, pack);
752 			if(packPos != -1) {
753 				packs = .remove(packs, packPos);
754 				return true;
755 			}
756 			return false;
757 		}
758 		foreach(repo; m_repositories) {
759 			if (removeFrom(repo.fromPath, pack)) {
760 				found = true;
761 				break;
762 			}
763 			// Maintain backward compatibility with pre v1.30.0 behavior,
764 			// this is equivalent to remove-local
765 			if (removeFrom(repo.localPackages, pack)) {
766 				found = true;
767 				break;
768 			}
769 		}
770 		if(!found)
771 			found = removeFrom(this.m_internal.localPackages, pack);
772 		enforce(found, "Cannot remove, package not found: '"~ pack.name ~"', path: " ~ to!string(pack.path));
773 
774 		logDebug("About to delete root folder for package '%s'.", pack.path);
775 		rmdirRecurse(pack.path.toNativeString());
776 		logInfo("Removed", Color.yellow, "%s %s", pack.name.color(Mode.bold), pack.version_);
777 	}
778 
779 	/// Compatibility overload. Use the version without a `force_remove` argument instead.
780 	deprecated("Use `remove(pack)` directly instead, the boolean has no effect")
781 	void remove(in Package pack, bool force_remove)
782 	{
783 		remove(pack);
784 	}
785 
786 	Package addLocalPackage(NativePath path, string verName, PlacementLocation type)
787 	{
788 		path.endsWithSlash = true;
789 		auto pack = Package.load(path);
790 		enforce(pack.name.length, "The package has no name, defined in: " ~ path.toString());
791 		if (verName.length)
792 			pack.version_ = Version(verName);
793 
794 		// don't double-add packages
795 		Package[]* packs = &m_repositories[type].localPackages;
796 		foreach (p; *packs) {
797 			if (p.path == path) {
798 				enforce(p.version_ == pack.version_, "Adding the same local package twice with differing versions is not allowed.");
799 				logInfo("Package is already registered: %s (version: %s)", p.name, p.version_);
800 				return p;
801 			}
802 		}
803 
804 		addPackages(*packs, pack);
805 
806 		this.m_repositories[type].writeLocalPackageList();
807 
808 		logInfo("Registered package: %s (version: %s)", pack.name, pack.version_);
809 		return pack;
810 	}
811 
812 	void removeLocalPackage(NativePath path, PlacementLocation type)
813 	{
814 		path.endsWithSlash = true;
815 
816 		Package[]* packs = &m_repositories[type].localPackages;
817 		size_t[] to_remove;
818 		foreach( i, entry; *packs )
819 			if( entry.path == path )
820 				to_remove ~= i;
821 		enforce(to_remove.length > 0, "No "~type.to!string()~" package found at "~path.toNativeString());
822 
823 		string[Version] removed;
824 		foreach (i; to_remove)
825 			removed[(*packs)[i].version_] = (*packs)[i].name;
826 
827 		*packs = (*packs).enumerate
828 			.filter!(en => !to_remove.canFind(en.index))
829 			.map!(en => en.value).array;
830 
831 		this.m_repositories[type].writeLocalPackageList();
832 
833 		foreach(ver, name; removed)
834 			logInfo("Deregistered package: %s (version: %s)", name, ver);
835 	}
836 
837 	/// For the given type add another path where packages will be looked up.
838 	void addSearchPath(NativePath path, PlacementLocation type)
839 	{
840 		m_repositories[type].searchPath ~= path;
841 		this.m_repositories[type].writeLocalPackageList();
842 	}
843 
844 	/// Removes a search path from the given type.
845 	void removeSearchPath(NativePath path, PlacementLocation type)
846 	{
847 		m_repositories[type].searchPath = m_repositories[type].searchPath.filter!(p => p != path)().array();
848 		this.m_repositories[type].writeLocalPackageList();
849 	}
850 
851 	void refresh(bool refresh)
852 	{
853 		logDiagnostic("Refreshing local packages (refresh existing: %s)...", refresh);
854 
855 		foreach (ref repository; this.m_repositories)
856 			repository.scanLocalPackages(refresh, this);
857 
858 		this.m_internal.scan(this, refresh);
859 		foreach (ref repository; this.m_repositories)
860 			repository.scan(this, refresh);
861 
862 		foreach (ref repository; this.m_repositories)
863 			repository.loadOverrides();
864 	}
865 
866 	alias Hash = ubyte[];
867 	/// Generates a hash digest for a given package.
868 	/// Some files or folders are ignored during the generation (like .dub and
869 	/// .svn folders)
870 	Hash hashPackage(Package pack)
871 	{
872 		string[] ignored_directories = [".git", ".dub", ".svn"];
873 		// something from .dub_ignore or what?
874 		string[] ignored_files = [];
875 		SHA256 hash;
876 		foreach(file; dirEntries(pack.path.toNativeString(), SpanMode.depth)) {
877 			const isDir = file.isDir;
878 			if(isDir && ignored_directories.canFind(NativePath(file.name).head.name))
879 				continue;
880 			else if(ignored_files.canFind(NativePath(file.name).head.name))
881 				continue;
882 
883 			hash.put(cast(ubyte[])NativePath(file.name).head.name);
884 			if(isDir) {
885 				logDebug("Hashed directory name %s", NativePath(file.name).head);
886 			}
887 			else {
888 				hash.put(openFile(NativePath(file.name)).readAll());
889 				logDebug("Hashed file contents from %s", NativePath(file.name).head);
890 			}
891 		}
892 		auto digest = hash.finish();
893 		logDebug("Project hash: %s", digest);
894 		return digest[].dup;
895 	}
896 
897 	/// Adds the package and scans for subpackages.
898 	private void addPackages(ref Package[] dst_repos, Package pack)
899 	const {
900 		// Add the main package.
901 		dst_repos ~= pack;
902 
903 		// Additionally to the internally defined subpackages, whose metadata
904 		// is loaded with the main dub.json, load all externally defined
905 		// packages after the package is available with all the data.
906 		foreach (spr; pack.subPackages) {
907 			Package sp;
908 
909 			if (spr.path.length) {
910 				auto p = NativePath(spr.path);
911 				p.normalize();
912 				enforce(!p.absolute, "Sub package paths must be sub paths of the parent package.");
913 				auto path = pack.path ~ p;
914 				if (!existsFile(path)) {
915 					logError("Package %s declared a sub-package, definition file is missing: %s", pack.name, path.toNativeString());
916 					continue;
917 				}
918 				sp = Package.load(path, NativePath.init, pack);
919 			} else sp = new Package(spr.recipe, pack.path, pack);
920 
921 			// Add the subpackage.
922 			try {
923 				dst_repos ~= sp;
924 			} catch (Exception e) {
925 				logError("Package '%s': Failed to load sub-package %s: %s", pack.name,
926 					spr.path.length ? spr.path : spr.recipe.name, e.msg);
927 				logDiagnostic("Full error: %s", e.toString().sanitize());
928 			}
929 		}
930 	}
931 }
932 
933 deprecated(OverrideDepMsg)
934 alias PackageOverride = PackageOverride_;
935 
936 package(dub) struct PackageOverride_ {
937 	private alias ResolvedDep = SumType!(NativePath, Version);
938 	string package_;
939 	VersionRange source;
940 	ResolvedDep target;
941 
942 	deprecated("Use `source` instead")
943 	@property inout(Dependency) version_ () inout return @safe {
944 		return Dependency(this.source);
945 	}
946 
947 	deprecated("Assign `source` instead")
948 	@property ref PackageOverride version_ (Dependency v) scope return @safe pure {
949 		this.source = v.visit!(
950 			(VersionRange range) => range,
951 			(any) {
952 				int a; if (a) return VersionRange.init; // Trick the compiler
953 				throw new Exception("Cannot use anything else than a `VersionRange` for overrides");
954 			},
955 		);
956 		return this;
957 	}
958 
959 	deprecated("Use `target.match` directly instead")
960 	@property inout(Version) targetVersion () inout return @safe pure nothrow @nogc {
961 		return this.target.match!(
962 			(Version v) => v,
963 			(any) => Version.init,
964 		);
965 	}
966 
967 	deprecated("Assign `target` directly instead")
968 	@property ref PackageOverride targetVersion (Version v) scope return pure nothrow @nogc {
969 		this.target = v;
970 		return this;
971 	}
972 
973 	deprecated("Use `target.match` directly instead")
974 	@property inout(NativePath) targetPath () inout return @safe pure nothrow @nogc {
975 		return this.target.match!(
976 			(NativePath v) => v,
977 			(any) => NativePath.init,
978 		);
979 	}
980 
981 	deprecated("Assign `target` directly instead")
982 	@property ref PackageOverride targetPath (NativePath v) scope return pure nothrow @nogc {
983 		this.target = v;
984 		return this;
985 	}
986 
987 	deprecated("Use the overload that accepts a `VersionRange` as 2nd argument")
988 	this(string package_, Dependency version_, Version target_version)
989 	{
990 		this.package_ = package_;
991 		this.version_ = version_;
992 		this.target = target_version;
993 	}
994 
995 	deprecated("Use the overload that accepts a `VersionRange` as 2nd argument")
996 	this(string package_, Dependency version_, NativePath target_path)
997 	{
998 		this.package_ = package_;
999 		this.version_ = version_;
1000 		this.target = target_path;
1001 	}
1002 
1003 	this(string package_, VersionRange src, Version target)
1004 	{
1005 		this.package_ = package_;
1006 		this.source = src;
1007 		this.target = target;
1008 	}
1009 
1010 	this(string package_, VersionRange src, NativePath target)
1011 	{
1012 		this.package_ = package_;
1013 		this.source = src;
1014 		this.target = target;
1015 	}
1016 }
1017 
1018 deprecated("Use `PlacementLocation` instead")
1019 enum LocalPackageType : PlacementLocation {
1020 	package_ = PlacementLocation.local,
1021 	user     = PlacementLocation.user,
1022 	system   = PlacementLocation.system,
1023 }
1024 
1025 private enum LocalPackagesFilename = "local-packages.json";
1026 private enum LocalOverridesFilename = "local-overrides.json";
1027 
1028 /**
1029  * A managed location, with packages, configuration, and overrides
1030  *
1031  * There exists three standards locations, listed in `PlacementLocation`.
1032  * The user one is the default, with the system and local one meeting
1033  * different needs.
1034  *
1035  * Each location has a root, under which the following may be found:
1036  * - A `packages/` directory, where packages are stored (see `packagePath`);
1037  * - A `local-packages.json` file, with extra search paths
1038  *   and manually added packages (see `dub add-local`);
1039  * - A `local-overrides.json` file, with manually added overrides (`dub add-override`);
1040  *
1041  * Additionally, each location host a config file,
1042  * which is not managed by this module, but by dub itself.
1043  */
1044 private struct Location {
1045 	/// The absolute path to the root of the location
1046 	NativePath packagePath;
1047 
1048 	/// Configured (extra) search paths for this `Location`
1049 	NativePath[] searchPath;
1050 
1051 	/**
1052 	 * List of manually registered packages at this `Location`
1053 	 * and stored in `local-packages.json`
1054 	 */
1055 	Package[] localPackages;
1056 
1057 	/// List of overrides stored at this `Location`
1058 	PackageOverride_[] overrides;
1059 
1060 	/**
1061 	 * List of packages stored under `packagePath` and automatically detected
1062 	 */
1063 	Package[] fromPath;
1064 
1065 	this(NativePath path) @safe pure nothrow @nogc
1066 	{
1067 		this.packagePath = path;
1068 	}
1069 
1070 	void loadOverrides()
1071 	{
1072 		this.overrides = null;
1073 		auto ovrfilepath = this.packagePath ~ LocalOverridesFilename;
1074 		if (existsFile(ovrfilepath)) {
1075 			logWarn("Found local override file: %s", ovrfilepath);
1076 			logWarn(OverrideDepMsg);
1077 			logWarn("Replace with a path-based dependency in your project or a custom cache path");
1078 			foreach (entry; jsonFromFile(ovrfilepath)) {
1079 				PackageOverride_ ovr;
1080 				ovr.package_ = entry["name"].get!string;
1081 				ovr.source = VersionRange.fromString(entry["version"].get!string);
1082 				if (auto pv = "targetVersion" in entry) ovr.target = Version(pv.get!string);
1083 				if (auto pv = "targetPath" in entry) ovr.target = NativePath(pv.get!string);
1084 				this.overrides ~= ovr;
1085 			}
1086 		}
1087 	}
1088 
1089 	private void writeOverrides()
1090 	{
1091 		Json[] newlist;
1092 		foreach (ovr; this.overrides) {
1093 			auto jovr = Json.emptyObject;
1094 			jovr["name"] = ovr.package_;
1095 			jovr["version"] = ovr.source.toString();
1096 			ovr.target.match!(
1097 				(NativePath path) { jovr["targetPath"] = path.toNativeString(); },
1098 				(Version	vers) { jovr["targetVersion"] = vers.toString(); },
1099 			);
1100 			newlist ~= jovr;
1101 		}
1102 		auto path = this.packagePath;
1103 		if (!existsDirectory(path)) mkdirRecurse(path.toNativeString());
1104 		writeJsonFile(path ~ LocalOverridesFilename, Json(newlist));
1105 	}
1106 
1107 	private void writeLocalPackageList()
1108 	{
1109 		Json[] newlist;
1110 		foreach (p; this.searchPath) {
1111 			auto entry = Json.emptyObject;
1112 			entry["name"] = "*";
1113 			entry["path"] = p.toNativeString();
1114 			newlist ~= entry;
1115 		}
1116 
1117 		foreach (p; this.localPackages) {
1118 			if (p.parentPackage) continue; // do not store sub packages
1119 			auto entry = Json.emptyObject;
1120 			entry["name"] = p.name;
1121 			entry["version"] = p.version_.toString();
1122 			entry["path"] = p.path.toNativeString();
1123 			newlist ~= entry;
1124 		}
1125 
1126 		NativePath path = this.packagePath;
1127 		if( !existsDirectory(path) ) mkdirRecurse(path.toNativeString());
1128 		writeJsonFile(path ~ LocalPackagesFilename, Json(newlist));
1129 	}
1130 
1131 	// load locally defined packages
1132 	void scanLocalPackages(bool refresh, PackageManager manager)
1133 	{
1134 		NativePath list_path = this.packagePath;
1135 		Package[] packs;
1136 		NativePath[] paths;
1137 		try {
1138 			auto local_package_file = list_path ~ LocalPackagesFilename;
1139 			if (!existsFile(local_package_file)) return;
1140 
1141 			logDiagnostic("Loading local package map at %s", local_package_file.toNativeString());
1142 			auto packlist = jsonFromFile(local_package_file);
1143 			enforce(packlist.type == Json.Type.array, LocalPackagesFilename ~ " must contain an array.");
1144 			foreach (pentry; packlist) {
1145 				try {
1146 					auto name = pentry["name"].get!string;
1147 					auto path = NativePath(pentry["path"].get!string);
1148 					if (name == "*") {
1149 						paths ~= path;
1150 					} else {
1151 						auto ver = Version(pentry["version"].get!string);
1152 
1153 						Package pp;
1154 						if (!refresh) {
1155 							foreach (p; this.localPackages)
1156 								if (p.path == path) {
1157 									pp = p;
1158 									break;
1159 								}
1160 						}
1161 
1162 						if (!pp) {
1163 							auto infoFile = Package.findPackageFile(path);
1164 							if (!infoFile.empty) pp = Package.load(path, infoFile);
1165 							else {
1166 								logWarn("Locally registered package %s %s was not found. Please run 'dub remove-local \"%s\"'.",
1167 										name, ver, path.toNativeString());
1168 								// Store a dummy package
1169 								pp = new Package(PackageRecipe(name), path);
1170 							}
1171 						}
1172 
1173 						if (pp.name != name)
1174 							logWarn("Local package at %s has different name than %s (%s)", path.toNativeString(), name, pp.name);
1175 						pp.version_ = ver;
1176 						manager.addPackages(packs, pp);
1177 					}
1178 				} catch (Exception e) {
1179 					logWarn("Error adding local package: %s", e.msg);
1180 				}
1181 			}
1182 		} catch (Exception e) {
1183 			logDiagnostic("Loading of local package list at %s failed: %s", list_path.toNativeString(), e.msg);
1184 		}
1185 		this.localPackages = packs;
1186 		this.searchPath = paths;
1187 	}
1188 
1189 	/**
1190 	 * Scan this location
1191 	 */
1192 	void scan(PackageManager mgr, bool refresh)
1193 	{
1194 		// If we're asked to refresh, reload the packages from scratch
1195 		auto existing = refresh ? null : this.fromPath;
1196 		if (this.packagePath !is NativePath.init) {
1197 			// For the internal location, we use `fromPath` to store packages
1198 			// loaded by the user (e.g. the project and its subpackages),
1199 			// so don't clean it.
1200 			this.fromPath = null;
1201 		}
1202 		foreach (path; this.searchPath)
1203 			this.scanPackageFolder(path, mgr, existing);
1204 		if (this.packagePath !is NativePath.init)
1205 			this.scanPackageFolder(this.packagePath, mgr, existing);
1206 	}
1207 
1208     /**
1209      * Scan the content of a folder (`packagePath` or in `searchPaths`),
1210      * and add all packages that were found to this location.
1211      */
1212 	void scanPackageFolder(NativePath path, PackageManager mgr,
1213 		Package[] existing_packages)
1214 	{
1215 		if (!path.existsDirectory())
1216 			return;
1217 
1218 		logDebug("iterating dir %s", path.toNativeString());
1219 		try foreach (pdir; iterateDirectory(path)) {
1220 			logDebug("iterating dir %s entry %s", path.toNativeString(), pdir.name);
1221 			if (!pdir.isDirectory) continue;
1222 
1223 			// Old / flat directory structure, used in non-standard path
1224 			// Packages are stored in $ROOT/$SOMETHING/`
1225 			auto pack_path = path ~ (pdir.name ~ "/");
1226 			auto packageFile = Package.findPackageFile(pack_path);
1227 
1228 			// New (since 2015) managed structure:
1229 			// $ROOT/$NAME-$VERSION/$NAME
1230 			// This is the most common code path
1231 			if (mgr.isManagedPath(path) && packageFile.empty) {
1232 				foreach (subdir; iterateDirectory(path ~ (pdir.name ~ "/")))
1233 					if (subdir.isDirectory && pdir.name.startsWith(subdir.name)) {
1234 						pack_path ~= subdir.name ~ "/";
1235 						packageFile = Package.findPackageFile(pack_path);
1236 						break;
1237 					}
1238 			}
1239 
1240 			if (packageFile.empty) continue;
1241 			Package p;
1242 			try {
1243 				foreach (pp; existing_packages)
1244 					if (pp.path == pack_path) {
1245 						p = pp;
1246 						break;
1247 					}
1248 				if (!p) p = Package.load(pack_path, packageFile);
1249 				mgr.addPackages(this.fromPath, p);
1250 			} catch (ConfigException exc) {
1251 				// Confiy error message already include the path
1252 				logError("Invalid recipe for local package: %S", exc);
1253 			} catch (Exception e) {
1254 				logError("Failed to load package in %s: %s", pack_path, e.msg);
1255 				logDiagnostic("Full error: %s", e.toString().sanitize());
1256 			}
1257 		}
1258 		catch (Exception e)
1259 			logDiagnostic("Failed to enumerate %s packages: %s", path.toNativeString(), e.toString());
1260 	}
1261 
1262 	/**
1263 	 * Looks up already-loaded packages at a specific version
1264 	 *
1265 	 * Looks up a package according to this `Location`'s priority,
1266 	 * that is, packages from the search path and local packages
1267 	 * have the highest priority.
1268 	 *
1269 	 * Params:
1270 	 *	 name = The full name of the package to look up
1271 	 *	 ver  = The version to look up
1272 	 *
1273 	 * Returns:
1274 	 *	 A `Package` if one was found, `null` if none exists.
1275 	 */
1276 	private inout(Package) lookup(string name, Version ver) inout {
1277 		foreach (pkg; this.localPackages)
1278 			if (pkg.name == name && pkg.version_.matches(ver, VersionMatchMode.strict))
1279 				return pkg;
1280 		foreach (pkg; this.fromPath)
1281 			if (pkg.name == name && pkg.version_.matches(ver, VersionMatchMode.strict))
1282 				return pkg;
1283 		return null;
1284 	}
1285 
1286 	/**
1287 	 * Get the final destination a specific package needs to be stored in.
1288 	 *
1289 	 * Note that there needs to be an extra level for libraries like `ae`
1290 	 * which expects their containing folder to have an exact name and use
1291 	 * `importPath "../"`.
1292 	 *
1293 	 * Hence the final format should be `$BASE/$NAME-$VERSION/$NAME`,
1294 	 * but this function returns `$BASE/$NAME-$VERSION/`
1295 	 * `$BASE` is `this.packagePath`.
1296 	 */
1297 	private NativePath getPackagePath (string name, string vers)
1298 	{
1299 		// + has special meaning for Optlink
1300 		string clean_vers = vers.chompPrefix("~").replace("+", "_");
1301 		NativePath result = this.packagePath ~ (name ~ "-" ~ clean_vers);
1302 		result.endsWithSlash = true;
1303 		return result;
1304 	}
1305 }
1306 
1307 private immutable string OverrideDepMsg =
1308 	"Overrides are deprecated as they are redundant with more fine-grained approaches";