Lucas F. Costa Emberi gondolatok az egzakt tudományokról

jan 16, 2022
admin

Szia, mindenki! Mint talán észrevettétek, nagyon érdekel a Go, és mivel beleszerettem ebbe a nyelvbe, szeretnék gyakrabban írni róla.

Ha még nem ismered a Go-t, akkor szerintem tényleg meg kellene tanulnod. A Go fantasztikus!

A Go egy viszonylag fiatal nyelv, és amikor a vendorálásról és a függőségkezelésről van szó, még mindig nem igazán érett. A Go közösség azonban átvett néhány gyakorlatot, és eszközöket hozott létre ennek az igénynek a kielégítésére. Erről fogunk ma beszélni.

A Go ökoszisztéma megismerése

A Go célja, hogy az egyszerűség segítségével összetett feladatokat valósítson meg, és ezáltal a nyelvet szórakoztatóbbá és produktívabbá tegye. A nyelv kezdete óta a Go csapat úgy döntött, hogy az import szintaxis önkényessé tételéhez string literálokat használnak, hogy leírják, mit importálnak.

Ezt írják a saját dokumentációjukban:

A Go kifejezett célja a kezdetektől fogva az volt, hogy a Go kódot csak a magában a forrásban található információk felhasználásával lehessen építeni, ne kelljen makefile-t vagy a makefile-ok számos modern helyettesítőjének egyikét írni. Ha a Gónak szüksége lenne egy konfigurációs fájlra ahhoz, hogy elmagyarázza, hogyan építse fel a programját, akkor a Go kudarcot vallott volna.

Egy ilyen cél elérése érdekében a Go csapat a konvenciókat részesíti előnyben a konfigurációkkal szemben. Ezek az elfogadott konvenciók, amikor a csomagkezelésről van szó:

  1. Az importálási útvonal mindig ismert módon származik a forráskód URL-jéből. Ezért néznek ki az importok így: github.com/author/pkgname. Ezzel egy automatikusan kezelt névteret is kapunk, hiszen ezek az online szolgáltatások már eleve egyedi elérési utakat kezelnek minden csomaghoz.
  2. Az import útvonalból ismert módon származtatjuk azt a helyet, ahol a csomagokat a fájlrendszerünkben tároljuk. Ha rákeresünk, hogy go hol tárolja a letöltött csomagokat, akkor azt az URL-hez tudjuk kapcsolni, ahonnan letöltöttük.
  3. A forrásfában minden könyvtár egyetlen csomagnak felel meg. Ez megkönnyíti az egyes csomagok forráskódjának megtalálását, és segít szabványosított módon megszervezni a kódot. Azzal, hogy a mappaszerkezetet a csomagszerkezethez kötjük, nem kell egyszerre mindkettővel foglalkoznunk, mert a fájlrendszer eszközei csomagkezelő eszközökké válnak.
  4. A csomag csak a forráskódból származó információk felhasználásával épül fel. Ez azt jelenti, hogy nincs makefiles és nincs configuration, és megszabadít attól, hogy speciális (és valószínűleg bonyolult) eszközláncokat kelljen elfogadnunk.

Most, hogy ezt elmondtuk, már könnyű megérteni, hogy a go get parancs miért működik úgy, ahogyan működik.

A Go Tools parancsainak megértése

Mielőtt belemennénk ezekbe a parancsokba, értsük meg, mi az a “$GOPATH“.

A $GOPATH egy környezeti változó, amely egy olyan könyvtárra mutat, amely munkaterület könyvtárnak tekinthető. Ez fogja tárolni a forráskódokat, a lefordított csomagokat és a futtatható binárisokat.

go get

A go get parancs egyszerű és szinte úgy működik, mint a git clone. A go get-nek átadandó argumentum egy egyszerű tároló URL címe. Ebben a példában a parancsot fogjuk használni: go get https://github.com/golang/oauth2.

A parancs futtatásakor a go egyszerűen lehívja a csomagot a megadott URL-ről, és a $GOPATH könyvtárba helyezi. Ha a $GOPATH mappába navigálsz, akkor most látni fogod, hogy van egy mappád a src/github.com/golang/oauth2 könyvtárban, amely tartalmazza a csomag forrásfájljait, és egy lefordított csomag a pkg könyvtárban (a függőségekkel együtt).

Az go get futtatásakor szem előtt kell tartanod, hogy a letöltött csomagok a letöltéshez használt URL-nek megfelelő könyvtárba kerülnek.

Egy csomó más flag is rendelkezésre áll, például a -u, amely frissíti a csomagot, vagy a -insecure, amely lehetővé teszi a csomagok letöltését nem biztonságos sémák, például HTTP segítségével. A go get parancs “haladó” használatáról ezen a linken olvashatsz többet.

Az go help gopath szerint a go get parancs a megszerzett csomagok almoduljait is frissíti.

go install

Az go install parancsot egy csomag forráskönyvtárában futtatva lefordítja az adott csomag legújabb verzióját és az összes függőségét a pkg könyvtárba.

go build

Go build a csomagok és függőségeik lefordításáért felelős, de az eredményeket nem telepíti!

A függőségkezelés megértése

Amint azt már észrevehettük abból, ahogyan a Go menti a függőségeit, a függőségkezelésnek ez a megközelítése néhány problémával jár.

Először is, nem tudjuk meghatározni, hogy egy csomag melyik verziójára van szükségünk, hacsak nem egy teljesen más tárolóban van elhelyezve, különben a go get mindig a csomag legújabb verzióját fogja lekérni. Ez azt jelenti, hogy ha valaki egy törő változtatást végez a csomagján, és nem teszi azt egy másik repóba, akkor te és a csapatod bajba kerülhet, mert a végén ugyanannak a csomagnak különböző verzióit fogod lehívni, ami aztán “az én gépemen működik” problémákhoz fog vezetni.

Egy másik nagy probléma, hogy mivel a go get a csomagokat a src könyvtár gyökerébe telepíti, nem leszel képes arra, hogy a függőségek különböző verzióit használd minden egyes projektedhez. Ez azt jelenti, hogy nem lehetnek olyan projektjeid, amelyek ugyanannak a csomagnak különböző verzióitól függenek, vagy az egyik, vagy a másik változatot kell egyszerre használnod.

Azért, hogy ezeket a problémákat enyhítsék, a Go 1.5 óta a Go csapat bevezetett egy vendoring funkciót. Ez a funkció lehetővé teszi, hogy a függőségi csomagod kódját a saját könyvtárába helyezd, így az mindig ugyanazokat a verziókat kapja minden buildhez.

Tegyük fel, hogy van egy awesome-project nevű projekted, ami függ a popular-package-től. Annak érdekében, hogy garantáld, hogy a csapatodban mindenki ugyanazt a verziót használja a popular-package-ből, a forrását a $GOPATH/src/awesome-project/vendor/popular-package-ban helyezheted el. Ez azért fog működni, mert a Go ekkor a $GOPATH/src helyett a saját mappájának vendor könyvtárából kiindulva próbálja majd feloldani a függőségek elérési útvonalát (ha van legalább egy .go fájl). Ez determinisztikussá (reprodukálhatóvá) teszi a buildjeidet is, mivel mindig ugyanazt a popular-package verziót fogják használni.

Azt is fontos megjegyezni, hogy a go get parancs nem fogja automatikusan a vendor mappába tenni a letöltött csomagokat. Ez a vendoring eszközök feladata.

A vendoring használata esetén ugyanazokat az importálási útvonalakat használhatjuk, mintha nem használnánk, mert a Go mindig a legközelebbi vendor könyvtárban próbálja megtalálni a függőségeket. Nincs szükség arra, hogy az importálási útvonalak elé vendor/-ot írj.

Hogy teljesen megértsük, hogyan működik a vendorálás, meg kell értenünk a Go által az importálási útvonalak feloldására használt algoritmust, ami a következő:

  1. Keresd meg az importot a helyi vendor könyvtárban (ha van)
  2. Ha nem találjuk ezt a csomagot a helyi vendor könyvtárban, akkor felmegyünk a szülői mappába, és megpróbáljuk megtalálni az ottani vendor könyvtárban (ha van)
  3. Megismételjük a 2. lépést, amíg el nem érjük a $GOPATH/src
  4. Az importált csomagot a $GOROOT-ban keressük
  5. Ha nem találjuk ezt a csomagot a $GOROOT-ban, akkor a $GOPATH/src mappánkban keressük

Gyakorlatilag, ez azt jelenti, hogy minden csomagnak saját függőségeit a saját szállítói könyvtárában oldhatjuk fel. Ha például függsz a x és a y csomagtól, és a x csomag szintén függ a y csomagtól, de annak egy másik verziójára van szüksége, akkor is gond nélkül futtathatod a kódodat, mert a x a saját vendor mappájában keresi a y-et, míg a te csomagod a projekted vendor mappájában keresi a y-et.

Most, egy gyakorlati példa. Tegyük fel, hogy a következő mappaszerkezeted van:

$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

Ha a main-on belülről importáltuk a github.com/user/package-one.go, akkor ennek a csomagnak a vendor könyvtárban lévő változatára oldódna fel ugyanabban a mappában:

$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

Most ha ugyanezt a csomagot a client.go-ben importáljuk, akkor ezt a csomagot is a vendor mappába fogja feloldani a saját könyvtárában:

$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

Ugyanez történik, ha a server.go mappában lévő csomagot importáljuk:

$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

A függőségkezelés megértése

Most, hogy mindezt megtanultuk arról, hogyan kezeli a Go az importálást és a vendorálást, itt az ideje, hogy végre a függőségkezelésről beszéljünk.

Az eszköz, amit jelenleg a függőségek kezelésére használok a saját projektjeimben, a godep. Úgy tűnik, hogy nagyon népszerű, és nekem jól működik, ezért nagyon ajánlom, hogy te is ezt használd.

Ez a vendoring működésére épül. Mindössze annyit kell tenned, hogy elkezdd használni, hogy használd a godep save parancsot, amikor el akarod menteni a függőségeidet a vendor mappádba.

Amikor futtatod a godep save parancsot, a godep elmenti az aktuális függőségeid listáját a Godeps/Godeps.json mappába, majd átmásolja a forráskódjukat a vendor mappába. Azt is fontos megjegyezni, hogy ezeket a függőségeket telepíteni kell a gépedre ahhoz, hogy a godep képes legyen bemásolni őket.

Most már rögzítheted a vendor mappát és annak tartalmát, hogy mindenki ugyanazoknak a csomagoknak ugyanazokat a verzióit kapja, amikor a csomagodat futtatod.

Egy másik érdekes parancs a godep restore, amely a Godeps/Godeps.json fájlban megadott verziókat telepíti a $GOPATH könyvtárba.

A függőség frissítéséhez csak annyit kell tenned, hogy frissíted a go get -u segítségével (ahogy korábban már beszéltünk róla), majd futtasd a godep save parancsot, hogy a godep frissítse a Godeps/Godeps.json fájlt és másolja a szükséges fájlokat a vendor könyvtárba.

Pár gondolat arról, ahogy a Go kezeli a függőségeket

A blogbejegyzés végén szeretném hozzáfűzni a saját véleményemet is arról, ahogy a Go kezeli a függőségeket.

Úgy gondolom, hogy a Go döntése, hogy külső tárolókat használ a csomagok névtereinek kezelésére, nagyszerű volt, mert sokkal egyszerűbbé teszi az egész csomagfeloldást azáltal, hogy a fájlrendszer és a névtér fogalmakat összekapcsolja. Ez az egész ökoszisztémát is függetlenné teszi, mert most már van egy decentralizált módja a csomagok lehívásának.

A decentralizált csomagkezelésnek azonban ára van, mégpedig az, hogy nem tudjuk ellenőrizni az összes csomópontot, amelyek ennek a “hálózatnak” a részét képezik. Ha valaki például úgy dönt, hogy kiveszi a csomagját a github-ről, akkor hirtelen több száz, ezer vagy akár több millió build elkezdhet hibásan működni. A névváltoztatások is ugyanezt a hatást érhetik el.

A Go fő céljait figyelembe véve ennek teljesen értelme van, és ez egy teljesen korrekt kompromisszum. A Go a konfiguráció helyett a konvenciókról szól, és nincs egyszerűbb módja a függőségek kezelésének, mint ahogyan azt jelenleg teszi.

Azért persze lehetne néhány fejlesztést eszközölni, például a git-tagek használatával konkrét csomagverziókat lehetne lekérni, és a felhasználóknak meg lehetne adni, hogy milyen verziókat használjanak a csomagjaik. Az is klassz lenne, ha ezeket a függőségeket le lehetne hívni ahelyett, hogy a forráskezelésen ellenőriznénk őket. Ez lehetővé tenné, hogy elkerüljük a piszkos diffeket, amelyek csak a vendor könyvtárban lévő kód módosításait tartalmazzák, és tisztábbá és könnyebbé tenné az egész tárat.

Kapcsolat!

Ha kétségeid, gondolataid vannak, vagy ha nem értesz egyet azzal, amit írtam, kérlek, oszd meg velem az alábbi hozzászólásokban, vagy keress meg a @thewizardlucas Twitteren. Szívesen meghallgatnám a véleményedet, és javítanék, ha hibáztam.

Végül, de nem utolsósorban, mindenképpen nézd meg Wisdom Omuya fantasztikus előadását a GopherCon 2016-on, amelyben elmagyarázza, hogyan működik a Go Vendoring, és rámutat néhány részletre a belső működésével kapcsolatban is.

Köszönöm, hogy elolvastad ezt!

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.