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