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


8.7 Utilitaires de construction

Dès que vous commencerez à écrire des définitions de paquets non triviales (voir Définition des paquets) ou d’autres actions de compilation (voir G-Expressions), vous commencerez probablement à chercher des fonctions auxiliaires pour les actions « de type shell » — créer des répertoires, copier et supprimer des fichiers de manière récursive, manipuler les phases de compilation, etc. Le module (guix build utils) fournit de telles procédures utilitaires.

La plupart des systèmes de construction chargent (guix build utils) (voir Systèmes de construction). Ainsi, lorsque vous écrivez des phases de construction personnalisées pour vos définitions de paquets, vous pouvez généralement supposer que ces procédures sont disponibles.

Lorsque vous écrivez des G-expressions, vous pouvez importer (guix build utils) « côté construction » en utilisant with-imported-modules et ensuite le rendre disponible avec la forme use-modules (voir Using Guile Modules dans GNU Guile Reference Manual) :

(with-imported-modules '((guix build utils))  ;on l'importe
  (computed-file "empty-tree"
                 #~(begin
                     ;; On le rend disponible.
                     (use-modules (guix build utils))

                     ;; On utilise sa procédure « mkdir-p » sans souci.
                     (mkdir-p (string-append #$output "/a/b/c")))))

Le reste de cette section est la référence pour la plupart des procédures auxiliaires fournies par (guix build utils).

8.7.1 Traitement des noms de fichiers du dépôt

Cette section documente les procédures de traitement des noms de fichier du dépôt.

Procédure :%store-directory

Renvoie le nom de répertoire du dépôt.

Procédure :store-file-name? fichier

Renvoie vrai si fichier est dans le dépôt.

Procédure :strip-store-file-name fichier

Enlevez le /gnu/store et le hash de fichier, un nom de fichier du dépôt. Le résultat est généralement une chaîne "paquet-version".

Procédure :package-name->name+version nom

Si l’on passe nom, un nom de paquet comme "toto-0.9.1b", on obtient deux valeurs : "toto" et "0.9.1b". Lorsque la partie version est indisponible, nom et #f sont renvoyés. Le premier trait d’union suivi d’un chiffre est considéré comme introduisant la partie version.

8.7.2 Types de fichier

Les procédures ci-dessous portent sur les fichiers et les types de fichiers.

Procédure :directory-exists? dir

Renvoie #t si dir existe et est un répertoire.

Procédure :executable-file? fichier

Renvoie #t si fichier existe et est exécutable.

Renvoie #t si fichier est un lien symbolique (un « symlink »).

Procédure :elf-file? fichier
Procédure :ar-file? fichier
Procédure :gzip-file? fichier

Renvoie #t si fichier est, respectivement, un fichier ELF, une archive ar (telle qu’une bibliothèque statique .a), ou un fichier gzip.

Procédure :reset-gzip-timestamp fichier [#:keep-mtime? #t]

Si fichier est un fichier gzip, réinitialise son horodatage intégré (comme avec gzip --no-name) et renvoie vrai. Sinon, renvoie #f. Lorsque keep-mtime? est vrai, conserve la date de modification de fichier.

8.7.3 Manipulation de fichiers

Les procédures et macros suivantes permettent de créer, modifier et supprimer des fichiers. Elles offrent des fonctionnalités comparables à celles des utilitaires shell courants tels que mkdir -p, cp -r, rm -r et sed. Elles complètent l’interface de système de fichiers étendue, mais de bas niveau, de Guile (voir POSIX dans GNU Guile Reference Manual).

Macro :with-directory-excursion répertoire corps…

Exécute corps avec répertoire comme répertoire actuel du processus.

En gros, cette macro change le répertoire actuel en répertoire avant d’évaluer corps, en utilisant chdir. (voir Processus dans Manuel de référence de GNU Guile). Elle revient au répertoire initial à la sortie de la portée dynamique de corps, qu’il s’agisse d’un retour de procédure normal ou d’une sortie non locale telle qu’une exception.

Procédure :mkdir-p dir

Crée le répertoire dir et tous ses ancètres.

Procédure :install-file fichier répertoire

Crée répertoire s’il n’existe pas et y copie fichier sous le même nom.

Procédure :make-file-writable fichier

Rend fichier inscriptible pour son propriétaire.

Procédure :copy-recursively source destination [#:log (current-output-port)] [#:follow-symlinks? #f]  [#:copy-file

copy-file] [#:keep-mtime? #f] [#:keep-permissions? #t]  [#:select? (const #t)] Copy source directory to destination. Follow symlinks if follow-symlinks? is true; otherwise, just preserve them. Call copy-file to copy regular files. Call select?, taking two arguments, file and stat, for each entry in source, where file is the entry’s absolute file name and stat is the result of lstat (or stat if follow-symlinks? is true); exclude entries for which select? does not return true. When keep-mtime? is true, keep the modification time of the files in source on those of destination. When keep-permissions? is true, preserve file permissions. Write verbose output to the log port.

Procédure :delete-file-recursively dir [#:follow-mounts? #f]

Efface récursivement dir, comme rm -rf, sans suivre les liens symboliques. Ne suit pas non plus les points de montage, à moins que follow-mounts? ne soit vrai. Rapporte mais ignore les erreurs.

Macro :substitute* fichier ((regexp match-var…) body…) … Remplace regexp dans

fichier par la chaîne renvoyée par body. body est évalué avec chaque match-var lié à la sous-expression de regexp positionnelle correspondante. Par exemple :

(substitute* file
  (("hello")
   "good morning\n")
  (("toto([a-z]+)titi(.*)$" all letters end)
   (string-append "tata" letters end)))

Ici, chaque fois qu’une ligne de fichier contient hello, elle est remplacée par good morning. Chaque fois qu’une ligne de fichier correspond à la deuxième regexp, all est lié à la correspondance complète, letters est lié à la première sous-expression, et end est lié à la dernière.

Lorsque l’une des match-var est _, aucune variable n’est liée à la sous-chaîne de correspondance associée.

Autrement, fichier peut être une liste de noms de fichier, auquel cas ils sont tous sujets aux substitutions.

Be careful about using $ to match the end of a line; by itself it won’t match the terminating newline of a line. For example, to match a whole line ending with a backslash, one needs a regex like "(.*)\\\\\n$".

8.7.4 Recherche de fichiers

Cette section documente les procédures pour chercher et filtrer des fichiers.

Procédure :file-name-predicate regexp

Renvoie un prédicat qui devient vrai lorsqu’on lui passe un nom de fichier dont le nom de base correspond à regexp.

Procédure :find-files dir [pred] [#:stat lstat] [#:directories ? #f] [#:fail-on-error ? #f]

Renvoie la liste des fichiers triés lexicographiquement sous dir pour lesquels pred renvoie « vrai ». Deux arguments sont passés à pred : le nom absolu du fichier et son tampon stat ; le prédicat par défaut renvoie toujours « vrai ». pred peut également être une expression régulière, auquel cas elle est équivalente à (file-name-predicate pred). stat est utilisé pour obtenir des informations sur les fichiers ; l’utilisation de lstat signifie que les liens symboliques ne sont pas suivis. Si directories? est vrai, alors les répertoires seront également inclus. Si fail-on-error? est vrai, il lance une exception en cas d’erreur.

Voici quelques exemples où nous supposons que le répertoire actuel est la racine de l’arborescence des sources de Guix :

;; Liste tous les fichiers normaux dans le répertoire actuel.
(find-files ".")
 ("./.dir-locals.el" "./.gitignore" )

;; Liste tous les fichiers .scm sous gnu/services.
(find-files "gnu/services" "\\.scm$")
 ("gnu/services/admin.scm" "gnu/services/audio.scm" )

;; Liste les fichiers ar dans le répertoire actuel.
(find-files "." (lambda (file stat) (ar-file? file)))
 ("./libformat.a" "./libstore.a" )
Procédure :which programme

Renvoie le nom complet du fichier pour programme tel qu’il se trouve dans $PATH, ou #f si programme n’a pas pu être trouvé.

Procédure :search-input-file entrées nom
Procédure :search-input-directory entrées nom

Renvoie le nom de fichier complet de nom trouvé dans entrées ; search-input-file recherche les fichiers normaux et search-input-directory recherche les dossiers. Si nom n’est pas trouvé, une exception est levée.

Ici, entrées doit être une liste d’association comme les variables inputs et native-inputs disponibles dans les phases de construction (voir Phases de construction).

Voici un exemple (simplifié) d’utilisation de search-input-file dans une phase de construction du paquet wireguard-tools :

(add-after 'install 'wrap-wg-quick
  (lambda* (#:key inputs outputs #:allow-other-keys)
    (let ((coreutils (string-append (assoc-ref inputs "coreutils")
                                    "/bin")))
      (wrap-program (search-input-file outputs "bin/wg-quick")
        #:sh (search-input-file inputs "bin/bash")
        `("PATH" ":" prefix ,(list coreutils))))))

8.7.5 Invocation de programme

Vous trouverez des procédures pratiques pour invoquer des processus dans ce module, en particulier des enveloppes pratiques autour de system* de Guile (voir system* dans le manuel de référence de Guile).

Procédure :invoke programme args…

Invoque le programme avec les args donnés. Lève une exception &invoke-error si le code de retour n’est pas zéro ; sinon renvoie #t.

L’avantage par rapport à system* c’est que vous n’avez pas besoin de vérifier la valeur de sortie. Cela réduit le code dans les petits scripts comme par exemple dans les phases de construction des paquets.

Procédure :invoke-error? c

Renvoie vrai si c est une condition &invoke-error.

Procédure :invoke-error-program c
Procédure :invoke-error-arguments c
Procédure :invoke-error-exit-status c
Procédure :invoke-error-term-signal c
Procédure :invoke-error-stop-signal c

Accède aux champs de c, une condition &invoke-error.

Procédure :report-invoke-error c [port]

Rapporte c, une condition &invoke-error, sur port (par défaut le port d’erreur actuel), dans un format humainement lisible.

L’utilisation habituelle ressemblerait à ceci :

(use-modules (srfi srfi-34) ;pour « guard »
             (guix build utils))

(guard (c ((invoke-error? c)
           (report-invoke-error c)))
  (invoke "date" "--option-imaginaire"))

-| command "date" "--option-imaginaire" failed with status 1
Procédure :invoke/quiet programme args…

Invoque programme avec args et capture la sortie standard et l’erreur standard de programme. Si programme termine sans erreur, n’affiche rien et renvoie la valeur non spécifiée ; sinon, lève une condition d’erreur &message qui inclut le code de statut et la sortie de programme.

Voici un exemple :

(use-modules (srfi srfi-34) ;pour « guard »
             (srfi srfi-35) ;pour « message-condition? »
             (guix build utils))

(guard (c ((message-condition? c)
           (display (condition-message c))))
  (invoke/quiet "date")  ; tout va bien
  (invoke/quiet "date" "--imaginary-option"))

-| 'date --imaginary-option' exited with status 1; output follows:

    date : option non reconnue '--imaginary-option'
  Saisissez « date --help » pour plus d'informations.

8.7.6 Phases de construction

Le (guix build utils) contient également des outils permettant de manipuler les phases de construction telles qu’elles sont utilisées par les systèmes de construction (voir Systèmes de construction). Les phases de construction sont représentées sous forme de listes d’associations ou « alists » (voir Association Lists dans Manuel de référence de GNU Guile) où chaque clé est un symbole désignant la phase et où la valeur associée est une procédure (voir Phases de construction).

Le noyau Guile et le module (srfi srfi-1) fournissent tous deux des outils pour manipuler les alignements. Le module (guix build utils) complète ces outils avec des outils écrits en tenant compte des phases de construction.

Macro :modify-phases phases clause…

Modifie phases séquentiellement selon chaque clause, qui peut avoir l’une des formes suivantes :

(delete old-phase-name)
(replace old-phase-name new-phase)
(add-before old-phase-name new-phase-name new-phase)
(add-after old-phase-name new-phase-name new-phase)

Où chaque phase-name ci-dessus est une expression s’évaluant en un symbole, et new-phase une expression évaluant à une procédure.

L’exemple ci-dessous est tiré de la définition du paquet grep. Il ajoute une phase à exécuter après la phase install, appelée fix-egrep-and-fgrep. Cette phase est une procédure (lambda* pour les procédures anonymes) qui prend un paramètre nommé #:outputs et ignore les paramètres nommés supplémentaires (voir Optional Arguments dans GNU Guile Reference Manual, pour en savoir plus sur lambda* et les arguments optionnels et nommés). La phase utilise substitute* pour modifier les scripts egrep et fgrep installés afin qu’ils se réfèrent à grep par son nom de fichier absolu :

(modify-phases %standard-phases
  (add-after 'install 'fix-egrep-and-fgrep
    ;; Corrige « egrep » et « fgrep » pour exécuter « grep » via son
    ;; nom de fichier absolu au lieu de le chercher dans $PATH.
    (lambda* (#:key outputs #:allow-other-keys)
      (let* ((out (assoc-ref outputs "out"))
             (bin (string-append out "/bin")))
        (substitute* (list (string-append bin "/egrep")
                           (string-append bin "/fgrep"))
          (("^exec grep")
           (string-append "exec " bin "/grep")))))))

Dans l’exemple ci-dessous, les phases sont modifiées de deux façons : la phase standard configure est supprimée, vraisemblablement parce que le paquet n’a pas de script configure ou quelque chose de similaire, et la phase par défaut install est remplacée par une phase qui copie manuellement les fichiers exécutables à installer :

(modify-phases %standard-phases
  (delete 'configure)      ;pas de script « configure »
  (replace 'install
    (lambda* (#:key outputs #:allow-other-keys)
      ;; Le Makefile du paquet ne fournit pas de règle « install »
      ;; alors on le fait nous-mêmes.
      (let ((bin (string-append (assoc-ref outputs "out")
                                "/bin")))
        (install-file "footswitch" bin)
        (install-file "scythe" bin)))))

8.7.7 Enveloppes

Il est courant qu’une commande ait besoin que certaines variables d’environnement soient initialisées pour fonctionner correctement, souvent des chemins de recherche (voir Chemins de recherche). Sans cela, la commande peut échouer à trouver les fichiers et les autres commandes dont elle a besoin, ou elle pourrait trouver la « mauvaise » dépendance — en dépendant de l’environnement sur lequel elle tourne. Voici quelques exemples :

Pour l’auteur ou l’autrice d’un paquet, le but est de s’assurer que les commandes fonctionnent toujours pareil plutôt que de dépendre de paramètres externes. Une manière d’y arriver est d’envelopper les commandes dans un petit script qui défini ces variables d’environnement, ce qui assure que ces dépendances à l’exécution seront trouvées. L’enveloppe serait utilisée pour initialiser PATH, GUILE_LOAD_PATH ou QT_PLUGIN_PATH dans les exemple ci-dessus.

Pour faciliter cette tâche, le module (guix build utils) fournit quelques fonctions auxiliaires pour envelopper des commandes.

Procédure :wrap-program program [#:sh sh] [#:rest variables]

Crée une enveloppe pour programme. variables doit ressembler à ceci :

'(variable délimiteur position liste-de-répertoires)

délimiteur est facultatif. : sera utilisé si délimiteur n’est pas fourni.

Par exemple, cet appel :

(wrap-program "toto"
              '("PATH" ":" = ("/gnu/.../titi/bin"))
              '("CERT_PATH" suffix ("/gnu/.../tata/certs"
                                    "/qux/certs")))

copiera toto dans .toto-real et créera le fichier toto avec le contenu suivant :

#!emplacement/de/bin/bash
export PATH="/gnu/.../titi/bin"
export CERT_PATH="$CERT_PATH${CERT_PATH:+:}/gnu/.../tata/certs:/qux/certs"
exec -a $0 emplacement/de/.foo-real "$@"

Si programme a déjà été enveloppé par wrap-program, l’enveloppe est agrandie avec les définitions de variables. Sinon, sh sera utilisé comme interpréteur.

Procédure :wrap-script programme [#:guile guile] [#:rest variables]

Enveloppe le script programme pour que variables soient définie avant. Le format de variables est le même que pour la procédure wrap-program. Cette procédure est différente de wrap-program car elle ne crée pas de script shell séparé. Au lieu de cela, program est modifié directement en ajoutant un script Guile au début, qui est interprété comme un commentaire par le langage de script.

Les commentaires à l’encodage spécifique pris en charge par Python sont recréés sur la seconde ligne.

Remarquez que cette procédure ne peut être utilisée qu’une seule fois par fichier car les scripts Guile ne sont pas pris en charge.


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