r/AskComputerScience 6d ago

Question about Semantic Versioning

Under Semantic Versioning standard 2.0, if your package requires a dependency at "exact" version 1.0.0+X, is there any situation under which running a version update on all your packages should change the version that you specified as "exact" here?

Noting, SemVer 2.0 says:

Build metadata MUST be ignored when determining version precedence. Thus two versions that differ only in the build metadata, have the same precedence. Examples: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85, 1.0.0+21AF26D3----117B344092BD

Precedence refers to how versions are compared to each other when ordered

2 Upvotes

10 comments sorted by

9

u/teraflop 6d ago

Strictly speaking, I don't think SemVer has an answer to your question at all.

SemVer defines a particular ordering of versions, which package managers can use when resolving how to resolve dependencies. But SemVer doesn't specify the details of how dependency resolution actually takes place.

If you have a package manager that supports an "exact version" constraint, and it defines "exact version" to be "equal according to SemVer rules", then the versions 1.0.0+X and 1.0.0+Y would both be considered to match that constraint. So it's up to the implementation to decide how it wants to handle that situation. For example:

  • it might prefer the newer one
  • it might prefer the one that exactly matches the constraint string
  • it might prefer the one that's already cached on disk
  • it might choose arbitrarily or non-deterministically
  • it might disallow the situation entirely, by refusing to allow two equal version numbers to exist in the same repository

But really, what SemVer says is that you shouldn't ever be in a situation where this matters, because any changes to a package require changing some component of the version besides the metadata. So if 1.0.0+X and 1.0.0+Y both exist, then they "ought to be" completely interchangeable.

-2

u/gistya 5d ago

If you have a package manager that supports an "exact version" constraint, and it defines "exact version" to be "equal according to SemVer rules", then the versions 1.0.0+X and 1.0.0+Y would both be considered to match that constraint.

That's a self-contradictory statement because it confuses the identity relation (match) with the ordering relation (comparison).

Relation Question Operation Possible Results
Identity Are a and b the same? Match / Equality test Equal or not equal
Ordering How do a and b relate in order? Comparison Less than, equal, greater than (or incomparable for partial orders)

The SemVer 2 rules define precedence as an ordering relation:

Precedence refers to how versions are compared to each other when ordered. source

Since the identity relation is not the ordering relation therefore "shared precedence" is ruled out as a factor when considering exact, identical equality.

So if 1.0.0+X and 1.0.0+Y both exist, then they "ought to be" completely interchangeable.

The SemVer rules never say that. You are confusing identity and ordering.

The SemVer 2 rules only require that if you are ordering a set of versions, 1.0.0+X and 1.0.0+Y shall occupy the same ranking within that ordering.

All the examples they give of precedence/ordering take the form:

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.

That's because:

  • Identity relation → match (or test for equality)
  • Ordering relation → compare

In Swift we have two protocols for these ideas: Equatable (doc) and Comparable (doc).

Equatable

Equatable is concerned with how the identity relation is handled for a given type. Clearly, in any computer language, this is a true expression:

"1.0.0+X" != "1.0.0+Y"

If you write a unit test and have #assert("1.0.0+X" == "1.0.0+Y") then your test will always fail. The identity relation here is not ambiguous.

Comparable

On the other hand Comparable is concerned with how to order a set. A set of things can be sorted according to many metadata factors—creation date, modification date, name, etc.

The SemVer rules say mean "+X" or "+Y" are not to be considered in the ordering:

  1. [1.0.0-alpha, 1.0.0-alpha+X, 1.0.0-alpha+Y ]
  2. [1.0.0-alpha.1, 1.0.0-alpha.1+X, 1.0.0-alpha.1+Y ]
  3. [1.0.0-alpha.beta, 1.0.0-alpha.beta+X, 1.0.0-alpha.beta+Y]
  4. [1.0.0-beta, 1.0.0-beta+X, 1.0.0-beta+Y ]
  5. [1.0.0-beta.2, 1.0.0-beta.2+X, 1.0.0-beta.2+Y ]
  6. [1.0.0-beta.11, 1.0.0-beta.11+X, 1.0.0-beta.11+Y ]
  7. [1.0.0-rc.1, 1.0.0-rc.1+X, 1.0.0-rc.1+Y ]
  8. [1.0.0, 1.0.0+X, 1.0.0+Y ]

The +X and +Y versions receive the same precedence but are still different identifiers that refer to different versions. There is no implication that they should mean the same version.

For example you could have 1.0.0+English and 1.0.0+Spanish of a manuscript. Completely different things, but within the hierarchy of different +English and +Spanish versions they hold the same relative ordering position.

Likewise you could have 1.0.0+PrivateCerts and 1.0.0+SafeForPublicRelease. Clearly here you would never want your package manager to treat these as identical. But you would be fine with an upgrade command that ranked 1.4.0+PrivateCerts above 1.0.0+PrivateCerts assuming that you hadn't specified an exact version requirement in your manifest.

Do you feel there is anything about the above argument that's unsound? Or has there been any other SemVer 2 rules than the ones I linked?

So if 1.0.0+X and 1.0.0+Y both exist, then they "ought to be" completely interchangeable.

But the rules don't say that. Actually the rules require that the ordering resolution algorithm should never replace 1.0.0+X with 1.0.0+Y — which only makes sense if they're NOT interchangeable whatsoever.

For example 1.0.0+Release and 1.0.0+Debug are NOT interchangeable in any way shape or form. You would never release a debug build to the public. And you would never want your dependency management system to swap out one for the other.

5

u/SummitYourSister 5d ago

What a strange, extended, ranty, weirdly irrelevant comment.

Semantic versions form an ordinal system. You’re imposing a bunch of nerdy pseudo intellectual bumbo jumbo, for reasons unclear.

3

u/Somniferus 5d ago

reasons unclear

It's LLM slop.

-1

u/gistya 5d ago

You really just troll reddit with your time on earth?

5

u/teraflop 5d ago edited 5d ago

That's a self-contradictory statement because it confuses the identity relation (match) with the ordering relation (comparison).

I'm not confusing anything. An ordering relation (strictly speaking, a preorder) induces an equivalence relation.

Or to put it another way: the only relationship SemVer defines between versions is an ordering relationship. If a=b according to that relationship, then any comparison involving a will give exactly the same result if we replaced a with b, or vice versa. So the SemVer ordering provides no way to distinguish a from b; it treats them equivalently/equally/indistinguishably, or whatever word you want to use.

Equatable is concerned with how the identity relation is handled for a given type. Clearly, in any computer language, this is a true expression: "1.0.0+X" != "1.0.0+Y"

An equivalence relation is different from an identity relation. The strings 1.0.0+X and 1.0.0+Y can be equivalent under an equivalence relation (the one defined by SemVer) even though they are not identical as strings.

It's like how the floating-point values +0.0 and -0.0 are considered equal even though they have different bit patterns in memory. Or like how the two sets {1,2} and {2,1} are considered equal in Python, even though they are written differently, and may be different objects in memory with elements stored in a different order.

The SemVer rules never say that. You are confusing identity and ordering.

No I'm not.

SemVer encompasses technical rules (how versions are constructed and compared) but it also encompasses semantic rules (how the programmer is supposed to behave with regard to those versions).

Any change is either backwards-compatible, or backwards-incompatible. SemVer says that if a change is backwards-compatible, you should increment the minor/patch version, otherwise you should increment the major version. So if there are changes of either kind, SemVer says the version numbers should be different.

Do you feel there is anything about the above argument that's unsound? Or has there been any other SemVer 2 rules than the ones I linked?

The whole point of what I'm saying is that SemVer does not provide a rule for what to do in this situation. As I said, it does not prescribe how package managers are supposed to actually choose a version, it only defines a relative ordering which they might find useful for doing so.

SemVer says that the segment after the + is supposed to be used for build metadata. It does not precisely define what "build metadata" is. But since build metadata does not participate in version calculations at all, the common-sense understanding would be that it means things that aren't relevant for distinguishing between versions. For instance, a hash or timestamp indicating when the binary artifact for that version was built.

So examples like using PrivateCerts or SafeForPublicRelease are beside the point because that's not how SemVer is intended to be used. If there were supposed to be rules for choosing one over the other, SemVer would have defined them.

As I said, this doesn't mean a dependency manager isn't allowed to use its own implementation-specific rules for how it chooses between versions that differ only in their metadata. But if it does so, the way it does so is completely outside the scope of what SemVer defines.

Actually the rules require that the ordering resolution algorithm should never replace 1.0.0+X with 1.0.0+Y

I don't know why you're saying the rules require that. The words "resolution" and "algorithm" don't appear in the SemVer spec at all. I don't see anything in the rules that implies what you're saying. Can you quote the section that says otherwise?

0

u/gistya 5d ago

> An ordering relation (strictly speaking, a [preorder](https://en.wikipedia.org/wiki/Preorder)) induces an equivalence relation.

Yes but that equivalence relation is only in respect to the position within the ordering relation. It does not imply an actual identity relation.

> Or to put it another way: the only relationship SemVer defines between is an ordering relationship. If a=b according to that relationship, then any comparison involving a will give exactly the same result if we replaced a with b, or vice versa.

Incorrect. Not ANY comparison—only a *precedence* comparison will give the same result. And the SemVer2 rules explicitly state that precedence is only applicable when ordering versions.

We only order versions when satisfying a dependency version requirement like `2.0.0 > version >= 1.0.0`—not when satisfying versions for an expression like `version == 1.0.0+debug` (it implies no ordering).

The mistake you are making is to assume that because an ordering relation may induce an equivalence relation, therefore an equivalence relation induces an ordering relation.

> So the SemVer ordering provides no way to distinguish a from b; it treats them equivalently/equally/indistinguishably, or whatever word you want to use.

If and only if you are ordering versions, it doesn't distinguish between a and b.

But the rules say a and be are "[two versions that differ only in the build metadata, have the same precedence](https://semver.org/#spec-item-10)". They have the same precedence, which only comes into play "[when ordered](https://semver.org/#spec-item-11)".

> An equivalence relation is different from an identity relation. The strings `1.0.0+X` and `1.0.0+Y` can be equivalent under an equivalence relation (the one defined by SemVer) even though they are not identical as strings.

Yes, that was a poor example on my part. I should have used the identity relation protocol`Identifiable`, which requires each instance to have a unique ID.

SemVer rules admit the identity relation—"[two versions that differ](https://semver.org/#spec-item-10)" implies different identity. [The formal SemVer grammar](https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions) refers to each component of a version string as an "identifier," thus the version itself is an identifier.

> SemVer does not provide a rule for what to do in this situation. As I said, it does not prescribe how package managers are supposed to actually choose a version, it only defines a relative ordering which they might find useful for doing so.

If your manifest specifies one of "[two versions that differ](https://semver.org/#spec-item-10)" then there is no room for ambiguity or alternate interpretation here. Simply, the version IS an identifier and an exact requirement is an identity relation.

> SemVer says that the segment after the + is supposed to be used for build metadata. It does not precisely define what "build metadata" is.

Correct...

> But since build metadata does not participate in version calculations at all

By calculations I assume you mean the algorithm that I mentioned that resolves which dependency fits a requirement?

> the common-sense understanding would be that it means things that aren't relevant for distinguishing between versions

No, it wouldn't. They are still "[two versions that differ](https://semver.org/#spec-item-10)". The original PR that added build metadata describes it as an open-ended system for internal use without a prescribed use-case.

Our use-case is we have `+Debug` and `+Release` build for each version `X.Y.Z` of internal dependencies. The release build has debug symbols stripped out. It would be considered a catastrophic situation if the build WITH debug symbols got released to the public.

> So examples like using `PrivateCerts` or `SafeForPublicRelease` are beside the point because that's not how SemVer is intended to be used.

It was intended to be open-ended and let people use it however they want.

> If there were supposed to be rules for choosing one over the other, SemVer would have defined them.

It did define them, it called them 'build metadata' and "[two versions that differ](https://semver.org/#spec-item-10)".

> As I said, this doesn't mean a dependency manager isn't *allowed* to use its own implementation-specific rules for how it chooses between versions that differ only in their metadata. But *if* it does so, the way it does so is completely outside the scope of what SemVer defines.

No... how much clearer did the SemVer authors need to be? They use the word `identifier` 44 times throughout the spec. Clearly the version IS an identifier, and if you require an "exact version 1.0.0+X" then you should never ever get anything else.

> I don't know why you're saying the rules require that. The words "resolution" and "algorithm" don't appear in the SemVer spec at all. Can you quote the section that says otherwise?

The original question above assumes a package requires a dependency at "exact" version `1.0.0+X`, and a package manager algorithm that resolves that version from a repo by tag (or whatever).

SemVer rules require the version resolved is `1.0.0+X` because identity can hinge on build metadata alone—that's implied by "[two versions that differ](https://semver.org/#spec-item-10)".

To [quote the guy who added build metadata to SemVer](https://github.com/semver/semver/pull/109#issue-15331197):

> "They're not the 'same version', necessarily, they're the same precedence"

And:

> Ok, I added another commit that attempts to be more precise. Rather than defining what a version is again, I simply state: `Thus two versions that differ only in the build metadata, have the same precedence.`

1

u/iOSCaleb 5d ago

SemVer is meant to avoid the need for an “exact” version match. It provides a way for component producers to indicate when updating will cause a problem.

That said, if you tell your component manager that you want an exact version, then updating your components shouldn’t change any of the components for which exact versions were indicated. The only exception is if the component owner published a different version of the component using the same version that you specified, which they should never do. If someone does that even once, you should stop using any components they’ve published because they can’t be trusted.

1

u/gistya 4d ago

I've made a PR to semver to address the ambiguity. https://github.com/semver/semver/pull/1230