Lucas F. Costa Pensamentos humanos sobre ciências exactas
Hi, todos! Como devem ter reparado, estou realmente interessado em Go e como me apaixonei por esta língua gostaria de escrever sobre ela com mais frequência.
Se ainda não conhecem Go, acho mesmo que deveriam aprender. Go é fantástico!
Go é uma língua relativamente jovem e quando se trata de vingança e gestão de dependência ainda não está realmente madura. A comunidade Go, entretanto, tem adotado algumas práticas e criado ferramentas para suprir essa demanda. É sobre isso que vamos falar hoje.
Entender o Ecossistema Go
Go visa usar a simplicidade para realizar tarefas complexas e, portanto, tornar a linguagem mais divertida e produtiva. Desde o início da linguagem, a equipe Go escolheu que eles usariam strings literais para tornar a sintaxe de importação arbitrária para descrever o que estava sendo importado.
Isso é o que está escrito em seus próprios documentos:
Um objetivo explícito para Go desde o início era poder construir o código Go usando apenas as informações encontradas no próprio código fonte, não precisando escrever um makefile ou um dos muitos substitutos modernos para makefiles. Se Go precisasse de um arquivo de configuração para explicar como construir seu programa, então Go teria falhado.
Para atingir este objetivo, a equipe Go favorece as convenções sobre as configurações. Estas são as convenções adotadas quando se trata de gerenciamento de pacotes:
- O caminho de importação é sempre derivado de uma forma conhecida a partir da URL do código fonte. É por isso que suas importações são parecidas:
github.com/author/pkgname
. Ao fazer isso também obtemos um namespace gerenciado automaticamente, já que esses serviços online já gerenciam caminhos únicos para cada pacote. - O lugar onde armazenamos os pacotes em nosso sistema de arquivos é derivado de uma forma conhecida a partir do caminho de importação. Se você procurar por onde
go
armazena os pacotes que você baixa você será capaz de relacioná-lo com a URL de onde foi baixado. - Cada diretório em uma árvore de código fonte corresponde a um único pacote. Isto torna mais fácil para você encontrar o código fonte de certos pacotes e ajuda a organizar seu código de uma forma padronizada. Ligando a estrutura de pastas à estrutura de pacotes não precisamos nos preocupar com ambos ao mesmo tempo, pois ferramentas de sistema de arquivos tornam-se ferramentas de gerenciamento de pacotes.
- O pacote é construído usando apenas informações do código-fonte. Isto significa não
makefiles
e nãoconfiguration
e liberta-o de ter de adoptar cadeias de ferramentas específicas (e provavelmente complicadas).
Agora já dissemos isso, é fácil de compreender porque é que o comando go get
funciona da forma que funciona.
Comandos de Go Tools
Antes de entrarmos nestes comandos vamos entender o que é o “$GOPATH
“.
O $GOPATH
é uma variável de ambiente que aponta para um diretório que pode ser considerado como um diretório de espaço de trabalho. Ela irá conter seus códigos fontes, pacotes compilados e binários executáveis.
go get
O comando go get
é simples e funciona quase como um git clone
. O argumento que você deve passar para go get
é um simples URL de repositório. Neste exemplo, vamos usar o comando: go get https://github.com/golang/oauth2
.
Ao executar este comando, go
simplesmente pegará o pacote da URL que você forneceu e o colocará no seu diretório $GOPATH
. Se você navegar para sua pasta $GOPATH
verá agora que você tem uma pasta em src/github.com/golang/oauth2
que contém os arquivos fonte do pacote e um pacote compilado no diretório pkg
(ao lado de suas dependências).
Quando você executar go get
você deve ter em mente que qualquer pacote baixado será colocado em um diretório que corresponda à URL que você usou para baixá-lo.
Também tem um monte de outras bandeiras disponíveis, como -u
que atualiza um pacote ou -insecure
que permite que você baixe pacotes usando esquemas inseguros como o HTTP. Você pode ler mais sobre o uso “avançado” do comando go get
neste link.
Também, de acordo com go help gopath
, o comando go get
também atualiza os submódulos dos pacotes que você está recebendo.
go install
Ao executar go install
no diretório fonte de um pacote ele compilará a última versão desse pacote e todas as suas dependências no diretório pkg
.
go build
Go build é responsável pela compilação dos pacotes e suas dependências, mas ele não instala os resultados!
Understanding Vendoring
Como você deve ter notado pelo caminho Go salva suas dependências, esta abordagem ao gerenciamento de dependências tem alguns problemas.
Primeiro de tudo, nós não somos capazes de determinar qual versão de um pacote nós precisamos a menos que ele esteja hospedado em um repositório completamente diferente, caso contrário go get
irá sempre buscar a última versão de um pacote. Isto significa que se alguém fizer uma mudança radical no seu pacote e não o colocar em outro repositório, você e a sua equipa vão acabar em problemas porque você pode acabar por ir buscar versões diferentes do mesmo pacote e isto vai levar a problemas “funciona na minha máquina”.
Outro grande problema é que, devido ao facto de go get
instalar pacotes na raiz do seu directório src
, você não será capaz de ter versões diferentes das suas dependências para cada um dos seus projectos. Isto significa que você não pode ter projetos que dependem de versões diferentes do mesmo pacote, você terá que ter uma versão ou outra de cada vez.
A fim de mitigar estes problemas, desde Go 1.5, a equipe Go introduziu uma funcionalidade vendoring
. Esta funcionalidade permite que você coloque o código da sua dependência para um pacote dentro do seu próprio diretório, assim ele será capaz de obter sempre as mesmas versões para todos os builds.
Vamos dizer que você tem um projeto chamado awesome-project
que depende de popular-package
. Para garantir que todos na sua equipe usarão a mesma versão de popular-package
você pode colocar seu código fonte dentro de $GOPATH/src/awesome-project/vendor/popular-package
. Isto funcionará porque Go
tentará então resolver o caminho das suas dependências a partir da sua própria pasta vendor
(se tiver pelo menos um ficheiro .go
) em vez de $GOPATH/src
. Isto também tornará seus builds determinísticos (reprodutíveis) já que eles sempre usarão a mesma versão de popular-package
.
É também importante notar que o comando go get
não irá colocar os pacotes baixados na pasta vendor
automaticamente. Este é um trabalho para vendoring tools.
Ao usar vendoring você será capaz de usar os mesmos caminhos de importação como se você não estivesse, porque Go
sempre tentará encontrar dependências no diretório vendor
mais próximo. Não há necessidade de pré-pender vendor/
para qualquer um dos seus caminhos de importação.
Para poder entender completamente como funciona a venda devemos entender o algoritmo usado por Go para resolver os caminhos de importação, que é o seguinte:
- Localize a importação no diretório local
vendor
(se houver) - Se não conseguirmos encontrar este pacote no diretório local
vendor
vamos até a pasta pai e tentamos encontrá-lo no diretóriovendor
lá (se any) - Repetimos o passo 2 até chegarmos a $GOPATH/src
- Procuramos o pacote importado a $GOROOT
- Se não conseguirmos encontrar este pacote a $GOROOT procuramos na nossa pasta $GOPATH/src
Basicamente, isto significa que cada pacote pode ter suas próprias dependências resolvidas para seu próprio diretório de fornecedores. Se você depende do pacote x
e pacote y
, por exemplo, e pacote x
também depende do pacote y
mas precisa de uma versão diferente dele, você ainda será capaz de executar seu código sem problemas, porque x
irá procurar por y
em sua própria pasta vendor
enquanto seu pacote irá procurar por y
na pasta do fornecedor do seu projeto.
Agora, um exemplo prático. Digamos que você tenha esta estrutura de pastas:
$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
Se nós importamos github.com/user/package-one
de dentro da pasta principal.go ele resolveria para a versão deste pacote no diretório vendor
na mesma pasta:
$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
Agora se importarmos o mesmo pacote em client.go
ele também resolverá este pacote para a pasta vendor
em seu próprio diretório:
$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
O mesmo acontece quando importamos este pacote no arquivo 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
>
A compreensão da Gestão de Dependências
Agora aprendemos todas estas coisas sobre como Go
lida com as importações e vendendo é hora de finalmente falarmos sobre a gestão de dependências.
A ferramenta que estou usando atualmente para gerenciar dependências em meus próprios projetos chama-se godep
. Parece ser muito popular e funciona bem para mim, por isso recomendo que você use isso também.
É construído em torno da forma como vendoring
funciona. Tudo que você tem que fazer para começar a usá-lo é usar o comando godep save
sempre que quiser salvar suas dependências na pasta vendor
.
Quando você executar godep save
, godep
irá salvar uma lista de suas dependências atuais em Godeps/Godeps.json
e então copiar seu código fonte para a pasta vendor
. Também é importante notar que você precisa ter essas dependências instaladas em sua máquina para que godep
possa copiá-las.
Agora você pode submeter a pasta vendor
e seu conteúdo para garantir que todos terão as mesmas versões dos mesmos pacotes sempre que rodar seu pacote.
Outro comando interessante é godep restore
, que irá instalar as versões especificadas no seu ficheiro Godeps/Godeps.json
para o seu ficheiro $GOPATH
.
Para actualizar uma dependência basta actualizá-la usando go get -u
(como falámos anteriormente) e depois correr godep save
para godep
actualizar o ficheiro Godeps/Godeps.json
e copiar os ficheiros necessários para o directório vendor
.
A Few Thoughts on the Way Go Handles Dependencies
No final deste post no blog, eu também gostaria de adicionar minha própria opinião sobre a maneira como Go lida com dependências.
Eu acho que a escolha de Go de usar repositórios externos para lidar com os namespaces dos pacotes foi ótima porque torna a resolução de pacotes inteira muito mais simples ao unir os conceitos de sistema de arquivos com os conceitos de namespace. Isso também faz todo o ecossistema funcionar de forma independente porque agora temos uma forma descentralizada de buscar pacotes.
No entanto, o gerenciamento descentralizado de pacotes tem um custo, que não está sendo capaz de controlar todos os nós que fazem parte dessa “rede”. Se alguém decide que vai tirar o seu pacote de github
, por exemplo, então centenas, milhares ou até milhões de compilações podem começar a falhar de repente. Mudanças de nome também podem ter o mesmo efeito.
Considerando os objetivos principais de Go isto faz total sentido e é uma troca totalmente justa. Go tem tudo a ver com convenção ao invés de configuração e não há uma maneira mais simples de lidar com dependências do que atualmente.
Obviamente, algumas melhorias poderiam ser feitas, como usar tags git para buscar versões específicas de pacotes e permitir aos usuários especificar quais versões seus pacotes irão usar. Também seria legal poder ir buscar essas dependências ao invés de verificá-las no controle do código fonte. Isso nos permitiria evitar diffs sujos contendo apenas alterações no código no diretório vendor
e tornar todo o repositório mais limpo e leve.
Entre em contato!
Se você tiver alguma dúvida, pensamento ou se discordar de alguma coisa que escrevi, por favor, compartilhe comigo nos comentários abaixo ou entre em contato comigo em @thewizardlucas no twitter. Eu adoraria ouvir o que você tem a dizer e fazer qualquer correção se eu cometi algum erro.
Finalmente, mas não menos importante, certifique-se de dar uma olhada nesta incrível palestra de Wisdom Omuya sobre a GopherCon 2016 na qual ele explica como Go Vendoring funciona e também aponta alguns detalhes sobre seu funcionamento interno.
Obrigado por ler isto!