QML Versioning

QML (and QtQuick) versioning is not the same as Qt versioning. This is understandably confusing for some people, so let me take the long-form approach and describe the what, why and how of QML versioning. Note: This is the essay version. The technical version with code snippets is in the docs where it should be. As an essay version it’s mostly “why”, for better answers to “what” clone the repository and look at the examples. For better answers to how clone and read the source code for the engine; qqmlengine.cpp contains fewer lines than this blog post 😉 .

What is QML versioning?

QML modules register types into a versioned namespace. This means that when you provide types to QML, you also must specify which versions these types exist in. Then when you import a module, e.g. ‘import QtQuick 1.1’, you import the types for a specific version. By QML versioning I am referring to this versioning system in the QML language, where you need to assign types to a specific version when you declare or import them. The version of the language will hopefully remain an implementation detail, because it doesn’t change that much.

Why does QML have its own versioning?

Because it’s a new language. C++ manages versioning with the details of linking in the shared libraries, QML needs something similar. Yet it also has to be something a little less technical than the offset specified for a symbol resolution, or at least easier to explain to less technical users.

What’s the problem versioning solves? The real problem that versioning solves is when you are using system libraries, and those system libraries get an update. If you aren’t using system libraries, then your app keeps working regardless because you chose which version to statically link or distribute. Even more specifically the problem refers to pre-existing software for the system. If you’re actively developing the software then you just need a bit of warning to recompile your app, and then let the package manager replace everything at once. So the main problem versioning solves, at least the one I was shown, is when you have a finished application left in the package manager or app store and moved on. The ecosystem would prefer not to throw your app away just because the system library changed – we’re assuming you left it because it’s perfect, done and verifiably bug-free (realistically, we’re assuming that some people still find it useful). Or maybe you moved on to develop another app or another version, and you just don’t want to drop everything to update your application in time for the system library update window. Another possibility is that subtle behavioural errors sneaked into the minor release and your application is so broken you need more time to make it work again. That last one we mitigate a little in reality but the theoretical framework says we fix that too 😉 .

With Qt when the Qt system libraries are updated the binary compatibility promises mean that you should be fine until the major version changes (which will break everything). The concern was, because this was around the time of the Nokia acquisition of Trolltech, that Symbian apps depending on Qt will want to use the system Qt instead of adding 10-20MB to every Qt application. Then if a Qt update is pushed out to devices, all applications needed to continue working; Symbian didn’t have enough apps to be able to afford losing any 😛 . At the very least, we needed to provide the QML version of binary compatibility.

The QML version of binary compatibility means to not interfere with symbol resolution. If you add new types or properties to a module, existing applications might be using those names already and a conflict ensues. In some cases this conflict leads to you using different types or properties and thus breaking existing behaviour. Unlike C++ symbols, QML as an interpreted language has much greater scope for confusion. Because of the object-oriented nature of C++, the most likely scenario I can think of is that if Qt added a new class and you didn’t compile Qt in a namespace then you might get a compile error when you redefine a class with the same name (hint: don’t start your classes with ‘Q’). In most cases it’ll still run fine, because the symbol is resolved to an offset inside your binary already. In QML a particular symbol lookup checks a whole hierarchy of contexts for the names of properties and items. If a property is added to an item, and that item has a binding to a property of the same name on the root item, then that could now resolve to the current item and you’ve changed the behavior of the app. If you had a Button.qml and we add a Button type to QtQuick, you might now resolve Button{} to mean a different type all together. Naturally we decided to take action to allow us to add properties or types ever again in the history of the language.

That problem is dealt with by forcing types into a specific version. When you add a new type you specify which version it was added in. The QML engine only loads types for that version, assuming that minor versions only add types and don’t remove them. You can also add properties, methods and signals to existing objects in later minor versions – if a lesser minor version is imported then those symbols aren’t available to muddy the waters. When the system Qt goes from 4.7->4.8 existing applications work just as before even if they had a PinchArea.qml or a property int lineCount on the root item. The binary interface for QDeclarativeEngine remained the same, it calls the new code to do roughly the same QML interpreting, but the QML interpreter can just pretend the new QtQuick 1.1 types and properties are not there. So a QtQuick 1.0 application will remain working until the application developer gets around to fixing it, should a potential conflict arise.

The level of detachment is even a little better than binary compatibility, because we don’t need d-pointers to handle protecting the backend. You could completely rewrite an element between versions and then register different C++ classes to the same type name at different versions. Complete backend rehaul, transparent to the user, sound familiar? This was the ideal for the QtQuick 2 transition as we knew we had to get away from Graphics View eventually.

As a quick digression, I’ll explain what’s wrong with Graphics View(GV). It’s not strictly worse, or better, than other options like SceneGraph(SG) – merely different. GV is great for multiple views on large numbers of static, custom items on a canvas. A 2D TBS with massive maps and thousands of units? GV could handle that with ablomb and easily allow you to have multiple views and minimaps (unless you animated the units). CAD applications were another thing GV’s feature set kept reminding me of. Not exactly the requirements for QtQuick though. Our items aren’t static, they’re highly animated. They aren’t combined custom items, they’re composed of many primitives. And QtQuick UIs never care about multiple views onto the ‘scene’ because it’s supposed to be a pixel perfect UI. Scene Graph is much better for these uses, with an additional embedded focus in the design to get the most out of hardware acceleration.

So that’s why the GV to SG transition was a foreseeable inevitability. What could theoretically happen given the versioning system is that we totally replace the backends for all the items and you don’t notice, you just import QtQuick 2.0 and suddenly everything’s faster. I remember when I was young and naive enough to believe that would actually happen…

Back in reality, there are two issues that prohibited such an easy transition. The major one is that it’s just too big a change. The displaying widget/window changes because it’s now a single integrated scene and view object. The completely different scene/canvas/window leads to a different base class for the item (QQuickItem has all the canvas integration now, no QGraphicsObject subclassing). The base class was pretty integral to the canvas anyways, as the definition of what was rendered originally came from traversing the tree of QGraphicsObjects and calling paint() to imperatively paint with QPainter. SG doesn’t so that, it generates paint nodes in updatePaintNode which will later paint the item using openGL. The canvas change done right just can’t be swept under the rug, this fundamental change to the base class and canvas is the reason why QtQuick 1 and QtQuick 2 don’t mix.

The other issue? QtQuick is too young to try to maintain that level of compatibility. Remember when you were young and did stupid things? I’m still there now, but the experienced reader is probably immensely grateful that they changed and over time the whole world forgot those things. QtQuick 1 had mistakes in it, and a major version change from 1->2 is the time to fix those mistakes regardless of the API incompatibilities. Some of this was just name changes, which are hard to justify, but for those that are obviously better names they needed to be fixed now or they’d never have a chance.

So now QtQuick 2 is a separate version that can’t mix in the same scene with QtQuick 1. While for the most part it’s API is compatible and the same, there is a bevvy of improvements you might need to adapt to. All listed on the porting page I hope.

How is this implemented?

Pretty much explained in the last section: the engine controls which symbols resolve to which types (or at all) based on both what is registered with the engine and what the user requested. The important point is that it’s done for you in the engine. Those who want to hack on the engine, I’ll just point you to QQmlType::availableInVersion, line 299 of qtdeclarative/src/qml/qml/qqmlmetatype.cpp . But that’s primarily to distract you from realizing that I don’t work with the engine that much 😉 .

Q’s

Now I will try to list some common questions and respond at moderate length. We’ll call it a Q ML session.

Q: Why not just use Qt versions?
ML: Note that we did in 4.7. The first thing we did was try to get that changed, because we realized we weren’t going to match the Qt release numbering with the QML version rules. QtQuick 1.1 was needed fast and Qt minor releases at the time were slow. QtQuick 1.1 came out in 4.7.4, and while arguably that’s more features than a patch release we justified it by the fact that no C++ API was added. C++ features crept in for other modules too in 4.7.x so maybe patch releases just weren’t being respected anyways. QML had just emerged from the chrysalis, and its development pace was faster than Qt because of it. This is still true, and I expect QtQuick 3.0 to come out during the Qt 5.x series. This is why Qt version semantics only apply to the C++ API and QML version semantics apply to the QML API.

Q: Why no Qt4Support like module for Qt Quick 1?
ML: Because Qt Quick 1 is in Qt 5. In terms of something that allows QtQuick 1 and QtQuick 2 in the same scene, the fact they use different canvases to render makes it very difficult to write a compatibility layer. Theoretically you could create a QtQuick1 element which renders a graphics scene into a QQuickPaintedItem and use that as a container. But the performance would be terrible, on par with QGraphicsProxyWidget probably, and it would take a while to write. From my perspective, it’s not worth putting a lot of effort into an element that you’ll just recommend everyone to pretend doesn’t exist.

Q: Why is Quick1 in Qt5?
ML: This is similar to how distros will probably package 4.x for some time – just that we’ve managed this separately for QML. QtQuick 1 applications shouldn’t be forced to upgrade overnight, and with the new modular nature of Qt the lean embedded platforms can easily remove it from the build. There are also some theoretical cases where your application can port pieces at a time from QtQuick 1 to QtQuick 2, and other theoretical cases wher QtQuick 1 development picks up again. Bottom line, recompile your application as soon as Qt 5 ships and then worry about QML porting at your leisure 🙂 .

Q: What about libraries of UI components which need to support multiple QtQuick versions?
ML: They get to weep quietly while they maintain two codebases. That’s what QtQuick did, although we restrict the feature development to QtQuick 2 now. It sucks, but I’m not aware of a solution given the radically different visual canvases that the element sets are based on.

Q: Are we going to have the same problem with QtQuick 3 in Qt 6?
ML: I hope not. Scenegraph is brand-new and should last a long time. Without a major change to the canvas, QQuickItem 3 should be compatible with QQuickItem 2 and then you can mix and match the version like the theory intends. That major version is much more likely to be because of significant redesigns to the element set, like maybe someone convinces me to replace positioners with layouts 😛 .

Q: What if I always want the latest and greatest?
ML: You don’t. I have heard people asking why you need to import one version instead of just getting the latest like you do with a system Qt. But that’s only true and viable between minor/patch releases, not major versions. So I hope no-one is actually thinking that you can just import any arbitrary later version and it’ll still work. Minor versions are supposed to be adding new functionality, so I don’t see why you’d care about that until you actually use that functionality (which means touching the code, you can update the version statement). The only reasonable part of that request I can think of is patch releases, if the backend becomes faster or more correct you might want that to just apply magically to your application. This is why you don’t pick a specific patch release in QML versions 😉 . Because QML versions are about the exposed API, there is no patch release. The patch release for QML is based on the C++ and so it’s the same as Qt’s. Qt 4.8.2 might be quite some time after QtQuick 1.1, but bugfixes for QtQuick 1.0 functionality still apply if you use QtQuick 1.0 from Qt 4.8.2. Pure QML modules can add a patch release number to their releases for packaging, i.e. you might find qtdesktop.1.1.2.i686 in your package manager, but the QML using them just imports 1.0 or 1.1 for the API and gets all the patches anyways.

Take-home summary: QML Versions control the API available to avoid symbol resolution problems, and the big break between QtQuick 1 and QtQuick 2 was just too big a break for even this version system to hide.

Edited 21/2/2013 to update doc links. Because it’s easier to find this post on the internet than to find the versioning docs directly.

One thought on “QML Versioning

  1. The type name conflict issue is more complex than you described in this post, for various reasons. In particular, the fact that composite types are currently dealt with in an entirely different manner to registered C++-defined types leads to a whole level of complexity which (imnsho) needs to be fixed in Qt 5.1.

    Great post, though – although I still think people will ask for the impractical even after reading this, unfortunately.

Leave a Reply

Your email address will not be published. Required fields are marked *