Nächste: , Vorige: , Nach oben: Programmierschnittstelle   [Inhalt][Index]


9.7 Werkzeuge zur Erstellung

Sobald Sie anfangen, nichttriviale Paketdefinitionen (siehe Pakete definieren) oder andere Erstellungsaktionen (siehe G-Ausdrücke) zu schreiben, würden Sie sich wahrscheinlich darüber freuen, Helferlein für „Shell-artige“ Aktionen vordefiniert zu bekommen, also Code, den Sie benutzen können, um Verzeichnisse anzulegen, Dateien rekursiv zu kopieren oder zu löschen, Erstellungsphasen anzupassen und Ähnliches. Das Modul (guix build utils) macht solche nützlichen Werkzeugprozeduren verfügbar.

Die meisten Erstellungssysteme laden (guix build utils) (siehe Erstellungssysteme). Wenn Sie also eigene Erstellungsphasen für Ihre Paketdefinitionen schreiben, können Sie in den meisten Fällen annehmen, dass diese Prozeduren bei der Auswertung sichtbar sein werden.

Beim Schreiben von G-Ausdrücken können Sie auf der „Erstellungsseite“ (guix build utils) mit with-imported-modules importieren und anschließend mit der use-modules-Form sichtbar machen (siehe Using Guile Modules in Referenzhandbuch zu GNU Guile):

(with-imported-modules '((guix build utils))  ;importieren
  (computed-file "leerer-verzeichnisbaum"
                 #~(begin
                     ;; Sichtbar machen.
                     (use-modules (guix build utils))

                     ;; Jetzt kann man problemlos 'mkdir-p' nutzen.
                     (mkdir-p (string-append #$output "/a/b/c")))))

Der Rest dieses Abschnitts stellt eine Referenz der meisten Werkzeugprozeduren dar, die (guix build utils) anbietet.

9.7.1 Umgehen mit Store-Dateinamen

Dieser Abschnitt dokumentiert Prozeduren, die sich mit Dateinamen von Store-Objekten befassen.

Scheme-Prozedur: %store-directory

Liefert den Verzeichnisnamen des Stores.

Scheme-Prozedur: store-file-name? Datei

Liefert wahr zurück, wenn sich Datei innerhalb des Stores befindet.

Scheme-Prozedur: strip-store-file-name Datei

Liefert den Namen der Datei, die im Store liegt, ohne den Anfang /gnu/store und ohne die Prüfsumme am Namensanfang. Als Ergebnis ergibt sich typischerweise eine Zeichenkette aus "Paket-Version".

Scheme-Prozedur: package-name->name+version Name

Liefert für den Paket-Namen (so etwas wie "foo-0.9.1b") zwei Werte zurück: zum einen "foo" und zum anderen "0.9.1b". Wenn der Teil mit der Version fehlt, werden der Name und #f zurückgeliefert. Am ersten Bindestrich, auf den eine Ziffer folgt, wird der Versionsteil abgetrennt.

9.7.2 Dateitypen

Bei den folgenden Prozeduren geht es um Dateien und Dateitypen.

Scheme-Prozedur: directory-exists? Verzeichnis

Liefert #t, wenn das Verzeichnis existiert und ein Verzeichnis ist.

Scheme-Prozedur: executable-file? Datei

Liefert #t, wenn die Datei existiert und ausführbar ist.

Liefert #t, wenn die Datei eine symbolische Verknüpfung ist (auch bekannt als „Symlink“).

Scheme-Prozedur: elf-file? Datei
Scheme-Prozedur: ar-file? Datei
Scheme-Prozedur: gzip-file? Datei

Liefert #t, wenn die Datei jeweils eine ELF-Datei, ein ar-Archiv (etwa eine statische Bibliothek mit .a) oder eine gzip-Datei ist.

Scheme-Prozedur: reset-gzip-timestamp Datei [#:keep-mtime? #t]

Wenn die Datei eine gzip-Datei ist, wird ihr eingebetteter Zeitstempel zurückgesetzt (wie bei gzip --no-name) und wahr geliefert. Ansonsten wird #f geliefert. Wenn keep-mtime? wahr ist, wird der Zeitstempel der letzten Modifikation von Datei beibehalten.

9.7.3 Änderungen an Dateien

Die folgenden Prozeduren und Makros helfen beim Erstellen, Ändern und Löschen von Dateien. Sie machen Funktionen ähnlich zu Shell-Werkzeugen wie mkdir -p, cp -r, rm -r und sed verfügbar. Sie ergänzen Guiles ausgiebige aber kleinschrittige Dateisystemschnittstelle (siehe POSIX in Referenzhandbuch zu GNU Guile).

Scheme-Syntax: with-directory-excursion Verzeichnis Rumpf

Den Rumpf ausführen mit dem Verzeichnis als aktuellem Verzeichnis des Prozesses.

Im Grunde ändert das Makro das aktuelle Arbeitsverzeichnis auf Verzeichnis bevor der Rumpf ausgewertet wird, mittels chdir (siehe Processes in Referenzhandbuch zu GNU Guile). Wenn der dynamische Bereich von Rumpf wieder verlassen wird, wechselt es wieder ins anfängliche Verzeichnis zurück, egal ob der Rumpf durch normales Zurückliefern eines Ergebnisses oder durch einen nichtlokalen Sprung wie etwa eine Ausnahme verlassen wurde.

Scheme-Prozedur: mkdir-p Verzeichnis

Das Verzeichnis und all seine Vorgänger erstellen.

Scheme-Prozedur: install-file Datei Verzeichnis

Verzeichnis erstellen, wenn es noch nicht existiert, und die Datei mit ihrem Namen dorthin kopieren.

Scheme-Prozedur: make-file-writable Datei

Dem Besitzer der Datei Schreibberechtigung darauf erteilen.

Scheme-Prozedur: copy-recursively Quelle Zielort [#:log (current-output-port)] [#:follow-symlinks? #f]  [#:copy-file

copy-file] [#:keep-mtime? #f] [#:keep-permissions? #t] Das Verzeichnis Quelle rekursiv an den Zielort kopieren. Wenn follow-symlinks? wahr ist, folgt die Rekursion symbolischen Verknüpfungen, ansonsten werden die Verknüpfungen als solche beibehalten. Zum Kopieren regulärer Dateien wird copy-file aufgerufen. Wenn keep-mtime? wahr ist, bleibt der Zeitstempel der letzten Änderung an den Dateien in Quelle dabei bei denen am Zielort erhalten. Wenn keep-permissions? wahr ist, bleiben Dateiberechtigungen erhalten. Ein ausführliches Protokoll wird in den bei log angegebenen Port geschrieben.

Scheme-Prozedur: delete-file-recursively Verzeichnis [#:follow-mounts? #f] Das Verzeichnis rekursiv löschen, wie bei

rm -rf, ohne symbolischen Verknüpfungen zu folgen. Auch Einhängepunkten wird nicht gefolgt, außer falls follow-mounts? wahr ist. Fehler dabei werden angezeigt aber ignoriert.

Scheme-Syntax: substitute* Datei ((Regexp Muster-Variable…) Rumpf…) … Den regulären

Ausdruck Regexp in der Datei durch die durch Rumpf berechnete Zeichenkette ersetzen. Bei der Auswertung von Rumpf wird jede Muster-Variable an den Teilausdruck an der entsprechenden Position der Regexp gebunden. Zum Beispiel:

(substitute* file
  (("Hallo")
   "Guten Morgen\n")
  (("foo([a-z]+)bar(.*)$" alles Buchstaben Ende)
   (string-append "baz" Buchstaben Ende)))

Jedes Mal, wenn eine Zeile in der Datei den Text Hallo enthält, wird dieser durch Guten Morgen ersetzt. Jedes Mal, wenn eine Zeile zum zweiten regulären Ausdruck passt, wird alles an die vollständige Übereinstimmung gebunden, Buchstaben wird an den ersten Teilausdruck gebunden und Ende an den letzten.

Wird für eine Muster-Variable nur _ geschrieben, so wird keine Variable an die Teilzeichenkette an der entsprechenden Position im Muster gebunden.

Alternativ kann statt einer Datei auch eine Liste von Dateinamen angegeben werden. In diesem Fall wird jede davon den Substitutionen unterzogen.

Seien Sie vorsichtig bei der Nutzung von $, um auf das Ende einer Zeile zu passen. $ passt nämlich nicht auf den Zeilenumbruch am Ende einer Zeile.

9.7.4 Dateien suchen

Dieser Abschnitt beschreibt Prozeduren, um Dateien zu suchen und zu filtern.

Scheme-Prozedur: file-name-predicate Regexp

Liefert ein Prädikat, das gegeben einen Dateinamen, dessen Basisnamen auf Regexp passt, wahr liefert.

Scheme-Prozedur: find-files Verzeichnis [Prädikat] [#:stat lstat] [#:directories? #f] [#:fail-on-error? #f] Liefert die

lexikografisch sortierte Liste der Dateien innerhalb Verzeichnis, für die das Prädikat wahr liefert. An Prädikat werden zwei Argumente übergeben: Der absolute Dateiname und der zugehörige Stat-Puffer. Das vorgegebene Prädikat liefert immer wahr. Als Prädikat kann auch ein regulärer Ausdruck benutzt werden; in diesem Fall ist er äquivalent zu (file-name-predicate Prädikat). Mit stat werden Informationen über die Datei ermittelt; wenn dafür lstat benutzt wird, bedeutet das, dass symbolische Verknüpfungen nicht verfolgt werden. Wenn directories? wahr ist, dann werden auch Verzeichnisse aufgezählt. Wenn fail-on-error? wahr ist, dann wird bei einem Fehler eine Ausnahme ausgelöst.

Nun folgen ein paar Beispiele, wobei wir annehmen, dass das aktuelle Verzeichnis der Wurzel des Guix-Quellbaums entspricht.

;; Alle regulären Dateien im aktuellen Verzeichnis auflisten.
(find-files ".")
 ("./.dir-locals.el" "./.gitignore" )

;; Alle .scm-Dateien unter gnu/services auflisten.
(find-files "gnu/services" "\\.scm$")
 ("gnu/services/admin.scm" "gnu/services/audio.scm" )

;; ar-Dateien im aktuellen Verzeichnis auflisten.
(find-files "." (lambda (file stat) (ar-file? file)))
 ("./libformat.a" "./libstore.a" )
Scheme-Prozedur: which Programm

Liefert den vollständigen Dateinamen für das Programm, der in $PATH gesucht wird, oder #f, wenn das Programm nicht gefunden werden konnte.

Scheme-Prozedur: search-input-file Eingaben Name
Scheme-Prozedur: search-input-directory Eingaben Name

Liefert den vollständigen Dateinamen von Name, das in allen Eingaben gesucht wird. search-input-file sucht nach einer regulären Datei, während search-input-directory nach einem Verzeichnis sucht. Wenn der Name nicht vorkommt, wird eine Ausnahme ausgelöst.

Hierbei muss für Eingaben eine assoziative Liste wie inputs oder native-inputs übergeben werden, die für Erstellungsphasen zur Verfügung steht (siehe Erstellungsphasen).

Hier ist ein (vereinfachtes) Beispiel, wie search-input-file in einer Erstellungsphase des wireguard-tools-Pakets benutzt wird:

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

9.7.5 Programme aufrufen

Im Modul finden Sie Prozeduren, die geeignet sind, Prozesse zu erzeugen. Hauptsächlich handelt es sich um praktische Wrapper für Guiles system* (siehe system* in Referenzhandbuch zu GNU Guile).

Scheme-Prozedur: invoke Programm Argumente

Programm mit Argumente aufrufen. Es wird eine &invoke-error-Ausnahme ausgelöst, wenn der Exit-Code ungleich null ist, ansonsten wird #t zurückgeliefert.

Der Vorteil gegenüber system* ist, dass Sie den Rückgabewert nicht zu überprüfen brauchen. So vermeiden Sie umständlichen Code in Shell-Skript-haften Schnipseln etwa in Erstellungsphasen von Paketen.

Scheme-Prozedur: invoke-error? c

Liefert wahr, wenn c ein &invoke-error-Zustand ist.

Scheme-Prozedur: invoke-error-program c
Scheme-Prozedur: invoke-error-arguments c
Scheme-Prozedur: invoke-error-exit-status c
Scheme-Prozedur: invoke-error-term-signal c
Scheme-Prozedur: invoke-error-stop-signal c

Auf bestimmte Felder von c zugreifen, einem &invoke-error-Zustand.

Scheme-Prozedur: report-invoke-error c [Port]

Auf Port (nach Vorgabe der current-error-port) eine Meldung für c, einem &invoke-error-Zustand, menschenlesbar ausgeben.

Normalerweise würden Sie das so benutzen:

(use-modules (srfi srfi-34) ;für 'guard'
             (guix build utils))

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

-| command "date" "--imaginary-option" failed with status 1
Scheme-Prozedur: invoke/quiet Programm Argumente

Programm mit Argumente aufrufen und dabei die Standardausgabe und Standardfehlerausgabe von Programm einfangen. Wenn Programm erfolgreich ausgeführt wird, wird nichts ausgegeben und der unbestimmte Wert *unspecified* zurückgeliefert. Andernfalls wird ein &message-Fehlerzustand ausgelöst, der den Status-Code und die Ausgabe von Programm enthält.

Hier ist ein Beispiel:

(use-modules (srfi srfi-34) ;für 'guard'
             (srfi srfi-35) ;für 'message-condition?'
             (guix build utils))

(guard (c ((message-condition? c)
           (display (condition-message c))))
  (invoke/quiet "date")  ;alles in Ordnung
  (invoke/quiet "date" "--imaginary-option"))

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

    date: Unbekannte Option »--imaginary-option«
    „date --help“ liefert weitere Informationen.

9.7.6 Erstellungsphasen

(guix build utils) enthält auch Werkzeuge, um die von Erstellungssystemen benutzten Erstellungsphasen zu verändern (siehe Erstellungssysteme). Erstellungsphasen werden durch assoziative Listen oder „Alists“ repräsentiert (siehe Association Lists in Referenzhandbuch zu GNU Guile), wo jeder Schlüssel ein Symbol ist, das den Namen der Phase angibt, und der assoziierte Wert eine Prozedur ist (siehe Erstellungsphasen).

Die zum Kern von Guile („Guile core“) gehörenden Prozeduren und das Modul (srfi srfi-1) stellen beide Werkzeuge zum Bearbeiten von Alists zur Verfügung. Das Modul (guix build utils) ergänzt sie um Werkzeuge, die speziell für Erstellungsphasen gedacht sind.

Scheme-Syntax: modify-phases Phasen Klausel

Die Phasen der Reihe nach entsprechend jeder Klausel ändern. Die Klauseln dürfen eine der folgenden Formen haben:

(delete alter-Phasenname)
(replace alter-Phasenname neue-Phase)
(add-before alter-Phasenname neuer-Phasenname neue-Phase)
(add-after alter-Phasenname neuer-Phasenname neue-Phase)

Jeder Phasenname oben ist ein Ausdruck, der zu einem Symbol auswertet, und neue-Phase ist ein Ausdruck, der zu einer Prozedur auswertet.

Folgendes Beispiel stammt aus der Definition des grep-Pakets. Es fügt eine neue Phase namens egrep-und-fgrep-korrigieren hinzu, die auf die install-Phase folgen soll. Diese Phase ist eine Prozedur (lambda* bedeutet, sie ist eine Prozedur ohne eigenen Namen), die ein Schlüsselwort #:outputs bekommt und die restlichen Schlüsselwortargumente ignoriert (siehe Optional Arguments in Referenzhandbuch zu GNU Guile für mehr Informationen zu lambda* und optionalen sowie Schlüsselwort-Argumenten). In der Phase wird substitute* benutzt, um die installierten Skripte egrep und fgrep so zu verändern, dass sie grep anhand seines absoluten Dateinamens aufrufen:

(modify-phases %standard-phases
  (add-after 'install 'egrep-und-fgrep-korrigieren
    ;; 'egrep' und 'fgrep' patchen, damit diese 'grep' über den
    ;; absoluten Dateinamen ausführen, statt es in $PATH zu suchen.
    (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")))))))

In dem Beispiel, das nun folgt, werden Phasen auf zweierlei Art geändert: Die Standard-configure-Phase wird gelöscht, meistens weil das Paket über kein configure-Skript oder etwas Ähnliches verfügt, und die vorgegebene install-Phase wird durch eine ersetzt, in der die zu installierenden ausführbaren Dateien manuell kopiert werden.

(modify-phases %standard-phases
  (delete 'configure)      ;kein 'configure'-Skript
  (replace 'install
    (lambda* (#:key outputs #:allow-other-keys)
      ;; Das Makefile im Paket enthält kein "install"-Ziel,
      ;; also müssen wir es selber machen.
      (let ((bin (string-append (assoc-ref outputs "out")
                                "/bin")))
        (install-file "footswitch" bin)
        (install-file "scythe" bin)))))

9.7.7 Wrapper

Es kommt vor, dass Befehle nur richtig funktionieren, wenn bestimmte Umgebungsvariable festgelegt sind. Meistens geht es dabei um Suchpfade (siehe Suchpfade). Wenn man sie nicht zuweist, finden die Programme vielleicht benötigte Dateien oder andere Befehle nicht oder sie nehmen die „falschen“, je nachdem, in welcher Umgebung man sie ausführt. Einige Beispiele:

Das Ziel einer Paketautorin ist, dass Befehle immer auf gleiche Weise funktionieren statt von externen Einstellungen abhängig zu sein. Eine Möglichkeit, das zu bewerkstelligen, ist, Befehle in ein dünnes Wrapper-Skript einzukleiden, welches diese Umgebungsvariablen festlegt und so dafür sorgt, dass solche Laufzeitabhängigkeiten sicherlich gefunden werden. Der Wrapper würde in den obigen Beispielen also benutzt, um PATH, GUILE_LOAD_PATH oder QT_PLUGIN_PATH festzulegen.

Das Wrappen wird erleichtert durch ein paar Hilfsprozeduren im Modul (guix build utils), mit denen Sie Befehle wrappen können.

Scheme-Prozedur: wrap-program Programm [#:sh sh] [#:rest Variable] Einen Wrapper für Programm

anlegen. Die Liste Variable sollte so aussehen:

'(Variable Trennzeichen Position Liste-von-Verzeichnissen)

Das Trennzeichen ist optional. Wenn Sie keines angeben, wird : genommen.

Zum Beispiel kopiert dieser Aufruf:

(wrap-program "foo"
              '("PATH" ":" = ("/gnu/…/bar/bin"))
              '("CERT_PATH" suffix ("/gnu/…/baz/certs"
                                    "/qux/certs")))

foo nach .foo-real und erzeugt die Datei foo mit folgendem Inhalt:

#!ort/mit/bin/bash
export PATH="/gnu/…/bar/bin"
export CERT_PATH="$CERT_PATH${CERT_PATH:+:}/gnu/…/baz/certs:/qux/certs"
exec -a $0 ort/mit/.foo-real "$@"

Wenn Programm bereits mit wrap-program gewrappt wurde, wird sein bestehender Wrapper um die Definitionen jeder Variable in der Variable-Liste ergänzt. Ansonsten wird ein Wrapper mit sh als Interpretierer angelegt.

Scheme-Prozedur: wrap-script Programm [#:guile guile] [#:rest Variable] Das Skript Programm in

einen Wrapper wickeln, damit Variable vorher festgelegt werden. Das Format für Variable ist genau wie bei der Prozedur wrap-program. Der Unterschied zu wrap-program ist, dass kein getrenntes Shell-Skript erzeugt wird, sondern das Programm selbst abgeändert wird, indem zu Beginn von Programm ein Guile-Skript platziert wird. In der Sprache des Skripts wird das Guile-Skript als Kommentar interpretiert.

Besondere Kommentare zur Kodierung der Datei, wie es sie bei Python geben kann, werden auf der zweiten Zeile neu erzeugt.

Beachten Sie, dass diese Prozedur auf dieselbe Datei nur einmal angewandt werden kann, denn sie auf Guile-Skripts loszulassen wird nicht unterstützt.


Nächste: Suchpfade, Vorige: Erstellungsphasen, Nach oben: Programmierschnittstelle   [Inhalt][Index]