[GH-ISSUE #782] [2.0.0] Make patch optional? #7489

Open
opened 2026-06-20 17:34:22 -05:00 by GiteaMirror · 13 comments
Owner

Originally created by @JC3 on GitHub (Dec 9, 2021).
Original GitHub issue: https://github.com/semver/semver/issues/782

I think the patch number should be specified as optional, and assumed to be 0 if not present.

I don't have a particularly compelling argument for this other than I very often find that I don't need a patch number, and that I prefer short version identifiers (e.g. "1.0") where possible. Also I think a lot of people already do this, because I see two-component version numbers all over the place. 🤷‍♂️

If this were to become the case, the following changes would need to be made to the 2.0.0 spec (plus a few others probably):

ac34620593

diff --git a/semver.md b/semver.md
index e84a0f3..8e38bbb 100644
--- a/semver.md
+++ b/semver.md
@@ -59,9 +59,9 @@ interpreted as described in [RFC 2119](http://tools.ietf.org/html/rfc2119).
 could be declared in the code itself or exist strictly in documentation.
 However it is done, it should be precise and comprehensive.
 
-1. A normal version number MUST take the form X.Y.Z where X, Y, and Z are
+1. A normal version number MUST take the form X.Y or X.Y.Z where X, Y, and Z are
 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.
+major version, Y is the minor version, and Z is the OPTIONAL patch version.
 Each element MUST increase numerically. For instance: 1.9.0 -> 1.10.0 -> 1.11.0.
 
 1. Once a versioned package has been released, the contents of that version
@@ -78,6 +78,9 @@ changes.
 compatible bug fixes are introduced. A bug fix is defined as an internal
 change that fixes incorrect behavior.
 
+1. Patch version MAY be omitted, in which cased it SHALL be considered 
+exactly equivalent to 0.
+
 1. Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards
 compatible functionality is introduced to the public API. It MUST be
 incremented if any public API functionality is marked as deprecated. It MAY be
@@ -114,8 +117,9 @@ Precedence MUST be calculated by separating the version into major, minor, patch
 and pre-release identifiers in that order (Build metadata does not figure 
 into precedence). Precedence is determined by the first difference when
 comparing each of these identifiers from left to right as follows: Major, minor,
-and patch versions are always compared numerically. 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
+and patch versions are always compared numerically. An omitted patch number is 
+equivalent to 0. Example: 1.0.0 < 2.0.0 <
+2.1 < 2.1.1. When major, minor, and patch are equal, a pre-release version has
 lower precedence than a normal version. Example: 1.0.0-alpha < 1.0.0. Precedence
 for two pre-release versions with the same major, minor, and patch version MUST
 be determined by comparing each dot separated identifier from left to right

Just a thought!

Originally created by @JC3 on GitHub (Dec 9, 2021). Original GitHub issue: https://github.com/semver/semver/issues/782 I think the patch number should be specified as optional, and assumed to be 0 if not present. I don't have a particularly compelling argument for this other than I very often find that I don't need a patch number, and that I prefer short version identifiers (e.g. "1.0") where possible. Also I think a lot of people already do this, because I see two-component version numbers all over the place. 🤷‍♂️ If this were to become the case, the following changes would need to be made to the 2.0.0 spec (plus a few others probably): https://github.com/JC3/semver/commit/ac346205935c38bcd06f0e3e5fc1b2ca5ec98ea9 ```diff diff --git a/semver.md b/semver.md index e84a0f3..8e38bbb 100644 --- a/semver.md +++ b/semver.md @@ -59,9 +59,9 @@ interpreted as described in [RFC 2119](http://tools.ietf.org/html/rfc2119). could be declared in the code itself or exist strictly in documentation. However it is done, it should be precise and comprehensive. -1. A normal version number MUST take the form X.Y.Z where X, Y, and Z are +1. A normal version number MUST take the form X.Y or X.Y.Z where X, Y, and Z are 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. +major version, Y is the minor version, and Z is the OPTIONAL patch version. Each element MUST increase numerically. For instance: 1.9.0 -> 1.10.0 -> 1.11.0. 1. Once a versioned package has been released, the contents of that version @@ -78,6 +78,9 @@ changes. compatible bug fixes are introduced. A bug fix is defined as an internal change that fixes incorrect behavior. +1. Patch version MAY be omitted, in which cased it SHALL be considered +exactly equivalent to 0. + 1. Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards compatible functionality is introduced to the public API. It MUST be incremented if any public API functionality is marked as deprecated. It MAY be @@ -114,8 +117,9 @@ Precedence MUST be calculated by separating the version into major, minor, patch and pre-release identifiers in that order (Build metadata does not figure into precedence). Precedence is determined by the first difference when comparing each of these identifiers from left to right as follows: Major, minor, -and patch versions are always compared numerically. 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 +and patch versions are always compared numerically. An omitted patch number is +equivalent to 0. Example: 1.0.0 < 2.0.0 < +2.1 < 2.1.1. When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version. Example: 1.0.0-alpha < 1.0.0. Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by comparing each dot separated identifier from left to right ``` Just a thought!
Author
Owner

@ljharb commented on GitHub (Dec 9, 2021):

The lack of precision here (is it an intentional omission? is it a bug?) seems much worse than the mild inconvenience of having to type a dot and a zero occasionally.

<!-- gh-comment-id:990321899 --> @ljharb commented on GitHub (Dec 9, 2021): The lack of precision here (is it an intentional omission? is it a bug?) seems much worse than the mild inconvenience of having to type a dot and a zero occasionally.
Author
Owner

@JC3 commented on GitHub (Dec 11, 2021):

The lack of precision here (is it an intentional omission? is it a bug?) seems much worse than the mild inconvenience of having to type a dot and a zero occasionally.

Is wondering if something is an intentional omission vs. a bug a productive line of thinking to begin with? I mean, that could be asked about pretty much... everything, ever; not just version numbers. 😄

Still, I think I can make a more compelling argument (I worked way too hard on this, lol... TL;DR just skip to the table below):

Firstly, on the semver parsing / consumption side, here are some notable real-world examples of current behavior:

  • Node.js - NPM: NPM's node-semver module is responsible for parsing package.json and others, and behaves as follows:
    • "If a partial version is provided as the first version in the inclusive range, then the missing pieces are replaced with zeroes." (ref)
    • "If a partial version is provided as the second version in the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but nothing that would be greater than the provided tuple parts." (ref)
    • When parsing single versions, missing components are not allowed. (ref + ref)
    • Other notes and oddities:
      • "A leading "=" or "v" character is stripped off and ignored." (ref)
      • Note that partial versions are always accepted in ranges; "loose" mode only enables misplaced alpha chars as well as leading 0's in components (ref).
      • Provides some special operators ~ and ^ for comparisons.
  • .NET Core (C#, ASP, etc.)
    • Semver: A popular semver parsing library for .NET Core:
      • Provides strict and loose modes, but defaults to loose mode, where minor and patch are optional and replaced with zeroes. (ref)
    • semver.net: Another popular .NET Core implementation.
      • NPM style ranges and missing-component handling in ranges. (ref)
      • Like NPM, does not accept individual partial versions. (ref)
      • Other notes:
        • Provides strict and loose modes, but these do not affect partial version acceptance; they are more similar to NPM behavior. (ref)
    • Unity: All Unity-based applications are also covered by .NET Core.
    • There are a few other options (some notable, such as Nuget's parser). I've omitted them because this is a lot of work, but my assumption is they are as varied as everything else here.
    • Non-Core: The Azure DevOps SDK and Powershell SDK provide an implementation but information is sparse.
  • C++ - Neargye/semver: 1st Google result and 1st Conan result for C++ semver:
    • Patch component is optional (ref) and defaults to zero (ref).
  • C++ - easz/cpp-semver: 2nd Google result for C++ semver:
    • Default parser: Minor and patch are optional and default to zero. (ref)
    • PEGTL parser: Minor and patch are optional and default to zero. (ref)
    • Other notes:
      • Provides node-semver style ^ and ~ support in comparisons.
      • "Inspired" by semver specification, heh ... (ref)
  • C - h2non/semver.c: 1st Conan result for C semver:
    • All components (including major, heh) optional and default to 0. (ref, also, I tested it)
    • Other notes:
      • Provides node-semver style ^ and ~ support in comparisons. (ref)
  • Java / JSP - Semver4j: Seems to be the go-to choice.
    • In non-strict mode, minor and patch are optional (ref) and are treated as null rather than zeroes (ref).
    • In NPM mode, I presume missing minor and patch in ranges behave as described above. I have not verified this; but the readme implies it.
    • Other notes:
      • Runs in multiple modes with subtle differences in range and wildcard behavior.
      • In general there are a lot of options available for Java. I haven't gone through all of them. However, this doesn't bode well for consistency.
  • PHP - php-semver
    • Seems to behave like NPM, although not compatible with Semver 2.0.0. (ref)
    • In loose mode, all components are optional and replaced with zeroes. (ref)
    • Other notes:
      • I only skimmed this one because PHP and 😱.
      • Appears to support NPM style tildes but not carets. (ref)
  • PHP - composer/semver: Composer's implementation.
    • In individual versions, all components optional and default to 0. (ref)
    • In ranges, partial versions are handled identically to NPM. (ref)
    • Other notes:
      • Does not support numeric prerelease identifiers. (ref)
      • Supports NPM style tilde and caret ranges. (ref)
      • Supports wildcard ranges. (ref)
  • And almost certainly many others (especially iOS and MacOS native i.e. Objective-C, which I'm unfamiliar with).

Secondly, although less compellingly (but maybe still worth noting), on the semver production / software versioning side, I can't be as thorough as above, but just off the top of my head you've got:

  • Latest Linux kernel preview is 5.16-rc3
  • SSL is at 3.0
  • Latest NVIDIA drivers are 497.something
  • OpenGL is at 4.6
  • .NET went 4.7 -> 4.7.1 -> 4.7.2 -> 4.8 and adds that .0 in random contexts.
  • MacOS used 10.14 -> 10.15 -> 11 -> 12 in titles and UI, and proper semvers internally.

Anyways back to the parsers let's break that down:

Parser Optional Minor
(in versions)
Optional Patch
(in versions)
Default Value
(in versions)
Partial Versions
(in ranges)
Wildcards
(in ranges)
~ and ^
(in ranges)
NPM No No N/A NPM Yes Yes
Semver Yes* Yes* 0 Zeroes No No
semver.net No No N/A NPM Yes Yes
Neargye/semver No Yes 0 Zeroes No No
easz/cpp-semver Yes Yes 0 Zeroes Yes Yes
h2non/semver.c Yes Yes 0 Zeroes No Yes
Semver4j No* No* null NPM No*† No*†
php-semver No* No* null* or 0 ? Yes Only ~
composer/semver Yes Yes 0 NPM Yes Yes

* Feature is supported! The default value is indicated.
† Wildcards, tilde, and caret supported in NPM mode. Wildcards and tilde supported in Cocoapods mode.
‡ Not verified.

So what we've got here, essentially, is a standard that does not allow optional version components in a world that clearly wants optional version components. To me, the logical choice, then, is to put it in the spec, because then it's well-defined, whereas currently it's ambiguous and implementations end up with inconsistent support (it's purely coincidence that most of the ones above behave the same).

I mean, it's sort of a classic issue, right:

  • Postulate: An adequate specification ensures interoperability through consistent behavior.
  • Hypothesis: Semver 2.0.0 is an adequate specification.
  • Fact: The majority of implementors add support for version forms that are not defined in Semver 2.0.0.
  • Fact: On top of that, some of the support is inconsistent.
  • Therefore: Interoperability has not been achieved, due to inconsistent behaviors, and...
  • Therefore: The majority of implementors found that Semver does not fully cover their requirements.
  • Conclusion: Semver 2.0.0 has not ensured interoperability through consistent behavior.
  • Conclusion: Semver 2.0.0 is inadequate.

So it seems to me there is strong motivation to do all of these, beyond my initial proposal of just making the patch optional:

  • Make patch optional and specify that the default is 0 (except possibly as noted below).
  • Make minor optional and specify that the default is 0 (except possibly as noted below).
  • Expand the specification for comparisons to essentially match NPM, Cocoapods, Nuget, and the like:
    • Define ~ and ^.
    • Define wildcards (*, x, X).
    • Define ranges, in addition to simple comparisons: From NPM: If partial version is first component in inclusive range, the missing components are essentially treated as wildcards. Otherwise, they're essentially treated as 0's.
    • The justification for defining ranges in Semver, btw, as opposed to just letting them be implicitly defined from basic comparisons, is that since the default value for missing components changes depending on range-dependent context, then Semver can't adequately specify the default values for missing components without also specifying how that changes in a range.
  • Possibly (I'm not convinced on this one though) define that a leading v is allowed (or at least, should be accepted but should not be used in new applications). Many implementations strip this out, and many don't -- which is a hint that v support is desired and so a consistent definition would at least ensure that all implementations behave the same when encountering one. (Edit: see https://github.com/semver/semver/pull/584#issuecomment-991823928)

So... yeah. A bit more than my original motivation, but it was sort of the implicit reason behind my original suggestion. I do think Semver is rather underspecified in its current form, and that standardizing optional components increases precision in a certain way. I also think that NPM's behavior is fairly ubiquitous and proven, and therefore might serve as a decent basis for expanding Semver as far as ranges go.

<!-- gh-comment-id:991773883 --> @JC3 commented on GitHub (Dec 11, 2021): > The lack of precision here (is it an intentional omission? is it a bug?) seems much worse than the mild inconvenience of having to type a dot and a zero occasionally. Is wondering if something is an intentional omission vs. a bug a productive line of thinking to begin with? I mean, that could be asked about pretty much... everything, ever; not just version numbers. 😄 Still, I think I can make a more compelling argument (I worked way too hard on this, lol... TL;DR just skip to the table below): Firstly, on the semver parsing / consumption side, here are some notable real-world examples of current behavior: - Node.js - **[NPM][npm]**: NPM's node-semver module is responsible for parsing package.json and others, and behaves as follows: - _"If a partial version is provided as the first version in the inclusive range, then the missing pieces are replaced with zeroes."_ ([ref][npm1]) - _"If a partial version is provided as the second version in the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but nothing that would be greater than the provided tuple parts."_ ([ref][npm2]) - *When parsing single versions, missing components are not allowed.* ([ref][npm3] + [ref][npm3b]) - Other notes and oddities: - "A leading `"="` or `"v"` character is stripped off and ignored." ([ref][npmvstrip]) - Note that _partial versions are always accepted in ranges_; "loose" mode only enables misplaced alpha chars as well as leading 0's in components ([ref][npmre]). - Provides some special operators `~` and `^` for comparisons. - .NET Core (C#, ASP, etc.) - **[Semver][cs]**: A popular semver parsing library for .NET Core: - Provides strict and loose modes, but *defaults to loose mode, where minor and patch are optional and replaced with zeroes*. ([ref][cs1]) - **[semver.net][csb]**: Another popular .NET Core implementation. - *NPM style ranges and missing-component handling in ranges.* ([ref][csb1]) - Like NPM, *does not accept individual partial versions.* ([ref][csb2]) - Other notes: - Provides strict and loose modes, but these do not affect partial version acceptance; they are more similar to NPM behavior. ([ref][csb3]) - **Unity**: All Unity-based applications are also covered by .NET Core. - There are a few other options (some notable, such as **Nuget**'s parser). I've omitted them because this is a lot of work, but my assumption is they are as varied as everything else here. - Non-Core: The [Azure DevOps SDK][csaz] and [Powershell SDK][csps] provide an implementation but information is sparse. - C++ - **[Neargye/semver][cppa]**: 1st Google result and 1st Conan result for C++ semver: - *Patch component is optional* ([ref][cppa1]) and *defaults to zero* ([ref][cppa2]). - C++ - **[easz/cpp-semver][cppb]**: 2nd Google result for C++ semver: - Default parser: *Minor and patch are optional and default to zero.* ([ref][cppb1]) - PEGTL parser: *Minor and patch are optional and default to zero.* ([ref][cppb2]) - Other notes: - Provides node-semver style `^` and `~` support in comparisons. - "Inspired" by semver specification, heh ... ([ref][cppblol]) - C - **[h2non/semver.c][c]**: 1st Conan result for C semver: - *All components ([including major][c29], heh) optional and default to 0.* ([ref][c1], also, I tested it) - Other notes: - Provides node-semver style `^` and `~` support in comparisons. ([ref][cops]) - Java / JSP - **[Semver4j][java]**: Seems to be the go-to choice. - In non-strict mode, *minor and patch are optional* ([ref][java1]) and *are treated as null rather than zeroes* ([ref][java3]). - In NPM mode, I presume missing minor and patch in ranges behave as described above. I have not verified this; but the [readme implies it][java2]. - Other notes: - - Runs in multiple modes with subtle differences in range and wildcard behavior. - In general there are [a lot of options available][javaopts] for Java. I haven't gone through all of them. However, this doesn't bode well for consistency. - PHP - **[php-semver][phpa]** - Seems to behave like NPM, although not compatible with Semver 2.0.0. ([ref][phpa1]) - In loose mode, *all components are optional and replaced with zeroes.* ([ref][phpa3]) - Other notes: - I only skimmed this one because PHP and 😱. - Appears to support NPM style tildes but not carets. ([ref][phpa2]) - PHP - **[composer/semver][phpb]**: Composer's implementation. - *In individual versions, all components optional and default to 0.* ([ref][phpb1]) - *In ranges, partial versions are handled identically to NPM.* ([ref][phpb2]) - Other notes: - Does not support numeric prerelease identifiers. ([ref][phpbpre]) - Supports NPM style tilde and caret ranges. ([ref][phpbtc]) - Supports wildcard ranges. ([ref][phpbwild]) - And almost certainly many others (especially iOS and MacOS native i.e. Objective-C, which I'm unfamiliar with). Secondly, although less compellingly (but maybe still worth noting), on the semver production / software versioning side, I can't be as thorough as above, but just off the top of my head you've got: - Latest Linux kernel preview is 5.16-rc3 - SSL is at 3.0 - Latest NVIDIA drivers are 497.something - OpenGL is at 4.6 - .NET went 4.7 -> 4.7.1 -> 4.7.2 -> 4.8 and adds that .0 in random contexts. - MacOS used 10.14 -> 10.15 -> 11 -> 12 in titles and UI, and proper semvers internally. Anyways back to the parsers let's break that down: |Parser|Optional Minor<br>(in versions)|Optional Patch<br>(in versions)|Default Value<br>(in versions)|Partial Versions<br>(in ranges)|Wildcards<br>(in ranges)|`~` and `^`<br>(in ranges) |-|-|-|-|-|-|-| | [NPM][npm] | No | No | N/A | NPM | Yes | Yes | | [Semver][cs] | Yes<sup>\*</sup> | Yes<sup>\*</sup> | 0 | [Zeroes][cs2] | No | No | | [semver.net][csb] | No | No | N/A | NPM | Yes | Yes | | [Neargye/semver][cppa] | No | Yes | 0 | Zeroes | No | No | | [easz/cpp-semver][cppb] | Yes | Yes | 0 | [Zeroes][cppb4]<sup>‡</sup> | [Yes][cppb3] | Yes | | [h2non/semver.c][c] | Yes | Yes | 0 | Zeroes | No | Yes | | [Semver4j][java] | No<sup>\*</sup> | No<sup>\*</sup> | null | NPM<sup>‡</sup> | No<sup>\*†</sup> | No<sup>\*†</sup> | | [php-semver][phpa] | No<sup>*</sup> | No<sup>*</sup> | null<sup>*</sup> or 0 | ?<sup>‡</sup> | Yes | Only `~` | | [composer/semver][phpb] | Yes | Yes | 0 | NPM | Yes | Yes | ***<sup>\* Feature is supported! The default value is indicated.</sup>*** *<sup>† Wildcards, tilde, and caret supported in NPM mode. Wildcards and tilde supported in Cocoapods mode.</sup>* *<sup>‡ Not verified.</sup>* So what we've got here, essentially, is a standard that does not allow optional version components in a world that clearly wants optional version components. To me, the logical choice, then, is to put it in the spec, because then it's well-defined, whereas currently it's ambiguous and implementations end up with inconsistent support (it's purely coincidence that most of the ones above behave the same). I mean, it's sort of a classic issue, right: - Postulate: An adequate specification ensures interoperability through consistent behavior. - Hypothesis: Semver 2.0.0 is an adequate specification. - Fact: The majority of implementors add support for version forms that are not defined in Semver 2.0.0. - Fact: On top of that, some of the support is inconsistent. - Therefore: Interoperability has not been achieved, due to inconsistent behaviors, and... - Therefore: The majority of implementors found that Semver does not fully cover their requirements. - Conclusion: Semver 2.0.0 has not ensured interoperability through consistent behavior. - Conclusion: Semver 2.0.0 is inadequate. So it seems to me there is strong motivation to do all of these, *beyond* my initial proposal of just making the patch optional: - Make patch optional *and* specify that the default is 0 (except possibly as noted below). - Make minor optional *and* specify that the default is 0 (except possibly as noted below). - Expand the specification for comparisons to essentially match NPM, Cocoapods, Nuget, and the like: - Define `~` and `^`. - Define wildcards (`*`, `x`, `X`). - Define ranges, in addition to simple comparisons: From NPM: If partial version is first component in inclusive range, the missing components are essentially treated as wildcards. Otherwise, they're essentially treated as 0's. - The justification for defining ranges in Semver, btw, as opposed to just letting them be implicitly defined from basic comparisons, is that since the default value for missing components changes depending on range-dependent context, then Semver can't adequately specify the default values for missing components without also specifying how that changes in a range. - ~~*Possibly* (I'm not convinced on this one though) define that a leading `v` is allowed (or at least, should be accepted but should not be used in new applications). Many implementations strip this out, and many don't -- which is a hint that `v` support is *desired* and so a consistent definition would at least ensure that all implementations behave the same when encountering one.~~ (Edit: see https://github.com/semver/semver/pull/584#issuecomment-991823928) So... yeah. A bit more than my original motivation, but it was sort of the implicit reason behind my original suggestion. I do think Semver is rather underspecified in its current form, and that standardizing optional components *increases* precision in a certain way. I also think that NPM's behavior is fairly ubiquitous and proven, and therefore might serve as a decent basis for expanding Semver as far as ranges go. [npm]:https://github.com/npm/node-semver [cs]:https://github.com/maxhauser/semver [csb]:https://github.com/adamreeve/semver.net [csaz]:https://docs.microsoft.com/en-us/dotnet/api/microsoft.teamfoundation.dashboards.webapi.semanticversion?view=azure-devops-dotnet [csps]:https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.semanticversion?view=powershellsdk-7.0.0 [cppa]:https://github.com/Neargye/semver [cppb]:https://github.com/easz/cpp-semver [c]:https://github.com/h2non/semver.c [java]:https://github.com/vdurmont/semver4j [phpa]:https://github.com/vierbergenlars/php-semver [phpb]:https://github.com/composer/semver [npm1]:https://github.com/npm/node-semver/blob/cb1ca1d5480a6c07c12ac31ba5f2071ed530c4ed/README.md?plain=1#L249-L252 [npm2]:https://github.com/npm/node-semver/blob/cb1ca1d5480a6c07c12ac31ba5f2071ed530c4ed/README.md?plain=1#L254-L260 [npm3]:https://github.com/npm/node-semver/blob/cb1ca1d5480a6c07c12ac31ba5f2071ed530c4ed/classes/semver.js#L35-L39 [npm3b]:https://github.com/npm/node-semver/blob/cb1ca1d5480a6c07c12ac31ba5f2071ed530c4ed/internal/re.js#L37-L43 [cs1]:https://github.com/maxhauser/semver/blob/c3b798d240af94e3348bd3e7c53a825bc6a9dff2/Semver/SemVersion.cs#L97-L181 [cs2]:https://github.com/maxhauser/semver/blob/c3b798d240af94e3348bd3e7c53a825bc6a9dff2/Semver/SemVersion.cs#L372-L387 [csb1]:https://github.com/adamreeve/semver.net/tree/713d3b79a68ed9368ec77190c590776650ca059c#readme [csb2]:https://github.com/adamreeve/semver.net/blob/713d3b79a68ed9368ec77190c590776650ca059c/src/SemanticVersioning/Version.cs#L94-L98 [csb3]:https://github.com/adamreeve/semver.net/blob/713d3b79a68ed9368ec77190c590776650ca059c/src/SemanticVersioning/Version.cs#L50-L86 [cppa1]:https://github.com/Neargye/semver/blob/c5d8a0f9ab0f6109ea38c6d73ca215940c2f985a/include/semver.hpp#L315-L318 [cppa2]:https://github.com/Neargye/semver/blob/c5d8a0f9ab0f6109ea38c6d73ca215940c2f985a/include/semver.hpp#L275 [cppb1]:https://github.com/easz/cpp-semver/blob/7b9141d99044e4d363eb3b0a81cfb1546a33f9dd/include/parser/parser.hpp#L75-L137 [cppb2]:https://github.com/easz/cpp-semver/blob/7b9141d99044e4d363eb3b0a81cfb1546a33f9dd/include/parser/peg.hpp#L56-L67 [cppb3]:https://github.com/easz/cpp-semver/blob/7b9141d99044e4d363eb3b0a81cfb1546a33f9dd/include/parser/parser.hpp#L36-L45 [cppb4]:https://github.com/easz/cpp-semver/blob/6230af03a439e5461ad293ecb82a3bc85184a6cf/include/cpp-semver.hpp#L30-L357 [c1]:https://github.com/h2non/semver.c/blob/bd1db234a68f305ed10268bd023df1ad672061d7/semver.c#L178-L201 [java1]:https://github.com/vdurmont/semver4j/blob/7653e418d610ffcd2811bcb55fd72d00d420950b/README.md?plain=1#L31-L58 [java2]:https://github.com/vdurmont/semver4j/blob/7653e418d610ffcd2811bcb55fd72d00d420950b/README.md?plain=1#L134 [java3]:https://github.com/vdurmont/semver4j/blob/7653e418d610ffcd2811bcb55fd72d00d420950b/src/main/java/com/vdurmont/semver4j/Semver.java#L40-L94 [phpa1]:https://github.com/vierbergenlars/php-semver/tree/2e8d58bf7736b5b8cd8fb1b240fa329f287eae33#readme [phpa2]:https://github.com/vierbergenlars/php-semver/blob/2e8d58bf7736b5b8cd8fb1b240fa329f287eae33/README.md?plain=1#L80-L89 [phpa3]:https://github.com/vierbergenlars/php-semver/blob/2e8d58bf7736b5b8cd8fb1b240fa329f287eae33/src/vierbergenlars/SemVer/version.php#L25 [phpb1]:https://github.com/composer/semver/blob/83e511e247de329283478496f7a1e114c9517506/src/VersionParser.php#L137-L141 [phpb2]:https://github.com/composer/semver/blob/83e511e247de329283478496f7a1e114c9517506/src/VersionParser.php#L330-L520 [npmvstrip]:https://github.com/npm/node-semver/blob/cb1ca1d5480a6c07c12ac31ba5f2071ed530c4ed/README.md?plain=1#L140 [npmre]:https://github.com/npm/node-semver/blob/cb1ca1d5480a6c07c12ac31ba5f2071ed530c4ed/internal/re.js [cppblol]:https://github.com/easz/cpp-semver/blob/7b9141d99044e4d363eb3b0a81cfb1546a33f9dd/README.md?plain=1#L7 [cops]:https://github.com/h2non/semver.c/blob/bd1db234a68f305ed10268bd023df1ad672061d7/README.md?plain=1#L142-L172 [c29]:https://github.com/h2non/semver.c/issues/29 [javaopts]:https://www.baeldung.com/java-comparing-versions [phpbpre]:https://github.com/composer/semver/blob/83e511e247de329283478496f7a1e114c9517506/src/VersionParser.php#L32 [phpbtc]:https://github.com/composer/semver/blob/83e511e247de329283478496f7a1e114c9517506/src/VersionParser.php#L332-L415 [phpbwild]:https://github.com/composer/semver/blob/83e511e247de329283478496f7a1e114c9517506/src/VersionParser.php#L417-L441
Author
Owner

@ljharb commented on GitHub (Dec 11, 2021):

Yes, that question should be asked about everything, everywhere, and it's why conveying intention is so important - it's also why omitting curly braces caused a massive SSL bug, for example.

For npm, omitting the patch is already a range, not a version, and this distinction is already coming to the semver spec (see #584) via npm's creator, so allowing 1.2 to be a version is already basically a nonstarter.

<!-- gh-comment-id:991790617 --> @ljharb commented on GitHub (Dec 11, 2021): Yes, that question should be asked about everything, everywhere, and it's why conveying intention is so important - it's also why omitting curly braces caused a massive SSL bug, for example. For npm, omitting the patch is already a _range_, not a version, and this distinction is already coming to the semver spec (see #584) via npm's creator, so allowing `1.2` to be a version is already basically a nonstarter.
Author
Owner

@JC3 commented on GitHub (Dec 11, 2021):

so allowing 1.2 to be a version is already basically a nonstarter.

Many (if not most) parsers will treat it as a version. This has and always will be the case. So I think it is a mistake to leave implementors in a position where the details of how partial versions are parsed (and they will be parsing them) is up to the implementor. I think we would want to have a specification that standardizes common use cases; that's what specifications are for, after all.

For npm, omitting the patch is already a range, not a version, and this distinction is already coming to the semver spec (see #584) via npm's creator,

As I mentioned, I definitely think it is a good idea to expand Semver with more details about ranges. However, I think the change that you describe (assuming that's the full description) would be a mistake, as well. It would make sense if Semver's intent is to limit itself to NPM. In reality, since so many parsers do, and will continue to, allow partial versions, adding that distinction to the spec will drive all partial-version-supporting implementations even further from Semver.

But I think it would be better overall if changes to Semver increased adoption, rather than decreased the applicability of the spec.

Like, I think Semver is a good spec it just needs a couple extra things to make it globally useful -- the fact that so many parsers choose to not strictly adhere to Semver is absolutely a sign that something's missing, I think.

Anyways, I think that's all I got!

<!-- gh-comment-id:991806583 --> @JC3 commented on GitHub (Dec 11, 2021): > so allowing `1.2` to be a version is already basically a nonstarter. Many (if not most) parsers will treat it as a version. This has and always will be the case. So I think it is a mistake to leave implementors in a position where the details of how partial versions are parsed (and they _will_ be parsing them) is up to the implementor. I think we would want to have a specification that standardizes common use cases; that's what specifications are for, after all. > For npm, omitting the patch is already a _range_, not a version, and this distinction is already coming to the semver spec (see #584) via npm's creator, As I mentioned, I definitely think it is a good idea to expand Semver with more details about ranges. However, I think the change that you describe (assuming that's the full description) would be a mistake, as well. It would make sense if Semver's intent is to limit itself to NPM. In reality, since so many parsers do, and will continue to, allow partial versions, adding that distinction to the spec will drive all partial-version-supporting implementations even further from Semver. But I think it would be better overall if changes to Semver increased adoption, rather than decreased the applicability of the spec. Like, I think Semver is a good spec it just needs a couple extra things to make it globally useful -- the fact that so many parsers choose to not strictly adhere to Semver is absolutely a sign that something's missing, I think. Anyways, I think that's all I got!
Author
Owner

@jwdonahue commented on GitHub (Dec 12, 2021):

@JC3 I like your approach to reasoning about these things, but I think you should get your facts straight:

Fact: The majority of implementors add support for version forms that are not defined in Semver 2.0.0.

Not true. Most implementers added support for SemVer, in many cases, long after they supported all the non-SemVer behaviors. Even some of the SemVer early adopters, did so grudgingly, and most of them stopped at 1.0.0 or even 1.0.0-beta.

Conclusion: Semver 2.0.0 is inadequate.

Most definitely. This is always what happens when we try to convert common denominators into a specification. It's always good to delineate the current best practices, particularly when the full range is described (not present in the current context), but when those commonalities are barely significant, as was the case here, it's really hard to drive consensus from it. But SemVer was proposed in addition to those common practices. It was a suggestion that we could all move towards something recognizable and better than the norm, that wouldn't break our existing tools, and that has had the effect of spreading adoption.

What we have now, is a situation where the dominant package tool owners, control the spec (fairly normal), and seem loathe to issue a v3 (also normal). As for whether SemVer should define a range specification for versions, I think it is doomed to fail. Not because there isn't a kernel of commonality out there, but because it will either have the effect of stifling innovation or drive all innovators away from the spec. Since few implementers barely adequately support the full 2.0.0 spec, if at all, I think the later is the most likely outcome.

There is a third possibility that I think is highly unlikely. The stakeholders might decide to develop reference implementations in each of their respective languages that can actually process version and range data interchangeably, without resorting to the lowest common denominator. I would hope that such an effort would include a standard dependency graph notation. I am pretty sure the talent and capital is there in each of their respective organizations, but do they have the will to solve all of the dependency hell problems in an open source environment?

<!-- gh-comment-id:991814828 --> @jwdonahue commented on GitHub (Dec 12, 2021): @JC3 I like your approach to reasoning about these things, but I think you should get your facts straight: > Fact: The majority of implementors add support for version forms that are not defined in Semver 2.0.0. Not true. Most implementers added support for SemVer, in many cases, long after they supported all the non-SemVer behaviors. Even some of the SemVer early adopters, did so grudgingly, and most of them stopped at 1.0.0 or even 1.0.0-beta. > Conclusion: Semver 2.0.0 is inadequate. Most definitely. This is always what happens when we try to convert common denominators into a specification. It's always good to delineate the current best practices, particularly when the full range is described (not present in the current context), but when those commonalities are barely significant, as was the case here, it's really hard to drive consensus from it. But SemVer was proposed _in addition to_ those common practices. It was a suggestion that we could all move towards something recognizable and better than the norm, that wouldn't break our existing tools, and that has had the effect of spreading adoption. What we have now, is a situation where the dominant package tool owners, control the spec (fairly normal), and seem loathe to issue a v3 (also normal). As for whether SemVer should define a range specification for versions, I think it is doomed to fail. Not because there isn't a kernel of commonality out there, but because it will either have the effect of stifling innovation or drive all innovators away from the spec. Since few implementers barely adequately support the full 2.0.0 spec, if at all, I think the later is the most likely outcome. There is a third possibility that I think is highly unlikely. The stakeholders might decide to develop reference implementations in each of their respective languages that can actually process version and range data interchangeably, without resorting to the lowest common denominator. I would hope that such an effort would include a standard dependency graph notation. I am pretty sure the talent and capital is there in each of their respective organizations, but do they have the will to solve all of the dependency hell problems in an open source environment?
Author
Owner

@jwdonahue commented on GitHub (Dec 12, 2021):

@JC3

I think the patch number should be specified as optional, and assumed to be 0 if not present.

Back when the original spec was written, the bits that had the widest distribution in the world (on the order of billions), used four version fields, and still do. Most version strings however, were one or two fields publicly and just a build number privately. There were also a lot of Major.Minor.Patch version strings out there, where those field names were taken literally, with no other semantics attached.

I have argued in the past against expanding the SemVer spec to include one or two fields, and specifying some default behavior for the missing fields, on the grounds that they would be ambiguous. After years of following every thread on this site, I have come to the conclusion that we must solve the version ambiguity problem. Current tools, including those that I have written, use a syntactic filter to include strings that should be treated as SemVer. Still, even a version triple is ambiguous! You don't ever really know what semantics were used to generate any particular version string. Even if SemVer was applied, you don't know which version of the spec is adhered to.

I have also argued that while we really need a SemVer 3.0.0, we MUST solve the disambiguation problem first. Allowing short-hand version notation in any formal format (i.e.; not a discussion forum) will only add to the ambiguity problem. It's one thing to reference 'SemVer 3' and quite another to refer to 'Px 3', in the former context, the semantics are obvious, but not in the later.

I am more ambivalent in the case of range specifications. '[3, 4)' seems clear to me that no matter the syntax or semantics of the versions, anything greater or equal to major 3 and less than major 4 is acceptable. That range could just as easily be floats, doubles, decimal or integers and still convey the same meaning.

<!-- gh-comment-id:991820144 --> @jwdonahue commented on GitHub (Dec 12, 2021): @JC3 > I think the patch number should be specified as optional, and assumed to be 0 if not present. Back when the original spec was written, the bits that had the widest distribution in the world (on the order of billions), used four version fields, and still do. Most version strings however, were one or two fields publicly and just a build number privately. There were also a lot of Major.Minor.Patch version strings out there, where those field names were taken literally, with no other semantics attached. I have argued in the past **against** expanding the SemVer spec to include one or two fields, and specifying some default behavior for the missing fields, on the grounds that they would be ambiguous. After years of following every thread on this site, I have come to the conclusion that we must solve the version ambiguity problem. Current tools, including those that I have written, use a syntactic filter to include strings that should be treated as SemVer. Still, even a version triple is ambiguous! You don't ever really know what semantics were used to generate any particular version string. Even if SemVer was applied, you don't know which version of the spec is adhered to. I have also argued that while we really need a SemVer 3.0.0, we MUST solve the disambiguation problem first. Allowing short-hand version notation in any formal format (i.e.; not a discussion forum) will only add to the ambiguity problem. It's one thing to reference 'SemVer 3' and quite another to refer to 'Px 3', in the former context, the semantics are obvious, but not in the later. I am more ambivalent in the case of range specifications. '[3, 4)' seems clear to me that no matter the syntax or semantics of the versions, anything greater or equal to major 3 and less than major 4 is acceptable. That range could just as easily be floats, doubles, decimal or integers and still convey the same meaning.
Author
Owner

@wtarreau commented on GitHub (Jul 26, 2025):

For me it's the opposite: I find that "3.0.0-dev1" is not clearly inferior to "3.0.0" for a human (and even in directory listings). And the patch will always remain zero during development. On the contrary, "3.0-dev1" makes it very clear to me that this is a development step of what will become 3.0, and that once you have the second ".", it indicates a stable and maintained version (e.g. "3.0.0"). And it does have the benefit of respecting lexicographic sorting, i.e. "3.0-dev1" < "3.0.0" in ASCII.

<!-- gh-comment-id:3121549453 --> @wtarreau commented on GitHub (Jul 26, 2025): For me it's the opposite: I find that "3.0.0-dev1" is not clearly inferior to "3.0.0" for a human (and even in directory listings). And the patch will always remain zero during development. On the contrary, "3.0-dev1" makes it very clear to me that this is a development step of what will become 3.0, and that once you have the second ".", it indicates a stable and maintained version (e.g. "3.0.0"). And it *does* have the benefit of respecting lexicographic sorting, i.e. "3.0-dev1" < "3.0.0" in ASCII.
Author
Owner

@jwdonahue commented on GitHub (Jul 26, 2025):

@wtarreau wrote:

And the patch will always remain zero during development.

I have never worked for an organization, nor owned one, where that would be an acceptable practice. My professional career in software development, started in 1987.

<!-- gh-comment-id:3121576141 --> @jwdonahue commented on GitHub (Jul 26, 2025): @wtarreau wrote: > And the patch will always remain zero during development. I have never worked for an organization, nor owned one, where that would be an acceptable practice. My professional career in software development, started in 1987.
Author
Owner

@wtarreau commented on GitHub (Jul 26, 2025):

What do you mean ? That you're incrementing the 3rd digit during development ? Development nowadays is just tags emitted on a tree at a given date., so it only contains minor updates (including some fixes) but doesn't deserve being incremented. And on the contrary I've yet to see any single project which does increment the patch number during development!

<!-- gh-comment-id:3121579181 --> @wtarreau commented on GitHub (Jul 26, 2025): What do you mean ? That you're incrementing the 3rd digit during development ? Development nowadays is just tags emitted on a tree at a given date., so it only contains minor updates (including some fixes) but doesn't deserve being incremented. And on the contrary I've yet to see any single project which does increment the patch number during development!
Author
Owner

@jwdonahue commented on GitHub (Jul 27, 2025):

Ah, I see our confusion. You probably have one or more files in your repo, that have version strings in them, but you're not publishing the updates, so they never get bumped. Pretty common. Not the best practice, but it's workable.

On my systems, versions strings in source code are held constant (0.0.0-a.Dev). Product versions are tracked in databases, and updated for each build, such that when a dev builds they get bits with something like 1.1.32-a.Dev+DevName.hash.whatever. Publish to test gets 1.1.32-a.Test+BuildMachine.hash.whatever, and then when a choice is made to dogfood or publish to early adopters, you change the tags, etc. Usually the build systems do apply non-dev version tags to the repo, when a proper release is published for milestone or whatever, but we do not trust repo history for purposes of provenance. It's just too mutable.


The practice ensures that a binary will not have a valid version string, if it was not built by sanctioned processes/machines. I got bit once, back in the 90's, when a product manager, got a hold of a dev's test ROM, ran a production run, and shipped product to customers. The QA inspector read a valid version string, and initialed the "inspected by" tag.

<!-- gh-comment-id:3124664608 --> @jwdonahue commented on GitHub (Jul 27, 2025): Ah, I see our confusion. You probably have one or more files in your repo, that have version strings in them, but you're not publishing the updates, so they never get bumped. Pretty common. Not the best practice, but it's workable. On my systems, versions strings in source code are held constant (0.0.0-a.Dev). Product versions are tracked in databases, and updated for each build, such that when a dev builds they get bits with something like 1.1.32-a.Dev+DevName.hash.whatever. Publish to test gets 1.1.32-a.Test+BuildMachine.hash.whatever, and then when a choice is made to dogfood or publish to early adopters, you change the tags, etc. Usually the build systems do apply non-dev version tags to the repo, when a proper release is published for milestone or whatever, but we do not trust repo history for purposes of provenance. It's just too mutable. ----- The practice ensures that a binary will not have a valid version string, if it was not built by sanctioned processes/machines. I got bit once, back in the 90's, when a product manager, got a hold of a dev's test ROM, ran a production run, and shipped product to customers. The QA inspector read a valid version string, and initialed the "inspected by" tag.
Author
Owner

@jwdonahue commented on GitHub (Jul 27, 2025):

The rule is simple. No two binaries, with differing source content hash, tool chain versions, or builders, SHALL ever have the same version string, no matter where, when or who produced them.

Example (simplified), global version history for product A:

1.0.0+ReleaseBuilder.hash0 // Release to customers.
1.1.0-a.Dev+JwD.hash1 // built by me for bench testing/debugging a new feature.
1.1.1-a.Dev+JwD.hash2 // built by me for bench testing a fix.
1.0.1-a.Dev+OtherDev.hash3 // built by OtherDev for bench testing a hot fix.
1.0.1-a.Test+CDBuild.hash3 // built on a build box for distribution to QA.
1.0.1+ReleaseBuilder.hash3 // Release to customers.
1.1.2-a.Dev+JwD.hash4 // built by me to test merge of OtherDev's patch with my code.
1.1.2-a.Test+CDBuild.hash4 // built on build box for distribution to QA.
1.1.2+ReleaseBuilder.hash4 // Release to customers.

I have also worked on teams where build numbers are used for version strings during development, with content hash metatags and the release processes would generate appropriate version M.m.p.b or similar things, but the rule is the same.

We also tend to track versions of tooling as well. I've had build lines for up to a dozen different tool version sets, producing bits for test automation. In some cases, the build tools or manifests specifying the build tools to be used, are checked into the repo, so that becomes part of hash.

The rule can be somewhat relaxed where tool chains are deterministic, but those are very hard to come by (embedded user, machine, timestamp, signing strings, etc).

<!-- gh-comment-id:3124690394 --> @jwdonahue commented on GitHub (Jul 27, 2025): The rule is simple. No two binaries, with differing source content hash, tool chain versions, or builders, SHALL ever have the same version string, no matter where, when or who produced them. Example (simplified), global version history for product A: 1.0.0+ReleaseBuilder.hash0 // Release to customers. 1.1.0-a.Dev+JwD.hash1 // built by me for bench testing/debugging a new feature. 1.1.1-a.Dev+JwD.hash2 // built by me for bench testing a fix. 1.0.1-a.Dev+OtherDev.hash3 // built by OtherDev for bench testing a hot fix. 1.0.1-a.Test+CDBuild.hash3 // built on a build box for distribution to QA. 1.0.1+ReleaseBuilder.hash3 // Release to customers. 1.1.2-a.Dev+JwD.hash4 // built by me to test merge of OtherDev's patch with my code. 1.1.2-a.Test+CDBuild.hash4 // built on build box for distribution to QA. 1.1.2+ReleaseBuilder.hash4 // Release to customers. I have also worked on teams where build numbers are used for version strings during development, with content hash metatags and the release processes would generate appropriate version M.m.p.b or similar things, but the rule is the same. We also tend to track versions of tooling as well. I've had build lines for up to a dozen different tool version sets, producing bits for test automation. In some cases, the build tools or manifests specifying the build tools to be used, are checked into the repo, so that becomes part of hash. The rule can be somewhat relaxed where tool chains are deterministic, but those are very hard to come by (embedded user, machine, timestamp, signing strings, etc).
Author
Owner

@nngo commented on GitHub (Sep 11, 2025):

I also see value in making the patch version optional. A major.minor version can still be a semantic version following the API breakage policy without the patch version so why make it a requirement in the spec? And as previously stated many libraries/apps start with a 1.0 version and only add a patch version when needed for a bug fix release.

<!-- gh-comment-id:3279872374 --> @nngo commented on GitHub (Sep 11, 2025): I also see value in making the patch version optional. A major.minor version can still be a semantic version following the API breakage policy without the patch version so why make it a requirement in the spec? And as previously stated many libraries/apps start with a 1.0 version and only add a patch version when needed for a bug fix release.
Author
Owner

@ljharb commented on GitHub (Sep 11, 2025):

Explicit > implicit, see https://github.com/semver/semver/issues/782#issuecomment-990321899

<!-- gh-comment-id:3282592588 --> @ljharb commented on GitHub (Sep 11, 2025): Explicit > implicit, see https://github.com/semver/semver/issues/782#issuecomment-990321899
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/semver#7489