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

FAQ/Question: does semver attempt to deal with environmental assumptions/changes? #526

Open
tommyjcarpenter opened this issue Aug 8, 2019 · 30 comments
Labels
question Question about SemVer and use cases

Comments

@tommyjcarpenter
Copy link

tommyjcarpenter commented Aug 8, 2019

I have a question about whether semver attempts to capture breaking changes due to environment rather than API. I can give two separate examples:

  1. I upgrade my library so that it only works in my language interpreter/runtime version >=Y and axes support for language interpreter/runtimes < Y.
  2. I upgrade my library, which is used in software that runs on some platform X (maybe a kubernetes version etc), and now my library requires X version Y or greater; meaning you can no longer use my package if you are running platform X version < Y.

Does semver attempt to capture such incompatible changes? Meaning, when I am versioning my library, do either scenarios warrant a major bump?

I think probably the README on semver.org needs clarity, because even after carefully reading, I'm still in a "grey area" about what to do; In both examples, I can construct a code change without any API change at all, literally a patch change by the API, however it would be an unsafe upgrade for clients.

Now that semver is becoming somewhat unanimous, clients do things like "give me package foo < next_major.0.0", that is "upgrade me to the highest version less than the next major". But the examples I gave above would actually break those clients, because they would slurp up a breaking change, even though the API didn't break.

@ljharb
Copy link
Contributor

ljharb commented Aug 8, 2019

Dropping support for any platform or language version is something I’d always consider a breaking change.

Is there a justification for treating it as anything but major?

@tommyjcarpenter
Copy link
Author

@ljharb the problem is that semver.org's current verbiage is ALL about the public API, but in these cases, the public API did not change at all. So the question isn't about justification, it's about whether semver allows this as a patch change, and here is where I think the documentation could be clarified.

The missing piece is along the lines of "can this software still be installed/used in your ecosystem".

@ljharb
Copy link
Contributor

ljharb commented Aug 8, 2019

The API isn't just the interface, it’s also the semantics - and if you drop support, the semantics change.

@mikehwang
Copy link

What if a project drops support for a certain platform but dropping support did not have any impact to the project's code base? In order to comply with semver, the project should change the version and specifically bump the major version?

A concrete example of what I'm asking is if a python package used to be built for both python2 and python3 but then drops python2. One could just not build the python2 artifact. The semantic has changed but the code has not.

@tommyjcarpenter
Copy link
Author

@ljharb I think semver.org should discuss or clarify what you mean by "semantics" in this context. Again, I'm not agreeing or disagreeing, I'm saying semver seems unclear and that this is a grey area and I would like it to be clarified.

@wizzwizz4
Copy link

Well, the environmental assumption is part of the dependencies, so this is a question about whether to change the major version when the major version of a dependency changes.

@tommyjcarpenter
Copy link
Author

@wizzwizz4 it may not be a major version; for example dropping support for python3.4 and supporting python3.5+, just as an example (but my question is in no way meant to be specific to python)

@ljharb
Copy link
Contributor

ljharb commented Aug 9, 2019

It’ll be breaking for a python 3.4 user, so that makes the change breaking.

@wizzwizz4
Copy link

Well, if the new dependency isn't breaking, surely that's fine?

@jwdonahue
Copy link
Contributor

@tommyjcarpenter,

What the spec doesn't tell you, that every developer worth a chair at a workstation knows, is that an API is a contract between a publisher and a consumer. The spec, rightfully avoids minutia, and leaves as many options open to the developer as it can. It only defines the semantics of version strings and avoids delving into your fiduciary responsibilities. It does not do a great job of defining business rules for producers and consumers.

I think the assumption is, that you will take the spec and apply it to your relationships with your customers, to maximize your market penetration. You should be an expert at understanding your customers needs and how to meet them. Consider your customer base when choosing whether the changes you make are breaking or not. I suggest that if you publish a minor or patch version that actually breaks a large percentage of your customers, you will piss them off, and find that nobody will take your updates anymore.

@jwdonahue
Copy link
Contributor

@tommyjcarpenter, unless you require further clarification, please close this thread at your earliest possible convenience.

@grv87
Copy link

grv87 commented Aug 24, 2019

the assumption is, that you will take the spec and apply it to your relationships with your customers, to maximize your market penetration. You should be an expert at understanding your customers needs and how to meet them. Consider your customer base when choosing whether the changes you make are breaking or not

@jwdonahue, well expressed. How about including this in the FAQ?

@tommyjcarpenter
Copy link
Author

tommyjcarpenter commented Aug 24, 2019

@jwdonahue I believe the optimal close out here, from my perspective, is a new FAQ added, something along the lines of "What do I do if my change does not break my code API, but it breaks/changes a required system dependency"? And, an answer. (Judging from the comments thus far, the answer is going to be something not super authoritative like "use your best judgement").

I'm pretty surprised that this thread didn't have a more concrete answer. I was expecting Semver to be more opinionated in this case, but I suppose I was wrong.

@jwdonahue
Copy link
Contributor

jwdonahue commented Aug 26, 2019

@tommyjcarpenter

What the spec does reference officially, with regard to the semantics, are API's. Though it does mention packages in #3, it does not discuss the semantics of versioning packages, it merely assumes some sort of package is the medium of release for an API.

The spec leaves a little wiggle room for adopters/implementers to work with. That's a feature of the spec, and one of the reasons it has been so widely adopted. If you look closely at the official site, you will see that the word dependency is not mentioned in the Semantic Versioning Specification (SemVer) section. It's really not intended to be explicit how you manage dependencies, if at all, because the correct way to do that, varies across tool-chains, and the object of the version string itself (SemVer is often loosely applied to non-API like objects). It's intended to tightly describe and name, a very narrow set of semantics used to label API's, which may be nodes in a dependency graph.

You must pay close attention to what it is you are applying version strings to. An "API" has no dependencies. Dependencies are a property of whatever depends-on, the specified API. Whether the dependent is an application, library or package, is not relevant to SemVer. That is left to the tool-chain designers and developers to figure out. The library developers in most ecosystems never really version just their API. They often use package tooling that help them manage their network of dependencies.

If you are having problems with this in tool-chain X, I suggest you take it up with the maintainers of that tool-chain.

If you do not intend to issue a PR, please close this issue at your earliest possible convenience.


If you think about it, a good API is always devoid of implementation details. Dependencies are an implementation detail.

@ljharb
Copy link
Contributor

ljharb commented Aug 26, 2019

In npm, peer dependencies are part of the api - whether that’s good or not is immaterial.

@jwdonahue
Copy link
Contributor

@ljharb, I personally think that is one very good and correct approach for versioning packages.

@tommyjcarpenter
Copy link
Author

I feel a little uneasy closing out my own issue in the case where I don't really think it has been answered concretely. There's a lot of helpful discussion in here, but there isn't really any concrete position on this subject. Answers have been of the form "listen to your users" and "semver allows for flexibility/interpretation in cases [...]".

Moreover, I would like to be able to cite semver as I often do, by referring people to documentation when discussions arise. It is easier to make a case, for example "I bumped this major because a major system dependency was added" etc, if I can point to the semver docs/FAQ and say "see bullet 12", rather than having to argue with different opinions on whether or not a change warrants the bump. Stated alternatively, I was looking for semver to take a clear stance here.

If the semver team closes out the issue, fine, but I'm not going to close my own issue yet, as I still feel this is somewhat grey.

@ljharb
Copy link
Contributor

ljharb commented Aug 26, 2019

@tommyjcarpenter i think that you can assume a pretty clear stance: if your project worked for anyone, and they weren’t explicitly unsupported (via docs or package manager manifest or similar declaring their environment as unsupported whether it worked or not), and you break them, it’s semver major.

You could choose to be less charitable about it, and say that it’s only major if it broke explicitly supported users, but that shifts the burden and the suffering from you to an unknowable number of users, so i wouldn’t advise it.

@jwdonahue
Copy link
Contributor

jwdonahue commented Aug 26, 2019

@tommyjcarpenter

That's fair, but if you dig deeper, you'll find this discussion has been hashed out a lot over the past nine years, and I think all we got out of it was one additional FAQ entry. That's pretty damn clear to me. It essentially says, that SemVer applies to your API, nothing else. But your conundrum is really driven by the fact that you are applying SemVer to a collection that includes your API and package manifests. Now if you think about it, those package manifests are interfaces that extend your API to include references to your dependencies.

So, you can apply SemVer literally and ignore your breaking changes to the manifest, if you want to argue to your customers that you are in fact versioning your API, not those manifests. Call them implementation details. That would all be well and good, if your tool-chain supports that. In other words, if your tool chain blocks or flags transitive dependency issues, you're good to go. If not, you're just going to piss-off your customers. The spec already says exactly what it needs to say on this topic and you probably aren't going to hear anybody give you better advice than you've already gotten hear.

See #468, #366, #341, #169, which are among the list you'll get if you search on is:issue transitive. #468 has a whole other list of related threads.

@tommyjcarpenter
Copy link
Author

tommyjcarpenter commented Aug 27, 2019

@jwdonahue thanks for the read, some of that discussion was helpful. That FAQ answer only covers cases where it's a minor or patch change, however, reading @ljharb answer, the answer seems to be that there is also a case where a MAJOR bump can occur. If you agree, I think that FAQ could be extended.

For example, your package P upgrades dependency X, and X now requires a system dependency that is not automatically installed. Users of P now blow up, because they don't have the system dependency installed, but by P upgrading to the new X, P is really the culprit from the user's perspective. Does P bump the major? The question here is whether X's dependency on some system thing transitively makes it part of Ps API.

So, I think that existing FAQ entry could perhaps include a new case.

@jwdonahue
Copy link
Contributor

jwdonahue commented Aug 27, 2019

@tommyjcarpenter, what tool-chain are you using?

You'll have a better chance of getting a PR accepted for a new or modified FAQ statement, than any changes to the wording of the spec itself. Waiting for others to issue a PR for you, could be a very long wait. I think the current set of deciders on such a PR, might frown on it infringing on the package management space. The correct answer for your particular situation might be to bump the major version, but for a different tool chain, that might not be the case. Generally, these sort of issues should be sorted out with whoever maintains your packaging tools and release channels/feeds.

The current FAQ only enforces the meaning of the spec as it is written. It literally says you are versioning an API, and therefore the semantics only apply to that API. But also assumes tooling that can construct a graph of all your transitive dependencies from the information available. I encourage you to issue a PR, but be prepared to defend it. Whether or not a change in a transitive dependency is breaking, depends on exactly what you are versioning, and the tool-chain. Some tools understand transitive dependencies and some don't.

The current maintainers of SemVer are all maintainers of the most used tools that support or require SemVer version strings. Those tools vary in their intended use. Some are API/Library oriented, others are oriented around collections of stuff. They vary in their use and their support for managing transitive dependencies.

I started my tour here, thinking that I could help improve on this spec and maybe create or support some tooling around it. Then I spent a lot of time diving into the details on this topic. I've come to the conclusion that SemVer 2.0.0 is about as far we can get, for the problem it was originally intended to solve. I think it becomes much more complicated and less useful if we try to expand on exactly how one applies SemVer to anything that is more complex than a simple API. That is why I started VersionMeta and VersionSchema. The additional complexity required to expand on SemVer, requires a more flexible and, unfortunately, complex infrastructure.

@tommyjcarpenter
Copy link
Author

tommyjcarpenter commented Aug 28, 2019

@jwdonahue what do you mean tool-chain?

Here is an actual recent example. We have a python library that depends on Kubernetes underneath.

Kubernetes made breaking changes in 1.16: https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/
(related, this harsh tweet positing semver is dead because of it (false clearly): https://twitter.com/fuzzychef/status/1161061645876994048)

our python library was updated, such that it only works with k8s Vx.x or later.
But our python library doesn't install kubernetes; it assumes the user has it. After this update, If the user has an old version, upgrading to the new version of our library blows up the user. But our API didn't change at all; what changed was an underlying "system" dependency (kubernetes version).

Note that I'm talking about a non-python dependency here, a system or platform dependency if you will. This isn't just a transitive dependency question.

In this example, would you declare this to be a major bump in the python library, even though the API of the python library did not change at all? Essentially, the system requirements of our library changed, and it isn't backwards compatible.

@jwdonahue
Copy link
Contributor

jwdonahue commented Aug 28, 2019

@tommyjcarpenter

what do you mean tool-chain?

The chain that you pull to start your...., no really, by tool-chain, I am referring to the tools you use in your development and build environment to produce whatever it is you are producing. You specified Python, so I assume you are using a set of Python related compilers, packaging tools, etc. Those are your tool-chain. They are different than what you would find in the typical Java, C/C++ or C# environment. All of them have or don't have, their own conventions for correctly handling transitive dependencies. I am pretty sure that Python has it's own tooling for managing libraries or packages. What do they recommend?

Around every tool-chain, evolves a community of developers and common practices. As a purveyor of Python libraries, it behooves you to adhere to that community's common practices. If they say breaking changes in your transitive dependencies are breaking changes in your library, then you will not succeed if you do not mark such changes with a major version bump. It is not SemVer's place to tell them they are right or wrong.

As I said earlier, it is okay to apply SemVer strictly to your API changes, if the library/package tooling is capable of detecting transitive dependencies. Most of them don't do it at all, and the few that try, don't get it right, because they do not capture enough information. Hence the constant stream of pleading to modify the SemVer spec and/or FAQ. If you really dig into this topic, you will find that its theoretical roots evolve from graph and network theory and are provably NP hard in most cases and NP complete in some corner cases.

So what we are left with here, is that the maintainers of SemVer, each own a piece of some of the most used tool-chains in the industry and each comply with the spec in their own ways, with varying levels of automation and convention. Kind of like letting the fox manage the hen house. If you are one of the chickens, hoping for a better outcome, you are probably out of luck.

@tommyjcarpenter
Copy link
Author

@jwdonahue thanks. I gave you a concrete example; your thoughts on that may help me formulate the wording of the PR.

@KyLeggiero
Copy link

KyLeggiero commented Sep 21, 2019

I think people are too squeamish about changing the major version. If the interface that developers use to program their applications changes in some way where they can't just drop-in replace the new version in place of their old one, then change the major version. It's that simple.

That means classes and functions and all that, yeah, but it also means things like package type, supported toolchains, licensing, etc.. Anything that means how someone/something consumes your software.

Why there is so much fear of changing a number to a higher number, I will never know. If that fear consumes you, then don't make any of these breaking changes, instead continue making completely-non-breaking changes.

If you make changes to your software which are not 100% backwards compatible, but you present it as if it is 100% backwards-compatible (e.g. only changing the MINOR or PATCH number when MAJOR > 0), that's just being rude, and it decreases trust. Do you want to lose users? Because that's how you lose users.

@lindastella
Copy link

help!

@jwdonahue
Copy link
Contributor

@lindastella, please, if you have a question related to this thread, please ask it. If you need help with something else related to SemVer in general, start a new thread.

@niklasekstrom
Copy link

Hi @tommyjcarpenter. Regarding your example, I would argue as follows. Your program (the Python library) has holes (parameters) that are filled by code from the environment. Let P(X, Y, Z) be the program that uses the dependencies X, Y, Z. This means that you actually have not one program, but many programs, one for each possible combination. Not all such programs behave correctly. For example, if your program assumes an x86 processor to work correctly, but it's run on a Sparc processor, then the program will most likely not work correctly.

I would argue that these issues are unrelated to the specification of the API that you claim that your program adheres to. In practice, I think your program should do tests against its environment to see if all assumptions needed to execute correctly are satisfied, and if not it should terminate with a status message indicating that some dependency is missing.

In your example I would say that Kubernetes should have incremented its major version number when making backwards incompatible changes, as not doing so makes it impossible for your program to check to see that Kubernetes 1.16 doesn't satisfy the assumptions your program makes about its environment, which leads to your program failing.

@tommyjcarpenter
Copy link
Author

@niklasekstrom in the theoretical sense, I completely agree with your argument.

However, the practical workflow bothers me. Consider the following:

  • my version is 2.0.0.
  • my 2.0.0 works on kubernetes (k8s) version X.
  • I make a change that requires k8s version X+1.
  • I push my patch up as 2.0.1
  • i have a customer, who was happily using 2.0.0, upgrade to 2.0.1, because they think I'm following semver, and they are nastily surprised when my library doesn't work anymore. All else being equal (for example the customer is still running k8s vX), upgrading my library from 2.0.0 to 2.0.1 has blown their code up.

The last part is the part that gets me; in some sense semver version numbers are supposed to promote/promise safe upgrades of libraries. A client should in theory be able to use 2.99.99 of my library without blowing up.

I do not know how to mentally reconcile the idea of semver, and the idea of a patch version of a library introducing a "change that breaks all else being equal".

@jwdonahue
Copy link
Contributor

I do not know how to mentally reconcile the idea of semver, and the idea of a patch version of a library introducing a "change that breaks all else being equal".

If you can't wrap your head around it, then it probably should have been a major version bump.

I believe this topic has been discussed ad nauseam in this and many other threads. Unless you have further questions or intend to issue a PR, please close this thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Question about SemVer and use cases
Projects
None yet
Development

No branches or pull requests

11 participants