Voir le flux RSS

tails

[Rust][AppImage][Docker][Linux] Création d'une AppImage à partir d'un binaire Rust avec Docker

Noter ce billet
par , 21/05/2020 à 16h17 (137 Affichages)
Bonjour, dans ce billet je vais essayer de parler simplement de la création d'une AppImage pour un binaire graphique Rust GTK, en utilisant une image Docker en mode intéractif.

Prérequis

Pour pouvoir suivre ce billet, il est recommandé de connaître les bases de
  • Rust: syntaxe de base, ownership/borrowing, struct/impl/enum, cargo, (juste pour comprendre les sources du projet si vous le souhaitez)
  • Gtk+Relm: les bases, j'ai présenté cette combinaison dans un billet précédent,
  • Docker: création/exécution d'images, mode intéractif (tty), volumes,
  • Git: pour pouvoir récupérer les sources du projet "cobaye"
  • Commandes de bases dans un terminal linux: cd, ls, notamment. En effet, l'utilisation du terminal sera massive dans ce tutoriel.


Le format AppImage

Il s'agit en quelque sortes d'un fichier compressée, embarquant un programme et ses dépendances dynamiques notamment, et qui se monte sur son propre systèmes de fichiers. Ce format tente donc de résoudre la problématique du déploiement d'application vis-à-vis de la fragmentation des nombreuses distributions Linux.

Cependant, il est basé sur l'utilisation de l'utilitaire Fuse. Ce programme, disponible sous forme de paquets dans certaines distributions Linux (s'il n'est pas déjà installé par défaut), permet de monter un système de fichiers sans pour autant que l'on soit administrateur (root).

Le programme "cobaye" et ses dépendances

Pour effectuer notre expérimentation, nous allons compiler une calculatrice on ne peut plus simple et ridicule et qui, pour des raisons pratiques, n'a pas d'opérateur division.

Veuillez cloner le projet dans le dossier de votre choix:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
$> git clone https://github.com/loloof64/SimpleCalculator.git calculatrice
En examinant les sources et le fichier Cargo.toml, vous pouvez constater qu'il s'agit d'une application Gtk+Relm minimaliste.

Veuillez compiler (et exécuter) le programme afin de pouvoir en analyser les dépendances dynamiques: (j'utilise la version 2018 de Rust, la dernière version à ce jour devrait convenir)

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
$> cargo run --release
Si tout se passe bien, l'exécutable se trouve dans le sous-dossier target/release. Analysons-donc les dépendances dynamiques à l'aide de la commande ldd. Voici ce que cela a donné de mon côté:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
$> ldd target/release/simple-calc
        linux-vdso.so.1 (0x00007fff669c3000)
        libgtk-3.so.0 => /lib/x86_64-linux-gnu/libgtk-3.so.0 (0x00007f8a33d5c000)
        libgdk-3.so.0 => /lib/x86_64-linux-gnu/libgdk-3.so.0 (0x00007f8a33c58000)
        libpango-1.0.so.0 => /lib/x86_64-linux-gnu/libpango-1.0.so.0 (0x00007f8a33c0c000)
        libcairo-gobject.so.2 => /lib/x86_64-linux-gnu/libcairo-gobject.so.2 (0x00007f8a33c00000)
        libcairo.so.2 => /lib/x86_64-linux-gnu/libcairo.so.2 (0x00007f8a33ae0000)
        libgdk_pixbuf-2.0.so.0 => /lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0 (0x00007f8a33ab8000)
        libgio-2.0.so.0 => /lib/x86_64-linux-gnu/libgio-2.0.so.0 (0x00007f8a338db000)
        libgobject-2.0.so.0 => /lib/x86_64-linux-gnu/libgobject-2.0.so.0 (0x00007f8a3387e000)
        libglib-2.0.so.0 => /lib/x86_64-linux-gnu/libglib-2.0.so.0 (0x00007f8a33756000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f8a33750000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f8a3372d000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f8a33713000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a33520000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f8a344dd000)
        libgmodule-2.0.so.0 => /lib/x86_64-linux-gnu/libgmodule-2.0.so.0 (0x00007f8a3351a000)
        libpangocairo-1.0.so.0 => /lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 (0x00007f8a33509000)
        libX11.so.6 => /lib/x86_64-linux-gnu/libX11.so.6 (0x00007f8a333cb000)
        libXi.so.6 => /lib/x86_64-linux-gnu/libXi.so.6 (0x00007f8a333b9000)
        libXfixes.so.3 => /lib/x86_64-linux-gnu/libXfixes.so.3 (0x00007f8a331b3000)
        libatk-1.0.so.0 => /lib/x86_64-linux-gnu/libatk-1.0.so.0 (0x00007f8a33187000)
        libatk-bridge-2.0.so.0 => /lib/x86_64-linux-gnu/libatk-bridge-2.0.so.0 (0x00007f8a33151000)
        libepoxy.so.0 => /lib/x86_64-linux-gnu/libepoxy.so.0 (0x00007f8a3301f000)
        libfribidi.so.0 => /lib/x86_64-linux-gnu/libfribidi.so.0 (0x00007f8a33002000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f8a32eb3000)
        libharfbuzz.so.0 => /lib/x86_64-linux-gnu/libharfbuzz.so.0 (0x00007f8a32dbc000)
        libpangoft2-1.0.so.0 => /lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 (0x00007f8a32da0000)
        libfontconfig.so.1 => /lib/x86_64-linux-gnu/libfontconfig.so.1 (0x00007f8a32d5a000)
        libfreetype.so.6 => /lib/x86_64-linux-gnu/libfreetype.so.6 (0x00007f8a32c9f000)
        libXinerama.so.1 => /lib/x86_64-linux-gnu/libXinerama.so.1 (0x00007f8a32c9a000)
        libXrandr.so.2 => /lib/x86_64-linux-gnu/libXrandr.so.2 (0x00007f8a32c8d000)
        libXcursor.so.1 => /lib/x86_64-linux-gnu/libXcursor.so.1 (0x00007f8a32c80000)
        libXcomposite.so.1 => /lib/x86_64-linux-gnu/libXcomposite.so.1 (0x00007f8a32c79000)
        libXdamage.so.1 => /lib/x86_64-linux-gnu/libXdamage.so.1 (0x00007f8a32c74000)
        libxkbcommon.so.0 => /lib/x86_64-linux-gnu/libxkbcommon.so.0 (0x00007f8a32c33000)
        libwayland-cursor.so.0 => /lib/x86_64-linux-gnu/libwayland-cursor.so.0 (0x00007f8a32c2a000)
        libwayland-egl.so.1 => /lib/x86_64-linux-gnu/libwayland-egl.so.1 (0x00007f8a32c25000)
        libwayland-client.so.0 => /lib/x86_64-linux-gnu/libwayland-client.so.0 (0x00007f8a32c14000)
        libXext.so.6 => /lib/x86_64-linux-gnu/libXext.so.6 (0x00007f8a32bfd000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f8a32bf2000)
        libthai.so.0 => /lib/x86_64-linux-gnu/libthai.so.0 (0x00007f8a32be7000)
        libpixman-1.so.0 => /lib/x86_64-linux-gnu/libpixman-1.so.0 (0x00007f8a32b40000)
        libpng16.so.16 => /lib/x86_64-linux-gnu/libpng16.so.16 (0x00007f8a32b08000)
        libxcb-shm.so.0 => /lib/x86_64-linux-gnu/libxcb-shm.so.0 (0x00007f8a32b01000)
        libxcb.so.1 => /lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f8a32ad8000)
        libxcb-render.so.0 => /lib/x86_64-linux-gnu/libxcb-render.so.0 (0x00007f8a32ac9000)
        libXrender.so.1 => /lib/x86_64-linux-gnu/libXrender.so.1 (0x00007f8a328bf000)
        libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f8a328a3000)
        libmount.so.1 => /lib/x86_64-linux-gnu/libmount.so.1 (0x00007f8a32843000)
        libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f8a32816000)
        libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f8a327fb000)
        libffi.so.6 => /lib/x86_64-linux-gnu/libffi.so.6 (0x00007f8a327f1000)
        libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f8a3277d000)
        libdbus-1.so.3 => /lib/x86_64-linux-gnu/libdbus-1.so.3 (0x00007f8a3272e000)
        libatspi.so.0 => /lib/x86_64-linux-gnu/libatspi.so.0 (0x00007f8a326f5000)
        libgraphite2.so.3 => /lib/x86_64-linux-gnu/libgraphite2.so.3 (0x00007f8a326c8000)
        libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007f8a3269a000)
        libuuid.so.1 => /lib/x86_64-linux-gnu/libuuid.so.1 (0x00007f8a32691000)
        libdatrie.so.1 => /lib/x86_64-linux-gnu/libdatrie.so.1 (0x00007f8a32687000)
        libXau.so.6 => /lib/x86_64-linux-gnu/libXau.so.6 (0x00007f8a3267f000)
        libXdmcp.so.6 => /lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f8a32677000)
        libblkid.so.1 => /lib/x86_64-linux-gnu/libblkid.so.1 (0x00007f8a32620000)
        libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f8a3259b000)
        libsystemd.so.0 => /lib/x86_64-linux-gnu/libsystemd.so.0 (0x00007f8a324f3000)
        libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f8a324d7000)
        liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007f8a324b0000)
        liblz4.so.1 => /lib/x86_64-linux-gnu/liblz4.so.1 (0x00007f8a32490000)
        libgcrypt.so.20 => /lib/x86_64-linux-gnu/libgcrypt.so.20 (0x00007f8a32372000)
        libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0 (0x00007f8a3234f000)
Elles sont toutes résolues : c'est la seule prérogative pour pouvoir construire un fichier AppImage à partir du binaire.

Nous pouvons donc passer à l'étape de la création du fichier AppImage. Pour cela je vais me servir de Docker, car il est recommandé de construire l'AppImage sur une version de linux assez ancienne afin qu'elle puisse ensuite s'exécuter sur la majorité des distributions existantes. Sans rentrer dans les détails, c'est notamment à cause de la libc.

J'ai donc opté pour l'utilisation de Docker pour me faciliter la tâche, sachant que je pourrais monter un volume sur l'image docker afin de pouvoir partager des fichiers depuis mon système hôte.

Sachez également que Travis CI (entre autres) permet de faire cela plus simplement depuis un repository Github, mais cela sort du cadre de ce billet.

Construction avec Docker

L'image personnalisée pour l’environnement de travail

Tout d'abord, nous pouvons nous constituer une image Docker qui contiendra à minima l'installation de Rust et les utilitaires pour construire l'AppImage. Par la suite nous lancerons un conteneur basé sur cette image (que l'on pourra réutiliser à souhait), en mode intéractif, afin de construire l'AppImage

Veuillez créer un conteneur basé sur la version 16.04 d'Ubuntu, en démarrant un bash en mode interactif:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
$> docker run -t -i --name ubuntu_16_rust_setup ubuntu:16.04 /bin/bash
Voici la liste des commandes à entrer, une à une, une fois que le conteneur a démarré (attention à ne pas insérer mes commentaires ):

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
apt update
apt install wget curl git fuse build-essential libgtk-3-dev -y
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # installation de Rust : veuillez chosir l'option par défaut
exit
De retour dans votre terminal hôte (grâce à la commande exit), veuillez transformer le conteneur juste créé en image:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
$> docker commit ubuntu_16_rust_setup ubuntu_16_rust:v1
Comme précisé plus haut, cette image pourra être réutilisée plusieurs fois.

Construction de l'AppImage

Nous pouvons donc démarrer un conteneur à partir de notre base de travail:
  • j'utilise encore le mode intéractif
  • je monte un volume me permettant d'accéder aux ressources que j'ai préparées pour mon projet (fichier desktop, icone): ici avec le dossier courant ($PWD)
  • j'indique également quelques drapeaux supplémentaires afin de pouvoir créer l'AppImage sans tracas
  • ce conteneur sera détruit (--rm) en le quittant : vous pouvez en décider autrement si vous le souhaitez (c'est juste dans mon utilisation personnelle, j'ai trouvé cela plus pratique)


Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
$> docker run -t -i -v "$PWD:/home/host_documents" --cap-add SYS_ADMIN --cap-add MKNOD --device=/dev/fuse --security-opt apparmor:unconfined --rm ubuntu_16_rust:v1 /bin/bash
Préparatifs côté hôte

Avant de commencer la construction de l'AppImage, préparons les ressources nécessaires, que nous placerons dans le dossier hôte sur lequel nous avons monté notre image de travail:
  • un fichier .desktop : je vous passe le contenu du mien plus bas
  • une icône png (la taille 16x16 peut faire l'affaire)
  • le script qui nous simplifiera la tâche, je vous en passe le contenu plus bas


Voici un fichier .desktop (SimpleCalc.desktop) que vous pouvez utiliser:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
[Desktop Entry]
Version=1.0
Type=Application
Name=Simple calculator
Comment=A ridiculously simple calculator
Exec=simple-calc
Icon=calculator
Categories=Utility;Education;
Il requiert donc que l'icône utilisée s'appelle calculator.png.

Voici le script que nous utiliserons dans le conteneur (build_appimage.sh) :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/bin/bash

##############################################################
# Building exectuable into AppImage (Laurent Bernabe)        #
# from an Ubuntu 14.04 x86_64 docker image.                  #
#                                                            #
# Also these arguments are needed when lauching docker run ! #
# --cap-add SYS_ADMIN                                        #
# --cap-add MKNOD                                            #
# --device=/dev/fuse                                         #
# --security-opt apparmor:unconfined                         #
#                                                            #
# Volume /home/host_documents should point to                #
# the build files folder !                                   #
#                                                            #
# Arguments:                                                 #
# 1 => Path to AppImage target (with AppImage extension)     #
# 2 => Path to executable to wrap                            #
# 3 => Desktop file name (with .desktop extension)           #
# 4 => Icon file name (with extension)                       #
##############################################################

if [[ "$#" -lt 4 ]]; then
    echo "Usage: build_appimage TARGET_APPIMAGE EXECUTABLE DESKTOP_FILE ICON_FILE"
    exit 1
fi


apt update
apt install wget fuse file -y

wget -P /tmp https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
wget -P /tmp https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x /tmp/linuxdeploy-x86_64.AppImage /tmp/appimagetool-x86_64.AppImage
cd /home/host_documents

/tmp/linuxdeploy-x86_64.AppImage -e $2 -d $3 -i $4 --appdir $1
/tmp/appimagetool-x86_64.AppImage $1

exit 0
Notamment, ce script:
  1. Télécharge les deux utilitaires necessaires à la création d'AppImage à partir d'un binaire existant,
  2. construit un dossier AppImage à partir de l'exécutable et de ses ressources,
  3. construit une archive AppImage à partir du dossier ainsi généré.


Etape finale

Allons-y !

Rendons les commandes de Rust disponibles:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
$> source $HOME/.cargo/env
Crééons un dossier projets:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
$> mkdir -p /home/projects
$> cd /home/projects
Clonons le projet et compilons-le:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
$> git clone https://github.com/loloof64/SimpleCalculator.git calculatrice
$> cd calculatrice
$> cargo build --release
Enfin passons à la production de l'AppImage, en n'oubliant pas de renseigner les différents paramètres

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
$> cd /home/host_documents
$> chmod +x build_appimage.sh
$> ./build_appimage.sh SimpleCalculator.AppImage /home/projects/calculatrice/target/release/simple-calc ./SimpleCalc.desktop ./calculator.png
Normalement vous devriez disposer du fichier AppImage dans votre dossier monté sur le système hôte. N'oubliez pas de quitter le conteneur

Conclusion

Voilà en ce qui concerne la création de l'AppImage, j'espère que cela n'a pas été indigeste.
Vous avez donc la possibilité de tester le programme sur différentes distributions Linux. Sachez que pour ma part, j'ai réussi à l'exécuter sur des distributions Ubuntu (Ubuntu, Kubuntu, Xubuntu), Gentoo, Manjaro Linux, Linux Mint datant de 2016/2017 et même 2020 entre autres.

Je tiens quand même à remercier les contributeurs des projets relatifs à AppImageKit, qui m'ont gentiment répondu à mes interrogations sur leur chat Irc : en particulier TheNinjaAssasin.

Envoyer le billet « [Rust][AppImage][Docker][Linux] Création d'une AppImage à partir d'un binaire Rust avec Docker » dans le blog Viadeo Envoyer le billet « [Rust][AppImage][Docker][Linux] Création d'une AppImage à partir d'un binaire Rust avec Docker » dans le blog Twitter Envoyer le billet « [Rust][AppImage][Docker][Linux] Création d'une AppImage à partir d'un binaire Rust avec Docker » dans le blog Google Envoyer le billet « [Rust][AppImage][Docker][Linux] Création d'une AppImage à partir d'un binaire Rust avec Docker » dans le blog Facebook Envoyer le billet « [Rust][AppImage][Docker][Linux] Création d'une AppImage à partir d'un binaire Rust avec Docker » dans le blog Digg Envoyer le billet « [Rust][AppImage][Docker][Linux] Création d'une AppImage à partir d'un binaire Rust avec Docker » dans le blog Delicious Envoyer le billet « [Rust][AppImage][Docker][Linux] Création d'une AppImage à partir d'un binaire Rust avec Docker » dans le blog MySpace Envoyer le billet « [Rust][AppImage][Docker][Linux] Création d'une AppImage à partir d'un binaire Rust avec Docker » dans le blog Yahoo

Mis à jour 21/05/2020 à 18h59 par tails

Catégories
Rust

Commentaires