sbt / sbt-dynver

An sbt plugin to dynamically set your version from git

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Default version pattern leads to conflicting versions

nigredo-tori opened this issue · comments

The default behavior of this plugin seems to be to generate versions like this one: 1.0.0+3-1234abcd+20140707-1030. However, it seems that some time ago Coursier has switched to the semver conventions. This means that, as far as Coursier and SBT are concerned, the above version is equivalent to 1.0.0, and SBT doesn't have to prioritize one above the other. Most annoyingly, these versions are treated as equal when comparing them, so the winner can change even inside one build!

That's unfortunate. Hopefully there's a setting in Coursier that allows ascii sorting to be used, so the version with the biggest commit distance is picked. But there's really nothing that can be done, as this is a limitation in SemVer: there's no way to define a post-version version number - it only allows for pre-release numbers.

That's really unfortunate, because it means that releases tagged with sbt-dynver can't be used by projects that use SBT 1.4. I guess I'll have to invent or find some versioning algorithm that would be consistent with semver.

Thanks all the same!

Yeah, it's unfortunate. The closest thing would be to bump the patch version. But then the build metadata doesn't make sense, because instead of 1.0.0+3-1234abcd+20140707-1030 you need 1.0.1+3.1234abcd.20140707.1030 or maybe 1.0.1-3.1234abcd+20140707.1030 using the pre-release segment, either of which is misleading. It's just not a convention that supports what this plugin seeks to do.

Yeah, there has to be an automatic bump of some kind. Still, I feel like this can be done, even if not in this project. I really like the workflow this plugin provides (where you only need to push a tag to get a proper release with a version change), and It'll be a shame to have to switch to something less convenient.

I'll try and come up with a scheme that would satisfy SemVer and various Maven-derived utilities. It definitely won't be nice, but I only need it for in-house development, so it'll do. 😄

I'd be interested to know what you come up with. We might be able to make it an opt-in option or even just document how to apply the change in the README.

It seems like the Coursier's Version is not really a semver implementation, but rather something else entirely. Like Maven, it has special qualifiers (alpha, rc...), but those don't include milestone/m for some reason...

For now I seem to have stumbled upon a relatively stable, but rather ugly versioning scheme described below. It seems to work with Maven and Coursier, as well as with proper SemVer (checked via the semverfi library). This has some idiosynchrazies built in (the -M* tag suffix).

Tags

Tags have shape of v\d+(\.\d+){0.2}(-M\d+)?. So v1, v0.1, v1-M1 and v1.2.3-M45 are all valid tags.

The dot-separated groups of numbers in the beginning are the base version. the -M* suffix is for milestone releases. It is implied that v1-M1 < v1.

Version numbers

These contain the following components. The base version component is separated from the other components via a dash (-), and all the other components are separated via a dot (.). Everything except the base version is optional.

  1. Base version, padded to three groups. If the last tag was not a milestone, and if we're not building a tagged release (as in, the tree is dirty or we had commits since the tag), the patch version is incremented. So when buliding v1 we have 1.0.0 here, when building dirty tag v1 we have 1.0.1, when building dirty tag v1-M2 we have 1.0.0.
  2. (If the latest tag was NOT a milestone) alpha.
  3. (If the latest tag was a milestone) beta. (Not milestone - Coursier doesn't support that).
  4. (If the latest tag was a milestone) The number of the milestone, incremented if the tree is dirty or we had commits since the tag. So when building v1-M2 we have 2 here, when building dirty v1-M2 we have 3. (UPD: the incrementing part was silly and wrong).
  5. (if dirty or had commits) The number of commits since the last tag. Zero if no commits were made.
  6. (if had commits) First 7 hex characters of the last commit's hash, as a decimal number (so that it is compared as one group by all version systems). This is a tiebreaker for the previous component.
  7. (if dirty) Date of the current timetamp as YYYYMMDD.
  8. (if dirty) Time of the current timetamp as HHMMSS.
  9. (if had commits) First 7 (or 8?) characters of the commit hash. This doesn't do anything for ordering, but it's nice to have this in the version number. This has to be in the end because it different version systems can see it as one or multiple groups.

Examples

// v0.1-M2
0.1.0-beta.2
// v0.1-M2 + dirty tree
0.1.0-beta.2.0.20210521.153300
// v0.1-M2 + 3 commits
0.1.0-beta.2.3.180146467.abcd123
// V0.1-M2 + 3 commits + dirty tree
0.1.0-beta.2.3.180146467.20210521.153300.abcd123
// v0.1
0.1.0
// v0.1 + dirty tree
0.1.1-alpha.0.20210521153300
// v0.1-M2 + 3 commits
0.1.1-alpha.3.180146467.abcd1234
// V0.1-M2 + 3 commits + dirty tree
0.1.1-alpha.3.180146467.20210521.153300.abcd123
// V0.1.1-M1
0.1.1-beta.1

UPD: this can be made more regular by adding an empty component for dirty tree without commits, where in the other case we would have the commit hash. So, compared to the above

// v0.1 + dirty tree
0.1.1-alpha.0.0.20210521.153300

UPD2: We can also add a zero component after alpha to match the milestone version in beta.

I'll try and build a proof-of-concept implementation (with some tweaks) later this weekend.

UPD: Here it is. This is slightly less convoluted than what I proposed above.