Lucas F. Costa Gânduri umane despre științele exacte
Bună ziua tuturor! După cum probabil ați observat, sunt foarte interesat de Go și, de când m-am îndrăgostit de acest limbaj, aș vrea să scriu mai des despre el.
Dacă nu cunoașteți încă Go, cred cu adevărat că ar trebui să-l învățați. Go este nemaipomenit!
Go este un limbaj relativ tânăr și când vine vorba de vending și managementul dependențelor încă nu este cu adevărat matur. Cu toate acestea, comunitatea Go a adoptat unele practici și a creat instrumente pentru a suplini această cerere. Despre acest lucru vom vorbi astăzi.
Înțelegerea ecosistemului Go
Go își propune să folosească simplitatea pentru a realiza sarcini complexe și, prin urmare, să facă limbajul mai distractiv și mai productiv. Încă de la începutul limbajului, echipa Go a ales că va folosi literali de șiruri de caractere pentru a face ca sintaxa de import să fie arbitrară pentru a descrie ceea ce este importat.
Acesta este ceea ce este scris în propriul lor docs:
Un obiectiv explicit pentru Go de la început a fost acela de a putea construi cod Go folosind doar informațiile găsite în sursa însăși, fără a fi nevoie să scrie un makefile sau unul dintre multele înlocuitori moderni pentru makefile. Dacă Go ar fi avut nevoie de un fișier de configurare pentru a explica cum să vă construiți programul, atunci Go ar fi eșuat.
Pentru a atinge acest obiectiv, echipa Go favorizează convențiile în locul configurațiilor. Acestea sunt convențiile adoptate când vine vorba de gestionarea pachetelor:
- Calea de import este întotdeauna derivată într-un mod cunoscut din URL-ul codului sursă. Acesta este motivul pentru care importurile dvs. arată astfel:
github.com/author/pkgname
. Făcând acest lucru, obținem, de asemenea, un spațiu de nume gestionat automat, deoarece aceste servicii online gestionează deja căi unice pentru fiecare pachet. - Locul în care stocăm pachetele în sistemul nostru de fișiere este derivat într-un mod cunoscut din calea de import. Dacă căutați locul în care
go
stochează pachetele pe care le descărcați, veți putea face legătura cu URL-ul de unde a fost descărcat. - Care director dintr-un arbore sursă corespunde unui singur pachet. Astfel, vă este mai ușor să găsiți codul sursă pentru anumite pachete și vă ajută să vă organizați codul într-un mod standardizat. Legând structura directoarelor de structura pachetelor, nu trebuie să ne mai facem griji pentru ambele în același timp, deoarece instrumentele sistemului de fișiere devin instrumente de gestionare a pachetelor.
- Pachetul este construit folosind numai informații din codul sursă. Acest lucru înseamnă că nu există
makefiles
și niciconfiguration
și vă scutește de necesitatea de a adopta lanțuri de instrumente specifice (și probabil complicate).
Acum că am spus asta, este ușor de înțeles de ce comanda go get
funcționează așa cum funcționează.
Înțelegerea comenzilor instrumentelor Go
Înainte de a intra în aceste comenzi, să înțelegem ce este „$GOPATH
„.
$GOPATH
Variabila de mediu $GOPATH
este o variabilă de mediu care indică un director care poate fi considerat ca fiind un director de spațiu de lucru. Acesta va conține codurile sursă, pachetele compilate și binarele executabile.
go get
Comanda go get
este simplă și funcționează aproape ca o git clone
. Argumentul pe care trebuie să îl transmiteți la go get
este un simplu URL de depozit. În acest exemplu, vom folosi comanda: go get https://github.com/golang/oauth2
.
Când se execută această comandă, go
va prelua pur și simplu pachetul de la URL-ul pe care l-ați furnizat și îl va pune în directorul $GOPATH
. Dacă navigați în dosarul $GOPATH
, veți vedea acum că aveți un dosar în src/github.com/golang/oauth2
care conține fișierele sursă ale pachetului și un pachet compilat în directorul pkg
(alături de dependențele sale).
Când rulați go get
ar trebui să aveți în vedere că orice pachet descărcat va fi plasat într-un director care corespunde URL-ului pe care l-ați folosit pentru a-l descărca.
De asemenea, are la dispoziție o mulțime de alți indicatori, cum ar fi -u
care actualizează un pachet sau -insecure
care vă permite să descărcați pachete folosind scheme nesigure, cum ar fi HTTP. Puteți citi mai multe despre utilizarea „avansată” a comenzii go get
la acest link.
De asemenea, conform go help gopath
, comanda go get
actualizează și submodulele pachetelor pe care le obțineți.
go install
Când se execută go install
în directorul sursă al unui pachet, aceasta va compila cea mai recentă versiune a acelui pachet și toate dependențele sale în directorul pkg
.
go build
Go build este responsabil de compilarea pachetelor și a dependențelor lor, dar nu instalează rezultatele!
Înțelegerea furnizorilor
După cum probabil ați observat prin modul în care Go își salvează dependențele, această abordare a gestionării dependențelor are câteva probleme.
În primul rând, nu suntem capabili să determinăm de ce versiune a unui pachet avem nevoie decât dacă acesta este găzduit într-un depozit complet diferit, altfel go get
va prelua întotdeauna cea mai recentă versiune a unui pachet. Acest lucru înseamnă că, dacă cineva face o modificare de ultimă oră la pachetul său și nu îl pune într-un alt depozit, dumneavoastră și echipa dumneavoastră veți avea probleme, deoarece s-ar putea să ajungeți să recuperați versiuni diferite ale aceluiași pachet și acest lucru va duce apoi la probleme de tipul „funcționează pe mașina mea”.
O altă mare problemă este că, datorită faptului că go get
instalează pachetele la rădăcina directorului src
, nu veți putea avea versiuni diferite ale dependențelor dumneavoastră pentru fiecare dintre proiectele dumneavoastră. Acest lucru înseamnă că nu puteți avea proiecte care să depindă de versiuni diferite ale aceluiași pachet, va trebui să aveți fie o versiune, fie cealaltă la un moment dat.
Pentru a atenua aceste probleme, începând cu Go 1.5, echipa Go a introdus o caracteristică vendoring
. Această caracteristică vă permite să puneți codul dependenței dvs. pentru un pachet în interiorul propriului său director, astfel încât acesta va putea obține întotdeauna aceleași versiuni pentru toate compilările.
Să spunem că aveți un proiect numit awesome-project
care depinde de popular-package
. Pentru a garanta că toată lumea din echipa dvs. va folosi aceeași versiune de popular-package
, puteți pune sursa acestuia în interiorul $GOPATH/src/awesome-project/vendor/popular-package
. Acest lucru va funcționa deoarece Go
va încerca apoi să rezolve calea dependențelor dvs. pornind de la directorul vendor
al propriului său dosar (dacă are cel puțin un fișier .go
) în loc de $GOPATH/src
. Acest lucru va face, de asemenea, ca compilările dvs. să fie deterministe (reproductibile), deoarece vor folosi întotdeauna aceeași versiune de popular-package
.
Este, de asemenea, important de observat că comanda go get
nu va pune automat pachetele descărcate în directorul vendor
. Aceasta este o treabă pentru instrumentele de vending.
Când folosiți vending veți putea folosi aceleași căi de import ca și când nu ați face-o, deoarece Go
va încerca întotdeauna să găsească dependențele în cel mai apropiat director vendor
. Nu este nevoie să adăugați vendor/
în prealabil la niciuna dintre căile de import.
Pentru a putea înțelege pe deplin modul în care funcționează distribuția trebuie să înțelegem algoritmul folosit de Go pentru a rezolva căile de import, care este următorul:
- Cercăm importul în directorul local
vendor
(dacă există) - Dacă nu găsim acest pachet în directorul local
vendor
urcăm în folderul părinte și încercăm să-l găsim în directorulvendor
de acolo (dacă există) - Repetăm pasul 2 până ajungem la $GOPATH/src
- Cercăm pachetul importat în $GOROOT
- Dacă nu găsim acest pachet în $GOROOT îl căutăm în folderul nostru $GOPATH/src
În principiu, acest lucru înseamnă că fiecare pachet poate avea propriile dependențe rezolvate în propriul său director furnizor. Dacă depindeți de pachetul x
și de pachetul y
, de exemplu, iar pachetul x
depinde, de asemenea, de pachetul y
, dar are nevoie de o versiune diferită a acestuia, veți putea în continuare să vă rulați codul fără probleme, deoarece x
va căuta y
în propriul său folder vendor
, în timp ce pachetul dvs. va căuta y
în folderul vendor al proiectului dvs. Să spunem că aveți această structură de dosare:
$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
Dacă am importat github.com/user/package-one
din interiorul main.go s-ar rezolva la versiunea acestui pachet în directorul vendor
din același dosar:
$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
Acum, dacă importăm același pachet în client.go
, acesta se va rezolva, de asemenea, la versiunea acestui pachet în directorul vendor
din propriul dosar:
$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
Același lucru se întâmplă când importăm acest pachet în dosarul 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
Înțelegerea managementului dependențelor
Acum că am învățat toate aceste lucruri despre modul în care Go
gestionează importurile și vânzările, este timpul să vorbim în sfârșit despre managementul dependențelor.
Instrumentul pe care îl folosesc în prezent pentru a gestiona dependențele în propriile mele proiecte se numește godep
. Se pare că este foarte popular și funcționează bine pentru mine, prin urmare vă recomand cu căldură să îl folosiți și voi.
Este construit în jurul modului în care funcționează vendoring
. Tot ce trebuie să faceți pentru a începe să îl folosiți este să folosiți comanda godep save
ori de câte ori doriți să vă salvați dependențele în folderul vendor
.
Când executați godep save
, godep
va salva o listă cu dependențele dvs. curente în Godeps/Godeps.json
și apoi va copia codul sursă al acestora în folderul vendor
. De asemenea, este important să observați că trebuie să aveți aceste dependențe instalate pe mașina dumneavoastră pentru ca godep
să le poată copia.
Acum puteți confirma dosarul vendor
și conținutul său pentru a vă asigura că toată lumea va avea aceleași versiuni ale acelorași pachete ori de câte ori va rula pachetul dumneavoastră.
O altă comandă interesantă este godep restore
, care va instala versiunile specificate în fișierul Godeps/Godeps.json
în $GOPATH
.
Pentru a actualiza o dependență tot ce trebuie să faceți este să o actualizați folosind go get -u
(așa cum am vorbit mai devreme) și apoi să rulați godep save
pentru ca godep
să actualizeze fișierul Godeps/Godeps.json
și să copieze fișierele necesare în directorul vendor
.
Câteva gânduri despre modul în care Go gestionează dependențele
La sfârșitul acestei postări pe blog, aș dori să adaug și părerea mea despre modul în care Go gestionează dependențele.
Cred că alegerea lui Go de a folosi depozite externe pentru a gestiona spațiile de nume ale pachetelor a fost excelentă, deoarece face ca întreaga rezolvare a pachetelor să fie mult mai simplă prin alăturarea conceptelor de sistem de fișiere cu cele de spații de nume. De asemenea, acest lucru face ca întregul ecosistem să funcționeze independent, deoarece acum avem o modalitate descentralizată de a prelua pachete.
Cu toate acestea, gestionarea descentralizată a pachetelor vine cu un cost, și anume acela de a nu putea controla toate nodurile care fac parte din această „rețea”. Dacă cineva decide că își va scoate pachetul din github
, de exemplu, atunci sute, mii sau chiar milioane de compilări pot începe să eșueze dintr-o dată. Schimbările de nume ar putea avea, de asemenea, același efect.
Considerând obiectivele principale ale Go, acest lucru are un sens total și este un compromis total corect. Go se bazează pe convenție în loc de configurare și nu există o modalitate mai simplă de a gestiona dependențele decât modul în care o face în prezent.
Desigur, ar putea fi făcute câteva îmbunătățiri, cum ar fi utilizarea etichetelor git pentru a prelua versiuni specifice ale pachetelor și a permite utilizatorilor să specifice ce versiuni vor folosi pachetele lor. De asemenea, ar fi grozav să se poată prelua aceste dependențe în loc de a le verifica în controlul sursei. Acest lucru ne-ar permite să evităm diff-urile murdare care conțin doar modificări ale codului din directorul vendor
și să facem întregul depozit mai curat și mai ușor.
Să ne contactăm!
Dacă aveți îndoieli, gânduri sau dacă nu sunteți de acord cu ceva din ceea ce am scris, vă rog să împărtășiți cu mine în comentariile de mai jos sau să mă contactați la @thewizardlucas pe twitter. Mi-ar plăcea să aud ce aveți de spus și să fac corecții dacă am făcut vreo greșeală.
În sfârșit, dar nu în ultimul rând, asigurați-vă că aruncați o privire la acest discurs minunat al lui Wisdom Omuya de la GopherCon 2016, în care explică modul în care funcționează Go Vendoring și, de asemenea, punctează câteva detalii despre funcționarea sa internă.
Mulțumesc pentru că ați citit asta!