Nächste: , Vorige: , Nach oben: Anleitung zum Paketeschreiben   [Inhalt][Index]


2.1.3 Erweitertes Beispiel

Einfacher als obiges Hallo-Welt-Beispiel wird es nicht. Pakete können auch komplexer als das sein und Guix eignet sich für fortgeschrittenere Szenarien. Schauen wir uns ein anderes, umfangreicheres Paket an (leicht modifiziert gegenüber Guix’ Quellcode):

(define-module (gnu packages version-control)
  #:use-module ((guix licenses) #:prefix license:)
  #:use-module (guix utils)
  #:use-module (guix packages)
  #:use-module (guix git-download)
  #:use-module (guix build-system cmake)
  #:use-module (gnu packages ssh)
  #:use-module (gnu packages web)
  #:use-module (gnu packages pkg-config)
  #:use-module (gnu packages python)
  #:use-module (gnu packages compression)
  #:use-module (gnu packages tls))

(define-public my-libgit2
  (let ((commit "e98d0a37c93574d2c6107bf7f31140b548c6a7bf")
        (revision "1"))
    (package
      (name "my-libgit2")
      (version (git-version "0.26.6" revision commit))
      (source (origin
                (method git-fetch)
                (uri (git-reference
                      (url "https://github.com/libgit2/libgit2/")
                      (commit commit)))
                (file-name (git-file-name name version))
                (sha256
                 (base32
                  "17pjvprmdrx4h6bb1hhc98w9qi6ki7yl57f090n9kbhswxqfs7s3"))
                (patches (search-patches "libgit2-mtime-0.patch"))
                (modules '((guix build utils)))
                ;; Remove bundled software.
                (snippet '(delete-file-recursively "deps"))))
      (build-system cmake-build-system)
      (outputs '("out" "debug"))
      (arguments
       `(#:tests? #true                         ; Run the test suite (this is the default)
         #:configure-flags '("-DUSE_SHA1DC=ON") ; SHA-1 collision detection
         #:phases
         (modify-phases %standard-phases
           (add-after 'unpack 'fix-hardcoded-paths
             (lambda _
               (substitute* "tests/repo/init.c"
                 (("#!/bin/sh") (string-append "#!" (which "sh"))))
               (substitute* "tests/clar/fs.h"
                 (("/bin/cp") (which "cp"))
                 (("/bin/rm") (which "rm")))))
           ;; Run checks more verbosely.
           (replace 'check
             (lambda _ (invoke "./libgit2_clar" "-v" "-Q")))
           (add-after 'unpack 'make-files-writable-for-tests
             (lambda _ (for-each make-file-writable (find-files "." ".*")))))))
      (inputs
       (list libssh2 http-parser python-wrapper))
      (native-inputs
       (list pkg-config))
      (propagated-inputs
       ;; These two libraries are in 'Requires.private' in libgit2.pc.
       (list openssl zlib))
      (home-page "https://libgit2.github.com/")
      (synopsis "Library providing Git core methods")
      (description
       "Libgit2 is a portable, pure C implementation of the Git core methods
provided as a re-entrant linkable library with a solid API, allowing you to
write native speed custom Git applications in any language with bindings.")
      ;; GPLv2 with linking exception
      (license license:gpl2))))

(In solchen Fällen, wo Sie nur ein paar wenige Felder einer Paketdefinition abändern wollen, wäre es wirklich besser, wenn Sie Vererbung einsetzen würden, statt alles abzuschreiben. Siehe unten.)

Reden wir über diese Felder im Detail.

2.1.3.1 git-fetch-Methode

Anders als die url-fetch-Methode erwartet git-fetch eine git-reference, welche ein Git-Repository und einen Commit entgegennimmt. Der Commit kann eine beliebige Art von Git-Referenz sein, z.B. ein Tag. Wenn die version also mit einem Tag versehen ist, kann sie einfach benutzt werden. Manchmal ist dem Tag ein Präfix v vorangestellt. In diesem Fall würden Sie (commit (string-append "v" version)) schreiben.

Um sicherzustellen, dass der Quellcode aus dem Git-Repository in einem Verzeichnis mit nachvollziehbarem Namen landet, schreiben wir (file-name (git-file-name name version)).

Mit der Prozedur git-version kann die Version beim Paketieren eines bestimmten Commits eines Programms entsprechend den Richtlinien für Beiträge zu Guix (siehe Versionsnummern in Referenzhandbuch zu GNU Guix) abgeleitet werden.

Sie fragen, woher man den darin angegebenen sha256-Hash bekommt? Indem Sie guix hash auf einem Checkout des gewünschten Commits aufrufen, ungefähr so:

git clone https://github.com/libgit2/libgit2/
cd libgit2
git checkout v0.26.6
guix hash -rx .

guix hash -rx berechnet einen SHA256-Hash des gesamten Verzeichnisses, abgesehen vom .git-Unterverzeichnis (siehe Aufruf von guix hash in Referenzhandbuch zu GNU Guix).

In Zukunft wird guix download diese Schritte hoffentlich für Sie erledigen können, genau wie es das für normales Herunterladen macht.

2.1.3.2 Schnipsel

„Snippets“, deutsch Schnipsel, sind mit z.B. quote-Zeichen maskierte, also nicht ausgewertete, Stücke Scheme-Code, mit denen der Quellcode gepatcht wird. Sie sind eine guixige Alternative zu traditionellen .patch-Dateien. Wegen der Maskierung werden sie erst dann ausgewertet, wenn sie an den Guix-Daemon zum Erstellen übergeben werden. Es kann so viele Schnipsel geben wie nötig.

In Schnipseln könnten zusätzliche Guile-Module benötigt werden. Diese können importiert werden, indem man sie im Feld modules angibt.

2.1.3.3 Eingaben

Es gibt 3 verschiedene Arten von Eingaben. Kurz gefasst:

native-inputs

Sie werden zum Erstellen gebraucht, aber nicht zur Laufzeit — wenn Sie ein Paket als Substitut installieren, werden diese Eingaben nirgendwo installiert.

inputs

Sie werden in den Store installiert, aber nicht in das Profil, und sie stehen beim Erstellen zur Verfügung.

propagated-inputs

Sie werden sowohl in den Store als auch ins Profil installiert und sind auch beim Erstellen verfügbar.

Siehe „package“-Referenz in Referenzhandbuch zu GNU Guix für mehr Details.

Der Unterschied zwischen den verschiedenen Eingaben ist wichtig: Wenn eine Abhängigkeit als input statt als propagated-input ausreicht, dann sollte sie auch so eingeordnet werden, sonst „verschmutzt“ sie das Profil des Benutzers ohne guten Grund.

Wenn eine Nutzerin beispielsweise ein grafisches Programm installiert, das von einem Befehlszeilenwerkzeug abhängt, sie sich aber nur für den grafischen Teil interessiert, dann sollten wir sie nicht zur Installation des Befehlszeilenwerkzeugs in ihr Benutzerprofil zwingen. Um die Abhängigkeit sollte sich das Paket kümmern, nicht seine Benutzerin. Mit Inputs können wir Abhängigkeiten verwenden, wo sie gebraucht werden, ohne Nutzer zu belästigen, indem wir ausführbare Dateien (oder Bibliotheken) in deren Profil installieren.

Das Gleiche gilt für native-inputs: Wenn das Programm einmal installiert ist, können Abhängigkeiten zur Erstellungszeit gefahrlos dem Müllsammler anvertraut werden. Sie sind auch besser, wenn ein Substitut verfügbar ist, so dass nur die inputs und propagated-inputs heruntergeladen werden; native-inputs braucht niemand, der das Paket aus einem Substitut heraus installiert.

Anmerkung: Vielleicht bemerken Sie hier und da Schnipsel, deren Paketeingaben recht anders geschrieben wurden, etwa so:

;; Der „alte Stil“ von Eingaben.
(inputs
 `(("libssh2" ,libssh2)
   ("http-parser" ,http-parser)
   ("python" ,python-wrapper)))

Früher hatte man Eingaben in diesem „alten Stil“ geschrieben, wo jede Eingabe ausdrücklich mit einer Bezeichnung (als Zeichenkette) assoziiert wurde. Er wird immer noch unterstützt, aber wir empfehlen, dass Sie stattdessen im weiter oben gezeigten Stil schreiben. Siehe „package“-Referenz in Referenzhandbuch zu GNU Guix für mehr Informationen.

2.1.3.4 Ausgaben

Genau wie ein Paket mehrere Eingaben haben kann, kann es auch mehrere Ausgaben haben.

Jede Ausgabe entspricht einem anderen Verzeichnis im Store.

Die Benutzerin kann sich entscheiden, welche Ausgabe sie installieren will; so spart sie Platz auf dem Datenträger und verschmutzt ihr Benutzerprofil nicht mit unerwünschten ausführbaren Dateien oder Bibliotheken.

Nach Ausgaben zu trennen ist optional. Wenn Sie kein outputs-Feld schreiben, heißt die standardmäßige und einzige Ausgabe (also das ganze Paket) schlicht "out".

Typische Namen für getrennte Ausgaben sind debug und doc.

Es wird empfohlen, getrennte Ausgaben nur dann anzubieten, wenn Sie gezeigt haben, dass es sich lohnt, d.h. wenn die Ausgabengröße signifikant ist (vergleichen Sie sie mittels guix size) oder das Paket modular aufgebaut ist.

2.1.3.5 Argumente ans Erstellungssystem

arguments ist eine Liste aus Schlüsselwort-Wert-Paaren (eine „keyword-value list“), mit denen der Erstellungsprozess konfiguriert wird.

Das einfachste Argument #:tests? kann man benutzen, um den Testkatalog bei der Erstellung des Pakets nicht zu prüfen. Das braucht man meistens dann, wenn das Paket überhaupt keinen Testkatalog hat. Wir empfehlen sehr, den Testkatalog zu benutzen, wenn es einen gibt.

Ein anderes häufiges Argument ist :make-flags, was eine Liste an den make-Aufruf anzuhängender Befehlszeilenargumente festlegt, so wie Sie sie auf der Befehlszeile angeben würden. Zum Beispiel werden die folgenden :make-flags

#:make-flags (list (string-append "prefix=" (assoc-ref %outputs "out"))
                   "CC=gcc")

übersetzt zu

$ make CC=gcc prefix=/gnu/store/...-<out>

Dadurch wird als C-Compiler gcc verwendet und als prefix-Variable (das Installationsverzeichnis in der Sprechweise von Make) wird (assoc-ref %outputs "out") verwendet, also eine globale Variable der Erstellungsschicht, die auf das Zielverzeichnis im Store verweist (so etwas wie /gnu/store/…-my-libgit2-20180408).

Auf gleiche Art kann man auch die Befehlszeilenoptionen für configure festlegen:

#:configure-flags '("-DUSE_SHA1DC=ON")

Die Variable %build-inputs wird auch in diesem Sichtbarkeitsbereich erzeugt. Es handelt sich um eine assoziative Liste, die von den Namen der Eingaben auf ihre Verzeichnisse im Store abbildet.

Das phases-Schlüsselwort listet der Reihe nach die vom Erstellungssystem durchgeführten Schritte auf. Zu den üblichen Phasen gehören unpack, configure, build, install und check. Um mehr über diese Phasen zu lernen, müssen Sie sich die Definition des zugehörigen Erstellungssystems in ‘$GUIX_CHECKOUT/guix/build/gnu-build-system.scm’ anschauen:

(define %standard-phases
  ;; Standard build phases, as a list of symbol/procedure pairs.
  (let-syntax ((phases (syntax-rules ()
                         ((_ p ...) `((p . ,p) ...)))))
    (phases set-SOURCE-DATE-EPOCH set-paths install-locale unpack
            bootstrap
            patch-usr-bin-file
            patch-source-shebangs configure patch-generated-file-shebangs
            build check install
            patch-shebangs strip
            validate-runpath
            validate-documentation-location
            delete-info-dir-file
            patch-dot-desktop-files
            install-license-files
            reset-gzip-timestamps
            compress-documentation)))

Alternativ auf einer REPL:

(add-to-load-path "/path/to/guix/checkout")
,use (guix build gnu-build-system)
(map first %standard-phases)
 (set-SOURCE-DATE-EPOCH set-paths install-locale unpack bootstrap patch-usr-bin-file patch-source-shebangs configure patch-generated-file-shebangs build check install patch-shebangs strip validate-runpath validate-documentation-location delete-info-dir-file patch-dot-desktop-files install-license-files reset-gzip-timestamps compress-documentation)

Wenn Sie mehr darüber wissen wollen, was in diesen Phasen passiert, schauen Sie in den jeweiligen Prozeduren.

Beispielsweise sieht momentan, als dies hier geschrieben wurde, die Definition von unpack für das GNU-Erstellungssystem so aus:

(define* (unpack #:key source #:allow-other-keys)
  "Unpack SOURCE in the working directory, and change directory within the
source.  When SOURCE is a directory, copy it in a sub-directory of the current
working directory."
  (if (file-is-directory? source)
      (begin
        (mkdir "source")
        (chdir "source")

        ;; Preserve timestamps (set to the Epoch) on the copied tree so that
        ;; things work deterministically.
        (copy-recursively source "."
                          #:keep-mtime? #true))
      (begin
        (if (string-suffix? ".zip" source)
            (invoke "unzip" source)
            (invoke "tar" "xvf" source))
        (chdir (first-subdirectory "."))))
  #true)

Beachten Sie den Aufruf von chdir: Damit wird das Arbeitsverzeichnis zu demjenigen gewechselt, wohin die Quelldateien entpackt wurden. In jeder Phase nach unpack dient also das Verzeichnis mit den Quelldateien als Arbeitsverzeichnis. Deswegen können wir direkt mit den Quelldateien arbeiten, zumindest solange keine spätere Phase das Arbeitsverzeichnis woandershin wechselt.

Die Liste der %standard-phases des Erstellungssystems ändern wir mit Hilfe des modify-phases-Makros über eine Liste von Änderungen. Sie kann folgende Formen haben:

Die Prozedur unterstützt die Schlüsselwortargumente inputs und outputs. Jede Eingabe (ob sie native, propagated oder nichts davon ist) und jedes Ausgabeverzeichnis ist in diesen Variablen mit dem jeweiligen Namen assoziiert. (assoc-ref outputs "out") ist also das Store-Verzeichnis der Hauptausgabe des Pakets. Eine Phasenprozedur kann so aussehen:

(lambda* (#:key inputs outputs #:allow-other-keys)
  (let ((bash-directory (assoc-ref inputs "bash"))
        (output-directory (assoc-ref outputs "out"))
        (doc-directory (assoc-ref outputs "doc")))
    ;; …
    #true))

Die Prozedur muss bei Erfolg #true zurückliefern. Auf den Rückgabewert des letzten Ausdrucks, mit dem die Phase angepasst wurde, kann man sich nicht verlassen, weil es keine Garantie gibt, dass der Rückgabewert #true sein wird. Deswegen schreiben wir dahinter #true, damit bei erfolgreicher Ausführung der richtige Wert geliefert wird.

2.1.3.6 Code-Staging

Aufmerksame Leser könnten die Syntax mit quasiquote und Komma im Argumentefeld bemerkt haben. Tatsächlich sollte der Erstellungscode in der Paketdeklaration nicht auf Client-Seite ausgeführt werden, sondern erst, wenn er an den Guix-Daemon übergeben worden ist. Der Mechanismus, über den Code zwischen zwei laufenden Prozessen weitergegeben wird, nennen wir Code-Staging.

2.1.3.7 Hilfsfunktionen

Beim Anpassen der phases müssen wir oft Code schreiben, der analog zu den äquivalenten Systemaufrufen funktioniert (make, mkdir, cp, etc.), welche in regulären „Unix-artigen“ Installationen oft benutzt werden.

Manche, wie chmod, sind Teil von Guile. Siehe das Referenzhandbuch zu Guile für eine vollständige Liste.

Guix stellt zusätzliche Hilfsfunktionen zur Verfügung, die bei der Paketverwaltung besonders praktisch sind.

Manche dieser Funktionalitäten finden Sie in ‘$GUIX_CHECKOUT/guix/guix/build/utils.scm’. Die meisten spiegeln das Verhalten traditioneller Unix-Systembefehle wider:

which

Das Gleiche wie der ‘which’-Systembefehl.

find-files

Wie der ‘find’ Systembefehl.

mkdir-p

Wie ‘mkdir -p’, das Elternverzeichnisse erzeugt, wenn nötig.

install-file

Ähnlich wie ‘install’ beim Installieren einer Datei in ein (nicht unbedingt existierendes) Verzeichnis. Guile kennt copy-file, das wie ‘cp’ funktioniert.

copy-recursively

Wie ‘cp -r’.

delete-file-recursively

Wie ‘rm -rf’.

invoke

Eine ausführbare Datei ausführen. Man sollte es benutzen und nicht system*.

with-directory-excursion

Den Rumpf in einem anderen Arbeitsverzeichnis ausführen und danach wieder in das vorherige Arbeitsverzeichnis wechseln.

substitute*

Eine „sed-artige“ Funktion.

Siehe Werkzeuge zur Erstellung in Referenzhandbuch zu GNU Guix für mehr Informationen zu diesen Werkzeugen.

2.1.3.8 Modulpräfix

Die Lizenz in unserem letzten Beispiel braucht ein Präfix. Der Grund liegt darin, wie das license-Modul importiert worden ist, nämlich #:use-module ((guix licenses) #:prefix license:). Der Importmechanismus von Guile-Modulen (siehe Using Guile Modules in Referenzhandbuch zu Guile) gibt Benutzern die volle Kontrolle über Namensräume. Man braucht sie, um Konflikte zu lösen, z.B. zwischen der ‘zlib’-Variablen aus ‘licenses.scm’ (dieser Wert repräsentiert eine Softwarelizenz) und der ‘zlib’-Variablen aus ‘compression.scm’ (ein Wert, der für ein Paket steht).


Nächste: , Vorige: , Nach oben: Anleitung zum Paketeschreiben   [Inhalt][Index]