[GH-ISSUE #538] Is semantic versioning a good approach? #7370

Closed
opened 2026-06-20 17:23:47 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @rpuntaie on GitHub (Oct 31, 2019).
Original GitHub issue: https://github.com/semver/semver/issues/538

I wonder whether semantic versioning is the right way to go at all.
New interfaces should rather be handled via new names and be installable in parallel.

As an example:
We would have pyyaml5 1.0 instead of pyyaml 5.1.0.
In code we would do import yaml5 instead of import yaml.

Originally created by @rpuntaie on GitHub (Oct 31, 2019). Original GitHub issue: https://github.com/semver/semver/issues/538 I wonder whether semantic versioning is the right way to go at all. New interfaces should rather be handled via new names and be installable in parallel. As an example: We would have `pyyaml5` 1.0 instead of `pyyaml 5.1.0`. In code we would do `import yaml5` instead of `import yaml`.
Author
Owner

@ljharb commented on GitHub (Oct 31, 2019):

You’re already welcome to do that using semver, as long as you never publish a v2 or anything - but it seems like it’d make it much harder for consumers to update, and to understand that foo4 and foo5 are related. Also, you’d have to have a package manager that optimistically reserved every permutation of your package name and a version number - or else i could register pyyaml6 and do Bad Things to all your users.

<!-- gh-comment-id:548450092 --> @ljharb commented on GitHub (Oct 31, 2019): You’re already welcome to do that using semver, as long as you never publish a v2 or anything - but it seems like it’d make it much harder for consumers to update, and to understand that foo4 and foo5 are related. Also, you’d have to have a package manager that optimistically reserved every permutation of your package name and a version number - or else i could register pyyaml6 and do Bad Things to all your users.
Author
Owner

@jeme commented on GitHub (Oct 31, 2019):

Ohh so you saw that key note did you now?
(To be fair, I don't know, but I know of someone who delivered a keynote with this exact notion a while back)


But lets take a case study and try to figure out what the different approaches may hold of negative or positive sides.

Say you have package named RegEx and I use that package, my project is rather large and consists of well over ten-thousands of files combinded.
Because your package is very common use, I use your package in well over thousands of files. The version I use of your package is rather old and
has reached eol so that I won't receive security updates. And I am on a platform that doesn't easily allow me to load multiple versions of the same package.
A Security defect is found in your package, but you have distributed a newer version with breaking changes, but this are limited to a couple of functions/methods - yet it also fixed the security defect.

If you distributed your next version with breaking changes as RegEx2

What are the consequence for me?

  • (-) All over my code I have imported your package as "import RegEx" and used that, to upgrade I would have to track all those down and replace them, forcing me to update thousands of files.
  • (-) If I overlooked some, I would still have the security vunabilities which was found in your package.
  • (-) If I depended on another package that also used your package, there would be no way for me to avoid loading your old package asside from compiling my own version of that dependency or wait til they update (Because the code changes).
  • (-) The relationship beween your RegEx and RegEx2 apears week to me, if there is any at all.
  • (-) If I was passing objects around to dependencies, I would have to map between versions if my platform wasn't dynamic, giving me more work to do.
  • (+) I can load and use multiple versions of your package on any platform with ease.
  • (+) A dependency that requires a newer version of your package that I require, won't force me to update if I am on a platform that does not support loading multiple versions of the same package.

If you distributed your next version with breaking changes as RegEx v2

What are the consequence for me?

  • (-) I can't load both version of your package if my platform doesn't support is, as such I am forced to fix all places that uses features with breaking changes.
  • (-) If a dependency that i use requires your package: I can't update your package to v2 if that dependency uses v1 and we have a hit on a breaking change - wihout also updating that package, that is if I even can do that.
  • (+) If none of the above is the case it's trivial for me to update, no code change, and given that I have an appropriage test coverage I can also quickly fix the places where your breaking changes is an issue for me.
  • (+) If a dependency uses your package, and it happens to not use any of the areas where you added breaking changes, I can force that package to load the new version of your package with the security fixes.
  • (+) I can't overlook usages of your package and thereby leave security holes in there unknowingly.

So the thing is, it's not all that black and white, there are multiple things to consider:

  • A) What platform are you on? - One that supports loading multiple versions of the same package simultaneously (e.g. Node) or one that does not?
  • B) How breaking is your v2, is it everything or just a single method or two or is it even more subtle (abandoning support for an old platform)?
  • C) How easy is to adapt to those breaking changes?

So, at the end of the day, there are times when it is very appropriate to rename, and there is times where it's appropriate to release 2.0.0, but it's up to you to make that call.
If you choose to rename, I dislike the idea of "Yaml_1" and "Yaml_2"... That is ONLY if you must because of your platform.


Bonus: - So guess what this does:

//package.json:
{
  ...
  "dependencies": {
    "mathjs320": "npm:mathjs@^3.20.1",
    "mathjs63": "npm:mathjs@^6.2.3"
  }
}

//test.js
const mathjs63 = require('mathjs63');
const mathjs320 = require('mathjs320');
console.log(mathjs63.sqrt(9) === mathjs320.sqrt(9));

That's right, it loads two different versions of the same package into two different local names, on any platform that supports that as elegantly as node, it becomes a completely mood point all together...

<!-- gh-comment-id:548504522 --> @jeme commented on GitHub (Oct 31, 2019): Ohh so you saw that key note did you now? (To be fair, I don't know, but I know of someone who delivered a keynote with this exact notion a while back) ---- But lets take a case study and try to figure out what the different approaches may hold of negative or positive sides. Say you have package named RegEx and I use that package, my project is rather large and consists of well over ten-thousands of files combinded. Because your package is very common use, I use your package in well over thousands of files. The version I use of your package is rather old and has reached eol so that I won't receive security updates. And I am on a platform that doesn't easily allow me to load multiple versions of the same package. A Security defect is found in your package, but you have distributed a newer version with breaking changes, but this are limited to a couple of functions/methods - yet it also fixed the security defect. **If you distributed your next version with breaking changes as RegEx2** What are the consequence for me? - (-) All over my code I have imported your package as "import RegEx" and used that, to upgrade I would have to track all those down and replace them, forcing me to update thousands of files. - (-) If I overlooked some, I would still have the security vunabilities which was found in your package. - (-) If I depended on another package that also used your package, there would be no way for me to avoid loading your old package asside from compiling my own version of that dependency or wait til they update (Because the code changes). - (-) The relationship beween your RegEx and RegEx2 apears week to me, if there is any at all. - (-) If I was passing objects around to dependencies, I would have to map between versions if my platform wasn't dynamic, giving me more work to do. + (+) I can load and use multiple versions of your package on any platform with ease. + (+) A dependency that requires a newer version of your package that I require, won't force me to update if I am on a platform that does not support loading multiple versions of the same package. **If you distributed your next version with breaking changes as RegEx v2** What are the consequence for me? - (-) I can't load both version of your package if my platform doesn't support is, as such I am forced to fix all places that uses features with breaking changes. - (-) If a dependency that i use requires your package: I can't update your package to v2 if that dependency uses v1 and we have a hit on a breaking change - wihout also updating that package, that is if I even can do that. + (+) If none of the above is the case it's trivial for me to update, no code change, and given that I have an appropriage test coverage I can also quickly fix the places where your breaking changes is an issue for me. + (+) If a dependency uses your package, and it happens to not use any of the areas where you added breaking changes, I can force that package to load the new version of your package with the security fixes. + (+) I can't overlook usages of your package and thereby leave security holes in there unknowingly. ---- So the thing is, it's not all that black and white, there are multiple things to consider: - A) What platform are you on? - One that supports loading multiple versions of the same package simultaneously (e.g. Node) or one that does not? - B) How breaking is your v2, is it everything or just a single method or two or is it even more subtle (abandoning support for an old platform)? - C) How easy is to adapt to those breaking changes? ---- So, at the end of the day, there are times when it is very appropriate to rename, and there is times where it's appropriate to release 2.0.0, but it's up to you to make that call. If you choose to rename, I dislike the idea of "Yaml_1" and "Yaml_2"... That is ONLY if you must because of your platform. ---- Bonus: - So guess what this does: ``` //package.json: { ... "dependencies": { "mathjs320": "npm:mathjs@^3.20.1", "mathjs63": "npm:mathjs@^6.2.3" } } //test.js const mathjs63 = require('mathjs63'); const mathjs320 = require('mathjs320'); console.log(mathjs63.sqrt(9) === mathjs320.sqrt(9)); ``` That's right, it loads two different versions of the same package into two different local names, on any platform that supports that as elegantly as node, it becomes a completely mood point all together...
Author
Owner

@rpuntaie commented on GitHub (Oct 31, 2019):

I agree, it is not all black and white.
But it is something to waste a thought on.

Interface changes of a fundamental package can produce efforts in many other depending packages,
which otherwise are unrelated.
Developers catch up with new versions in their own pace.
Different names gives them time.
It decouples unrelated development efforts.

It depends on the perspective:

From the perspective of the developers:

  • semantic versioning, because an interface is stable, and the developers are free for new ventures in the same repo
  • but maybe, if really dramatic interface changes are planned, a new repo and a new name is appropriate (e.g. jinja2 or qt5)

From the perspective of a distro:

  • different names for major interface changes are better to allow installing them in parallel
    (e.g. nixos provides for this by design),
  • else patches are needed here and there, or contributions to upstream

Distros sometimes need to convert a major versions into separate names: e.g. python2 and python3
If upstream does the renaming the distros have less work.

Nevertheless:

Semantic versioning is a solidification of previous practices.
It solidifies the problems that were there before.

Different names for different interfaces would simplify distro work and it would not make developer's work harder.
If all the packages out there followed the principle sw1, sw2, ...
it would be less effort overall.
Users of the packages would use the names (sw1, ...), therefore no unexpected breakage.
When time allows, they would "grep" or "sed" to the new names and adapt their code to the new interface.

Names like sw1, sw2, ... would probably make more aware that the interface is very important.

  • Without a number should mean: no stable interface yet or between two stable interfaces.
    Others should not use it unless with number.
  • With number, the major version is part of the namespace, already upstream
    (1 name= 1 concept= 1 interface)
  • Minor version for additions to interface (features/functions) without any removal.
  • Patch version for fixes without addition.

Deprecation warnings go with minor versions,
removal goes with major versions, i.e. with the name change.

This kind of versioning could be used throughout the namespace:
somefun1, somefun2, ..., instead of an arbitrary suffix (e.g. -Ex in Windows API).

The number at the end would always mean, that it will replace a lower number,
when enough deprecation has been accumulated for a new major version.

<!-- gh-comment-id:548597347 --> @rpuntaie commented on GitHub (Oct 31, 2019): I agree, it is not all black and white. But it is something to waste a thought on. Interface changes of a fundamental package can produce efforts in many other depending packages, which otherwise are unrelated. Developers catch up with new versions in their own pace. Different names gives them time. It decouples unrelated development efforts. It depends on the perspective: From the perspective of the developers: - semantic versioning, because an interface is stable, and the developers are free for new ventures in the same repo - but maybe, if really dramatic interface changes are planned, a new repo and a new name is appropriate (e.g. jinja2 or qt5) From the perspective of a distro: - different names for major interface changes are better to allow installing them in parallel (e.g. [nixos](https://nixos.org/nix/) provides for this by design), - else patches are needed here and there, or contributions to upstream Distros sometimes need to convert a major versions into separate names: e.g. python2 and python3 If upstream does the renaming the distros have less work. Nevertheless: *Semantic versioning* is a solidification of previous practices. It solidifies the problems that were there before. Different names for different interfaces would simplify distro work and it would not make developer's work harder. If all the packages out there followed the principle `sw1`, `sw2`, ... it would be less effort overall. Users of the packages would use the names (`sw1`, ...), therefore no unexpected breakage. When time allows, they would "grep" or "sed" to the new names and adapt their code to the new interface. Names like `sw1`, `sw2`, ... would probably make more aware that the interface is very important. - Without a number should mean: no stable interface yet or between two stable interfaces. Others should not use it unless with number. - With number, the major version is part of the namespace, already upstream (1 name= 1 concept= 1 interface) - Minor version for additions to interface (features/functions) without any removal. - Patch version for fixes without addition. Deprecation warnings go with minor versions, removal goes with major versions, i.e. with the name change. This kind of versioning could be used throughout the namespace: somefun1, somefun2, ..., instead of an arbitrary suffix (e.g. -Ex in Windows API). The number at the end would always mean, that it will replace a lower number, when enough deprecation has been accumulated for a new major version.
Author
Owner

@wizzwizz4 commented on GitHub (Nov 1, 2019):

@rpuntaie If we're doing that, then wouldn't it better to just have a rolling release with no backwards-incompatibility and have new function names when stuff changes like that? Then, to update breakingly, you can just grep most of it and manually fix the few things, and it's clearer what you need to change.

This is the approach taken by the PHP language.

<!-- gh-comment-id:548923278 --> @wizzwizz4 commented on GitHub (Nov 1, 2019): @rpuntaie If we're doing that, then wouldn't it better to just have a rolling release with no backwards-incompatibility and have new function names when stuff changes like that? Then, to update breakingly, you can just grep most of it and manually fix the few things, and it's clearer what you need to change. This is the approach taken by the PHP language.
Author
Owner

@jwdonahue commented on GitHub (Jan 10, 2020):

@rpuntaie wrote:

Is semantic versioning a good approach?

The obvious answer is that it seems to be a fairly good one for many of us, but the answer really depends on your goals, target environments and development tool chain support. It seems you may be trying to overcome the limitations of an environment that does not support side-by-side (SxS) execution of different versions of a product. First, let me point out that, while it puts a small dent in the problem, your proposal does not solve all of the SxS execution issues.

See Depenency Hell
See DLL Hell

I would also point out that, not everyone suffers from the lack of SxS execution support.

This thread does not appear to be directed at achieving any changes in the SemVer spec and doesn't ask/answer any questions about its application. While package naming schemes are beyond the scope of this site, renaming is a common enough recommended solution to problems that are within scope.

See:

#522, specifically my response to issue 522
#518
#463
#395

And probably many others. The point is that the SemVer spec covers how its particular versioning scheme is applied to API's and to some extent, the packages that contain them. In practice, this mostly comes down to associating a version label with a package label, such that L+V is a universally unique key/value pair.

What you seem to be proposing is not new. You are proposing a syntactical change that would not require any differences in semantics yes? As such, we might have to modify some parsers to extract the major version from the package name. In practice, you still have L+V. A breaking change with no semantic differences what-so-ever. In practice, you do not need to modify or abandon the SemVer spec.

We can simply modify the build and packaging tool chain to optionally yield DLL's and Libs that have the full or partial version strings embedded in their file names. Most of them already do this at the package directory naming level anyway. In fact, in many cases, just grouping the files in directories named L+V is sufficient. I was once part of a small team, that created SemVer compliant package tooling that provides conditional renaming capabilities at the manifest level, that is used by tens of thousands of developers world-wide. We did not even consider pushing for abandonment or modification of SemVer. We simply attacked the problem directly, in a way that did not place any additional burden on users who did not have that particular problem to solve.

So what you are proposing, sounds more like a tooling feature than anything else. I would suggest you broach the subject with your favorite package tool maintainers. You will have to talk to them anyway, if you wish them to abandon SemVer in favor of any other schemes. In the mean-time, unless you intend to issue a PR or have further questions, please close this thread at your earliest possible convenience.

<!-- gh-comment-id:573205064 --> @jwdonahue commented on GitHub (Jan 10, 2020): @rpuntaie wrote: > Is semantic versioning a good approach? The obvious answer is that it seems to be a fairly good one for many of us, but the answer really depends on your goals, target environments and development tool chain support. It seems you may be trying to overcome the limitations of an environment that does not support side-by-side (SxS) execution of different versions of a product. First, let me point out that, while it puts a small dent in the problem, your proposal does not solve all of the SxS execution issues. See [Depenency Hell](https://en.wikipedia.org/wiki/Dependency_hell) See [DLL Hell](https://en.wikipedia.org/wiki/DLL_Hell) I would also point out that, not everyone suffers from the lack of SxS execution support. This thread does not appear to be directed at achieving any changes in the SemVer spec and doesn't ask/answer any questions about its application. While package naming schemes are beyond the scope of this site, renaming is a common enough recommended solution to problems that are within scope. See: #522, specifically [my response to issue 522](https://github.com/semver/semver/issues/522#issuecomment-522107844) #518 #463 #395 And probably many others. The point is that the SemVer spec covers how its particular versioning scheme is applied to API's and to some extent, the packages that contain them. In practice, this mostly comes down to associating a version label with a package label, such that L+V is a universally unique key/value pair. What you seem to be proposing is not new. You are proposing a syntactical change that would not require any differences in semantics yes? As such, we might have to modify some parsers to extract the major version from the package name. In practice, you still have L+V. A breaking change with no semantic differences what-so-ever. In practice, you do not need to modify or abandon the SemVer spec. We can simply modify the build and packaging tool chain to optionally yield DLL's and Libs that have the full or partial version strings embedded in their file names. Most of them already do this at the package directory naming level anyway. In fact, in many cases, just grouping the files in directories named L+V is sufficient. I was once part of a small team, that created SemVer compliant package tooling that provides conditional renaming capabilities at the manifest level, that is used by tens of thousands of developers world-wide. We did not even consider pushing for abandonment or modification of SemVer. We simply attacked the problem directly, in a way that did not place any additional burden on users who did not have that particular problem to solve. So what you are proposing, sounds more like a tooling feature than anything else. I would suggest you broach the subject with your favorite package tool maintainers. You will have to talk to them anyway, if you wish them to abandon SemVer in favor of any other schemes. In the mean-time, unless you intend to issue a PR or have further questions, please close this thread at your earliest possible convenience.
Author
Owner

@rpuntaie commented on GitHub (Jan 11, 2020):

I just wanted to get some other opinions, also for the general public. Thanks.

<!-- gh-comment-id:573335903 --> @rpuntaie commented on GitHub (Jan 11, 2020): I just wanted to get some other opinions, also for the general public. Thanks.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/semver#7370