Lucas F. Costa Ludzkie myśli o naukach ścisłych
Witam wszystkich! Jak pewnie zauważyliście, jestem bardzo zainteresowany Go i odkąd zakochałem się w tym języku, chciałbym pisać o nim częściej.
Jeśli jeszcze nie znasz Go, to naprawdę uważam, że powinieneś się go nauczyć. Go jest niesamowite!
Go jest stosunkowo młodym językiem i jeśli chodzi o zarządzanie sprzedażą i zależnościami, wciąż nie jest jeszcze naprawdę dojrzałe. Społeczność Go jednak przyjęła pewne praktyki i tworzy narzędzia, aby zaspokoić to zapotrzebowanie. Właśnie o tym będziemy dziś rozmawiać.
Zrozumienie ekosystemu Go
Go ma na celu wykorzystanie prostoty do realizacji złożonych zadań, a przez to uczynienie języka bardziej przyjemnym i produktywnym. Od początku istnienia języka, zespół Go zdecydował, że będzie używał literałów łańcuchowych, aby składnia importu była arbitralna i opisywała to, co jest importowane.
Tak jest napisane w ich własnych dokumentach:
Jasnym celem Go od początku była możliwość budowania kodu Go używając tylko informacji znalezionych w samym źródle, bez potrzeby pisania pliku makefile lub jednego z wielu nowoczesnych zamienników plików makefile. Jeśli Go potrzebowałoby pliku konfiguracyjnego, aby wyjaśnić, jak zbudować swój program, to Go by zawiodło.
Aby osiągnąć ten cel, zespół Go preferuje konwencje nad konfiguracjami. Oto konwencje przyjęte, jeśli chodzi o zarządzanie pakietami:
- Ścieżka importu jest zawsze wyprowadzana w znany sposób z adresu URL kodu źródłowego. To dlatego twój import wygląda jak:
github.com/author/pkgname
. Robiąc to otrzymujemy również automatycznie zarządzaną przestrzeń nazw, ponieważ te usługi online już zarządzają unikalnymi ścieżkami dla każdego pakietu. - Miejsce, w którym przechowujemy pakiety w naszym systemie plików jest wyprowadzane w znany sposób ze ścieżki importu. Jeśli poszukasz, gdzie
go
przechowuje pakiety, które pobierasz, będziesz w stanie powiązać to z adresem URL, z którego zostały pobrane. - Każdy katalog w drzewie źródeł odpowiada pojedynczemu pakietowi. Ułatwia to znalezienie kodu źródłowego dla określonych pakietów i pomaga zorganizować kod w standardowy sposób. Wiążąc strukturę katalogów ze strukturą pakietów nie musimy się martwić o obie te rzeczy naraz, ponieważ narzędzia systemu plików stają się narzędziami zarządzania pakietami.
- Pakiet jest budowany przy użyciu tylko informacji z kodu źródłowego. Oznacza to brak
makefiles
i brakconfiguration
i uwalnia od konieczności przyjęcia specyficznych (i prawdopodobnie skomplikowanych) łańcuchów narzędzi.
Teraz, gdy już to powiedzieliśmy, łatwo zrozumieć, dlaczego polecenie go get
działa tak, jak działa.
Zrozumienie poleceń narzędzi Go
Zanim przejdziemy do tych poleceń, zrozummy czym jest „$GOPATH
„.
$GOPATH
jest zmienną środowiskową, która wskazuje na katalog, który może być uważany za katalog obszaru roboczego. Będzie on przechowywał twoje kody źródłowe, skompilowane pakiety i uruchamialne binarki.
go get
Komenda go get
jest prosta i działa prawie jak git clone
. Argumentem, który musisz przekazać do go get
jest prosty adres URL repozytorium. W tym przykładzie użyjemy polecenia: go get https://github.com/golang/oauth2
.
Po uruchomieniu tego polecenia, go
po prostu pobierze pakiet z podanego adresu URL i umieści go w twoim katalogu $GOPATH
. Jeśli przejdziesz do folderu $GOPATH
, zobaczysz, że masz teraz folder w src/github.com/golang/oauth2
, który zawiera pliki źródłowe pakietu i skompilowany pakiet w katalogu pkg
(wraz z jego zależnościami).
Kiedy uruchomisz go get
, powinieneś mieć na uwadze, że każdy pobrany pakiet zostanie umieszczony w katalogu, który odpowiada URL, którego użyłeś do pobrania go.
Ma także kilka innych dostępnych flag, takich jak -u
, która aktualizuje pakiet lub -insecure
, która pozwala na pobranie pakietów używając niepewnych schematów, takich jak HTTP. Możesz przeczytać więcej o „zaawansowanym” użyciu polecenia go get
na tym linku.
A także, zgodnie z go help gopath
, polecenie go get
aktualizuje także submoduły pakietów, które pobierasz.
go install
Po uruchomieniu go install
w katalogu źródłowym pakietu skompiluje on najnowszą wersję tego pakietu i wszystkie jego zależności w katalogu pkg
.
go build
Go build jest odpowiedzialny za kompilację pakietów i ich zależności, ale nie instaluje wyników!
Zrozumienie Vendoringu
Jak mogłeś zauważyć po sposobie, w jaki Go zapisuje swoje zależności, takie podejście do zarządzania zależnościami ma kilka problemów.
Po pierwsze, nie jesteśmy w stanie określić, której wersji pakietu potrzebujemy, chyba że jest on hostowany w zupełnie innym repozytorium, w przeciwnym razie go get
zawsze pobierze najnowszą wersję pakietu. Oznacza to, że jeśli ktoś zrobi przełomową zmianę w swoim pakiecie i nie umieści go w innym repo, ty i twój zespół będziecie mieli kłopoty, ponieważ możecie skończyć pobierając różne wersje tego samego pakietu, a to doprowadzi do problemów z „działa na mojej maszynie”.
Innym dużym problemem jest to, że z powodu tego, że go get
instaluje pakiety w korzeniu twojego katalogu src
, nie będziesz mógł mieć różnych wersji swoich zależności dla każdego z twoich projektów. Oznacza to, że nie możesz mieć projektów, które zależą od różnych wersji tego samego pakietu, będziesz musiał albo mieć jedną wersję albo drugą na raz.
Aby złagodzić te problemy, od Go 1.5, zespół Go wprowadził funkcję vendoring
. Ta funkcja pozwala na umieszczenie kodu zależności dla pakietu wewnątrz jego własnego katalogu, dzięki czemu będzie on w stanie zawsze uzyskać te same wersje dla wszystkich kompilacji.
Powiedzmy, że masz projekt o nazwie awesome-project
, który zależy od popular-package
. Aby zagwarantować, że wszyscy w twoim zespole będą używać tej samej wersji popular-package
, możesz umieścić jego źródło wewnątrz $GOPATH/src/awesome-project/vendor/popular-package
. To zadziała, ponieważ Go
będzie wtedy próbował rozwiązać ścieżkę twoich zależności zaczynając od katalogu vendor
swojego folderu (jeśli ma przynajmniej jeden plik .go
) zamiast od $GOPATH/src
. To również sprawi, że twoje kompilacje będą deterministyczne (powtarzalne), ponieważ zawsze będą używać tej samej wersji popular-package
.
Ważne jest również, aby zauważyć, że polecenie go get
nie umieści pobranych pakietów w folderze vendor
automatycznie. Jest to zadanie dla narzędzi vendoringu.
Kiedy używasz vendoringu, będziesz mógł używać tych samych ścieżek importu, jak gdybyś ich nie używał, ponieważ Go
zawsze będzie próbował znaleźć zależności w najbliższym katalogu vendor
. Nie ma potrzeby dopisywania vendor/
do żadnej z twoich ścieżek importu.
Aby móc w pełni zrozumieć, jak działa vendoring, musimy zrozumieć algorytm używany przez Go do rozwiązywania ścieżek importu, który jest następujący:
- Poszukaj importu w lokalnym katalogu
vendor
(jeśli istnieje) - Jeśli nie możemy znaleźć tego pakietu w lokalnym katalogu
vendor
, przechodzimy do folderu nadrzędnego i próbujemy znaleźć go w tamtejszym kataloguvendor
(jeśli - Powtarzamy krok 2 aż do $GOPATH/src
- Szukamy importowanego pakietu w $GOROOT
- Jeśli nie możemy znaleźć tego pakietu w $GOROOT, szukamy go w naszym folderze $GOPATH/src
Podstawowo, oznacza to, że każdy pakiet może mieć swoje własne zależności rozwiązane w swoim własnym katalogu dostawcy. Jeśli na przykład zależysz od pakietu x
i pakietu y
, a pakiet x
również zależy od pakietu y
, ale potrzebuje innej jego wersji, nadal będziesz mógł uruchomić swój kod bez problemów, ponieważ x
będzie szukał y
w swoim własnym folderze vendor
, podczas gdy twój pakiet będzie szukał y
w folderze dostawcy twojego projektu.
Teraz praktyczny przykład. Załóżmy, że masz taką strukturę folderów:
$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
Gdybyśmy zaimportowali github.com/user/package-one
z wnętrza main.go to rozwiązałby się on do wersji tego pakietu w katalogu vendor
w tym samym folderze:
$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
Teraz jeśli zaimportujemy ten sam pakiet w client.go
to również rozwiąże on ten pakiet do katalogu vendor
w swoim własnym katalogu:
$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
Tak samo dzieje się przy imporcie tego pakietu na pliku 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
Understanding Dependency Management
Teraz, gdy dowiedzieliśmy się już tych wszystkich rzeczy o tym, jak Go
radzi sobie z importem i sprzedażą, nadszedł czas, abyśmy wreszcie porozmawiali o zarządzaniu zależnościami.
Narzędzie, którego obecnie używam do zarządzania zależnościami w moich własnych projektach, nazywa się godep
. Wydaje się być bardzo popularne i działa dobrze dla mnie, więc gorąco polecam Ci korzystanie z niego.
Jest ono zbudowane wokół sposobu, w jaki działa vendoring
. Wszystko, co musisz zrobić, aby zacząć go używać, to użyć polecenia godep save
za każdym razem, gdy chcesz zapisać swoje zależności do folderu vendor
.
Gdy uruchomisz godep save
, godep
zapisze listę twoich aktualnych zależności w Godeps/Godeps.json
, a następnie skopiuje ich kod źródłowy do folderu vendor
. Ważne jest również, aby zauważyć, że musisz mieć zainstalowane te zależności na swojej maszynie, aby godep
mógł je skopiować.
Teraz możesz zatwierdzić folder vendor
i jego zawartość, aby upewnić się, że wszyscy będą mieli te same wersje tych samych pakietów podczas uruchamiania twojego pakietu.
Innym interesującym poleceniem jest godep restore
, które zainstaluje wersje określone w twoim pliku Godeps/Godeps.json
do twojego $GOPATH
.
Aby zaktualizować zależność wszystko co musisz zrobić to zaktualizować ją używając go get -u
(o czym mówiliśmy wcześniej), a następnie uruchomić godep save
, aby godep
zaktualizował plik Godeps/Godeps.json
i skopiował potrzebne pliki do katalogu vendor
.
Kilka myśli na temat sposobu, w jaki Go radzi sobie z zależnościami
Na koniec tego wpisu na blogu, chciałbym również dodać moją własną opinię na temat sposobu, w jaki Go radzi sobie z zależnościami.
Myślę, że wybór Go dotyczący używania zewnętrznych repozytoriów do obsługi przestrzeni nazw pakietów był świetny, ponieważ sprawia, że całe rozwiązywanie pakietów jest dużo prostsze poprzez połączenie koncepcji systemu plików z koncepcjami przestrzeni nazw. To również sprawia, że cały ekosystem działa niezależnie, ponieważ mamy teraz zdecentralizowany sposób na pobieranie pakietów.
Jednakże zdecentralizowane zarządzanie pakietami wiąże się z kosztem, którym jest brak możliwości kontrolowania wszystkich węzłów, które są częścią tej „sieci”. Jeśli ktoś zdecyduje, że zamierza zabrać swój pakiet z github
, na przykład, wtedy setki, tysiące, a nawet miliony kompilacji mogą nagle zacząć zawodzić. Zmiany nazw również mogą mieć ten sam efekt.
Patrząc na główne cele Go, ma to całkowity sens i jest całkowicie sprawiedliwym kompromisem. W Go chodzi o konwencję, a nie o konfigurację i nie ma prostszego sposobu na obsługę zależności niż ten, który jest obecnie stosowany.
Oczywiście, można by wprowadzić kilka ulepszeń, takich jak użycie znaczników git do pobierania konkretnych wersji pakietów i pozwolenie użytkownikom na określenie, których wersji będą używać ich pakiety. Byłoby również fajnie móc pobierać te zależności zamiast sprawdzać je w kontroli źródła. To pozwoliłoby nam uniknąć brudnych diffów zawierających tylko zmiany w kodzie w katalogu vendor
i uczynić całe repozytorium czystszym i bardziej lekkim.
Get in touch!
Jeśli masz jakieś wątpliwości, przemyślenia lub nie zgadzasz się z czymkolwiek co napisałem, proszę podziel się tym ze mną w komentarzach poniżej lub osiągnij mnie na @thewizardlucas na twitterze. Chętnie usłyszę, co masz do powiedzenia i dokonam korekty, jeśli popełniłem jakieś błędy.
Na koniec, ale nie mniej ważne, upewnij się, że spojrzysz na ten niesamowity wykład Wisdoma Omuyi na GopherCon 2016, w którym wyjaśnia, jak działa Go Vendoring, a także wskazuje na kilka szczegółów dotyczących jego wewnętrznego funkcjonowania.
Dzięki za przeczytanie tego!