Einführung in Go-Module

Die bevorstehende Version 1.11 der Programmiersprache Go bietet experimentelle Unterstützung für Module - ein neues Abhängigkeitsmanagementsystem für Go. (Anmerkung Übersetzung: Veröffentlichung erfolgte )


Kürzlich habe ich bereits einen kleinen Beitrag dazu geschrieben . Seitdem hat sich etwas leicht geändert, und wir sind der Veröffentlichung näher gekommen. Daher scheint es mir, dass die Zeit für einen neuen Artikel gekommen ist - lassen Sie uns mehr Übung hinzufügen.


Also, hier ist, was wir tun werden: Erstellen Sie ein neues Paket und machen Sie dann ein paar Releases, um zu sehen, wie es funktioniert.


Modulerstellung


Erstellen Sie zunächst unser Paket. Nennen wir es testmod. Wichtiges Detail: Das Paketverzeichnis sollte außerhalb von $GOPATH , da darin die $GOPATH standardmäßig deaktiviert ist . Go-Module sind der erste Schritt in Richtung einer vollständigen Aufgabe von $GOPATH in der Zukunft.


 $ mkdir testmod $ cd testmod 

Unser Paket ist ganz einfach:


 package testmod import "fmt" // Hi returns a friendly greeting func Hi(name string) string { return fmt.Sprintf("Hi, %s", name) } 

Das Paket ist fertig, aber noch kein Modul . Lass es uns reparieren.


 $ go mod init github.com/robteix/testmod go: creating new go.mod: module github.com/robteix/testmod 

Wir haben eine neue Datei namens go.mod im Paketverzeichnis mit folgendem Inhalt:


 module github.com/robteix/testmod 

Ein bisschen, aber genau das macht unser Paket zu einem Modul .


Jetzt können wir diesen Code in das Repository verschieben:


 $ git init $ git add * $ git commit -am "First commit" $ git push -u origin master 

Bisher würde sich jeder bewerben go get unser Paket nutzen möchte:


 $ go get github.com/robteix/testmod 

Und dieser Befehl würde den neuesten Code aus dem Hauptzweig bringen. Diese Option funktioniert immer noch, aber es wäre besser, wenn wir es nicht mehr tun, denn jetzt "gibt es einen besseren Weg". Das direkte Entnehmen von Code aus dem Hauptzweig ist in der Tat gefährlich, da wir nie sicher wissen, dass die Autoren des Pakets keine Änderungen vorgenommen haben, die unseren Code „beschädigen“. Um dieses Problem zu lösen, wurden Go-Module erfunden.


Ein kleiner Exkurs über Versionierungsmodule


Go-Module sind versioniert, und es gibt einige Besonderheiten einzelner Versionen. Sie müssen sich mit den Konzepten vertraut machen, die der semantischen Versionierung zugrunde liegen.


Darüber hinaus verwendet Go bei der Suche nach Versionen Repository-Tags, und einige Versionen unterscheiden sich von den anderen: Beispielsweise müssen Versionen 2 und höher einen anderen Importpfad haben als die Versionen 0 und 1 (wir werden darauf zurückkommen).


Standardmäßig lädt Go die neueste Version herunter , für die ein Tag im Repository verfügbar ist.
Dies ist eine wichtige Funktion, da sie bei der Arbeit mit dem Hauptzweig verwendet werden kann.


Für uns ist es jetzt wichtig, dass wir beim Erstellen der Version unseres Pakets ein Etikett mit der Version in das Repository einfügen.


Lass es uns tun.


Machen Sie Ihre erste Veröffentlichung


Unser Paket ist fertig und wir können es auf der ganzen Welt "ausrollen". Wir tun dies mit versionierten Etiketten. Die Versionsnummer sei 1.0.0:


 $ git tag v1.0.0 $ git push --tags 

Diese Befehle erstellen ein Tag in meinem Github-Repository, das das aktuelle Commit als Release 1.0.0 kennzeichnet.


Go besteht nicht darauf, aber es ist eine gute Idee, einen zusätzlichen neuen Zweig ("v1") zu erstellen, an den wir Patches senden können.


 $ git checkout -b v1 $ git push -u origin v1 

Jetzt können wir in der Hauptniederlassung arbeiten, ohne uns Sorgen machen zu müssen, dass wir unsere Version brechen können.


Mit unserem Modul


Verwenden wir das erstellte Modul. Wir werden ein einfaches Programm schreiben, das unser neues Paket importiert:


 package main import ( "fmt" "github.com/robteix/testmod" ) func main() { fmt.Println(testmod.Hi("roberto")) } 

Bis jetzt haben Sie go get github.com/robteix/testmod , um das Paket herunterzuladen, aber mit Modulen wird es interessanter. Zunächst müssen wir die Modulunterstützung in unserem neuen Programm aktivieren.


 $ go mod init mod 

Wie Sie wahrscheinlich erwartet haben, wurde basierend auf dem, was Sie zuvor gelesen haben, eine neue go.mod Datei im Verzeichnis mit dem Namen des darin enthaltenen Moduls go.mod :


 module mod 

Die Situation wird noch interessanter, wenn wir versuchen, unser Programm zusammenzustellen:


 $ go build go: finding github.com/robteix/testmod v1.0.0 go: downloading github.com/robteix/testmod v1.0.0 

Wie Sie sehen können, hat der Befehl go das von unserem Programm importierte Paket automatisch gefunden und heruntergeladen.
Wenn wir unsere go.mod Datei überprüfen, werden wir go.mod , dass sich etwas geändert hat:


 module mod require github.com/robteix/testmod v1.0.0 

Und wir haben eine weitere neue Datei namens go.sum , die die Hashes der Pakete enthält, um die richtige Version und die richtigen Dateien zu überprüfen.


 github.com/robteix/testmod v1.0.0 h1:9EdH0EArQ/rkpss9Tj8gUnwx3w5p0jkzJrd5tRAhxnA= github.com/robteix/testmod v1.0.0/go.mod h1:UVhi5McON9ZLc5kl5iN2bTXlL6ylcxE9VInV71RrlO8= 

Erstellen einer Version zur Fehlerbehebung


Nehmen wir an, wir haben ein Problem in unserem Paket gefunden: Die Begrüßung enthält keine Interpunktion!
Einige Leute werden wütend sein, weil unser freundlicher Gruß nicht mehr so ​​freundlich ist.
Lassen Sie uns das beheben und eine neue Version veröffentlichen:


 // Hi returns a friendly greeting func Hi(name string) string { - return fmt.Sprintf("Hi, %s", name) + return fmt.Sprintf("Hi, %s!", name) } 

Wir haben diese Änderung direkt in der v1 Verzweigung vorgenommen, da sie nichts mit dem zu tun hat, was wir als nächstes in der v2 Verzweigung tun werden. Im wirklichen Leben sollten Sie diese Änderungen jedoch möglicherweise am master vornehmen und sie dann auf v1 . In jedem Fall sollte sich das v1 Zweig v1 und wir müssen dies als neue Version markieren.


 $ git commit -m "Emphasize our friendliness" testmod.go $ git tag v1.0.1 $ git push --tags origin v1 

Module aktualisieren


Standardmäßig aktualisiert Go Module nicht ohne Anforderung. "Und das ist gut", da wir alle Vorhersehbarkeit in unseren Builds wünschen. Wenn die Go-Module bei jeder Veröffentlichung einer neuen Version automatisch aktualisiert würden, würden wir zu den "dunklen Zeiten vor Go1.11" zurückkehren. Aber nein, wir müssen Go mitteilen , dass die Module für uns aktualisiert werden sollen.


Und wir werden es mit Hilfe unseres alten Freundes tun - go get :


  • Führen Sie go get -u , um die letzte Neben- oder Patch- Version zu verwenden (d. h. der Befehl wird von 1.0.0 auf beispielsweise 1.0.1 oder 1.1.0 aktualisiert, wenn eine solche Version verfügbar ist).


  • Führen Sie go get -u=patch , um die neueste Patch-Version zu verwenden (d. h. das Paket wird auf 1.0.1 aktualisiert, jedoch nicht auf 1.1.0).


  • Führen Sie go get package@version , um ein Upgrade auf eine bestimmte Version github.com/robteix/testmod@v1.0.1 (z. B. github.com/robteix/testmod@v1.0.1 ).



In dieser Liste gibt es keine Möglichkeit, auf die neueste Hauptversion zu aktualisieren. Dafür gibt es einen guten Grund, wie wir gleich sehen werden.


Da unser Programm Version 1.0.0 unseres Pakets verwendet hat und wir gerade Version 1.0.1 erstellt haben, aktualisiert uns jeder der folgenden Befehle auf 1.0.1:


 $ go get -u $ go get -u=patch $ go get github.com/robteix/testmod@v1.0.1 

Nach dem Start (sagen wir go get -u ) hat sich unser go.mod geändert:


 module mod require github.com/robteix/testmod v1.0.1 

Hauptversion


Gemäß der Spezifikation der semantischen Versionierung unterscheidet sich die Hauptversion von der Nebenversion. Hauptversionen können die Abwärtskompatibilität beeinträchtigen. Aus Sicht der Go-Module ist die Hauptversion ein völlig anderes Paket .


Es mag zunächst wild klingen, aber es macht Sinn: Zwei Versionen der Bibliothek, die nicht miteinander kompatibel sind, sind zwei verschiedene Bibliotheken.


Nehmen wir eine wesentliche Änderung in unserem Paket vor. Angenommen, im Laufe der Zeit wurde uns klar, dass unsere API zu einfach und zu begrenzt für die Benutzerfälle unserer Benutzer ist. Daher müssen wir die Hi() Funktion ändern, um die Begrüßungssprache als Parameter zu akzeptieren:


 package testmod import ( "errors" "fmt" ) // Hi returns a friendly greeting in language lang func Hi(name, lang string) (string, error) { switch lang { case "en": return fmt.Sprintf("Hi, %s!", name), nil case "pt": return fmt.Sprintf("Oi, %s!", name), nil case "es": return fmt.Sprintf("¡Hola, %s!", name), nil case "fr": return fmt.Sprintf("Bonjour, %s!", name), nil default: return "", errors.New("unknown language") } } 

Bestehende Programme, die unsere API verwenden, werden unterbrochen, weil sie a) die Sprache nicht als Parameter übergeben und b) keine Fehlerrückgabe erwarten. Unsere neue API ist nicht mehr mit Version 1.x kompatibel. Treffen Sie also Version 2.0.0.


Ich habe bereits erwähnt, dass einige Versionen Funktionen haben, und jetzt ist dies der Fall.
Version 2 oder höher sollte den Importpfad ändern. Nun sind dies verschiedene Bibliotheken.


Dazu fügen wir dem Namen unseres Moduls einen neuen versionierten Pfad hinzu.


 module github.com/robteix/testmod/v2 

Alles andere ist das gleiche: Drücken Sie, setzen Sie ein Etikett, das v2.0.0 ist (und lassen Sie optional einen Zweig v2 durchnässen)


 $ git commit testmod.go -m "Change Hi to allow multilang" $ git checkout -b v2 # optional but recommended $ echo "module github.com/robteix/testmod/v2" > go.mod $ git commit go.mod -m "Bump version to v2" $ git tag v2.0.0 $ git push --tags origin v2 # or master if we don't have a branch 

Hauptversions-Update


Obwohl wir eine neue inkompatible Version unserer Bibliothek veröffentlicht haben, sind die vorhandenen Programme nicht kaputt gegangen , da sie weiterhin Version 1.0.1 verwenden.
go get -u wird die Version 2.0.0 nicht herunterladen.


Aber irgendwann möchte ich als Bibliotheksbenutzer möglicherweise ein Upgrade auf Version 2.0.0 durchführen, da ich beispielsweise einer der Benutzer bin, die Unterstützung für mehrere Sprachen benötigen.


Zum Aktualisieren muss ich mein Programm entsprechend ändern:


 package main import ( "fmt" "github.com/robteix/testmod/v2" ) func main() { g, err := testmod.Hi("Roberto", "pt") if err != nil { panic(err) } fmt.Println(g) } 

Wenn ich jetzt go build starte, wird es "beendet" und die Version 2.0.0 für mich heruntergeladen. Beachten Sie, dass Go, obwohl der Importpfad jetzt mit "v2" endet, Go immer noch mit seinem richtigen Namen ("testmod") auf das Modul verweist.


Wie gesagt, die Hauptversion ist in jeder Hinsicht ein anderes Paket. Diese beiden Go-Module sind in keiner Weise miteinander verbunden. Dies bedeutet, dass wir zwei inkompatible Versionen in einer Binärdatei haben können:


 package main import ( "fmt" "github.com/robteix/testmod" testmodML "github.com/robteix/testmod/v2" ) func main() { fmt.Println(testmod.Hi("Roberto")) g, err := testmodML.Hi("Roberto", "pt") if err != nil { panic(err) } fmt.Println(g) } 

Dadurch wird das häufig auftretende Problem beim Abhängigkeitsmanagement beseitigt, wenn Abhängigkeiten von verschiedenen Versionen derselben Bibliothek abhängen.



Kehren wir zur vorherigen Version zurück, die nur testmod 2.0.0 verwendet. Wenn wir go.mod den Inhalt von go.mod überprüfen, werden wir etwas bemerken:


 module mod require github.com/robteix/testmod v1.0.1 require github.com/robteix/testmod/v2 v2.0.0 

Standardmäßig entfernt Go Abhängigkeiten nicht aus go.mod bis Sie danach fragen. Wenn Sie Abhängigkeiten haben, die nicht mehr benötigt werden, und diese bereinigen möchten, können Sie den neuen Befehl tidy :


 $ go mod tidy 

Jetzt haben wir nur noch die Abhängigkeiten, die wir wirklich verwenden.


Verkauf


Go-Module ignorieren standardmäßig den vendor/ Verzeichnis. Die Idee ist, den Verkauf nach und nach loszuwerden 1 . Wenn wir jedoch weiterhin die "getrennten" Abhängigkeiten zu unserer Versionskontrolle hinzufügen möchten, können wir dies tun:


 $ go mod vendor 

Das Team erstellt den vendor/ Verzeichnis im Stammverzeichnis unseres Projekts, der den Quellcode aller Abhängigkeiten enthält.


Beim standardmäßigen go build wird der Inhalt dieses Verzeichnisses jedoch weiterhin ignoriert. Wenn Sie Abhängigkeiten vom vendor/ Verzeichnis erfassen möchten, müssen Sie explizit danach fragen.


 $ go build -mod vendor 

Ich go build davon aus, dass viele Entwickler, die Vending verwenden möchten, wie gewohnt auf ihren Computern bauen und den -mod vendor auf ihrem CI verwenden werden.


Wiederum entfernen sich Go-Module von der Idee des Verkaufs von Proxys für Module für diejenigen, die nicht direkt von vorgelagerten Versionskontrolldiensten abhängig sein möchten.


Es gibt Möglichkeiten, um sicherzustellen, dass go Netzwerk nicht verfügbar ist (z. B. mit GOPROXY=off ). Dies ist jedoch das Thema des nächsten Artikels.


Fazit


Der Artikel mag jemandem kompliziert erscheinen, aber das liegt daran, dass ich versucht habe, viel auf einmal zu erklären. Die Realität ist, dass Go-Module heutzutage im Allgemeinen einfach sind - wir importieren das Paket wie üblich in unseren Code und das go Team erledigt den Rest für uns. Abhängigkeiten werden beim Zusammenbau automatisch geladen.


Die Module machen auch $GOPATH , was ein Stolperstein für neue Go-Entwickler war, die Probleme hatten zu verstehen, warum sie etwas in ein bestimmtes Verzeichnis stellen sollten.


Der Verkauf (inoffiziell) wurde zugunsten der Verwendung eines Proxys abgelehnt. 1
Ich kann einen separaten Artikel über Proxys für Go-Module verfassen.


Anmerkungen:


1 Ich denke, dies ist ein zu lauter Ausdruck, und einige haben möglicherweise den Eindruck, dass der Verkauf gerade entfernt wird. Es ist nicht so. Der Verkauf funktioniert immer noch, wenn auch etwas anders als zuvor. Anscheinend besteht der Wunsch, den Verkauf durch etwas Besseres zu ersetzen, zum Beispiel einen Proxy (keine Tatsache). Bisher ist dies einfach das Streben nach einer besseren Lösung. Der Verkauf wird erst beendet, wenn ein guter Ersatz gefunden wurde (falls vorhanden).

Source: https://habr.com/ru/post/de421411/


All Articles