ii. Remarques techniques sur la chaîne de compilation

Cette section explique certains détails logiques et techniques de la méthode de construction. Il n'est pas essentiel de tout comprendre immédiatement. La plupart de ces informations seront plus claires une fois votre première construction complète terminée. Cette section peut servir de référence à tout moment lors du processus de construction.

Le but global du Chapitre 5 et du Chapitre 6 est de générer un espace temporaire comportant un ensemble d'outils connus et approuvés, qui sont isolés du système hôte. En utilisant la commande chroot, les compilations réalisées dans le reste des chapitres se cantonneront à cet environnement, en assurant une construction du système LFS cible propre et sans faille. Le processus de construction a été conçu pour minimiser les risques auprès des nouveaux lecteurs et pour fournir une valeur éducative maximale.

Le processus de construction se base sur la compilation croisée. La compilation croisée est normalement utilisée pour construire un compilateur ainsi que sa chaîne de compilation sur une machine différente de celle utilisée pour la construction. Ce n’est pas nécessaire pour LFS, puisque la machine sur laquelle le nouveau système est construit est la même que celle utilisée pour la construction. Mais le grand avantage de la compilation croisée, c’est que tout ce qui est compilé est indépendant de l'environnement hôte.

À propos de la compilation croisée

[Note]

Note

Le livre LFS n'est pas (et ne contient pas) un tutoriel générique sur la construction d'une chaîne de compilation croisée (ou native). N'utilisez pas les commandes de ce livre pour construire une chaîne de compilation croisée autre que celle utilisée pour LFS, à moins de vraiment comprendre ce que vous faites.

La compilation croisée utilise certains concepts qui mériteraient une section à part. Même si vous pouvez passer cette section lors de votre première lecture, nous vous recommandons fortement d'y revenir plus tard pour bien comprendre le processus de construction.

Définissons d'abord certains termes utilisés dans ce contexte.

La construction (build)

est la machine où nous construisons les programmes. Cette machine est appelée « hôte » dans les autres sections.

L’hôte (host)

est la machine ou le système où les programmes seront lancés. Le terme « hôte » n’a pas la même définition dans les autres sections.

La cible (target)

est seulement utilisée pour les compilateurs. C'est la machine pour laquelle le compilateur produit du code. Elle peut être différente de l’hôte et de la construction.

Par exemple, imaginons le scénario suivant (parfois appelé « Canadian Cross ») : nous avons un compilateur sur une machine lente, que nous appellerons A, et le compilateur ccA. Nous avons également une machine rapide (B), mais aucun compilateur pour (B). Nous voulons produire du code pour une troisième machine, lente cette fois (C). Il y a trois étapes à suivre pour construire un compilateur destiné à la machine C.

Étape Construction Hôte Cible Action
1 A A B Construire un compilateur croisé cc1 avec ccA sur la machine A.
2 A B C Construire un compilateur croisé cc2 avec cc1 sur la machine A.
3 B C C Construire le compilateur ccC avec cc2 sur la machine B.

Ensuite, tous les programmes requis par la machine C peuvent être compilés avec cc2 sur la machine rapide B. À moins que B ne puisse lancer les programmes produits pour C, il n'existe aucun moyen de tester les nouveaux programmes construits avant de les lancer sur la machine C. Par exemple, pour tester ccC, nous pouvons ajouter une quatrième étape :

Étape Construction Hôte Cible Action
4 C C C Reconstruire et tester ccC avec lui-même sur la machine C.

Dans l'exemple ci-dessus, seuls cc1 et cc2 sont des compilateurs croisés, c'est-à-dire qu'ils produisent du code pour une machine différente de celle sur laquelle ils tournent. Les autres compilateurs ccA et ccC produisent du code pour la machine sur laquelle ils tournent. Ces compilateurs sont appelés compilateurs natifs.

Implémentation de la compilation croisée dans LFS

[Note]

Note

Tous les paquets compilés de manière croisée dans ce livre utilisent un système de construction basé sur autoconf. Le système de construction basé sur autoconf accepte des types de systèmes de la forme cpu-fabriquant-noyau-os, nommés triplets systèmes. Comme le champ fabriquant est souvent inutile, autoconf vous autorise à ne pas le renseigner.

Le lecteur attentif se demandera pourquoi un « triplet » désigne un nom à quatre composantes. Le champ du noyau et de l'os ont d'abord commencé comme un seul champ « système ». Cette forme à trois champs est toujours valide de nos jours pour certains systèmes, par exemple x86_64-unknown-freebsd. Mais deux systèmes peuvent partager le même noyau et être trop différents pour utiliser le même triplet pour les désigner. Par exemple Android qui tourne sur les téléphones est complètement différent d'Ubuntu sur un serveur ARM64, même s'ils utilisent tous les deux le même type de CPU (ARM64) et utilisent le même noyau (Linux).

Sans une couche d'émulation, vous ne pouvez pas lancer un exécutable pour un serveur sur un téléphone portable et vice-versa. Donc le champ « système » a été divisé en les champs noyau et os, pour désigner trois systèmes sans ambiguïté. Dans notre exemple, le système Android est appelé aarch64-unknown-linux-android et le système Ubuntu est appelé aarch64-unknown-linux-gnu.

Le mot « triplet » reste ancré dans le vocabulaire informatique. Pour déterminer simplement votre triplet système, vous pouvez lancer le script config.guess fournit avec les sources de nombreux paquets. Désarchivez les sources de binutils, lancez le script ./config.guess, et notez la sortie. Par exemple pour un processeur Intel 32 bits la sortie sera i686-pc-linux-gnu. Sur un système 64 bits elle sera x86_64-pc-linux-gnu. Sur la plupart des systèmes Linux la commande encore plus simple gcc -dumpmachine vous donner cette information.

Faites également attention au nom de l'éditeur de liens dynamiques de la plateforme, souvent appelé chargeur dynamique (à ne pas confondre avec l'éditeur de liens standard ld qui fait partie de binutils). Le chargeur dynamique fourni par glibc trouve et charge les bibliothèques partagées nécessaires à l’exécution d’un programme, prépare le programme, puis l'exécute.Le nom du chargeur dynamique pour une machine Intel 32 bits sera ld-linux.so.2 (ld-linux-x86-64.so.2 pour les systèmes 64 bits). Pour déterminer le nom du chargeur dynamique, inspectez un binaire au hasard sur le système hôte en exécutant : readelf -l <nom du binaire> | grep interpreter et récupérez le résultat. La référence officielle qui couvre toutes les plateformes se trouve sur une page wiki de Glibc.

Pour simuler une compilation croisée dans LFS, le nom du triplet hôte est légèrement modifié en changeant le champ « fabriquant » dans la variable d’environnement LFS_TGT pour qu’il indique « lfs . Nous utilisons également l'option --with-sysroot lors de la construction de l'éditeur de liens et du compilateur croisés pour leur indiquer l’emplacement des fichiers hôte requis. Cette option permet de s’assurer qu'aucun autre programme construit dans le Chapitre 6 ne peut s’associer aux bibliothèques sur la machine de construction. Seules deux étapes sont obligatoires, en plus d’une étape supplémentaire destinée aux tests.

Étape Construction Hôte Cible Action
1 pc pc lfs Construire un compilateur croisé cc1 avec cc-pc sur pc.
2 pc lfs lfs Construire un compilateur cc-lfs avec cc1 sur pc.
3 lfs lfs lfs Reconstruire et tester cc-lfs avec lui-même sur lfs.

Dans le tableau précédent, « sur pc » signifie que les commandes sont exécutées sur une machine qui utilise la distribution déjà installée. « Sur lfs » signifie que les commandes sont exécutées dans un environnement chroot.

Ce n'est pas la fin de l'histoire. Le langage C n'est pas seulement un compilateur, mais il définit aussi une bibliothèque standard. Dans ce livre, nous utilisons la bibliothèque C de GNU, appelée glibc (son alternative étant « musl »). Cette bibliothèque doit être compilée pour la machine LFS, c'est-à-dire à l’aide du compilateur croisé cc1. Mais le compilateur lui-même utilise une bibliothèque interne qui exécute des instructions complexes indisponibles dans le jeu d'instructions de l'assembleur. Cette bibliothèque interne, libgcc, doit être liée à la bibliothèque glibc pour fonctionner correctement ! De plus, la bibliothèque standard C++ (libstdc++) a aussi besoin d'être associée à glibc. La solution à ce problème consiste d'abord à construire une libgcc inférieure basée sur cc1, qui ne dispose pas de fonctionnalités avancées comme les threads et le traitement des exceptions, puis de construire glibc avec ce compilateur inférieur (glibc elle-même n'étant pas inférieure), puis de construire libstdc++. Cette bibliothèque ne dispose pas des fonctionnalités avancées de libgcc.

La conséquence du paragraphe précédent est que cc1 est incapable de construire une libstdc++ complètement fonctionnelle avec la libgcc dégradée, mais cc1 est le seul compilateur disponible pour construire les bibliothèques C/C++ à la deuxième étape. Il y a deux raisons pour lesquelles nous n'utilisons pas immédiatement le compilateur construit à l'étape 2, cc-lfs, pour construire ces bibliothèques.

  • En général, cc-lfs ne peut pas se lancer sur pc (le système hôte). Même si les triplets pour pc et lfs sont compatibles l'un avec l'autre, un exécutable pour lfs doit dépendre de glibc-2.40. La distribution hôte peut utiliser une implémentation différent de la libc (par exemple, musl) ou une version précédente de glibc (par exemple glibc-2.13).

  • Même si cc-lfs peut s'exécuter sur pc, l'utiliser sur pc induirait le risque de se lier aux bibliothèques de pc, comme cc-lfs est un compilateur natif.

Ainsi, lorsque nous construisons l’étape 2 de gcc, nous demandons au système de construction de reconstruire libgcc et libstdc++ avec cc1, mais nous lions libstdc++ à la nouvelle libgcc reconstruite plutôt qu’à l'ancienne construction dégradée, pour faire en sorte que la libstdc++ reconstruite soit entièrement opérationnelle.

Dans le Chapitre 8, (ou « l’étape 3 »), tous les paquets nécessaires au système LFS sont construits. Même si vous avez déjà installé un paquet sur le système LFS dans un chapitre précédent, vous devrez reconstruire le paquet, à moins d’être certain qu’il n’est pas nécessaire. La stabilisation de ces paquets est la raison principale de leur reconstruction : si vous réinstallez un paquet LFS sur un système complet LFS, le contenu du paquet installé devrait être identique au contenu de ce paquet installé dans le Chapitre 8. Les paquets temporaires installés dans le Chapitre 6 ou le Chapitre 7 ne sont pas concernés, car certains d’entre eux sont construits sans dépendance optionnelle, et autoconf ne peut pas exécuter certaines vérifications dans le Chapitre 6 à cause de la compilation croisée. Les paquets temporaires ne disposent donc pas de certaines fonctionnalités optionnelles ou utilisent des routines sous-optimales. De plus, en reconstruisant les paquets, vous permettez l’exécution de la suite de tests.

Détails supplémentaires de procédure

Le compilateur croisé sera installé dans un répertoire $LFS/tools séparé, puisqu’il ne fera pas partie du système final.

Binutils est installé en premier parce que la commande configure de gcc et glibc effectuent des tests de fonctionnalités sur l'assembleur et l'éditeur de liens pour déterminer quelles fonctionnalités logicielles activer ou désactiver. Cette installation est plus importante que ce que vous pouvez penser. Un gcc ou une glibc mal configurés peuvent casser la chaîne de compilation, et l'impact d’une telle configuration ne se verrait qu’à la fin de la construction de la distribution complète. La suite de tests indiquera généralement cette erreur avant que vous n’ayez trop avancé.

Binutils installe son assembleur et son éditeur de liens à deux emplacements, $LFS/tools/bin et $LFS/tools/$LFS_TGT/bin. Les outils situés à un emplacement sont liés à l’autre par un lien en dur. L’ordre de recherche des bibliothèques est un aspect important de l’éditeur de liens. Vous pouvez obtenir des informations détaillées à partir de la commande ld en lui passant l’option --verbose. Par exemple, la commande ld --verbose | grep SEARCH affichera les chemins de recherche actuels et leur ordre. Cet exemple peut être exécuté en mode lecture par l’utilisateur lfs. Si vous revenez plus tard sur cette page, remplacez la commande $LFS_TGT-ld par ld.

Le prochain paquet installé est gcc. Voici un exemple de ce qui peut s’afficher pendant l'exécution de la commande configure :

checking what assembler to use... /mnt/lfs/tools/i686-lfs-linux-gnu/bin/as
checking what linker to use... /mnt/lfs/tools/i686-lfs-linux-gnu/bin/ld

Il s’agit d’un paquet important pour les raisons mentionnées plus haut. C’est également la preuve que le script configure de gcc ne parcourt pas les répertoires du PATH pour trouver quels outils utiliser. Cependant, lors de l’exécution normale de la commande gcc, les mêmes chemins de recherche ne sont pas forcément utilisés. Pour trouver quel éditeur de liens standard gcc utilise, exécutez la commande $LFS_TGT-gcc -print-prog-name=ld. À nouveau, retirez la commande $LFS_TGT- si vous revenez ici plus tard.

Vous pouvez obtenir des informations détaillées grâce à la commande gcc en lui passant l’option -v lors de la compilation d'un programme. Par exemple, la commande $LFS_TGT-gcc -v example.c (sans $LFS_TGT- si vous revenez plus tard) affichera des informations détaillées sur les phases de préprocesseur, de compilation et d’assemblage, ainsi que les chemins de recherche de gcc pour les en-têtes inclus et leur ordre.

L’installation suivante concerne les en-têtes nettoyés de l’API de Linux. Ils permettent à la bibliothèque standard C (glibc) d’interagir avec les fonctionnalités fournies par le noyau Linux.

Glibc est le paquet suivant à installer. Le compilateur, les outils binaires et les en-têtes du noyau sont les éléments les plus importants à prendre en considération pour construire glibc. Le compilateur et les outils binaires ne posent généralement pas de problème car glibc utilise toujours ceux liés à l’option --host passée à son script configure. Par exemple, dans notre cas, le compilateur sera $LFS_TGT-gcc et l'outil readelf sera $LFS_TGT-readelf. Les en-têtes du noyau s’avèrent un peu plus compliqués. Ne prenez donc pas de risque et utilisez l'option de configure disponible pour exécuter la bonne sélection. Après l'exécution de configure, vérifiez le contenu et les détails importants du fichier config.make dans le répertoire build. Ces éléments mettent en avant un aspect important du paquet glibc : il est auto-suffisant en termes de construction et ne repose généralement pas sur la chaîne de compilation par défaut.

Comme indiqué précédemment, la bibliothèque standard C++ est ensuite compilée, suivie dans le Chapitre 6 par les autres programmes qui nécessitent une compilation croisée en raison des dépendances circulaires qu’ils cassent lors de la construction. L’installation forcée de tous ces paquets sur le système de fichiers LFS nécessite la variable DESTDIR.

À la fin du Chapitre 6, le compilateur LFS natif est installé. binutils-pass2 est construit en premier, avec le même répertoire d’installation DESTDIR que les autres programmes, puis la deuxième passe de gcc est construite sans les bibliothèques inutiles. En raison d’un comportement illogique dans le script configure de gcc, CC_FOR_TARGET devient cc lorsque l'hôte est identique à la cible, mais différent du système de construction. C'est pourquoi le paramètre CC_FOR_TARGET=$LFS_TGT-gcc est déclaré de façon explicite dans les options de configure.

À l’entrée de l'environnement chroot dans le Chapitre 7, les programmes nécessaires au bon fonctionnement de la chaîne de compilation sont installés de manière temporaire. À partir de là, la chaîne de construction de base est auto-suffisante et auto-hébergée. Dans le Chapitre 8, vous construirez, testerez et installerez les versions finales de tous les paquets nécessaires au bon fonctionnement du système complet.