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 }