1 /**
2 	Dependency specification functionality.
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.dependency;
9 
10 import dub.internal.utils;
11 import dub.internal.vibecompat.core.file;
12 import dub.internal.vibecompat.data.json;
13 import dub.internal.vibecompat.inet.path;
14 import dub.package_;
15 import dub.semver;
16 import dub.internal.logging;
17 
18 import dub.internal.dyaml.stdsumtype;
19 
20 import std.algorithm;
21 import std.array;
22 import std.exception;
23 import std.string;
24 
25 
26 /** Encapsulates the name of a package along with its dependency specification.
27 */
28 struct PackageDependency {
29 	/// Name of the referenced package.
30 	string name;
31 
32 	/// Dependency specification used to select a particular version of the package.
33 	Dependency spec;
34 }
35 
36 /**
37 	Represents a dependency specification.
38 
39 	A dependency specification either represents a specific version or version
40 	range, or a path to a package. In addition to that it has `optional` and
41 	`default_` flags to control how non-mandatory dependencies are handled. The
42 	package name is notably not part of the dependency specification.
43 */
44 struct Dependency {
45 	/// We currently support 3 'types'
46 	private alias Value = SumType!(VersionRange, NativePath, Repository);
47 
48 	/// Used by `toString`
49 	private static immutable string[] BooleanOptions = [ "optional", "default" ];
50 
51 	// Shortcut to create >=0.0.0
52 	private enum ANY_IDENT = "*";
53 
54 	private Value m_value;
55 	private bool m_optional;
56 	private bool m_default;
57 
58 	/// A Dependency, which matches every valid version.
59 	static @property Dependency any() @safe { return Dependency(VersionRange.Any); }
60 
61 	/// An invalid dependency (with no possible version matches).
62 	static @property Dependency invalid() @safe
63 	{
64 		return Dependency(VersionRange.Invalid);
65 	}
66 
67 	/** Constructs a new dependency specification that matches a specific
68 		path.
69 	*/
70 	this(NativePath path) @safe
71 	{
72 		this.m_value = path;
73 	}
74 
75 	/** Constructs a new dependency specification that matches a specific
76 		Git reference.
77 	*/
78 	this(Repository repository) @safe
79 	{
80 		this.m_value = repository;
81 	}
82 
83 	/** Constructs a new dependency specification from a string
84 
85 		See the `versionSpec` property for a description of the accepted
86 		contents of that string.
87 	*/
88 	this(string spec) @safe
89 	{
90 		this(VersionRange.fromString(spec));
91 	}
92 
93 	/** Constructs a new dependency specification that matches a specific
94 		version.
95 	*/
96 	this(const Version ver) @safe
97 	{
98 		this(VersionRange(ver, ver));
99 	}
100 
101 	/// Construct a version from a range of possible values
102 	this (VersionRange rng) @safe
103 	{
104 		this.m_value = rng;
105 	}
106 
107 	deprecated("Instantiate the `Repository` struct with the string directy")
108 	this(Repository repository, string spec) @safe
109 	{
110 		assert(repository.m_ref is null);
111 		repository.m_ref = spec;
112 		this(repository);
113 	}
114 
115 	/// If set, overrides any version based dependency selection.
116 	deprecated("Construct a new `Dependency` object instead")
117 	@property void path(NativePath value) @trusted
118 	{
119 		this.m_value = value;
120 	}
121 	/// ditto
122 	@property NativePath path() const @safe
123 	{
124 		return this.m_value.match!(
125 			(const NativePath p) => p,
126 			(      any         ) => NativePath.init,
127 		);
128 	}
129 
130 	/// If set, overrides any version based dependency selection.
131 	deprecated("Construct a new `Dependency` object instead")
132 	@property void repository(Repository value) @trusted
133 	{
134 		this.m_value = value;
135 	}
136 	/// ditto
137 	@property Repository repository() const @safe
138 	{
139 		return this.m_value.match!(
140 			(const Repository p) => p,
141 			(      any         ) => Repository.init,
142 		);
143 	}
144 
145 	/// Determines if the dependency is required or optional.
146 	@property bool optional() const scope @safe pure nothrow @nogc
147 	{
148 		return m_optional;
149 	}
150 	/// ditto
151 	@property void optional(bool optional) scope @safe pure nothrow @nogc
152 	{
153 		m_optional = optional;
154 	}
155 
156 	/// Determines if an optional dependency should be chosen by default.
157 	@property bool default_() const scope @safe pure nothrow @nogc
158 	{
159 		return m_default;
160 	}
161 	/// ditto
162 	@property void default_(bool value) scope @safe pure nothrow @nogc
163 	{
164 		m_default = value;
165 	}
166 
167 	/// Returns true $(I iff) the version range only matches a specific version.
168 	@property bool isExactVersion() const scope @safe
169 	{
170 		return this.m_value.match!(
171 			(NativePath v) => false,
172 			(Repository v) => false,
173 			(VersionRange v) => v.isExactVersion(),
174 		);
175 	}
176 
177 	/// Returns the exact version matched by the version range.
178 	@property Version version_() const @safe {
179 		auto range = this.m_value.match!(
180 			// Can be simplified to `=> assert(0)` once we drop support for v2.096
181 			(NativePath   p) { int dummy; if (dummy) return VersionRange.init; assert(0); },
182 			(Repository   r) { int dummy; if (dummy) return VersionRange.init; assert(0); },
183 			(VersionRange v) => v,
184 		);
185 		enforce(range.isExactVersion(),
186 				"Dependency "~range.toString()~" is no exact version.");
187 		return range.m_versA;
188 	}
189 
190 	/// Sets/gets the matching version range as a specification string.
191 	deprecated("Create a new `Dependency` instead and provide a `VersionRange`")
192 	@property void versionSpec(string ves) @trusted
193 	{
194 		this.m_value = VersionRange.fromString(ves);
195 	}
196 
197 	/// ditto
198 	deprecated("Use `Dependency.visit` and match `VersionRange`instead")
199 	@property string versionSpec() const @safe {
200 		return this.m_value.match!(
201 			(const NativePath   p) => ANY_IDENT,
202 			(const Repository   r) => r.m_ref,
203 			(const VersionRange p) => p.toString(),
204 		);
205 	}
206 
207 	/** Returns a modified dependency that gets mapped to a given path.
208 
209 		This function will return an unmodified `Dependency` if it is not path
210 		based. Otherwise, the given `path` will be prefixed to the existing
211 		path.
212 	*/
213 	Dependency mapToPath(NativePath path) const @trusted {
214 		// NOTE Path is @system in vibe.d 0.7.x and in the compatibility layer
215 		return this.m_value.match!(
216 			(NativePath v) {
217 				if (v.empty || v.absolute) return this;
218 				return Dependency(path ~ v);
219 			},
220 			(Repository v) => this,
221 			(VersionRange v) => this,
222 		);
223 	}
224 
225 	/** Returns a human-readable string representation of the dependency
226 		specification.
227 	*/
228 	string toString() const scope @trusted {
229 		// Trusted because `SumType.match` doesn't seem to support `scope`
230 
231 		string Stringifier (T, string pre = null) (const T v)
232 		{
233 			const bool extra = this.optional || this.default_;
234 			return format("%s%s%s%-(%s, %)%s",
235 					pre, v,
236 					extra ? " (" : "",
237 					BooleanOptions[!this.optional .. 1 + this.default_],
238 					extra ? ")" : "");
239 		}
240 
241 		return this.m_value.match!(
242 			Stringifier!Repository,
243 			Stringifier!(NativePath, "@"),
244 			Stringifier!VersionRange
245 		);
246 	}
247 
248 	/** Returns a JSON representation of the dependency specification.
249 
250 		Simple specifications will be represented as a single specification
251 		string (`versionSpec`), while more complex specifications will be
252 		represented as a JSON object with optional "version", "path", "optional"
253 		and "default" fields.
254 
255 		Params:
256 		  selections = We are serializing `dub.selections.json`, don't write out
257 			  `optional` and `default`.
258 	*/
259 	Json toJson(bool selections = false) const @safe
260 	{
261 		// NOTE Path and Json is @system in vibe.d 0.7.x and in the compatibility layer
262 		static void initJson(ref Json j, bool opt, bool def, bool s = selections)
263 		{
264 			j = Json.emptyObject;
265 			if (!s && opt) j["optional"] = true;
266 			if (!s && def) j["default"] = true;
267 		}
268 
269 		Json json;
270 		this.m_value.match!(
271 			(const NativePath v) @trusted {
272 				initJson(json, optional, default_);
273 				json["path"] = v.toString();
274 			},
275 
276 			(const Repository v) @trusted {
277 				initJson(json, optional, default_);
278 				json["repository"] = v.toString();
279 				json["version"] = v.m_ref;
280 			},
281 
282 			(const VersionRange v) @trusted {
283 				if (!selections && (optional || default_))
284 				{
285 					initJson(json, optional, default_);
286 					json["version"] = v.toString();
287 				}
288 				else
289 					json = Json(v.toString());
290 			},
291 		);
292 		return json;
293 	}
294 
295 	@trusted unittest {
296 		Dependency d = Dependency("==1.0.0");
297 		assert(d.toJson() == Json("1.0.0"), "Failed: " ~ d.toJson().toPrettyString());
298 		d = fromJson((fromJson(d.toJson())).toJson());
299 		assert(d == Dependency("1.0.0"));
300 		assert(d.toJson() == Json("1.0.0"), "Failed: " ~ d.toJson().toPrettyString());
301 	}
302 
303 	@trusted unittest {
304 		Dependency dependency = Dependency(Repository("git+http://localhost", "1.0.0"));
305 		Json expected = Json([
306 			"repository": Json("git+http://localhost"),
307 			"version": Json("1.0.0")
308 		]);
309 		assert(dependency.toJson() == expected, "Failed: " ~ dependency.toJson().toPrettyString());
310 	}
311 
312 	@trusted unittest {
313 		Dependency d = Dependency(NativePath("dir"));
314 		Json expected = Json([ "path": Json("dir") ]);
315 		assert(d.toJson() == expected, "Failed: " ~ d.toJson().toPrettyString());
316 	}
317 
318 	/** Constructs a new `Dependency` from its JSON representation.
319 
320 		See `toJson` for a description of the JSON format.
321 	*/
322 	static Dependency fromJson(Json verspec)
323 	@trusted { // NOTE Path and Json is @system in vibe.d 0.7.x and in the compatibility layer
324 		Dependency dep;
325 		if( verspec.type == Json.Type.object ){
326 			if( auto pp = "path" in verspec ) {
327 				if (auto pv = "version" in verspec)
328 					logDiagnostic("Ignoring version specification (%s) for path based dependency %s", pv.get!string, pp.get!string);
329 
330 				dep = Dependency(NativePath(verspec["path"].get!string));
331 			} else if (auto repository = "repository" in verspec) {
332 				enforce("version" in verspec, "No version field specified!");
333 				enforce(repository.length > 0, "No repository field specified!");
334 
335 				dep = Dependency(Repository(
336                                      repository.get!string, verspec["version"].get!string));
337 			} else {
338 				enforce("version" in verspec, "No version field specified!");
339 				auto ver = verspec["version"].get!string;
340 				// Using the string to be able to specify a range of versions.
341 				dep = Dependency(ver);
342 			}
343 
344 			if (auto po = "optional" in verspec) dep.optional = po.get!bool;
345 			if (auto po = "default" in verspec) dep.default_ = po.get!bool;
346 		} else {
347 			// canonical "package-id": "version"
348 			dep = Dependency(verspec.get!string);
349 		}
350 		return dep;
351 	}
352 
353 	@trusted unittest {
354 		assert(fromJson(parseJsonString("\">=1.0.0 <2.0.0\"")) == Dependency(">=1.0.0 <2.0.0"));
355 		Dependency parsed = fromJson(parseJsonString(`
356 		{
357 			"version": "2.0.0",
358 			"optional": true,
359 			"default": true,
360 			"path": "path/to/package"
361 		}
362 			`));
363 		Dependency d = NativePath("path/to/package"); // supposed to ignore the version spec
364 		d.optional = true;
365 		d.default_ = true;
366 		assert(d == parsed);
367 	}
368 
369 	/** Compares dependency specifications.
370 
371 		These methods are suitable for equality comparisons, as well as for
372 		using `Dependency` as a key in hash or tree maps.
373 	*/
374 	bool opEquals(in Dependency o) const scope @safe {
375 		if (o.m_optional != this.m_optional) return false;
376 		if (o.m_default  != this.m_default)  return false;
377 		return this.m_value == o.m_value;
378 	}
379 
380 	/// ditto
381 	int opCmp(in Dependency o) const @safe {
382 		alias ResultMatch = match!(
383 			(VersionRange r1, VersionRange r2) => r1.opCmp(r2),
384 			(_1, _2) => 0,
385 		);
386 		if (auto result = ResultMatch(this.m_value, o.m_value))
387 			return result;
388 		if (m_optional != o.m_optional) return m_optional ? -1 : 1;
389 		return 0;
390 	}
391 
392 	/** Determines if this dependency specification is valid.
393 
394 		A specification is valid if it can match at least one version.
395 	*/
396 	bool valid() const @safe {
397 		return this.m_value.match!(
398 			(NativePath v) => true,
399 			(Repository v) => true,
400 			(VersionRange v) => v.isValid(),
401 		);
402 	}
403 
404 	/** Determines if this dependency specification matches arbitrary versions.
405 
406 		This is true in particular for the `any` constant.
407 	*/
408 	deprecated("Use `VersionRange.matchesAny` directly")
409 	bool matchesAny() const scope @safe {
410 		return this.m_value.match!(
411 			(NativePath v) => true,
412 			(Repository v) => true,
413 			(VersionRange v) => v.matchesAny(),
414 		);
415 	}
416 
417 	/** Tests if the specification matches a specific version.
418 	*/
419 	bool matches(string vers, VersionMatchMode mode = VersionMatchMode.standard) const @safe
420 	{
421 		return matches(Version(vers), mode);
422 	}
423 	/// ditto
424 	bool matches(in  Version v, VersionMatchMode mode = VersionMatchMode.standard) const @safe {
425 		return this.m_value.match!(
426 			(NativePath i) => true,
427 			(Repository i) => true,
428 			(VersionRange i) => i.matchesAny() || i.matches(v, mode),
429 		);
430 	}
431 
432 	/** Merges two dependency specifications.
433 
434 		The result is a specification that matches the intersection of the set
435 		of versions matched by the individual specifications. Note that this
436 		result can be invalid (i.e. not match any version).
437 	*/
438 	Dependency merge(ref const(Dependency) o) const @trusted {
439 		alias Merger = match!(
440 			(const NativePath a, const NativePath b) => a == b ? this : invalid,
441 			(const NativePath a,       any         ) => o,
442 			(      any         , const NativePath b) => this,
443 
444 			(const Repository a, const Repository b) => a.m_ref == b.m_ref ? this : invalid,
445 			(const Repository a,       any         ) => this,
446 			(      any         , const Repository b) => o,
447 
448 			(const VersionRange a, const VersionRange b) {
449 				if (a.matchesAny()) return o;
450 				if (b.matchesAny()) return this;
451 
452 				VersionRange copy = a;
453 				copy.merge(b);
454 				if (!copy.isValid()) return invalid;
455 				return Dependency(copy);
456 			}
457 		);
458 
459 		Dependency ret = Merger(this.m_value, o.m_value);
460 		ret.m_optional = m_optional && o.m_optional;
461 		return ret;
462 	}
463 }
464 
465 /// Allow direct access to the underlying dependency
466 public auto visit (Handlers...) (const auto ref Dependency dep)
467 {
468     return dep.m_value.match!(Handlers);
469 }
470 
471 //// Ditto
472 public auto visit (Handlers...) (auto ref Dependency dep)
473 {
474     return dep.m_value.match!(Handlers);
475 }
476 
477 
478 unittest {
479 	Dependency a = Dependency(">=1.1.0"), b = Dependency(">=1.3.0");
480 	assert (a.merge(b).valid() && a.merge(b).toString() == ">=1.3.0", a.merge(b).toString());
481 
482 	assertThrown(Dependency("<=2.0.0 >=1.0.0"));
483 	assertThrown(Dependency(">=2.0.0 <=1.0.0"));
484 
485 	a = Dependency(">=1.0.0 <=5.0.0"); b = Dependency(">=2.0.0");
486 	assert (a.merge(b).valid() && a.merge(b).toString() == ">=2.0.0 <=5.0.0", a.merge(b).toString());
487 
488 	assertThrown(a = Dependency(">1.0.0 ==5.0.0"), "Construction is invalid");
489 
490 	a = Dependency(">1.0.0"); b = Dependency("<2.0.0");
491 	assert (a.merge(b).valid(), a.merge(b).toString());
492 	assert (a.merge(b).toString() == ">1.0.0 <2.0.0", a.merge(b).toString());
493 
494 	a = Dependency(">2.0.0"); b = Dependency("<1.0.0");
495 	assert (!(a.merge(b)).valid(), a.merge(b).toString());
496 
497 	a = Dependency(">=2.0.0"); b = Dependency("<=1.0.0");
498 	assert (!(a.merge(b)).valid(), a.merge(b).toString());
499 
500 	a = Dependency("==2.0.0"); b = Dependency("==1.0.0");
501 	assert (!(a.merge(b)).valid(), a.merge(b).toString());
502 
503 	a = Dependency("1.0.0"); b = Dependency("==1.0.0");
504 	assert (a == b);
505 
506 	a = Dependency("<=2.0.0"); b = Dependency("==1.0.0");
507 	Dependency m = a.merge(b);
508 	assert (m.valid(), m.toString());
509 	assert (m.matches(Version("1.0.0")));
510 	assert (!m.matches(Version("1.1.0")));
511 	assert (!m.matches(Version("0.0.1")));
512 
513 
514 	// branches / head revisions
515 	a = Dependency(Version.masterBranch);
516 	assert(a.valid());
517 	assert(a.matches(Version.masterBranch));
518 	b = Dependency(Version.masterBranch);
519 	m = a.merge(b);
520 	assert(m.matches(Version.masterBranch));
521 
522 	//assertThrown(a = Dependency(Version.MASTER_STRING ~ " <=1.0.0"), "Construction invalid");
523 	assertThrown(a = Dependency(">=1.0.0 " ~ Version.masterBranch.toString()), "Construction invalid");
524 
525 	immutable string branch1 = Version.branchPrefix ~ "Branch1";
526 	immutable string branch2 = Version.branchPrefix ~ "Branch2";
527 
528 	//assertThrown(a = Dependency(branch1 ~ " " ~ branch2), "Error: '" ~ branch1 ~ " " ~ branch2 ~ "' succeeded");
529 	//assertThrown(a = Dependency(Version.MASTER_STRING ~ " " ~ branch1), "Error: '" ~ Version.MASTER_STRING ~ " " ~ branch1 ~ "' succeeded");
530 
531 	a = Dependency(branch1);
532 	b = Dependency(branch2);
533 	assert(!a.merge(b).valid, "Shouldn't be able to merge to different branches");
534 	b = a.merge(a);
535 	assert(b.valid, "Should be able to merge the same branches. (?)");
536 	assert(a == b);
537 
538 	a = Dependency(branch1);
539 	assert(a.matches(branch1), "Dependency(branch1) does not match 'branch1'");
540 	assert(a.matches(Version(branch1)), "Dependency(branch1) does not match Version('branch1')");
541 	assert(!a.matches(Version.masterBranch), "Dependency(branch1) matches Version.masterBranch");
542 	assert(!a.matches(branch2), "Dependency(branch1) matches 'branch2'");
543 	assert(!a.matches(Version("1.0.0")), "Dependency(branch1) matches '1.0.0'");
544 	a = Dependency(">=1.0.0");
545 	assert(!a.matches(Version(branch1)), "Dependency(1.0.0) matches 'branch1'");
546 
547 	// Testing optional dependencies.
548 	a = Dependency(">=1.0.0");
549 	assert(!a.optional, "Default is not optional.");
550 	b = a;
551 	assert(!a.merge(b).optional, "Merging two not optional dependencies wrong.");
552 	a.optional = true;
553 	assert(!a.merge(b).optional, "Merging optional with not optional wrong.");
554 	b.optional = true;
555 	assert(a.merge(b).optional, "Merging two optional dependencies wrong.");
556 
557 	// SemVer's sub identifiers.
558 	a = Dependency(">=1.0.0-beta");
559 	assert(!a.matches(Version("1.0.0-alpha")), "Failed: match 1.0.0-alpha with >=1.0.0-beta");
560 	assert(a.matches(Version("1.0.0-beta")), "Failed: match 1.0.0-beta with >=1.0.0-beta");
561 	assert(a.matches(Version("1.0.0")), "Failed: match 1.0.0 with >=1.0.0-beta");
562 	assert(a.matches(Version("1.0.0-rc")), "Failed: match 1.0.0-rc with >=1.0.0-beta");
563 
564 	// Approximate versions.
565 	a = Dependency("~>3.0");
566 	b = Dependency(">=3.0.0 <4.0.0-0");
567 	assert(a == b, "Testing failed: " ~ a.toString());
568 	assert(a.matches(Version("3.1.146")), "Failed: Match 3.1.146 with ~>0.1.2");
569 	assert(!a.matches(Version("0.2.0")), "Failed: Match 0.2.0 with ~>0.1.2");
570 	assert(!a.matches(Version("4.0.0-beta.1")));
571 	a = Dependency("~>3.0.0");
572 	assert(a == Dependency(">=3.0.0 <3.1.0-0"), "Testing failed: " ~ a.toString());
573 	a = Dependency("~>3.5");
574 	assert(a == Dependency(">=3.5.0 <4.0.0-0"), "Testing failed: " ~ a.toString());
575 	a = Dependency("~>3.5.0");
576 	assert(a == Dependency(">=3.5.0 <3.6.0-0"), "Testing failed: " ~ a.toString());
577 	assert(!Dependency("~>3.0.0").matches(Version("3.1.0-beta")));
578 
579 	a = Dependency("^0.1.2");
580 	assert(a == Dependency(">=0.1.2 <0.1.3-0"));
581 	a = Dependency("^1.2.3");
582 	assert(a == Dependency(">=1.2.3 <2.0.0-0"), "Testing failed: " ~ a.toString());
583 	a = Dependency("^1.2");
584 	assert(a == Dependency(">=1.2.0 <2.0.0-0"), "Testing failed: " ~ a.toString());
585 
586 	a = Dependency("~>0.1.1");
587 	b = Dependency("==0.1.0");
588 	assert(!a.merge(b).valid);
589 	b = Dependency("==0.1.9999");
590 	assert(a.merge(b).valid);
591 	b = Dependency("==0.2.0");
592 	assert(!a.merge(b).valid);
593 	b = Dependency("==0.2.0-beta.1");
594 	assert(!a.merge(b).valid);
595 
596 	a = Dependency("~>1.0.1-beta");
597 	b = Dependency(">=1.0.1-beta <1.1.0-0");
598 	assert(a == b, "Testing failed: " ~ a.toString());
599 	assert(a.matches(Version("1.0.1-beta")));
600 	assert(a.matches(Version("1.0.1-beta.6")));
601 
602 	a = Dependency("~d2test");
603 	assert(!a.optional);
604 	assert(a.valid);
605 	assert(a.version_ == Version("~d2test"));
606 
607 	a = Dependency("==~d2test");
608 	assert(!a.optional);
609 	assert(a.valid);
610 	assert(a.version_ == Version("~d2test"));
611 
612 	a = Dependency.any;
613 	assert(!a.optional);
614 	assert(a.valid);
615 	assertThrown(a.version_);
616 	assert(a.matches(Version.masterBranch));
617 	assert(a.matches(Version("1.0.0")));
618 	assert(a.matches(Version("0.0.1-pre")));
619 	b = Dependency(">=1.0.1");
620 	assert(b == a.merge(b));
621 	assert(b == b.merge(a));
622 	b = Dependency(Version.masterBranch);
623 	assert(a.merge(b) == b);
624 	assert(b.merge(a) == b);
625 
626 	a.optional = true;
627 	assert(a.matches(Version.masterBranch));
628 	assert(a.matches(Version("1.0.0")));
629 	assert(a.matches(Version("0.0.1-pre")));
630 	b = Dependency(">=1.0.1");
631 	assert(b == a.merge(b));
632 	assert(b == b.merge(a));
633 	b = Dependency(Version.masterBranch);
634 	assert(a.merge(b) == b);
635 	assert(b.merge(a) == b);
636 
637 	assert(Dependency("1.0.0").matches(Version("1.0.0+foo")));
638 	assert(Dependency("1.0.0").matches(Version("1.0.0+foo"), VersionMatchMode.standard));
639 	assert(!Dependency("1.0.0").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
640 	assert(Dependency("1.0.0+foo").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
641 	assert(Dependency("~>1.0.0+foo").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
642 	assert(Dependency("~>1.0.0").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
643 
644 	logDebug("Dependency unittest success.");
645 }
646 
647 unittest {
648 	assert(VersionRange.fromString("~>1.0.4").toString() == "~>1.0.4");
649 	assert(VersionRange.fromString("~>1.4").toString() == "~>1.4");
650 	assert(VersionRange.fromString("~>2").toString() == "~>2");
651 	assert(VersionRange.fromString("~>1.0.4+1.2.3").toString() == "~>1.0.4");
652 	assert(VersionRange.fromString("^0.1.2").toString() == "^0.1.2");
653 	assert(VersionRange.fromString("^1.2.3").toString() == "^1.2.3");
654 	assert(VersionRange.fromString("^1.2").toString() == "~>1.2"); // equivalent; prefer ~>
655 }
656 
657 /**
658 	Represents an SCM repository.
659 */
660 struct Repository
661 {
662 	private string m_remote;
663 	private string m_ref;
664 
665 	private Kind m_kind;
666 
667 	enum Kind
668 	{
669 		git,
670 	}
671 
672 	/**
673 		Params:
674 			remote = Repository remote.
675 			ref_   = Reference to use (SHA1, tag, branch name...)
676 	 */
677 	this(string remote, string ref_)
678 	{
679 		enforce(remote.startsWith("git+"), "Unsupported repository type (supports: git+URL)");
680 
681 		m_remote = remote["git+".length .. $];
682 		m_kind = Kind.git;
683 		m_ref = ref_;
684 		assert(m_remote.length);
685 		assert(m_ref.length);
686 	}
687 
688 	/// Ditto
689 	deprecated("Use the constructor accepting a second parameter named `ref_`")
690 	this(string remote)
691 	{
692 		enforce(remote.startsWith("git+"), "Unsupported repository type (supports: git+URL)");
693 
694 		m_remote = remote["git+".length .. $];
695 		m_kind = Kind.git;
696 		assert(m_remote.length);
697 	}
698 
699 	string toString() const nothrow pure @safe
700 	{
701 		if (empty) return null;
702 		string kindRepresentation;
703 
704 		final switch (kind)
705 		{
706 			case Kind.git:
707 				kindRepresentation = "git";
708 		}
709 		return kindRepresentation~"+"~remote;
710 	}
711 
712 	/**
713 		Returns:
714 			Repository URL or path.
715 	*/
716 	@property string remote() const @nogc nothrow pure @safe
717 	in { assert(m_remote !is null); }
718 	do
719 	{
720 		return m_remote;
721 	}
722 
723 	/**
724 		Returns:
725 			The reference (commit hash, branch name, tag) we are targeting
726 	*/
727 	@property string ref_() const @nogc nothrow pure @safe
728 	in { assert(m_remote !is null); }
729 	in { assert(m_ref !is null); }
730 	do
731 	{
732 		return m_ref;
733 	}
734 
735 	/**
736 		Returns:
737 			Repository type.
738 	*/
739 	@property Kind kind() const @nogc nothrow pure @safe
740 	{
741 		return m_kind;
742 	}
743 
744 	/**
745 		Returns:
746 			Whether the repository was initialized with an URL or path.
747 	*/
748 	@property bool empty() const @nogc nothrow pure @safe
749 	{
750 		return m_remote.empty;
751 	}
752 }
753 
754 
755 /**
756 	Represents a version in semantic version format, or a branch identifier.
757 
758 	This can either have the form "~master", where "master" is a branch name,
759 	or the form "major.update.bugfix-prerelease+buildmetadata" (see the
760 	Semantic Versioning Specification v2.0.0 at http://semver.org/).
761 */
762 struct Version {
763 	private {
764 		static immutable MAX_VERS = "99999.0.0";
765 		static immutable masterString = "~master";
766 		enum branchPrefix = '~';
767 		string m_version;
768 	}
769 
770 	static immutable Version minRelease = Version("0.0.0");
771 	static immutable Version maxRelease = Version(MAX_VERS);
772 	static immutable Version masterBranch = Version(masterString);
773 
774 	/** Constructs a new `Version` from its string representation.
775 	*/
776 	this(string vers) @safe pure
777 	{
778 		enforce(vers.length > 1, "Version strings must not be empty.");
779 		if (vers[0] != branchPrefix)
780 			enforce(vers.isValidVersion(), "Invalid SemVer format: " ~ vers);
781 		m_version = vers;
782 	}
783 
784 	/** Constructs a new `Version` from its string representation.
785 
786 		This method is equivalent to calling the constructor and is used as an
787 		endpoint for the serialization framework.
788 	*/
789 	static Version fromString(string vers) @safe pure { return Version(vers); }
790 
791 	bool opEquals(in Version oth) const scope @safe pure
792 	{
793 		return opCmp(oth) == 0;
794 	}
795 
796 	/// Tests if this represents a branch instead of a version.
797 	@property bool isBranch() const scope @safe pure nothrow @nogc
798 	{
799 		return m_version.length > 0 && m_version[0] == branchPrefix;
800 	}
801 
802 	/// Tests if this represents the master branch "~master".
803 	@property bool isMaster() const scope @safe pure nothrow @nogc
804 	{
805 		return m_version == masterString;
806 	}
807 
808 	/** Tests if this represents a pre-release version.
809 
810 		Note that branches are always considered pre-release versions.
811 	*/
812 	@property bool isPreRelease() const scope @safe pure nothrow @nogc
813 	{
814 		if (isBranch) return true;
815 		return isPreReleaseVersion(m_version);
816 	}
817 
818 	/** Tests two versions for equality, according to the selected match mode.
819 	*/
820 	bool matches(in Version other, VersionMatchMode mode = VersionMatchMode.standard)
821 	const scope @safe pure
822 	{
823 		if (mode == VersionMatchMode.strict)
824 			return this.toString() == other.toString();
825 		return this == other;
826 	}
827 
828 	/** Compares two versions/branches for precedence.
829 
830 		Versions generally have precedence over branches and the master branch
831 		has precedence over other branches. Apart from that, versions are
832 		compared using SemVer semantics, while branches are compared
833 		lexicographically.
834 	*/
835 	int opCmp(in Version other) const scope @safe pure
836 	{
837 		if (isBranch || other.isBranch) {
838 			if(m_version == other.m_version) return 0;
839 			if (!isBranch) return 1;
840 			else if (!other.isBranch) return -1;
841 			if (isMaster) return 1;
842 			else if (other.isMaster) return -1;
843 			return this.m_version < other.m_version ? -1 : 1;
844 		}
845 
846 		return compareVersions(m_version, other.m_version);
847 	}
848 
849 	/// Returns the string representation of the version/branch.
850 	string toString() const return scope @safe pure nothrow @nogc
851 	{
852 		return m_version;
853 	}
854 }
855 
856 /**
857  * A range of versions that are acceptable
858  *
859  * While not directly described in SemVer v2.0.0, a common set
860  * of range operators have appeared among package managers.
861  * We mostly NPM's: https://semver.npmjs.com/
862  *
863  * Hence the acceptable forms for this string are as follows:
864  *
865  * $(UL
866  *  $(LI `"1.0.0"` - a single version in SemVer format)
867  *  $(LI `"==1.0.0"` - alternative single version notation)
868  *  $(LI `">1.0.0"` - version range with a single bound)
869  *  $(LI `">1.0.0 <2.0.0"` - version range with two bounds)
870  *  $(LI `"~>1.0.0"` - a fuzzy version range)
871  *  $(LI `"~>1.0"` - a fuzzy version range with partial version)
872  *  $(LI `"^1.0.0"` - semver compatible version range (same version if 0.x.y, ==major >=minor.patch if x.y.z))
873  *  $(LI `"^1.0"` - same as ^1.0.0)
874  *  $(LI `"~master"` - a branch name)
875  *  $(LI `"*" - match any version (see also `VersionRange.Any`))
876  * )
877  *
878  * Apart from "$(LT)" and "$(GT)", "$(GT)=" and "$(LT)=" are also valid
879  * comparators.
880  */
881 public struct VersionRange
882 {
883 	private Version m_versA;
884 	private Version m_versB;
885 	private bool m_inclusiveA = true; // A comparison > (true) or >= (false)
886 	private bool m_inclusiveB = true; // B comparison < (true) or <= (false)
887 
888 	/// Matches any version
889 	public static immutable Any = VersionRange(Version.minRelease, Version.maxRelease);
890 	/// Doesn't match any version
891 	public static immutable Invalid = VersionRange(Version.maxRelease, Version.minRelease);
892 
893 	///
894 	public int opCmp (in VersionRange o) const scope @safe
895 	{
896 		if (m_inclusiveA != o.m_inclusiveA) return m_inclusiveA < o.m_inclusiveA ? -1 : 1;
897 		if (m_inclusiveB != o.m_inclusiveB) return m_inclusiveB < o.m_inclusiveB ? -1 : 1;
898 		if (m_versA != o.m_versA) return m_versA < o.m_versA ? -1 : 1;
899 		if (m_versB != o.m_versB) return m_versB < o.m_versB ? -1 : 1;
900 		return 0;
901 	}
902 
903 	public bool matches (in Version v, VersionMatchMode mode = VersionMatchMode.standard)
904 		const scope @safe
905 	{
906 		if (m_versA.isBranch) {
907 			enforce(this.isExactVersion());
908 			return m_versA == v;
909 		}
910 
911 		if (v.isBranch)
912 			return m_versA == v;
913 
914 		if (m_versA == m_versB)
915 			return this.m_versA.matches(v, mode);
916 
917 		return doCmp(m_inclusiveA, m_versA, v) &&
918 			doCmp(m_inclusiveB, v, m_versB);
919 	}
920 
921 	/// Modify in place
922 	public void merge (const VersionRange o) @safe
923 	{
924 		int acmp = m_versA.opCmp(o.m_versA);
925 		int bcmp = m_versB.opCmp(o.m_versB);
926 
927 		this.m_inclusiveA = !m_inclusiveA && acmp >= 0 ? false : o.m_inclusiveA;
928 		this.m_versA = acmp > 0 ? m_versA : o.m_versA;
929 		this.m_inclusiveB = !m_inclusiveB && bcmp <= 0 ? false : o.m_inclusiveB;
930 		this.m_versB = bcmp < 0 ? m_versB : o.m_versB;
931 	}
932 
933 	/// Returns true $(I iff) the version range only matches a specific version.
934 	@property bool isExactVersion() const scope @safe
935 	{
936 		return this.m_versA == this.m_versB;
937 	}
938 
939 	/// Determines if this dependency specification matches arbitrary versions.
940 	/// This is true in particular for the `any` constant.
941 	public bool matchesAny() const scope @safe
942 	{
943 		return this.m_inclusiveA && this.m_inclusiveB
944 			&& this.m_versA == Version.minRelease
945 			&& this.m_versB == Version.maxRelease;
946 	}
947 
948 	unittest {
949 		assert(VersionRange.fromString("*").matchesAny);
950 		assert(!VersionRange.fromString(">0.0.0").matchesAny);
951 		assert(!VersionRange.fromString(">=1.0.0").matchesAny);
952 		assert(!VersionRange.fromString("<1.0.0").matchesAny);
953 	}
954 
955 	public static VersionRange fromString (string ves) @safe
956 	{
957 		static import std.string;
958 
959 		enforce(ves.length > 0);
960 
961 		if (ves == Dependency.ANY_IDENT) {
962 			// Any version is good.
963 			ves = ">=0.0.0";
964 		}
965 
966 		if (ves.startsWith("~>")) {
967 			// Shortcut: "~>x.y.z" variant. Last non-zero number will indicate
968 			// the base for this so something like this: ">=x.y.z <x.(y+1).z"
969 			ves = ves[2..$];
970 			return VersionRange(
971 				Version(expandVersion(ves)), Version(bumpVersion(ves) ~ "-0"),
972 				true, false);
973 		}
974 
975 		if (ves.startsWith("^")) {
976 			// Shortcut: "^x.y.z" variant. "Semver compatible" - no breaking changes.
977 			// if 0.x.y, ==0.x.y
978 			// if x.y.z, >=x.y.z <(x+1).0.0-0
979 			// ^x.y is equivalent to ^x.y.0.
980 			ves = ves[1..$].expandVersion;
981 			return VersionRange(
982 				Version(ves), Version(bumpIncompatibleVersion(ves) ~ "-0"),
983 				true, false);
984 		}
985 
986 		if (ves[0] == Version.branchPrefix) {
987 			auto ver = Version(ves);
988 			return VersionRange(ver, ver, true, true);
989 		}
990 
991 		if (std..string.indexOf("><=", ves[0]) == -1) {
992 			auto ver = Version(ves);
993 			return VersionRange(ver, ver, true, true);
994 		}
995 
996 		auto cmpa = skipComp(ves);
997 		size_t idx2 = std..string.indexOf(ves, " ");
998 		if (idx2 == -1) {
999 			if (cmpa == "<=" || cmpa == "<")
1000 				return VersionRange(Version.minRelease, Version(ves), true, (cmpa == "<="));
1001 
1002 			if (cmpa == ">=" || cmpa == ">")
1003 				return VersionRange(Version(ves), Version.maxRelease, (cmpa == ">="), true);
1004 
1005 			// Converts "==" to ">=a&&<=a", which makes merging easier
1006 			return VersionRange(Version(ves), Version(ves), true, true);
1007 		}
1008 
1009 		enforce(cmpa == ">" || cmpa == ">=",
1010 				"First comparison operator expected to be either > or >=, not " ~ cmpa);
1011 		assert(ves[idx2] == ' ');
1012 		VersionRange ret;
1013 		ret.m_versA = Version(ves[0..idx2]);
1014 		ret.m_inclusiveA = cmpa == ">=";
1015 		string v2 = ves[idx2+1..$];
1016 		auto cmpb = skipComp(v2);
1017 		enforce(cmpb == "<" || cmpb == "<=",
1018 				"Second comparison operator expected to be either < or <=, not " ~ cmpb);
1019 		ret.m_versB = Version(v2);
1020 		ret.m_inclusiveB = cmpb == "<=";
1021 
1022 		enforce(!ret.m_versA.isBranch && !ret.m_versB.isBranch,
1023 				format("Cannot compare branches: %s", ves));
1024 		enforce(ret.m_versA <= ret.m_versB,
1025 				"First version must not be greater than the second one.");
1026 
1027 		return ret;
1028 	}
1029 
1030 	/// Returns a string representation of this range
1031 	string toString() const @safe {
1032 		static import std.string;
1033 
1034 		string r;
1035 
1036 		if (this == Invalid) return "no";
1037 		if (this.isExactVersion() && m_inclusiveA && m_inclusiveB) {
1038 			// Special "==" case
1039 			if (m_versA == Version.masterBranch) return "~master";
1040 			else return m_versA.toString();
1041 		}
1042 
1043 		// "~>", "^" case
1044 		if (m_inclusiveA && !m_inclusiveB && !m_versA.isBranch) {
1045 			auto vs = m_versA.toString();
1046 			auto i1 = std..string.indexOf(vs, '-'), i2 = std..string.indexOf(vs, '+');
1047 			auto i12 = i1 >= 0 ? i2 >= 0 ? i1 < i2 ? i1 : i2 : i1 : i2;
1048 			auto va = i12 >= 0 ? vs[0 .. i12] : vs;
1049 			auto parts = va.splitter('.').array;
1050 			assert(parts.length == 3, "Version string with a digit group count != 3: "~va);
1051 
1052 			foreach (i; 0 .. 3) {
1053 				auto vp = parts[0 .. i+1].join(".");
1054 				auto ve = Version(expandVersion(vp));
1055 				auto veb = Version(bumpVersion(vp) ~ "-0");
1056 				if (ve == m_versA && veb == m_versB) return "~>" ~ vp;
1057 
1058 				auto veb2 = Version(bumpIncompatibleVersion(expandVersion(vp)) ~ "-0");
1059 				if (ve == m_versA && veb2 == m_versB) return "^" ~ vp;
1060 			}
1061 		}
1062 
1063 		if (m_versA != Version.minRelease) r = (m_inclusiveA ? ">=" : ">") ~ m_versA.toString();
1064 		if (m_versB != Version.maxRelease) r ~= (r.length==0 ? "" : " ") ~ (m_inclusiveB ? "<=" : "<") ~ m_versB.toString();
1065 		if (this.matchesAny()) r = ">=0.0.0";
1066 		return r;
1067 	}
1068 
1069 	public bool isValid() const @safe {
1070 		return m_versA <= m_versB && doCmp(m_inclusiveA && m_inclusiveB, m_versA, m_versB);
1071 	}
1072 
1073 	private static bool doCmp(bool inclusive, in Version a, in Version b)
1074 		@safe
1075 	{
1076 		return inclusive ? a <= b : a < b;
1077 	}
1078 
1079 	private static bool isDigit(char ch) @safe { return ch >= '0' && ch <= '9'; }
1080 	private static string skipComp(ref string c) @safe {
1081 		size_t idx = 0;
1082 		while (idx < c.length && !isDigit(c[idx]) && c[idx] != Version.branchPrefix) idx++;
1083 		enforce(idx < c.length, "Expected version number in version spec: "~c);
1084 		string cmp = idx==c.length-1||idx==0? ">=" : c[0..idx];
1085 		c = c[idx..$];
1086 		switch(cmp) {
1087 			default: enforce(false, "No/Unknown comparison specified: '"~cmp~"'"); return ">=";
1088 			case ">=": goto case; case ">": goto case;
1089 			case "<=": goto case; case "<": goto case;
1090 			case "==": return cmp;
1091 		}
1092 	}
1093 }
1094 
1095 enum VersionMatchMode {
1096 	standard,  /// Match according to SemVer rules
1097 	strict     /// Also include build metadata suffix in the comparison
1098 }
1099 
1100 unittest {
1101 	Version a, b;
1102 
1103 	assertNotThrown(a = Version("1.0.0"), "Constructing Version('1.0.0') failed");
1104 	assert(!a.isBranch, "Error: '1.0.0' treated as branch");
1105 	assert(a == a, "a == a failed");
1106 
1107 	assertNotThrown(a = Version(Version.masterString), "Constructing Version("~Version.masterString~"') failed");
1108 	assert(a.isBranch, "Error: '"~Version.masterString~"' treated as branch");
1109 	assert(a.isMaster);
1110 	assert(a == Version.masterBranch, "Constructed master version != default master version.");
1111 
1112 	assertNotThrown(a = Version("~BRANCH"), "Construction of branch Version failed.");
1113 	assert(a.isBranch, "Error: '~BRANCH' not treated as branch'");
1114 	assert(!a.isMaster);
1115 	assert(a == a, "a == a with branch failed");
1116 
1117 	// opCmp
1118 	a = Version("1.0.0");
1119 	b = Version("1.0.0");
1120 	assert(a == b, "a == b with a:'1.0.0', b:'1.0.0' failed");
1121 	b = Version("2.0.0");
1122 	assert(a != b, "a != b with a:'1.0.0', b:'2.0.0' failed");
1123 
1124 	a = Version.masterBranch;
1125 	b = Version("~BRANCH");
1126 	assert(a != b, "a != b with a:MASTER, b:'~branch' failed");
1127 	assert(a > b);
1128 	assert(a < Version("0.0.0"));
1129 	assert(b < Version("0.0.0"));
1130 	assert(a > Version("~Z"));
1131 	assert(b < Version("~Z"));
1132 
1133 	// SemVer 2.0.0-rc.2
1134 	a = Version("2.0.0-rc.2");
1135 	b = Version("2.0.0-rc.3");
1136 	assert(a < b, "Failed: 2.0.0-rc.2 < 2.0.0-rc.3");
1137 
1138 	a = Version("2.0.0-rc.2+build-metadata");
1139 	b = Version("2.0.0+build-metadata");
1140 	assert(a < b, "Failed: "~a.toString()~"<"~b.toString());
1141 
1142 	// 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
1143 	Version[] versions;
1144 	versions ~= Version("1.0.0-alpha");
1145 	versions ~= Version("1.0.0-alpha.1");
1146 	versions ~= Version("1.0.0-beta.2");
1147 	versions ~= Version("1.0.0-beta.11");
1148 	versions ~= Version("1.0.0-rc.1");
1149 	versions ~= Version("1.0.0");
1150 	for(int i=1; i<versions.length; ++i)
1151 		for(int j=i-1; j>=0; --j)
1152 			assert(versions[j] < versions[i], "Failed: " ~ versions[j].toString() ~ "<" ~ versions[i].toString());
1153 
1154 	assert(Version("1.0.0+a") == Version("1.0.0+b"));
1155 
1156 	assert(Version("1.0.0").matches(Version("1.0.0+foo")));
1157 	assert(Version("1.0.0").matches(Version("1.0.0+foo"), VersionMatchMode.standard));
1158 	assert(!Version("1.0.0").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
1159 	assert(Version("1.0.0+foo").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
1160 }
1161 
1162 /// Determines whether the given string is a Git hash.
1163 bool isGitHash(string hash) @nogc nothrow pure @safe
1164 {
1165 	import std.ascii : isHexDigit;
1166 	import std.utf : byCodeUnit;
1167 
1168 	return hash.length >= 7 && hash.length <= 40 && hash.byCodeUnit.all!isHexDigit;
1169 }
1170 
1171 @nogc nothrow pure @safe unittest {
1172 	assert(isGitHash("73535568b79a0b124bc1653002637a830ce0fcb8"));
1173 	assert(!isGitHash("735"));
1174 	assert(!isGitHash("73535568b79a0b124bc1-53002637a830ce0fcb8"));
1175 	assert(!isGitHash("73535568b79a0b124bg1"));
1176 }