Merging breaking changes of internal API

From Qt Wiki
Revision as of 09:13, 13 July 2022 by EdwardWelbourne (talk | contribs) (typo fix)
Jump to navigation Jump to search

While public API cannot be removed except in a new major Qt release, the same does not hold for private API. However, simply removing private API in one module can break other Qt modules which depend on it. While the submodule update 'bot workflow allows those modules to keep reusing a version of Qt that is still compatible with them, at some point they have to move to the latest version of their dependencies. Thus, any time one removes or modifies existing API, one should follow the procedure below to provide a smooth development experience for everyone working on Qt.

Check if your change breaks other modules
If you suspect that your change might break other modules, it is generally a good idea to at least compile the essential modules before merging your change. In the following steps, we assume that you actually found some breakage. Otherwise, you can just merge your change.
Preferred Workflow
If you can, try to split your change into two: One that adds the new API, and one that removes the old API. Then:
  1. Merge the change which adds the new API
  2. Once each dependent module has been updated to use a SHA that has your change in, create a commit to port its usage of the old API to the new API.
  3. When all modules have been ported to the new API, integrate the change that removes the old API.
Alternative Workflow
Sometimes, it may however not be possible, or may be very inconvenient, to split your change into two atomic changes as described above. A common occurrence of this is when a bug fix necessitates a behavior change of existing API, or a change to signature in a virtual API. In those cases, you can follow the following steps:
  1. Create commits to fix all modules and upload them to gerrit.
  2. Ensure that they get reviewed and accepted, but do not stage them yet.
  3. Integrate the breaking change.
  4. After the change has been integrated, add the submodule update 'bot as a reviewer to the changes in the other modules. Your changes will stage automatically with the next Submodule Update round.

Of course it's possible that you will miss some submodules that are broken. It can help to have a check-out with more than just the essential modules – ideally all modules – and search for uses of the API you're breaking, for example using find . -type f -print | xargs grep APIname on Unix. In any case, others will notice your change blocking the submodule update 'bot, use git blame and/or git bisect to hopefully find your change, and comment on your Gerrit review telling you what you've broken. You can then fix the breakage using the alternative workflow, above.

Deprecations

When you deprecate an API in a module, whether public or private, you need to treat it as a breaking change in all modules that use the newly-deprecated API. The combination of #if QT_DEPRECATED_SINCE(maj, min) and QT_DEPRECATED_VERSION_maj_min (or its _X_ variant) lets you defer the deprecation from biting until the specified version, so you need to test-compile with QT_DEPRECATED_WARNINGS_SINCE set to QT_VERSION_CHECK(maj, min, 0) (with versions matching your QT_DEPRECATED_VERSION_maj_min) to catch the modules that you're breaking. (If you run into problems with others' deprecations breaking such a build, ask git blame what change introduced the deprecation and comment on its gerrit review. You can work round such breakage by locally changing your deprecation to use QT_DEPRECATED_VERSION_4_0 (or its _X_ variant) and seeing what your build finds with the default QT_DEPRECATED_WARNINGS_SINCE.) The modules that show problems with this shall need fixes, in the same manner as above.

In the common case where you're adding a new API and deprecating an old one, the two-step process above can be used, first adding the new API, then updating each of the the other modules to use it, once their dependencies.yaml lists a version of the upstream that includes your new API, finally staging your deprecation change to the upstream.

Of course, there is always the risk that someone unaware of your change will add new uses of the old API in between your fixing of the dependent modules and the point where your deprecations land. The submodule update 'bot will typically catch this and someone will most likely comment on your deprecation review letting you know about it. A subsequent fix to the new uses of the deprecated API can then resolve that, as in the alternative workflow described above.