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