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.log;
12 import dub.internal.vibecompat.core.file;
13 import dub.internal.vibecompat.data.json;
14 import dub.internal.vibecompat.inet.url;
15 import dub.package_;
16 import dub.semver;
17 
18 import std.algorithm;
19 import std.array;
20 import std.exception;
21 import std.regex;
22 import std..string;
23 import std.typecons;
24 static import std.compiler;
25 
26 
27 /** Encapsulates the name of a package along with its dependency specification.
28 */
29 struct PackageDependency {
30 	/// Name of the referenced package.
31 	string name;
32 
33 	/// Dependency specification used to select a particular version of the package.
34 	Dependency spec;
35 }
36 
37 
38 /**
39 	Represents a dependency specification.
40 
41 	A dependency specification either represents a specific version or version
42 	range, or a path to a package. In addition to that it has `optional` and
43 	`default_` flags to control how non-mandatory dependencies are handled. The
44 	package name is notably not part of the dependency specification.
45 */
46 struct Dependency {
47 @safe:
48 
49 	private {
50 		// Shortcut to create >=0.0.0
51 		enum ANY_IDENT = "*";
52 		bool m_inclusiveA = true; // A comparison > (true) or >= (false)
53 		Version m_versA;
54 		bool m_inclusiveB = true; // B comparison < (true) or <= (false)
55 		Version m_versB;
56 		NativePath m_path;
57 		bool m_optional = false;
58 		bool m_default = false;
59 	}
60 
61 	/// A Dependency, which matches every valid version.
62 	static @property Dependency any() { return Dependency(ANY_IDENT); }
63 
64 	/// An invalid dependency (with no possible version matches).
65 	static @property Dependency invalid() { Dependency ret; ret.m_versA = Version.maxRelease; ret.m_versB = Version.minRelease; return ret; }
66 
67 	/** Constructs a new dependency specification from a string
68 
69 		See the `versionSpec` property for a description of the accepted
70 		contents of that string.
71 	*/
72 	this(string spec)
73 	{
74 		this.versionSpec = spec;
75 	}
76 
77 	/** Constructs a new dependency specification that matches a specific
78 		version.
79 	*/
80 	this(in Version ver)
81 	{
82 		m_inclusiveA = m_inclusiveB = true;
83 		m_versA = ver;
84 		m_versB = ver;
85 	}
86 
87 	/** Constructs a new dependency specification that matches a specific
88 		path.
89 	*/
90 	this(NativePath path)
91 	{
92 		this(ANY_IDENT);
93 		m_path = path;
94 	}
95 
96 	/// If set, overrides any version based dependency selection.
97 	@property void path(NativePath value) { m_path = value; }
98 	/// ditto
99 	@property NativePath path() const { return m_path; }
100 
101 	/// Determines if the dependency is required or optional.
102 	@property bool optional() const { return m_optional; }
103 	/// ditto
104 	@property void optional(bool optional) { m_optional = optional; }
105 
106 	/// Determines if an optional dependency should be chosen by default.
107 	@property bool default_() const { return m_default; }
108 	/// ditto
109 	@property void default_(bool value) { m_default = value; }
110 
111 	/// Returns true $(I iff) the version range only matches a specific version.
112 	@property bool isExactVersion() const { return m_versA == m_versB; }
113 
114 	/// Returns the exact version matched by the version range.
115 	@property Version version_() const {
116 		enforce(m_versA == m_versB, "Dependency "~this.versionSpec~" is no exact version.");
117 		return m_versA;
118 	}
119 
120 	/** Sets/gets the matching version range as a specification string.
121 
122 		The acceptable forms for this string are as follows:
123 
124 		$(UL
125 			$(LI `"1.0.0"` - a single version in SemVer format)
126 			$(LI `"==1.0.0"` - alternative single version notation)
127 			$(LI `">1.0.0"` - version range with a single bound)
128 			$(LI `">1.0.0 <2.0.0"` - version range with two bounds)
129 			$(LI `"~>1.0.0"` - a fuzzy version range)
130 			$(LI `"~>1.0"` - a fuzzy version range with partial version)
131 			$(LI `"~master"` - a branch name)
132 			$(LI `"*" - match any version (see also `any`))
133 		)
134 
135 		Apart from "$(LT)" and "$(GT)", "$(GT)=" and "$(LT)=" are also valid
136 		comparators.
137 
138 	*/
139 	@property void versionSpec(string ves)
140 	{
141 		static import std..string;
142 
143 		enforce(ves.length > 0);
144 		string orig = ves;
145 
146 		if (ves == ANY_IDENT) {
147 			// Any version is good.
148 			ves = ">=0.0.0";
149 		}
150 
151 		if (ves.startsWith("~>")) {
152 			// Shortcut: "~>x.y.z" variant. Last non-zero number will indicate
153 			// the base for this so something like this: ">=x.y.z <x.(y+1).z"
154 			m_inclusiveA = true;
155 			m_inclusiveB = false;
156 			ves = ves[2..$];
157 			m_versA = Version(expandVersion(ves));
158 			m_versB = Version(bumpVersion(ves) ~ "-0");
159 		} else if (ves[0] == Version.branchPrefix) {
160 			m_inclusiveA = true;
161 			m_inclusiveB = true;
162 			m_versA = m_versB = Version(ves);
163 		} else if (std..string.indexOf("><=", ves[0]) == -1) {
164 			m_inclusiveA = true;
165 			m_inclusiveB = true;
166 			m_versA = m_versB = Version(ves);
167 		} else {
168 			auto cmpa = skipComp(ves);
169 			size_t idx2 = std..string.indexOf(ves, " ");
170 			if (idx2 == -1) {
171 				if (cmpa == "<=" || cmpa == "<") {
172 					m_versA = Version.minRelease;
173 					m_inclusiveA = true;
174 					m_versB = Version(ves);
175 					m_inclusiveB = cmpa == "<=";
176 				} else if (cmpa == ">=" || cmpa == ">") {
177 					m_versA = Version(ves);
178 					m_inclusiveA = cmpa == ">=";
179 					m_versB = Version.maxRelease;
180 					m_inclusiveB = true;
181 				} else {
182 					// Converts "==" to ">=a&&<=a", which makes merging easier
183 					m_versA = m_versB = Version(ves);
184 					m_inclusiveA = m_inclusiveB = true;
185 				}
186 			} else {
187 				enforce(cmpa == ">" || cmpa == ">=", "First comparison operator expected to be either > or >=, not "~cmpa);
188 				assert(ves[idx2] == ' ');
189 				m_versA = Version(ves[0..idx2]);
190 				m_inclusiveA = cmpa == ">=";
191 				string v2 = ves[idx2+1..$];
192 				auto cmpb = skipComp(v2);
193 				enforce(cmpb == "<" || cmpb == "<=", "Second comparison operator expected to be either < or <=, not "~cmpb);
194 				m_versB = Version(v2);
195 				m_inclusiveB = cmpb == "<=";
196 
197 				enforce(!m_versA.isBranch && !m_versB.isBranch, format("Cannot compare branches: %s", ves));
198 				enforce(m_versA <= m_versB, "First version must not be greater than the second one.");
199 			}
200 		}
201 	}
202 	/// ditto
203 	@property string versionSpec()
204 	const {
205 		static import std..string;
206 
207 		string r;
208 
209 		if (this == invalid) return "invalid";
210 
211 		if (m_versA == m_versB && m_inclusiveA && m_inclusiveB) {
212 			// Special "==" case
213 			if (m_versA == Version.masterBranch) return "~master";
214 			else return m_versA.toString();
215 		}
216 
217 		// "~>" case
218 		if (m_inclusiveA && !m_inclusiveB && !m_versA.isBranch) {
219 			auto vs = m_versA.toString();
220 			auto i1 = std..string.indexOf(vs, '-'), i2 = std..string.indexOf(vs, '+');
221 			auto i12 = i1 >= 0 ? i2 >= 0 ? i1 < i2 ? i1 : i2 : i1 : i2;
222 			auto va = i12 >= 0 ? vs[0 .. i12] : vs;
223 			auto parts = va.splitter('.').array;
224 			assert(parts.length == 3, "Version string with a digit group count != 3: "~va);
225 
226 			foreach (i; 0 .. 3) {
227 				auto vp = parts[0 .. i+1].join(".");
228 				auto ve = Version(expandVersion(vp));
229 				auto veb = Version(bumpVersion(vp) ~ "-0");
230 				if (ve == m_versA && veb == m_versB) return "~>" ~ vp;
231 			}
232 		}
233 
234 		if (m_versA != Version.minRelease) r = (m_inclusiveA ? ">=" : ">") ~ m_versA.toString();
235 		if (m_versB != Version.maxRelease) r ~= (r.length==0 ? "" : " ") ~ (m_inclusiveB ? "<=" : "<") ~ m_versB.toString();
236 		if (m_versA == Version.minRelease && m_versB == Version.maxRelease) r = ">=0.0.0";
237 		return r;
238 	}
239 
240 	/** Returns a modified dependency that gets mapped to a given path.
241 
242 		This function will return an unmodified `Dependency` if it is not path
243 		based. Otherwise, the given `path` will be prefixed to the existing
244 		path.
245 	*/
246 	Dependency mapToPath(NativePath path)
247 	const @trusted { // NOTE Path is @system in vibe.d 0.7.x and in the compatibility layer
248 		if (m_path.empty || m_path.absolute) return this;
249 		else {
250 			Dependency ret = this;
251 			ret.path = path ~ ret.path;
252 			return ret;
253 		}
254 	}
255 
256 	/** Returns a human-readable string representation of the dependency
257 		specification.
258 	*/
259 	string toString()()
260 	const {
261 		auto ret = versionSpec;
262 		if (optional) {
263 			if (default_) ret ~= " (optional, default)";
264 			else ret ~= " (optional)";
265 		}
266 
267 		// NOTE Path is @system in vibe.d 0.7.x and in the compatibility layer
268 		() @trusted {
269 			if (!path.empty) ret ~= " @"~path.toNativeString();
270 		} ();
271 
272 		return ret;
273 	}
274 
275 	/** Returns a JSON representation of the dependency specification.
276 
277 		Simple specifications will be represented as a single specification
278 		string (`versionSpec`), while more complex specifications will be
279 		represented as a JSON object with optional "version", "path", "optional"
280 		and "default" fields.
281 	*/
282 	Json toJson()
283 	const @trusted { // NOTE Path and Json is @system in vibe.d 0.7.x and in the compatibility layer
284 		Json json;
285 		if( path.empty && !optional ){
286 			json = Json(this.versionSpec);
287 		} else {
288 			json = Json.emptyObject;
289 			json["version"] = this.versionSpec;
290 			if (!path.empty) json["path"] = path.toString();
291 			if (optional) json["optional"] = true;
292 			if (default_) json["default"] = true;
293 		}
294 		return json;
295 	}
296 
297 	@trusted unittest {
298 		Dependency d = Dependency("==1.0.0");
299 		assert(d.toJson() == Json("1.0.0"), "Failed: " ~ d.toJson().toPrettyString());
300 		d = fromJson((fromJson(d.toJson())).toJson());
301 		assert(d == Dependency("1.0.0"));
302 		assert(d.toJson() == Json("1.0.0"), "Failed: " ~ d.toJson().toPrettyString());
303 	}
304 
305 	/** Constructs a new `Dependency` from its JSON representation.
306 
307 		See `toJson` for a description of the JSON format.
308 	*/
309 	static Dependency fromJson(Json verspec)
310 	@trusted { // NOTE Path and Json is @system in vibe.d 0.7.x and in the compatibility layer
311 		Dependency dep;
312 		if( verspec.type == Json.Type.object ){
313 			if( auto pp = "path" in verspec ) {
314 				if (auto pv = "version" in verspec)
315 					logDiagnostic("Ignoring version specification (%s) for path based dependency %s", pv.get!string, pp.get!string);
316 
317 				dep = Dependency.any;
318 				dep.path = NativePath(verspec["path"].get!string);
319 			} else {
320 				enforce("version" in verspec, "No version field specified!");
321 				auto ver = verspec["version"].get!string;
322 				// Using the string to be able to specify a range of versions.
323 				dep = Dependency(ver);
324 			}
325 
326 			if (auto po = "optional" in verspec) dep.optional = po.get!bool;
327 			if (auto po = "default" in verspec) dep.default_ = po.get!bool;
328 		} else {
329 			// canonical "package-id": "version"
330 			dep = Dependency(verspec.get!string);
331 		}
332 		return dep;
333 	}
334 
335 	@trusted unittest {
336 		assert(fromJson(parseJsonString("\">=1.0.0 <2.0.0\"")) == Dependency(">=1.0.0 <2.0.0"));
337 		Dependency parsed = fromJson(parseJsonString(`
338 		{
339 			"version": "2.0.0",
340 			"optional": true,
341 			"default": true,
342 			"path": "path/to/package"
343 		}
344 			`));
345 		Dependency d = Dependency.any; // supposed to ignore the version spec
346 		d.optional = true;
347 		d.default_ = true;
348 		d.path = NativePath("path/to/package");
349 		assert(d == parsed);
350 		// optional and path not checked by opEquals.
351 		assert(d.optional == parsed.optional);
352 		assert(d.default_ == parsed.default_);
353 		assert(d.path == parsed.path);
354 	}
355 
356 	/** Compares dependency specifications.
357 
358 		These methods are suitable for equality comparisons, as well as for
359 		using `Dependency` as a key in hash or tree maps.
360 	*/
361 	bool opEquals(in Dependency o)
362 	const {
363 		// TODO(mdondorff): Check if not comparing the path is correct for all clients.
364 		return o.m_inclusiveA == m_inclusiveA && o.m_inclusiveB == m_inclusiveB
365 			&& o.m_versA == m_versA && o.m_versB == m_versB
366 			&& o.m_optional == m_optional && o.m_default == m_default;
367 	}
368 
369 	/// ditto
370 	int opCmp(in Dependency o)
371 	const {
372 		if (m_inclusiveA != o.m_inclusiveA) return m_inclusiveA < o.m_inclusiveA ? -1 : 1;
373 		if (m_inclusiveB != o.m_inclusiveB) return m_inclusiveB < o.m_inclusiveB ? -1 : 1;
374 		if (m_versA != o.m_versA) return m_versA < o.m_versA ? -1 : 1;
375 		if (m_versB != o.m_versB) return m_versB < o.m_versB ? -1 : 1;
376 		if (m_optional != o.m_optional) return m_optional ? -1 : 1;
377 		return 0;
378 	}
379 
380 	/// ditto
381 	hash_t toHash()
382 	const nothrow @trusted  {
383 		try {
384 			size_t hash = 0;
385 			hash = m_inclusiveA.hashOf(hash);
386 			hash = m_versA.toString().hashOf(hash);
387 			hash = m_inclusiveB.hashOf(hash);
388 			hash = m_versB.toString().hashOf(hash);
389 			hash = m_optional.hashOf(hash);
390 			hash = m_default.hashOf(hash);
391 			return hash;
392 		} catch (Exception) assert(false);
393 	}
394 
395 	/** Determines if this dependency specification is valid.
396 
397 		A specification is valid if it can match at least one version.
398 	*/
399 	bool valid() const {
400 		return m_versA <= m_versB && doCmp(m_inclusiveA && m_inclusiveB, m_versA, m_versB);
401 	}
402 
403 	/** Determines if this dependency specification matches arbitrary versions.
404 
405 		This is true in particular for the `any` constant.
406 	*/
407 	bool matchesAny()
408 	const {
409 		return m_inclusiveA && m_inclusiveB
410 			&& m_versA.toString() == "0.0.0"
411 			&& m_versB == Version.maxRelease;
412 	}
413 
414 	unittest {
415 		assert(Dependency("*").matchesAny);
416 		assert(!Dependency(">0.0.0").matchesAny);
417 		assert(!Dependency(">=1.0.0").matchesAny);
418 		assert(!Dependency("<1.0.0").matchesAny);
419 	}
420 
421 	/** Tests if the specification matches a specific version.
422 	*/
423 	bool matches(string vers) const { return matches(Version(vers)); }
424 	/// ditto
425 	bool matches(const(Version) v) const { return matches(v); }
426 	/// ditto
427 	bool matches(ref const(Version) v) const {
428 		if (this.matchesAny) return true;
429 		//logDebug(" try match: %s with: %s", v, this);
430 		// Master only matches master
431 		if(m_versA.isBranch) {
432 			enforce(m_versA == m_versB);
433 			return m_versA == v;
434 		}
435 		if(v.isBranch || m_versA.isBranch)
436 			return m_versA == v;
437 		if( !doCmp(m_inclusiveA, m_versA, v) )
438 			return false;
439 		if( !doCmp(m_inclusiveB, v, m_versB) )
440 			return false;
441 		return true;
442 	}
443 
444 	/** Merges two dependency specifications.
445 
446 		The result is a specification that matches the intersection of the set
447 		of versions matched by the individual specifications. Note that this
448 		result can be invalid (i.e. not match any version).
449 	*/
450 	Dependency merge(ref const(Dependency) o)
451 	const {
452 		if (this.matchesAny) return o;
453 		if (o.matchesAny) return this;
454 		if (m_versA.isBranch != o.m_versA.isBranch) return invalid;
455 		if (m_versB.isBranch != o.m_versB.isBranch) return invalid;
456 		if (m_versA.isBranch) return m_versA == o.m_versA ? this : invalid;
457 		// NOTE Path is @system in vibe.d 0.7.x and in the compatibility layer
458 		if (() @trusted { return this.path != o.path; } ()) return invalid;
459 
460 		int acmp = m_versA.opCmp(o.m_versA);
461 		int bcmp = m_versB.opCmp(o.m_versB);
462 
463 		Dependency d = this;
464 		d.m_inclusiveA = !m_inclusiveA && acmp >= 0 ? false : o.m_inclusiveA;
465 		d.m_versA = acmp > 0 ? m_versA : o.m_versA;
466 		d.m_inclusiveB = !m_inclusiveB && bcmp <= 0 ? false : o.m_inclusiveB;
467 		d.m_versB = bcmp < 0 ? m_versB : o.m_versB;
468 		d.m_optional = m_optional && o.m_optional;
469 		if (!d.valid) return invalid;
470 
471 		return d;
472 	}
473 
474 	private static bool isDigit(char ch) { return ch >= '0' && ch <= '9'; }
475 	private static string skipComp(ref string c) {
476 		size_t idx = 0;
477 		while (idx < c.length && !isDigit(c[idx]) && c[idx] != Version.branchPrefix) idx++;
478 		enforce(idx < c.length, "Expected version number in version spec: "~c);
479 		string cmp = idx==c.length-1||idx==0? ">=" : c[0..idx];
480 		c = c[idx..$];
481 		switch(cmp) {
482 			default: enforce(false, "No/Unknown comparison specified: '"~cmp~"'"); return ">=";
483 			case ">=": goto case; case ">": goto case;
484 			case "<=": goto case; case "<": goto case;
485 			case "==": return cmp;
486 		}
487 	}
488 
489 	private static bool doCmp(bool inclusive, ref const Version a, ref const Version b) {
490 		return inclusive ? a <= b : a < b;
491 	}
492 }
493 
494 unittest {
495 	Dependency a = Dependency(">=1.1.0"), b = Dependency(">=1.3.0");
496 	assert (a.merge(b).valid() && a.merge(b).versionSpec == ">=1.3.0", a.merge(b).toString());
497 
498 	assertThrown(Dependency("<=2.0.0 >=1.0.0"));
499 	assertThrown(Dependency(">=2.0.0 <=1.0.0"));
500 
501 	a = Dependency(">=1.0.0 <=5.0.0"); b = Dependency(">=2.0.0");
502 	assert (a.merge(b).valid() && a.merge(b).versionSpec == ">=2.0.0 <=5.0.0", a.merge(b).toString());
503 
504 	assertThrown(a = Dependency(">1.0.0 ==5.0.0"), "Construction is invalid");
505 
506 	a = Dependency(">1.0.0"); b = Dependency("<2.0.0");
507 	assert (a.merge(b).valid(), a.merge(b).toString());
508 	assert (a.merge(b).versionSpec == ">1.0.0 <2.0.0", a.merge(b).toString());
509 
510 	a = Dependency(">2.0.0"); b = Dependency("<1.0.0");
511 	assert (!(a.merge(b)).valid(), a.merge(b).toString());
512 
513 	a = Dependency(">=2.0.0"); b = Dependency("<=1.0.0");
514 	assert (!(a.merge(b)).valid(), a.merge(b).toString());
515 
516 	a = Dependency("==2.0.0"); b = Dependency("==1.0.0");
517 	assert (!(a.merge(b)).valid(), a.merge(b).toString());
518 
519 	a = Dependency("1.0.0"); b = Dependency("==1.0.0");
520 	assert (a == b);
521 
522 	a = Dependency("<=2.0.0"); b = Dependency("==1.0.0");
523 	Dependency m = a.merge(b);
524 	assert (m.valid(), m.toString());
525 	assert (m.matches(Version("1.0.0")));
526 	assert (!m.matches(Version("1.1.0")));
527 	assert (!m.matches(Version("0.0.1")));
528 
529 
530 	// branches / head revisions
531 	a = Dependency(Version.masterBranch);
532 	assert(a.valid());
533 	assert(a.matches(Version.masterBranch));
534 	b = Dependency(Version.masterBranch);
535 	m = a.merge(b);
536 	assert(m.matches(Version.masterBranch));
537 
538 	//assertThrown(a = Dependency(Version.MASTER_STRING ~ " <=1.0.0"), "Construction invalid");
539 	assertThrown(a = Dependency(">=1.0.0 " ~ Version.masterBranch.toString()), "Construction invalid");
540 
541 	immutable string branch1 = Version.branchPrefix ~ "Branch1";
542 	immutable string branch2 = Version.branchPrefix ~ "Branch2";
543 
544 	//assertThrown(a = Dependency(branch1 ~ " " ~ branch2), "Error: '" ~ branch1 ~ " " ~ branch2 ~ "' succeeded");
545 	//assertThrown(a = Dependency(Version.MASTER_STRING ~ " " ~ branch1), "Error: '" ~ Version.MASTER_STRING ~ " " ~ branch1 ~ "' succeeded");
546 
547 	a = Dependency(branch1);
548 	b = Dependency(branch2);
549 	assert(!a.merge(b).valid, "Shouldn't be able to merge to different branches");
550 	b = a.merge(a);
551 	assert(b.valid, "Should be able to merge the same branches. (?)");
552 	assert(a == b);
553 
554 	a = Dependency(branch1);
555 	assert(a.matches(branch1), "Dependency(branch1) does not match 'branch1'");
556 	assert(a.matches(Version(branch1)), "Dependency(branch1) does not match Version('branch1')");
557 	assert(!a.matches(Version.masterBranch), "Dependency(branch1) matches Version.masterBranch");
558 	assert(!a.matches(branch2), "Dependency(branch1) matches 'branch2'");
559 	assert(!a.matches(Version("1.0.0")), "Dependency(branch1) matches '1.0.0'");
560 	a = Dependency(">=1.0.0");
561 	assert(!a.matches(Version(branch1)), "Dependency(1.0.0) matches 'branch1'");
562 
563 	// Testing optional dependencies.
564 	a = Dependency(">=1.0.0");
565 	assert(!a.optional, "Default is not optional.");
566 	b = a;
567 	assert(!a.merge(b).optional, "Merging two not optional dependencies wrong.");
568 	a.optional = true;
569 	assert(!a.merge(b).optional, "Merging optional with not optional wrong.");
570 	b.optional = true;
571 	assert(a.merge(b).optional, "Merging two optional dependencies wrong.");
572 
573 	// SemVer's sub identifiers.
574 	a = Dependency(">=1.0.0-beta");
575 	assert(!a.matches(Version("1.0.0-alpha")), "Failed: match 1.0.0-alpha with >=1.0.0-beta");
576 	assert(a.matches(Version("1.0.0-beta")), "Failed: match 1.0.0-beta with >=1.0.0-beta");
577 	assert(a.matches(Version("1.0.0")), "Failed: match 1.0.0 with >=1.0.0-beta");
578 	assert(a.matches(Version("1.0.0-rc")), "Failed: match 1.0.0-rc with >=1.0.0-beta");
579 
580 	// Approximate versions.
581 	a = Dependency("~>3.0");
582 	b = Dependency(">=3.0.0 <4.0.0-0");
583 	assert(a == b, "Testing failed: " ~ a.toString());
584 	assert(a.matches(Version("3.1.146")), "Failed: Match 3.1.146 with ~>0.1.2");
585 	assert(!a.matches(Version("0.2.0")), "Failed: Match 0.2.0 with ~>0.1.2");
586 	assert(!a.matches(Version("4.0.0-beta.1")));
587 	a = Dependency("~>3.0.0");
588 	assert(a == Dependency(">=3.0.0 <3.1.0-0"), "Testing failed: " ~ a.toString());
589 	a = Dependency("~>3.5");
590 	assert(a == Dependency(">=3.5.0 <4.0.0-0"), "Testing failed: " ~ a.toString());
591 	a = Dependency("~>3.5.0");
592 	assert(a == Dependency(">=3.5.0 <3.6.0-0"), "Testing failed: " ~ a.toString());
593 	assert(!Dependency("~>3.0.0").matches(Version("3.1.0-beta")));
594 
595 	a = Dependency("~>0.1.1");
596 	b = Dependency("==0.1.0");
597 	assert(!a.merge(b).valid);
598 	b = Dependency("==0.1.9999");
599 	assert(a.merge(b).valid);
600 	b = Dependency("==0.2.0");
601 	assert(!a.merge(b).valid);
602 	b = Dependency("==0.2.0-beta.1");
603 	assert(!a.merge(b).valid);
604 
605 	a = Dependency("~>1.0.1-beta");
606 	b = Dependency(">=1.0.1-beta <1.1.0-0");
607 	assert(a == b, "Testing failed: " ~ a.toString());
608 	assert(a.matches(Version("1.0.1-beta")));
609 	assert(a.matches(Version("1.0.1-beta.6")));
610 
611 	a = Dependency("~d2test");
612 	assert(!a.optional);
613 	assert(a.valid);
614 	assert(a.version_ == Version("~d2test"));
615 
616 	a = Dependency("==~d2test");
617 	assert(!a.optional);
618 	assert(a.valid);
619 	assert(a.version_ == Version("~d2test"));
620 
621 	a = Dependency.any;
622 	assert(!a.optional);
623 	assert(a.valid);
624 	assertThrown(a.version_);
625 	assert(a.matches(Version.masterBranch));
626 	assert(a.matches(Version("1.0.0")));
627 	assert(a.matches(Version("0.0.1-pre")));
628 	b = Dependency(">=1.0.1");
629 	assert(b == a.merge(b));
630 	assert(b == b.merge(a));
631 	b = Dependency(Version.masterBranch);
632 	assert(a.merge(b) == b);
633 	assert(b.merge(a) == b);
634 
635 	a.optional = true;
636 	assert(a.matches(Version.masterBranch));
637 	assert(a.matches(Version("1.0.0")));
638 	assert(a.matches(Version("0.0.1-pre")));
639 	b = Dependency(">=1.0.1");
640 	assert(b == a.merge(b));
641 	assert(b == b.merge(a));
642 	b = Dependency(Version.masterBranch);
643 	assert(a.merge(b) == b);
644 	assert(b.merge(a) == b);
645 
646 	logDebug("Dependency unittest success.");
647 }
648 
649 unittest {
650 	assert(Dependency("~>1.0.4").versionSpec == "~>1.0.4");
651 	assert(Dependency("~>1.4").versionSpec == "~>1.4");
652 	assert(Dependency("~>2").versionSpec == "~>2");
653 	assert(Dependency("~>1.0.4+1.2.3").versionSpec == "~>1.0.4");
654 }
655 
656 
657 /**
658 	Represents a version in semantic version format, or a branch identifier.
659 
660 	This can either have the form "~master", where "master" is a branch name,
661 	or the form "major.update.bugfix-prerelease+buildmetadata" (see the
662 	Semantic Versioning Specification v2.0.0 at http://semver.org/).
663 */
664 struct Version {
665 @safe:
666 	private {
667 		static immutable MAX_VERS = "99999.0.0";
668 		static immutable UNKNOWN_VERS = "unknown";
669 		static immutable masterString = "~master";
670 		enum branchPrefix = '~';
671 		string m_version;
672 	}
673 
674 	static immutable Version minRelease = Version("0.0.0");
675 	static immutable Version maxRelease = Version(MAX_VERS);
676 	static immutable Version masterBranch = Version(masterString);
677 	static immutable Version unknown = Version(UNKNOWN_VERS);
678 
679 	/** Constructs a new `Version` from its string representation.
680 	*/
681 	this(string vers)
682 	{
683 		enforce(vers.length > 1, "Version strings must not be empty.");
684 		if (vers[0] != branchPrefix && vers.ptr !is UNKNOWN_VERS.ptr)
685 			enforce(vers.isValidVersion(), "Invalid SemVer format: " ~ vers);
686 		m_version = vers;
687 	}
688 
689 	/** Constructs a new `Version` from its string representation.
690 
691 		This method is equivalent to calling the constructor and is used as an
692 		endpoint for the serialization framework.
693 	*/
694 	static Version fromString(string vers) { return Version(vers); }
695 
696 	bool opEquals(const Version oth) const { return opCmp(oth) == 0; }
697 
698 	/// Tests if this represents a branch instead of a version.
699 	@property bool isBranch() const { return m_version.length > 0 && m_version[0] == branchPrefix; }
700 
701 	/// Tests if this represents the master branch "~master".
702 	@property bool isMaster() const { return m_version == masterString; }
703 
704 	/** Tests if this represents a pre-release version.
705 
706 		Note that branches are always considered pre-release versions.
707 	*/
708 	@property bool isPreRelease() const {
709 		if (isBranch) return true;
710 		return isPreReleaseVersion(m_version);
711 	}
712 
713 	/// Tests if this represents the special unknown version constant.
714 	@property bool isUnknown() const { return m_version == UNKNOWN_VERS; }
715 
716 	/** Compares two versions/branches for precedence.
717 
718 		Versions generally have precedence over branches and the master branch
719 		has precedence over other branches. Apart from that, versions are
720 		compared using SemVer semantics, while branches are compared
721 		lexicographically.
722 	*/
723 	int opCmp(ref const Version other)
724 	const {
725 		if (isUnknown || other.isUnknown) {
726 			throw new Exception("Can't compare unknown versions! (this: %s, other: %s)".format(this, other));
727 		}
728 		if (isBranch || other.isBranch) {
729 			if(m_version == other.m_version) return 0;
730 			if (!isBranch) return 1;
731 			else if (!other.isBranch) return -1;
732 			if (isMaster) return 1;
733 			else if (other.isMaster) return -1;
734 			return this.m_version < other.m_version ? -1 : 1;
735 		}
736 
737 		return compareVersions(m_version, other.m_version);
738 	}
739 	/// ditto
740 	int opCmp(in Version other) const { return opCmp(other); }
741 
742 	/// Returns the string representation of the version/branch.
743 	string toString() const { return m_version; }
744 }
745 
746 unittest {
747 	Version a, b;
748 
749 	assertNotThrown(a = Version("1.0.0"), "Constructing Version('1.0.0') failed");
750 	assert(!a.isBranch, "Error: '1.0.0' treated as branch");
751 	assert(a == a, "a == a failed");
752 
753 	assertNotThrown(a = Version(Version.masterString), "Constructing Version("~Version.masterString~"') failed");
754 	assert(a.isBranch, "Error: '"~Version.masterString~"' treated as branch");
755 	assert(a.isMaster);
756 	assert(a == Version.masterBranch, "Constructed master version != default master version.");
757 
758 	assertNotThrown(a = Version("~BRANCH"), "Construction of branch Version failed.");
759 	assert(a.isBranch, "Error: '~BRANCH' not treated as branch'");
760 	assert(!a.isMaster);
761 	assert(a == a, "a == a with branch failed");
762 
763 	// opCmp
764 	a = Version("1.0.0");
765 	b = Version("1.0.0");
766 	assert(a == b, "a == b with a:'1.0.0', b:'1.0.0' failed");
767 	b = Version("2.0.0");
768 	assert(a != b, "a != b with a:'1.0.0', b:'2.0.0' failed");
769 	a = Version.masterBranch;
770 	b = Version("~BRANCH");
771 	assert(a != b, "a != b with a:MASTER, b:'~branch' failed");
772 	assert(a > b);
773 	assert(a < Version("0.0.0"));
774 	assert(b < Version("0.0.0"));
775 	assert(a > Version("~Z"));
776 	assert(b < Version("~Z"));
777 
778 	// SemVer 2.0.0-rc.2
779 	a = Version("2.0.0-rc.2");
780 	b = Version("2.0.0-rc.3");
781 	assert(a < b, "Failed: 2.0.0-rc.2 < 2.0.0-rc.3");
782 
783 	a = Version("2.0.0-rc.2+build-metadata");
784 	b = Version("2.0.0+build-metadata");
785 	assert(a < b, "Failed: "~a.toString()~"<"~b.toString());
786 
787 	// 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
788 	Version[] versions;
789 	versions ~= Version("1.0.0-alpha");
790 	versions ~= Version("1.0.0-alpha.1");
791 	versions ~= Version("1.0.0-beta.2");
792 	versions ~= Version("1.0.0-beta.11");
793 	versions ~= Version("1.0.0-rc.1");
794 	versions ~= Version("1.0.0");
795 	for(int i=1; i<versions.length; ++i)
796 		for(int j=i-1; j>=0; --j)
797 			assert(versions[j] < versions[i], "Failed: " ~ versions[j].toString() ~ "<" ~ versions[i].toString());
798 
799 	a = Version.unknown;
800 	b = Version.minRelease;
801 	assertThrown(a == b, "Failed: compared " ~ a.toString() ~ " with " ~ b.toString() ~ "");
802 
803 	a = Version.unknown;
804 	b = Version.unknown;
805 	assertThrown(a == b, "Failed: UNKNOWN == UNKNOWN");
806 
807 	assert(Version("1.0.0+a") == Version("1.0.0+b"));
808 }