Merging breaking changes of internal API

From Qt Wiki
Revision as of 08:39, 22 August 2022 by Ivan.solovev (talk | contribs) (Update macro names)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
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

Note about macro naming
This article uses the macros QT_DISABLE_DEPRECATED_UP_TO and QT_WARN_DEPRECATED_UP_TO. Those macros are introduced in Qt 6.5. If you are using an older version of Qt, use QT_DISABLE_DEPRECATED_BEFORE and QT_DEPRECATED_WARNINGS_SINCE respectively.

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 taking effect until the specified versions. However, that carries with it a duty, on the part of whoever deprecates it, to ensure that nothing breaks when those versions roll around. One of them will cause the declarations it marks to provoke warnings (which may well be treated as errors) wherever they're exercised; the other will replace those warnings with missing symbol errors.

Precisely when warnings and disappearance will take effect depends on macros that configure the effects of deprecation, which you can use to test for mistakes in how folk have deprecated things. However, the simplest way to test your own deprecation won't break the rest of Qt is to locally edit the QT_DEPRECATED_VERSION_* macro you've added to your declaration to use version 4_0 and see what your build finds (without messing with the defaults for QT_WARN_DEPRECATED_UP_TO or QT_DISABLE_DEPRECATED_UP_TO). The modules that get warnings 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 deprecation lands. 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.