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. import dub.semver;
  17.  
  18. import std.algorithm;
  19. import std.array;
  20. import std.exception;
  21. import std.regex;
  22. import std.string;
  23. import std.typecons;
  24. static import std.compiler;
  25.  
  26.  
  27. /**
  28. Representing a dependency, which is basically a version string and a
  29. compare methode, e.g. '>=1.0.0 <2.0.0' (i.e. a space separates the two
  30. version numbers)
  31. */
  32. struct Dependency {
  33. private {
  34. // Shortcut to create >=0.0.0
  35. enum ANY_IDENT = "*";
  36. string m_cmpA;
  37. Version m_versA;
  38. string m_cmpB;
  39. Version m_versB;
  40. Path m_path;
  41. bool m_optional = false;
  42. }
  43.  
  44. // A Dependency, which matches every valid version.
  45. static @property ANY() { return Dependency(ANY_IDENT); }
  46. static @property INVALID() { Dependency ret; ret.m_versA = Version.HEAD; ret.m_versB = Version.RELEASE; return ret; }
  47.  
  48. this(string ves)
  49. {
  50. enforce(ves.length > 0);
  51. string orig = ves;
  52.  
  53. if (ves == ANY_IDENT) {
  54. // Any version is good.
  55. ves = ">=0.0.0";
  56. }
  57.  
  58. if (ves.startsWith("~>")) {
  59. // Shortcut: "~>x.y.z" variant. Last non-zero number will indicate
  60. // the base for this so something like this: ">=x.y.z <x.(y+1).z"
  61. m_cmpA = ">=";
  62. m_cmpB = "<";
  63. ves = ves[2..$];
  64. m_versA = Version(expandVersion(ves));
  65. m_versB = Version(bumpVersion(ves));
  66. } else if (ves[0] == Version.BRANCH_IDENT) {
  67. m_cmpA = ">=";
  68. m_cmpB = "<=";
  69. m_versA = m_versB = Version(ves);
  70. } else if (std.string.indexOf("><=", ves[0]) == -1) {
  71. m_cmpA = ">=";
  72. m_cmpB = "<=";
  73. m_versA = m_versB = Version(ves);
  74. } else {
  75. m_cmpA = skipComp(ves);
  76. size_t idx2 = std.string.indexOf(ves, " ");
  77. if (idx2 == -1) {
  78. if (m_cmpA == "<=" || m_cmpA == "<") {
  79. m_versA = Version.RELEASE;
  80. m_cmpB = m_cmpA;
  81. m_cmpA = ">=";
  82. m_versB = Version(ves);
  83. } else if (m_cmpA == ">=" || m_cmpA == ">") {
  84. m_versA = Version(ves);
  85. m_versB = Version.HEAD;
  86. m_cmpB = "<=";
  87. } else {
  88. // Converts "==" to ">=a&&<=a", which makes merging easier
  89. m_versA = m_versB = Version(ves);
  90. m_cmpA = ">=";
  91. m_cmpB = "<=";
  92. }
  93. } else {
  94. assert(ves[idx2] == ' ');
  95. m_versA = Version(ves[0..idx2]);
  96. string v2 = ves[idx2+1..$];
  97. m_cmpB = skipComp(v2);
  98. m_versB = Version(v2);
  99.  
  100. enforce(!m_versA.isBranch, "Partly a branch (A): %s", ves);
  101. enforce(!m_versB.isBranch, "Partly a branch (B): %s", ves);
  102.  
  103. if (m_versB < m_versA) {
  104. swap(m_versA, m_versB);
  105. swap(m_cmpA, m_cmpB);
  106. }
  107. enforce( m_cmpA != "==" && m_cmpB != "==", "For equality, please specify a single version.");
  108. }
  109. }
  110. }
  111.  
  112. this(in Version ver)
  113. {
  114. m_cmpA = ">=";
  115. m_cmpB = "<=";
  116. m_versA = ver;
  117. m_versB = ver;
  118. }
  119.  
  120. this(Path path)
  121. {
  122. this(ANY_IDENT);
  123. m_path = path;
  124. }
  125.  
  126. @property void path(Path value) { m_path = value; }
  127. @property Path path() const { return m_path; }
  128. @property bool optional() const { return m_optional; }
  129. @property void optional(bool optional) { m_optional = optional; }
  130. @property bool isExactVersion() const { return m_versA == m_versB; }
  131.  
  132. @property Version version_() const {
  133. enforce(m_versA == m_versB, "Dependency "~versionString()~" is no exact version.");
  134. return m_versA;
  135. }
  136.  
  137. @property string versionString()
  138. const {
  139. string r;
  140. if( m_versA == m_versB && m_cmpA == ">=" && m_cmpB == "<=" ){
  141. // Special "==" case
  142. if (m_versA == Version.MASTER ) r = "~master";
  143. else r = m_versA.toString();
  144. } else {
  145. if( m_versA != Version.RELEASE ) r = m_cmpA ~ m_versA.toString();
  146. if( m_versB != Version.HEAD ) r ~= (r.length==0?"" : " ") ~ m_cmpB ~ m_versB.toString();
  147. if( m_versA == Version.RELEASE && m_versB == Version.HEAD ) r = ">=0.0.0";
  148. }
  149. return r;
  150. }
  151.  
  152. Dependency mapToPath(Path path)
  153. const {
  154. if (m_path.empty || m_path.absolute) return this;
  155. else {
  156. Dependency ret = this;
  157. ret.path = path ~ ret.path;
  158. return ret;
  159. }
  160. }
  161.  
  162. string toString()()
  163. const {
  164. auto ret = versionString;
  165. if (optional) ret ~= " (optional)";
  166. if (!path.empty) ret ~= " @"~path.toNativeString();
  167. return ret;
  168. }
  169.  
  170. Json toJson() const {
  171. Json json;
  172. if( path.empty && !optional ){
  173. json = Json(versionString());
  174. } else {
  175. json = Json.emptyObject;
  176. json["version"] = versionString();
  177. if (!path.empty) json["path"] = path.toString();
  178. if (optional) json["optional"] = true;
  179. }
  180. return json;
  181. }
  182.  
  183. unittest {
  184. Dependency d = Dependency("==1.0.0");
  185. assert(d.toJson() == Json("1.0.0"), "Failed: " ~ d.toJson().toPrettyString());
  186. d = fromJson((fromJson(d.toJson())).toJson());
  187. assert(d == Dependency("1.0.0"));
  188. assert(d.toJson() == Json("1.0.0"), "Failed: " ~ d.toJson().toPrettyString());
  189. }
  190.  
  191. static Dependency fromJson(Json verspec) {
  192. Dependency dep;
  193. if( verspec.type == Json.Type.object ){
  194. if( auto pp = "path" in verspec ) {
  195. if (auto pv = "version" in verspec)
  196. logDiagnostic("Ignoring version specification (%s) for path based dependency %s", pv.get!string, pp.get!string);
  197.  
  198. dep = Dependency.ANY;
  199. dep.path = Path(verspec.path.get!string());
  200. } else {
  201. enforce("version" in verspec, "No version field specified!");
  202. auto ver = verspec["version"].get!string;
  203. // Using the string to be able to specifiy a range of versions.
  204. dep = Dependency(ver);
  205. }
  206. if( auto po = "optional" in verspec ) {
  207. dep.optional = verspec.optional.get!bool();
  208. }
  209. } else {
  210. // canonical "package-id": "version"
  211. dep = Dependency(verspec.get!string());
  212. }
  213. return dep;
  214. }
  215.  
  216. unittest {
  217. assert(fromJson(parseJsonString("\">=1.0.0 <2.0.0\"")) == Dependency(">=1.0.0 <2.0.0"));
  218. Dependency parsed = fromJson(parseJsonString(`
  219. {
  220. "version": "2.0.0",
  221. "optional": true,
  222. "path": "path/to/package"
  223. }
  224. `));
  225. Dependency d = Dependency(Version("2.0.0"));
  226. d.optional = true;
  227. d.path = Path("path/to/package");
  228. assert(d == parsed);
  229. // optional and path not checked by opEquals.
  230. assert(d.optional == parsed.optional);
  231. assert(d.path == parsed.path);
  232. }
  233.  
  234. bool opEquals(in Dependency o)
  235. const {
  236. // TODO(mdondorff): Check if not comparing the path is correct for all clients.
  237. return o.m_cmpA == m_cmpA && o.m_cmpB == m_cmpB
  238. && o.m_versA == m_versA && o.m_versB == m_versB
  239. && o.m_optional == m_optional;
  240. }
  241.  
  242. int opCmp(in Dependency o)
  243. const {
  244. if (m_cmpA != o.m_cmpA) return m_cmpA < o.m_cmpA ? -1 : 1;
  245. if (m_cmpB != o.m_cmpB) return m_cmpB < o.m_cmpB ? -1 : 1;
  246. if (m_versA != o.m_versA) return m_versA < o.m_versA ? -1 : 1;
  247. if (m_versB != o.m_versB) return m_versB < o.m_versB ? -1 : 1;
  248. if (m_optional != o.m_optional) return m_optional ? -1 : 1;
  249. return 0;
  250. }
  251.  
  252. hash_t toHash() const nothrow @trusted {
  253. try {
  254. auto strhash = &typeid(string).getHash;
  255. auto str = this.toString();
  256. return strhash(&str);
  257. } catch assert(false);
  258. }
  259. bool valid() const {
  260. return m_versA == m_versB // compare not important
  261. || (m_versA < m_versB && doCmp(m_cmpA, m_versB, m_versA) && doCmp(m_cmpB, m_versA, m_versB));
  262. }
  263. bool matches(string vers) const { return matches(Version(vers)); }
  264. bool matches(const(Version) v) const { return matches(v); }
  265. bool matches(ref const(Version) v) const {
  266. if (this == ANY) return true;
  267. //logDebug(" try match: %s with: %s", v, this);
  268. // Master only matches master
  269. if(m_versA.isBranch) {
  270. enforce(m_versA == m_versB);
  271. return m_versA == v;
  272. }
  273. if(v.isBranch || m_versA.isBranch)
  274. return m_versA == v;
  275. if( !doCmp(m_cmpA, v, m_versA) )
  276. return false;
  277. if( !doCmp(m_cmpB, v, m_versB) )
  278. return false;
  279. return true;
  280. }
  281. /// Merges to versions
  282. Dependency merge(ref const(Dependency) o)
  283. const {
  284. if (this == ANY) return o;
  285. if (o == ANY) return this;
  286. if (!this.valid || !o.valid) return INVALID;
  287. if (m_versA.isBranch != o.m_versA.isBranch) return INVALID;
  288. if (m_versB.isBranch != o.m_versB.isBranch) return INVALID;
  289. if (m_versA.isBranch) return m_versA == o.m_versA ? this : INVALID;
  290. if (this.path != o.path) return INVALID;
  291.  
  292. Version a = m_versA > o.m_versA ? m_versA : o.m_versA;
  293. Version b = m_versB < o.m_versB ? m_versB : o.m_versB;
  294. Dependency d = this;
  295. d.m_cmpA = !doCmp(m_cmpA, a,a)? m_cmpA : o.m_cmpA;
  296. d.m_versA = a;
  297. d.m_cmpB = !doCmp(m_cmpB, b,b)? m_cmpB : o.m_cmpB;
  298. d.m_versB = b;
  299. d.m_optional = m_optional && o.m_optional;
  300. return d;
  301. }
  302. private static bool isDigit(char ch) { return ch >= '0' && ch <= '9'; }
  303. private static string skipComp(ref string c) {
  304. size_t idx = 0;
  305. while (idx < c.length && !isDigit(c[idx]) && c[idx] != Version.BRANCH_IDENT) idx++;
  306. enforce(idx < c.length, "Expected version number in version spec: "~c);
  307. string cmp = idx==c.length-1||idx==0? ">=" : c[0..idx];
  308. c = c[idx..$];
  309. switch(cmp) {
  310. default: enforce(false, "No/Unknown comparision specified: '"~cmp~"'"); return ">=";
  311. case ">=": goto case; case ">": goto case;
  312. case "<=": goto case; case "<": goto case;
  313. case "==": return cmp;
  314. }
  315. }
  316. private static bool doCmp(string mthd, ref const Version a, ref const Version b) {
  317. //logDebug("Calling %s%s%s", a, mthd, b);
  318. switch(mthd) {
  319. default: throw new Exception("Unknown comparison operator: "~mthd);
  320. case ">": return a>b;
  321. case ">=": return a>=b;
  322. case "==": return a==b;
  323. case "<=": return a<=b;
  324. case "<": return a<b;
  325. }
  326. }
  327. }
  328.  
  329. unittest {
  330. Dependency a = Dependency(">=1.1.0"), b = Dependency(">=1.3.0");
  331. assert (a.merge(b).valid() && a.merge(b).versionString == ">=1.3.0", a.merge(b).toString());
  332. a = Dependency("<=1.0.0 >=2.0.0");
  333. assert (!a.valid(), a.toString());
  334. a = Dependency(">=1.0.0 <=5.0.0"), b = Dependency(">=2.0.0");
  335. assert (a.merge(b).valid() && a.merge(b).versionString == ">=2.0.0 <=5.0.0", a.merge(b).toString());
  336. assertThrown(a = Dependency(">1.0.0 ==5.0.0"), "Construction is invalid");
  337. a = Dependency(">1.0.0"), b = Dependency("<2.0.0");
  338. assert (a.merge(b).valid(), a.merge(b).toString());
  339. assert (a.merge(b).versionString == ">1.0.0 <2.0.0", a.merge(b).toString());
  340. a = Dependency(">2.0.0"), b = Dependency("<1.0.0");
  341. assert (!(a.merge(b)).valid(), a.merge(b).toString());
  342. a = Dependency(">=2.0.0"), b = Dependency("<=1.0.0");
  343. assert (!(a.merge(b)).valid(), a.merge(b).toString());
  344. a = Dependency("==2.0.0"), b = Dependency("==1.0.0");
  345. assert (!(a.merge(b)).valid(), a.merge(b).toString());
  346.  
  347. a = Dependency("1.0.0"), b = Dependency("==1.0.0");
  348. assert (a == b);
  349. a = Dependency("<=2.0.0"), b = Dependency("==1.0.0");
  350. Dependency m = a.merge(b);
  351. assert (m.valid(), m.toString());
  352. assert (m.matches(Version("1.0.0")));
  353. assert (!m.matches(Version("1.1.0")));
  354. assert (!m.matches(Version("0.0.1")));
  355.  
  356.  
  357. // branches / head revisions
  358. a = Dependency(Version.MASTER_STRING);
  359. assert(a.valid());
  360. assert(a.matches(Version.MASTER));
  361. b = Dependency(Version.MASTER_STRING);
  362. m = a.merge(b);
  363. assert(m.matches(Version.MASTER));
  364.  
  365. //assertThrown(a = Dependency(Version.MASTER_STRING ~ " <=1.0.0"), "Construction invalid");
  366. assertThrown(a = Dependency(">=1.0.0 " ~ Version.MASTER_STRING), "Construction invalid");
  367.  
  368. immutable string branch1 = Version.BRANCH_IDENT ~ "Branch1";
  369. immutable string branch2 = Version.BRANCH_IDENT ~ "Branch2";
  370.  
  371. //assertThrown(a = Dependency(branch1 ~ " " ~ branch2), "Error: '" ~ branch1 ~ " " ~ branch2 ~ "' succeeded");
  372. //assertThrown(a = Dependency(Version.MASTER_STRING ~ " " ~ branch1), "Error: '" ~ Version.MASTER_STRING ~ " " ~ branch1 ~ "' succeeded");
  373.  
  374. a = Dependency(branch1);
  375. b = Dependency(branch2);
  376. assert(!a.merge(b).valid, "Shouldn't be able to merge to different branches");
  377. b = a.merge(a);
  378. assert(b.valid, "Should be able to merge the same branches. (?)");
  379. assert(a == b);
  380.  
  381. a = Dependency(branch1);
  382. assert(a.matches(branch1), "Dependency(branch1) does not match 'branch1'");
  383. assert(a.matches(Version(branch1)), "Dependency(branch1) does not match Version('branch1')");
  384. assert(!a.matches(Version.MASTER), "Dependency(branch1) matches Version.MASTER");
  385. assert(!a.matches(branch2), "Dependency(branch1) matches 'branch2'");
  386. assert(!a.matches(Version("1.0.0")), "Dependency(branch1) matches '1.0.0'");
  387. a = Dependency(">=1.0.0");
  388. assert(!a.matches(Version(branch1)), "Dependency(1.0.0) matches 'branch1'");
  389.  
  390. // Testing optional dependencies.
  391. a = Dependency(">=1.0.0");
  392. assert(!a.optional, "Default is not optional.");
  393. b = a;
  394. assert(!a.merge(b).optional, "Merging two not optional dependencies wrong.");
  395. a.optional = true;
  396. assert(!a.merge(b).optional, "Merging optional with not optional wrong.");
  397. b.optional = true;
  398. assert(a.merge(b).optional, "Merging two optional dependencies wrong.");
  399.  
  400. // SemVer's sub identifiers.
  401. a = Dependency(">=1.0.0-beta");
  402. assert(!a.matches(Version("1.0.0-alpha")), "Failed: match 1.0.0-alpha with >=1.0.0-beta");
  403. assert(a.matches(Version("1.0.0-beta")), "Failed: match 1.0.0-beta with >=1.0.0-beta");
  404. assert(a.matches(Version("1.0.0")), "Failed: match 1.0.0 with >=1.0.0-beta");
  405. assert(a.matches(Version("1.0.0-rc")), "Failed: match 1.0.0-rc with >=1.0.0-beta");
  406.  
  407. // Approximate versions.
  408. a = Dependency("~>3.0");
  409. b = Dependency(">=3.0.0 <4.0.0");
  410. assert(a == b, "Testing failed: " ~ a.toString());
  411. assert(a.matches(Version("3.1.146")), "Failed: Match 3.1.146 with ~>0.1.2");
  412. assert(!a.matches(Version("0.2.0")), "Failed: Match 0.2.0 with ~>0.1.2");
  413. a = Dependency("~>3.0.0");
  414. assert(a == Dependency(">=3.0.0 <3.1.0"), "Testing failed: " ~ a.toString());
  415. a = Dependency("~>3.5");
  416. assert(a == Dependency(">=3.5.0 <4.0.0"), "Testing failed: " ~ a.toString());
  417. a = Dependency("~>3.5.0");
  418. assert(a == Dependency(">=3.5.0 <3.6.0"), "Testing failed: " ~ a.toString());
  419.  
  420. a = Dependency("~>1.0.1-beta");
  421. b = Dependency(">=1.0.1-beta <1.1.0");
  422. assert(a == b, "Testing failed: " ~ a.toString());
  423. assert(a.matches(Version("1.0.1-beta")));
  424. assert(a.matches(Version("1.0.1-beta.6")));
  425.  
  426. a = Dependency("~d2test");
  427. assert(!a.optional);
  428. assert(a.valid);
  429. assert(a.version_ == Version("~d2test"));
  430.  
  431. a = Dependency("==~d2test");
  432. assert(!a.optional);
  433. assert(a.valid);
  434. assert(a.version_ == Version("~d2test"));
  435.  
  436. a = Dependency.ANY;
  437. assert(!a.optional);
  438. assert(a.valid);
  439. assertThrown(a.version_);
  440. b = Dependency(">=1.0.1");
  441. assert(b == a.merge(b));
  442. assert(b == b.merge(a));
  443.  
  444. logDebug("Dependency Unittest sucess.");
  445. }
  446.  
  447.  
  448. /**
  449. A version in the format "major.update.bugfix-prerelease+buildmetadata"
  450. according to Semantic Versioning Specification v2.0.0.
  451.  
  452. (deprecated):
  453. This also supports a format like "~master", to identify trunk, or
  454. "~branch_name" to identify a branch. Both Version types starting with "~"
  455. refer to the head revision of the corresponding branch.
  456. This is subject to be removed soon.
  457. */
  458. struct Version {
  459. private {
  460. enum MAX_VERS = "99999.0.0";
  461. enum UNKNOWN_VERS = "unknown";
  462. string m_version;
  463. }
  464.  
  465. static @property RELEASE() { return Version("0.0.0"); }
  466. static @property HEAD() { return Version(MAX_VERS); }
  467. static @property MASTER() { return Version(MASTER_STRING); }
  468. static @property UNKNOWN() { return Version(UNKNOWN_VERS); }
  469. static @property MASTER_STRING() { return "~master"; }
  470. static @property BRANCH_IDENT() { return '~'; }
  471. this(string vers)
  472. {
  473. enforce(vers.length > 1, "Version strings must not be empty.");
  474. if (vers[0] != BRANCH_IDENT && vers != UNKNOWN_VERS)
  475. enforce(vers.isValidVersion(), "Invalid SemVer format: " ~ vers);
  476. m_version = vers;
  477. }
  478.  
  479. bool opEquals(const Version oth) const {
  480. if (isUnknown || oth.isUnknown) {
  481. throw new Exception("Can't compare unknown versions! (this: %s, other: %s)".format(this, oth));
  482. }
  483. return m_version == oth.m_version;
  484. }
  485. /// Returns true, if this version indicates a branch, which is not the trunk.
  486. @property bool isBranch() const { return !m_version.empty && m_version[0] == BRANCH_IDENT; }
  487. @property bool isMaster() const { return m_version == MASTER_STRING; }
  488. @property bool isPreRelease() const {
  489. if (isBranch) return true;
  490. return isPreReleaseVersion(m_version);
  491. }
  492. @property bool isUnknown() const { return m_version == UNKNOWN_VERS; }
  493.  
  494. /**
  495. Comparing Versions is generally possible, but comparing Versions
  496. identifying branches other than master will fail. Only equality
  497. can be tested for these.
  498. */
  499. int opCmp(ref const Version other)
  500. const {
  501. if (isUnknown || other.isUnknown) {
  502. throw new Exception("Can't compare unknown versions! (this: %s, other: %s)".format(this, other));
  503. }
  504. if (isBranch || other.isBranch) {
  505. if(m_version == other.m_version) return 0;
  506. if (!isBranch) return 1;
  507. else if (!other.isBranch) return -1;
  508. if (isMaster) return 1;
  509. else if (other.isMaster) return -1;
  510. return this.m_version < other.m_version ? -1 : 1;
  511. }
  512.  
  513. return compareVersions(isMaster ? MAX_VERS : m_version, other.isMaster ? MAX_VERS : other.m_version);
  514. }
  515. int opCmp(in Version other) const { return opCmp(other); }
  516. string toString() const { return m_version; }
  517. }
  518.  
  519. unittest {
  520. Version a, b;
  521.  
  522. assertNotThrown(a = Version("1.0.0"), "Constructing Version('1.0.0') failed");
  523. assert(!a.isBranch, "Error: '1.0.0' treated as branch");
  524. assert(a == a, "a == a failed");
  525.  
  526. assertNotThrown(a = Version(Version.MASTER_STRING), "Constructing Version("~Version.MASTER_STRING~"') failed");
  527. assert(a.isBranch, "Error: '"~Version.MASTER_STRING~"' treated as branch");
  528. assert(a.isMaster);
  529. assert(a == Version.MASTER, "Constructed master version != default master version.");
  530.  
  531. assertNotThrown(a = Version("~BRANCH"), "Construction of branch Version failed.");
  532. assert(a.isBranch, "Error: '~BRANCH' not treated as branch'");
  533. assert(!a.isMaster);
  534. assert(a == a, "a == a with branch failed");
  535.  
  536. // opCmp
  537. a = Version("1.0.0");
  538. b = Version("1.0.0");
  539. assert(a == b, "a == b with a:'1.0.0', b:'1.0.0' failed");
  540. b = Version("2.0.0");
  541. assert(a != b, "a != b with a:'1.0.0', b:'2.0.0' failed");
  542. a = Version(Version.MASTER_STRING);
  543. b = Version("~BRANCH");
  544. assert(a != b, "a != b with a:MASTER, b:'~branch' failed");
  545. assert(a > b);
  546. assert(a < Version("0.0.0"));
  547. assert(b < Version("0.0.0"));
  548. assert(a > Version("~Z"));
  549. assert(b < Version("~Z"));
  550. // SemVer 2.0.0-rc.2
  551. a = Version("2.0.0-rc.2");
  552. b = Version("2.0.0-rc.3");
  553. assert(a < b, "Failed: 2.0.0-rc.2 < 2.0.0-rc.3");
  554. a = Version("2.0.0-rc.2+build-metadata");
  555. b = Version("2.0.0+build-metadata");
  556. assert(a < b, "Failed: "~a.toString()~"<"~b.toString());
  557. // 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
  558. Version[] versions;
  559. versions ~= Version("1.0.0-alpha");
  560. versions ~= Version("1.0.0-alpha.1");
  561. versions ~= Version("1.0.0-beta.2");
  562. versions ~= Version("1.0.0-beta.11");
  563. versions ~= Version("1.0.0-rc.1");
  564. versions ~= Version("1.0.0");
  565. for(int i=1; i<versions.length; ++i)
  566. for(int j=i-1; j>=0; --j)
  567. assert(versions[j] < versions[i], "Failed: " ~ versions[j].toString() ~ "<" ~ versions[i].toString());
  568.  
  569. a = Version.UNKNOWN;
  570. b = Version.RELEASE;
  571. assertThrown(a == b, "Failed: compared " ~ a.toString() ~ " with " ~ b.toString() ~ "");
  572.  
  573. a = Version.UNKNOWN;
  574. b = Version.UNKNOWN;
  575. assertThrown(a == b, "Failed: UNKNOWN == UNKNOWN");
  576. }