Skip to content
Search

Semantic Versioning

lastmod 2026-01-19created 2020-09-259 edits

A versioning rule of the form MAJOR.MINOR.PATCH

Summary

Given a version number MAJOR.MINOR.PATCH:

  • increment the “MAJOR version” when the API changes in a way that is incompatible with previous versions,
  • increment the “MINOR version” when adding new functionality in a way compatible with previous versions,
  • increment the “PATCH version” when making bug fixes compatible with previous versions.

Labels for pre-release versions and build metadata can also be appended to the MAJOR.MINOR.PATCH format.

Introduction

In the world of software management there is a dreaded problem called “dependency hell.” The bigger your system grows and the more packages you pull in, the more likely you are to find yourself, someday, in this pit of despair.

In systems with many dependencies, releasing new package versions can quickly become a nightmare. If the dependency specifications are too strict, you are in danger of version lock (the inability to upgrade a package without having to release new versions of every dependent package). If dependencies are specified too loosely, you will inevitably be bitten by version promiscuity (assuming compatibility with more future versions than is reasonable). Dependency hell is where you are when version lock or version promiscuity prevents you from easily and safely moving your project forward.

As a solution to this problem, we propose a set of rules and requirements that dictate how version numbers are assigned and incremented. These rules are based on, but not necessarily constrained to, rules widely used in existing open-source/closed-source software. For this system to work, you first need to declare a public API. This can be expressed through documentation or by the source code itself. Either way, the API must be clear. Once you have defined your public API, you communicate changes to it by incrementing the version number. Versions take the form X.Y.Z (MAJOR.MINOR.PATCH). Bug fixes that do not affect the API increment the PATCH version, backward-compatible API changes or additions increment the MINOR version, and backward-incompatible API changes increment the MAJOR version.

We call this system “Semantic Versioning.” Under this scheme, version numbers and the way they change convey how the code changed from one version to the next.

Semantic Versioning Specification (SemVer)

Below, the expressions “MUST, REQUIRED, SHALL”, “MUST NOT, SHALL NOT”, “SHOULD, RECOMMENDED”, “SHOULD NOT”, and “MAY, OPTIONAL” are to be interpreted as described in RFC 2119.

  • Software using Semantic Versioning MUST declare a public API. This API could be declared in the code itself or exist strictly in documentation. However it is done, it should be precise and easy to understand.
  • A normal version number MUST take the form X.Y.Z where X, Y, and Z are natural numbers (non-negative integers), and MUST NOT contain leading zeroes. X is the MAJOR version, Y is the MINOR version, and Z is the PATCH version. Each element MUST increase. For example: 1.9.0 -> 1.10.0 -> 1.11.0.
  • Once a versioned package has been released, the contents of that version MUST NOT be modified. Any modifications MUST be released as a new version.
  • MAJOR version zero (0.y.z) is for initial development. Anything may change at any time. The public API SHOULD NOT be considered stable.
  • Version 1.0.0 defines the public API. Version numbers after this are incremented according to how the public API released at that point changes.
  • PATCH version Z (x.y.Z | x > 0) MUST be incremented only for bug fixes compatible with the previous API. A bug fix is defined as a change that fixes incorrect internal behavior.
  • MINOR version Y (x.Y.z | x > 0) MUST be incremented when new backward-compatible functionality is added to the public API. It MUST also be incremented when any part of the public API is marked as deprecated (to be removed in the future). It MAY be incremented when substantial new functionality or improvements are introduced in the private code. A MINOR increment MAY include PATCH-level changes. The PATCH version MUST be reset to 0 when the MINOR version is incremented.
  • MAJOR version X (X.y.z | X > 0) MUST be incremented when backward-incompatible changes are introduced to the public API. It MAY include MINOR- and PATCH-level changes. The MINOR and PATCH versions MUST be reset to 0 when the MAJOR version is incremented.
  • A pre-release version MAY be denoted by appending a hyphen (-) immediately after the PATCH version, followed by dot (.)-separated identifiers. Identifiers MUST comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-]. Identifiers MUST be at least one character long. Numeric identifiers MUST NOT include leading zeroes. Pre-release versions have lower precedence than the associated normal version. A pre-release version is still unstable and might not satisfy the compatibility requirements of its associated normal version. For example: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.
  • Build metadata MAY be denoted by appending a plus (+) sign immediately after the PATCH version or pre-release identifier, followed by dot-separated identifiers. Identifiers MUST comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-]. Identifiers MUST be at least one character long. Build metadata MUST be ignored when determining precedence between versions. Therefore, two versions that differ only in build metadata have the same precedence. For example: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.
  • Precedence refers to how versions are compared to each other when sorting them. Precedence MUST be calculated by separating the version into MAJOR, MINOR, PATCH, and pre-release identifiers (build metadata does not affect precedence). Precedence is determined by the first difference found when comparing each of these in order: MAJOR, MINOR, and PATCH are compared numerically. For example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1. When MAJOR, MINOR, and PATCH are equal, a pre-release version has lower precedence. For example: 1.0.0-alpha < 1.0.0. Precedence between two pre-release versions with the same MAJOR, MINOR, and PATCH MUST be determined by comparing each dot-separated identifier in order until a difference is found: identifiers consisting only of digits are compared numerically, and identifiers containing letters or hyphens are compared in ASCII sort order. Numeric identifiers always have lower precedence than identifiers containing letters or hyphens. When all preceding identifiers are equal, the pre-release version with more fields has higher precedence. For example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.

BNF (Backus-Naur Form) grammar for valid Semantic Versions

<유의적 버전> ::= <버전 몸통>
             | <버전 몸통> "-" <배포 전 버전>
             | <버전 몸통> "+" <빌드>
             | <버전 몸통> "-" <배포 전 버전> "+" <빌드>

<버전 몸통> ::= <주> "." <부> "." <수>

<주> ::= <숫자 식별자>

<부> ::= <숫자 식별자>

<수> ::= <숫자 식별자>

<배포 전 버전> ::= <마침표로 구분된 배포 전 식별자들>

<마침표로 구분된 배포 전 식별자들> ::= <배포 전 식별자>
                              | <배포 전 식별자> "." <마침표로 구분된 배포 전 식별자들>

<빌드> ::= <마침표로 구분된 빌드 식별자들>

<마침표로 구분된 빌드 식별자들> ::= <빌드 식별자>
                            | <빌드 식별자> "." <마침표로 구분된 빌드 식별자들>

<배포 전 식별자> ::= <숫자와 알파벳으로 구성된 식별자>
                | <숫자 식별자>

<빌드 식별자> ::= <숫자와 알파벳으로 구성된 식별자>
             | <숫자들>

<숫자와 알파벳으로 구성된 식별자> ::= <숫자 아닌 것>
                             | <숫자 아닌 것> <식별자 문자들>
                             | <식별자 문자들> <숫자 아닌 것>
                             | <식별자 문자들> <숫자 아닌 것> <식별자 문자들>

<숫자 식별자> ::= "0"
             | <양의 숫자>
             | <양의 숫자> <숫자들>

<식별자 문자들> ::= <식별자 문자>
               | <식별자 문자> <식별자 문자들>

<식별자 문자> ::= <숫자>
             | <숫자 아닌 것>

<숫자 아닌 것> ::= <문자>
              | "-"

<숫자들> ::= <숫자>
         | <숫자> <숫자들>

<숫자> ::= "0"
        | <양의 숫자>

<양의 숫자> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

<문자> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J"
        | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T"
        | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d"
        | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n"
        | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x"
        | "y" | "z"

Why use Semantic Versioning?

Semantic Versioning is not a revolutionary idea. In fact, you may already be doing something close to it. The problem is that “close” is not good enough. Without compliance to some sort of formal specification, version numbers are essentially useless for dependency management. By giving a name and a clear definition to the above ideas, it becomes easy to communicate your intentions to the users of your software. Only once these intentions are clear can flexible (but not too flexible) dependency specifications be made.

A simple example will show how Semantic Versioning can get you out of dependency hell. Consider a library called “Firetruck.” It depends on a Semantically Versioned package named “Ladder.” When Firetruck was created, Ladder was at version 3.1.0. Since Firetruck uses functionality first introduced in 3.1.0, you can specify the Ladder dependency as greater than or equal to 3.1.0 but less than 4.0.0. Now, when Ladder versions 3.1.1 and 3.2.0 are released, you can put them into your package management system and know they will be compatible with existing software.

Of course, as a responsible developer you will want to verify that any package upgrades function as advertised. The real world is a messy place; there is nothing we can do about that but be vigilant. By using Semantic Versioning, you can release and upgrade packages without wrestling with new versions of dependent packages, saving time and hassle.

If all of this sounds desirable, all you need to do to start using Semantic Versioning is to decide to do so and follow the rules. Add a link to this website in your README file so others can benefit from it too.

FAQ

How should I deal with versions in the 0.y.z initial development phase?
The simplest thing to do is start your initial development release at 0.1.0 and then increment the MINOR version for each subsequent release.
How do I know when to release 1.0.0?
If your software is being used in production, it can probably already be considered 1.0.0. If you have a stable API that users can rely on, it should be 1.0.0. If you have started worrying about backward compatibility, it may already be 1.0.0.
Doesn't Semantic Versioning discourage rapid development and fast iteration?
MAJOR version zero is all about rapid development. If you are changing the API every day, you should either still be on version 0.y.z or be working on a separate development branch for the next MAJOR release.
If even the tiniest backward-incompatible change to the public API requires a MAJOR version bump, won't I end up at version 42.0.0 very quickly?
This is a question of responsible development and foresight. Incompatible changes should not be introduced lightly to software that has a lot of dependent code. The cost of upgrading can be enormous. Having to bump MAJOR versions to release incompatible changes means you must fully evaluate the impact of your changes and their costs and benefits.
Documenting the entire public API is too much work!
It is your responsibility as a professional developer to properly document software intended for use by others. Managing software complexity is a hugely important part of keeping a project efficient, and that is hard to do if nobody knows how to use your software or which methods are safe to call. In the long run, Semantic Versioning and a well-defined public API keep everyone and everything running smoothly.
What do I do if I accidentally release a backward-incompatible change as a MINOR version?
As soon as you realize you have broken the Semantic Versioning specification, fix the problem, restore the broken compatibility, and release a new MINOR version. Even in this case, you must not modify an already released version. If appropriate, document the offending version so users are warned about it.
What should I do if I change my internal dependencies without changing the public API?
It is considered compatible, since it does not affect the public API. Software that explicitly depends on the same dependencies as your package should have its own dependency specifications, and its author will notice any conflicts. Whether a change is PATCH-level or MINOR-level depends on whether you made it to fix a bug or to add new functionality. In the latter case, additional code is to be expected, so the MINOR version should obviously be incremented.
What if I accidentally change the public API in a way that does not match the version change? (i.e. incorrect code was introduced in a PATCH release and broke things)
Use your best judgment. If a large user base would be affected by changing the public API back to what it was originally intended to be, it may be best to make a MAJOR release, even if the fix could strictly be considered a PATCH release. Remember, Semantic Versioning is all about conveying meaning through how the version number changes. If a change is significant to your users, use the version number to let them know.
How should I handle deprecating functionality?
Removing existing functionality is a normal part of software development and is often required to move forward. When you want to remove part of the public API, you should do two things: (1) update your documentation to let users know about the change, and (2) release a new MINOR version in a timely manner with the functionality marked for removal. Before you completely remove the functionality in a new MAJOR release, there should be at least one MINOR release with the deprecation marked so that users can smoothly transition to the new API.
Does SemVer have a size limit on the version string?
No, but use good judgment. A 255-character version string, for example, is probably overkill. Also, some systems may impose their own limits on string length.
Is “v1.2.3” a semantic version?
No, “v1.2.3” is not a semantic version. However, a “v” is sometimes prefixed to a semantic version to indicate a version number. Using “v” as an abbreviation for “version” is common in version control. For example: in git tag v1.2.3 -m "Release version 1.2.3", “v1.2.3” is the tag name and the semantic version is “1.2.3”.

Is there a regular expression (RegEx) to check a SemVer string?

There are two. The first is a regular expression with named capture groups (supported by PCRE [Perl Compatible Regular Expressions, i.e. Perl, PHP and R], Python, and Go).

See: https://regex101.com/r/Ly7O1x/3/

^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$

The second is a regular expression for engines that do not support named capture groups.

See: https://regex101.com/r/vkijKf/1/

^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$

The sed regex I use

PROTOC_VERSION=`"$RECC_DIR/python" -m grpc_tools.protoc --version | sed 's/libprotoc //g'`
PROTOC_MAJOR=`cat $PROTOC_VERSION | sed -E 's/^([0-9]+)\.([0-9]+)\.([0-9]+)$/\1/'`
PROTOC_MINOR=`cat $PROTOC_VERSION | sed -E 's/^([0-9]+)\.([0-9]+)\.([0-9]+)$/\2/'`
PROTOC_PATCH=`cat $PROTOC_VERSION | sed -E 's/^([0-9]+)\.([0-9]+)\.([0-9]+)$/\3/'`

Python implementation

# -*- coding: utf-8 -*-

from typing import Final, NamedTuple, Optional


class SemanticVersion(NamedTuple):
    major: int
    minor: Optional[int] = None
    patch: Optional[int] = None


def parse_version_numbers(ver: str) -> tuple:
    return tuple([int(d) for d in ver.split("-")[0].split(".")])


def parse_semantic_version(ver: str) -> SemanticVersion:
    n = parse_version_numbers(ver)
    if len(n) != 3:
        raise ValueError(
            f"The argument `ver` is not in semantic versioning format: '{ver}'"
        )

    assert isinstance(n[0], int)
    assert isinstance(n[1], int)
    assert isinstance(n[2], int)

    if n[0] < 0:
        raise ValueError(
            f"The major version number must be greater than or equal to 0 (vs {n[0]})"
        )
    if n[1] < 0:
        raise ValueError(
            f"The minor version number must be greater than or equal to 0 (vs {n[1]})"
        )
    if n[2] < 0:
        raise ValueError(
            f"The patch version number must be greater than or equal to 0 (vs {n[2]})"
        )

    return SemanticVersion(n[0], n[1], n[2])


def normalize_version(ver: str) -> str:
    return ver.replace("-", ".")


version_text: Final[str] = "2.0.4"
version_tuple: Final[SemanticVersion] = parse_semantic_version(version_text)

assert len(version_tuple) == 3
assert isinstance(version_tuple[0], int)
assert isinstance(version_tuple[1], int)
assert isinstance(version_tuple[2], int)
assert version_tuple[0] >= 0
assert version_tuple[1] >= 0
assert version_tuple[2] >= 0

TestCase

# -*- coding: utf-8 -*-

import unittest

from recc.util.version import (
    normalize_version,
    parse_version_numbers,
    version_text,
    version_tuple,
)


class VersionTestCase(unittest.TestCase):
    def test_version(self):
        self.assertEqual(3, len(version_text.split(".")))
        self.assertLessEqual(2, version_tuple[0])

    def test_parse_semantic_version(self):
        self.assertTupleEqual((1, 2, 3), parse_version_numbers("1.2.3-dev10"))
        self.assertTupleEqual((0, 0, 0), parse_version_numbers("0.0.0"))
        self.assertTupleEqual((90, 2, 20), parse_version_numbers("90.02.20"))

    def test_normalize_version(self):
        normalize_version_info = normalize_version(version_text).split(".")
        is_extend_version_info = version_text.find("-") >= 0
        self.assertEqual(
            4 if is_extend_version_info else 3, len(normalize_version_info)
        )


if __name__ == "__main__":
    unittest.main()

Libraries

See also

Favorite site

author Zer0 sitemap