O próximo lançamento da versão 1.11 da linguagem de programação Go trará suporte experimental para módulos - um novo sistema de gerenciamento de dependências para Go. (tradução da nota: a liberação ocorreu )
Recentemente, eu já escrevi um pequeno post sobre isso . Desde então, algo mudou um pouco, e nos aproximamos do lançamento, então parece-me que chegou a hora de um novo artigo - vamos adicionar mais prática.
Então, eis o que faremos: crie um novo pacote e faça alguns lançamentos para ver como ele funciona.
Criação de módulo
Primeiro, crie nosso pacote. Vamos chamá-lo de testmod. Detalhe importante: o diretório do pacote deve ser colocado fora do seu $GOPATH
, porque, dentro dele, o suporte ao módulo é desativado por padrão . Os módulos Go são o primeiro passo para um abandono completo do $GOPATH
no futuro.
$ mkdir testmod $ cd testmod
Nosso pacote é bastante simples:
package testmod import "fmt"
O pacote está pronto, mas ainda não é um módulo . Vamos consertar.
$ go mod init github.com/robteix/testmod go: creating new go.mod: module github.com/robteix/testmod
Temos um novo arquivo chamado go.mod
no diretório do pacote com o seguinte conteúdo:
module github.com/robteix/testmod
Um pouco, mas é isso que transforma nosso pacote em um módulo .
Agora podemos enviar esse código para o repositório:
$ git init $ git add * $ git commit -am "First commit" $ git push -u origin master
Até agora, quem quiser usar nosso pacote se candidatará, go get
:
$ go get github.com/robteix/testmod
E este comando traria o código mais recente da ramificação master
. Essa opção ainda funciona, mas seria melhor se não o fizermos mais, porque agora "existe uma maneira melhor". Receber código diretamente da ramificação master
é, de fato, perigoso, pois nunca sabemos ao certo que os autores do pacote não fizeram alterações que “quebrarão” nosso código. Para resolver esse problema, os módulos Go foram inventados.
Uma pequena digressão sobre os módulos de versão
Os módulos Go são versionados, além de haver alguma especificidade de versões individuais. Você precisará se familiarizar com os conceitos subjacentes ao controle de versão semântico .
Além disso, o Go usa tags de repositório ao procurar versões, e algumas versões diferem das demais: por exemplo, as versões 2 e mais devem ter um caminho de importação diferente do das versões 0 e 1 (chegaremos a isso).
Por padrão, o Go baixa a versão mais recente, que possui uma tag disponível no repositório.
Esse é um recurso importante, pois pode ser usado ao trabalhar com a ramificação master
.
Para nós agora, é importante que, ao criar o lançamento do nosso pacote, precisamos colocar um rótulo com a versão no repositório.
Vamos fazer isso.
Fazendo seu primeiro lançamento
Nosso pacote está pronto e podemos "lançá-lo" para o mundo inteiro. Fazemos isso usando rótulos com versão. Deixe o número da versão ser 1.0.0:
$ git tag v1.0.0 $ git push --tags
Esses comandos criam uma tag no meu repositório do Github que marca o commit atual como versão 1.0.0.
O Go não insiste nisso, mas é uma boa idéia criar uma nova ramificação adicional ("v1") para a qual possamos enviar patches.
$ git checkout -b v1 $ git push -u origin v1
Agora podemos trabalhar no ramo master
sem nos preocuparmos com a possibilidade de quebrar nossa versão.
Usando nosso módulo
Vamos usar o módulo criado. Escreveremos um programa simples que importa nosso novo pacote:
package main import ( "fmt" "github.com/robteix/testmod" ) func main() { fmt.Println(testmod.Hi("roberto")) }
Até agora, você deve go get github.com/robteix/testmod
para baixar o pacote, mas com os módulos fica mais interessante. Primeiro, precisamos ativar o suporte ao módulo em nosso novo programa.
$ go mod init mod
Como você provavelmente esperava, com base no que leu anteriormente, um novo arquivo go.mod
apareceu no diretório com o nome do módulo:
module mod
A situação se torna ainda mais interessante quando tentamos montar nosso programa:
$ go build go: finding github.com/robteix/testmod v1.0.0 go: downloading github.com/robteix/testmod v1.0.0
Como você pode ver, o comando go
encontrou e baixou automaticamente o pacote importado pelo nosso programa.
Se verificarmos nosso arquivo go.mod
, veremos que algo mudou:
module mod require github.com/robteix/testmod v1.0.0
E temos outro novo arquivo chamado go.sum
, que contém os hashes dos pacotes para verificar a versão e os arquivos corretos.
github.com/robteix/testmod v1.0.0 h1:9EdH0EArQ/rkpss9Tj8gUnwx3w5p0jkzJrd5tRAhxnA= github.com/robteix/testmod v1.0.0/go.mod h1:UVhi5McON9ZLc5kl5iN2bTXlL6ylcxE9VInV71RrlO8=
Fazendo um lançamento de correção de bug
Agora, digamos que encontramos um problema em nosso pacote: não há pontuação na saudação!
Algumas pessoas ficam furiosas, porque nossa saudação amigável não é mais tão amigável.
Vamos corrigir isso e lançar uma nova versão:
// Hi returns a friendly greeting func Hi(name string) string { - return fmt.Sprintf("Hi, %s", name) + return fmt.Sprintf("Hi, %s!", name) }
Fizemos essa alteração diretamente na ramificação da v1
, porque ela não tem nada a ver com o que faremos a seguir na ramificação da v2
, mas na vida real, talvez você deva fazer essas alterações no master
e depois suportá-las na v1
. De qualquer forma, a correção deve estar na ramificação v1
e precisamos marcar isso como uma nova versão.
$ git commit -m "Emphasize our friendliness" testmod.go $ git tag v1.0.1 $ git push --tags origin v1
Atualização de Módulos
Por padrão, o Go não atualiza os módulos sem demanda. "E isso é bom", já que todos nós gostaríamos de previsibilidade em nossas construções. Se os módulos Go forem atualizados automaticamente sempre que uma nova versão for lançada, retornaremos à "idade das trevas antes do Go1.11". Mas não, precisamos dizer ao Go para atualizar os módulos para nós.
E faremos isso com a ajuda de nosso velho amigo - go get
:
execute go get -u
para usar a última versão secundária ou de patch menor (ou seja, o comando será atualizado da 1.0.0 para, digamos, 1.0.1 ou 1.1.0, se essa versão estiver disponível)
execute go get -u=patch
para usar a versão mais recente do patch (ou seja, o pacote será atualizado para 1.0.1, mas não para 1.1.0)
execute go get package@version
para atualizar para uma versão específica (por exemplo, github.com/robteix/testmod@v1.0.1
)
Não há nenhuma maneira nesta lista para atualizar para a versão principal mais recente. Há uma boa razão para isso, como veremos em breve.
Como nosso programa usou a versão 1.0.0 do nosso pacote e acabamos de criar a versão 1.0.1, qualquer um dos seguintes comandos nos atualizará para a 1.0.1:
$ go get -u $ go get -u=patch $ go get github.com/robteix/testmod@v1.0.1
Depois de iniciar (digamos, go get -u
), nosso go.mod
mudou:
module mod require github.com/robteix/testmod v1.0.1
Versão principal
De acordo com a especificação do controle de versão semântico, a versão principal difere da versão secundária. As versões principais podem quebrar a compatibilidade com versões anteriores. Do ponto de vista dos módulos Go, a versão principal é um pacote completamente diferente .
Pode parecer selvagem no começo, mas faz sentido: duas versões da biblioteca que são incompatíveis entre si são duas bibliotecas diferentes.
Vamos fazer uma grande mudança em nosso pacote. Suponha que, com o tempo, tenha ficado claro para nós que nossa API é muito simples, muito limitada para os casos de usuários, portanto, precisamos alterar a função Hi()
para aceitar o idioma de boas-vindas como parâmetro:
package testmod import ( "errors" "fmt" )
Os programas existentes que usam nossa API serão interrompidos porque: a) não passam o idioma como parâmetro eb) não esperam um retorno de erro. Nossa nova API não é mais compatível com a versão 1.x, portanto, conheça a versão 2.0.0.
Eu mencionei anteriormente que algumas versões têm recursos, e agora esse é o caso.
A versão 2 ou posterior deve alterar o caminho de importação. Agora, essas são bibliotecas diferentes.
Faremos isso adicionando um novo caminho com versão ao nome do nosso módulo.
module github.com/robteix/testmod/v2
Tudo o resto é o mesmo: empurre, coloque um rótulo que seja v2.0.0 (e, opcionalmente, faça uma ramificação v2)
$ git commit testmod.go -m $ git checkout -b v2 # optional but recommended $ echo > go.mod $ git commit go.mod -m $ git tag v2.0.0 $ git push --tags origin v2 # or master if we don't have a branch
Atualização da versão principal
Embora tenhamos lançado uma nova versão incompatível de nossa biblioteca, os programas existentes não foram interrompidos porque continuam a usar a versão 1.0.1.
go get -u
não fará o download da versão 2.0.0.
Mas, em algum momento, eu, como usuário da biblioteca, talvez queira atualizar para a versão 2.0.0, porque, por exemplo, sou um daqueles usuários que precisam de suporte para vários idiomas.
Para atualizar, preciso alterar meu programa de acordo:
package main import ( "fmt" "github.com/robteix/testmod/v2" ) func main() { g, err := testmod.Hi("Roberto", "pt") if err != nil { panic(err) } fmt.Println(g) }
Agora, quando executo o go build
, ele "fecha" e baixa a versão 2.0.0 para mim. Observe que, embora o caminho de importação agora termine em "v2", o Go ainda se refere ao módulo por seu nome real ("testmod").
Como eu disse, a versão principal é, sob todos os aspectos, um pacote diferente. Esses dois módulos Go não estão conectados de forma alguma. Isso significa que podemos ter duas versões incompatíveis em um binário:
package main import ( "fmt" "github.com/robteix/testmod" testmodML "github.com/robteix/testmod/v2" ) func main() { fmt.Println(testmod.Hi("Roberto")) g, err := testmodML.Hi("Roberto", "pt") if err != nil { panic(err) } fmt.Println(g) }
E isso elimina o problema comum com o gerenciamento de dependências quando as dependências dependem de versões diferentes da mesma biblioteca.
Colocamos as coisas em ordem
Vamos voltar à versão anterior, que usa apenas testmod 2.0.0 - se verificarmos o conteúdo de go.mod
, perceberemos algo:
module mod require github.com/robteix/testmod v1.0.1 require github.com/robteix/testmod/v2 v2.0.0
Por padrão, o Go não remove dependências do go.mod
até que você solicite. Se você tiver dependências que não são mais necessárias e deseja limpá-las, poderá usar o novo comando tidy
:
$ go mod tidy
Agora só temos as dependências que realmente usamos.
Vending
Por padrão, os módulos ignoram o vendor/
diretório. A idéia é livrar-se gradualmente das vendas 1 . Mas se ainda queremos adicionar as dependências "desanexadas" ao nosso controle de versão, podemos fazer o seguinte:
$ go mod vendor
A equipe criará o vendor/
diretório na raiz do nosso projeto, contendo o código fonte de todas as dependências.
No entanto, a go build
por padrão ainda ignora o conteúdo deste diretório. Se você deseja coletar dependências do vendor/
diretório, solicite explicitamente.
$ go build -mod vendor
Suponho que muitos desenvolvedores que desejam usar o vending executem go build
, como de costume, em suas máquinas e usem -mod vendor
em seu IC.
Novamente, os módulos Go estão se afastando da ideia de vender para usar proxies para módulos para aqueles que não desejam depender diretamente dos serviços de controle de versão upstream.
Existem maneiras de garantir que go
rede não esteja disponível (por exemplo, usando GOPROXY=off
), mas este é o tópico do próximo artigo.
Conclusão
O artigo pode parecer complicado para alguém, mas é porque tentei explicar muito de uma vez. A realidade é que os módulos Go são geralmente simples hoje - nós, como sempre, importamos o pacote para o nosso código, e a equipe go
faz o resto por nós. As dependências são carregadas automaticamente durante a montagem.
Os módulos também eliminam a necessidade de $GOPATH
, que foi um obstáculo para os novos desenvolvedores do Go que tiveram problemas para entender por que deveriam colocar algo em um diretório específico.
A venda (não oficial) foi descontinuada em favor do uso de um proxy. 1
Eu posso criar um artigo separado sobre proxies para os módulos Go.
Notas:
1 Acho que essa é uma expressão muito alta e alguns podem ter a impressão de que as vendas estão sendo removidas agora. Isto não é verdade. A venda ainda está funcionando, embora um pouco diferente do que antes. Aparentemente, há um desejo de substituir a venda por algo melhor, por exemplo, um proxy (não um fato). Até agora, isso é simplesmente a busca de uma solução melhor. A venda não desaparecerá até que um bom substituto seja encontrado (se houver).