Qu'est que la cross-compilation1 ?

La cross compilation est la possibilité sur une machine avec un matériel spécifique (architecture) et avec un système d'exploitation donné, de compiler des programmes pour une autre architecture, ou pour un autre système d'exploitation.

Cela peut être utilisé par exemple pour compiler un programme sur votre ordinateur de tous les jours (sous Gnu/Linux, avec une architecture i386) à destination de votre téléphone mobile, qui lui est sous Symbian avec un processeur ARM.

Les raisons de faire de la compilation croisée peuvent donc être multiples :

  • Éviter de redémarrer votre machine pour compiler vos binaires.
  • Disponibilité des outils sur votre machine / Indisponibilité des outils de compilation sur la machine de destination (on trouve rarement des outils de compilation sur des téléphones portables2).
  • Puissances des calculs (la compilation prendra moins de temps sur votre PC de bureau que sur votre appareil mobile3).
  • Licence : Vous voulez compiler à destination d'un système d'exploitation que vous ne possédez pas

Attention: La compilation croisée ne garantie pas que programme fonctionnera, vous devrez toujours faire quelques tests à partir d'un émulateur ou à partir du système d'exploitation final.

Bref, à partir du moment où vous avez besoin de compiler un programme pour une autre architecture, ou pour un autre système d'exploitation que votre machine actuelle, vous avez besoin de faire de la compilation croisée.

De quoi va parler ce billet ?

Ce billet ne va pas parler de la compilation croisée entre deux architectures différentes, mais uniquement de la compilation croisée à destination d'une machine Windows à partir d'une machine Linux. La compilation croisée entre architectures pourra être vue dans un futur4 article, ou sur d'autres sites.

Afin de pouvoir faire de la compilation croisée, il vous faudra installer les outils suivants :

  • MinGW5 : utilisé en tant que cross-compilateur, il nous génèrera un exécutable Windows.
  • Wine6 : Qui nous servira à vérifier l'exécutable créé.

Les différentes étapes de la constitution de ce billet seront :

  • Installation des outils
  • Compilation d'un programme simple
  • Compilation d'un programme Qt simple

Installation des outils

Nous y sommes :). Nous allons commencer par installer les outils qui nous permettront de faire de la compilation croisée. Sous la distribution de votre choix, il vous faudra donc installer mingw ainsi que wine7. Sous une Gnu/Debian, on pourra par exemple faire :

# sudo aptitude install mingw32-runtime wine

Pour vérifier la version actuelle de mingw vous pouvez faire :

# i586-mingw32msvc-gcc --version
i586-mingw32msvc-gcc (GCC) 4.2.1-sjlj (mingw32-2)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Compilation d'un programme simple.

Commençons par le programme le plus simple du monde:

#include <iostream>

int main(int argc, char** argv)
{
    std::cout << "Hello" << std::endl;
    return 0;
}

Puis compilons :

# i586-mingw32msvc-g++ -o test.exe test.cpp 
# ls
test.c 
test.exe
# file test.exe
test.exe: PE32 executable (console) Intel 80386, for MS Windows

Voilà nous avons donc un programme à destination de Windows. Il ne nous reste plus qu'à le tester :

# wine ./test.exe 
Hello

Et voilà, nous avons écrit un petit programme Windows, et nous l'avons testé à l'aide de Wine. Généralement, on utilise l'utilisation de Makefile, voir même des générateurs de Makefile. Nous allons compléter l'exemple avec CMake. Voici donc un exemple de fichier CMake :

project(test)
add_executable(test test.cpp)

Nous allons donc lancer la compilation, sous Linux :

# mkdir build
# cd build
# cmake ../
-- The C compiler identification is GNU
-- The CXX compiler identification is GNU
-- Check for working C compiler: /usr/lib/ccache/gcc
-- Check for working C compiler: /usr/lib/ccache/gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/lib/ccache/c++
-- Check for working CXX compiler: /usr/lib/ccache/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/build
# make
Scanning dependencies of target test
[100%] Building CXX object CMakeFiles/test.dir/test.cpp.o                                                                                                                                                                                           
Linking CXX executable test

[100%] Built target test
# file test
test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0xf17337fcecb8f3b6ed589d8dce8978be08f2caca, not stripped

Nous avons donc un binaire pour Gnu/Linux. Recommençons donc mais avec Windows :

# cmake -DCMAKE_C_COMPILER=i586-mingw32msvc-gcc -DCMAKE_CXX_COMPILER=i586-mingw32msvc-g++ ../
-- The C compiler identification is GNU
-- The CXX compiler identification is GNU
-- Check for working C compiler: /usr/lib/ccache/i586-mingw32msvc-gcc
-- Check for working C compiler: /usr/lib/ccache/i586-mingw32msvc-gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/lib/ccache/i586-mingw32msvc-g++
-- Check for working CXX compiler: /usr/lib/ccache/i586-mingw32msvc-g++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/build-windows
# make
Scanning dependencies of target test
[100%] Building CXX object CMakeFiles/test.dir/test.cpp.o

Linking CXX executable test                                                                                                                                                                                                                         
[100%] Built target test
# file test
test: PE32 executable (console) Intel 80386, for MS Windows

Nous avons donc maintenant la possibilité de compiler notre application multi-platformes depuis Linux pour les systèmes Linux, mais aussi pour les systèmes Windows.

Compilation d'un programme écrit avec Qt

Nous allons maintenant nous compliquer un peu la tâche en compilant un programme ayant une dépendance avec une librairie externe : Qt. Qt est un framework proposant une boîte à outil de classe permettant de faire des interfaces graphiques mais aussi de faire des applications consoles rapidement.

Nous allons donc écrire le petit programme suivant, qui affiche une boîte de dialogue inutile, avec un bouton inutile :

#include <QApplication>
#include <QPushButton>

int main(int argc, char** argv)
{   
    QApplication app(argc, argv);
    QPushButton * btn = new QPushButton("Do nothing");
    btn->show();

    return app.exec();
}

Avec le fichier qmake associé tout simple :

[qmake]
TEMPLATE = app
TARGET = 
DEPENDPATH += .
INCLUDEPATH += .

# Input
SOURCES += test.cpp

On test la compilation à l'aide de qmake ; make et on lance le programme :

Maintenant que notre programme compile et fonctionne sous Gnu/Linux, nous allons pouvoir faire le même test mais en compilant une version Windows. Pour cela, il va nous falloir la version Windows de Qt (nous n'allons pas compiler Qt, alors que la librairie existe déjà).

Vous pouvez commencer par télécharger la dernière version de Qt (ou celle qui vous convient) à l'adresse suivante http://qt.nokia.com/products/ et l'installer. Vous n'avez pas besoin de MinGW, ni de QtCreator. Vous pouvez donc télécharger directement la version qui ne contient que la librairie.

Si à l'installation, l'application demande l'installation ou l'emplacement de MinGW, vous n'avez pas besoin de le renseigner, nous utiliserons la version Linux de MinGW.

Enfin nous allons faire un peu de paramétrage. Nous allons récupérer le dossier de specs Qt pour windows et l'adapter pour MinGW sous Linux. Les adaptations à faire sont :

  • Utilisation de MinGW
  • Définition des dossiers de Qt Windows et MinGW
  • Suppression de l'extension .exe

Commençons par créer le fichier qmake.conf :

sudo cp -Rf /usr/share/qt4/mkspecs/win32-g++ /usr/share/qt4/mkspecs/win32-x-g++
sudo nano /usr/share/qt4/mkspecs/win32-x-g++/qmake.conf

Voici le fichier de diff8 qui contient les choses à modifier, vous pouvez le récupérer et utiliser la commande patch pour reporter les modifications, ou faire les modifications à la mains :

17c17
< QMAKE_CC              = gcc
---
> QMAKE_CC              = i586-mingw32msvc-gcc
30c30
< QMAKE_CXX             = g++
---
> QMAKE_CXX             = i586-mingw32msvc-g++
44,46c44,46
< QMAKE_INCDIR          =
< QMAKE_INCDIR_QT               = $$[QT_INSTALL_HEADERS]
< QMAKE_LIBDIR_QT               = $$[QT_INSTALL_LIBS]
---
> QMAKE_INCDIR          = /usr/i586-mingw32msvc/include
> QMAKE_INCDIR_QT               = /home/phoenix/.wine/drive_c/Qt/4.8.2/include
> QMAKE_LIBDIR_QT               = /home/phoenix/.wine/drive_c/Qt/4.8.2/lib
53,54c53,54
< QMAKE_LINK            = g++
< QMAKE_LINK_C          = gcc
---
> QMAKE_LINK            = i586-mingw32msvc-g++
> QMAKE_LINK_C          = i586-mingw32msvc-gcc
77c77
< !isEmpty(QMAKE_SH) {
---
> #!isEmpty(QMAKE_SH) {
88,100c88,100
< } else {
<       QMAKE_COPY              = copy /y
<       QMAKE_COPY_DIR          = xcopy /s /q /y /i
<       QMAKE_MOVE              = move
<       QMAKE_DEL_FILE          = del
<       QMAKE_MKDIR             = mkdir
<       QMAKE_DEL_DIR           = rmdir
<     QMAKE_CHK_DIR_EXISTS      = if not exist
< }
< 
< QMAKE_MOC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}moc.exe
< QMAKE_UIC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}uic.exe
< QMAKE_IDC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}idc.exe
---
> #} else {
> #     QMAKE_COPY              = copy /y
> #     QMAKE_COPY_DIR          = xcopy /s /q /y /i
> #     QMAKE_MOVE              = move
> #     QMAKE_DEL_FILE          = del
> #     QMAKE_MKDIR             = mkdir
> #     QMAKE_DEL_DIR           = rmdir
> #    QMAKE_CHK_DIR_EXISTS     = if not exist
> #}
> 
> QMAKE_MOC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}moc-qt4
> QMAKE_UIC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}uic-qt4
> QMAKE_IDC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}idc-qt4

Une fois terminé on peut compiler et lancer l'application de la manière suivante :

# qmake-qt4 -spec win32-x-g++
# make
# wine ./release/test.exe

Conclusion

Et voilà, vous êtes maintenant capable de faire de la compilation croisée pour des programmes aussi simples que complexes :). Quand vos programmes ont des dépendances, et si vous le pouvez, préférez la version binaire. Sinon vous devrez compiler les librairies vous-même au format Windows de la même manière avant de compiler votre programme. Cela peut vous obliger à modifier les scriptes de build.


  1. En français cela donne compilation croisée 

  2. bien que ... 

  3. là aussi avec les smartphones actuels, on peut en douter 

  4. très lointain 

  5. MinGW est un portage de gcc pour Window. Il nous permettra donc de générer un executable Windows à partir de notre Gnu/Linux 

  6. Un émulateur pour démarrer des programmes Windows sous Linux 

  7. L'installation de wine sous une distribution 64-bit peut-être un peu plus compliqué que prévu, mais reste néanmoins faisable. Référez vous à la documentation de votre distribution. 

  8. Vous pouvez aussi le télécharger ici