I. L'article original

Le blog KDAB est rédigé par les ingénieurs de KDAB s'occupant des formations, de la consultance ainsi que du développement (de Qt et de produits additionnels). Vous pouvez trouver les versions originales.

Cet article est une traduction de l'article original écrit par Stephen Kelly paru le 11 juin 2012.

Cet article est une traduction de l'un des articles en anglais écrits par KDAB. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à KDAB.

II. Introduction

Par rapport au portage de Qt 3 vers Qt 4, les classes centrales n'ont pas connu de gros nettoyages de leurs API ni de nouvelles bibliothèques en remplacement des anciennes (comme QPtrList et QValueList remplacées par QList, les « itemwiews » remplacent Q3ListView et les « graphicsviews » remplacent les API Canvas) et les modifications qui compilent mais affectent le comportement à l'exécution, comme la méthode QWidget ::show qui devient non virtuelle et la méthode « painting » qui devient restrictive sur le paintEvent.

Certes, l'effort de portage n'est pas nul, cet article résume quelques-unes des étapes requises pour porter KDE vers Qt 5.

Image non disponible

KDE PIM est une des dernières parties à avoir été portée complètement vers Qt 4 et kdelibs 4. Le portage vers Qt 5 sera donc plus rapide.

III. Avant le portage

Dans la stratégie de portage, il convient d'effectuer une compilation du code avec l'ancienne et la nouvelle version de Qt. Ceci permettra alors de contrôler les parties du code qui devront être modifiées lors du portage. Il faudra également s'assurer que les tests unitaires continuent à fonctionner durant le temps de portage, les régressions résultant du portage du code seront facilement distinguées des bogues introduits par Qt 5.

III-A. Portage du support de Qt 3

Le portage des logiciels vers Qt 5 peut démarrer par la modernisation de la base du code vers Qt 4.

Un des changements les plus marquants de Qt 5 d'un point de vue portage d'une ancienne base de code est la suppression du module Qt3Support et la suppression des API de ce module. Dans la plupart des cas, le code de Qt3Support consiste en une méthode qui a été renommée dans Qt 4. L'ancienne méthode appelle la nouvelle, comme la méthode QWidget::setShow() qui appelle la méthode QWidget::setVisible(). Certaines parties de KDE utilisent encore ces vieilles méthodes et ces mêmes parties utilisent encore des vieilles bases de code.

Le portage des API Qt3Support vers Qt 4 est une des étapes nécessaires et inévitables d'un portage de Qt 4 vers Qt 5, même s'il est possible en théorie de compiler Qt3Support avec Qt 5 pour certains points.

III-B. Correction des fichiers d'en-têtes

Un des changements majeurs dans l'infrastructure interne de Qt 5, par rapport à Qt 4, est la séparation des widgets du module Qt Gui vers un nouveau module, Qt Widgets. Ceci requiert des modifications du système de compilation, mais aussi dans les inclusions de fichiers d'en-têtes qui n'étaient pas présentes auparavant ; certaines inclusions ont été supprimées des fichiers d'en-têtes qui restent maintenant dans le module Qt Gui. Une des manifestations les plus courantes est de devoir ajouter #include « QDrag » alors qu'il n'y en avait pas besoin avant. Ceci est dû à la non-inclusion du fichier d'en-tête « gui/kernel/qevent.h », mais ce n'est plus le cas dans Qt 5.

D'autres problèmes venant des inclusions dans le portage de Qt 4 vers Qt 5 sont de modifier les en-têtes des classes qui ont été déplacés vers le module Qt Widget. Tandis que la base de code Qt 4 pourrait utiliser :

 
Sélectionnez
#include <QtGui/QWidget>

Son équivalent Qt 5 devra utiliser :

 
Sélectionnez
#include <QtWidgets/QWidget> 

Ou, plus probablement (qui fonctionne avec Qt 4 et Qt 5) :

 
Sélectionnez
#include <QtWidget>

Un petit script peut être utilisé pour effectuer les changements. Comme la suppression de l'utilisation Qt3Support, le nettoyage des inclusions peut largement être achevé dans une base de code Qt 4 avant le portage.

III-C. Correction des définitions de plateformes

Il semble y avoir quelques modifications à apporter dans les codes de base de Qt et KDE concernant les plateformes spécifiques, utilisant les macros Q_WS_* à la place de Q_OS_*. Par exemple :

 
Sélectionnez
#ifdef Q_WS_WIN
// Appel de l'API Windows
#endif

à la place de :

 
Sélectionnez
#ifdef Q_OS_WIN
// Appel de l'API Windows
#endif

Dans Qt 5, les macros Q_WS_* ont été supprimées, le code les utilisant ne pourra plus être compilé. Quand c'est approprié (c'est-à-dire quand le code contenu dans l'enveloppe est spécifique au système d'exploitation et non spécifique au système Windows), chaque code peut et doit être porté dans les macros Q_OS_*.

III-D. Macros Q_OBJECT oubliées et nettoyage des métatypes

Qt 4 peut pardonner quand une sous-classe de QObject requiert l'utilisation de la macro Q_OBJECT, seulement ceci peut apporter des bogues lors de l'exécution dans le système QMetaObject. Ceci ne change pas dans Qt 5, mais, si on tente de mettre un pointeur vers un type dérivé de QObject dans un objet QVariant en utilisant la macro Q_DECLARE_METATYPE, on peut obtenir une erreur de compilation dans Qt 5 (comme dans Qt 4, mais ce sera une autre erreur de compilation). Ceci est dû à Qvariant, qui enregistre maintenant le fait que le type qui est enregistré est un pointeur vers un type dérivé de QObject, ce qui est utilisé pour des fonctionnalités intéressantes dans Qt Declarative, les langages de bindings et d'autres bibliothèques qui utilisent énormément d'API d'introspection de QMetaObject.

Un autre type d'effet est que l'argument typé dans la macro Q_DECDLARE_METATYPE doit être défini complètement, mais non déclaré. Comme l'exemple ci-dessous, mais qui ne compile pas :

 
Sélectionnez
class MyType;
Q_DECLARE_METATYPE(MyType); 

La macro doit être déplacée à un emplacement où MyType est déclaré complètement (comme le fichier d'en-tête où il est déclaré). Dans certains cas où MyType est dérivé de QObject, la macro doit être retirée entièrement.

III-E. Refactoring

L'un des changements majeurs de Qt 5 est un bon focus sur QML, un langage interprété pour la création d'interfaces graphiques, et Qt Quick, l'API qui accompagne ce langage. Bien que Qt Widgets soit toujours disponible, testé et fonctionnel sous Qt 5, l'attention sur les performances et les bénéfices des interactions avec l'utilisateur ont été portés vers QML.

Comme QML est un langage interprété et ne possède pas les mêmes contraintes sur les types sécurisés dont dispose C++, il est largement destiné à être utilisé avec les modèles de données représentés par des sous-classes QObject et leurs propriétés et autres types qui peuvent être contenus dans un objet QVariant.

Si la part des buts de portage d'une base de code vers Qt 5 est d'augmenter l'utilisation de QML, alors l'accent peut être mis sur la refactorisation du code existant pour qu'il soit plus logique et plus représentatif du modèle - la représentation de l'état de l'application et de son contenu - et séparé des widgets utilisés par l'application. Ce type de refactoring peut être effectué avec un code de base Qt 4. Le travail d'un portage expérimental vers QML peut aussi être fait avec une base de code Qt 4 pour vérifier le concept de refactoring. Ceci est un bel effet de QML devenu disponible durant les versions de Qt 4 - en effet, il est présent dans la bibliothèque Qt 5 Support.

III-F. Portage depuis QWS

Le système QWS ne fait pas partie de Qt 5 et ces API peuvent être supprimées. Le code utilisant ces API aura besoin d'être porté vers le nouveau système QPA, qui est la partie centrale de Qt 5. QPA est actuellement introduit dans Qt 4.8 (quoique l'API Qt 5 soit quelque peu différente).

Il est possible de porter ce code vers QPA avec Qt 4.8, avec des modifications mineures pour Qt 5, mais on aura largement le même design en haut niveau. Il n'y a pas de documentation des API de QPA dans Qt 4.8 mais il existe quelques références d'implémentation pour comparaison.

IV. Portage

Les étapes de portage sont décrites pour des sources compatibles avec Qt 4, ce qui veut dire que la maintenance régulière du projet de base en Qt 4 doit être terminée et qu'il est envisagé de se tourner vers Qt 5. Certaines API ont vu leurs sources modifiées avec le passage vers Qt 5, la plupart étant indiquées dans les fichiers de modifications. Dans la plupart des cas, ces problèmes ne sont pas pertinents sur du code « normal », car c'est rarement utilisé ou les modifications peuvent apporter des problèmes sur des cas mineurs.

Néanmoins, ces changements doivent faire partie de la stratégie de portage. Comme cela cause nécessairement des différences dans la base de code entre Qt 4 et Qt 5, il faudra alors supprimer le support de la compilation depuis Qt 4 ou entourer le code des directives de préprocesseur #ifdef pour les autres versions.

IV-A. Chargement des plug-ins

Un autre fardeau significatif du portage est le système de plug-ins lorsque le code utilisateur requiert le chargement d'un plug-in. L'outil moc est maintenant responsable de la génération des métadonnées du plug-in, via une macro du préprocesseur dans un fichier C++ (Q_EXPORT_PLUGIN2 dans Qt 4), mais dans Qt 5 une nouvelle macro doit être utilisée dans le fichier d'en-tête, que moc pourra voir.

Ce processus est décrit par Lars et est relativement direct. Il faut alors modifier le code contenant la directive comme KDE avec K_EXPORT_PLUGIN.

Une telle enveloppe de la nouvelle macro ne serait pas possible avec Qt 5 car moc n'en fait pas d'analyse syntaxique (moc ne faisant pas le prétraitement de manière générale).

IV-B. Saut des tests unitaires

Un des nombreux changements dans les sources et incompatibles avec Qt 5 se trouve dans le module QtTestLib (un changement dans la macro QSKIP). Dans Qt 4, cette macro prenait deux arguments ; dans Qt 5, seulement un.

Ceci pose un problème de portabilité. La solution dans KDE est de créer une macro qui prend toujours deux arguments et en passe un sous silence en mode Qt 5. Ceci est fait dans l'optique d'une suppression future lorsque la compilation en mode Qt 4 ne sera plus requise dans KDE.

Une autre solution a été introduite il y a quelques temps, permettant d'utiliser C99 et C++11. Si vous activez l'option -std=c++11 quand vous compilez votre logiciel, la compatibilité des sources sera restaurée.

IV-C. QMetaMethod::signature

Dans les logiciels qui utilisent massivement le système d'introspection de QMetaObject, il est relativement commun d'utiliser l'API QMetaMethod::signature. Dans Qt 4, elle retourne un const char*. Dans Qt 5, cependant, la valeur retournée est construite dynamiquement, elle est donc d'un autre type (cela n'a pas de sens de retourner un pointeur vers une valeur temporaire). Maintenant, la méthode équivalente retourne un type différent (QByteArray) et possède un nom différent (QMetaMethod::methodSignature). Un portage naïf vers la nouvelle méthode peut créer des bogues à l'exécution :

 
Sélectionnez
// Vieille base de code Qt 4. La variable const char * retournée
// est assignée à une variable locale et utilisée
const char *name = mm.signature();
otherApi(name);
 
// Nouvelle base de code Qt 5 portée naïvement
const char *name = mm.methodSignature();
// Oh-oh, la variable name est un pointeur. La variable QByteArray
// retournée par la méthode methodSignature a déjà supprimé la donnée.
otherApi(name); // Un plantage s'apprête maintenant à exploser.


Le bogue à l'exécution peut être évité en définissant QT_NO_CAST_FROM_BYTEARRAY lors de la compilation du code. Ceci peut et doit être activé dans une base de code Qt 4. La méthode est renommée et la modification du type de la valeur retournée peut être effectuée dans une étape de portage indépendante.

IV-D. Modifications des méthodes virtuelles

Un petit nombre de méthodes virtuelles a changé de signature dans Qt 5. Cela ne cause pas d'erreur de compilation durant le portage (excepté dans le cas de modifications de méthode virtuelles pures), mais causera des erreurs à l'exécution quand la méthode « ignorée » ne sera pas exécutée. Un autre exemple est le signal QAbstractItem::dataChanged qui gagne un paramètre.

Il y a plusieurs solutions pour résoudre ceci. Le nouveau standard C++11 a une syntaxe pour indiquer qu'une méthode particulière dans une classe est une méthode ignorée d'une méthode virtuelle dans une des classes de base. L'utilisation de cette syntaxe peut être une étape de pré-portage provoquant des erreurs durant l'étape actuelle de portage. Les erreurs de compilation sont toujours préférées aux erreurs d'exécution, car elles sont plus simples à trouver (bien sûr, on ne peut pas les éviter).

Une autre option est d'activer les avertissements du compilateur pour trouver le problème. GCC peut afficher les avertissements si une méthode dans une classe dérivée cache une méthode virtuelle dans une classe de base (-Woverloaded-virtual). Cela peut être activé dans votre système de compilation comme une étape de pré-portage, l'étape de portage peut donc être plus explicite. De plus, comme le programmeur pragmatique l'a enseigné, il faut toujours utiliser les hauts niveaux d'options d'avertissements disponibles avec le compilateur, aussi vous pourrez ajouter ceci à votre système de compilation.

V. Après le portage

Tous les logiciels ont besoin d'être maintenus, pas seulement écrits (ou portés, dans le cas présent): il y a des étapes supplémentaires à réaliser pour terminer le portage de Qt 4 vers Qt 5.

Cela signifie éventuellement qu'il ne sera plus possible de compiler avec Qt 4 et Qt 5 dans la même branche, avec le même code. Avoir un portage terminé avec une base de code Qt 5 et avec tous les tests unitaires qui passent marque le point où il faut commencer à utiliser différentes branches pour les versions basées sur Qt 4 et basées sur Qt 5. Une conséquence de ceci est de permettre de porter le code utilisant les API de Qt 5 et qui est déprécié dans Qt 4.

V-A. Portage des méthodes dépréciées

Les méthodes dépréciées sont des méthodes qui existent dans Qt 5, mais qui sont devenues obsolètes ou ont été remplacées par d'autres méthodes. Dans le cas de Qt 5, une large partie de l'API a été conservée pour garder une compatibilité avec Qt 5, et ainsi faciliter le portage, de façon similaire avec les méthodes originales de Qt 3 dans Qt 4.

Il est bénéfique de porter également les méthodes dépréciées le plus tôt possible, car il y aura des avertissements durant la compilation, qui peuvent donner plus ou moins de messages d'erreurs selon le compilateur, aussi car les nouvelles API peuvent être plus rapides et utilisent des concepts qui vont de pair avec Qt.

V-B. Portage vers QML

Le portage des interfaces graphiques existantes vers QML est une étape optionnelle dans un portage vers Qt 5 qui peut commencer avant ou après le portage vers Qt 5.

La version de QML qui est disponible dans Qt 4 est maintenant disponible dans Qt 5 sous le nom de Qt Quick 1. Elle est seulement fournie pour une étape de portage et ne recevra pas d'améliorations de performance ou tout autre soin particulier. Le code l'utilisant pourra être porté vers QML 2 (et l'API Qt Quick dans Qt 5) pour bénéficier de la maintenance et de l'amélioration des performances. Le portage vers QML 2 est un cas d'utilisation des noms de classes pertinents dans l'API C++ et met à jour les éléments personnalisés qui sont dessinés. Comme QML 2 utilise une scène graphique plutôt que l'API QPainter (c'est de là que viennent bénéfices en performance), les éléments personnalisés doivent être dessinés en utilisant une API différente.

http://www.kdab.com/wp-content/uploads/stories/kaddressbook-mobile
Saisie des contacts dans KDE PIM avec QML 1

VI. Conclusion

Ceci est une liste non exhaustive mais représentative des étapes nécessaires pour le portage de Qt 4 vers Qt 5. Une liste plus longue mais plus complète des modifications liées au portage fait partie des notes de version de Qt 5.

Il faut aussi noter que cet article de blog cible les étapes requises pour compiler une application avec Qt 5, mais ne parle pas des erreurs d'exécution de portage, qui demandent un réel effort de portage. Quelques applications KDE montrent des bogues subtils qui devront être corrigés :

http://www.kdab.com/wp-content/uploads/stories/dolphin
La page « À propos » de Dolphin avec des erreurs d'encodage
Image non disponible
Konqueror avec un bogue mineur dans le menu « Help »

Les modifications sont inscrites dans le fichier de modification et indiquent quelles sont les différences de Qt 5 qui peuvent causer des bogues dans le code porté.

VII. Remerciements

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à KDAB pour nous avoir autorisés à traduire cet article !

Je tiens à remercier Thibaut Cuvelier pour ses conseils et Fabien et Cédric Duprez pour sa relecture.