mirror of
https://github.com/semver/semver.git
synced 2026-03-25 04:22:25 -05:00
Version after dependency changes? #95
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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?
@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 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.
@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.
@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.
@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...
@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):
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
Awesomenesswith 2 public methodsFixAllTheThings()andDrinkAllTheBeer().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?
Lib A is updated to 12.4.0 with some minor non-breaking additional features, what should you do?
Lib A is updated to 13.0.0 because 'FixAllTheThings()' has been removed, what should you do?
Awesomenessin your public API, and can work around it without changing your public API, this is a patch and you should release 1.1.2.Awesomenessin 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.Awesomenessin your public API, but relied onFixAllTheThings()to a point that you have to remove features in your public API, this is a major and you should release 2.0.0.Awesomenessin your public API, this is a major and you should release 2.0.0.@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.
@EddieGarmon commented on GitHub (Aug 3, 2013):
if you have to recompile, something changed
@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?
@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.
@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).
@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:
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:
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.
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).
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.
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.
@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?
@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.
@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:
Tool 1.0.0depends onLibrary 1.0.0.Interpreter 1.0.0.Tool 1.0.0andLibrary 1.0.0declare dependency onInterpreter ~> 1.0Interpreter 1.0.0is no longer supported and has outstanding bugs. These manifest inLibrary 1.0.0and are fixed inInterpreter 1.1.0.So,
Librarybumps it's dependency onInterpretertoInterpreter 1.1.0to side-step the bug.Question:
What should the version of
Librarybe?.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 usingTool), so there's no need for even a minor bump inLibrary.Even if
Library'sAPI is augmented by a different interpreter version (Interpreter 1.1.0), it doesn't matter.Details:
Although
Interpreter 1.1.0can "extend" the API ofLibrary, it isn'tLibrary'sresponsibility to document and manage that API. That's becauseToolcan further bump the dependency onInterpretertoInterpreter 1.2.0- outside the control ofLibrary.In practice:
The interesting thing here is that users typically see
Interpreteras a "special case", which is "outside" SemVer. So they expectLibrary 1.0.0to be bumped toLibrary 2.0.0, because the following case seems "broken":Interpreter 1.0.0Tool 1.0.0and the latest version ofLibrary(1.0.1), expecting patch level to not have a significant impact.Library 1.0.1cannot be used onInterpreter 1.0.0.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
Librarywas erroneously bumped to2.0.0(just to avoid the "install breakage" above), thenToolis still usingLibrary 1.0.0, which not only contains the bug, but needs a dependency change to to the major version ofLibrary 2.0.0to 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').
@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.
@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).
@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?
@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.
@chrisknoll commented on GitHub (Dec 19, 2020):
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...
@ljharb commented on GitHub (Dec 19, 2020):
@chrisknoll
npm installdoesn't install peer deps, and if you have a conflicting version installed,npm lswill 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.@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
@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.
@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?
@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.
@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.
@jwdonahue commented on GitHub (Jun 26, 2024):
@GeorgeWL, what tool chain are you using? Are you purveying an app or a library?
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.
@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.
@jwdonahue commented on GitHub (Jun 27, 2024):
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?
@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.
@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.