Siguiente: , Anterior: , Subir: Interfaz programática   [Índice general][Índice]


8.10 Expresiones-G

Por tanto tenemos “derivaciones”, que representan una secuencia de acciones de construcción a realizar para producir un elemento en el almacén (véase Derivaciones). Estas acciones de construcción se llevan a cabo cuando se solicita al daemon construir realmente la derivación; se ejecutan por el daemon en un contenedor (véase Invocación de guix-daemon).

No debería ser ninguna sorpresa que nos guste escribir estas acciones de construcción en Scheme. Cuando lo hacemos, terminamos con dos estratos de código Scheme20: el “código anfitrión”—código que define paquetes, habla al daemon, etc.—y el “código de construcción”—código que realmente realiza las acciones de construcción, como la creación de directorios, la invocación de make, etcétera (véase Fases de construcción).

Para describir una derivación y sus acciones de construcción, típicamente se necesita embeber código de construcción dentro del código anfitrión. Se resume en la manipulación de código de construcción como datos, y la homoiconicidad de Scheme—el código tiene representación directa como datos—es útil para ello. Pero necesitamos más que el mecanismo normal de quasiquote en Scheme para construir expresiones de construcción.

El módulo (guix gexp) implementa las expresiones-G, una forma de expresiones-S adaptada para expresiones de construcción. Las expresiones-G, o gexps, consiste esencialmente en tres formas sintácticas: gexp, ungexp y ungexp-splicing (o simplemente: #~, #$ y #$@), que son comparables a quasiquote, unquote y unquote-splicing, respectivamente (véase quasiquote en GNU Guile Reference Manual). No obstante, hay importantes diferencias:

Este mecanismo no se limita a objetos de paquete ni derivación: pueden definirse compiladores capaces de “bajar el nivel” de otros objetos de alto nivel a derivaciones o archivos en el almacén, de modo que esos objetos puedan introducirse también en expresiones-G. Por ejemplo, un tipo útil de objetos de alto nivel que pueden insertarse en una expresión-G son los “objetos tipo-archivo”, los cuales facilitan la adición de archivos al almacén y su referencia en derivaciones y demás (vea local-file y plain-file más adelante).

Para ilustrar la idea, aquí está un ejemplo de expresión-G:

(define exp-construccion
  #~(begin
      (mkdir #$output)
      (chdir #$output)
      (symlink (string-append #$coreutils "/bin/ls")
               "enumera-archivos")))

Esta expresión-G puede pasarse a gexp->derivation; obtenemos una derivación que construye un directorio que contiene exactamente un enlace simbólico a /gnu/store/…-coreutils-8.22/bin/ls:

(gexp->derivation "la-cosa" exp-construccion)

Como se puede esperar, la cadena "/gnu/store/…-coreutils-8.22" se sustituye por la referencia al paquete coreutils en el código de construcción real, y coreutils se marca automáticamente como una entrada a la derivación. Del mismo modo, #$output (equivalente a (ungexp output)) se reemplaza por una cadena que contiene el nombre del directorio de la salida de la derivación.

En un contexto de compilación cruzada, es útil distinguir entre referencias a construcciones nativas del paquete—que pueden ejecutarse en el sistema anfitrión—de referencias de compilaciones cruzadas de un paquete. Para dicho fin, #+ tiene el mismo papel que #$, pero es una referencia a una construcción nativa del paquete:

(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")

En el ejemplo previo, se usa la construcción nativa de coreutils, de modo que ln pueda realmente ejecutarse en el anfitrión; pero se hace referencia a la construcción de compilación cruzada de emacs.

Otra característica de las expresiones-G son los módulos importados: a veces deseará ser capaz de usar determinados módulos Guile del “entorno anfitrión” en la expresión-G, de modo que esos módulos deban ser importados en el “entorno de construcción”. La forma with-imported-modules le permite expresarlo:

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

En este ejemplo, el módulo (guix build utils) se incorpora automáticamente dentro del entorno de construcción aislado de nuestra expresión-G, de modo que (use-modules (guix build utils)) funciona como se espera.

De manera habitual deseará que la clausura del módulo se importe—es decir, el módulo en sí y todos los módulos de los que depende—en vez del módulo únicamente; si no se hace, cualquier intento de uso del módulo fallará porque faltan módulos dependientes. El procedimiento source-module-closure computa la clausura de un módulo mirando en las cabeceras de sus archivos de fuentes, lo que es útil en este caso:

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

(with-imported-modules (source-module-closure
                         '((guix build utils)
                           (gnu build vm)))
  (gexp->derivation "algo-con-maq-virtuales"
                    #~(begin
                        (use-modules (guix build utils)
                                     (gnu build vm))
                        )))

De la misma manera, a veces deseará importar no únicamente módulos puros de Scheme, pero también “extensiones” como enlaces Guile a bibliotecas C u otros paquetes “completos”. Si, digamos, necesitase el paquete guile-json disponible en el lado de construcción, esta sería la forma de hacerlo:

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

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

La forma sintáctica para construir expresiones-G se resume a continuación.

Sintaxis Scheme: #~exp
Sintaxis Scheme: (gexp exp)

Devuelve una expresión-G que contiene exp. exp puede contener una o más de las siguientes formas:

#$obj
(ungexp obj)

Introduce una referencia a obj. obj puede tener uno de los tipos permitidos, por ejemplo un paquete o derivación, en cuyo caso la forma ungexp se substituye por el nombre de archivo de su salida—por ejemplo, "/gnu/store/…-coreutils-8.22.

Si obj es una lista, se recorre y las referencias a objetos permitidos se substituyen de manera similar.

Si obj es otra expresión-G, su contenido se inserta y sus dependencias se añaden a aquellas de la expresión-G que la contiene.

Si obj es otro tipo de objeto, se inserta tal cual es.

#$obj:salida
(ungexp obj salida)

Como la forma previa, pero referenciando explícitamente la salida de obj—esto es útil cuando obj produce múltiples salidas (véase Paquetes con múltiples salidas).

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

Igual que ungexp, pero produce una referencia a la construcción nativa de obj cuando se usa en un contexto de compilación cruzada.

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

Inserta una referencia a la salida de la derivación salida, o a la salida principal cuando salida se omite.

Esto únicamente tiene sentido para expresiones-G pasadas a gexp->derivation.

#$@lst
(ungexp-splicing lst)

Lo mismo que la forma previa, pero expande el contenido de la lista lst como parte de la lista que la contiene.

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

Lo mismo que la forma previa, pero hace referencia a las construcciones nativas de los objetos listados en lst.

Las expresiones-G creadas por gexp o #~ son objetos del tipo gexp? en tiempo de ejecución (véase a continuación).

Sintaxis Scheme: with-imported-modules módulos cuerpo

Marca las expresiones-G definidas en el cuerpo… como si requiriesen módulos en su entorno de ejecución.

Cada elemento en módulos puede ser el nombre de un módulo, como (guix build utils), o puede ser el nombre de un módulo, seguido de una flecha, seguido de un objeto tipo-archivo:

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

En el ejemplo previo, los dos primeros módulos se toman de la ruta de búsqueda, y el último se crea desde el objeto tipo-archivo proporcionado.

Esta forma tiene ámbito léxico: tiene efecto en las expresiones-G definidas en cuerpo…, pero no en aquellas definidas, digamos, en procedimientos llamados por cuerpo….

Sintaxis Scheme: with-extensions extensiones cuerpo

Marca que las expresiones definidas en cuerpo… requieren extensiones en su entorno de construcción y ejecución. extensiones es típicamente una lista de objetos de paquetes como los que se definen en el módulo (gnu packages guile).

De manera concreta, los paquetes listados en extensiones se añaden a la ruta de carga mientras se compilan los módulos importados en cuerpo…; también se añaden a la ruta de carga en la expresión-G devuelta por cuerpo….

Procedimiento Scheme: gexp? obj

Devuelve #t si obj es una expresión-G.

Las expresiones-G están destinadas a escribirse en disco, tanto en código que construye alguna derivación, como en archivos planos en el almacén. Los procedimientos monádicos siguientes le permiten hacerlo (véase La mónada del almacén, para más información sobre mónadas).

Procedimiento monádico: gexp->derivation nombre 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 nombre "-builder")] [#:deprecation-warnings #f] [#:local-build? #f] [#:substitutable? #t] [#:properties '()] [#:guile-for-build #f]

Devuelve una derivación nombre que ejecuta exp (una expresión-G) con guile-for-build (una derivación) en el sistema system; exp se almacena en un archivo llamado script-name. Cuando target tiene valor verdadero, se usa como tripleta de compilación cruzada para paquetes a los que haga referencia exp.

modules está obsoleto en favor de with-imported-modules. Su significado es hacer que los módulos modules estén disponibles en el contexto de evaluación de exp; modules es una lista de nombres de módulos Guile buscados en module-path para ser copiados al almacén, compilados y disponibles en la ruta de carga durante la ejecución de exp—por ejemplo, ((guix build utils) (gui build gnu-build-system)).

effective-version determina la cadena usada cuando se añaden las extensiones de exp (vea with-extensions) a la ruta de búsqueda—por ejemplo, "2.2".

graft? determina si los paquetes a los que exp hace referencia deben ser injertados cuando sea posible.

Cuando references-graphs es verdadero, debe ser una lista de tuplas de una de las formas siguientes:

(nombre-archivo paquete)
(nombre-archivo paquete salida)
(nombre-archivo derivación)
(nombre-archivo derivación salida)
(nombre-archivo elemento-almacén)

El lado derecho de cada elemento de references-graphs se convierte automáticamente en una entrada del proceso de construcción de exp. En el entorno de construcción, cada nombre-archivo contiene el grafo de referencias del elemento correspondiente, en un formato de texto simple.

allowed-references debe ser o bien #f o una lista de nombres y paquetes de salida. En el último caso, la lista denota elementos del almacén a los que el resultado puede hacer referencia. Cualquier referencia a otro elemento del almacén produce un error de construcción. De igual manera con disallowed-references, que enumera elementos a los que las salidas no deben hacer referencia.

deprecation-warnings determina si mostrar avisos de obsolescencia durante la compilación de los módulos. Puede ser #f, #t o 'detailed.

El resto de parámetros funcionan como en derivation (véase Derivaciones).

Los procedimientos local-file, plain-file, computed-file, program-file y scheme-file a continuación devuelven objetos tipo-archivo. Esto es, cuando se expanden en una expresión-G, estos objetos dirigen a un archivo en el almacén. Considere esta expresión-G:

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

El efecto aquí es el “internamiento” de /tmp/mi-nscd.conf mediante su copia al almacén. Una vez expandida, por ejemplo vía gexp->derivation, la expresión-G hace referencia a la copia bajo /gnu/store; por tanto, la modificación o el borrado del archivo en /tmp no tiene ningún efecto en lo que la expresión-G hace. plain-file puede usarse de manera similar; se diferencia en que el contenido del archivo se proporciona directamente como una cadena.

Procedimiento Scheme: local-file archivo [nombre] [#:recursive? #f] [#:select? (const #t)]

Devuelve un objeto que representa el archivo local archivo a añadir al almacén; este objeto puede usarse en una expresión-G. Si archivo es un nombre de archivo relativo, se busca de forma relativa al archivo fuente donde esta forma aparece; si archivo no es una cadena literal, se buscará de manera relativa al directorio de trabajo durante la ejecución. archivo se añadirá al almacén bajo nombre—de manera predeterminada el nombre de archivo sin los directorios.

Cuando recursive? es verdadero, los contenidos del archivo se añaden recursivamente; si archivo designa un archivo plano y recursive? es verdadero, sus contenidos se añaden, y sus bits de permisos se mantienen.

Cuando recursive? es verdadero, llama a (select? archivo stat) por cada entrada del directorio, donde archivo es el nombre absoluto de archivo de la entrada y stat es el resultado de lstat; excluyendo las entradas para las cuales select? no devuelve verdadero.

Esta es la contraparte declarativa del procedimiento monádico interned-file (véase interned-file).

Procedimiento Scheme: plain-file nombre contenido

Devuelve un objeto que representa un archivo de texto llamado nombre con el contenido proporcionado (una cadena o un vector de bytes) para ser añadido al almacén.

Esta es la contraparte declarativa de text-file.

Procedimiento Scheme: computed-file nombre gexp [#:local-build? #t] [#:options '()]

Devuelve un objeto que representa el elemento del almacén nombre, un archivo o un directorio computado por gexp. Cuando local-build? tiene valor verdadero (el caso predeterminado), la derivación se construye de manera local. options es una lista de parámetros adicionales proporcionados a gexp->derivation.

Esta es la contraparte declarativa de gexp->derivation.

Procedimiento monádico: gexp->script nombre exp [#:guile (default-guile)] [#:module-path %load-path]  [#:system

(%current-system)] [#:target #f] Devuelve un guión ejecutable nombre que ejecuta exp usando guile, con los módulos importados por exp en su ruta de búsqueda. Busca los módulos de exp en module-path.

El ejemplo siguiente construye un guión que simplemente invoca la orden ls:

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

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

Cuando se ejecuta a través del almacén (véase run-with-store), obtenemos una derivación que produce un archivo ejecutable /gnu/store/…-enumera-archivos más o menos así:

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

Devuelve un objeto que representa el elemento ejecutable del almacén nombre que ejecuta gexp. guile es el paquete Guile usado para ejecutar el guión. Los módulos importados por gexp se buscan en module-path.

Esta es la contraparte declarativa de gexp->script.

Procedimiento monádico: gexp->file nombre exp [#:set-load-path? #t] [#:module-path %load-path] [#:splice? #f] [#:guile (default-guile)]

Devuelve una derivación que construye un archivo nombre que contiene exp. Cuando splice? es verdadero, se considera que exp es una lista de expresiones que deben ser expandidas en el archivo resultante.

Cuando set-load-path es verdadero, emite código en el archivo resultante para establecer %load-path y %load-compiled-path de manera que respeten los módulos importados por exp. Busca los módulos de exp en module-path.

El archivo resultante hace referencia a todas las dependencias de exp o a un subconjunto de ellas.

Procedimiento Scheme: scheme-file nombre exp [#:splice? #f] [#:set-load-path? #t]

Devuelve un objeto que representa el archivo Scheme nombre que contiene exp.

Esta es la contraparte declarativa de gexp->file.

Procedimiento monádico: text-file* nombre texto

Devuelve como un valor monádico una derivación que construye un archivo de texto que contiene todo texto. texto puede ser una lista de, además de cadenas, objetos de cualquier tipo que pueda ser usado en expresiones-G: paquetes, derivaciones, archivos locales, objetos, etc. El archivo del almacén resultante hace referencia a todos ellos.

Esta variante debe preferirse sobre text-file cuando el archivo a crear haga referencia a elementos del almacén. Esto es el caso típico cuando se construye un archivo de configuración que embebe nombres de archivos del almacén, como este:

(define (perfil.sh)
  ;; Devuelve el nombre de un guión shell en el almacén
  ;; que establece la variable de entorno 'PATH'
  (text-file* "perfil.sh"
              "export PATH=" coreutils "/bin:"
              grep "/bin:" sed "/bin\n"))

En este ejemplo, el archivo /gnu/store/…-perfil.sh resultante hará referencia a coreutils, grep y sed, por tanto evitando que se recolecten como basura durante su tiempo de vida.

Procedimiento Scheme: mixed-text-file nombre texto

Devuelve un objeto que representa el archivo del almacén nombre que contiene texto. texto es una secuencia de cadenas y objetos tipo-archivo, como en:

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

Esta es la contraparte declarativa de text-file*.

Procedimiento Scheme: file-union nombre archivos

Devuelve un <computed-file> que construye un directorio que contiene todos los archivos. Cada elemento en archivos debe ser una lista de dos elementos donde el primer elemento es el nombre de archivo usado en el nuevo directorio y el segundo elemento es una expresión-G que denota el archivo de destino. Aquí está un ejemplo:

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

Esto emite un directorio etc que contiene estos dos archivos.

Procedimiento Scheme: directory-union nombre cosas

Devuelve un directorio que es la unión de cosas, donde cosas es una lista de objetos tipo-archivo que denotan directorios. Por ejemplo:

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

emite un directorio que es la unión de los paquetes guile y emacs.

Procedimientos Scheme: file-append obj sufijo

Devuelve un objeto tipo-archivo que se expande a la concatenación de obj y sufijo, donde obj es un objeto que se puede bajar de nivel y cada sufijo es una cadena.

Como un ejemplo, considere esta expresión-G:

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

El mismo efecto podría conseguirse con:

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

Hay una diferencia no obstante: en el caso de file-append, el guión resultante contiene una ruta absoluta de archivo como una cadena, mientras que en el segundo caso, el guión resultante contiene una expresión (string-append …) para construir el nombre de archivo en tiempo de ejecución.

Sintaxis Scheme: let-system sistema cuerpo
Sintaxis Scheme: let-system (sistema objetivo) cuerpo

Asocia sistema al sistema objetivo actual—por ejemplo, "x86_64-linux"—dentro de cuerpo.

En el segundo caso, asocia también objetivo al objetivo actual de compilación cruzada—una tripleta de GNU como "arm-linux-gnueabihf"—o #f si no se trata de una compilación cruzada.

let-system es útil en el caso ocasional en el que el objeto introducido en la expresión-G depende del sistema objetivo, como en este ejemplo:

#~(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 "¡ni idea!"))))
   "-net" "user" #$image)
Sintaxis Scheme: with-parameters ((parámetro valor …) exp

Este macro es similar a la forma parameterize para parámetros asociados de forma dinámica (véase Parameters en GNU Guile Reference Manual). La principal diferencia es que se hace efectivo cuando el objeto tipo-archivo devuelto por exp se baja de nivel a una derivación o un elemento del almacén.

Un uso típico de with-parameters es para forzar el sistema efectivo de cierto objeto:

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

El ejemplo previo devuelve un objeto que corresponde a la construcción en i686 de Coreutils, independientemente del valor actual de %current-system.

Por supuesto, además de expresiones-G embebidas en código “anfitrión”, hay también módulos que contienen herramientas de construcción. Para clarificar que están destinados para su uso en el estrato de construcción, estos módulos se mantienen en el espacio de nombres (guix build …).

Internamente, los objetos de alto nivel se bajan de nivel, usando su compilador, a derivaciones o elementos del almacén. Por ejemplo, bajar de nivel un paquete emite una derivación, y bajar de nivel un plain-file emite un elemento del almacén. Esto se consigue usando el procedimiento monádico lower-object.

Procedimiento monádico: lower-object obj [sistema] [#:target #f]

Devuelve como un valor en %store-monad la derivación o elemento del almacén que corresponde a obj en sistema, compilando de manera cruzada para target si target es verdadero. obj debe ser un objeto que tiene asociado un compilador de expresiones-G, como por ejemplo un objeto del tipo <package>.


Notas al pie

(20)

El término estrato en este contexto se debe a Manuel Serrano et al. en el contexto de su trabajo en Hop. Oleg Kiselyov, quien ha escrito profundos ensayos sobre el tema, se refiere a este tipo de generación de código como separación en etapas o staging.


Siguiente: , Anterior: , Subir: Interfaz programática   [Índice general][Índice]