Lucas F. Costa Mänskliga tankar om exakta vetenskaper

jan 16, 2022
admin

Hej, allihop! Som ni kanske har märkt är jag väldigt intresserad av Go och eftersom jag blivit förälskad i det här språket vill jag skriva om det oftare.

Om du inte kan Go ännu tycker jag verkligen att du borde lära dig det. Go är fantastiskt!

Go är ett relativt ungt språk och när det gäller vendoring och beroendehantering är det fortfarande inte riktigt moget än. Go-communityt har dock antagit vissa metoder och skapat verktyg för att tillgodose denna efterfrågan. Detta är vad vi kommer att prata om idag.

Understanding The Go Ecosystem

Go syftar till att använda enkelhet för att uppnå komplexa uppgifter och därför göra språket roligare och mer produktivt. Sedan språkets början valde Go-teamet att de skulle använda stränglitteraler för att göra importsyntaxen godtycklig för att beskriva vad som importeras.

Det här är vad som står skrivet i deras egen dokumentation:

Ett uttalat mål för Go från början var att kunna bygga Go-kod med hjälp av endast den information som finns i själva källkoden, och att man inte behövde skriva en makefile eller någon av de många moderna ersättningarna för makefiles. Om Go hade behövt en konfigurationsfil för att förklara hur ditt program ska byggas skulle Go ha misslyckats.

För att uppnå detta mål föredrar Go-teamet konventioner framför konfigurationer. Dessa är de konventioner som antagits när det gäller pakethantering:

  1. Importvägen härleds alltid på ett känt sätt från källkodens URL. Detta är anledningen till att dina importer ser ut som: github.com/author/pkgname. Genom att göra detta får vi också ett automatiskt hanterat namnområde, eftersom dessa onlinetjänster redan hanterar unika sökvägar för varje paket.
  2. Den plats där vi lagrar paketen i vårt filsystem härleds på ett känt sätt från importvägen. Om du söker efter var go lagrar paketen som du laddar ner kommer du att kunna relatera det till webbadressen som det laddades ner från.
  3. Varje katalog i ett källkodsträd motsvarar ett enskilt paket. Detta gör det lättare för dig att hitta källkod för vissa paket och hjälper dig att organisera din kod på ett standardiserat sätt. Genom att binda mappstrukturen till paketstrukturen behöver vi inte oroa oss för båda samtidigt, eftersom filsystemsverktyg blir pakethanteringsverktyg.
  4. Paketet byggs endast med hjälp av information från källkoden. Detta innebär inga makefiles och inga configuration och befriar dig från att behöva anta specifika (och förmodligen komplicerade) verktygskedjor.

När vi har sagt detta är det lätt att förstå varför kommandot go get fungerar som det gör.

Förstå Go Tools’ Commands

Innan vi går in på dessa kommandon ska vi först förstå vad ”$GOPATH” är.

$GOPATH är en miljövariabel som pekar på en katalog som kan betraktas som en arbetsplatskatalog. Den kommer att innehålla dina källkoder, kompilerade paket och körbara binärer.

go get

Kommandot go get är enkelt och fungerar nästan som ett git clone. Argumentet du måste skicka till go get är en enkel URL till ett arkiv. I det här exemplet kommer vi att använda kommandot: go get https://github.com/golang/oauth2.

När du kör det här kommandot hämtar go helt enkelt paketet från URL:n som du angett och lägger det i din $GOPATH-katalog. Om du navigerar till din mapp $GOPATH kommer du nu att se att du har en mapp i src/github.com/golang/oauth2 som innehåller paketets källfiler och ett kompilerat paket i katalogen pkg (tillsammans med dess beroenden).

När du kör go get bör du ha i åtanke att alla hämtade paket kommer att placeras i en katalog som motsvarar den URL som du använde för att hämta paketet.

Den har också ett gäng andra flaggor tillgängliga, till exempel -u som uppdaterar ett paket eller -insecure som gör det möjligt att hämta paket med hjälp av osäkra scheman som HTTP. Du kan läsa mer om ”avancerad” användning av go get-kommandot på den här länken.

Enligt go help gopath uppdaterar go get-kommandot även undermoduler till de paket du hämtar.

go install

När du kör go install i ett pakets källkatalog kommer det att kompilera den senaste versionen av paketet och alla dess beroenden i katalogen pkg.

go build

Go build ansvarar för att kompilera paketen och deras beroenden, men det installerar inte resultaten!

Understanding Vendoring

Som du kanske har märkt av det sätt som Go sparar sina beroenden har detta sätt att hantera beroenden några problem.

För det första kan vi inte avgöra vilken version av ett paket vi behöver om det inte finns i ett helt annat arkiv, annars kommer go get alltid att hämta den senaste versionen av ett paket. Detta innebär att om någon gör en brytande ändring i sitt paket och inte lägger det i en annan repo kommer du och ditt team att hamna i trubbel eftersom det kan sluta med att ni hämtar olika versioner av samma paket och detta kommer sedan att leda till ”works on my machine”-problem.

Ett annat stort problem är att, på grund av det faktum att go get installerar paket till roten av din src-katalog, kommer du inte att kunna ha olika versioner av dina beroenden för vart och ett av dina projekt. Detta innebär att du inte kan ha projekt som är beroende av olika versioner av samma paket, du måste antingen ha den ena eller den andra versionen i taget.

För att mildra dessa problem har Go-teamet sedan Go 1.5 infört en vendoring-funktion. Denna funktion gör det möjligt att placera din beroendes kod för ett paket i en egen katalog så att den alltid kan få samma versioner för alla byggningar.

Säg att du har ett projekt som heter awesome-project som är beroende av popular-package. För att garantera att alla i ditt team kommer att använda samma version av popular-package kan du lägga dess källkod i $GOPATH/src/awesome-project/vendor/popular-package. Detta kommer att fungera eftersom Go då kommer att försöka lösa dina beroendens sökväg med utgångspunkt i den egna mappens vendor-katalog (om den har minst en .go-fil) i stället för $GOPATH/src. Detta kommer också att göra dina byggen deterministiska (reproducerbara) eftersom de alltid kommer att använda samma version av popular-package.

Det är också viktigt att notera att kommandot go get inte kommer att placera de nedladdade paketen i mappen vendor automatiskt. Detta är ett jobb för vendoring-verktyg.

När du använder vendoring kommer du att kunna använda samma importvägar som om du inte gjorde det, eftersom Go alltid kommer att försöka hitta beroenden i den närmaste vendor-katalogen. Det finns ingen anledning att sätta vendor/ före någon av dina importvägar.

För att fullt ut kunna förstå hur vendoring fungerar måste vi förstå den algoritm som Go använder för att lösa importsökvägar, som är följande:

  1. Leta efter importen i den lokala vendor-katalogen (om sådan finns)
  2. Om vi inte kan hitta paketet i den lokala vendor-katalogen går vi upp till den överordnade mappen och försöker hitta det i vendor-katalogen där (om (om det finns)
  3. Vi upprepar steg 2 tills vi når $GOPATH/src
  4. Vi letar efter det importerade paketet i $GOROOT
  5. Om vi inte hittar paketet i $GOROOT letar vi efter det i vår mapp $GOPATH/src

Grundläggande, innebär detta att varje paket kan ha sina egna beroenden som löses upp i sin egen leverantörskatalog. Om du till exempel är beroende av paket x och paket y, och paket x också är beroende av paket y men behöver en annan version av det, kommer du fortfarande att kunna köra din kod utan problem, eftersom x kommer att leta efter y i sin egen mapp vendor medan ditt paket kommer att leta efter y i projektets leverantörsmapp.

Nu kommer ett praktiskt exempel. Låt oss säga att du har den här mappstrukturen:

$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

Om vi importerade github.com/user/package-one inifrån main.go skulle det lösa upp till versionen av detta paket i katalogen vendor i samma mapp:

$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

Om vi nu importerar samma paket i client.go kommer det också att lösa upp detta paket till katalogen vendor i sin egen katalog:

$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

Det samma händer när vi importerar detta paket på filen server.go:

$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

Förståelse av beroendehantering

Nu när vi har lärt oss alla dessa saker om hur Go hanterar import och försäljning är det dags att äntligen tala om beroendehantering.

Det verktyg jag för närvarande använder för att hantera beroenden i mina egna projekt heter godep. Det verkar vara väldigt populärt och det fungerar bra för mig, därför rekommenderar jag starkt att du också använder det.

Det är byggt på samma sätt som vendoring fungerar. Allt du behöver göra för att börja använda den är att använda kommandot godep save när du vill spara dina beroenden till din vendor-mapp.

När du kör godep save kommer godep att spara en lista över dina aktuella beroenden i Godeps/Godeps.json och sedan kopiera deras källkod till vendor-mappen. Det är också viktigt att notera att du måste ha dessa beroenden installerade på din maskin för att godep ska kunna kopiera dem.

Nu kan du bekräfta mappen vendor och dess innehåll för att försäkra dig om att alla har samma versioner av samma paket när de kör ditt paket.

Ett annat intressant kommando är godep restore, som kommer att installera de versioner som anges i din Godeps/Godeps.json-fil till din $GOPATH.

För att uppdatera ett beroende är allt du behöver göra att uppdatera det med hjälp av go get -u (som vi har talat om tidigare) och sedan köra godep save för att godep ska uppdatera Godeps/Godeps.json-filen och kopiera de nödvändiga filerna till vendor-katalogen.

För några tankar om hur Go hanterar beroenden

I slutet av det här blogginlägget vill jag också lägga till min egen åsikt om hur Go hanterar beroenden.

Jag tycker att Go:s val att använda externa repositories för att hantera paketens namnrymder var bra eftersom det gör hela paketupplösningen mycket enklare genom att filsystemkonceptet förenas med namnrymderkonceptet. Det gör också att hela ekosystemet fungerar självständigt eftersom vi nu har ett decentraliserat sätt att hämta paket.

Den decentraliserade pakethanteringen har dock en kostnad, nämligen att man inte kan kontrollera alla noder som ingår i detta ”nätverk”. Om någon bestämmer sig för att ta bort sitt paket från github, till exempel, kan hundratals, tusentals eller till och med miljontals byggnationer börja misslyckas helt plötsligt. Namnändringar kan också få samma effekt.

Med tanke på Go:s huvudmål är detta helt logiskt och en helt rättvis kompromiss. Go handlar om konventioner istället för konfiguration och det finns inget enklare sätt att hantera beroenden än det sätt som det gör för närvarande.

Självklart kan några förbättringar göras, t.ex. genom att använda Git-taggar för att hämta specifika versioner av paket och låta användarna ange vilka versioner som deras paket ska använda. Det skulle också vara häftigt att kunna hämta dessa beroenden i stället för att kontrollera dem i källkontrollen. Detta skulle göra det möjligt för oss att undvika smutsiga diffs som endast innehåller ändringar av koden i vendor-katalogen och göra hela förrådet renare och mer lättviktigt.

Ta kontakt!

Om du har några tvivel, tankar eller om du inte håller med om något som jag har skrivit, dela gärna med dig av det till mig i kommentarerna nedan eller nå mig på @thewizardlucas på twitter. Jag vill gärna höra vad du har att säga och göra eventuella korrigeringar om jag gjort några misstag.

Sist men inte minst, se till att ta en titt på detta fantastiska föredrag av Wisdom Omuya på GopherCon 2016 där han förklarar hur Go Vendoring fungerar och även pekar på några detaljer om dess inre arbete.

Tack för att du läste det här!

Lämna ett svar

Din e-postadress kommer inte publiceras.