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


6.7 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 Scheme19 : le « code hôte » — le code qui défini 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.

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 dérivations : on peut définir des compilateurs capable « d’abaisser » d’autres objets de haut-niveau ou des fichiers dans le dépôt, pour que ces objets puissent aussi être insérés dans des gexps. Par exemple, des objets haut-niveau utiles qui pourraient être insérées dans une gexp sont les « objets simili-fichiers », qui rendent facile l’ajout de fichiers dans le dépôt et les références vers eux dans les 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 une 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)
       (system* (string-append #+coreutils "/bin/ln")
                "-s"
                (string-append #$emacs "/bin/emacs")
                (string-append #$output "/bin/vi")))
   #:target "mips64el-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 vm)))
  (gexp->derivation "something-with-vms"
                    #~(begin
                        (use-modules (guix build utils)
                                     (gnu build vm))
                        …)))

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.

Syntaxe Scheme : #~exp
Syntaxe Scheme : (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).

#+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 à l’exécution du type gexp? (voir plus bas).

Syntaxe Scheme : 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….

Syntaxe Scheme : 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 Scheme : 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édure 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] [#:substitutable? #t] [#:properties '()] [#:guile-for-build #f]

Renvoie une dérivation name qui lance exp (une gexp) avec guile-for-build (une dérivation) sur system ; exp est stocké dans un fichier appelé script-name. Lorsque target est vraie, elle est utilisée comme triplet de cible de compilation croisée pour les paquets référencés par exp.

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 package)
(file-name package output)
(file-name derivation)
(file-name derivation output)
(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 Scheme : local-file file [name] [#:recursive? #f] [#:select? (const #t)]

Renvoie un objet représentant un fichier local file à ajouter au dépôt ; cet objet peut être utilisé dans une gexp. Si file est un nom de fichier relatif, il est récupéré à partir de la position du fichier source dans lequel il apparaît. file sera ajouté au dépôt sous le nom 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 Scheme : 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 Scheme : computed-file name gexp [#:options '(#:local-build? #t)]

Renvoie un objet représentant un élément du dépôt name, un fichier ou un répertoire calculé par gexp. 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]

Renvoie un script exécutable name qui lance exp avec guile, avec les modules importés de exp dans son chemin de recherche. Cherche 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 Scheme : 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 honorer 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 : scheme-file name exp [#:splice? #f]

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 Scheme : 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 Scheme : 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 Scheme : 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 Scheme : 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.

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 la dérivation ou l’élément du dépôt comme une valeur de %store-monad qui correspond à obj pour system, en compilant de manière croisée pour target si target est vrai. obj doit être un objet qui a un compilateur de gexp associé, comme un <package>.


Notes de bas de page

(19)

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


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