Nächste: Andere Erstellungssysteme, Vorige: Herangehensweisen, Nach oben: Anleitung zum Paketeschreiben [Inhalt][Index]
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 compression) #:use-module (gnu packages pkg-config) #:use-module (gnu packages python) #:use-module (gnu packages ssh) #:use-module (gnu packages tls) #:use-module (gnu packages web)) (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))) ;; Gebündelte Software entfernen wir. (snippet '(delete-file-recursively "deps")))) (build-system cmake-build-system) (outputs '("out" "debug")) (arguments `(#:tests? #true ;Testkatalog ausführen (wie ohnehin vorgegeben) #:configure-flags '("-DUSE_SHA1DC=ON") ;Kollisionserkennung für SHA-1 aktivieren #: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"))))) ;; Ausführliche Testausgaben einschalten. (replace 'check (lambda* (#:key tests? #:allow-other-keys) (when tests? (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 ;; Weil diese zwei Bibliotheken unter 'Requires.private' in libgit2.pc stehen. (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.") ;; Wir schreiben als Kommentar, dass es eigentlich die GPL2 mit Ausnahmen ist: ;; 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.
git-fetch
-Methodegit-fetch
-MethodeAnders 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.
„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.
Es gibt 3 verschiedene Arten von Eingaben. Kurz gefasst:
Sie werden zum Erstellen gebraucht, aber nicht zur Laufzeit – wenn Sie ein Paket als Substitut installieren, werden diese Eingaben nirgendwo installiert.
Sie werden in den Store installiert, aber nicht in das Profil, und sie stehen beim Erstellen zur Verfügung.
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.
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.
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:
(add-before Phase neue-Phase Prozedur)
:
Prozedur unter dem Namen neue-Phase vor Phase ausführen.
(add-after Phase neue-Phase Prozedur)
: Genauso,
aber danach.
(replace Phase Prozedur)
.
(delete Phase)
.
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.
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.
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.
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: Andere Erstellungssysteme, Vorige: Herangehensweisen, Nach oben: Anleitung zum Paketeschreiben [Inhalt][Index]