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