Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Please add a FAQ entry about platform support #716

Open
mvz opened this issue Jun 25, 2021 · 73 comments
Open

Please add a FAQ entry about platform support #716

mvz opened this issue Jun 25, 2021 · 73 comments

Comments

@mvz
Copy link

mvz commented Jun 25, 2021

I've been in several discussions about what to do about supported platforms (i.e., for Ruby gems, versions of Ruby, for npm packages, versions of node).

Here's a recent example: #708

I see two possible positions:

  1. Semver is only about the documented API, so similar to updating dependencies, dropping supported platforms could even happen in a patch release.
  2. Dropping a supported platform is a breaking change so that should only happen in a major release.

(I have a preference but that is not really relevant to this ticket!)

There is already an entry in the FAQ section about updating dependencies. Having one for platform support would help projects a lot in adhering to semver.

@ljharb
Copy link
Contributor

ljharb commented Jun 25, 2021

Supported platforms are always part of the documented API. A FAQ entry would be nice but it doesn't change that that's how it works.

@mvz
Copy link
Author

mvz commented Jun 25, 2021

@ljharb this ticket is explicitly about a FAQ entry. I have not heard from anyone but you on this, and you have already indicated you can't make a FAQ entry happen. I'd like te see a response from someone who can.

@krader1961
Copy link

It seems to me pretty obvious that semantic versioning only applies to the public API. Dropping support for a platform does not change the public API. Doing so should not result in a bump of the major version. I don't think this warrants a FAQ entry but I have no objection to adding such a FAQ clarification.

@ljharb
Copy link
Contributor

ljharb commented Jun 28, 2021

A breaking change is something that requires the consumer to make changes to their application after updating. Upgrading the platform version is included in this - dropping support for a platform is both obviously and objectively a breaking change.

@krader1961
Copy link

A breaking change is something that requires the consumer to make changes to their application after updating.

Obviously adding a platform is not a breaking change that warrants bumping the major number. Which is why removing a platform is not a breaking change. Anyone using a module on a platform that is no longer supported can simply pin their use to the last supported version. The project should ensure that if they do not do so their builds will fail. I don't think dropping support for a platform warrants bumping the major number.

@ljharb
Copy link
Contributor

ljharb commented Jun 28, 2021

@krader1961 no, adding a platform is adding stuff, so it's a minor version. removal is typically major, addition is typically minor.

The platform's support status is irrelevant.

@krader1961
Copy link

@ljharb, I am ambivalent with respect to your argument. Please point us to where "Supported platforms are always part of the documented API" is itself documented. Even for a different project. A platform might be dropped for many reasons. Such as the platform no longer being actively maintained. Dropping support for that platform is not a breaking change for every other platform.

I understand why you are making your argument. If a project drops support for a platform its public API for that platform has, technically, changed. But no other platform is affected and the affected users of the, now unsupported, platform can simply pin their dependency. It is not at all obvious that dropping support for a platform warrants changing the major version since the reason for dropping support has nothing to do with changing the public API other than for that platform.

@ljharb
Copy link
Contributor

ljharb commented Jun 29, 2021

“changing the API other than <changing the API for nonzero users>“

@07831968138
Copy link

F

@jwdonahue
Copy link
Contributor

jwdonahue commented Sep 5, 2021

This is a perennial issue that arises out of the fact that the spec talks about API's more than packages AND it has a broad definition of what an API is:

1 Software using Semantic Versioning MUST declare a public API. This API could be declared in the code itself or exist strictly in documentation. However it is done, it SHOULD be precise and comprehensive.

It turns out that in practice, we rarely ever actually version our Programming Interfaces (classic API's). Most of the SemVer implementing tools are in fact packaging tools, where the version string applies to an entire collection of interface/implementation code, data, binaries and metadata. The later normally includes something like a package manifest. The package manifests have come to be considered part of the public API, in many of these tool chains.

It's all about what exactly are you versioning? If it's a core set of function/method signatures in a header file, then implementation details regarding supported platforms would not be included, as literally specified by the SemVer spec. But the cases where arguments arise over breaking changes around supported platforms never arise in such pure API cases. They arise out of environments/tool-chains where the version string is applied to a package and its manifest, which tend to reference implementation details and dependencies.

@nVitius
Copy link

nVitius commented Nov 1, 2021

It sounds like the maintainers of semver have strong opinions on this one; I don't think discussing it further is really going to take us anywhere. This issue has gotten off-track though. To avoid further questions about how this should or should not work, can we please add an entry into the FAQ regarding this point of discussion?

@broofa
Copy link

broofa commented Dec 4, 2021

Stirring the pot...

We (the uuid module maintainers) are dropping support for node 8 and IE 11 after [email protected]. Specifically, we're dropping those platforms from our CI testing matrix and, moving forward, will be closing issues specific to those platforms as "WON'T FIX". So how should this be reflected in the release version?

The answer is self-evident: It has to be a major release. The fact we no longer test legacy platforms means that when a legacy-breaking change gets introduced (probably in the form of some otherwise minor change), we are unlikely to know about it.

Thus, the only responsible option is to do a major version bump now, even if everything else in the API remains unchanged.

@ljharb
Copy link
Contributor

ljharb commented Dec 4, 2021

@broofa indeed, that's a breaking change, so you'd need to bump to v9 (altho i'm sad about that decision)

@mvz
Copy link
Author

mvz commented Dec 4, 2021

@broofa you can certainly do that if you want, but semver as defined does not force you to do so.

@ljharb
Copy link
Contributor

ljharb commented Dec 4, 2021

@mvz yes it does, because users on node 8 will see their code no longer work when they install the update, which the spec forces you to mark as a breaking change.

@mvz
Copy link
Author

mvz commented Dec 4, 2021

@ljharb feel free to continue to ignore the meaning of the term 'API'.

@ljharb
Copy link
Contributor

ljharb commented Dec 4, 2021

The platform it runs on is part of the interface. When the bulk of the industry considers it included, and you don’t, then perhaps your definition of the term is the problem.

@broofa
Copy link

broofa commented Dec 4, 2021

@mvz @ljharb Are we arguing? If so, why? Aren't we all saying that supported platforms should be treated as part of the API, and that this project should be more explicit about that fact (either in the FAQ or the spec)?

@mvz
Copy link
Author

mvz commented Dec 4, 2021

@broofa Yes, we're arguing :-). I think the fundamental problem in all these discussions is the question of what exacly 'API' means. Obiously, @ljharb and myself disagree on this. It seems your opinion is more in line with that of @ljharb, while AFAICS, @krader1961 seems to agree with me.

The main problem I have with @ljharb's position is that it seems to make the use of the word API in the semver spec pointless, i.e., you could just replace

increment the MAJOR version when you make incompatible API changes,

with:

increment the MAJOR version when you make incompatible changes,

That said, I can totally accept if the understanding of 'API' has changed over the years (as has happened for many terms). So, I think the best way forward is for inclusion in the semver spec of a definition of how semver understands the term 'API'.

@ljharb
Copy link
Contributor

ljharb commented Dec 4, 2021

I think it's a reasonable argument that if someone doesn't understand that "API" includes the platform it runs on, that the document should clarify what API does mean (and always has meant).

An incompatible API change is any change that breaks the P - the program.

@jwdonahue
Copy link
Contributor

jwdonahue commented Dec 4, 2021

Let's not forget that transitive dependencies are handled differently in different environments. The spec contains a phrase or two that implies we should ignore those dependencies and focus on the interface, but then it also mentions packages. It does not contain sufficient language to delineate between versioning a package vs versioning an interface, and how the nuances of specific environments drives how dependencies should be handled in those two major use cases. Doing so would probably qualify as a breaking change.

The spec mentions solving the dependency hell problem, and then goes on to say in the FAQ that dependencies should not be considered. It implies that most of the causes of dependency hell should be ignored when versioning your API.

The spec has a dual personality that cannot be resolved without sufficient churn that it would require a major version bump. Something the maintainers seem loathe to do.

@jwdonahue
Copy link
Contributor

jwdonahue commented Dec 4, 2021

I would add that in an environment where the entire dependency sub-graph of any node is easily constructed from available data, it is sufficient to limit the scope of versioning decisions, for any one node, to reflect the type of changes for that node alone, without regard to dependencies. You would be saying "my changes may be breaking relative to my previous version of my own code" or "my changes should not be breaking relative to the previous version of my own code", and it is up to the environment to prevent consumers of that node from taking it, if any node in that sub-graph would break that consumer.

I think that was probably the original dream wrt to SemVer:

In systems with many dependencies, releasing new package versions can quickly become a nightmare. If the dependency specifications are too tight, you are in danger of version lock (the inability to upgrade a package without having to release new versions of every dependent package). If dependencies are specified too loosely, you will inevitably be bitten by version promiscuity (assuming compatibility with more future versions than is reasonable). Dependency hell is where you are when version lock and/or version promiscuity prevent you from easily and safely moving your project forward.

Try to visualize the sub-graph where green nodes are safe and red nodes contain breaking changes. If you apply the SemVer rules as @ljharb argues, then the whole sub-graph goes red and you lose the information you need to make informed decisions or even how to get unblocked. If you just apply them to your local changes, then the sub-graph distinguishes exactly which nodes are potentially breaking and you can more easily address them. As always, it's more complicated than that in reality.

The key is that it's not just that the top node in the sub-graph took a major version bump on an immediate dependency. Whether that sub-graph will break a particular consumer depends on the rest of that consumer's dependency sub-graph. No package owner can know the full superpositions of all their customer's dependency graphs, over their own dependency graph, so why not just say "hey, my node change is breaking relative to my previous state" and let their consumers resolve their own dependency graphs?

The answer to that question is the tooling we use to decide whether to take a package update isn't smart enough. We generally define our nearest dependencies in terms of version ranges. We say that we'll take P X.y.z where X==N. We don't say we'll take any P greater than N.y.z whose sub-graph will not break me with regard to the rest of my sub-graph. At least that is the general status-quo I am aware of at the moment.

Resolving the dependency graph is a provably hard problem, but might be tamable today.

The main difference between when the spec was first written and today, is the DevOps trend has exploded exponentially in the interim. To make that happen with the available tooling, we had to realize that the purist approach to interpreting SemVer wasn't practical, and encourage developers to reflect their immediate transitive dependency changes in their own versions. We broke SemVer's potential to help us get unstuck, because we didn't have the resources to resolve the entire dependency graph on the fly. So we're a little bit better off than a decade ago, but there is a lot of potential going forward.

That is why I have opposed making any spec changes that lock in the way we currently handle transitive dependencies on most systems. It would be better to establish that there is the ideal of communicating the effect of changes made to your own node's code, but that in practice, we have less than adequate packaging schemes that require us to manually communicate the status of our sub-graph in our own version numbers.

@ljharb
Copy link
Contributor

ljharb commented Dec 4, 2021

@jwdonahue it's impossible not to make the entire subgraph red, because "my code" directly uses the transitive dependency, so if it breaks, my code does too.

@jwdonahue
Copy link
Contributor

First, when I say node here, I am not talking about Node or referencing any particular environment.

Consider Pa depends on Pb N.y.z. The owner of Pa might consider the effort of making their code compatible with Pb N+1.y.z is reasonable and it doesn't involve them modifying their own interfaces (ignoring packaging and environmental details for now).

Now consider Pz => Pa where Pz has no other node in its dependency graph that takes a dependency on Pb. When that is the case, Pa's internal changes will not break Pz because Pz's own interface hasn't changed. The owner of Pa might have many more customers who's only dependence on Pb is through Pa (no diamond dependency problem here), than customers who have other direct or indirect dependencies on Pb (diamond dependency issue). It may in fact be the case that many of Pa's customers have already adopted Pb N+1 and wouldn't be broken by the transitive update through Pa.

It is only necessary for Pa to communicate whether they broke their own interface, not that they took a breaking change from a dependency. Provided the consumer can see Pa's full dependency graph, they can decide whether Pa's changes are breaking for them or not. Whether a node in the graph is breaking for you, depends on whether you take a direct dependency on its interface. If a node's interface is hidden from view, you should not care if one or more of your dependencies absorbed breaking changes from their dependencies.

There are also some environments where side by side execution of different versions of a component is possible and their build ingestion process doesn't halt on Pa's transition from Pb N to N+1. Whether this holds true depends on the tool chain and environment.

The spec should remain neutral wrt to tool chains and environments.

@broofa
Copy link

broofa commented Dec 5, 2021

@jwdonahue: I don't see how your comments help answer the question, "Are supported platforms part of the API". E.g. How does your Pa/Pb/Pz example map to my real-world uuid problem? If uuid is "Pa", where does the dependency on node, as a supported platform, manifest?

It seems like there are three options here:

  1. uuid is Pa, platforms aren't part of the public API, so the version doesn't change
  2. uuid is Pa, platforms are part of the public API but are internal, and so are reflected in the Pa version string (i.e. no Pb)... somehow?
  3. uuid is Pa, and platforms are subordinate nodes (Pb = node)

It is only necessary for Pa to communicate whether they broke their own interface, not that they took a breaking change from a dependency.

If we go with option 3, above, which I think is what you (and @mvz and @krader1961) are suggesting (???) then I read this as saying we needn't concern ourselves with informing users of platforms changes. But that's simply not an option in practice. Users on dropped platforms would crucify us for that.

So what am I missing here?

@jwdonahue
Copy link
Contributor

@broofa I wasn't even trying to answer your question or referring to uuid in any way. I was engaging with @ljharb and merely pointing out why I think we should be very careful about changing the spec or the FAQ around this issue. To some, the spec says "version your interface and implementation, ignore your dependencies" and to others it says "when you upgrade a dependency to the next major version, your major version must get a bump". I was trying to explain why that is and why we should be cautious. The spec is loose and there's these purist and pragmatist view points that are in conflict.

Not that it's remotely within my sphere of influence, but I would object if the spec did not allow for the purist's view. I really do think the real problem here is the lack of adequate tooling, not the spec. Well, when the tooling improves we could tighten the spec.

I really don't know enough about Node, NPM or Ruby to weigh-in on what is the appropriate convention. The issue had to do with transitive dependencies, and that is something I know a little bit about. If uuid is built in an environment where tooling or the environment is inadequate, and dropping support for a platform breaks your consumers, then by all means, bump your major version. That's just not universally the case, so we should be very careful about modifying the spec to cover it.

I recall related PR's in the past, some of which I heartily supported adoption of, but I see that they haven't been committed to master after many years. I can tell you that any suggestion that isn't accompanied by a PR, has near zero chance of adoption.

@ljharb
Copy link
Contributor

ljharb commented Dec 5, 2021

I don’t think the looseness is actually a benefit. In any ecosystem, without exception, if your users upgrade and things break, it should be a semver-major. It doesn’t matter whether the cause is first-party code change in the dep, or a transitive dep with a bug, or a transitive dep with a breaking change on that code path.

If bumping a transitive dep - even across a major - isn’t breaking for consumers, then it’s not semver-major; if it is, it is. There are no exceptions here and no room for interpretation.

@jwdonahue
Copy link
Contributor

I think the maintainers see it as a benefit. It allows them innovate while claiming adherence to a standard their customers are demanding. I agree that breaking your customers workflows is not acceptable, but I do not think that you can make the blanket claim that upgrading a dependency to the next major version always requires you to bump your own major version. It really does depend on your environment and tool chain. I know of at least one such proprietary environment that looks at the union of your dependency graph and its configured policies to determine whether to take an update or issue a warning or error. It detects circular and diamond dependencies and flags whether a specific forest of nodes has ever been tested before. But it's not a JavaScript environment.

@ljharb
Copy link
Contributor

ljharb commented Dec 5, 2021

Sure, I haven't made that claim. You can certainly update a dep's major and have it not be a breaking change for your consumers. However, if that dep drops support for a platform you support, it's a breaking change just the same as if you did that yourself.

@broofa
Copy link

broofa commented Dec 5, 2021

the real problem here is the lack of adequate tooling

I don't know what this means. What kind of tooling are we talking about?

Platform support isn't an issue specific to Node or NPM or Ruby. It's a universal problem for any software, whether you're writing a browser, or an OS, or a network driver or a BIOS. There's a certain "stack" of stuff on which your code sits. Communicating what that stack can and can't be is just as important as communicating the API. How is that a tooling problem?

@jwdonahue
Copy link
Contributor

Nothing in the supported universe of that product was broken by the change, therefore there was no breaking change.

If we have to bump the major version of our products every time we break a few users relying on an unsupported feature, we risk having supported users get locked-in to previous versions. Lock-in is bad. Every major version bump is a barrier to automatic ingestion of bug fixes.

@jwdonahue
Copy link
Contributor

jwdonahue commented Dec 17, 2021

@ljharb For the most part, I agree with you, but I don't think it's a Boolean choice. If someone argued that they only support P.v(LKG), I'd want to know what they are smoking. I think there's wide support for the idea that P.LatestLTS - N is far more reasonable, but there's going to be some lamenting exactly what N should be, but when P.IsEOL(v) is true, nobody should have to support it.

@ljharb
Copy link
Contributor

ljharb commented Dec 17, 2021

Nobody has to. But everyone has to do a semver-major bump when dropping it, because it breaks nonzero users.

@jwdonahue
Copy link
Contributor

jwdonahue commented Dec 17, 2021

Nobody has to. But everyone has to do a semver-major bump when dropping it, because it breaks nonzero users.

So far, nobody has been able to get language committed to the spec that is anywhere near that prescriptive. There's plenty of content here that argues that we don't have to promise not to ever break anyone without a major version bump. Common sense, IMO, argues that some corner cases need not be supported. It's just a matter of how thick skinned the publisher is vs how noisy the corner cases are. That will always be the reality, no matter what the spec says.


And to use your own words:

the semver spec says nothing about support status.

@EricMCornelius
Copy link

Nothing in the supported universe of that product was broken by the change, therefore there was no breaking change.

Seriously incorrect. Hence all the activity on said issues. Windows 7 is still in extended support via Microsoft and used throughout huge numbers of enterprises. Eliminating the ability to build properly security-patched software for still supported OSes used in the wild is not an esoteric edge case.

That being said.

I am asking yet again: who is the actual authority on the semver spec and intent?

@jwdonahue
Copy link
Contributor

As I have said before, the spec is the only authority. What the spec says, is what it means. What it doesn't say is just as important. I am not saying it's right, I just spent a lot of time in this space and I know that it has a loose definition of API, and the FAQ literally states that dependencies are beyond its applicable scope.

I have also been a proponent of tightening it up a bit wrt to dependencies, at least in certain environments where it makes sense to do so.

Windows 7 is still in extended support via Microsoft and used throughout huge numbers of enterprises

Yes, and they all pay Microsoft for that support. Has anyone offered to pay the owners of libuv/libuv to continue supporting it? It is OSS, so you are welcome to fork and extend its Windows 7 support for as long you like. Apparently somebody already did that. The owner of libuv/libuv is under no obligation to do so, even if someone offers to pay them.

@Spudz76
Copy link

Spudz76 commented Dec 17, 2021

The hilarious part is I'm pretty sure the original system call still exists even in Win11, so the correct move would be to leave the API alone until the legacy system call actually is removed, at which point swap in the "new" one because it's the only way to do it anymore. Only move to new API's when forced, not when someone said something is over. Nothing is over until it's actually not working anymore. If there is some feature the new API provides that is sufficiently performance enhancing then you're maintaining both flavors until the users confirm they are no longer using whatever legacy thing and then you can remove it (monkeybars method). This way, nobody made the decision, it was forced by reality, and not a single end-user can argue with that. They (we) will however argue incessantly about a false reality decreed on high by M$ or maintainers or really anyone.

Windows is nothing but API's, this library in question is just glue with an API on each side. The API provided did not change, but its function ceased to work, because it (prematurely) changed how it uses the Windows APIs on the other side. I would consider this an API change.

And there is a perfectly fine patch but they won't merge it because it's "too many lines"... every one knows one line of work takes 40 lines when you're dealing with M$ API's because you have to call stuff twice (once for sizing, once for the real call) and other idiocy like lines being 800 chars long if you kept it on one line so then code style decrees one line must be 10 lines... so it's not that many operations and very few if any vectors for bugs (their insistence that more lines === more bugs is at a minimum childish, like refusing to eat vegetables, pure petulance if not straight up power tripping)

The rest of the ecosystem is never going to follow on to using a corrected fork because it's not the authoritative version, unless of course the fork is decreed an official spin-off "for Windows 7 use-cases" and is maintained with backports. But then you may as well just accept the patch that already works great and turns the legacy support "ON" as-needed and functions entirely just like normal on every other platform/OS. The "lines" excuse is precisely laziness combined with something between paranoia and superstition. The "lines" literally evaporate when it's "not Win7" therefore doen't really "exist"... unless someone is doing something like still using Win7 (gasp! monocles!)

@jwdonahue
Copy link
Contributor

@Spudz76 That all has a ring of truth to it, but it's wasted here. The owners of that library are the deciders, however arbitrary, and they do have the wording of the SemVer spec on their side. It's not the first time I've seen the EOL of an OS version being used as an excuse to simplify the code base, and it won't be the last.

@EricMCornelius
Copy link

Yes, and they all pay Microsoft for that support. Has anyone offered to pay the owners of libuv/libuv to continue supporting it? It is OSS, so you are welcome to fork and extend its Windows 7 support for as long you like. Apparently somebody already did that. The owner of libuv/libuv is under no obligation to do so, even if someone offers to pay them.

Literally came here to point out the fact that the intention of the spec is ambiguous and used as an excuse for poor engineering practices and bikeshedding, and get treated to a masterclass in bikeshedding.

So I'll reiterate again: who is the formal maintainer of the spec who can authoritatively speak to the intention of where semantic versioning actually means anything about guarantees around breakage w.r.t minor and patch versions, and whether an API contract should be considered broken when it breaks existing usage or not?

This is the question. I've provided a concrete example where there is a lack of consensus on interpretation in a major project. Ergo ambiguity clearly exists and the spec warrants clarification of intent.

And if that intent Is that major version upgrades are not actually expected when breaking existing usage, so be it, but acknowledge that fact up front so that communities can migrate to more responsible versioning schemes.

@jwdonahue
Copy link
Contributor

@EricMCornelius

who is the formal maintainer of the spec

Read the CONTRIBUTING.md file.

@jwdonahue
Copy link
Contributor

@EricMCornelius

and used as an excuse for poor engineering practices

I call it wise engineering management, but there's always a few who insist that their specific corner case is worthy of continued support. Sometimes, you have to respond to it, some times you don't.

@FichteFoll
Copy link

FichteFoll commented Dec 19, 2021

The entire discussion, like any other similar discussion, solely concerns the definition of the "public API" or a tool or library, which is generally left for the authors or maintainers of that tool or library to define, because SemVer aspires to provide a largely generic approach that can be applied to many facets of software. In general, you are most likely to understand the scope of a public API in say a C header file, where the API is directly defined, but that may not be everything. Documentation is also part of the public API, including things such as deprecation notices, platform support, or other usage restrictions that a simple function signature cannot convey.

In that sense, I entirely agree with @jwdonahue in that if the library claims to only support operating systems that are supported by their vendors, then breaking your software for such an unsupported OS is not a breaking change since you didn't support it in the first place.
If such a statement was missing and the documentation would lead one to believe that such an OS was supported, then I agree that the SemVer spec is violated with a minor-level release and should have been major.

That's pretty much it, as far as the spec is concerned, and I highly doubt that will ever change. Any more specific claims regarding a public API can be made on top of SemVer, where SemVer just defines the common ground. Thus, each such decision must consider the context it is made in, which includes the kind of software, the kind of API it provides, the documentation that is provided, the target audience, etc., which is such a wild field that SemVer cannot possibly claim to address in all its facets.


For the particular case of libuv, the points you are making for a major and breaking change are reasonable and one half of the discussion. The other is that a major version release comes with a cost for all its users, because, per the SemVer contract, each user needs to evaluate whether they are affected by the break, which can take an unknown amount of effort. As such, a major version increase should only be done if the change is actually breaking, where we now enter the territory of whether it was a breaking change as per the "public API" or not. I won't go into more into detail because I haven't studied the documentation of libuv and don't intend to do so either, since I don't have any authority on the decision, nor do I particularly care personally. However, I want to iterate again that as far as I'm concerned, given the documentation was proper, there is no violation of the SemVer spec.

@pboling
Copy link

pboling commented May 2, 2022

I have always interpreted the SemVer spec as requiring a major version bump in case of dropping support for a platform. If a tool works on my platform, and I install a patch, I expect it to work.

I will always be surprised if a project stops working when the only delta is a patch update, and I'll internally consider this a bug, even if the project attempts an alternate framing. Therefore platform support can't be dropped in a patch, and it follows, can't be dropped in a minor update either, because it is obviously and implicitly, a breaking change.

This is a problem in Ruby, becuase the maintainer of rubocop (@bbatsov) inteprets the SemVer spec differently, (the narrow API version) and drops Ruby version support in minor updates.

Apparently his interpretation is common enough to warrant repeated hashing of this issue, and though I've been a supporter of SemVer for many years, if the spec isn't clear enough to put this discussion to rest it seems flawed.

I wrote about my isssues here:
https://dev.to/pboling/rubocop-ruby-matrix-gems-nj

@mvz
Copy link
Author

mvz commented May 3, 2022

@pboling the rubocop case is a little different because dropping a supported TargetRubyVersion is not caught by the installer, and usually people don't run rubocop on the lowest Ruby version they want to support with their gem.

So I'm fully in agreement with you that dropping a TargetRubyVersion (which is a feature and not a dependency) is a breaking change and should be accompanied by a major version bump.

@pboling
Copy link

pboling commented May 3, 2022

Rubocop has never done a major bump to accommodate dropping a Ruby, and 1.29.0 will drop Ruby 2.5, both from the syntax target (TargetRubyVersion) and from the minimum install supported Ruby, if current plans materialize unchanged.

However, I think both types of version support being dropped, from installing, to syntax parsing with the target setting, require a major version bump according to SemVer, and that appears to be what this discussion is about.

The maintainer doesn't see drops as changing the API in a breaking way, because it is a gem with a "real" API in the classic sense.

But almost no end users use that API, they all use rubocop via package managers, and command line. The current release strategy breaks the intended SemVer-based utility of ~> 1.0.

As has been made clear in previous comments, the platform is part of the interface of a tool. The fact that deviance from proper SemVer is somewhat mitigated by the required_ruby_version setting from the gemspec is irrelevant.

The fact that SemVer 2.0 is defined in a way that tramples the principle of least surprise is the issue.

@mvz
Copy link
Author

mvz commented May 3, 2022

Yes, I understand what the discussion in this issue is about.

I was just commenting on the fact that dropping a TargetRubyVersion is a problem orthogonal to your position on dropping support for a platform a gem runs on.

@bbatsov
Copy link

bbatsov commented May 3, 2022

Yep, also notice that we have an extended window for supporting Rubies that reached EOL at the runtime level - we'll now be dropping support for a Ruby release that reached EOL over 1 year ago. I totally agree that changes to TargetRubyVersion are a breaking change, but the runtime compatibility stuff is definitely a gray area, especially when this is unlikely to affect anyone in practice (given how old the Ruby 2.5 is).

I do agree, however, it'd be nice if SemVer's spec was clear on this issue.

@pboling
Copy link

pboling commented May 3, 2022

this is unlikely to affect anyone in practice (given how old the Ruby 2.5 is).

I disagree. Many gem authors who support older rubies in their projects. Rubocop is heavily used by FLOSS gems that have widely varying scopes of support, for example, one of the most popular gems, rspec, still supports Ruby 1.8.7.

If 2.5 is dropped outside a major version bump, I'll have to make a mental note of "remember to stop before the arbitrary and meaningless number 1.29!" when updating many gems. 1.29 would effectively be a "magic number".

Granted I just spent dozens of hours wrapping this problem in a SemVer (as I interpret it) solution, so I can avoid rubocop versioning altogether... https://GitHub.com/rubocop-SemVer

To put it simply:

it is precisely because neither Ruby, nor Rubocop, release patterns dictate when my projects drop support for ancient-but-still functional platforms, that I am invested in this.

My support efforts become harder when platforms are dropped without a major bump.

This is something which, until today, I thought was settled, accepting that various projects didn't follow SemVer for particular reasons.

EOL for a still-functional dependency should not be relevant to SemVer, IMO.

@igorpupkinable
Copy link

igorpupkinable commented May 3, 2022

I did not expect so much ambiguity around this. At the end of the day all of us change some data in some container (folder/file/repo/HDD/cloud/etc.). The tiniest data piece is a bit. So it can really go down to a simple question: will a change of one bit break any possible variation of a consumer out there?

  • Yes, it can > Breaking change (MAJOR).
  • No, it cannot > MINOR or PATCH.

The last thing consumer should worry about is where this changed bit of data belongs to, either manifest file, code file, image file, documentation file, README, .gitignore, generated dynamically, etc. We simply should not care as we are versioning deliverable in some format via some channel.

It seems too much effort is made to define what API is, where focus should be shifted to understand that something has changed and if a change in something can break consumers.

@mvz
Copy link
Author

mvz commented May 12, 2022

So it can really go down to a simple question: will a change of one bit break any possible variation of a consumer out there?

No matter what your opinion about platform version support is, this is emphatically not what SemVer is about. Fixing a bug might break a consumer.

@igorpupkinable
Copy link

@mvz

... version numbers ... convey ... what has been modified ...
https://semver.org/

So it is exactly about that. Changing anything in the text below (or equivalent) is a change, so it is author's responsibility to "convey what has been modified" and how it can affect consumers.

"engines": {
    "node": ">=14.15"
  },

@mvz
Copy link
Author

mvz commented May 23, 2022

@igorpupkinable what is your point? We're discussing breaking changes, not what has been modified in general.

@igorpupkinable
Copy link

@igorpupkinable what is your point? We're discussing breaking changes, not what has been modified in general.

Platform requirements change is a breaking change if you narrowing or restricting it.

@mvz
Copy link
Author

mvz commented Jul 15, 2022

@igorpupkinable you're ignoring the context of my comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

15 participants