diff --git a/source/dub/dependency.d b/source/dub/dependency.d index 29d1d48..c880ced 100644 --- a/source/dub/dependency.d +++ b/source/dub/dependency.d @@ -25,13 +25,14 @@ static import std.compiler; /** - A version in the format "major.update.bugfix-prerelease+buildmetadata" or - "~master", to identify trunk, or "~branch_name" to identify a branch. Both - Version types starting with "~" refer to the head revision of the - corresponding branch. - - Except for the "~branch" Version format, this follows the Semantic Versioning - Specification (SemVer) 2.0.0-rc.2. + A version in the format "major.update.bugfix-prerelease+buildmetadata" + according to Semantic Versioning Specification v2.0.0. + + (deprecated): + This also supports a format like "~master", to identify trunk, or + "~branch_name" to identify a branch. Both Version types starting with "~" + refer to the head revision of the corresponding branch. + This is subject to be removed soon. */ struct Version { private { @@ -131,9 +132,11 @@ assert(versions[j] < versions[i], "Failed: " ~ to!string(versions[j]) ~ "<" ~ to!string(versions[i])); } -/// Representing a dependency, which is basically a version string and a -/// compare methode, e.g. '>=1.0.0 <2.0.0' (i.e. a space separates the two -/// version numbers) +/** + Representing a dependency, which is basically a version string and a + compare methode, e.g. '>=1.0.0 <2.0.0' (i.e. a space separates the two + version numbers) +*/ struct Dependency { private { string m_cmpA; @@ -156,8 +159,8 @@ m_cmpA = ">="; m_cmpB = "<"; ves = ves[2..$]; - m_versA = Version(ves); - m_versB = Version(incrementVersion(ves)); + m_versA = Version(expandVersion(ves)); + m_versB = Version(bumpVersion(ves)); } else if (ves[0] == Version.BRANCH_IDENT) { m_cmpA = ">="; m_cmpB = "<="; @@ -409,19 +412,21 @@ assert(a.matches(Version("1.0.0")), "Failed: match 1.0.0 with >=1.0.0-beta"); assert(a.matches(Version("1.0.0-rc")), "Failed: match 1.0.0-rc with >=1.0.0-beta"); - // Dependency shortcut. - a = Dependency("~>0.1.2"); - b = Dependency(">=0.1.2 <0.2.0"); + // Approximate versions. + a = Dependency("~>3.0"); + b = Dependency(">=3.0.0 <4.0.0"); assert(a == b, "Testing failed: " ~ a.to!string()); - assert(a.matches(Version("0.1.146")), "Failed: Match 0.1.146 with ~>0.1.2"); + assert(a.matches(Version("3.1.146")), "Failed: Match 3.1.146 with ~>0.1.2"); assert(!a.matches(Version("0.2.0")), "Failed: Match 0.2.0 with ~>0.1.2"); - - a = Dependency("~>1.0.2"); - b = Dependency(">=1.0.2 <1.1.0"); - assert(a == b, "Testing failed: " ~ a.to!string()); + a = Dependency("~>3.0.0"); + assert(a == Dependency(">=3.0.0 <3.1.0"), "Testing failed: " ~ a.to!string()); + a = Dependency("~>3.5"); + assert(a == Dependency(">=3.5.0 <4.0.0"), "Testing failed: " ~ a.to!string()); + a = Dependency("~>3.5.0"); + assert(a == Dependency(">=3.5.0 <3.6.0"), "Testing failed: " ~ a.to!string()); a = Dependency("~>1.0.1-beta"); - b = Dependency(">=1.0.1-beta <1.0.1-betb"); + b = Dependency(">=1.0.1-beta <1.1.0"); assert(a == b, "Testing failed: " ~ a.to!string()); assert(a.matches(Version("1.0.1-beta"))); assert(a.matches(Version("1.0.1-beta.6"))); @@ -439,6 +444,9 @@ logDebug("Dependency Unittest sucess."); } +/** + Stuff for a dependency lookup. +*/ struct RequestedDependency { this( string pkg, Dependency de) { dependency = de; diff --git a/source/dub/semver.d b/source/dub/semver.d index 107d23a..8da46ca 100644 --- a/source/dub/semver.d +++ b/source/dub/semver.d @@ -9,7 +9,7 @@ import std.range; import std.string; -import std.algorithm : join, split; +import std.algorithm : join, split, max; import std.conv; /* @@ -177,68 +177,70 @@ /** - Given a valid semver version string, increments it in the sense of + Given version string, increments the next to last version number. + Prerelease and build metadata information is ignored. + @param ver Does not need to be a valid semver version. + @return Valid semver version + + The semantics of this are the same as for the "approximate" version + specifier from rubygems. + (https://github.com/rubygems/rubygems/tree/81d806d818baeb5dcb6398ca631d772a003d078e/lib/rubygems/version.rb) + + Examples: + 1.5 -> 2.0 1.5.67 -> 1.6.0 - 1.5.67-a -> 1.5.67-b - - This helps with the "~>1.5.6" version specifier. - - The version strings must be validated using isValidVersion() before being - passed to this function. + 1.5.67-a -> 1.6.0 */ -string incrementVersion(string ver) { - // Cut off metadata, does not matter. - auto mi = ver.indexOf("+"); +string bumpVersion(string ver) { + // Cut off metadata and prerelease information. + auto mi = ver.indexOfAny("+-"); if (mi > 0) ver = ver[0..mi]; - - // Check and increment pre-release numbers - mi = ver.indexOf("-"); - if (mi > 0) return ver[0..mi+1] ~ incrementDotted(ver[(mi+1)..$]); - - // Increment simple a.b.c - return incrementDotted(ver); -} - -private string incrementDotted(string ver) { - auto items = split(ver, "."); - // Find item to increment: last item before last non-zero item. - sizediff_t idx = items.length-1; - while (idx >= 0 && isValidNumber(items[idx]) && to!int(items[idx]) == 0) - --idx; - - // idx is the last non-zero item, take the one before it or the first item. - idx = idx<=0 ? 0 : idx - 1; - auto to_increment = items[idx]; - if (isValidNumber(to_increment)) { - to_increment = to!string(to!int(to_increment) + 1); - } else { - // "000Z" -> "000a" - if (to_increment[$-1] == 'Z') to_increment = to_increment[0..$-1] ~ 'a'; - // "000z" -> "000z0" - else if(to_increment[$-1] == 'z') to_increment = to_increment ~ '0'; - // "000y" -> "000z" - else to_increment = to_increment[0..$-1] ~ cast(char)( cast(int)(to_increment[$-1]) + 1); - } - items[idx] = to_increment; - string incremented = join(items[0 .. idx+1], "."); - // Fill up with zeros. - foreach (i; idx + 1 .. items.length) - incremented ~= ".0"; - return incremented; + // Increment next to last version from a[.b[.c]]. + auto splitted = split(ver, "."); + assert(splitted.length > 0 && splitted.length <= 3, "Version corrupt: " ~ ver); + auto to_inc = splitted.length == 3? 1 : 0; + splitted = splitted[0 .. to_inc+1]; + splitted[to_inc] = to!string(to!int(splitted[to_inc]) + 1); + // Fill up to three compontents to make valid SemVer version. + while (splitted.length < 3) splitted ~= "0"; + return splitted.join("."); } unittest { - assert("1.0.0" == incrementVersion("0.0.0")); - assert("2.0.0" == incrementVersion("1.0.0")); - assert("2.0.0" == incrementVersion("1.1.0")); - assert("1.1.0" == incrementVersion("1.0.1")); - assert("1.0.1-b" == incrementVersion("1.0.1-a")); - assert("1.0.1-z0" == incrementVersion("1.0.1-z")); - assert("1.0.1-a" == incrementVersion("1.0.1-Z")); - assert("1.0.1-bbbbbba" == incrementVersion("1.0.1-bbbbbbZ")); - assert("1.0.1-bbbbbbZ.48.0" == incrementVersion("1.0.1-bbbbbbZ.47.11")); - assert("1.1.0" == incrementVersion("1.0.1+metadata")); - assert("1.0.1-a.c.0" == incrementVersion("1.0.1-a.b.c+metadata")); + assert("1.0.0" == bumpVersion("0")); + assert("1.0.0" == bumpVersion("0.0")); + assert("0.1.0" == bumpVersion("0.0.0")); + assert("1.3.0" == bumpVersion("1.2.3")); + assert("1.3.0" == bumpVersion("1.2.3+metadata")); + assert("1.3.0" == bumpVersion("1.2.3-pre.release")); + assert("1.3.0" == bumpVersion("1.2.3-pre.release+metadata")); +} + +/** + Takes a abbreviated version and expands it to a valid SemVer version. + E.g. "1.0" -> "1.0.0" +*/ +string expandVersion(string ver) { + auto mi = ver.indexOfAny("+-"); + auto sub = ""; + if (mi > 0) { + sub = ver[mi..$]; + ver = ver[0..mi]; + } + auto splitted = split(ver, "."); + assert(splitted.length > 0 && splitted.length <= 3, "Version corrupt: " ~ ver); + while (splitted.length < 3) splitted ~= "0"; + return splitted.join(".") ~ sub; +} + +unittest { + assert("1.0.0" == expandVersion("1")); + assert("1.0.0" == expandVersion("1.0")); + assert("1.0.0" == expandVersion("1.0.0")); + // These are rather excotic variants... + assert("1.0.0-pre.release" == expandVersion("1-pre.release")); + assert("1.0.0+meta" == expandVersion("1+meta")); + assert("1.0.0-pre.release+meta" == expandVersion("1-pre.release+meta")); } private int compareIdentifier(ref string a, ref string b)