Skip to content

Instantly share code, notes, and snippets.

@pbrisbin
Created February 16, 2018 00:31
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pbrisbin/4b31ce9cac904cb7ce515ee1fa50969d to your computer and use it in GitHub Desktop.
Save pbrisbin/4b31ce9cac904cb7ce515ee1fa50969d to your computer and use it in GitHub Desktop.
Versioning Haskell: Semver <> PVP

I personally prefer Semver. I think it's reasonable, simple, and makes sense. But as a good Haskell citizen, I'd like to be PVP-compliant as well. Here is a bit of a graphic showing how the two systems are almost the same:

PVP:            A . B     . C     . ...
Semver:             Major . Minor . Patch
                    ^       ^       ^
                    |       |       |
                    |       |       ` increment for other changes
                    |       |
                    |       ` increment on non-breaking change
                    |
                    ` increment on breaking change

Basically, PVP's A is useless from a compatibility standpoint. It's just a bystander in the "increment A.B for breaking changes" rule. So I just set it to 0 and never change it. The remaining 3 components then follow Semver.

So for any of my packages, you can interpret versions as: 0.Major.Minor.Patch

@jcornaz
Copy link

jcornaz commented Apr 23, 2020

Hi,

May I ask how you handle pre-releases? Semver allow us to have version like 1.0.0-alpha.1. How do you translate that into PVP?

@pbrisbin
Copy link
Author

Good question!

Based on https://pvp.haskell.org/, you technically cannot:

The components of the version number MUST be numbers! Historically Cabal supported version numbers with string tags at the end, e.g. 1.0-beta This proved not to work well because the ordering for tags was not well defined. Version tags are no longer supported and mostly ignored, however some tools will fail in some circumstances if they encounter them.

And https://pvp.haskell.org/faq/#semver,

Also, while SemVer allows for appending version tags and/or build metadata (e.g. 1.0.0-alpha+git.5114f85), the PVP does not regulate nor support such additional information in version numbers.

My (perhaps disappointing) answer to this is that I just never do it 🤷.

Any "pre-release" periods I've needed have always been during initial development, where I choose SemVer's "nothing matters if you're pre 1.0" guide. If I want to do any pre-release testing after that, I usually add the release branch as a git dependency into test projects. I've never released a pre-release package for users.

Again in https://pvp.haskell.org/faq/#semver,

Another important difference between SemVer and PVP is that the PVP doesn’t distinguish between 0.x.y and 1.x.y. SemVer considers the major version zero for initial development and allows the API to change without requiring major version increment. The PVP, however, does not provide for such an exception

I depart from PVP here and I will not hold myself to compatibility bumps in releases until I reach a 0.1.0.0 version, which I consider "1.0" in SemVer's parlance. For example, 0.0.0.1 -> 0.0.1.0 may include a breaking change in one of my packages. That said, I don't hesitate greatly in getting to 1.0, so this period is typically short and I (or my team) is usually the only user of the package during that time.

Looking over https://pvp.haskell.org/faq/#semver, I would probably amend this bit from the gist:

Basically, PVP's A is useless from a compatibility standpoint ... So I just set it to 0 and never change it

The examples they give are good ones, that I have and would use this component for myself:

  • ... to denote “epochs” of APIs ...
  • ... signaling of large vs. not-so-large backward incompatible changes
  • ... for marketing purposes

It just so happens I rarely do this in practice, so most of my packages will sit at 0..

@jcornaz
Copy link

jcornaz commented Apr 23, 2020

Yeah, that's quite annoying.

The thing is I like to have automated deployments for every commits I push to my default branch, and I make my work available as soon as possible, even if it is not "stable" yet. I believe it is an invaluable way to get good and early feedback about the API. It also save me from asking myself "when do I release? should I release?". I always release everything, every commit. So the only remaining choice I have to make is: "what is the stability I want to communicate to my users".

Sadly that doesn't seem possible with PVP :-/

I depart from PVP here and I will not hold myself to compatibility bumps in releases until I reach a 0.1.0.0 version, which I consider "1.0" in SemVer's parlance.

That's an understandable point of view, but it is important to realize that a breaking change when going from 0.0.1.0 to 0.0.2.0 is technically not PVP compliant. So I am not sure weather I'd allow myself to follow the same pattern :-(.

I guess the only thing I can do is follow the 0.MAJOR.MINOR.PATCH convention, and publish everything as stable, even if it is not... I guess that it is not possible to do better in Haskell. Quite a shame.

The examples they give are good ones, that I have and would use this component for myself

I am not sure how good they are. To me they sound like an excuse to not change for semver. At least from my point of view they are not good enough to justify the usage of a language specific versioning scheme (that does not support pre-release concept) rather than following semver.

And actually the very fact that they give multiple different usages of the first major member is a sign that is is not good. It means it will always have different meaning depending on the library and it is open to interpretation. The goal of any versioning specification is to eliminate room for subjective interpretation.

Thanks for taking the time to respond with so much details and sources.

Last question, in your git repository, do you create tag after semver or PVP version? In other words, do your tags contain the leading 0. ?

@jcornaz
Copy link

jcornaz commented Apr 23, 2020

Okay, I think I know what I will do.

I follow semantic versioning just the "normal" way. And my automated release process only has to be slightly more complex than it usually is.

For each release:

  • Generate the change log, create the git tag (semver style), and github release (as usual)
  • If, and only if, the version is a stable version (not starting at 0 and not suffixed with a pre-release tag):
    • publish to hackage by prefixing the version number with 0.. Or maybe I will pick something different in order to remove the "unstable" feeling that we have when looking at version number starting with 0. Maybe I will prefix it either 1. or 42.

And finally, I simply don't publish non-final versions to hackage, forcing users to depend on the git repository directly.
For versions that are not published, I don't write any version number in the cabal/stack file.

Not being able to share work in progress is a shame, but I don't see what else I could do...

@pbrisbin
Copy link
Author

do you create tag after semver or PVP version? In other words, do your tags contain the leading 0. ?

It contains the leading A..

Regardless of how I'm bringing a SemVer perspective, or being PVP compliant, the version is the version: whatever string comes after version: in my package.yaml. Intention or perspective doesn't matter once it's been set for the package.

Okay, I think I know what I will do.

That's a nice process. You're sort of leaning into them being two different things and using SemVer generally, but "converting" to a PVP version specifically for Hackage. This is different than my goal, which is to just have one versioning scheme that makes sense through both lenses.

I agree it's probably not great to intentionally not be PVP-compliant in any pre0.1.0.0 versions. I'll continue to consider it and probably do it less often, only in extremely not-used packages. For anything with users that may be impacted I would probably just push 0.1.0.0 sooner and follow PVP thereafter. I'm like you in that I work in the open and publish immediately, regardless of polish.

@jcornaz
Copy link

jcornaz commented Apr 23, 2020

Regardless of how I'm bringing a SemVer perspective, or being PVP compliant, the version is the version: whatever string comes after version: in my package.yaml. Intention or perspective doesn't matter once it's been set for the package.

Well, we cannot write arbitrary version numbers after version: in package.yaml. Stack fails to build if I write 1.0.0-alpha.1 in my package.yaml.

That's why I have to include how to deal with this limitation in my process :-/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment