1 /**
2 	Stuff with dependencies.
3 
4 	Copyright: © 2012-2013 Matthias Dondorff
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.conv;
21 import std.exception;
22 import std.regex;
23 import std.string;
24 import std.typecons;
25 static import std.compiler;
26 
27 /**
28 	A version in the format "major.update.bugfix-prerelease+buildmetadata" or
29 	"~master", to identify trunk, or "~branch_name" to identify a branch. Both 
30 	Version types starting with "~"	refer to the head revision of the 
31 	corresponding branch.
32 	
33 	Except for the "~branch" Version format, this follows the Semantic Versioning
34 	Specification (SemVer) 2.0.0-rc.2.
35 */
36 struct Version {
37 	private { 
38 		enum MASTER_VERS = cast(size_t)(-1);
39 		enum MAX_VERS = "99999.0.0";
40 		string m_version;
41 	}
42 
43 	static @property RELEASE() { return Version("0.0.0"); }
44 	static @property HEAD() { return Version(MAX_VERS); }
45 	static @property MASTER() { return Version(MASTER_STRING); }
46 	static @property MASTER_STRING() { return "~master"; }
47 	static @property BRANCH_IDENT() { return '~'; }
48 	
49 	this(string vers)
50 	{
51 		enforce(vers.length > 1, "Version strings must not be empty.");
52 		enforce(vers[0] == BRANCH_IDENT || vers.isValidVersion(), "Invalid SemVer format: "~vers);
53 		m_version = vers;
54 	}
55 
56 	bool opEquals(ref const Version oth) const { return m_version == oth.m_version; }
57 	bool opEquals(const Version oth) const { return m_version == oth.m_version; }
58 	
59 	/// Returns true, if this version indicates a branch, which is not the trunk.
60 	@property bool isBranch() const { return m_version[0] == BRANCH_IDENT; }
61 	@property bool isMaster() const { return m_version == MASTER_STRING; }
62 	@property bool isPreRelease() const {
63 		if (isBranch) return true;
64 		return isPreReleaseVersion(m_version);
65 	}
66 
67 	/** 
68 		Comparing Versions is generally possible, but comparing Versions 
69 		identifying branches other than master will fail. Only equality
70 		can be tested for these.
71 	*/
72 	int opCmp(ref const Version other)
73 	const {
74 		if(isBranch || other.isBranch) {
75 			if(m_version == other.m_version) return 0;
76 			else throw new Exception("Can't compare branch versions! (this: %s, other: %s)".format(this, other));
77 		}
78 
79 		return compareVersions(isMaster ? MAX_VERS : m_version, other.isMaster ? MAX_VERS : other.m_version);
80 	}
81 	int opCmp(in Version other) const { return opCmp(other); }
82 	
83 	string toString() const { return m_version; }
84 }
85 
86 unittest {
87 	Version a, b;
88 
89 	assertNotThrown(a = Version("1.0.0"), "Constructing Version('1.0.0') failed");
90 	assert(!a.isBranch, "Error: '1.0.0' treated as branch");
91 	assert(a == a, "a == a failed");
92 
93 	assertNotThrown(a = Version(Version.MASTER_STRING), "Constructing Version("~Version.MASTER_STRING~"') failed");
94 	assert(a.isBranch, "Error: '"~Version.MASTER_STRING~"' treated as branch");
95 	assert(a.isMaster);
96 	assert(a == Version.MASTER, "Constructed master version != default master version.");
97 
98 	assertNotThrown(a = Version("~BRANCH"), "Construction of branch Version failed.");
99 	assert(a.isBranch, "Error: '~BRANCH' not treated as branch'");
100 	assert(!a.isMaster);
101 	assert(a == a, "a == a with branch failed");
102 
103 	// opCmp
104 	a = Version("1.0.0");
105 	b = Version("1.0.0");
106 	assert(a == b, "a == b with a:'1.0.0', b:'1.0.0' failed");
107 	b = Version("2.0.0");
108 	assert(a != b, "a != b with a:'1.0.0', b:'2.0.0' failed");
109 	a = Version(Version.MASTER_STRING);
110 	b = Version("~BRANCH");
111 	assert(a != b, "a != b with a:MASTER, b:'~branch' failed");
112 	
113 	// SemVer 2.0.0-rc.2
114 	a = Version("2.0.0-rc.2");
115 	b = Version("2.0.0-rc.3");
116 	assert(a < b, "Failed: 2.0.0-rc.2 < 2.0.0-rc.3");
117 	
118 	a = Version("2.0.0-rc.2+build-metadata");
119 	b = Version("2.0.0+build-metadata");
120 	assert(a < b, "Failed: "~to!string(a)~"<"~to!string(b));
121 	
122 	// 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
123 	Version[] versions;
124 	versions ~= Version("1.0.0-alpha");
125 	versions ~= Version("1.0.0-alpha.1");
126 	versions ~= Version("1.0.0-beta.2");
127 	versions ~= Version("1.0.0-beta.11");
128 	versions ~= Version("1.0.0-rc.1");
129 	versions ~= Version("1.0.0");
130 	for(int i=1; i<versions.length; ++i)
131 		for(int j=i-1; j>=0; --j)
132 			assert(versions[j] < versions[i], "Failed: " ~ to!string(versions[j]) ~ "<" ~ to!string(versions[i]));
133 }
134 
135 /// Representing a dependency, which is basically a version string and a 
136 /// compare methode, e.g. '>=1.0.0 <2.0.0' (i.e. a space separates the two
137 /// version numbers)
138 struct Dependency {
139 	private {
140 		string m_cmpA;
141 		Version m_versA;
142 		string m_cmpB;
143 		Version m_versB;
144 		Path m_path;
145 		string m_configuration = "library";
146 		bool m_optional = false;
147 	}
148 
149 	this(string ves)
150 	{
151 		enforce(ves.length > 0);
152 		string orig = ves;
153 		if (ves[0] == Version.BRANCH_IDENT) {
154 			m_cmpA = ">=";
155 			m_cmpB = "<=";
156 			m_versA = m_versB = Version(ves);
157 		} else {
158 			m_cmpA = skipComp(ves);
159 			size_t idx2 = std..string.indexOf(ves, " ");
160 			if (idx2 == -1) {
161 				if (m_cmpA == "<=" || m_cmpA == "<") {
162 					m_versA = Version.RELEASE;
163 					m_cmpB = m_cmpA;
164 					m_cmpA = ">=";
165 					m_versB = Version(ves);
166 				} else if (m_cmpA == ">=" || m_cmpA == ">") {
167 					m_versA = Version(ves);
168 					m_versB = Version.HEAD;
169 					m_cmpB = "<=";
170 				} else {
171 					// Converts "==" to ">=a&&<=a", which makes merging easier
172 					m_versA = m_versB = Version(ves);
173 					m_cmpA = ">=";
174 					m_cmpB = "<=";
175 				}
176 			} else {
177 				assert(ves[idx2] == ' ');
178 				m_versA = Version(ves[0..idx2]);
179 				string v2 = ves[idx2+1..$];
180 				m_cmpB = skipComp(v2);
181 				m_versB = Version(v2);
182 
183 				enforce(!m_versA.isBranch, "Partly a branch (A): %s", ves);
184 				enforce(!m_versB.isBranch, "Partly a branch (B): %s", ves);
185 
186 				if (m_versB < m_versA) {
187 					swap(m_versA, m_versB);
188 					swap(m_cmpA, m_cmpB);
189 				}
190 				enforce( m_cmpA != "==" && m_cmpB != "==", "For equality, please specify a single version.");
191 			}
192 		}
193 	}
194 
195 	this(in Version ver)
196 	{
197 		m_cmpA = ">=";
198 		m_cmpB = "<=";
199 		m_versA = ver;
200 		m_versB = ver;
201 	}
202 
203 	@property void path(Path value) { m_path = value; }
204 	@property Path path() const { return m_path; }
205 	@property bool optional() const { return m_optional; }
206 	@property void optional(bool optional) { m_optional = optional; }
207 
208 	@property Version version_() const { assert(m_versA == m_versB); return m_versA; }
209 	
210 	string toString()
211 	const {
212 		string r;
213 		// Special "==" case
214 		if( m_versA == m_versB && m_cmpA == ">=" && m_cmpB == "<=" ){
215 			if( m_versA == Version.MASTER ) r = "~master";
216 			else r = "==" ~ to!string(m_versA);
217 		} else {
218 			if( m_versA != Version.RELEASE ) r = m_cmpA ~ to!string(m_versA);
219 			if( m_versB != Version.HEAD ) r ~= (r.length==0?"" : " ") ~ m_cmpB ~ to!string(m_versB);
220 			if( m_versA == Version.RELEASE && m_versB == Version.HEAD ) r = ">=0.0.0";
221 		}
222 		// TODO(mdondorff): add information to path and optionality.
223 		return r;
224 	}
225 
226 	bool opEquals(in Dependency o)
227 	const {
228 		// TODO(mdondorff): Check if not comparing the path is correct for all clients.
229 		return o.m_cmpA == m_cmpA && o.m_cmpB == m_cmpB 
230 			&& o.m_versA == m_versA && o.m_versB == m_versB 
231 			&& o.m_configuration == m_configuration
232 			&& o.m_optional == m_optional;
233 	}
234 	
235 	bool valid() const {
236 		return m_versA == m_versB // compare not important
237 			|| (m_versA < m_versB && doCmp(m_cmpA, m_versB, m_versA) && doCmp(m_cmpB, m_versA, m_versB));
238 	}
239 	
240 	bool matches(string vers) const { return matches(Version(vers)); }
241 	bool matches(const(Version) v) const { return matches(v); }
242 	bool matches(ref const(Version) v) const {
243 		//logDebug(" try match: %s with: %s", v, this);
244 		// Master only matches master
245 		if(m_versA.isBranch) {
246 			enforce(m_versA == m_versB);
247 			return m_versA == v;
248 		}
249 		if(v.isBranch || m_versA.isBranch)
250 			return m_versA == v;
251 		if( !doCmp(m_cmpA, v, m_versA) )
252 			return false;
253 		if( !doCmp(m_cmpB, v, m_versB) )
254 			return false;
255 		return true;
256 	}
257 	
258 	/// Merges to versions
259 	Dependency merge(ref const(Dependency) o) const {
260 		if (!valid()) return this;
261 		if (!o.valid()) return o;
262 		if (m_configuration != o.m_configuration)
263 			return Dependency(">=1.0.0 <=0.0.0");
264 		
265 		Version a = m_versA > o.m_versA? m_versA : o.m_versA;
266 		Version b = m_versB < o.m_versB? m_versB : o.m_versB;
267 	
268 		Dependency d = this;
269 		d.m_cmpA = !doCmp(m_cmpA, a,a)? m_cmpA : o.m_cmpA;
270 		d.m_versA = a;
271 		d.m_cmpB = !doCmp(m_cmpB, b,b)? m_cmpB : o.m_cmpB;
272 		d.m_versB = b;
273 		d.m_optional = m_optional && o.m_optional;
274 		
275 		return d;
276 	}
277 	
278 	private static bool isDigit(char ch) { return ch >= '0' && ch <= '9'; }
279 	private static string skipComp(ref string c) {
280 		size_t idx = 0;
281 		while (idx < c.length && !isDigit(c[idx]) && c[idx] != Version.BRANCH_IDENT) idx++;
282 		enforce(idx < c.length, "Expected version number in version spec: "~c);
283 		string cmp = idx==c.length-1||idx==0? ">=" : c[0..idx];
284 		c = c[idx..$];
285 		switch(cmp) {
286 			default: enforce(false, "No/Unknown comparision specified: '"~cmp~"'"); return ">=";
287 			case ">=": goto case; case ">": goto case;
288 			case "<=": goto case; case "<": goto case;
289 			case "==": return cmp;
290 		}
291 	}
292 	
293 	private static bool doCmp(string mthd, ref const Version a, ref const Version b) {
294 		//logDebug("Calling %s%s%s", a, mthd, b);
295 		switch(mthd) {
296 			default: throw new Exception("Unknown comparison operator: "~mthd);
297 			case ">": return a>b;
298 			case ">=": return a>=b;
299 			case "==": return a==b;
300 			case "<=": return a<=b;
301 			case "<": return a<b;
302 		}
303 	}
304 }
305 
306 unittest {
307 	Dependency a = Dependency(">=1.1.0"), b = Dependency(">=1.3.0");
308 	assert( a.merge(b).valid() && to!string(a.merge(b)) == ">=1.3.0", to!string(a.merge(b)) );
309 	
310 	a = Dependency("<=1.0.0 >=2.0.0");
311 	assert( !a.valid(), to!string(a) );
312 	
313 	a = Dependency(">=1.0.0 <=5.0.0"), b = Dependency(">=2.0.0");
314 	assert( a.merge(b).valid() && to!string(a.merge(b)) == ">=2.0.0 <=5.0.0", to!string(a.merge(b)) );
315 	
316 	assertThrown(a = Dependency(">1.0.0 ==5.0.0"), "Construction is invalid");
317 	
318 	a = Dependency(">1.0.0"), b = Dependency("<2.0.0");
319 	assert( a.merge(b).valid(), to!string(a.merge(b)));
320 	assert( to!string(a.merge(b)) == ">1.0.0 <2.0.0", to!string(a.merge(b)) );
321 	
322 	a = Dependency(">2.0.0"), b = Dependency("<1.0.0");
323 	assert( !(a.merge(b)).valid(), to!string(a.merge(b)));
324 	
325 	a = Dependency(">=2.0.0"), b = Dependency("<=1.0.0");
326 	assert( !(a.merge(b)).valid(), to!string(a.merge(b)));
327 	
328 	a = Dependency("==2.0.0"), b = Dependency("==1.0.0");
329 	assert( !(a.merge(b)).valid(), to!string(a.merge(b)));
330 	
331 	a = Dependency("<=2.0.0"), b = Dependency("==1.0.0");
332 	Dependency m = a.merge(b);
333 	assert( m.valid(), to!string(m));
334 	assert( m.matches( Version("1.0.0") ) );
335 	assert( !m.matches( Version("1.1.0") ) );
336 	assert( !m.matches( Version("0.0.1") ) );
337 
338 
339 	// branches / head revisions
340 	a = Dependency(Version.MASTER_STRING);
341 	assert(a.valid());
342 	assert(a.matches(Version.MASTER));
343 	b = Dependency(Version.MASTER_STRING);
344 	m = a.merge(b);
345 	assert(m.matches(Version.MASTER));
346 
347 	//assertThrown(a = Dependency(Version.MASTER_STRING ~ " <=1.0.0"), "Construction invalid");
348 	assertThrown(a = Dependency(">=1.0.0 " ~ Version.MASTER_STRING), "Construction invalid");
349 
350 	a = Dependency(">=1.0.0");
351 	b = Dependency(Version.MASTER_STRING);
352 
353 	//// support crazy stuff like this?
354 	//m = a.merge(b);
355 	//assert(m.valid());
356 	//assert(m.matches(Version.MASTER));
357 
358 	//b = Dependency("~not_the_master");
359 	//m = a.merge(b);
360 //	assert(!m.valid());
361 
362 	immutable string branch1 = Version.BRANCH_IDENT ~ "Branch1";
363 	immutable string branch2 = Version.BRANCH_IDENT ~ "Branch2";
364 
365 	//assertThrown(a = Dependency(branch1 ~ " " ~ branch2), "Error: '" ~ branch1 ~ " " ~ branch2 ~ "' succeeded");
366 	//assertThrown(a = Dependency(Version.MASTER_STRING ~ " " ~ branch1), "Error: '" ~ Version.MASTER_STRING ~ " " ~ branch1 ~ "' succeeded");
367 
368 	a = Dependency(branch1);
369 	b = Dependency(branch2);
370 	assertThrown(a.merge(b), "Shouldn't be able to merge to different branches");
371 	assertNotThrown(b = a.merge(a), "Should be able to merge the same branches. (?)");
372 	assert(a == b);
373 
374 	a = Dependency(branch1);
375 	assert(a.matches(branch1), "Dependency(branch1) does not match 'branch1'");
376 	assert(a.matches(Version(branch1)), "Dependency(branch1) does not match Version('branch1')");
377 	assert(!a.matches(Version.MASTER), "Dependency(branch1) matches Version.MASTER");
378 	assert(!a.matches(branch2), "Dependency(branch1) matches 'branch2'");
379 	assert(!a.matches(Version("1.0.0")), "Dependency(branch1) matches '1.0.0'");
380 	a = Dependency(">=1.0.0");
381 	assert(!a.matches(Version(branch1)), "Dependency(1.0.0) matches 'branch1'");
382 
383 	// Testing optional dependencies.
384 	a = Dependency(">=1.0.0");
385 	assert(!a.optional, "Default is not optional.");
386 	b = a;
387 	assert(!a.merge(b).optional, "Merging two not optional dependencies wrong.");
388 	a.optional = true;
389 	assert(!a.merge(b).optional, "Merging optional with not optional wrong.");
390 	b.optional = true;
391 	assert(a.merge(b).optional, "Merging two optional dependencies wrong.");
392 
393 	a = Dependency("~d2test");
394 	assert(!a.optional);
395 	assert(a.valid);
396 	assert(a.version_ == Version("~d2test"));
397 
398 	a = Dependency("==~d2test");
399 	assert(!a.optional);
400 	assert(a.valid);
401 	assert(a.version_ == Version("~d2test"));
402 
403 	logDebug("Dependency Unittest sucess.");
404 }
405 
406 struct RequestedDependency {
407 	this( string pkg, Dependency de) {
408 		dependency = de;
409 		packages[pkg] = de;
410 	}
411 	Dependency dependency;
412 	Dependency[string] packages;
413 }
414 
415 class DependencyGraph {	
416 	this(const Package root) {
417 		m_root = root;
418 		m_packages[m_root.name] = root;
419 	}
420 	
421 	void insert(const Package p) {
422 		enforce(p.name != m_root.name, format("Dependency with the same name as the root package (%s) detected.", p.name));
423 		m_packages[p.name] = p;
424 	}
425 	
426 	void remove(const Package p) {
427 		enforce(p.name != m_root.name);
428 		Rebindable!(const Package)* pkg = p.name in m_packages;
429 		if( pkg ) m_packages.remove(p.name);
430 	}
431 	
432 	private
433 	{
434 		alias Rebindable!(const Package) PkgType;
435 	}
436 	
437 	void clearUnused() {
438 		Rebindable!(const Package)[string] unused = m_packages.dup;
439 		unused.remove(m_root.name);
440 		forAllDependencies( (const PkgType* avail, string s, Dependency d, const Package issuer) {
441 			if(avail && d.matches(avail.vers))
442 				unused.remove(avail.name);
443 		});
444 		foreach(string unusedPkg, d; unused) {
445 			logDebug("Removed unused package: "~unusedPkg);
446 			m_packages.remove(unusedPkg);
447 		}
448 	}
449 	
450 	RequestedDependency[string] conflicted() const {
451 		RequestedDependency[string] deps = needed();
452 		RequestedDependency[string] conflicts;
453 		foreach(string pkg, d; deps)
454 			if(!d.dependency.valid())
455 				conflicts[pkg] = d;
456 		return conflicts;
457 	}
458 	
459 	RequestedDependency[string] missing() const {
460 		RequestedDependency[string] deps;
461 		forAllDependencies( (const PkgType* avail, string pkgId, Dependency d, const Package issuer) {
462 			if(!d.optional && (!avail || !d.matches(avail.vers)))
463 				addDependency(deps, pkgId, d, issuer);
464 		});
465 		return deps;
466 	}
467 	
468 	RequestedDependency[string] needed() const {
469 		RequestedDependency[string] deps;
470 		forAllDependencies( (const PkgType* avail, string pkgId, Dependency d, const Package issuer) {
471 			if(!d.optional)
472 				addDependency(deps, pkgId, d, issuer);
473 		});
474 		return deps;
475 	}
476 
477 	RequestedDependency[string] optional() const {
478 		RequestedDependency[string] allDeps;
479 		forAllDependencies( (const PkgType* avail, string pkgId, Dependency d, const Package issuer) {
480 			addDependency(allDeps, pkgId, d, issuer);
481 		});
482 		RequestedDependency[string] optionalDeps;
483 		foreach(id, req; allDeps)
484 			if(req.dependency.optional) optionalDeps[id] = req;
485 		return optionalDeps;
486 	}
487 	
488 	private void forAllDependencies(void delegate (const PkgType* avail, string pkgId, Dependency d, const Package issuer) dg) const {
489 		foreach(string issuerPackag, issuer; m_packages) {
490 			foreach(string depPkg, dependency; issuer.dependencies) {
491 				auto availPkg = depPkg in m_packages;
492 				dg(availPkg, depPkg, dependency, issuer);
493 			}
494 		}
495 	}
496 	
497 	private static void addDependency(ref RequestedDependency[string] deps, string packageId, Dependency d, const Package issuer) {
498 		auto d2 = packageId in deps;
499 		if(!d2) {
500 			deps[packageId] = RequestedDependency(issuer.name, d);
501 		}
502 		else {
503 			d2.dependency = d2.dependency.merge(d);
504 			d2.packages[issuer.name] = d;
505 		}
506 	}
507 	
508 	private {
509 		const Package m_root;
510 		PkgType[string] m_packages;
511 	}
512 
513 	unittest {
514 		/*
515 			R (master) -> A (master)
516 		*/
517 		auto R_json = parseJsonString(`
518 		{
519 			"name": "R",
520 			"dependencies": {
521 				"A": "~master",
522 				"B": "1.0.0"
523 			},
524 			"version": "~master"
525 		}
526 			`);
527 		Package r_master = new Package(R_json);
528 		auto graph = new DependencyGraph(r_master);
529 
530 		assert(graph.conflicted.length == 0, "There are conflicting packages");
531 
532 		void expectA(RequestedDependency[string] requested, string name) {
533 			assert("A" in requested, "Package A is not the "~name~" package");
534 			assert(requested["A"].dependency == Dependency("~master"), "Package A is not "~name~" as ~master version.");
535 			assert("R" in requested["A"].packages, "Package R is not the issuer of "~name~" Package A(~master).");
536 			assert(requested["A"].packages["R"] == Dependency("~master"), "Package R is not the issuer of "~name~" Package A(~master).");
537 		}
538 		void expectB(RequestedDependency[string] requested, string name) {
539 			assert("B" in requested, "Package B is not the "~name~" package");
540 			assert(requested["B"].dependency == Dependency("1.0.0"), "Package B is not "~name~" as 1.0.0 version.");
541 			assert("R" in requested["B"].packages, "Package R is not the issuer of "~name~" Package B(1.0.0).");
542 			assert(requested["B"].packages["R"] == Dependency("1.0.0"), "Package R is not the issuer of "~name~" Package B(1.0.0).");
543 		}
544 		auto missing = graph.missing();
545 		assert(missing.length == 2, "Invalid count of missing items");
546 		expectA(missing, "missing");
547 		expectB(missing, "missing");
548 
549 		auto needed = graph.needed();
550 		assert(needed.length == 2, "Invalid count of needed packages.");		
551 		expectA(needed, "needed");
552 		expectB(needed, "needed");
553 
554 		assert(graph.optional.length == 0, "There are optional packages reported");
555 
556 		auto A_json = parseJsonString(`
557 		{
558 			"name": "A",
559 			"dependencies": {
560 			},
561 			"version": "~master"
562 		}
563 			`);
564 		Package a_master = new Package(A_json);
565 		graph.insert(a_master);
566 
567 		assert(graph.conflicted.length == 0, "There are conflicting packages");
568 
569 		auto missing2 = graph.missing;
570 		assert(missing2.length == 1, "Missing list does not contain an package.");
571 		expectB(missing2, "missing2");
572 
573 		needed = graph.needed;
574 		assert(needed.length == 2, "Invalid count of needed packages.");		
575 		expectA(needed, "needed");
576 		expectB(needed, "needed");
577 
578 		assert(graph.optional.length == 0, "There are optional packages reported");
579 	}
580 
581 	unittest {
582 		/*
583 			R -> R:sub
584 		*/
585 		auto R_json = parseJsonString(`
586 		{
587 			"name": "R",
588 			"dependencies": {
589 				"R:sub": "~master"
590 			},
591 			"version": "~master",
592 			"subPackages": [
593 				{
594 					"name": "sub"
595 				}
596 			]
597 		}
598 			`);
599 
600 		Package r_master = new Package(R_json);
601 		auto graph = new DependencyGraph(r_master);
602 		assert(graph.missing().length == 1);
603 		// Subpackages need to be explicitly added.
604 		graph.insert(r_master.subPackages[0]);
605 		assert(graph.missing().length == 0);
606 	}
607 
608 	unittest {
609 		/*
610 			R -> S:sub
611 		*/
612 		auto R_json = parseJsonString(`
613 		{
614 			"name": "R",
615 			"dependencies": {
616 				"S:sub": "~master"
617 			},
618 			"version": "~master"
619 		}
620 			`);
621 		auto S_w_sub_json = parseJsonString(`
622 		{
623 			"name": "S",
624 			"version": "~master",
625 			"subPackages": [
626 				{
627 					"name": "sub"
628 				}
629 			]
630 		}
631 			`);
632 		auto S_wout_sub_json = parseJsonString(`
633 		{
634 			"name": "S",
635 			"version": "~master"
636 		}
637 			`);
638 		auto sub_json = parseJsonString(`
639 		{
640 			"name": "sub",
641 			"version": "~master"
642 		}
643 			`);
644 
645 		Package r_master = new Package(R_json);
646 		auto graph = new DependencyGraph(r_master);
647 		assert(graph.missing().length == 1);
648 		Package s_master = new Package(S_w_sub_json);
649 		graph.insert(s_master);
650 		assert(graph.missing().length == 1);
651 		graph.insert(s_master.subPackages[0]);
652 		assert(graph.missing().length == 0);
653 
654 		graph = new DependencyGraph(r_master);
655 		assert(graph.missing().length == 1);
656 		s_master = new Package(S_wout_sub_json);
657 		graph.insert(s_master);
658 		assert(graph.missing().length == 1);
659 
660 		graph = new DependencyGraph(r_master);
661 		assert(graph.missing().length == 1);
662 		s_master = new Package(sub_json);
663 		graph.insert(s_master);
664 		assert(graph.missing().length == 1);
665 	}
666 }