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