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 21 mai 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. Trouver Qt 5

Un des principaux changements lors de l'utilisation de CMake avec Qt est relative à la modularisation de Qt 5. Qt 5 a été conçu dans le but d'être beaucoup plus modulaire que Qt 4. Avec ce dernier, on utilisait CMake de la sorte :

 
Sélectionnez
FIND_PACKAGE(Qt4 COMPONENTS QTCORE QTGUI)

Avec Qt 5, il vous faut maintenant trouver les différents modules un par un en utilisant les commandes suivantes :

 
Sélectionnez
FIND_PACKAGE(Qt5Widgets)
FIND_PACKAGE(Qt5Declarative)

Il sera bientôt possible de trouver plusieurs modules en une seule commande comme suit mais ce n'est pas encore le cas avec Qt 5.0.0 :

 
Sélectionnez
FIND_PACKAGE(Qt5 COMPONENTS Widgets Declarative)

III. Compiler un projet utilisant Qt 5 avec CMake

Une fois Qt 4 trouvé, les utilisateurs pouvaient utiliser la variable ${QT_INCLUDES} pour définir le répertoire des entêtes lors de la compilation ou encore ${QT_ LIBRARIES} et ${QT_GUI_LIBRARIES} pour l'édition des liens. Les utilisateurs de CMake à l'époque de Qt 4 ont aussi pu utiliser ${QT_USE_FILE} dans le but d'inclure les bons entêtes et les bons defines de façon semi-automatique.

Avec le découpage de Qt 5 en différents modules, les variables seront maintenant ${Qt5Widgets_INCLUDE_DIRS}, ${Qt5Widgets_LIBRARIES}, ${QT5Declarative_INCLUDE_DIRS}, ${QT5Declarative_INCLUDE_LIBRARIES} et ainsi de suite pour chaque module utilisé.

Cette nouvelle façon de procéder est source d'incompatibilité pour les projets basés sur Qt 4 qui utilisaient CMake. Une rétrocompatibilité est cependant aisément réalisable en cherchant et remplaçant quelques variables CMake.

Avec CMake, il est légèrement plus difficile de générer des exécutables avec Qt 5 qu'avec Qt 4. Une des différences réside dans la façon dont Qt 5 est compilé et empaqueté vis-à-vis de Qt 4. Ainsi, avec Qt 5, l'option -reduce-relocations est utilisée par défaut. Des compilations sont donc effectuées avec l'option -Bsymbolic-functions activée, ce qui rend la comparaison de pointeurs de fonctions caduque… à moins d'avoir activé l'option -fPIE lors de la compilation de l'exécutable ou -fPIC pour une bibliothèque.

Si Qt est configuré manuellement, il est bien évidemment possible d'utiliser l'option -no-reduce-relocations et ainsi éviter des soucis, cette valeur par défaut sera obligatoire pour les tiers, qui devront également générer du code indépendant de la position. Cela peut être réalisé simplement de cette façon :

 
Sélectionnez
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")

Il existe une variable Qt5<NomDuModule>{EXECUTABLE_COMPILE_FLAGS} pour chacun des modules disponibles dans Qt 5 qui a pour valeur soit -fPIE, soit une chaîne de caractères vide, selon la façon dont Qt a été configuré. Néanmoins, l'option -fPIE ne doit être utilisée que pour les exécutables et en aucun cas pour les bibliothèques.

Utiliser l'option -fPIC de manière globale, même pour la génération d'exécutables, peut fonctionner mais ne devrait être envisagée que si les autres solutions ne fonctionnent pas :

 
Sélectionnez
SET(CMAKE_CXX_FLAGS "-fPIC")

En utilisant différentes nouvelles fonctionnalités de CMake comme l'appel automatique du Moc, un simple CMakeLists.txt utilisant Qt 5 sera très semblable à un autre écrit pour Qt 4 :

 
Sélectionnez
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.7)
PROJECT(hello-world)
# Appeler automatique le moc quand nécessaire
SET(CMAKE_AUTOMOC ON)
# Les fichiers générés par le moc sont générés dans le dossier bin, dire à CMake de toujours 	
# inclure les entêtes de ce dossier
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
# Le module QtWidgets trouve ses propres dépendances (QtGui and QtCore)
FIND_PACKAGE(Qt5Widgets REQUIRED)
# Qt5Widgets_INCLUDES inclut aussi les entêtes pour ses dépendances (QtCore et QtGui)
INCLUDE_DIRECTORIES(${Qt5Widgets_INCLUDES})
#  -DQT_WIDGETS_LIB est nécessaire quand on utilise QtWidgets dans Qt&#0160;5.
ADD_DEFINITIONS(${Qt5Widgets_DEFINITIONS})
# La création de l'exécutable échoue dans Qt&#0160;5 avec la configuration par défaut
SET(CMAKE_CXX_FLAGS "${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")
ADD_EXECUTABLE(hello_world main.cpp mainwindow.cpp)
# Qt5Widgets_LIBRARIES contient aussi QtGui et QtCore
TARGET_LINK_LIBRARIES(hello_world ${Qt5Widgets_LIBRARIES})

On peut constater dans cet exemple le grand nombre de répétitions et aussi beaucoup de choses auxquelles il faut faire attention.

L'utilisation de Qt5Widgets_EXECUTABLE_COMPILE_FLAGS ne présente aucun intérêt et peut même apporter son lot de difficultés. Il subsiste ainsi quelques effets de bord que les utilisateurs ayant effectué des tests unitaires sur du code Qt 4 connaîtront. Ainsi, si l'option -DQT_GUI_LIB est utilisée (ce qui arrive notamment si l'on se sert de QT_USE_FILE), alors tous les test unitaires devront lier avec le module QtGui, même si ce module n'est pas utilisé dans les tests. Ceci est dû à quelques hacks magiques dans les entêtes des fichiers du module QtTest. Une façon de contourner le problème consiste soit à soigneusement séparer les cibles et les répertoires, soit à modifier manuellement certaines variables qui permettent de contrôler la façon dont Qt 4 est trouvé par CMake.

IV. Faire mieux avec les nouvelles versions de CMake

À partir de CMake 2.8.8, il est possible de faire beaucoup mieux :

 
Sélectionnez
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8)
PROJECT(hello-world)
# Appeler automatique le moc quand nécessaire
SET(CMAKE_AUTOMOC ON)
# Les fichiers générés par le moc sont générés dans le dossier bin, dire à CMake de toujours 	
# inclure les entêtes de ce dossier
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
# Le module QtWidgets trouve ses propres dépendances
FIND_PACKAGE(Qt5Widgets REQUIRED)
ADD_EXECUTABLE(hello_world main.cpp mainwindow.cpp)
QT5_USE_MODULES(hello_world Widgets)
# La fonction QT5_USE_MODULE encapsule toutes les routines nécessaires à l'utilisation 	
# d'un module
QT5_USE_MODULES(hello_world Widgets Declarative)

C'est très similaire à la façon d'opérer avec QMake.

 
Sélectionnez
TARGET = hello_world 	
QT += widgets declarative

Toutes ces propriétés sont restreintes à la cible où la fonction est utilisée, au lieu d'être communes à tout le système de compilation CMake et ainsi d'affecter toutes les bibliothèques et exécutables.

Par exemple :

 
Sélectionnez
ADD_EXECUTABLE(hello_world main.cpp mainwindow.cpp)
ADD_LIBRARY(hello_library lib.cpp)
ADD_EXECUTABLE(hello_coretest test.cpp)
FIND_PACKAGE(Qt5Widgets)
QT5_USE_PACKAGE_use_package(hello_world Widgets)
QT5_USE_PACKAGE(hello_library Core)
QT5_USE_PACKAGE(hello_coretest Test)

On peut remarquer que les options sont restreintes aux cibles (exécutables ou bibliothèque) sur lesquelles elles doivent agir. Ainsi, l'option -fPIE n'est pas considérée lors de la génération de la bibliothèque hello_library et, de même, l'option -DQT_GUI_LIB n'est pas utilisée pour la génération de l'exécutable hello_coretest.

C'est vraiment la bonne façon de procéder pour écrire des systèmes de compilation basés sur CMake.

Nous espérons dans le futur pouvoir profiter de telles fonctionnalités pour CMake. Vous pouvez vous attendre à pouvoir utiliser de telles fonctions avec n'importe quel paquet CMake.

V. Détails d'implémentation

Une fonctionnalité de CMake bien connue de ses utilisateurs est FIND. L'idée de base est d'écrire un fichier FIND pour chaque dépendance du projet ou alors d'utiliser un fichier FIND existant fourni par un tiers. CMake fournit déjà une multitude de fichiers FIND et le projet KDE se prépare à distribuer ses fichiers FIND élaborés ces dernières années à tous les utilisateurs de CMake. Un des fichiers FIND fourni par CMake est FindQt4.cmake. Ce fichier permet de trouver Qt sur le système et ainsi d'appeler la fonction FIND_PACKAGE comme ceci :

 
Sélectionnez
FIND_PACKAGE(Qt4)

Ce fichier FIND permet d'utiliser la variable ${QT_INCLUDES} afin de sélectionner le dossier des entêtes.

Ces fichiers ont cependant un désavantage dans la mesure où ils font partie intégrante de CMake et non de Qt. Ainsi lors de la sortie de Qt 4.6 en décembre 2009, un nouveau module appelé Qt Multimedia fit son apparition. Ce module ne fut totalement supporté que lors de la sortie de CMake 2.8.2 en juin 2010. Cela signifie que, si quelqu'un voulait alors utiliser le module QtMultimedia, il pouvait soit attendre une nouvelle version de CMake, soit tenter de copier le fichier FIND dans son projet pour utiliser la version d'alors de CMake. Cette copie ne garantissait aucunement une réussite dans la mesure où le nouveau fichier FIND pouvait utiliser de nouvelles fonctionnalités de CMake.

Sous le capot, Qt 5 est maintenant trouvé d'une façon légèrement différente. Outre la possibilité de trouver les dépendances en utilisant un fichier FIND, CMake est aussi capable de lire les fichiers fournis par les dépendances elles-mêmes afin de déterminer où se trouvent les bibliothèques et les fichiers d'entête. De tels fichiers sont appelés fichiers CONFIG et sont généralement générés par CMake lui-même. Une compilation de Qt 5 générera un fichier de CONFIG pour CMake, sans pour autant faire dépendre Qt 5 de CMake.

Le principal bénéfice de cela est que les nouvelles fonctionnalités de Qt (ainsi que ses modules) qui peuvent être utilisées avec CMake ne dépendront pas de la version de CMake. Tous les modules essentiels et additionnels créeront leurs propres fichiers CONFIG et les fonctionnalités fournies par ces modules seront disponibles à travers de CMake sans devoir attendre quoi que ce soit.

Lors de la sortie de nouveaux modules, ils seront maintenant utilisables avec CMake dès qu'ils seront dans les dépôts. Un autre avantage à l'utilisation des fichiers CONFIG au lieu des fichiers FIND réside dans le fait d'avoir accès à tous les aspects de la compilation de Qt lui-même, y compris la façon dont il a été configuré et où il a été installé. Cela aide a maintenir un niveau de complexité relativement raisonnable dans le cas de compilations statiques ou de compilations croisées.

VI. 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 pour sa relecture.