Eigene Skripte über Homebrew verteilen
(justin.searls.co)- Homebrew ist ein Paketmanager für macOS, mit dem sich CLI-Tools einfach installieren und verwalten lassen, sodass Entwickler ihre Systemumgebung mit häufig genutzten Werkzeugen effizient einrichten können
- Dieser Leitfaden erklärt, wie man mit Homebrew persönliche CLI-Skripte verteilt, und zeigt, wie sich die Wartung durch GitHub-Integration und automatisierte Workflows vereinfachen lässt
- Der Distributionsprozess verläuft über CLI erstellen → GitHub-Release → Tap anlegen → Formula schreiben und aktualisieren und ermöglicht am Ende die Installation allein mit den Befehlen
brew tapundbrew install - Wer das Begriffssystem und die Best Practices von Homebrew versteht, kann eine stabile Distribution mit besserer Reproduzierbarkeit und Supply-Chain-Sicherheit aufsetzen
- Der Prozess lässt sich mit GitHub Actions automatisieren; ist er einmal eingerichtet, wird auch die spätere Distribution anderer CLIs sehr viel einfacher
Hintergrund und Motivation
- Homebrew ist der bevorzugte Paketmanager für die Installation von CLI-Tools und wird von vielen Entwicklern genutzt
- Eigene CLIs werden jedoch oft über npm oder RubyGems verteilt, weshalb sich der Distributionsweg über Homebrew zunächst ungewohnt anfühlen kann
- Da das offizielle Core-Repository von Homebrew selbst entwickelte Tools nur ungern aufnimmt, verteilen normale Entwickler sie über einen separaten Tap und eine eigene Formula
- Dieser Leitfaden basiert auf Erfahrungen mit der Distribution einer einfachen Ruby-basierten CLI
Begriffserklärung
- Homebrew verwendet eigene Begriffe im Stil des Bierbrauens; wer sie versteht, kann die Systemstruktur leichter erfassen
- Eine Formula ist eine Paketdefinitionsdatei, die Anweisungen zur Installation von Quellcode oder Binärdateien enthält
- Ein Tap ist ein Git-Repository für Formulas, mit dem benutzer- oder organisationsspezifische Pakete verwaltet werden
- Ein Cask ist ein Installationsmanifest für GUI-Apps oder große Binärdateien; ähnlich wie eine Formula, aber für vorgebaute Dateien
- Eine Bottle ist ein vorgebautes Binärpaket, das statt eines Builds aus dem Quellcode einfach kopiert wird und so die Installation beschleunigt
- Der Cellar ist das Verzeichnis, in dem installierte Formulas liegen, zum Beispiel unter
/opt/homebrew/Cellar - Ein Keg ist das Installationsinstanz-Verzeichnis einer bestimmten Formula und liegt versionsweise innerhalb des Cellar
Überblick
- Das Homebrew-Core-Repository nimmt nischige oder persönliche Einreichungen nicht an, daher müssen Nutzer ein separates Tap-Repository anlegen, um ihre CLI zu verteilen
- 1. CLI erstellen, auf GitHub hochladen und ein Tag-Release veröffentlichen
- 2. Mit
brew tap-newein Tap anlegen und zu GitHub pushen - 3. Mit
brew createeine Formula erstellen (einschließlich Tarball-URL und SHA256) - 4. Bei jeder neuen Version die Formula aktualisieren, damit Nutzer die CLI einfach mit
brew installinstallieren können
- Nach Abschluss der Distribution können Nutzer die CLI mit zwei Befehlen installieren:
brew tap your_github_handle/tapundbrew install your_cool_cli- Dieser Leitfaden überspringt die CLI-Entwicklung und konzentriert sich auf das Anlegen des Tap, das Erstellen der Formula und deren Aktualisierung
- Als Beispiel dient die CLI
imsg, die aus einer iMessage-Datenbank ein interaktives Web-Archiv erstellt
Tap anlegen
- Dazu folgt man dem Leitfaden zum Erstellen eines Tap von Homebrew und ersetzt dabei GitHub-Benutzernamen oder Organisationsnamen entsprechend
- Um künftig alle CLI-Tools in einem Tap zu bündeln, wird der Name
homebrew-tapempfohlen; das Präfixhomebrewwird von der CLI speziell behandelt und das Präfixtapist gängige Konvention
- Um künftig alle CLI-Tools in einem Tap zu bündeln, wird der Name
- Befehl zum Anlegen des Tap:
brew tap-new searlsco/homebrew-tap- Dadurch wird ein Scaffold unter
/opt/homebrew/Library/Taps/searlsco/homebrew-taperzeugt - Anschließend erstellt man das entsprechende Repository auf GitHub und pusht den generierten Inhalt:
cd /opt/homebrew/Library/Taps/searlsco/homebrew-tap,git remote add origin git@github.com:searlsco/homebrew-tap.git,git push -u origin main
- Dadurch wird ein Scaffold unter
- Sobald man Eigentümer des Tap ist, können andere Nutzer das Repository mit
brew tap searlsco/tapklonen und unter/opt/homebrew/Library/Tapsablegen- Anfangs ist dort noch nichts Nützliches enthalten, aber das Grundverhalten lässt sich bereits überprüfen
Formula erstellen
- Homebrew kann GitHub-Repositories direkt referenzieren, empfiehlt aber die Verwendung von versionierten Tarballs und Checksummen, um Reproduzierbarkeit und Open-Source-Supply-Chain-Sicherheit zu stärken
- GitHub hostet beim Pushen von Tags Tarballs unter vorhersagbaren URLs; im
imsg-Repository entsteht nachgit tag v0.0.5undgit push --tagszum Beispielhttps://github.com/searlsco/imsg/archive/refs/tags/v0.0.5.tar.gz
- GitHub hostet beim Pushen von Tags Tarballs unter vorhersagbaren URLs; im
- Befehl zum Erstellen der Formula:
brew create https://github.com/searlsco/imsg/archive/refs/tags/v0.0.5.tar.gz --tap searlsco/homebrew-tap --set-name imsg --ruby- Das Flag
--tapgibt das benutzerdefinierte Tap an und legt die Formula unter/opt/homebrew/Library/Taps/searlsco/homebrew-tap/Formulaab --set-name imsgsetzt den Namen der Formula explizit; dieser sollte eindeutig gewählt werden, um Namenskonflikte zu vermeiden (zum Beispiel mit vorhandenem TLDR oder der Standard-CLI)--rubyist ein Template-Preset für Ruby-CLIs und eines von mehreren Optionen, die die Anpassung vereinfachen
- Das Flag
- Die erzeugte Formula funktioniert anfangs möglicherweise noch nicht, daher kann man sie mit Hilfe eines LLM überarbeiten:
brew install --verbose imsgausführen, den Fehler in ChatGPT eingeben und die Formula wiederholt anpassen- Die finale Datei Formula/imsg.rb kann als Ausgangspunkt für die Distribution einer Ruby-CLI kopiert werden
- Wer statt eines sprachspezifischen Paketmanagers über Homebrew verteilt, ermöglicht Nutzern reibungslose Upgrades auch dann, wenn sich die Implementierungssprache ändert
Wichtige Highlights der Formula
- Alle Formulas werden in Ruby geschrieben, da viele beliebte Entwicklerwerkzeuge vor JavaScript oder AI auf Ruby basierten
- Mit der Methode
headlässt sich ein Git-Repository angeben, auch wenn der tatsächliche Effekt unklar ist - Das Hinzufügen von
livechecklohnt sich, weil es Versionsupdates der Formula erleichtert - Ein Test für die Ausführung der Binärdatei lässt sich einfach über die Prüfung der Hilfsausgabe implementieren; man sollte sich von den generierten Kommentaren nicht einschüchtern lassen
- Mit
brew style searlsco/taplassen sich Stilfehler prüfen - Das Standard-
uses_from_macos "ruby"des--ruby-Templates verwendet Version 2.6.10 (ein Release von vor COVID, seit 3 Jahren EOL), daher wird stattdessendepends_on "ruby@3"als Abhängigkeit von der aktuellen Ruby-Formula empfohlen
- Mit der Methode
- Wenn die Formula zufriedenstellend ist, wird sie mit
git pushlive verteilt; Nutzer können sie dann mitbrew tap searlsco/tapundbrew install imsginstallieren
Formula bei jedem CLI-Release aktualisieren
- Die
urlund dersha256-Hash am Anfang der Formula müssen bei jedem Release manuell aktualisiert werden, was mühsam ist; selbst das Pushen eines Tags oder das Erstellen eines GitHub-Releases kann auf Dauer lästig werden- Mit dem Homebrew-Befehl
bump-formula-proder per GitHub Action lässt sich zwar ein PR erzeugen, aber der Prozess mit Fork und PR ist unnötig komplex - Wenn man Eigentümer des Tap ist, ist ein einfacher Weg mit direkten Commits auf den Main-Branch wünschenswerter
- Mit dem Homebrew-Befehl
- Um das zu vermeiden, wird empfohlen, dem Formula-Repository einen GitHub-Workflow hinzuzufügen, der das Tap beim Release automatisch aktualisiert
- Das Workflow-Beispiel kann direkt kopiert und verwendet werden
- Erforderliche Einrichtung: Beim Erstellen eines GitHub Personal Access Token (PAT) dem Repository
homebrew-tapdie BerechtigungContent→Writegeben und das Token in den Secrets des Formula-Repositories unterHOMEBREW_TAP_TOKENspeichern - Tap und Formula werden über Umgebungsvariablen festgelegt (zum Beispiel in Zeile 13–15)
- Für Updates wird das GitHub-Bot-Konto empfohlen:
GH_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com,GH_NAME: github-actions[bot]
- Nach dem Erstellen eines Releases genügt
git push --tags, und innerhalb weniger Sekunden erfolgt die automatische Aktualisierung; Nutzer können dann mitbrew updateundbrew upgrade imsgupgraden
Das Beste daran
- Der Prozess ist zwar komplex, aber sobald das Tap eingerichtet und ein Formula-Beispiel fertig ist, wird die Verteilung zusätzlicher CLIs fast trivial
- Neue Formulas lassen sich bequem in wenigen Minuten veröffentlichen
- Der offizielle Homebrew-Prozess ist etwas kompliziert, wird durch Automatisierung aber deutlich angenehmer
- So lässt sich der Aufwand zwischen Tool-Release und Distribution verringern und auch auf CLIs in verschiedenen Sprachen ausweiten
- Ob tatsächlich noch eine weitere Formula veröffentlicht wird, ist offen, aber allein die Möglichkeit fühlt sich lohnend an
2 Kommentare
Es gibt eine
--no-fork-Option, mit der man direkt auf den Branch pushen und mergen kann; außerdem bietet sie eine automatische Update-Funktion.Hacker-News-Kommentare
Die Namenskonventionen von Homebrew wirken manchmal etwas verwirrend, aber insgesamt merke ich immer wieder, wie nützlich das Tool wirklich ist
Außerdem hatte ich nicht erwartet, dass es so einfach ist, mit einem eigenen Tap seine Tools zu verteilen
Im Vergleich zu sprachspezifischen Paketmanagern wie z. B. uv würde mich interessieren, was daran besser ist
Vor allem würde ich gern wissen, ob es für Leute außerhalb eines bestimmten Ökosystems einfacher ist, also ob es unter dem Gesichtspunkt der Allgemeingültigkeit einen Vorteil hat
Danke dafür. Andere Tools, die Paket-Registries verwenden, erfordern in der Regel die Erstellung eines Kontos, Zwei-Faktor-Authentifizierung, Signaturprozesse usw.
Bei Homebrew dienen die GitHub-Nutzungsbedingungen (ToS) als Vertrauensgrundlage, wodurch insgesamt vieles deutlich einfacher wird
Das Homebrew-Team kann dank dieses Ansatzes viel Komplexität vermeiden
Wenn man von Python-Paketen ausgeht, ist der Versuch, wie bei uv alles in einem Schritt zu paketieren, in der Praxis schwierig
Deshalb verwendet man normalerweise den Ansatz, nur festgelegte Abhängigkeiten in einer venv-Umgebung zu installieren
Als konkretes Beispiel kann man sich diese Formula ansehen
Was uv betrifft: Ich hatte versucht, mit offiziellen Tools (
brew update-python-resources,homebrew-pypi-poet) private Pakete zu unterstützen, aber das hat nicht richtig funktioniertDeshalb habe ich selbst uvbrew erstellt, um die Ressourcenerzeugung zu unterstützen
Es gibt auch eine offizielle Dokumentation als Referenz zum Schreiben von Python-Formulas in Homebrew
Für Go-Entwickler würde ich das Tool Goreleaser empfehlen
Damit lässt sich die Verteilung von Binärdateien im eigenen Tap sehr einfach umsetzen (im offiziellen Core ist diese Vorgehensweise verboten)
Es ist für das Projektmanagement in den jeweiligen Sprachen sehr nützlich
Persönlich finde ich es idealer, Updates direkt auf der Tap-Seite zu verwalten
Das ähnelt dem üblichen Vorgehen, bei dem im Upstream aktualisiert wird
Wenn man sich diesen Workflow ansieht, kann man auch Formulas/Casks, die man nicht besitzt, leicht aktualisieren
Mit dem Befehl
brew bumpkann man alles scannen, automatisch PRs erstellen und mitbrew test-botsogar die Tests automatisierenEin echtes Beispiel für einen PR findet sich hier
Normalerweise habe ich wegen der verfügbaren GitHub-Actions-Zeit gezögert, aber da es für Open Source kostenlos ist, scheint das eine gute Nutzung zu sein
Ich habe auch selbst einen automatischen Workflow zum Bumpen von Versionen für meinen eigenen Homebrew-Tap geschrieben: homebrew-bump-revision
Ich nutze das erfolgreich in mehreren privaten Projekten
Ich war zu faul, es selbst auszuprobieren, aber es ist ein gutes Tool
Im Ruby-Rogues-Podcast gab es eine Episode mit verschiedenen Tipps zur Verteilung von CLI-Tools über Homebrew
Unter diesem Episodenlink kann man noch mehr dazu hören
Ich habe etwas Interessantes beim Paketieren von Python-Tools festgestellt
Bei einigen Python-Paketen entstehen während des Build-Prozesses Abhängigkeitszyklen, sodass sie mit Homebrew nicht kompatibel sind
Bei pip gibt es dieses Problem nicht, weil es Binär-Releases herunterlädt, Homebrew baut jedoch alle Abhängigkeiten selbst, weshalb der Vorgang viel länger dauert
Deshalb kann selbst der Build einer "bottle" für ein mittelgroßes Python-Projekt mehr als eine Stunde dauern
Seit ich für die Systemverwaltung angefangen habe, nix zu verwenden, habe ich es kein einziges Mal bereut
Der einzige Wermutstropfen ist, dass ich wegen Multiplayer-Spielen in einem Punkt immer noch auf Windows angewiesen bin