Compiler un AOSP sans changer de distribution
Pourquoi se donner tant de mal ?
Pour compiler Android, Google suggère aux utilisateurs d'installer Ubuntu 14.04 et ne supporte pas d'autres distributions Linux. Il est toujours possible d'utiliser une autre distribution, mais sans garantie de réussite. Il est, par exemple, possible de compiler Android 7 avec Debian Stretch, mais pas Android 6 (java 1.7 ne fait plus partie des dépôts). Les distributions Arch et Ubuntu 18.04 par contre ne compilent pas Android 7.
Un des avantages de Linux reste de pouvoir choisir et personnaliser sa distribution, et c'est bien dommage de réinstaller un système ou une machine virtuelle pour pouvoir compiler un programme.
Le but de cet article est d'installer Ubuntu sur un disque virtuel et de monter le disque avec la distribution déjà installée pour l'utiliser avec chroot. Nous aurons alors un système hôte avec la distribution choisie par le développeur et un système invité (Ubuntu) choisi par Google sans en avoir l'environnement (graphique, entre autres), comme c'est le cas avec une machine virtuelle. Nous évitons aussi les problèmes de communication entre les deux environnements, comme le copier/coller ou la gestion du focus. Pour compiler Android, un terminal dans l'environnement invité suffit et le reste du développement peut se faire sur l'hôte.
Nous aurons besoin d'un disque virtuel de seulement 10Go, avec une partition swap et une partition pour le système, Nous verrons en particulier comment partager les fichiers entre les deux systèmes pour ne pas avoir les 130Go de l'espace de travail AOSP dupliqués ou cloisonnés dans le système invité. Ce disque de 10Go pourra être utilisé pour compiler différents AOSP sur le disque de l'hôte, nous n'avons pas besoin d'un disque pour chaque espace de travail.
En se passant d'une machine virtuelle (au sens émulation lourde d'un OS telle que VirtualBox), nous évitons aussi les problèmes de performances.
Dans la suite de cet article le terme "machine virtuelle" désigne le "chroot dans le disque virtuel" construit à l'aide de QEMU.
Mise en garde
Après quelques manipulations, il est possible que la machine virtuelle n'arrive pas à démarrer. Dans ce cas là, vous pouvez démarrer le mode d'urgence d'Ubuntu et utiliser des outils de diagnostic fournis. Si vous avez besoin d'accéder à un tty de la machine virtuelle, le raccourci Ctrl+Alt+F2
serait capturé par l'hôte. Il faut à la place utiliser Ctrl+Alt+2
pour accéder à la console qemu, ensuite taper sendkey ctrl-alt-f2
et retourner au mode graphique avec Ctrl+Alt+1
Télécharger les dépôts AOSP
Pour commencer, il faut télécharger AOSP. La documentation Google vous indiquera comment faire, mais je ne détaillerai pas plus ici que les commandes suivantes :
$ mkdir <workspace>/AOSP $ cd <workspace>/AOSP $ repo init -u <manifest> $ repo sync -j8
Préparer la machine virtuelle avec Ubuntu 14.04
Pour installer Ubuntu, nous allons utiliser le CD-Rom d'installation que nous allons exécuter dans qemu.
Nous commencerons par créer un disque virtuel de 10Go en utilisant le format raw (plutôt qu'un format spécifique comme qcow). Cela nous permettra de monter les partitions plus facilement par la suite.
Le partitionnement par défaut d'Ubuntu ne fonctionne pas pour un disque aussi petit. Il faut configurer manuellement les partitions. J'ai choisi de mettre 1Go pour la partition swap et les 9Go restant pour la partition racine.
Il faut bien entendu un CD ou une image d'installation d'Ubuntu 14.04. Vous pouvez en trouver facilement sur le site d'Ubuntu.
$ mkdir ../ubuntu $ cd ../ubuntu $ sudo dd if=/dev/zero of=ubuntu_system.img bs=1M count=10240 $ sudo qemu-system-x86_64 -enable-kvm -cdrom ubuntu-14.04.5-desktop-amd64.iso -drive file=ubuntu_system.img,format=raw -m 8G -boot d
Dans cet exemple et dans les exemples qui suivent, le disque virtuel et le disque d'installation d'Ubuntu se trouvent dans un dossier nommé ubuntu
à côté du dossier AOSP
.
A part pour les partitions, l'installation d'Ubuntu ne doit pas poser problème. J'ai pris le même nom d'utilisateur pour la machine hôte et invitée. Ce n'est pas important mais les exemples partiront de ce principe.
Il est indispensable de s'assurer que les utilisateurs de chaque système aient le même UID. La façon dont les fichiers seront partagés entre les deux systèmes préservera les droits d'accès et leur propriétaire (donc leur UID). À l'installation, le système Ubuntu donnera à l'utilisateur principal l'UID 1000, si ce n'est pas le même que l'utilisateur hôte, il faudra créer un autre utilisateur avec le bon UID.
Configurer la machine virtuelle
La distribution Ubuntu a été installée dans le disque virtuel. Nous pouvons maintenant la démarrer facilement avec Qemu.
Pour démarrer la machine virtuelle :
$ sudo qemu-system-x86_64 -enable-kvm -drive file=ubuntu_system.img,format=raw -m 8G -k fr
Nous devons installer les dépendances nécessaire pour compiler Android :
# sudo apt-get update # sudo apt-get upgrade # sudo apt-get install openjdk-7-jdk # Important pour Android 6 # sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev libgl1-mesa-dev libxml2-utils xsltproc unzip # sudo apt-get install ssh
La dernière ligne installe le paquet ssh. Ce paquet nous permettra de se connecter à la machine invitée depuis l'hôte.
Pour pouvoir nous connecter sur la machine virtuelle, il est nécessaire de configurer une redirection du port 22 de la VM vers un port libre de l'hôte. Cela se fait avec l'option qemu -redir tcp:2222::22
:
$ sudo qemu-system-x86_64 -enable-kvm -drive file=ubuntu_system.img,format=raw -m 8G -k fr -redir tcp:2222::22
Nous pouvons maintenant nous connecter à la machine virtuelle depuis la machine hôte en utilisant l'adresse IP localhost et le port utilisé dans la ligne de commande :
$ ssh -p 2222 localhost
Accéder aux données du système invité
Maintenant que le disque virtuel contient un système complet, nous pouvons monter ses partitions dans le système de fichiers de l'hôte.
Pour cela nous devons déterminer où se trouvent les différentes partitions dans le fichier image, puis nous nous servirons de l'option offset
de la commande mount
pour monter la partition système correctement.
$ sudo fdisk -l ubuntu_system.img
Disk ubuntu_system.img: 10 GiB, 10737418240 bytes, 20971520 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x00061aab Device Boot Start End Sectors Size Id Type ubuntu_system.img1 * 2048 2000895 1998848 976M 82 Linux swap / Solaris ubuntu_system.img2 2002942 20969471 18966530 9G 5 Extended ubuntu_system.img5 2002944 20969471 18966528 9G 83 Linux
Le résultat de la commande dépend de l'installation d'Ubuntu. Pendant le partitionnement j'ai créé une partition swap et une partition pour le système.
La partition ubuntu_system.img2 est de type Extended, démarre deux blocs avant ubuntu_system.img5 et est plus grande de deux blocs. Nous en déduisons que ubunt_system.img2 est la partition étendue (une partition qui contient d'autre partitions) et que ubuntu_system.img5 est la partitions system que nous cherchons à monter.
Pour déterminer l'argument offset de la commande mount, nous devons multiplier la taille d'un bloc par le bloc de départ de la partition. Dans la sortie précédente, l'offset est de 512*2002944 = 1025507328. Un fois cet offset calculé, nous pouvons monter la partition directement depuis l'hôte.
$ mkdir /tmp/ed $ sudo mount -o loop,offset=1025507328 ubuntu_system.img /tmp/ed
L'option loop
permet de monter un fichier plutôt qu'un périphérique.
Utilisation de chroot
Maintenant que le système de fichiers d'Ubuntu est accessible depuis notre hôte, nous allons utiliser chroot
pour facilement utiliser les outils de compilation d'Ubuntu depuis notre hôte.
chroot
permet de substituer la racine du système de fichiers pour un processus, ce qui nous permettra d'executer le shell bash
d'Ubuntu, et d'utiliser les binaires de ce système plutôt que le notre.
À la création de la machine virtuelle nous avons utilisé un disque d'une faible capacité avec l'idée de partager les répertoires volumineux de l'hôte. Il y a bien entendu l'espace de travail d'AOSP mais il ne faut pas oublier le dossier ~/.cccache
, qui devient rapidement très volumineux..
Si ce n'est plus le cas, il faut monter de nouveau le système de fichiers d'Ubuntu dans celui du système hôte.
$ sudo mount -o loop,offset=1025507328 ubuntu_system.img /tmp/ed
Nous utiliserons la version de ccache fourni avec l'AOSP :
$ prebuilts/misc/linux-x86/ccache/ccache -M 20G $ echo "export USE_CCACHE=1" >> /tmp/ed/home/anloh/.bash_aliases $ mkdir /tmp/ed/home/anloh/.ccache
Ensuite nous devons lier certaines ressources des deux systèmes. Il faut partager /proc
pour le bon fonctionnement de l'environnement chroot, le cache et l'espace de travail AOSP pour que les fichiers produits puissent être utilisés depuis le système hôte :
$ mkdir /tmp/ed/proc $ sudo mount --bind /proc /tmp/ed/proc $ mkdir /tmp/ed/home/anloh/AOSP $ sudo mount --bind ../AOSP /tmp/ed/home/anloh/AOSP $ mkdir /tmp/ed/home/anloh/.ccache $ sudo mount --bind ~/.ccache /tmp/ed/home/anloh/.ccache
Ensuite nous créons l'environnement chroot.
Le programme exécuté par défaut par chroot est bash
mais j'utilise plutôt login
, pour avoir une configuration correcte de l'environnement.
$ sudo chroot /tmp/ed/ login
Cette commande demande la saisie du mot de passe de l'hôte pour l'utilisation de sudo
puis l'identifiant et le mot de passe de l'utilisateur de la machine invitée qui se connecte. Faites attention ici à ne pas taper le mot de passe hôte quand la console demande et affiche le nom de l'utilisateur de la machine virtuelle.
Une fois l'environnement en place, tout est réuni pour compiler l'AOSP :
# cd AOSP # source build/envsetup.sh # lunch <device> # make -j8
Une fois la compilation terminée, nous pouvons nettoyer les points de montage (depuis la machine hôte) :
$ sudo umount /home/tmp/ed/proc $ sudo umount /home/tmp/ed/home/anloh/AOSP $ sudo umount /home/tmp/ed/home/anloh/.ccache $ sudo umount /home/tmp/ed
Résumé
Pour résumer l'utilisation de l'environnement avec chroot, l'environnement est mis en place avec les commandes :
$ sudo mount -o loop,offset=1025507328 ubuntu_system.img /tmp/ed $ sudo mount --bind ../AOSP /tmp/ed/home/anloh/AOSP $ sudo mount --bind /proc /tmp/ed/proc $ sudo mount --bind ~/.ccache /tmp/ed/home/anloh/ $ sudo chroot /tmp/ed/ login
puis il est nettoyé avec les commandes :
$ sudo umount /home/tmp/ed/home/anloh/.ccache $ sudo umount /home/tmp/ed/home/anloh/AOSP $ sudo umount /home/tmp/ed/proc $ sudo umount /home/tmp/ed
Conclusion
Nous avons vu dans cet article comment compiler Android avec une distribution qui ne le permet pas. Nous avons conservé notre environnement et utilisé en même temps la distribution adaptée au développement AOSP. Nous avons aussi limité fortement les pertes en performances qu'aurait introduit la virtualisation.
Aujourd'hui nous avons souvent besoin d'un environnement spécifique pour développer un logiciel. Plutôt que de choisir une distribution ou de forcer un environnement, nous pourrions penser à utiliser/déployer des conteneurs, en cherchant à déterminer une limite entre ce qui relève de l'environnement de développement, qui est un choix de chaque développeur et des pré-requis. Cet article s'intègre dans un cas où le problème viens de la compilation qui se fait en ligne de commande, mais d'autres développements requièrent une interface graphique, ce qui limite notre solution.