Suivant: , Précédent: , Monter: Didacticiel d'empaquetage   [Table des matières][Index]


2.1.3 Exemple avancé

L’exemple « Hello World » précédent est le plus simple possible. Les paquets peuvent devenir plus complexes que cela et Guix peut gérer des scénarios plus avancés. Voyons un autre paquet plus sophistiqué (légèrement modifié à partir des sources) :

(define-module (gnu packages version-control)
  #:use-module ((guix licenses) #:prefix license:)
  #:use-module (guix utils)
  #:use-module (guix packages)
  #:use-module (guix git-download)
  #:use-module (guix build-system cmake)
  #:use-module (gnu packages ssh)
  #:use-module (gnu packages web)
  #:use-module (gnu packages pkg-config)
  #:use-module (gnu packages python)
  #:use-module (gnu packages compression)
  #:use-module (gnu packages tls))

(define-public my-libgit2
  (let ((commit "e98d0a37c93574d2c6107bf7f31140b548c6a7bf")
        (revision "1"))
    (package
      (name "my-libgit2")
      (version (git-version "0.26.6" revision commit))
      (source (origin
                (method git-fetch)
                (uri (git-reference
                      (url "https://github.com/libgit2/libgit2/")
                      (commit commit)))
                (file-name (git-file-name name version))
                (sha256
                 (base32
                  "17pjvprmdrx4h6bb1hhc98w9qi6ki7yl57f090n9kbhswxqfs7s3"))
                (patches (search-patches "libgit2-mtime-0.patch"))
                (modules '((guix build utils)))
                ;; Suppression des logiciels embarqués.
                (snippet '(delete-file-recursively "deps"))))
      (build-system cmake-build-system)
      (outputs '("out" "debug"))
      (arguments
       `(#:tests? #true                         ; Lancer la suite de tests (c'est la valeur par défaut)
         #:configure-flags '("-DUSE_SHA1DC=ON") ; détection de collision SHA-1
         #:phases
         (modify-phases %standard-phases
           (add-after 'unpack 'fix-hardcoded-paths
             (lambda _
               (substitute* "tests/repo/init.c"
                 (("#!/bin/sh") (string-append "#!" (which "sh"))))
               (substitute* "tests/clar/fs.h"
                 (("/bin/cp") (which "cp"))
                 (("/bin/rm") (which "rm")))))
           ;; Lancer les tests avec plus de verbosité.
           (replace 'check
             (lambda _ (invoke "./libgit2_clar" "-v" "-Q")))
           (add-after 'unpack 'make-files-writable-for-tests
             (lambda _ (for-each make-file-writable (find-files "." ".*")))))))
      (inputs
       (list libssh2 http-parser python-wrapper))
      (native-inputs
       (list pkg-config))
      (propagated-inputs
       ;; Ces deux bibliothèques sont dans « Requires.private », dans libgit2.pc.
       (list openssl zlib))
      (home-page "https://libgit2.github.com/")
      (synopsis "Library providing Git core methods")
      (description
       "Libgit2 is a portable, pure C implementation of the Git core methods
provided as a re-entrant linkable library with a solid API, allowing you to
write native speed custom Git applications in any language with bindings.")
      ;; GPLv2 with linking exception
      (license license:gpl2))))

(Dans les cas où vous voulez seulement changer quelques champs d’une définition de paquets, vous devriez utiliser l’héritage au lieu de tout copier-coller. Voir plus bas.)

Parlons maintenant de ces champs en détail.

2.1.3.1 La méthode git-fetch

Contrairement à la méthode url-fetch, git-fetch a besoin d’un git-reference qui prend un dépôt Git et un commit. Le commit peut être n’importe quelle référence Git comme des tags, donc si la version a un tag associé, vous pouvez l’utiliser directement. Parfois le tag est précédé de v, auquel cas vous pouvez utiliser (commit (string-append "v" version)).

Pour vous assurer que le code source du dépôt Git est stocké dans un répertoire avec un nom descriptif, utilisez (file-name (git-file-name name version)).

Vous pouvez utiliser la procédure git-version pour calculer la version quand vous empaquetez des programmes pour un commit spécifique, en suivant le guide de contribution (voir Numéros de version dans le manuel de référence de GNU Guix).

Comment obtenir le hash sha256, vous demandez-vous ? En invoquant guix hash sur un clone du commit voulu, de cette manière :

git clone https://github.com/libgit2/libgit2/
cd libgit2
git checkout v0.26.6
guix hash -rx .

guix hash -rx calcul un SHA256 sur le répertoire entier, en excluant le sous-répertoire .git (voir Invoquer guix hash dans le manuel de référence de GNU Guix).

Dans le futur, guix download sera sans doute capable de faire cela pour vous, comme il le fait pour les téléchargements directs.

2.1.3.2 Les bouts de code

Les bouts de code (snippet) sont des fragments quotés (c.-à-d. non évalués) de code Scheme utilisés pour modifier les sources. C’est une alternative aux fichiers .patch traditionnels, plus proche de l’esprit de Guix. À cause de la quote, le code n’est évalué que lorsqu’il est passé au démon Guix pour la construction. Il peut y avoir autant de bout de code que nécessaire.

Les bouts de code on parfois besoin de modules Guile supplémentaires qui peuvent être importés dans le champ modules.

2.1.3.3 Entrées

Il y a trois types d’entrées. En résumé :

native-inputs

Requis pour construire mais pas à l’exécution – installer un paquet avec un substitut n’installera pas ces entrées.

inputs

Installées dans le dépôt mais pas dans le profil, et présentes à la construction.

propagated-inputs

Installées dans le dépôt et dans le profil, et présentes à la construction.

Voir Référence de package dans le manuel de référence de GNU Guix pour plus de détails.

La différence entre les différents types d’entrées est importante : si une dépendance peut être utilisée comme entrée plutôt que comme entrée propagée, il faut faire ça, sinon elle « polluera » le profil utilisateur sans raison.

Par exemple, si vous installez un programme graphique qui dépend d’un outil en ligne de commande, vous êtes probablement intéressé uniquement par la partie graphique, donc inutile de forcer l’outil en ligne de commande à être présent dans le profil utilisateur. Les dépendances sont gérés par les paquets, pas par les utilisateurs et utilisatrices. Les entrées permettent de gérer les dépendances sans ennuyer les utilisateurs et utilisatrices en ajoutant des fichiers exécutables (ou bibliothèque) inutiles dans leur profil.

Pareil pour native-inputs : une fois le programme installé, les dépendances à la construction peuvent être supprimées sans problème par le ramasse-miettes. Lorsqu’un substitut est disponible, seuls les entrées et les entrées propagées sont récupérées : les entrées natives ne sont pas requises pour installer un paquet à partir d’un substitut.

Remarque : Vous trouverez ici et là des extraits où les entrées des paquets sont écrites assez différemment, comme ceci :

;; « L'ancien style » pour les entrées.
(inputs
 `(("libssh2" ,libssh2)
   ("http-parser" ,http-parser)
   ("python" ,python-wrapper)))

C’est « l’ancien style », où chaque entrée est une liste que donne une étiquette explicite (une chaine). C’est une méthode prise en charge mais nous vous recommandons plutôt d’utiliser le style présenté plus haut. Voir Référence de package dans le manuel de référence de GNU Guix, pour plus d’informations.

2.1.3.4 Sorties

De la même manière qu’un paquet peut avoir plusieurs entrées, il peut aussi avoir plusieurs sorties.

Chaque sortie correspond à un répertoire différent dans le dépôt.

Vous pouvez choisir quelle sortie installer ; c’est utile pour préserver l’espace disque et éviter de polluer le profil utilisateur avec des exécutables et des bibliothèques inutiles.

La séparation des sorties est facultative. Lorsque le champ outputs n’est pas spécifié, l’unique sortie par défaut (le paquet complet donc) est "out".

Les sorties séparées sont en général debug et doc.

Vous devriez séparer les sorties seulement si vous pouvez montrer que c’est utile : si la taille de la sortie est importante (vous pouvez comparer avec guix size) ou si le paquet est modulaire.

2.1.3.5 Arguments du système de construction

Le champ arguments est une liste de mot-clés et de valeurs utilisés pour configurer le processus de construction.

L’argument le plus simple est #:tests? et on l’utilise pour désactiver la suite de tests pendant la construction du paquet. C’est surtout utile si le paquet n’a pas de suite de tests. Nous vous recommandons fortement de laisser tourner la suite de tests s’il y en a une.

Un autre argument courant est #:make-flags, qui spécifie une liste de drapeaux à ajouter en lançant make, comme ce que vous feriez sur la ligne de commande. Par exemple, les drapeaux suivants

#:make-flags (list (string-append "prefix=" (assoc-ref %outputs "out"))
                   "CC=gcc")

se traduisent en

$ make CC=gcc prefix=/gnu/store/...-<out>

Cela indique que le compilateur C sera gcc et la variable prefix (le répertoire d’installation pour Make) sera (assoc-ref %outputs "out"), qui est une variable globale côté construction qui pointe vers le répertoire de destination dans le dépôt (quelque chose comme /gnu/store/…-my-libgit2-20180408).

De manière identique, vous pouvez indiquer les drapeaux de configuration :

#:configure-flags '("-DUSE_SHA1DC=ON")

La variable %build-inputs est aussi générée dans cette portée. C’est une liste d’association qui fait correspondre les noms des entrées à leur répertoire dans le dépôt.

Le mot-clé phases liste la séquence d’étapes du système de construction. Les phases usuelles sont unpack, configure, build, install et check. Pour en savoir plus, vous devez trouver la bonne définition du système de construction dans ‘$GUIX_CHECKOUT/guix/build/gnu-build-system.scm’ :

(define %standard-phases
  ;; Standard build phases, as a list of symbol/procedure pairs.
  (let-syntax ((phases (syntax-rules ()
                         ((_ p ...) `((p . ,p) ...)))))
    (phases set-SOURCE-DATE-EPOCH set-paths install-locale unpack
            bootstrap
            patch-usr-bin-file
            patch-source-shebangs configure patch-generated-file-shebangs
            build check install
            patch-shebangs strip
            validate-runpath
            validate-documentation-location
            delete-info-dir-file
            patch-dot-desktop-files
            install-license-files
            reset-gzip-timestamps
            compress-documentation)))

Ou depuis la REPL :

(add-to-load-path "/path/to/guix/checkout")
,use (guix build gnu-build-system)
(map first %standard-phases)
 (set-SOURCE-DATE-EPOCH set-paths install-locale unpack bootstrap patch-usr-bin-file patch-source-shebangs configure patch-generated-file-shebangs build check install patch-shebangs strip validate-runpath validate-documentation-location delete-info-dir-file patch-dot-desktop-files install-license-files reset-gzip-timestamps compress-documentation)

Si vous voulez en apprendre plus sur ce qui arrive pendant ces phases, consultez les procédures associées.

Par exemple, au moment d’écrire ces lignes, la définition de unpack dans le système de construction de GNU est :

(define* (unpack #:key source #:allow-other-keys)
  "Unpack SOURCE in the working directory, and change directory within the
source.  When SOURCE is a directory, copy it in a sub-directory of the current
working directory."
  (if (file-is-directory? source)
      (begin
        (mkdir "source")
        (chdir "source")

        ;; Preserve timestamps (set to the Epoch) on the copied tree so that
        ;; things work deterministically.
        (copy-recursively source "."
                          #:keep-mtime? #true))
      (begin
        (if (string-suffix? ".zip" source)
            (invoke "unzip" source)
            (invoke "tar" "xvf" source))
        (chdir (first-subdirectory "."))))
  #true)

Remarquez l’appel à chdir : il change de répertoire courant vers la source qui vient d’être décompressée. Ainsi toutes les phases suivantes utiliseront le répertoire des sources comme répertoire de travail, ce qui explique qu’on peut travailler directement sur les fichiers sources. Du moins, tant qu’une phase suivante ne change pas le répertoire de travail.

Nous modifions la liste des %standard-phases du système de construction avec la macro modify-phases qui indique la liste des modifications, sous cette formes :

La procédure prend en charge les arguments inputs et outputs sous forme de mot-clés. Les entrées (natives, propagées et simples) et répertoires de sortie sont référencés par leur nom dans ces variables. Ainsi (assoc-ref outputs "out") est le répertoire du dépôt de la sortie principale du paquet. Une procédure de phase ressemble à cela :

(lambda* (#:key inputs outputs #:allow-other-keys)
  (let ((bash-directory (assoc-ref inputs "bash"))
        (output-directory (assoc-ref outputs "out"))
        (doc-directory (assoc-ref outputs "doc")))
    ;; ...
    #true))

La procédure doit renvoyer #true si elle réussit. S’appuyer sur la valeur de retour de la dernière expression n’est pas très solide parce qu’il n’y a pas de garantie qu’elle sera #true. Donc le #true à la fin permet de s’assurer que la bonne valeur est renvoyée si la phase réussit.

2.1.3.6 Échelonnage de code

Si vous avez été attentif, vous aurez remarqué la quasi-quote et la virgule dans le champ argument. En effet, le code de construction dans la déclaration du paquet ne doit pas être évalué côté client, mais seulement après avoir été passé au démon Guix. Ce mécanisme de passage de code entre deux processus s’appelle l’échelonnage de code.

2.1.3.7 Fonctions utilitaires

Lorsque vous modifiez les phases, vous aurez souvent besoin d’écrire du code qui ressemble aux invocation équivalentes (make, mkdir, cp, etc) couramment utilisées durant une installatio plus standard dans le monde Unix.

Certaines comme chmod sont natives dans Guile. Voir Guile reference manual pour une liste complète.

Guix fournit des fonctions utilitaires supplémentaires qui sont particulièrement utiles pour la gestion des paquets.

Certaines de ces fonctions se trouvent dans ‘$GUIX_CHECKOUT/guix/guix/build/utils.scm’. La plupart copient le comportement des commandes systèmes Unix traditionnelles :

which

Fonctionne comme la commande système ‘which’.

find-files

Fonctionne un peu comme la commande ‘find’.

mkdir-p

Fonctionne comme ‘mkdir -p’, qui crée tous les parents si besoin.

install-file

Fonctionne comme ‘install’ pour installer un fichier vers un répertoire (éventuellement non existant). Guile a copy-file qui fonctionne comme ‘cp’.

copy-recursively

Fonctionne comme ‘cp -r’.

delete-file-recursively

Fonctionne comme ‘rm -rf’.

invoke

Lance un exécutable. Vous devriez utiliser cela à la place de system*.

with-directory-excursion

Lance le corps dans un répertoire de travail différent, puis revient au répertoire de travail précédent.

substitute*

Une fonction similaire à sed.

Voir Utilitaires de construction dans le manuel de référence de GNU Guix, pour plus d’informations sur ces utilitaires.

2.1.3.8 Préfixe de module

La licence dans notre dernier exemple a besoin d’un préfixe à cause de la manière dont le module licenses a été importé dans le paquet, avec #:use-module ((guix licenses) #:prefix license:). Le mécanisme d’import de module de Guile (voir Using Guile Modules dans Guile reference manual) permet de contrôler complètement l’espace de nom. Cela évite les conflits entre, disons, la variable ‘zlib’ de ‘licenses.scm’ (un licence) et la variable ‘zlib’ de ‘compression.scm’ (un paquet).


Suivant: , Précédent: , Monter: Didacticiel d'empaquetage   [Table des matières][Index]