Compilation croisée Gnunux vers MacOS | Blog | Superflu Riteurnz

Compilation croisée Gnunux vers MacOS | Blog | Superflu Riteurnz Compilation croisée Gnunux vers MacOS | Blog | Superflu Riteurnz
  • Steam
  • Nintendo Switch
  • Itch.io
  • Google Play
Superflu et son assistante Sophie

Blog

Compilation croisée Gnunux vers MacOS

2022-03-16

La dernière fois, je vous avais expliquer comment compiler et distribuer un jeu pour Windows depuis Gnunux. Cette fois, on va faire la même chose, mais avec MacOS comme système cible, une procédure un peu moins bien documentée sur Internet.

OSXCross

Là où MinGW nous permettait de compiler des programmes Windows depuis Gnunux, c'est un logiciel du nom de OSXCross qui va nous permettre de faire de même pour des programmes MacOS. Contrairement à MinGW, ce dernier n'est pas inclus dans les dépôts des distributions usuelles, et on va donc devoir l'installer à la main.

En premier lieu, il faut télécharger XCode sur le site officiel, ce qui requiert de créer un compte (groumpf, mais passons). Attention, le fichier est volumineux (dans les 10 Gio), ne vous plantez donc pas de version (vérifiez bien qui est compatible avec quoi, entre OSXCross, XCode et le MacOS le plus ancien que vous voulez supporter). Personnellement, j'utilise XCode 11, qui permet de supporter MacOS Catalina.

Ensuite, il suffit de suivre la procédure décrite sur le Github de OSXCross, on package le SDK, on le déplace dans le dossier tarballs, etc.

On compile OSXCross tout simplement avec build.sh.

On prend ensuite soin d'ajouter le chemin des exécutables installés, personnellement j'ai mis tout ça dans /home/gee/local/osxcross. On précise également la version cible de MacOS, tout cela dans notre .bashrc, .zshrc. ou autre :


export MACOSX_DEPLOYMENT_TARGET=10.15
export PATH=/home/gee/local/osxcross/bin:$PATH

Si vous jetez un œil au contenu de ${INSTALL_PATH}/bin, vous trouverez des choses qui ressemblent beaucoup à ce qu'on avait avec MinGW :

  • x86_64-apple-darwin19-clang++ pour compiler du C++ pour une architecture 64 bits
  • x86_64-apple-darwin19-clang pareil pour du C
  • x86_64-apple-darwin19-otool qui permet d'afficher les objets et bibliothèques utilisées par un exécutable (on va en rappeler plus bas)

Bref, des outils de développement MacOS mais sur votre Gnunux !

Bibliothèques tierces

Rappelons à nouveau que Superflu Riteurnz repose sur 3 bibliothèques tierces :

Ces bibliothèques fonctionnent bien sur Gnunux, Windows… et MacOS. Pour les installer, OSXCross fournit un portage des Macports, et on peut donc très simplement faire :


$ osxcross-macports install libsdl2 libsdl2_image libsdl2_mixer libsdl2_ttf libyaml lz4

Les bibliothèques en question sont installées avec l'arborescence habituelle (lib, include, etc.) au sein du dossier {INSTALL_PATH}/macports/pkgs/opt/local/.

Configuration & compilation

Encore une fois, ça va vous rappeler l'article sur MinGW, puisqu'on va utiliser un fichier de toolchain CMake :


set(CMAKE_SYSTEM_NAME Darwin)

set(TOOLCHAIN_PREFIX x86_64-apple-darwin19)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-clang)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-clang++)
set(CMAKE_C_COMPILER_AR ${TOOLCHAIN_PREFIX}-ar)
set(CMAKE_CXX_COMPILER_AR ${TOOLCHAIN_PREFIX}-ar)
set(CMAKE_LINKER ${TOOLCHAIN_PREFIX}-ld)

set(CMAKE_FIND_ROOT_PATH /home/gee/local/osxcross/macports/pkgs/opt/local/)
set(CMAKE_OSX_SYSROOT /home/gee/local/osxcross/SDK/MacOSX10.15.sdk/)
set(CMAKE_REQUIRED_MAC_LIBS "libyaml-0.2.dylib;libSDL2-2.0.0.dylib;libSDL2_image-2.0.0.dylib;libSDL2_ttf-2.0.0.dylib;libSDL2_mixer-2.0.0.dylib;liblz4.1.dylib;liblz4.1.9.3.dylib;libpng16.16.dylib;libjpeg.8.dylib;libjpeg.8.2.2.dylib;libtiff.5.dylib;libz.1.dylib;libz.1.2.11.dylib;libwebp.7.dylib;libmodplug.1.dylib;libvorbisfile.3.dylib;libvorbis.0.dylib;libFLAC.8.dylib;libmpg123.0.dylib;libopusfile.0.dylib;libzstd.1.dylib;libzstd.1.5.2.dylib;liblzma.5.dylib;libogg.0.dylib;libopus.0.dylib")

La variable CMAKE_REQUIRED_MAC_LIBS remplie à la main, je n'en suis pas vraiment satisfait, mais je n'ai pas trouvé mieux : il s'agit de toutes les bibliothèques nécessaires pour faire tourner l'exécutable. Pour les trouver, il suffit d'utiliser la commande x86_64-apple-darwin19-otool sur l'exécutable, de regarder quelles bibliothèques s'affichent, et de recommencer sur chaque bibliothèque jusqu'à avoir fait le tour. Oui, ça se scripte bien aussi.

Ensuite, il suffit de lancer CMake avec ce fichier toolchain, puis on compile normalement avec make, ce qui nous donne un exécutable superfluous-returnz compatible MacOS !

Installateur fait maison

Vous connaissez la rengaine : un exécutable, c'est bien joli, mais ça ne suffit pas pour partager un jeu. Pour Windows, on avait pu profiter du générateur CPack NSIS, malheureusement le générateur « classique » DragNDrop pour MacOS n'est disponible… que sur MacOS. Qu'à cela ne tienne : on va s'en occuper à l'ancienne, à la main !

Pas mal d'applis sur MacOS se distribuent sous la forme d'un fichier DMG, qui correspond en gros à un dossier finissant en .app avec une certaine arborescence normalisée qu'on va compresser. L'arborescence qu'on va utiliser nous ressemble à ça :


superfluous-returnz.app
└── Contents
    ├── Info.plist # Liste de propriétés
    ├── MacOS
    │   └── superfluous-returnz # L'exécutable
    └── Resources
        ├── data
        |   ├── # Les données du jeu
        │   └── # (...)
        ├── icon.icns # L'icone de l'exécutable
        └── libs
            ├── # Les bibliothèques tierces
            └── # (...)

L'icône est sous le format .icns : on peut facilement convertir un PNG en ICNS avec le programme png2icns disponible dans le paquet icnsutils (sur Debian et dérivées, mais j'imagine que c'est aussi dispo dans les autres distributions). Le fichier Info.plist est un fichier au format XML qui contient un ensemble de propriétées :


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleName</key>
    <string>superfluous-returnz</string>
    <key>CFBundleDisplayName</key>
    <string>Superfluous Returnz</string>
    <key>CFBundleIdentifier</key>
    <string>net.ptilouk.superfluous</string>
    <key>CFBundleVersion</key>
    <string>1.1.0</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleSignature</key>
    <string>sflu</string>
    <key>CFBundleExecutable</key>
    <string>superfluous-returnz</string>
    <key>CFBundleIconFile</key>
    <string>icon</string>
</dict>
</plist>

Même si le générateur CPack pour créer un DMG n'est pas dispo sur Gnunux, on peut malgré tout indiquer une procédure d'installation qui génère l'arborescence voulue via CMake :


if (CMAKE_SYSTEM_NAME STREQUAL Darwin)
  target_compile_options(${SOSAGE_EXE_NAME} PUBLIC -DSOSAGE_INSTALL_DATA_FOLDER="data/")
  install(TARGETS ${SOSAGE_EXE_NAME} DESTINATION "${SOSAGE_EXE_NAME}.app/Contents/MacOS")
  install(DIRECTORY ${SOSAGE_DATA_FOLDER}/data/ DESTINATION "${SOSAGE_EXE_NAME}.app/Contents/Resources/data")
  foreach(lib ${CMAKE_REQUIRED_MAC_LIBS})
    install(FILES ${CMAKE_FIND_ROOT_PATH}/lib/${lib} DESTINATION "${SOSAGE_EXE_NAME}.app/Contents/libs/")
  endforeach()
  install(FILES "${SOSAGE_DATA_FOLDER}/resources/Info.plist" DESTINATION "${SOSAGE_EXE_NAME}.app/Contents/")
  install(FILES "${SOSAGE_DATA_FOLDER}/resources/icon.icns" DESTINATION "${SOSAGE_EXE_NAME}.app/Contents/Resources")
endif()

On voit ici l'intérêt d'avoir listé les bibliothèques nécessaires dans la variable CMAKE_REQUIRED_MAC_LIBS.

Là, on se dit qu'il devrait suffire de compresser ce dossier au format DMG pour avoir notre installateur… mais bien sûr, ce n'est pas aussi simple. Le nœud du problème, c'est que votre programme est linké au bibliothèques tierces avec un chemin absolu qui pointe sur les fichiers .dylib de votre système… sauf que bien sûr, on voudrait pointer sur ceux fournis dans le DMG !

J'ai essayé pas mal de méthodes pour faire ça via CMake mais je ne suis arrivé à rien de concluant, j'ai donc fini par faire un script qui utilise les outils fournis par OSXCross : otool pour analyser les fichiers binaires, et install_name_tool pour changer le lien pointé.

En pratique, on va simplement vouloir remplacer des liens au format /opt/local/lib/[library.dylib] par un format @executable_path/../libs/[library.dylib] (@executable_path spécifiant l'endroit où se trouve l'exécutable, comme vous l'avez sans doute compris). J'ai donc bricolé un script Python qu'il suffit d'appeler sur l'exécutable ainsi que tous les fichiers dylib et qui s'occupe du bouzin :


import subprocess
import sys

for exe in sys.argv[1:]:
    libs = subprocess.check_output('x86_64-apple-darwin19-otool -L ' + exe, shell=True).decode().split('\n')
    for l in libs:
        if '/opt/local/lib/' in l:
            lname = l.split('/opt/local/lib/')[1].split(' ')[0]
            old = '/opt/local/lib/' + lname
            new = '@executable_path/../libs/' + lname
            subprocess.run('x86_64-apple-darwin19-install_name_tool -change "' + old + '" "' + new + '" ' + exe, shell=True, check=True)

C'est un peu crade, mais bon… ça marche.

Ensuite, tout est prêt pour générer notre fichier DMG, ce qu'on peut faire très simplement avec la commande genisoimage :


$ genisoimage -V superfluous-returnz.app -D -R -apple -no-pad -o superfluous-returnz.dmg install

Idéalement, pour faire un véritable installateur « drag'n'drop » comme les mac-users en ont l'habitude, il faudrait ajouter un fond d'écran et placer l'icone du jeu à côté d'un raccourci vers le dossier « Applications » de MacOS en invitant à déposer le premier sur le second. C'est par exemple ce que fait le jeu Supertux :

Supertux Installer

Malheureusement, je n'ai pas trouvé de façon élégante de faire ça sans passer par des manipulations « à la main » sur un MacOS en copiant-collant le fichier .DS_store du répertoire… du coup j'ai préféré laisser tomber cette étape. Je suppose que les gens sous MacOS ont suffisamment l'habitude pour comprendre qu'il faut copier le jeu dans « Applications » pour réellement l'installer sur la machine.

Installer

Test sur machine virtuelle

Contrairement à Microsoft, Apple ne propose pas de machine virtuelle clef en main de son système d'exploitation : fort heureusement, un script nommé macos-virtualbox est disponible pour générer une VM depuis les fichiers d'installation d'Apple. C'est un peu plus fastidieux que pour Windows donc, mais rien d'insurmontable, il suffit de suivre le README du projet.

Une fois notre machine virtuelle configurée, on peut transférer le jeu, le lancer et le débuguer si besoin !

Game

Gros avantage de MacOS sur Windows : les sorties C++ standards std::cout et std::cerr fonctionnent directement dans le terminal comme sur Gnunux, il n'y a donc pas besoin d'adapter le code. En fait, j'ai beau avoir défini une macro MAC comme pour les autre systèmes d'exploitation :


#if defined(__ANDROID__)
#define SOSAGE_ANDROID
#elif defined(__APPLE__)
#define SOSAGE_MAC
#elif defined(_WIN32)
#define SOSAGE_WINDOWS
#define WINVER 0x0600 // Enable use of locale functions
#elif defined(__linux__)
#define SOSAGE_GNUNUX
#endif

… je dois bien avouer que je n'ai jamais eu besoin de l'utiliser jusqu'à maintenant, ce qui est plutôt chouette ! Moins on a de code spécifique à chaque OS, moins c'est le bazar à maintenir.

Conclusion

La compilation croisée de Gnunux vers MacOS demande définitivement plus d'efforts que celle vers Windows : l'absence de logiciel dédié dans les dépôts des distributions Gnunux usuelles et l'abscence de générateur CPack pour MacOS sur Gnunux sont les deux points noirs.

Malgré tout, avec un peu de patience et pas mal d'espace disque, on peut y arriver ! Un énorme merci aux personnes derrière les logiciels OSXCross et macos-virtualbox sans qui tout cela serait sans doute largement plus compliqué.

i