Suivant: , Précédent: , Monter: Interface de programmation   [Table des matières][Index]


8.12 G-Expressions

On a donc des « dérivations » qui représentent une séquence d’actions de construction à effectuer pour produire un élément du dépôt (voir Dérivations). Ces actions de construction sont effectuées lorsqu’on demande au démon de construire effectivement les dérivations ; elles sont lancées par le démon dans un conteneur (voir Invoquer guix-daemon).

Ça ne devrait pas vous surprendre, mais nous aimons écrire ces actions de construction en Scheme. Lorsqu’on fait ça, on fini avec deux strates de code Scheme24 : le « code hôte » — le code qui définit les paquets, parle au démon, etc. — et le « code côté construction » — le code qui effectue effectivement les actions de construction, comme créer des répertoires, invoquer make, etc. (voir Phases de construction).

Pour décrire une dérivation et ses actions de construction, on a typiquement besoin d’intégrer le code de construction dans le code hôte. Ça revient à manipuler le code de construction comme de la donnée, et l’homoiconicité de Scheme — le code a une représentation directe en tant que donnée — est très utile pour cela. Mais on a besoin de plus que le mécanisme de quasiquote en Scheme pour construire des expressions de construction.

Le module (guix gexp) implémente les G-expressions, une forme de S-expression adaptée aux expressions de construction. Les G-expression, ou gexps, consistent en gros en trois formes syntaxiques : gexp, ungexp et ungexp-splicing (ou plus simplement : #~, #$ et #$@), qui sont comparable à quasiquote, unquote et unquote-splicing respectivement (voir quasiquote dans GNU Guile Reference Manual). Cependant il y a des différences majeures :

Ce mécanisme n’est pas limité aux paquets et aux objets de dérivation : Des compilateurs capables d’ « abaisser » d’autres objets de haut niveau vers des dérivations ou des fichiers dans le dépôt peuvent être définis, de sorte que ces objets peuvent également être insérés dans des gexps. Par exemple, un type utile d’objets de haut niveau pouvant être insérés dans un gexp est celui des « objets de type fichier », qui permettent d’ajouter facilement des fichiers au dépôt et d’y faire référence dans des dérivations et autres (voir local-file et plain-file ci-dessous).

Pour illustrer cette idée, voici un exemple de gexp :

(define build-exp
  #~(begin
      (mkdir #$output)
      (chdir #$output)
      (symlink (string-append #$coreutils "/bin/ls")
               "list-files")))

Cette gexp peut être passée à gexp->derivation ; on obtient une dérivation qui construit un répertoire contenant exactement un lien symbolique à /gnu/store/…-coreutils-8.22/bin/ls :

(gexp->derivation "the-thing" build-exp)

Comme on pourrait s’y attendre, la chaîne "/gnu/store/…-coreutils-8.22" est substituée à la place de la référence au paquet coreutils dans le code de construction final, et coreutils est automatiquement devenu une entrée de la dérivation. De même, #$output (équivalent à (ungexp output)) est remplacé par une chaîne de caractères contenant le nom du répertoire de la sortie de la dérivation.

Dans le contexte d’une compilation croisée, il est utile de distinguer entre des références à la construction native d’un paquet — qui peut être lancé par l’hôte — et des références à la construction croisée d’un paquet. Pour cela, #+ joue le même rôle que #$, mais référence une construction native d’un paquet :

(gexp->derivation "vi"
   #~(begin
       (mkdir #$output)
       (mkdir (string-append #$output "/bin"))
       (system* (string-append #+coreutils "/bin/ln")
                "-s"
                (string-append #$emacs "/bin/emacs")
                (string-append #$output "/bin/vi")))
   #:target "aarch64-linux-gnu")

Dans l’exemple ci-dessus, la construction native de coreutils est utilisée, pour que ln puisse effectivement être lancé sur l’hôte ; mais ensuite la construction croisée d’emacs est utilisée.

Une autre fonctionnalité, ce sont les modules importés : parfois vous voudriez pouvoir utiliser certains modules Guile de « l’environnement hôte » dans la gexp, donc ces modules devraient être importés dans « l’environnement de construction ». La forme with-imported-modules vous permet d’exprimer ça :

(let ((build (with-imported-modules '((guix build utils))
               #~(begin
                   (use-modules (guix build utils))
                   (mkdir-p (string-append #$output "/bin"))))))
  (gexp->derivation "empty-dir"
                    #~(begin
                        #$build
                        (display "success!\n")
                        #t)))

Dans cet exemple, le module (guix build utils) est automatiquement récupéré dans l’environnement de construction isolé de notre gexp, pour que (use-modules (guix build utils)) fonctionne comme on s’y attendrait.

Typiquement, vous voudriez que la closure complète du module soit importé — c.-à-d. le module lui-même et tous les modules dont il dépend — plutôt que seulement le module ; sinon, une tentative de chargement du module échouera à cause des modules dépendants manquants. La procédure source-module-closure calcule la closure d’un module en cherchant dans ses en-têtes sources, ce qui est pratique dans ce cas :

(use-modules (guix modules))   ;pour 'source-module-closure'

(with-imported-modules (source-module-closure
                         '((guix build utils)
                           (gnu build image)))
  (gexp->derivation "something-with-vms"
                    #~(begin
                        (use-modules (guix build utils)
                                     (gnu build image))
                        )))

Dans la même idée, parfois vous pouvez souhaiter importer non seulement des modules en Scheme pur, mais aussi des « extensions » comme des liaisons Guile de bibliothèques C ou d’autres paquet « complets ». Disons que vous voulez utiliser le paquet guile-json du côté de la construction, voici comme procéder :

(use-modules (gnu packages guile))  ;pour 'guile-json'

(with-extensions (list guile-json)
  (gexp->derivation "something-with-json"
                    #~(begin
                        (use-modules (json))
                        )))

La forme syntaxique pour construire des gexps est résumée ci-dessous.

Macro :#~exp
Macro :(gexp exp)

Renvoie une G-expression contenant exp. exp peut contenir une ou plusieurs de ces formes :

#$obj
(ungexp obj)

Introduit une référence à obj. obj peut être d’un des types supportés, par exemple un paquet ou une dérivation, auquel cas la forme ungexp est remplacée par le nom de fichier de sa sortie — p. ex. "/gnu/store/…-coreutils-8.22.

Si boj est une liste, elle est traversée et les références aux objets supportés sont substitués de manière similaire.

Si obj est une autre gexp, son contenu est inséré et ses dépendances sont ajoutées à celle de la gexp qui l’entoure.

Si obj est un autre type d’objet, il est inséré tel quel.

#$obj:output
(ungexp obj output)

Cette forme est similaire à la précédente, mais se réfère explicitement à la sortie output de l’objet obj — c’est utile lorsque obj produit plusieurs sorties (voir Des paquets avec plusieurs résultats).

Sometimes a gexp unconditionally refers to the "out" output, but the user of that gexp would still like to insert a reference to another output. The gexp-input procedure aims to address that. Voir gexp-input.

#+obj
#+obj:output
(ungexp-native obj)
(ungexp-native obj output)

Comme ungexp, mais produit une référence à la construction native de obj lorsqu’elle est utilisée dans une compilation croisée.

#$output[:output]
(ungexp output [output])

Insère une référence à la sortie output de la dérivation, ou à la sortie principale lorsque output est omis.

Cela ne fait du sens que pour les gexps passées à gexp->derivation.

#$@lst
(ungexp-splicing lst)

Comme au dessus, mais recolle (splice) le contenu de lst dans la liste qui la contient.

#+@lst
(ungexp-native-splicing lst)

Comme au dessus, mais se réfère à la construction native des objets listés dans lst.

Les G-expressions créées par gexp ou #~ sont des objets d’exécution du type gexp? (voir ci-dessous).

Macro :with-imported-modules modules body…

Marque les gexps définies dans body… comme requérant modules dans leur environnement d’exécution.

Chaque élément dans module peut être le nom d’un module, comme (guix build utils) ou le nom d’un module suivi d’une flèche, suivie d’un objet simili-fichier :

`((guix build utils)
  (guix gcrypt)
  ((guix config) => ,(scheme-file "config.scm"
                                  #~(define-module ))))

Dans l’exemple au dessus, les deux premiers modules sont récupérés dans le chemin de recherche, et le dernier est créé à partir d’un objet simili-fichier.

Cette forme a une portée lexicale : elle a un effet sur les gexp directement définies dans body…, mais pas sur celles définies dans des procédures appelées par body….

Macro :with-extensions extensions body…

Marque les gexps définies dans body… comme requérant extensions dans leur environnement de construction et d’exécution. extensions est typiquement une liste d’objets paquets comme définis dans le module (gnu packages guile).

Concrètement, les paquets listés dans extensions sont ajoutés au chemin de chargement lors de la compilation des modules importés dans body… ; ils sont aussi ajoutés au chemin de chargement de la gexp renvoyée par body….

Procédure :gexp? obj

Renvoie #t si obj est une G-expression.

Les G-expressions sont conçues pour être écrites sur le disque, soit en tant que code pour construire une dérivation, soit en tant que fichier normal dans le dépôt. Les procédures monadiques suivantes vous permettent de faire cela (voir La monade du dépôt, pour plus d’information sur les monads).

Procédure monadique :gexp->derivation name exp [#:system (%current-system)] [#:target #f] [#:graft ? #t] [#:hash #f] [#:hash-algo #f] [#:recursive ? #f] [#:env-vars '()] [#:modules '()] [#:module-path %load-path] [#:effective-version "2. 2"] [#:references-graphs #f] [#:allowed-references #f] [#:disallowed-references #f] [#:leaked-env-vars #f] [#:script-name (string-append name "-builder")] [#:deprecation-warnings #f] [#:local-build ? #f] [#:substituable ? #t] [#:properties '()] [#:guile-for-build #f]

Renvoie une dérivation name qui exécute exp (un gexp) avec guile-for-build (une dérivation) sur system ; exp est stocké dans un fichier appelé script-name. Lorsque target est vrai, il est utilisé comme le triplet cible de compilation croisée pour les paquets auxquels exp fait référence.

modules est devenu obsolète en faveur de with-imported-modules. Sa signification est de rendre modules disponibles dans le contexte d’évaluation de exp ; modules est une liste de noms de modules Guile qui sont cherchés dans module-path pour les copier dans le dépôt, les compiler et les rendre disponibles dans le chemin de chargement pendant l’exécution de exp — p. ex. ((guix build utils) (guix build gnu-build-system)).

effective-version détermine la chaîne à utiliser lors d’ajout d’extensions de exp (voir with-extensions) au chemin de recherche — p. ex. "2.2".

graft? détermine si les paquets référencés par exp devraient être greffés si possible.

Lorsque references-graphs est vrai, il doit s’agir d’une liste de tuples de la forme suivante :

(file-name obj)
(file-name obj output)
(file-name gexp-input)
(file-name store-item)

La partie droite des éléments de references-graphs est automatiquement transformée en une entrée du processus de construction exp. Dans l’environnement de construction, chaque file-name contient le graphe des références de l’élément correspondant, dans un format texte simple.

allowed-references doit soit être #f, soit une liste de noms de sorties ou de paquets. Dans ce dernier cas, la liste dénote les éléments du dépôt auxquels le résultat a le droit de faire référence. Toute référence à un autre élément du dépôt conduira à une erreur à la construction. Comme pour disallowed-references, qui peut lister des éléments qui ne doivent pas être référencés par les sorties.

deprecation-warnings détermine s’il faut afficher les avertissement d’obsolescence à la compilation de modules. Il peut valoir #f, t ou 'detailed.

Les autres arguments sont les mêmes que pour derivation (voir Dérivations).

Les procédures local-file, plain-file, computed-file, program-file et scheme-file ci-dessous renvoient des objets simili-fichiers. C’est-à-dire, lorsqu’ils sont unquotés dans une G-expression, ces objets donnent un fichier dans le dépôt. Considérez cette G-expression :

#~(system* #$(file-append glibc "/sbin/nscd") "-f"
           #$(local-file "/tmp/my-nscd.conf"))

Ici, l’effet est « d’internaliser » /tmp/my-nscd.conf en le copiant dans le dépôt. Une fois étendu, par exemple via gexp->derivation, la G-expression se réfère à cette copie dans /gnu/store ; ainsi, modifier ou supprimer le fichier dans /tmp n’a aucun effet sur ce que fait la G-expression. plain-file peut être utilisé de la même manière ; elle est seulement différente par le fait que le contenu du fichier est passé directement par une chaîne de caractères.

Procédure :local-file file [name] [#:recursive? #f] [#:select? (const #t)]

Renvoie un objet représentant le fichier local file à ajouter au magasin ; cet objet peut être utilisé dans un gexp. Si file est une chaîne littérale désignant un nom de fichier relatif, il est recherché par rapport au fichier source où il apparaît ; si file n’est pas une chaîne littérale, il est recherché par rapport au répertoire de travail courant au moment de l’exécution. file sera ajouté au dépôt sous name–par défaut le nom de base de file.

Lorsque recursive? est vraie, le contenu de file est ajouté récursivement ; si file désigne un fichier simple et que recursive? est vrai, son contenu est ajouté et ses bits de permissions sont préservés.

Lorsque recursive? est vraie, appelle (select? file stat) pour chaque répertoire où file est le nom de fichier absolu de l’entrée et stat est le résultat de lstat ; à l’exception des entrées pour lesquelles select? ne renvoie pas vrai.

C’est la version déclarative de la procédure monadique interned-file (voir interned-file).

Procédure :plain-file name content

Renvoie un objet représentant un fichier texte nommé name avec pour contenu content (une chaîne de caractères ou un vecteur d’octets) à ajouter un dépôt.

C’est la version déclarative de text-file.

Procédure :computed-file name gexp [#:local-build? #t] [#:options '()]

Renvoie un objet représentant l’élément du dépôt name, un fichier ou un répertoire calculé par gexp. Lorsque local-build? est vrai (par défaut), la dérivation est construite localement. options est une liste d’arguments supplémentaires à passer à gexp->derivation.

C’est la version déclarative de gexp->derivation.

Procédure monadique :gexp->script name exp [#:guile (default-guile)] [#:module-path %load-path] [#:system (%current-system)] [#:target #f]

Renvoie un script exécutable name qui exécute exp en utilisant guile, avec les modules importés de exp dans son chemin de recherche. Recherchez les modules de exp dans module-path.

L’exemple ci-dessous construit un script qui invoque simplement la commande ls :

(use-modules (guix gexp) (gnu packages base))

(gexp->script "list-files"
              #~(execl #$(file-append coreutils "/bin/ls")
                       "ls"))

Lorsqu’elle est « lancée » à travers le dépôt (voir run-with-store), on obtient une dérivation qui produit une fichier exécutable /gnu/store/…-list-files qui ressemble à :

#!/gnu/store/…-guile-2.0.11/bin/guile -ds
!#
(execl "/gnu/store/…-coreutils-8.22"/bin/ls" "ls")
Procédure :program-file name exp [#:guile #f] [#:module-path %load-path]

Renvoie un objet représentant un élément du dépôt name qui lance gexp. guile est le paquet Guile à utiliser pour exécuter le script. Les modules importés par gexp sont recherchés dans module-path.

C’est la version déclarative de gexp->script.

Procédure monadique :gexp->file name exp [#:set-load-path? #t] [#:module-path %load-path] [#:splice? #f] [#:guile (default-guile)]

Renvoie une dérivation qui construit un fichier name contenant exp. Lorsque splice? est vrai, exp est considéré comme une liste d’expressions qui seront splicée dans le fichier qui en résulte.

Lorsque set-load-path? est vrai, émet du code dans le fichier de résultat pour initialiser %load-path et %load-compiled-path pour prendre en compte les modules importés de exp. Les modules de exp sont trouvés dans module-path.

Le fichier qui en résulte retient les références à toutes les dépendances de exp ou un sous-ensemble.

Procédure :scheme-file name exp [#:splice? #f] [#:set-load-path? #t]

Renvoie un objet représentant le fichier Scheme name qui contient exp.

C’est la version déclarative de gexp->file.

Procédure monadique :text-file* name text

Renvoie une valeur monadique qui construit un ficher texte contenant text. text peut lister, en plus de chaînes de caractères, des objet de n’importe quel type qui peut être utilisé dans une gexp : des paquets, des dérivations, des fichiers objet locaux, etc. Le fichier du dépôt qui en résulte en retient toutes les références.

Cette variante devrait être préférée à text-file lorsque vous souhaitez créer des fichiers qui référencent le dépôt. Cela est le cas typiquement lorsque vous construisez un fichier de configuration qui contient des noms de fichiers du dépôt, comme ceci :

(define (profile.sh)
  ;; Renvoie le nom d'un script shell dans le dépôt qui initialise
  ;; la variable d'environnement « PATH ».
  (text-file* "profile.sh"
              "export PATH=" coreutils "/bin:"
              grep "/bin:" sed "/bin\n"))

Dans cet exemple, le fichier /gnu/store/…-profile.sh qui en résulte référence coreutils, grep et sed, ce qui les empêche d’être glanés tant que le script est accessible.

Procédure :mixed-text-file name text …

Renvoie un objet représentant le fichier du dépôt name contenant text. text est une séquence de chaînes de caractères et de fichiers simili-objets, comme dans :

(mixed-text-file "profile"
                 "export PATH=" coreutils "/bin:" grep "/bin")

C’est la version déclarative de text-file*.

Procédure :file-union name files

Renvoie un <computed-file> qui construit un répertoire qui contient tous les fichiers de files. Chaque élément de files doit être une paire où le premier élément est le nom de fichier à utiliser dans le nouveau répertoire et le second élément est une gexp dénotant le fichier cible. Voici un exemple :

(file-union "etc"
            `(("hosts" ,(plain-file "hosts"
                                    "127.0.0.1 localhost"))
              ("bashrc" ,(plain-file "bashrc"
                                     "alias ls='ls --color=auto'"))))

Cela crée un répertoire etc contenant ces deux fichiers.

Procédure :directory-union name things

Renvoie un répertoire qui est l’union de things, où things est une liste d’objets simili-fichiers qui dénotent des répertoires. Par exemple :

(directory-union "guile+emacs" (list guile emacs))

crée un répertoire qui est l’union des paquets guile et emacs.

Procédure :file-append obj suffix …

Renvoie un objet simili-fichier qui correspond à la concaténation de obj et suffixobj est un objet abaissable et chaque suffix est une chaîne de caractères.

Par exemple, considérez cette gexp :

(gexp->script "run-uname"
              #~(system* #$(file-append coreutils
                                        "/bin/uname")))

On peut obtenir le même effet avec :

(gexp->script "run-uname"
              #~(system* (string-append #$coreutils
                                        "/bin/uname")))

Il y a une différence cependant : dans le cas file-append, le script qui en résulte contient le nom de fichier absolu comme une chaîne de caractère alors que dans le deuxième cas, le script contient une expression (string-append …) pour construire le nom de fichier à l’exécution.

Macro :let-system system body…
Macro :let-system (system target) body…

Lier system au système actuellement visé—par exemple, "x86_64-linux"—dans body.

Dans le second cas, lier également target à la cible de compilation croisée actuelle–un triplet GNU tel que "arm-linux-gnueabihf"—ou #f si nous ne faisons pas de compilation croisée.

let-system est utile occasionellement dans le cas où l’objet raccordé au gexp dépend de la cible sur le système cible, comme dans cet exemple :

#~(system*
   #+(let-system system
       (cond ((string-prefix? "armhf-" system)
              (file-append qemu "/bin/qemu-system-arm"))
             ((string-prefix? "x86_64-" system)
              (file-append qemu "/bin/qemu-system-x86_64"))
             (else
              (error "dunno!"))))
   "-net" "user" #$image)
Macro :with-parameters ((parameter value) …) exp

Cette macro est similaire à la forme parameterize pour les paramètres liés dynamiquement (voir Parameters dans GNU Guile Reference Manual). La principale différence est qu’il prend effet lorsque l’objet de type fichier renvoyé par exp est abaissé à un élément de dérivation ou de stockage.

Une utilisation typique de with-parameters consiste à forcer le système en vigueur pour un objet donné :

(with-parameters ((%current-system "i686-linux"))
  coreutils)

L’exemple ci-dessus renvoie un objet qui correspond à la version i686 de Coreutils, quelle que soit la valeur actuelle de %current-système.

Procedure :gexp-input obj [output] [#:native? #f]

Return a gexp input record for the given output of file-like object obj, with #:native? determining whether this is a native reference (as with ungexp-native) or not.

This procedure is helpful when you want to pass a reference to a specific output of an object to some procedure that may not know about that output. For example, assume you have this procedure, which takes one file-like object:

(define (make-symlink target)
  (computed-file "the-symlink"
                 #~(symlink #$target #$output)))

Here make-symlink can only ever refer to the default output of target—the "out" output (voir Des paquets avec plusieurs résultats). To have it refer to, say, the "lib" output of the hwloc package, you can call it like so:

(make-symlink (gexp-input hwloc "lib"))

You can also compose it like any other file-like object:

(make-symlink
  (file-append (gexp-input hwloc "lib") "/lib/libhwloc.so"))

Bien sûr, en plus de gexps inclues dans le code « hôte », certains modules contiennent des outils de construction. Pour savoir facilement qu’ils sont à utiliser dans la strate de construction, ces modules sont gardés dans l’espace de nom (guix build …).

En interne, les objets de haut-niveau sont abaissés, avec leur compilateur, soit en des dérivations, soit en des objets du dépôt. Par exemple, abaisser un paquet crée une dérivation, et abaisser un plain-file crée un élément du dépôt. Cela est effectué par la procédure monadique lower-object.

Procédure monadique :lower-object obj [system] [#:target #f]

Renvoie comme valeur dans %store-monad la dérivation ou l’élément de dépôt correspondant à obj pour system, en effectuant une compilation croisée pour target si target est vrai. obj doit être un objet auquel est associé un compilateur gexp, tel qu’un <package>.

Procédure :gexp->approximate-sexp gexp

Il peut parfois être utile de convertir une G-expression en S-expression. Par exemple, certains outils d’analyse statique de style (dits « linters », voir Invoquer guix lint) vérifient les phases de compilation des paquets pour détecter des problèmes potentiels. Cette conversion peut être réalisée avec cette procédure. Toutefois, certaines informations peuvent être perdues au cours de l’opération. Plus spécifiquement, les objets abaissables seront remplacés silencieusement par un objet arbitraire – pour l’instant la liste (*approximate*), mais cela pourrait changer.


Notes de bas de page

(24)

Le terme de strate dans ce contexte a été inventé par Manuel Serrano et ses collègues dans le contexte de leurs travaux sur Hop. Oleg Kiselyov, qui a écrit des essais perspicaces ainsi que du code sur le sujet, utilise le terme de « mise en scène » pour ce genre de génération de code.


Suivant: Invoquer guix repl, Précédent: La monade du dépôt, Monter: Interface de programmation   [Table des matières][Index]