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