“Semantic Versioning”, commonly called SemVer, is a specification for
assigning and reasoning about software version numbers. In short, it codifies
some common practices regarding $MAJOR.$MINOR.$REVISION
style version
numbers, including guarentees about API compatibility and dependency
resolution.
Beautifully simple semantics, outlined by a clear and concise specification. What’s not to like? Technically, SemVer is very strong. However, statistically speaking, software developers can not be trusted to maintain the semantics promised by SemVer. A small handful of individuals and projects use SemVer, or something like it, to good effect. The rest can’t be trusted not to introduce major bugs or breaking changes with each and every revision.
Every engineer who has ever worked on a project with more than a handful of external dependencies has had the horrifying experience of deploying some small dependency upgrades only to have their entire house of cards come crashing down. As a result, mature organizations tend to maintain their own package repositories, vendor all dependencies, or otherwise carefully scrutinize and control each and every external component.
Packaging systems such as Ruby’s Bundler and Node’s NPM often default
to or otherwise encourage SemVer. Depending on a
pessimistic version constraint, such as ~> 1.2
, is tantamount to saying
“I trust that the maintainer of this project both understands SemVer and is
capable of enforcing its guarantees.” Sadly, this is rarely, if ever, true.
Versioning is a social contract. A maintainer makes a promise regarding API stability and versioning policy. Consumers make a judgement call regarding the veracity of the maintainer’s promise. If the README file says “This project uses SemVer” and the project’s maintainer is Tom Preston-Werner, then you can trust that pessimistic version constraint in your dependencies file.
However, the README might say “Releases are only packaged for long-term support
versions. Please use the stable
branch for new production systems.” In such
a case, you might want to consider depending directly on that branch or even on
a particular commit hash. Speaking of branches and commits, this calls to mind
a far preferable default for packaging and dependency resolution systems:
refspecs. It would be great if the SemVer spec was updated to include
policies for defining branch and tag names in your version control system of
choice. Instead of depending on ~> 1.2
, you could depend on
branches/v1.2.x
. This grants maintainers greater flexibility in making social
contracts. It also grants consumers greater control over how much trust they
give to maintainers.