Version after dependency changes? #95

Closed
opened 2026-02-17 11:12:50 -06:00 by GiteaMirror · 31 comments
Owner

Originally created by @tvthompsonii on GitHub (Aug 1, 2013).

I don't see anything in the spec that says what a packages version should be if it needed to be published solely because a dependency changed. For example, let's say my package is at version 1.2.3 and a dependent product just moved from its version 2.3.4 up to version 3.0.0. That version change signals that my product needs to be rebuilt and published since the dependecy has changed it's public api.

What would my product now be? Is it up to what changes I had to make? I mean, if I changed my public api to get things working, then obviously I would release 2.0.0. But otherwise, would it be 1.2.4 or 1.3.0?

Both seem a little "off". My package technically is the same "version" it was before. I would almost rather have a "build number" at the end. My package is just a rebuild, maybe no code changes, so it would go from 1.2.3-1 to 1.2.3-2, for example. Users still know they have the same "version" but there is still something in the version number to help the build system with its dependency management.

What is the "semvar way" though?

Originally created by @tvthompsonii on GitHub (Aug 1, 2013). I don't see anything in the spec that says what a packages version should be if it needed to be published solely because a dependency changed. For example, let's say my package is at version 1.2.3 and a dependent product just moved from its version 2.3.4 up to version 3.0.0. That version change signals that my product needs to be rebuilt and published since the dependecy has changed it's public api. What would my product now be? Is it up to what changes I had to make? I mean, if I changed my public api to get things working, then obviously I would release 2.0.0. But otherwise, would it be 1.2.4 or 1.3.0? Both seem a little "off". My package technically is the same "version" it was before. I would almost rather have a "build number" at the end. My package is just a rebuild, maybe no code changes, so it would go from 1.2.3-1 to 1.2.3-2, for example. Users still know they have the same "version" but there is still something in the version number to help the build system with its dependency management. What is the "semvar way" though?
Author
Owner

@EddieGarmon commented on GitHub (Aug 1, 2013):

#80 covers a subset of this, but I believe that it is still unanswered and is core to what you are asking.

If you completely hide/internalize all types in the dependency, then the version update to 1.2.4 or 1.3.0 should reflect your level of changes needed to consume the dependency.

  • if you do not change your public api, then 1.2.4.
  • if you add features to your public api based on new dependency features then 1.3.0.

If on the other hand you expose ANY types from the dependency, and those types had any breaking changes, you now have a breaking change and should release 2.0.0.

@EddieGarmon commented on GitHub (Aug 1, 2013): #80 covers a subset of this, but I believe that it is still unanswered and is core to what you are asking. If you completely hide/internalize all types in the dependency, then the version update to 1.2.4 or 1.3.0 should reflect your level of changes needed to consume the dependency. - if you do not change your public api, then 1.2.4. - if you add features to your public api based on new dependency features then 1.3.0. If on the other hand you expose _ANY_ types from the dependency, and _those types had any breaking changes_, you now have a breaking change and should release 2.0.0.
Author
Owner

@Tieske commented on GitHub (Aug 1, 2013):

I think it is more related to #146. In this case (assuming the api didn't change), you actually have a reason to publish an 'unchanged' version (externally, internally it did change because of the updated dependency).

The question is then; how to version an unchanged external API, with a changed internal implementation.

@Tieske commented on GitHub (Aug 1, 2013): I think it is more related to #146. In this case (assuming the api didn't change), you actually have a reason to publish an 'unchanged' version (externally, internally it did change because of the updated dependency). The question is then; how to version an unchanged external API, with a changed internal implementation.
Author
Owner

@EddieGarmon commented on GitHub (Aug 2, 2013):

I wouldn't say more related, but equally related. Both are concerned with dependencies changing, each having different outcomes based on changes reflected in your public API.

@EddieGarmon commented on GitHub (Aug 2, 2013): I wouldn't say more related, but equally related. Both are concerned with dependencies changing, each having different outcomes based on changes reflected in your public API.
Author
Owner

@tvthompsonii commented on GitHub (Aug 2, 2013):

I guess the point is that a lot of times the "changes" required to absorb a dependency change, is simple a recompile. Nothing changed in your public api, and you didn't add any new features.

The rules seem lacking here.

It isn't a bug fix/patch so 1.2.4 seems wrong.
It isn't an addition of functionality so 1.3.0 seems wrong.
It is still backwards compatible so 2.0.0 seems wrong.

Now, one could ask the dependency what the change was for. If it was a bug fix then maybe bump your version up by a micro. If it added new features (that you obviously aren't using) you could possibly bump up the minor. If it was a major change, that somehow affects you such that you are now no longer backwards compatible, then use a major.

But those all seem to be using your version to indicate changes in other packages, not yours. Odd...

@tvthompsonii commented on GitHub (Aug 2, 2013): I guess the point is that a lot of times the "changes" required to absorb a dependency change, is simple a recompile. Nothing changed in your public api, and you didn't add any new features. The rules seem lacking here. It isn't a bug fix/patch so 1.2.4 seems wrong. It isn't an addition of functionality so 1.3.0 seems wrong. It is still backwards compatible so 2.0.0 seems wrong. Now, one could ask the dependency what the change was for. If it was a bug fix then maybe bump your version up by a micro. If it added new features (that you obviously aren't using) you could possibly bump up the minor. If it was a major change, that somehow affects you such that you are now no longer backwards compatible, then use a major. But those all seem to be using your version to indicate changes in other packages, not yours. Odd...
Author
Owner

@EddieGarmon commented on GitHub (Aug 2, 2013):

If you had to recompile, then it is a patch (you edited linker metadata), otherwise you should just use binding redirects or the likes.

This is the core of dependency management, if you don't have to do anything to use an updated dependency, then do n't do anything at all. Let your consumer decide.

If you want to force people to use the new dependency, then you have made a change and need a new version of some form.

@EddieGarmon commented on GitHub (Aug 2, 2013): If you had to recompile, then it is a patch (you edited linker metadata), otherwise you should just use binding redirects or the likes. This is the core of dependency management, if you don't have to do anything to use an updated dependency, then do n't do anything at all. Let your consumer decide. If you want to force people to use the new dependency, then you have made a change and need a new version of some form.
Author
Owner

@EddieGarmon commented on GitHub (Aug 2, 2013):

An example of the options available assuming you do want to take an update to a current dependency follows below. If you do not want to update, then do not and you are done.

Lib A is at version 12.3.4 and has type Awesomeness with 2 public methods FixAllTheThings() and DrinkAllTheBeer().

Say you have a dependency on Lib A and your current version is at 1.1.1.

Lib A is updated with a patch release, to 12.3.5, what should you do?

  • If you can consume 12.3.5 without recompiling you should, and no new release.
  • If you have to recompile (but you shouldn't have to except for things like strong names) but nothing else, this is a patch and you should release 1.1.2.
  • There should be no other option if you don't change your code.

Lib A is updated to 12.4.0 with some minor non-breaking additional features, what should you do?

  • If you can consume 12.4.0 without recompiling you should, and no new release.
  • If you have to recompile (but you shouldn't have to except for things like strong names) but nothing else, this is a patch and you should release 1.1.2.
  • If you want to take advantage of the new features only in your internal implementation, this is a patch and you should release 1.1.2.
  • If you want to take advantage of the new features in your public API in a non-breaking way, this is a minor and you should release 1.2.0.
  • If you want to take advantage of the new features in your public API in a breaking way, this is a major and you should release 2.0.0

Lib A is updated to 13.0.0 because 'FixAllTheThings()' has been removed, what should you do?

  • If you never used this method and can consume 13.0.0 without recompiling you should, and no new release.
  • If you only used this method in your private implementation and never exposed type Awesomeness in your public API, and can work around it without changing your public API, this is a patch and you should release 1.1.2.
  • If you only used this method in your private implementation and never exposed type Awesomeness in your public API, but instead add new features in your public API to fill the gap, this is a minor and you should release 1.2.0.
  • If you only used this method in your private implementation and never exposed type Awesomeness in your public API, but relied on FixAllTheThings() to a point that you have to remove features in your public API, this is a major and you should release 2.0.0.
  • If you did exposed type Awesomeness in your public API, this is a major and you should release 2.0.0.
@EddieGarmon commented on GitHub (Aug 2, 2013): An example of the options available assuming you do want to take an update to a current dependency follows below. If you do not want to update, then do not and you are done. Lib A is at version 12.3.4 and has type `Awesomeness` with 2 public methods `FixAllTheThings()` and `DrinkAllTheBeer()`. Say you have a dependency on Lib A and your current version is at 1.1.1. Lib A is updated with a patch release, to 12.3.5, what should you do? - If you can consume 12.3.5 without recompiling you should, and no new release. - If you have to recompile (but you shouldn't have to except for things like strong names) but nothing else, this is a patch and you should release 1.1.2. - There should be no other option if you don't change your code. Lib A is updated to 12.4.0 with some minor non-breaking additional features, what should you do? - If you can consume 12.4.0 without recompiling you should, and no new release. - If you have to recompile (but you shouldn't have to except for things like strong names) but nothing else, this is a patch and you should release 1.1.2. - If you want to take advantage of the new features only in your internal implementation, this is a patch and you should release 1.1.2. - If you want to take advantage of the new features in your public API in a non-breaking way, this is a minor and you should release 1.2.0. - If you want to take advantage of the new features in your public API in a breaking way, this is a major and you should release 2.0.0 Lib A is updated to 13.0.0 because 'FixAllTheThings()' has been removed, what should you do? - If you never used this method and can consume 13.0.0 without recompiling you should, and no new release. - If you only used this method in your private implementation and never exposed type `Awesomeness` in your public API, and can work around it without changing your public API, this is a patch and you should release 1.1.2. - If you only used this method in your private implementation and never exposed type `Awesomeness` in your public API, but instead add new features in your public API to fill the gap, this is a minor and you should release 1.2.0. - If you only used this method in your private implementation and never exposed type `Awesomeness` in your public API, but relied on `FixAllTheThings()` to a point that you have to remove features in your public API, this is a major and you should release 2.0.0. - If you did exposed type `Awesomeness` in your public API, this is a major and you should release 2.0.0.
Author
Owner

@Tieske commented on GitHub (Aug 2, 2013):

question: if you only need to recompile, no code changes whatsoever, shouldn't the update go into the build metadata? The source is the same, only a different build.

@Tieske commented on GitHub (Aug 2, 2013): question: if you only need to recompile, no code changes whatsoever, shouldn't the update go into the build metadata? The source is the same, only a different build.
Author
Owner

@EddieGarmon commented on GitHub (Aug 3, 2013):

if you have to recompile, something changed

@EddieGarmon commented on GitHub (Aug 3, 2013): **if you have to recompile, something changed**
Author
Owner

@wcooley commented on GitHub (Oct 1, 2013):

To throw a little gasoline on the fire: What if your dependency change increases the minimum required version for the dependency? And moreover, what if that dependency was not a minor, easily-upgraded component but a broad piece of the platform, such as dropping support for Ruby 1.8?

@wcooley commented on GitHub (Oct 1, 2013): To throw a little gasoline on the fire: What if your dependency change increases the minimum required version for the dependency? And moreover, what if that dependency was not a minor, easily-upgraded component but a broad piece of the platform, such as dropping support for Ruby 1.8?
Author
Owner

@EddieGarmon commented on GitHub (Oct 2, 2013):

@wcooley would you consider dropping a platform a breaking change? I think I would, which would mean the next major version should be used. Don't be scared that version numbers go up, that's what their job is.

@EddieGarmon commented on GitHub (Oct 2, 2013): @wcooley would you consider dropping a platform a breaking change? I think I would, which would mean the next major version should be used. Don't be scared that version numbers go up, that's what their job is.
Author
Owner

@wcooley commented on GitHub (Oct 3, 2013):

@EddieGarmon Oh yes, I definitely would consider dropping a platform a breaking change -- but I am mainly looking at this from the ops/deployment side, where "public API" is either less meaningful or means something different.

The v2.0.0 spec does not really address this in the FAQ "What should I do if I update my own dependencies without changing the public API?" or perhaps says the opposite: "That would be considered compatible since it does not affect the public API."

I am motivated to seek clarification because I am seeing Github's own projects dropping support for Ruby 1.8 and only incrementing the patch level and wanted to make sure that I am not misunderstanding something. See, for example gollum/gollum-lib#44 (which propagates up to gollum itself).

@wcooley commented on GitHub (Oct 3, 2013): @EddieGarmon Oh yes, I definitely would consider dropping a platform a breaking change -- but I am mainly looking at this from the ops/deployment side, where "public API" is either less meaningful or means something different. The v2.0.0 spec does not really address this in the FAQ "What should I do if I update my own dependencies without changing the public API?" or perhaps says the opposite: "That would be considered compatible since it does not affect the public API." I am motivated to seek clarification because I am seeing Github's own projects dropping support for Ruby 1.8 and only incrementing the patch level and wanted to make sure that I am not misunderstanding something. See, for example gollum/gollum-lib#44 (which propagates up to gollum itself).
Author
Owner

@mpalmer commented on GitHub (Jan 30, 2014):

I don't like to talk in terms of a "public API" when it comes to versioning, for just this reason. It's too limiting. Instead, I consider the entireity of what my code provides and relies on, and how others view that, from the point of view of published information (or perhaps "reasonable expectation"; I'm not sure which is more appropriate, but I do know that all published information would form part of a reasonable expectation). I don't have a single word to describe what I'm thinking of, but it is essentially the "universal everything" that isn't the code under consideration. I'll call it the "gestalt" for the purposes of this comment, because it's a cool word. You'll hate it by the time you're finished reading this, though.

To expand a little further, I like to think about this problem in terms of the set of "valid gestalts". A gestalt is considered valid if it conforms to every piece of published information and/or reasonable expectation about the code. If the other parts of the gestalt rely on the API in the manner it has been described, and the gestalt contains a set of other components that the code has stated are required for its proper operation, then the gestalt is a part of the valid set. If a gestalt is missing something essential, or it relies on undefined behaviour of the code to get its job done, then it is an invalid gestalt, and is not part of the set.

I apply this "set of valid gestalts" principle like this:

  1. If the set of valid gestalts of the previous version of my code is identical to the set of valid gestalts of the new version of my code, then from the external perspective, nothing has changed. I can just bump the patch level.
  2. If the set of valid gestalts of the previous version of my code is a strict subset of the valid gestalts of the new version, then my code is backwards compatible with the previous code. The minor version should be bumped.
  3. Otherwise, the set of valid gestalts of the old version is not entirely contained by the valid gestalts of the new version -- something that used to be OK will no longer be OK. That's non-backwards-compatible, and needs a major version bump.

Some examples, borrowing from the gollum-lib example from @wcooley, because that's how I came across this issue and decided to drop my (very wordy) 2c in:

  1. If I drop support for a version of Ruby, then that's a major version bump (nokogiri should have done this in the first case). The set of gestalts previously included Ruby 1.8, and now it doesn't.

  2. If I change the dependencies in my package such that a new version of that dependency must be installed, that's a major version bump. Gollum-lib should have done this when it bumped the nokogiri dependency (whether or not nokogiri did the right thing).

    I expect this to be controversial, but it does follow logically from the "gestalt view" (in that the set of valid gestalts in the old version included an old version of the package, but the set of valid gestalts in the new version does not, therefore the new set of gestalts is not a superset). This isn't as theoretical as it appears, either -- if I bump a dependency, that could have some nasty ripple effects (if my package is a dependency of something that also depends on one of my dependencies, and I major-bump my dependency, I've now broken things).

  3. If I modify my code so that I now run under a newer (or older!) version of Ruby when I didn't previously, then the set of valid gestalts with the new version contain all the old ones -- all the previously supported versions of Ruby still work -- and also contains gestalts that weren't valid previously (ones that are identical to the existing ones, but also include this new version of Ruby). This can be released with a minor version bump.

  4. If I make a modification to my code, such that things now run faster, this illustrates the "published information" principle. If the previous version made no statement about performance, then that's just a patch bump -- the set of valid gestalts is unchanged (because performance was not an axis along which performance could be measured).

    On the other hand, if I did promise some certain level of performance, then I need to make at least a minor version bump, if my promise was "it'll run at least this fast". In that case, the set of valid gestalts included all those which were OK with your code taking up to the time it used to take, and also those gestalts which needed the code to run as fast as it does now. If, however, you stated a valid time range, and you're no longer within that range, then that's a major version bump -- gestalts which previously assumed that the code would take a certain time to run will no longer be valid.

    (As an aside, before anyone says, "that's a ridiculous example, why would anyone have a problem with your code running faster?", I'll just point to PC XT games running on an 80286... those machines had a turbo button for a reason...)

Anyway, this is potentially the longest all-text comment ever to be posted to a Github Issue, and I do apologise. In summary, I think that just considering the "public API" of a piece of code is insufficient to be able to properly version the code in a way that allows others to safely rely on the semantics of the version number when making decisions.

@mpalmer commented on GitHub (Jan 30, 2014): I don't like to talk in terms of a "public API" when it comes to versioning, for just this reason. It's too limiting. Instead, I consider the entireity of what my code provides _and_ relies on, and how others view that, from the point of view of _published information_ (or perhaps "reasonable expectation"; I'm not sure which is more appropriate, but I do know that all published information would form part of a reasonable expectation). I don't have a single word to describe what I'm thinking of, but it is essentially the "universal everything" that isn't the code under consideration. I'll call it the "gestalt" for the purposes of this comment, because it's a cool word. You'll hate it by the time you're finished reading this, though. To expand a little further, I like to think about this problem in terms of the set of "valid gestalts". A gestalt is considered valid if it conforms to every piece of published information and/or reasonable expectation about the code. If the other parts of the gestalt rely on the API in the manner it has been described, and the gestalt contains a set of other components that the code has stated are required for its proper operation, then the gestalt is a part of the valid set. If a gestalt is missing something essential, or it relies on undefined behaviour of the code to get its job done, then it is an invalid gestalt, and is not part of the set. I apply this "set of valid gestalts" principle like this: 1. If the set of valid gestalts of the previous version of my code is identical to the set of valid gestalts of the new version of my code, then from the external perspective, nothing has changed. I can just bump the patch level. 2. If the set of valid gestalts of the previous version of my code is a strict subset of the valid gestalts of the new version, then my code is backwards compatible with the previous code. The minor version should be bumped. 3. Otherwise, the set of valid gestalts of the old version is not entirely contained by the valid gestalts of the new version -- something that _used_ to be OK will no longer be OK. That's non-backwards-compatible, and needs a major version bump. Some examples, borrowing from the gollum-lib example from @wcooley, because that's how I came across this issue and decided to drop my (very wordy) 2c in: 1. If I drop support for a version of Ruby, then that's a major version bump (nokogiri should have done this in the first case). The set of gestalts previously included Ruby 1.8, and now it doesn't. 2. If I change the dependencies in my package such that a new version of that dependency must be installed, that's a major version bump. Gollum-lib should have done this when it bumped the nokogiri dependency (whether or not nokogiri did the right thing). I expect this to be controversial, but it does follow logically from the "gestalt view" (in that the set of valid gestalts in the old version included an old version of the package, but the set of valid gestalts in the new version does not, therefore the new set of gestalts is not a superset). This isn't as theoretical as it appears, either -- if I bump a dependency, that could have some nasty ripple effects (if my package is a dependency of something that also depends on one of my dependencies, and I major-bump my dependency, I've now broken things). 3. If I modify my code so that I now run under a newer (or older!) version of Ruby when I didn't previously, then the set of valid gestalts with the new version contain all the old ones -- all the previously supported versions of Ruby still work -- and also contains gestalts that weren't valid previously (ones that are identical to the existing ones, but also include this new version of Ruby). This can be released with a minor version bump. 4. If I make a modification to my code, such that things now run faster, this illustrates the "published information" principle. If the previous version made no statement about performance, then that's just a patch bump -- the set of valid gestalts is unchanged (because performance was not an axis along which performance could be measured). On the other hand, if I did promise some certain level of performance, then I need to make at least a minor version bump, if my promise was "it'll run at least this fast". In that case, the set of valid gestalts included all those which were OK with your code taking up to the time it used to take, and _also_ those gestalts which needed the code to run as fast as it does now. If, however, you stated a valid time _range_, and you're no longer within that range, then that's a major version bump -- gestalts which previously assumed that the code would take a certain time to run will no longer be valid. (As an aside, before anyone says, "that's a ridiculous example, why would anyone have a problem with your code running faster?", I'll just point to PC XT games running on an 80286... those machines had a turbo button for a _reason_...) Anyway, this is potentially the longest all-text comment ever to be posted to a Github Issue, and I do apologise. In summary, I think that just considering the "public API" of a piece of code is insufficient to be able to properly version the code in a way that allows others to safely rely on the semantics of the version number when making decisions.
Author
Owner

@remcoros commented on GitHub (Jan 22, 2015):

So this just happened: https://github.com/quartznet/quartznet/issues/229, anyone more familiar with semver can comment on this?

@remcoros commented on GitHub (Jan 22, 2015): So this just happened: https://github.com/quartznet/quartznet/issues/229, anyone more familiar with semver can comment on this?
Author
Owner

@FichteFoll commented on GitHub (Jan 22, 2015):

If the dependency whose major was updated is linked or bundled statically and does not impose changes for the project's API it should be okay to just increase patch or minor if you suspect few issues to arise.

If, however, you are linking or just working dynamically in general, I think increasing minor is a must and probably even major since a library can have two types of interfaces, incoming and outgoing. Upgrading the logger dependency would be an outgoing interface change and must be considered part of the public API.

@FichteFoll commented on GitHub (Jan 22, 2015): If the dependency whose major was updated is linked or bundled statically and does not impose changes for the project's API it should be okay to just increase patch or minor if you suspect few issues to arise. If, however, you are linking or just working dynamically in general, I think increasing minor is a must and probably even major since a library can have two types of interfaces, incoming and outgoing. Upgrading the logger dependency would be an outgoing interface change and must be considered part of the public API.
Author
Owner

@e2 commented on GitHub (Apr 25, 2016):

I agree with @FichteFoll here:

"If the dependency whose major was updated is linked or bundled statically and does not impose changes for the project's API it should be okay to just increase patch or minor if you suspect few issues to arise."

The trail of thought is: single responsibility principle.

A dependency should only be responsible for the API it exposes itself.

(Even if it's API can be augmented by another dependency).

Example:

  1. Tool 1.0.0 depends on Library 1.0.0.
  2. Both are written in a scripting language interpreter Interpreter 1.0.0.
  3. So, both Tool 1.0.0 and Library 1.0.0 declare dependency on Interpreter ~> 1.0
  4. After many months, Interpreter 1.0.0 is no longer supported and has outstanding bugs. These manifest in Library 1.0.0 and are fixed in Interpreter 1.1.0.

So, Library bumps it's dependency on Interpreter to Interpreter 1.1.0 to side-step the bug.

Question:

What should the version of Library be?.

Answer:

Library 1.0.1 (patchlevel change).

Why?

Because the API didn't change - specifically, there are no code changes necessary in Tool (or apps using Tool), so there's no need for even a minor bump in Library.

Even if Library's API is augmented by a different interpreter version (Interpreter 1.1.0), it doesn't matter.

Details:

Although Interpreter 1.1.0 can "extend" the API of Library, it isn't Library's responsibility to document and manage that API. That's because Tool can further bump the dependency on Interpreter to Interpreter 1.2.0 - outside the control of Library.

In practice:

The interesting thing here is that users typically see Interpreter as a "special case", which is "outside" SemVer. So they expect Library 1.0.0 to be bumped to Library 2.0.0, because the following case seems "broken":

  1. User has Interpreter 1.0.0
  2. User attempts to install Tool 1.0.0 and the latest version of Library (1.0.1), expecting patch level to not have a significant impact.
  3. Installation fails since Library 1.0.1 cannot be used on Interpreter 1.0.0.
  4. User freaks out and complains that SemVer has been violated.
  5. Insanely long discussions ensure...

Suggestion:

So it might help if this case was accurately covered by SemVer.

(That according to single responsibility rule, a change in patchlevel does not guarantee that dependencies will stay satiable).

I think it's an error to suggest a major bump in this case because: you no longer can patch the previous major version with automatic upgrades.

E.g. If Library was erroneously bumped to 2.0.0 (just to avoid the "install breakage" above), then Tool is still using Library 1.0.0, which not only contains the bug, but needs a dependency change to to the major version of Library 2.0.0 to be fixed. And that misleadingly suggests an API change. (Which never happened).

In short: a backward-compatible bugfix should be reflected by no more than a patchlevel change, regardless of the circumstances (dependencies or dependencies of dependencies needed for the patch).

I'm wondering how to present this in a generic way (without the notion of an 'interpreter').

@e2 commented on GitHub (Apr 25, 2016): I agree with @FichteFoll here: "If the dependency whose major was updated is linked or bundled statically and does not impose changes for the project's API it should be okay to just increase patch or minor if you suspect few issues to arise." The trail of thought is: single responsibility principle. A dependency should only be responsible for the API it exposes itself. (Even if it's API can be augmented by another dependency). #### Example: 1. `Tool 1.0.0` depends on `Library 1.0.0`. 2. Both are written in a scripting language interpreter `Interpreter 1.0.0`. 3. So, both `Tool 1.0.0` and `Library 1.0.0` declare dependency on `Interpreter ~> 1.0` 4. After many months, `Interpreter 1.0.0` is no longer supported and has outstanding bugs. These manifest in `Library 1.0.0` and are fixed in `Interpreter 1.1.0`. So, `Library` bumps it's dependency on `Interpreter` to `Interpreter 1.1.0` to side-step the bug. #### Question: What should the version of `Library` be?. #### Answer: `Library 1.0.1` (patchlevel change). #### Why? Because the API didn't change - specifically, there are no code changes necessary in `Tool` (or apps using `Tool`), so there's no need for even a minor bump in `Library`. Even if `Library's` API is augmented by a different interpreter version (`Interpreter 1.1.0`), it doesn't matter. #### Details: Although `Interpreter 1.1.0` can "extend" the API of `Library`, it isn't `Library's` responsibility to document and manage that API. That's because `Tool` can further bump the dependency on `Interpreter` to `Interpreter 1.2.0` - outside the control of `Library`. #### In practice: The interesting thing here is that users typically see `Interpreter` as a "special case", which is "outside" SemVer. So they expect `Library 1.0.0` to be bumped to `Library 2.0.0`, because the following case seems "broken": 1. User has `Interpreter 1.0.0` 2. User attempts to install `Tool 1.0.0` and the latest version of `Library` (`1.0.1`), expecting patch level to not have a significant impact. 3. Installation fails since `Library 1.0.1` cannot be used on `Interpreter 1.0.0`. 4. User freaks out and complains that SemVer has been violated. 5. Insanely long discussions ensure... #### Suggestion: So it might help if this case was accurately covered by SemVer. (That according to single responsibility rule, a change in patchlevel does not guarantee that dependencies will stay satiable). I think it's an error to suggest a major bump in this case because: you no longer can patch the previous major version with automatic upgrades. E.g. If `Library` was erroneously bumped to `2.0.0` (just to avoid the "install breakage" above), then `Tool` is still using `Library 1.0.0`, which not only contains the bug, but needs a dependency change to to the major version of `Library 2.0.0` to be fixed. And that misleadingly suggests an API change. (Which never happened). In short: a backward-compatible bugfix should be reflected by no more than a patchlevel change, regardless of the circumstances (dependencies or dependencies of dependencies needed for the patch). I'm wondering how to present this in a generic way (without the notion of an 'interpreter').
Author
Owner

@jwdonahue commented on GitHub (Dec 2, 2017):

@tvthompsonii, any reason to keep this issue open? If not, please close it at your earliest possible convenience.

@jwdonahue commented on GitHub (Dec 2, 2017): @tvthompsonii, any reason to keep this issue open? If not, please close it at your earliest possible convenience.
Author
Owner

@tvthompsonii commented on GitHub (Dec 2, 2017):

This seems to be an open debate, and no clear answer has been given. My recommendation would still be to add an optional build number to the spec. If you re-release really is just a rebuild, not something hitting the corner cases raised above such as needing to install a new set of dependent libraries, then a build number extension would seem ideal.
Closing this as other threads have picked up this conversation (and I have been requested to close).

@tvthompsonii commented on GitHub (Dec 2, 2017): This seems to be an open debate, and no clear answer has been given. My recommendation would still be to add an optional build number to the spec. If you re-release really is just a rebuild, not something hitting the corner cases raised above such as needing to install a new set of dependent libraries, then a build number extension would seem ideal. Closing this as other threads have picked up this conversation (and I have been requested to close).
Author
Owner

@epicfaace commented on GitHub (Jun 18, 2019):

What about upgrading a peer dependency (in this case React) by a major version bump? What is the recommended semver bump for this case?

@epicfaace commented on GitHub (Jun 18, 2019): What about upgrading a peer dependency (in this case React) by a major version bump? What is the recommended semver bump for this case?
Author
Owner

@ljharb commented on GitHub (Jun 18, 2019):

that’s absolutely a breaking change, because it forces consumers to upgrade the peer dep.

npm peer deps are part of your api - adding one is breaking, as is bumping it at all - not just a major.

@ljharb commented on GitHub (Jun 18, 2019): that’s absolutely a breaking change, because it forces consumers to upgrade the peer dep. npm peer deps are part of your api - adding one is breaking, as is bumping it *at all* - not just a major.
Author
Owner

@chrisknoll commented on GitHub (Dec 19, 2020):

that’s absolutely a breaking change, because it forces consumers to upgrade the peer dep.

npm peer deps are part of your api - adding one is breaking, as is bumping it at all - not just a major.

Are you sure about this? If you go from 3.0.1 to 3.2.2 in an npm package, don't you still have to do a npm install the same as if it was 3.0.1 to 4.0.2? So because the peers are forced to upgrade to their peer dep (based on a minor version update) your logic says that is a breaking change. I'm not so sure about that.

I'm currently faced with moving from springboot 1.5.3 to something in the 5.x line..and if I can keep all the existing application behavior and api the same going from 1.5.3 to 5.x, I'm thinking of doing this in a minor release. I use major release changes to indicate breaking backwards compatibility (ie features go away or the data model changes in some significant way (scalars become collections, etc).

I definitely would not call this a 'patch' release, simply because the scope of code changes goes beyond calling them a simple 'fix'.

But doing this move will result in zero documentation changes, zero api changes, zero behavior changes, so...that smells like a minor release to me. Even if there are new Spring framework APIs made available to the 5.x release, i think all those changes are internal-facing...

@chrisknoll commented on GitHub (Dec 19, 2020): > that’s absolutely a breaking change, because it forces consumers to upgrade the peer dep. > > npm peer deps are part of your api - adding one is breaking, as is bumping it at all - not just a major. Are you sure about this? If you go from 3.0.1 to 3.2.2 in an npm package, don't you still have to do a npm install the same as if it was 3.0.1 to 4.0.2? So because the peers are forced to upgrade to their peer dep (based on a minor version update) your logic says that is a breaking change. I'm not so sure about that. I'm currently faced with moving from springboot 1.5.3 to something in the 5.x line..and if I can keep all the existing application behavior and api the same going from 1.5.3 to 5.x, I'm thinking of doing this in a minor release. I use major release changes to indicate breaking backwards compatibility (ie features go away or the data model changes in some significant way (scalars become collections, etc). I definitely would not call this a 'patch' release, simply because the scope of code changes goes beyond calling them a simple 'fix'. But doing this move will result in zero documentation changes, zero api changes, zero behavior changes, so...that smells like a minor release to me. Even if there are new Spring framework APIs made available to the 5.x release, i think all those changes are internal-facing...
Author
Owner

@ljharb commented on GitHub (Dec 19, 2020):

@chrisknoll npm install doesn't install peer deps, and if you have a conflicting version installed, npm ls will start erroring, indicating your dependency graph is invalid. So yes, if you raise the minimum bar for a peer dep, ever, it is a breaking change. You can, however, extend it - ie, you can have a peer dep of ^1.5.3 || ^2 || ^3 || ^4 || ^5 - and this will just be semver-minor.

@ljharb commented on GitHub (Dec 19, 2020): @chrisknoll `npm install` doesn't install peer deps, and if you have a conflicting version installed, `npm ls` will start erroring, indicating your dependency graph is invalid. So yes, if you raise the minimum bar for a peer dep, ever, it is a breaking change. You can, however, *extend* it - ie, you can have a peer dep of `^1.5.3 || ^2 || ^3 || ^4 || ^5` - and this will just be semver-minor.
Author
Owner

@in-op commented on GitHub (Feb 16, 2021):

There is a very colorful discussion regarding the inclusion of the Rust compiler on a minor version bump for the cryptography python package here: https://github.com/pyca/cryptography/issues/5771

@in-op commented on GitHub (Feb 16, 2021): There is a very colorful discussion regarding the inclusion of the Rust compiler on a minor version bump for the cryptography python package here: https://github.com/pyca/cryptography/issues/5771
Author
Owner

@ljharb commented on GitHub (Feb 16, 2021):

@alex’s summary there fails to recognize that platform support requirements are part of the public API, and thus semver adherence would have avoided the issues in that thread.

@ljharb commented on GitHub (Feb 16, 2021): @alex’s summary there fails to recognize that platform support requirements are part of the public API, and thus semver adherence would have avoided the issues in that thread.
Author
Owner

@GeorgeWL commented on GitHub (Jun 26, 2024):

a related question, what if the package has a minor update due to adding an optional new feature e.g. 1.2.3 to 1.3.0, but you as the consumer are not using any of the code from that new feature.

should one increase your semver by a patch? or leave it as no change?

@GeorgeWL commented on GitHub (Jun 26, 2024): a related question, what if the package has a minor update due to adding an optional new feature e.g. 1.2.3 to 1.3.0, but you as the consumer are not using any of the code from that new feature. should one increase your semver by a patch? or leave it as no change?
Author
Owner

@chrisknoll commented on GitHub (Jun 26, 2024):

Semver isn't a 'how is it used?' statement, it is 'how has it changed'. New functions imply minor update so 1.23 to 1.3.0 is expected here. If it was backwards compatibility breaking, it would go to 2.0.0.

@chrisknoll commented on GitHub (Jun 26, 2024): Semver isn't a 'how is it used?' statement, it is 'how has it changed'. New functions imply minor update so 1.23 to 1.3.0 is expected here. If it was backwards compatibility breaking, it would go to 2.0.0.
Author
Owner

@ljharb commented on GitHub (Jun 26, 2024):

@GeorgeWL if you indeed don't care about any of the bug fixes or additions since 1.2.3, then there's no reason to upgrade.

@ljharb commented on GitHub (Jun 26, 2024): @GeorgeWL if you indeed don't care about any of the bug fixes or additions since 1.2.3, then there's no reason to upgrade.
Author
Owner

@jwdonahue commented on GitHub (Jun 26, 2024):

@GeorgeWL, what tool chain are you using? Are you purveying an app or a library?

... or leave it as no change?

No change, is never an option, if you're publishing an update. There should never be two different content publications with the same product name and version number.

As for the rest of your questions, it really depends on where that updated package lands. In most of the commonly used environments, the package lands somewhere that your customer will have access to it directly, independent of your usage. Many packaging schemes pile everything into one directory somewhere, and possibly also some form of cache. So, if you're publishing a library, that depends on that updated library, your bits and theirs wind up in the same scope as your customer's bits. If you're adding features to that pool, even ones you don't use, they may be features your customer must test or at least analyze the security surfaces of, before deploying.

If you're publishing a stand-alone application, that doesn't mix into the rest of your customers bits, or pollute any caches, then you can probably get by with a minor bump. You should really be aware of how your particular development silo works, from end to end, and apply common sense.

Without further context, no one blanket statement is the right answer to your question, but the minor bump for minor bump answer is mostly likely correct and rarely has down side.

Speaking of down sides, does your update include critical security fixes, independent of that updated dependency? If you want to ensure your bug fixes get taken up quickly, publish them without that library update first. Many teams have either automated ingestion or relaxed restrictions on taking up patches, but more constrained rules around new features and breaking changes.

@jwdonahue commented on GitHub (Jun 26, 2024): @GeorgeWL, what tool chain are you using? Are you purveying an app or a library? > ... or leave it as no change? No change, is never an option, if you're publishing an update. There should never be two different content publications with the same product name and version number. As for the rest of your questions, it really depends on where that updated package lands. In most of the commonly used environments, the package lands somewhere that your customer will have access to it directly, independent of your usage. Many packaging schemes pile everything into one directory somewhere, and possibly also some form of cache. So, if you're publishing a library, that depends on that updated library, your bits and theirs wind up in the same scope as your customer's bits. If you're adding features to that pool, even ones you don't use, they may be features your customer must test or at least analyze the security surfaces of, before deploying. If you're publishing a stand-alone application, that doesn't mix into the rest of your customers bits, or pollute any caches, then you can probably get by with a minor bump. You should really be aware of how your particular development silo works, from end to end, and apply common sense. Without further context, no one blanket statement is the right answer to your question, but the minor bump for minor bump answer is mostly likely correct and rarely has down side. Speaking of down sides, does your update include critical security fixes, independent of that updated dependency? If you want to ensure your bug fixes get taken up quickly, publish them without that library update first. Many teams have either automated ingestion or relaxed restrictions on taking up patches, but more constrained rules around new features and breaking changes.
Author
Owner

@ljharb commented on GitHub (Jun 26, 2024):

To clarify, the question was about when a library dependency publishes an update, do you need to update to it if you're not using anything in the update?

I'm sure it depends on the ecosystem, but in npm, semver ranges should mean that application consumers will get the latest by default, but aren't required to unless you've also updated.

@ljharb commented on GitHub (Jun 26, 2024): To clarify, the question was about when a library _dependency_ publishes an update, do you need to update to it if you're not using anything in the update? I'm sure it depends on the ecosystem, but in npm, semver ranges should mean that application consumers will get the latest by default, but aren't _required to_ unless you've also updated.
Author
Owner

@jwdonahue commented on GitHub (Jun 27, 2024):

a related question, what if the package has a minor update due to adding an optional new feature e.g. 1.2.3 to 1.3.0, but you as the consumer are not using any of the code from that new feature.

should one increase your semver by a patch? or leave it as no change?

The question, as I read it, has to do with what kind of version bump should you take, if any, on your own bits, if you update a dependency. The "should one increase your semver..." part of that seems to imply that something which is about to be published, was updated, why else would you consider a version bump, if you're not about to publish something?

@jwdonahue commented on GitHub (Jun 27, 2024): > a related question, what if the package has a minor update due to adding an optional new feature e.g. 1.2.3 to 1.3.0, but you as the consumer are not using any of the code from that new feature. >should one increase your semver by a patch? or leave it as no change? The question, as I read it, has to do with what kind of version bump should you take, if any, on your own bits, if you update a dependency. The "should one increase your semver..." part of that seems to imply that something which is about to be published, was updated, why else would you consider a version bump, if you're not about to publish something?
Author
Owner

@chrisknoll commented on GitHub (Jun 27, 2024):

If you update a dependency it should, at a minimum, be reflected in the patch version part of semver (major.minor.PATCH). However, the new dependency may expose new configuration options that your app can support. This is new functionality, so it would be at least a minor update (major.MINOR.patch). Sometimes, tho, a dependency may lead to a tool chain update (ie: this dependency requires JDK 17 which is different than the minimum supported JDK of 8) That type of toolchain update I'd consider major (MAJOR.minor.patch).

If you don't need or want the new dependency version, then you're not actually changing anything in your code, so no semver required because you're not releasing anything new.

@chrisknoll commented on GitHub (Jun 27, 2024): If you update a dependency it should, at a minimum, be reflected in the patch version part of semver (major.minor.PATCH). However, the new dependency may expose new configuration options that your app can support. This is new functionality, so it would be at least a minor update (major.MINOR.patch). Sometimes, tho, a dependency may lead to a tool chain update (ie: this dependency requires JDK 17 which is different than the minimum supported JDK of 8) That type of toolchain update I'd consider major (MAJOR.minor.patch). If you don't need or want the new dependency version, then you're not actually changing anything in your code, so no semver required because you're not releasing anything new.
Author
Owner

@remcoros commented on GitHub (Jun 27, 2024):

I settled on not thinking about dependencies at all anymore. If you consider all dependencies as your own internal code, it makes things really simple for both maintainers and end-users: breaking changes > Major, New features > Minor, Fixes > Patch. Doesn't matter if those come from "internal" changes or "external dependencies", just treat them as your own.

@remcoros commented on GitHub (Jun 27, 2024): I settled on not thinking about dependencies at all anymore. If you consider all dependencies as your own internal code, it makes things really simple for both maintainers and end-users: breaking changes > Major, New features > Minor, Fixes > Patch. Doesn't matter if those come from "internal" changes or "external dependencies", just treat them as your own.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/semver#95