[C++/Qt] CMake et Qt

Posté le Sunday, 26 June 2011 in Programmation

Suite à un billet datant de 2008, je reviens vers vous pour ajouter quelques précisions sur la compilation de programme Qt avec CMake. En effet, pour mon programme XINX, j'ai modifié la chaîne de compilation actuelle utilisant QMake par une chaîne de compilation CMake.

CMake est un puissant générateur de Makefile, il permet de remplacer les anciens (mais pas complètement révolus) autotools. CMake ne remplace donc pas le programme make mais vient se placer en amont.

CMake permet de compiler un programme à différents endroits du dossier des sources, ce qui permet de garder le répertoire des sources propre.

Nous allons considérer dans la suite le dossier projet suivant :

  • projet
    • source
    • build

Compilation

Nous passerons sous silence la compilation d'un programme non Qt qui peut être retrouvé dans la documentation et nous nous limiterons aux explications liées aux programmes écrits en ''Qt' (qui peuvent également être retrouvés dans d'autres tutoriels sur Internet).

Package à utiliser

Pour utiliser Qt4 avec CMake, il faut inclure le package Qt4 :

project(lenomdemonprojet) 
cmake_minimum_required(VERSION 2.8.0)
find_package(Qt4 REQUIRED)

Définir les modules Qt à utiliser :

Il est ensuite possible d'activer ou de désactiver les différents modules de Qt à utiliser suivant le programme que vous écrivez :

set(QT_DONT_USE_QTCORE TRUE)
set(QT_DONT_USE_QTGUI TRUE)
set(QT_USE_QT3SUPPORT TRUE)
set(QT_USE_QTASSISTANT TRUE)
set(QT_USE_QAXCONTAINER TRUE)
set(QT_USE_QAXSERVER TRUE)
set(QT_USE_QTDESIGNER TRUE)
set(QT_USE_QTMOTIF TRUE)
set(QT_USE_QTMAIN TRUE)
set(QT_USE_QTNETWORK TRUE)
set(QT_USE_QTNSPLUGIN TRUE)
set(QT_USE_QTOPENGL TRUE)
set(QT_USE_QTSQL TRUE)
set(QT_USE_QTXML TRUE)
set(QT_USE_QTXMLPATTERNS TRUE)
set(QT_USE_QTWEBKIT TRUE)
set(QT_USE_QTSVG TRUE)
set(QT_USE_QTTEST TRUE)
set(QT_USE_QTUITOOLS TRUE)
set(QT_USE_QTDBUS TRUE)
set(QT_USE_QTSCRIPT TRUE)
set(QT_USE_QTASSISTANTCLIENT TRUE)
set(QT_USE_QTHELP TRUE)
set(QT_USE_PHONON TRUE)

Par exemple pour faire un programme utilisant le module Xml, XmlPatterns, et Webkit il faut ajouter :

set(QT_USE_QTXML TRUE)
set(QT_USE_QTXMLPATTERNS TRUE)
set(QT_USE_QTWEBKIT TRUE)

Quelques déclarations supplémentaires

Après avoir compilé les différentes parties du programme avec le générateur de Makefile de Qt (QMake), j'ai relevé les différentes définitions à ajouter lors de la compilation. De plus une fois les modules définis, il faut inclure également le module UseQt4.

add_definitions(-DUNICODE)
# Les Q_ASSERT, Q_ASSERT_X, ... sont désactivés en mode Release
if(CMAKE_BUILD_TYPE STREQUAL "Release") 
    add_definitions(-DQT_NO_DEBUG)
endif()
# Include utilisé par Qt (du style include (UseQt4))
include(${QT_USE_FILE})
# Ajoute les définitions propres à Qt (suivant les modules ajoutés)
add_definitions(${QT_DEFINITIONS})

Macros pour générer les fichiers moc.

Macro par défaut

La facilité de programmation en Qt est due à l'ajout par le framework de la notion de méta-objet. Cette notion se fait à l'aide d'un générateur propre à ce framework : moc1.

Pour générer les méta-objets, il faut définir sur les objets descendants de QObject, une macro Q_OBJECT. Cette macro ajoute, côté interface, la déclaration de méthodes supplémentaires pour la gestion des méta-informations, et coté implémentation, ajoute l'implémentation de ces méta-informations ainsi que la déclaration des signaux et de slots de l'objet2.

Pour générer les fichiers d'implémentation générés par le compilateur moc, il est possible d'utiliser la macro suivante pour une liste de fichier sources (extension .cpp, ou .cxx, ...) :

qt4_wrap_cpp(outfiles inputfile ... OPTIONS ...)

Cette macro générera un fichier de type moc_nomfichier.cxx qu'il faudra inclure dans le ficher CPP (car il n'est pas inclus automatiquement dans la liste des fichiers à compiler.

#include "moc_nomfichier.cxx"
Une autre macro

Autre possibilité, faire comme avec les fichiers .pro de QMake et générer un fichier moc pour chaque fichier .h possédant une macro Q_OBJECT. Pour cela j'ai écrit la petite macro suivante :

macro(xinx_automoc outfiles)
qt4_get_moc_flags(moc_flags)
qt4_extract_options(moc_files moc_options ${ARGN})
foreach (it ${moc_files})
    get_filename_component(it ${it} ABSOLUTE)
    if ( EXISTS ${it} )
        file(READ ${it} _contents)
        string(REGEX MATCHALL "Q_OBJECT" _match "${_contents}")      
        if(_match)          
            qt4_make_output_file(${it} moc_ cxx outfile)           
            qt4_create_moc_command(${it} ${outfile} "${moc_flags}" "${moc_options}")            
            macro_add_file_dependencies(${it} ${outfile})           
            set(${outfiles} ${${outfiles}} ${outfile})        
        endif(_match)    
    endif ( EXISTS ${it} )  
endforeach(it)endmacro(xinx_automoc)

Elle s'utilise ainsi :

xinx_automoc(moc_headers ${headers})

Macro pour générer les fichiers en tête d'interface graphique.

Les interfaces graphiques sont développées à l'aide de Qt-Designer. Ces fichiers sont au format XML.

La macro CMake suivante permet de transformer ces fichiers XML en fichier d'entête C.

qt4_wrap_ui(outfiles inputfile ...)

Macro pour compiler les fichiers de resources

La macro CMake suivante permet de compiler le fichier de ressource (extension *.qrc) en fichier cpp qui sera ensuite compilé avec le reste du programme :

qt4_add_resources(outfiles inputfile ... OPTIONS ...)

Macro pour compiler les fichiers de traduction

La macro CMake suivante est utilisée pour compiler un fichier de traduction ts en fichier de traduction compilé qm. Ce dernier est ensuite à inclure dans le programme (soit par un fichier de ressource, soit dans un dossier à coté).

qt4_add_translation(qm_files ts_files ...)

Compilation d'un plugin

En plus des options décrites ci-dessus, pour compiler un plugin, il faut ajouter quelques définitions supplémentaires.

Il faut bien faire attention à ce que le plugin soit compilé en mode Release si le programme l'est aussi (Ce qui n'est pas forcément pratique pour faire du debug).

Plugin dynamique
add_definitions(-DQT_PLUGIN)
add_definitions(-DQT_SHARED)
add_library(webplugin SHARED ${webplugin_SRCS} ${webplugin_MOC_SRCS})
Plugin Static
add_definitions(-DQT_PLUGIN)
add_definitions(-DQT_STATICPLUGIN)
if(CMAKE_SIZEOF_VOID_P MATCHES 8)  
    add_definitions(-fPIC)
endif()
add_library(webplugin STATIC ${webplugin_SRCS} ${webplugin_MOC_SRCS})

Ajout d'un fichier RC pour windows

Les fichiers RC Windows contiennent des informations, comme par exemple, la version et le nom du programme.

Ces informations peuvent être utilisées par les programmes d'installation sous Windows ou aussi par Windows lui-même.

Afin de pouvoir ajouter un fichier RC file au programme, on peut ajouter ceci :

if(MINGW)
     set(CMAKE_RC_COMPILER_INIT windres)    
     enable_language(RC)    
     set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> <DEFINES> -O coff -o <OBJECT> <SOURCE>")
endif(MINGW)

Ensuite le fichier RC se compile comme tout autre fichier (C++, ...)

Compilation d'une librairie static

add_definitions(-DQT_SHARED)
add_library(xinxplugins STATIC ${xinxplugins_SRCS} ${xinxplugins_MOC_SRCS})

Utiliser le programme CMake

Une fois le programme configuré, il est possible de compiler ce dernier à l'aide des commandes suivantes :

mkdir build
cd build
cmake ../sources
make
make install

Sous Windows, il est possible de compiler en utilisant MinGW pour compiler ou d'utiliser le compilateur de Visual Studio.

Il est également possible de définir un emplacement pour l'installation différent du dossier par défaut :

cmake -DCMAKE_INSTALL_PREFIX=/home/login/usr/

  1. C'est ce qui fait tout l'avantage de Qt mais est aussi son inconvénient car il ajoute une couche supplémentaire. C'est ensuite une question de goût. 

  2. facilitant la gestion des évènements dans Qt 

qt