Opam repository package versioning policy?

I checked the existing policies here: opam-repository/governance/policies at master · ocaml/opam-repository · GitHub

However I could not find anything that talks explicitly about a package versioning policy ie what versions should be given to packages and under what criteria.

There is one mention of versioning that seems to imply a Semantic Versioning policy

Early < constraints are allowed if it is known for sure that the package will not be compatible with said upcoming version or to prevent likely runtime failures with a next major version for example.

The assumption being that a major version update could cause a breaking change.

My question is: can we formalize a package versioning policy? Imho packages in opam-repository should have a consistent versioning policy to avoid confusion among users and make it a bit easier to predict what impact new versions might have.

The obvious most popular policy is SemVer. Of course, SemVer doesn’t exactly work with OCaml because of eg functors, but imho a best-effort attempt to conform to it is better than no attempt.

Any thoughts on this?

1 Like

Note it’s not that it doesn’t work, it’s rather that most API changes can hypothetically break a client. So if you apply semver religiously you end up only having major versions.

Yes. Use your judgement. For example most modules out there do not end up being used as functor arguments, so if you add a new function to your API you likely don’t have to bump your major number.

2 Likes

As explained here, my policy is like (for major.minor.patch):

  • patch is when I update an implementation without a breaking change
  • minor is when I do a breaking change on the API
  • major is when the whole API change

As @dbuenzli said, if we count all breaking changes into the major number, we will end up to change only major version.

This method is also applied to a more or less extent to all Mirage projects (such as mirage-crypto or ocaml-dns). Of course, due to their age and the diversity of their original authors, it is difficult to standardise all releases.

However, what remains the rule is the way OPAM manages versions: opam - Manual.

1 Like

I’m not exactly sure how you distinguish between these two things. Depending on your notion of “whole” you end up never incrementing major :–)

Also if your API has been out for long enough, you have a lot of users, and the API overhaul is such that existing code has to be rewritten to support the new one (is that your notion of whole ?). It’s often better for your users to add the version name in the library name itself (e.g. sqlite3) or rename your library.

To make my “Use your judgement” more precise, here’s a little bit more. I think semver is amenable to OCaml if we introduce the notion of systematic and non-systematic breakage. The latter being allowed for minor versions.

  • Changing existing structure items in a way that systematically breaks user code if used is a major change. This includes: changing a type name or expression in an incompatible way, removing a label from a function signature, removing an argument from a function signature, adding a required argument to a function signature, adding or removing a new field to a non-abstract record etc. You get the idea.
  • Changes that do not systematically break user code is a minor change. This include: adding and using type aliases, adding an optional argument to a function (though that one breaks users more often that you would like) or adding a new structure item to a module that is not explicitly designed to serve as a functor argument.

Also I often have modules (e.g. for writing backends) in which I explicitely say they can change in incompatible even between minor versions of the library. Just warn the user on the stability they should expect.

If we take the example you linked to, according to my criterions, this would have been a major version. It removes a label and renames and changes the type of a record field. Note the latter change could have been handled with a backwards compatiblily story and a minor change had the type been made abstract.

In an ideal world scattered with infinite time, users would carefully read release notes and versions numbers wouldn’t matter much. Since that world does not exist I think semver is useful to give a hint of what upgrading to a new version could entail. But it remains just that: a hint.

2 Likes

Yes, I think another question related to breaking changes is how the releaser reacts to the revdeps in their library. From my point of view, it’s still a case-by-case basis, even though the opam CI helps us know what will break.

We may need to break the API, increase minor and associate a series of patches with the release to allow other maintainers to integrate these changes. However, there is a problem with this (as shown in the example), which is that releasers are not necessarily omniscient about all revdeps.

Note the latter change could have been handled with a backwards compatiblily story and a minor change had the type been made abstract.

I completely agree, and we should build a time machine together so we can go back and blame our previous versions for syndic, as well as for Arg.conv :slight_smile: .

In general, we can’t know what revdeps will break. Opam-repository can give us a partial picture but it’s not complete for the obvious reason that OCaml projects using packages aren’t all on opam-repository (for obvious reasons).

That’s why other package ecosystems don’t try to provide a full and complete answer to the question of breaking changes and just give a best-effort answer…like some kind of package versioning policy. The idea is to make changes (bugfixes, backward-compatible new features, and breaking changes) predictable for most people.