Lucas F. Costa Menselijke gedachten over exacte wetenschappen
Hoi, iedereen! Zoals jullie misschien hebben gemerkt ben ik erg geinteresseerd in Go en sinds ik verliefd ben geworden op deze taal wil ik er vaker over schrijven.
Als je Go nog niet kent denk ik echt dat je het moet leren. Go is geweldig!
Go is een relatief jonge taal en als het aankomt op vendoring en dependency management is het nog niet echt volwassen. De Go gemeenschap heeft echter een aantal praktijken aangenomen en gereedschappen gemaakt om aan deze vraag te voldoen. Dit is waar we het vandaag over gaan hebben.
Uitleg over het Go Ecosysteem
Go heeft als doel om eenvoud te gebruiken om complexe taken te volbrengen en daarmee de taal leuker en productiever te maken. Vanaf het begin van de taal heeft het Go team ervoor gekozen om string literals te gebruiken om de import syntax willekeurig te maken om te beschrijven wat er geïmporteerd werd.
Dit is wat er in hun eigen docs staat:
Een expliciet doel voor Go vanaf het begin was om Go code te kunnen bouwen met alleen de informatie die in de broncode zelf staat, zonder een makefile te hoeven schrijven of een van de vele moderne vervangers voor makefiles. Als Go een configuratiebestand nodig zou hebben om uit te leggen hoe je je programma moet bouwen, dan zou Go gefaald hebben.
Om dit doel te bereiken, geeft het Go team de voorkeur aan conventies boven configuraties. Dit zijn de conventies die zijn aangenomen als het gaat om pakketbeheer:
- Het importpad is altijd op een bekende manier afgeleid van de URL van de broncode. Daarom ziet uw import eruit als:
github.com/author/pkgname
. Door dit te doen krijgen we ook een automatisch beheerde namespace, omdat deze online diensten al unieke paden voor elk pakket beheren. - De plaats waar we pakketten opslaan in ons bestandssysteem is op een bekende manier afgeleid van het import pad. Als u zoekt naar waar
go
de pakketten opslaat die u downloadt, zult u het kunnen relateren aan de URL waarvandaan het werd gedownload. - Elke directory in een broncodeboom komt overeen met een enkel pakket. Dit maakt het gemakkelijker voor u om broncode voor bepaalde pakketten te vinden en helpt u uw code op een gestandaardiseerde manier te organiseren. Door de mappenstructuur aan de pakketstructuur te koppelen, hoeven we ons niet over beide tegelijk zorgen te maken, omdat bestandssysteemgereedschappen pakketbeheergereedschappen worden.
- Het pakket wordt gebouwd met alleen informatie uit de broncode. Dit betekent geen
makefiles
en geenconfiguration
en bevrijdt u van specifieke (en waarschijnlijk ingewikkelde) toolchains.
Nu we dat gezegd hebben, is het gemakkelijk te begrijpen waarom het go get
commando werkt zoals het doet.
Gebruik van Go Tools’ Commando’s
Voordat we in deze commando’s duiken, laten we eerst begrijpen wat de “$GOPATH
” is.
De $GOPATH
is een omgevingsvariabele die wijst naar een directory die kan worden beschouwd als een werkruimtedirectory. Het zal uw broncodes, gecompileerde pakketten en runnable binaries bevatten.
go get
Het go get
commando is eenvoudig en werkt bijna als een git clone
. Het argument dat je moet doorgeven aan go get
is een eenvoudige URL van een archief. In dit voorbeeld, zullen we het commando gebruiken: go get https://github.com/golang/oauth2
.
Wanneer dit commando wordt uitgevoerd, zal go
eenvoudigweg het pakket van de door u opgegeven URL ophalen en het in uw $GOPATH
directory plaatsen. Als u naar uw $GOPATH
directory navigeert, zult u nu zien dat u een folder heeft in src/github.com/golang/oauth2
die de bronbestanden van het package bevat en een gecompileerd package in de pkg
directory (samen met zijn dependencies).
Wanneer u go get
uitvoert, moet u in gedachten houden dat alle gedownloade pakketten in een directory worden geplaatst die overeenkomt met de URL die u gebruikte om het te downloaden.
Het heeft ook een hoop andere vlaggen beschikbaar, zoals -u
dat een pakket bijwerkt of -insecure
dat u toestaat om pakketten te downloaden met onveilige schema’s zoals HTTP. U kunt meer lezen over “geavanceerd” gebruik van het go get
commando op deze link.
Ook, volgens go help gopath
, werkt het go get
commando ook submodules bij van de pakketten die u binnenhaalt.
go install
Wanneer go install
in de bron-directory van een pakket wordt uitgevoerd, zal het de laatste versie van dat pakket en al zijn afhankelijkheden in de pkg
directory compileren.
go build
Go build is verantwoordelijk voor het compileren van de pakketten en hun afhankelijkheden, maar het installeert niet de resultaten!
Vendoring
Zoals u misschien heeft gemerkt aan de manier waarop Go zijn afhankelijkheden opslaat, heeft deze benadering van afhankelijkhedenbeheer een paar problemen.
Op de eerste plaats zijn we niet in staat om te bepalen welke versie van een pakket we nodig hebben, tenzij het in een compleet andere repository wordt gehost, anders haalt go get
altijd de laatste versie van een pakket op. Dit betekent dat als iemand een brekende verandering aan zijn pakket doet en het niet in een andere repo plaatst, u en uw team in de problemen komen omdat u uiteindelijk verschillende versies van hetzelfde pakket kunt ophalen en dit zal dan leiden tot “werkt op mijn machine” problemen.
Een ander groot probleem is dat, als gevolg van het feit dat go get
pakketten installeert in de root van uw src
directory, u niet in staat zult zijn om verschillende versies van uw afhankelijkheden te hebben voor elk van uw projecten. Dit betekent dat u geen projecten kunt hebben die afhankelijk zijn van verschillende versies van hetzelfde pakket, u zult of de ene of de andere versie tegelijk moeten hebben.
Om deze problemen te verzachten, heeft het Go team sinds Go 1.5 een vendoring
functie geïntroduceerd. Met deze functie kunt u de code van uw afhankelijkheid voor een pakket in zijn eigen directory zetten, zodat het altijd dezelfde versies krijgt voor alle builds.
Stel dat u een project hebt met de naam awesome-project
dat afhankelijk is van popular-package
. Om te garanderen dat iedereen in je team dezelfde versie van popular-package
zal gebruiken, kun je de broncode ervan in $GOPATH/src/awesome-project/vendor/popular-package
zetten. Dit zal werken omdat Go
dan zal proberen het pad van uw afhankelijkheden op te lossen vanuit de vendor
directory van zijn eigen map (als het tenminste één .go
bestand heeft) in plaats van $GOPATH/src
. Dit zal uw builds ook deterministisch (reproduceerbaar) maken, aangezien ze altijd dezelfde versie van popular-package
.
Het is ook belangrijk om op te merken dat het go get
commando de gedownloade pakketten niet automatisch in de vendor
map zal zetten. Dit is een taak voor vendoring tools.
Wanneer u vendoring gebruikt, kunt u dezelfde import paden gebruiken als wanneer u dat niet zou doen, omdat Go
altijd zal proberen om afhankelijkheden te vinden in de dichtstbijzijnde vendor
directory. Het is niet nodig om vendor/
voor te schrijven aan een van uw import paden.
Om volledig te kunnen begrijpen hoe vendoring werkt, moeten we het algoritme begrijpen dat Go gebruikt om import paden op te lossen, en dat is het volgende:
- Kijk voor de import in de lokale
vendor
directory (indien aanwezig) - Als we dit pakket niet in de lokale
vendor
directory kunnen vinden, gaan we naar de bovenliggende map en proberen het daar in devendor
directory te vinden (als die er is) - Als we dit pakket niet in de lokale
vendor
directory kunnen vinden, gaan we naar de bovenliggende map en proberen het daar in devendor
directory te vinden (als - We herhalen stap 2 totdat we $GOPATH/src
- We zoeken naar het geïmporteerde pakket in $GOROOT
- Als we dit pakket niet kunnen vinden in $GOROOT zoeken we het in onze $GOPATH/src map
Basically, betekent dit dat elk pakket zijn eigen afhankelijkheden kan hebben, opgelost in zijn eigen vendor directory. Als u bijvoorbeeld afhankelijk bent van package x
en package y
, en package x
is ook afhankelijk van package y
maar heeft er een andere versie van nodig, dan kunt u uw code nog steeds zonder problemen uitvoeren, omdat x
naar y
zal zoeken in zijn eigen vendor
map, terwijl uw package naar y
zal zoeken in de vendor map van uw project.
Nu, een praktisch voorbeeld. Laten we zeggen dat je deze mapstructuur hebt:
$GOPATH src/ github.com/user/package-one/ one.go myproject main.go vendor/ github.com/user/package-one/ one.go client/ client.go vendor/ github.com/user/package-one/ server/ server.go vendor/ github.com/user/package-one/ one.go
Als we github.com/user/package-one
importeren vanuit main.go zouden importeren, zou het oplossen naar de versie van dit pakket in de vendor
map in dezelfde map:
$GOPATH src/ github.com/user/package-one/ one.go myproject main.go <-- Importing package-one from here vendor/ github.com/user/package-one/ <-- resolves to here one.go client/ client.go vendor/ github.com/user/package-one/ server/ server.go vendor/ github.com/user/package-one/ one.go
Nu als we hetzelfde pakket importeren in client.go
zal het ook dit pakket oplossen naar de vendor
map in zijn eigen map:
$GOPATH src/ github.com/user/package-one/ one.go myproject main.go vendor/ github.com/user/package-one/ one.go client/ client.go <-- Importing package-one from here vendor/ github.com/user/package-one/ <-- resolves to here server/ server.go vendor/ github.com/user/package-one/ one.go
Hetzelfde gebeurt als we dit pakket importeren in het server.go
bestand:
$GOPATH src/ github.com/user/package-one/ one.go myproject main.go vendor/ github.com/user/package-one/ one.go client/ client.go vendor/ github.com/user/package-one/ server/ server.go <-- Importing package-one from here vendor/ github.com/user/package-one/ <-- resolves to here one.go
Uitleg over afhankelijkheidsbeheer
Nu we al deze dingen hebben geleerd over hoe Go
omgaat met import en vendoring, wordt het tijd dat we het eindelijk gaan hebben over afhankelijkheidsbeheer.
De tool die ik momenteel gebruik om afhankelijkheden in mijn eigen projecten te beheren heet godep
. Het schijnt erg populair te zijn en het werkt prima voor mij, dus ik raad je ten zeerste aan om dat ook te gebruiken.
Het is gebouwd rond de manier waarop vendoring
werkt. Alles wat je moet doen om het te gaan gebruiken is het commando godep save
gebruiken wanneer je je afhankelijkheden wilt opslaan in je vendor
map.
Wanneer je godep save
uitvoert, zal godep
een lijst van je huidige afhankelijkheden opslaan in Godeps/Godeps.json
en vervolgens hun broncode kopiëren naar de vendor
map. Het is ook belangrijk op te merken dat u deze afhankelijkheden op uw machine geïnstalleerd moet hebben om godep
in staat te stellen ze te kopiëren.
Nu kunt u de vendor
map en zijn inhoud vastleggen om ervoor te zorgen dat iedereen dezelfde versies van dezelfde pakketten zal hebben wanneer u uw pakket uitvoert.
Een ander interessant commando is godep restore
, dat de versies die in uw Godeps/Godeps.json
bestand staan, zal installeren in uw $GOPATH
.
Om een dependency te updaten, hoeft u deze alleen maar te updaten met go get -u
(zoals we eerder hebben besproken) en dan godep save
uit te voeren, zodat godep
het Godeps/Godeps.json
bestand zal updaten en de benodigde bestanden naar de vendor
directory zal kopiëren.
Een paar gedachten over de manier waarop Go omgaat met afhankelijkheden
Aan het eind van deze blog post, wil ik ook graag mijn eigen mening geven over de manier waarop Go omgaat met afhankelijkheden.
Ik denk dat Go’s keuze om externe repositories te gebruiken om de namespaces van pakketten af te handelen geweldig was omdat het de hele package resolutie veel eenvoudiger maakt door de bestandssysteem concepten te verbinden met de namespace concepten. Dit zorgt er ook voor dat het hele ecosysteem onafhankelijk werkt, omdat we nu een gedecentraliseerde manier hebben om pakketten op te halen.
Het gedecentraliseerde pakketbeheer heeft echter een prijs, namelijk dat we niet in staat zijn om alle knooppunten te controleren die deel uitmaken van dit “netwerk”. Als iemand besluit om zijn pakket uit github
te halen, bijvoorbeeld, dan kunnen honderden, duizenden of zelfs miljoenen builds plotseling beginnen te falen. Naamsveranderingen kunnen ook hetzelfde effect hebben.
Gezien Go’s hoofddoelen is dit volkomen logisch en een volkomen eerlijke afweging. Go draait om conventie in plaats van configuratie en er is geen eenvoudiger manier om met afhankelijkheden om te gaan dan de manier waarop het nu doet.
Natuurlijk kunnen er een paar verbeteringen worden aangebracht, zoals het gebruik van git tags om specifieke versies van pakketten op te halen en gebruikers toe te staan om te specificeren welke versies hun pakketten zullen gebruiken. Het zou ook cool zijn om in staat te zijn om deze afhankelijkheden op te halen in plaats van ze uit te pluizen op broncontrole. Dit zou ons in staat stellen om vuile diffs te vermijden die alleen wijzigingen aan code in de vendor
directory bevatten en het hele repository schoner en lichter te maken.
Get in touch!
Als je twijfels of gedachten hebt of het niet eens bent met iets wat ik heb geschreven, deel het dan met me in de reacties hieronder of bereik me op @thewizardlucas op twitter. Ik zou graag horen wat je te zeggen hebt en eventuele correcties doen als ik fouten heb gemaakt.
Tot slot, maar niet in het minst, zorg ervoor dat je een kijkje neemt op deze geweldige talk van Wisdom Omuya op GopherCon 2016 waarin hij uitlegt hoe Go Vendoring werkt en ook wijst op een aantal details over de innerlijke werking ervan.
Dank voor het lezen van dit!