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.

2010

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.

Nov 2011 · @mojombo

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.

semver@7092112 — "add build versions"

Dec 2011 · 2.0.0-rc.1

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.

semver.org/spec/v2.0.0-rc.1.html

Mar 2013 · 2.0.0-rc.2 · @haacked & @jeffhandley

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 ↓

rc.1 → rc.2 diff · semver/semver#77 · 57fd194 · 13aaada

Since 2013

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.

Today · 1.0.0

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.

Watch the talk: "Spec-tacular — SemVer & Beyond"

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.

SEMVER.md 2.0.0-rc.1 → 2.0.0-rc.2 +6−5
@@ -33,3 +31,4 @@ Build definition
33-A build version MAY be denoted by appending a plus sign …
34-Build versions satisfy and have a higher precedence than the associated normal version.
35-Examples: 1.0.0+build.1, 1.3.7+build.11.e0f985a
31+Build metadata MAY be denoted by appending a plus sign …
32+Build metadata SHOULD be ignored when determining version precedence.
33+Thus two packages with the same version but different build metadata are considered to be the same version.
34+Examples: 1.0.0-alpha+001, 1.0.0+20130313144700
@@ -40,2 +38,2 @@ Precedence
40-Precedence MUST be calculated by separating the version into major, minor, patch, pre-release, and build identifiers in that order. …
41-Pre-release and build version precedence MUST be determined by comparing each dot separated identifier.
38+Precedence MUST be calculated by separating the version into major, minor, patch and pre-release identifiers in that order.
39+(Build metadata does not figure into precedence.)

See 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.