History
How build lost its meaning.
Semantic Versioning is well intentioned, but it carries a few bad definitions and a few missing ones. The most consequential mistake happened between two release candidates, and we still live with it today. Here is the paper trail.
Node & npm sidestep dependency hell
Node introduces nested modules and npm arrives. Because you can safely scope while still consuming conflicting transitive dependencies, JavaScript avoids the first circle of dependency hell that trapped single-version ecosystems. The community goes on to publish more packages than anywhere else.
Builds are introduced
Tom Preston-Werner adds build versions to the spec. A build functions as the antithesis of a pre-release (you could call it a post-release) and it participates in precedence. Two builds of the same normal version can be ordered.
The high-water mark
2.0.0-rc.1 is published. As far as we can tell it is the most accurate
versioning spec to date: it encompasses common practice of generating builds and build
variants, and keeps their precedence intact. This is the document
Semantic Versions takes as its starting point.
Builds become inert "build metadata"
Two years later, Phil Haack and Jeff Handley, contributors from the NuGet and .NET ecosystem, redefine builds as build metadata and strip away their precedence. Overnight, every version with build metadata becomes equivalent to any other of the same normal or pre-release version. Six artifacts with different git hashes and architectures are now semantically identical, with no way in the spec to order or choose between them. See the exact wording that changed ↓
The ecosystem forgets builds exist
npm's own SemVer implementation, which itself adds grammars and logic on top of the spec, defaults to stripping build metadata from versions. The result: there are effectively no packages in the registry carrying build metadata, even though it is supposed to be a valid part of a version. A legitimate, useful concept was co-opted into a non-functional one, confusing tooling authors ever since.
Semantic Versions
Semantic Versions reverts the build change, restores build precedence, corrects the definition of a patch, and finally writes down the sets, ranges and comparators the ecosystem already relies on. If we want to avoid becoming heretics in the sixth circle, we have to codify what we actually do.
The exact change
Two sentences, a decade of confusion.
The intent of the + build was never in doubt in
2.0.0-rc.1: a build was a post-release that ordered above its
normal version. Between the two release candidates, the sentence that gave it meaning
was struck and replaced. Here is that change, as a diff of the verbatim spec text.
1.0.0+build.1, 1.3.7+build.11.e0f985a1.0.0-alpha+001, 1.0.0+20130313144700See the full side-by-side: rc.1 → rc.2 diff · rc.1 text · rc.2 text
Why they did it, and the opening it left
It wasn't malice; it answered a real need. NuGet was designing its v3 feed and wanted
build metadata to be diagnostic only: a commit hash, date, or build
number that never changes a package's identity. If build affected precedence, then
1.0.0+a and 1.0.0+b would be different packages:
ambiguous equality, duplicate registry entries, and unpredictable folder paths, URLs and
.nupkg filenames that NuGet constructs from the normalized version.
Declaring build “ignored” made identity deterministic. That local win became the global
default, and npm's own SemVer, which strips build, sealed it.
The irony is the opportunity. Because the ecosystem then stopped using build at all, the slot is empty real estate. Semantic Versions restores its precedence and puts it to work exactly where nothing else lives: Dependency Locks and Package Integrity are what a meaningful build was always for.
Reasoning drawn from “Supporting Semantic Versioning 2.0.0” (.NET Blog) and the NuGet SemVer 2.0.0 support notes.