Newer
Older
dub_jkp / source / dub / dependency.d
@Robert Klotzner Robert Klotzner on 15 Mar 2013 15 KB TODO "optimize": Done
  1. /**
  2. Stuff with dependencies.
  3.  
  4. Copyright: © 2012 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.utils;
  11. import dub.package_;
  12.  
  13. import vibecompat.core.log;
  14. import vibecompat.core.file;
  15. import vibecompat.data.json;
  16. import vibecompat.inet.url;
  17.  
  18. import std.array;
  19. import std.string;
  20. import std.exception;
  21. import std.algorithm;
  22. import std.typecons;
  23. import std.conv;
  24. static import std.compiler;
  25.  
  26.  
  27. /**
  28. A version in the format "major.update.bugfix" or "~master", to identify trunk,
  29. or "~branch_name" to identify a branch. Both Version types starting with "~"
  30. refer to the head revision of the corresponding branch.
  31. */
  32. struct Version {
  33. static const Version RELEASE = Version("0.0.0");
  34. static const Version HEAD = Version(to!string(MAX_VERS)~"."~to!string(MAX_VERS)~"."~to!string(MAX_VERS));
  35. static const Version INVALID = Version();
  36. static const Version MASTER = Version(MASTER_STRING);
  37. static const string MASTER_STRING = "~master";
  38. static immutable char BRANCH_IDENT = '~';
  39. private {
  40. static const size_t MAX_VERS = 9999;
  41. static const size_t MASTER_VERS = cast(size_t)(-1);
  42. string sVersion;
  43. }
  44. this(string vers)
  45. {
  46. enforce(vers.length > 1);
  47. enforce(vers[0] == BRANCH_IDENT || count(vers, ".") == 2);
  48. sVersion = vers;
  49. /*
  50. if(vers == MASTER_STRING) {
  51. v = [MASTER_VERS, MASTER_VERS, MASTER_VERS];
  52. } else {
  53. auto toks = split(vers, ".");
  54. v.length = toks.length;
  55. foreach( i, t; toks ) v[i] = t.to!size_t();
  56. }
  57. */
  58. }
  59.  
  60. this(const Version o)
  61. {
  62. sVersion = o.sVersion;
  63. }
  64. bool opEquals(ref const Version oth) const { return sVersion == oth.sVersion; }
  65. bool opEquals(const Version oth) const { return sVersion == oth.sVersion; }
  66. /// Returns true, if this version indicates a branch, which is not the trunk.
  67. @property bool isBranch() const { return sVersion[0] == BRANCH_IDENT && sVersion != MASTER_STRING; }
  68.  
  69. /**
  70. Comparing Versions is generally possible, but comparing Versions
  71. identifying branches other than master will fail. Only equality
  72. can be tested for these.
  73. */
  74. int opCmp(ref const Version other)
  75. const {
  76. if(isBranch || other.isBranch) {
  77. if(sVersion == other.sVersion) return 0;
  78. else throw new Exception("Can't compare branch versions! (this: %s, other: %s)".format(this, other));
  79. }
  80.  
  81. size_t v[] = toArray();
  82. size_t ov[] = other.toArray();
  83.  
  84. foreach( i; 0 .. min(v.length, ov.length) )
  85. if( v[i] != ov[i] )
  86. return cast(int)v[i] - cast(int)ov[i];
  87. return cast(int)v.length - cast(int)ov.length;
  88. }
  89. int opCmp(in Version other) const { return opCmp(other); }
  90. string toString() const { return sVersion; }
  91.  
  92. private size_t[] toArray() const {
  93. enforce(!isBranch, "Cannot convert a branch an array representation (%s)", sVersion);
  94.  
  95. size_t v[];
  96. if(sVersion == MASTER_STRING) {
  97. v = [MASTER_VERS, MASTER_VERS, MASTER_VERS];
  98. } else {
  99. auto toks = split(sVersion, ".");
  100. v.length = toks.length;
  101. foreach( i, t; toks ) v[i] = t.to!size_t();
  102. }
  103. return v;
  104. }
  105. }
  106.  
  107. unittest {
  108. Version a, b;
  109.  
  110. assertNotThrown(a = Version("1.0.0"), "Constructing Version('1.0.0') failed");
  111. assert(!a.isBranch, "Error: '1.0.0' treated as branch");
  112. size_t[] arrRepr = [ 1, 0, 0 ];
  113. assert(a.toArray() == arrRepr, "Array representation of '1.0.0' is wrong.");
  114. assert(a == a, "a == a failed");
  115.  
  116. assertNotThrown(a = Version(Version.MASTER_STRING), "Constructing Version("~Version.MASTER_STRING~"') failed");
  117. assert(!a.isBranch, "Error: '"~Version.MASTER_STRING~"' treated as branch");
  118. arrRepr = [ Version.MASTER_VERS, Version.MASTER_VERS, Version.MASTER_VERS ];
  119. assert(a.toArray() == arrRepr, "Array representation of '"~Version.MASTER_STRING~"' is wrong.");
  120. assert(a == Version.MASTER, "Constructed master version != default master version.");
  121.  
  122. assertNotThrown(a = Version("~BRANCH"), "Construction of branch Version failed.");
  123. assert(a.isBranch, "Error: '~BRANCH' not treated as branch'");
  124. assertThrown(a.toArray(), "Error: Converting branch version to array succeded.");
  125. assert(a == a, "a == a with branch failed");
  126.  
  127. // opCmp
  128. a = Version("1.0.0");
  129. b = Version("1.0.0");
  130. assert(a == b, "a == b with a:'1.0.0', b:'1.0.0' failed");
  131. b = Version("2.0.0");
  132. assert(a != b, "a != b with a:'1.0.0', b:'2.0.0' failed");
  133. a = Version(Version.MASTER_STRING);
  134. b = Version("~BRANCH");
  135. assert(a != b, "a != b with a:MASTER, b:'~branch' failed");
  136. }
  137.  
  138. /// Representing a dependency, which is basically a version string and a
  139. /// compare methode, e.g. '>=1.0.0 <2.0.0' (i.e. a space separates the two
  140. /// version numbers)
  141. class Dependency {
  142. private {
  143. string m_cmpA;
  144. Version m_versA;
  145. string m_cmpB;
  146. Version m_versB;
  147. Path m_path;
  148. string m_configuration = "library";
  149. }
  150.  
  151. this( string ves ) {
  152. enforce( ves.length > 0);
  153. string orig = ves;
  154. if(ves[0] == Version.BRANCH_IDENT) {
  155. m_cmpA = ">=";
  156. m_cmpB = "<=";
  157. m_versA = m_versB = Version(ves);
  158. }
  159. else {
  160. m_cmpA = skipComp(ves);
  161. size_t idx2 = std.string.indexOf(ves, " ");
  162. if( idx2 == -1 ) {
  163. if( m_cmpA == "<=" || m_cmpA == "<" ) {
  164. m_versA = Version(Version.RELEASE);
  165. m_cmpB = m_cmpA;
  166. m_cmpA = ">=";
  167. m_versB = Version(ves);
  168. }
  169. else if( m_cmpA == ">=" || m_cmpA == ">" ) {
  170. m_versA = Version(ves);
  171. m_versB = Version(Version.HEAD);
  172. m_cmpB = "<=";
  173. }
  174. else {
  175. // Converts "==" to ">=a&&<=a", which makes merging easier
  176. m_versA = m_versB = Version(ves);
  177. m_cmpA = ">=";
  178. m_cmpB = "<=";
  179. }
  180. } else {
  181. assert(ves[idx2] == ' ');
  182. m_versA = Version(ves[0..idx2]);
  183. string v2 = ves[idx2+1..$];
  184. m_cmpB = skipComp(v2);
  185. m_versB = Version(v2);
  186.  
  187. enforce(!m_versA.isBranch, "Partly a branch (A): %s", ves);
  188. enforce(!m_versB.isBranch, "Partly a branch (B): %s", ves);
  189.  
  190. if( m_versB < m_versA ) {
  191. swap(m_versA, m_versB);
  192. swap(m_cmpA, m_cmpB);
  193. }
  194. enforce( m_cmpA != "==" && m_cmpB != "==", "For equality, please specify a single version.");
  195. }
  196. }
  197. }
  198.  
  199. this(in Version ver)
  200. {
  201. m_cmpA = ">=";
  202. m_cmpB = "<=";
  203. m_versA = ver;
  204. m_versB = ver;
  205. }
  206.  
  207. this(const Dependency o) {
  208. m_cmpA = o.m_cmpA; m_versA = Version(o.m_versA);
  209. m_cmpB = o.m_cmpB; m_versB = Version(o.m_versB);
  210. enforce( m_cmpA != "==" || m_cmpB == "==");
  211. enforce(m_versA <= m_versB);
  212. m_path = o.m_path;
  213. m_configuration = o.m_configuration;
  214. }
  215.  
  216. @property void path(Path value) { m_path = value; }
  217. @property Path path() const { return m_path; }
  218.  
  219. @property Version version_() const { assert(m_versA == m_versB); return m_versA; }
  220. override string toString() const {
  221. string r;
  222. // Special "==" case
  223. if( m_versA == m_versB && m_cmpA == ">=" && m_cmpB == "<=" ){
  224. if( m_versA == Version.MASTER ) r = "~master";
  225. else r = "==" ~ to!string(m_versA);
  226. } else {
  227. if( m_versA != Version.RELEASE ) r = m_cmpA ~ to!string(m_versA);
  228. if( m_versB != Version.HEAD ) r ~= (r.length==0?"" : " ") ~ m_cmpB ~ to!string(m_versB);
  229. if( m_versA == Version.RELEASE && m_versB == Version.HEAD ) r = ">=0.0.0";
  230. }
  231. return r;
  232. }
  233.  
  234. override bool opEquals(Object b)
  235. {
  236. if (this is b) return true; if (b is null) return false; if (typeid(this) != typeid(b)) return false;
  237. Dependency o = cast(Dependency) b;
  238. return o.m_cmpA == m_cmpA && o.m_cmpB == m_cmpB && o.m_versA == m_versA && o.m_versB == m_versB && o.m_configuration == m_configuration;
  239. }
  240. bool valid() const {
  241. return m_versA == m_versB // compare not important
  242. || (m_versA < m_versB && doCmp(m_cmpA, m_versB, m_versA) && doCmp(m_cmpB, m_versA, m_versB));
  243. }
  244. bool matches(string vers) const { return matches(Version(vers)); }
  245. bool matches(const(Version) v) const { return matches(v); }
  246. bool matches(ref const(Version) v) const {
  247. //logTrace(" try match: %s with: %s", v, this);
  248. // Master only matches master
  249. if(m_versA == Version.MASTER || m_versA.isBranch) {
  250. enforce(m_versA == m_versB);
  251. return m_versA == v;
  252. }
  253. if(v.isBranch)
  254. return m_versA == v;
  255. if(m_versA == Version.MASTER || v == Version.MASTER)
  256. return m_versA == v;
  257. if( !doCmp(m_cmpA, v, m_versA) )
  258. return false;
  259. if( !doCmp(m_cmpB, v, m_versB) )
  260. return false;
  261. return true;
  262. }
  263. /// Merges to versions
  264. Dependency merge(ref const(Dependency) o) const {
  265. if(!valid())
  266. return new Dependency(this);
  267. if(!o.valid())
  268. return new Dependency(o);
  269. if( m_configuration != o.m_configuration )
  270. return new Dependency(">=1.0.0 <=0.0.0");
  271. Version a = m_versA > o.m_versA? m_versA : o.m_versA;
  272. Version b = m_versB < o.m_versB? m_versB : o.m_versB;
  273. //logTrace(" this : %s", this);
  274. //logTrace(" other: %s", o);
  275. Dependency d = new Dependency(this);
  276. d.m_cmpA = !doCmp(m_cmpA, a,a)? m_cmpA : o.m_cmpA;
  277. d.m_versA = a;
  278. d.m_cmpB = !doCmp(m_cmpB, b,b)? m_cmpB : o.m_cmpB;
  279. d.m_versB = b;
  280. //logTrace(" merged: %s", d);
  281. return d;
  282. }
  283. private static bool isDigit(char ch) { return ch >= '0' && ch <= '9'; }
  284. private static string skipComp(ref string c) {
  285. size_t idx = 0;
  286. while( idx < c.length && !isDigit(c[idx]) ) idx++;
  287. enforce(idx < c.length, "Expected version number in version spec: "~c);
  288. string cmp = idx==c.length-1||idx==0? ">=" : c[0..idx];
  289. c = c[idx..$];
  290. switch(cmp) {
  291. default: enforce(false, "No/Unknown comparision specified: '"~cmp~"'"); return ">=";
  292. case ">=": goto case; case ">": goto case;
  293. case "<=": goto case; case "<": goto case;
  294. case "==": return cmp;
  295. }
  296. }
  297. private static bool doCmp(string mthd, ref const Version a, ref const Version b) {
  298. //logTrace("Calling %s%s%s", a, mthd, b);
  299. switch(mthd) {
  300. default: throw new Exception("Unknown comparison operator: "~mthd);
  301. case ">": return a>b;
  302. case ">=": return a>=b;
  303. case "==": return a==b;
  304. case "<=": return a<=b;
  305. case "<": return a<b;
  306. }
  307. }
  308. }
  309.  
  310. unittest {
  311. Dependency a = new Dependency(">=1.1.0"), b = new Dependency(">=1.3.0");
  312. assert( a.merge(b).valid() && to!string(a.merge(b)) == ">=1.3.0", to!string(a.merge(b)) );
  313. a = new Dependency("<=1.0.0 >=2.0.0");
  314. assert( !a.valid(), to!string(a) );
  315. a = new Dependency(">=1.0.0 <=5.0.0"), b = new Dependency(">=2.0.0");
  316. assert( a.merge(b).valid() && to!string(a.merge(b)) == ">=2.0.0 <=5.0.0", to!string(a.merge(b)) );
  317. assertThrown(a = new Dependency(">1.0.0 ==5.0.0"), "Construction is invalid");
  318. a = new Dependency(">1.0.0"), b = new 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. a = new Dependency(">2.0.0"), b = new Dependency("<1.0.0");
  322. assert( !(a.merge(b)).valid(), to!string(a.merge(b)));
  323. a = new Dependency(">=2.0.0"), b = new Dependency("<=1.0.0");
  324. assert( !(a.merge(b)).valid(), to!string(a.merge(b)));
  325. a = new Dependency("==2.0.0"), b = new Dependency("==1.0.0");
  326. assert( !(a.merge(b)).valid(), to!string(a.merge(b)));
  327. a = new Dependency("<=2.0.0"), b = new Dependency("==1.0.0");
  328. Dependency m = a.merge(b);
  329. assert( m.valid(), to!string(m));
  330. assert( m.matches( Version("1.0.0") ) );
  331. assert( !m.matches( Version("1.1.0") ) );
  332. assert( !m.matches( Version("0.0.1") ) );
  333.  
  334.  
  335. // branches / head revisions
  336. a = new Dependency(Version.MASTER_STRING);
  337. assert(a.valid());
  338. assert(a.matches(Version.MASTER));
  339. b = new Dependency(Version.MASTER_STRING);
  340. m = a.merge(b);
  341. assert(m.matches(Version.MASTER));
  342.  
  343. //assertThrown(a = new Dependency(Version.MASTER_STRING ~ " <=1.0.0"), "Construction invalid");
  344. assertThrown(a = new Dependency(">=1.0.0 " ~ Version.MASTER_STRING), "Construction invalid");
  345.  
  346. a = new Dependency(">=1.0.0");
  347. b = new Dependency(Version.MASTER_STRING);
  348.  
  349. //// support crazy stuff like this?
  350. //m = a.merge(b);
  351. //assert(m.valid());
  352. //assert(m.matches(Version.MASTER));
  353.  
  354. //b = new Dependency("~not_the_master");
  355. //m = a.merge(b);
  356. // assert(!m.valid());
  357.  
  358. immutable string branch1 = Version.BRANCH_IDENT ~ "Branch1";
  359. immutable string branch2 = Version.BRANCH_IDENT ~ "Branch2";
  360.  
  361. //assertThrown(a = new Dependency(branch1 ~ " " ~ branch2), "Error: '" ~ branch1 ~ " " ~ branch2 ~ "' succeeded");
  362. //assertThrown(a = new Dependency(Version.MASTER_STRING ~ " " ~ branch1), "Error: '" ~ Version.MASTER_STRING ~ " " ~ branch1 ~ "' succeeded");
  363.  
  364. a = new Dependency(branch1);
  365. b = new Dependency(branch2);
  366. assertThrown(a.merge(b), "Shouldn't be able to merge to different branches");
  367. assertNotThrown(b = a.merge(a), "Should be able to merge the same branches. (?)");
  368. assert(a == b);
  369.  
  370. a = new Dependency(branch1);
  371. assert(a.matches(branch1), "Dependency(branch1) does not match 'branch1'");
  372. assert(a.matches(Version(branch1)), "Dependency(branch1) does not match Version('branch1')");
  373. assert(!a.matches(Version.MASTER), "Dependency(branch1) matches Version.MASTER");
  374. assert(!a.matches(branch2), "Dependency(branch1) matches 'branch2'");
  375. assert(!a.matches(Version("1.0.0")), "Dependency(branch1) matches '1.0.0'");
  376. a = new Dependency(">=1.0.0");
  377. assert(!a.matches(Version(branch1)), "Dependency(1.0.0) matches 'branch1'");
  378.  
  379. logTrace("Dependency Unittest sucess.");
  380. }
  381.  
  382. struct RequestedDependency {
  383. this( string pkg, const Dependency de) {
  384. dependency = new Dependency(de);
  385. packages[pkg] = new Dependency(de);
  386. }
  387. Dependency dependency;
  388. Dependency[string] packages;
  389. }
  390.  
  391. class DependencyGraph {
  392. this(const Package root) {
  393. m_root = root;
  394. m_packages[m_root.name] = root;
  395. }
  396. void insert(const Package p) {
  397. enforce(p.name != m_root.name);
  398. m_packages[p.name] = p;
  399. }
  400. void remove(const Package p) {
  401. enforce(p.name != m_root.name);
  402. Rebindable!(const Package)* pkg = p.name in m_packages;
  403. if( pkg ) m_packages.remove(p.name);
  404. }
  405. private
  406. {
  407. alias Rebindable!(const Package) PkgType;
  408. }
  409. void clearUnused() {
  410. Rebindable!(const Package)[string] unused = m_packages.dup;
  411. unused.remove(m_root.name);
  412. forAllDependencies( (const PkgType* avail, string s, const Dependency d, const Package issuer) {
  413. if(avail && d.matches(avail.vers))
  414. unused.remove(avail.name);
  415. });
  416. foreach(string unusedPkg, d; unused) {
  417. logTrace("Removed unused package: "~unusedPkg);
  418. m_packages.remove(unusedPkg);
  419. }
  420. }
  421. RequestedDependency[string] conflicted() const {
  422. RequestedDependency[string] deps = needed();
  423. RequestedDependency[string] conflicts;
  424. foreach(string pkg, d; deps)
  425. if(!d.dependency.valid())
  426. conflicts[pkg] = d;
  427. return conflicts;
  428. }
  429. RequestedDependency[string] missing() const {
  430. RequestedDependency[string] deps;
  431. forAllDependencies( (const PkgType* avail, string pkgId, const Dependency d, const Package issuer) {
  432. if(!avail || !d.matches(avail.vers))
  433. addDependency(deps, pkgId, d, issuer);
  434. });
  435. return deps;
  436. }
  437. RequestedDependency[string] needed() const {
  438. RequestedDependency[string] deps;
  439. forAllDependencies( (const PkgType* avail, string pkgId, const Dependency d, const Package issuer) {
  440. addDependency(deps, pkgId, d, issuer);
  441. });
  442. return deps;
  443. }
  444. private void forAllDependencies(void delegate (const PkgType* avail, string pkgId, const Dependency d, const Package issuer) dg) const {
  445. foreach(string issuerPackag, issuer; m_packages) {
  446. foreach(string depPkg, dependency; issuer.dependencies) {
  447. auto availPkg = depPkg in m_packages;
  448. dg(availPkg, depPkg, dependency, issuer);
  449. }
  450. }
  451. }
  452. private static void addDependency(ref RequestedDependency[string] deps, string packageId, const Dependency d, const Package issuer) {
  453. logTrace("addDependency "~packageId~", '%s'", d);
  454. auto d2 = packageId in deps;
  455. if(!d2) {
  456. deps[packageId] = RequestedDependency(issuer.name, d);
  457. }
  458. else {
  459. d2.dependency = d2.dependency.merge(d);
  460. d2.packages[issuer.name] = new Dependency(d);
  461. }
  462. }
  463. private {
  464. const Package m_root;
  465. PkgType[string] m_packages;
  466. }
  467. }
  468.  
  469. unittest {
  470. /*
  471. */
  472.  
  473. }