tl;dr: When using semantic versioning, consider not only changes to the public API, but also changes to system requirements.
Semantic Versioning concentrates on the public API signature as the determiner of when to change version numbers. If the public API changes in a backwards-incompatible way, then you have bump the major version number. When I upgrade a dependency to a minor or patch version, I am hypothetically assured that the upgrade will be successfully completed, and that I will not have to change anything about my existing system or codebase.
However, I have come to think that while SemVer’s concentration on the public API is a necessary indicator of the upgradability without other changes, it is not a sufficient indicator. Here’s a real-world upgrade scenario:
Package 1.0.0 is released, dependent on features available in Language 4.0.0.
Package 1.1.0 is released, with a public API identical to 1.0.0, but is now dependent on new features available only in Language 4.1.0.
SemVer is honored here by both the Package and the Langauge. Neither has introduced backwards-incompatible public API changes: the Package API is unchanged, and the Language API adds new backwards-compatible features.
But now the Package internals depend on the new features of the Language. As such, it is not possible for the Package 1.0.0 consumers to upgrade to Package 1.1.0 without also upgrading to Language 4.1.0. If they try to upgrade, the system will break, even though SemVer indicates it should be safe to upgrade.
It looks like there is more to backwards compatibility than just the public API.
Some will argue that when the package requirements are properly specified in a package manifest, the package manager prevent a break from occurring in the above scenario. The package manager, reading the manifest, will note that Language 4.1.0 is not installed, and refuse to upgrade Package from 1.0.0 to 1.1.0.
However, this is beside the point that I am trying to make. Yes, the package manager prevents breaking installations based on a manifest. But that prevention does not mean that the changes introduced in Package 1.1.0 are backwards-compatible with a system running 1.0.0. The 1.1.0 changes require a system modification because of a requirements incompatibility.
So it is true that the package manager provides a defense against breaks, but is also true that the version number of the package does not indicate there is a backwards incompatibility with the existing system. It is the indication of incompatibility (among other things) that makes SemVer valuable.
I opine that requiring a change in the public environment into which a package is installed is just as major an incompatibility as introducing a breaking change to the public API of the package. To cover that case, I offer the following as a draft addendum to the SemVer spec:
- If the package consumer has to change a publicly-available system resource to upgrade a package, then the package upgrade is not backwards-compatible with the existing system, and the package SHOULD receive a major version bump.
Using “SHOULD” makes this rule somewhat less strict than the MUST of a major version bump when changing the package API. It’s possible that Language 4.0.0 is no longer supported or installed on any of the target systems, in which case it might be reasonable that a Package minor version could require a change to the Language version. But you do need to have a good technical or practical reason (not a marketing reason 😉 to avoid the major version bump when changing the public system requirements for the package.
(You may also wish to review Romantic Versioning for an additional take on Semantic Versioning.)