Cross-Compilation - Compiler un programme pour MS/Windows sous Gnu/Linux
Qu'est que la cross-compilation[1] ?
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 portables[2]).
- Puissances des calculs (la compilation prendra moins de temps sur votre PC de bureau que sur votre appareil mobile[3]).
- 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 futur[4] article, ou sur d'autres sites.
Afin de pouvoir faire de la compilation croisée, il vous faudra installer les outils suivants :
- MinGW[5] : utilisé en tant que cross-compilateur, il nous génèrera un exécutable Windows.
- Wine[6] : 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
Notes
[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
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 wine
[1]. 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
# 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 diff 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.
Note
[1] 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.