Novidade do Go 1.21: Toolchains
Na versão 1.21 da linguagem uma novidade importante foi implementada. Segundo a documentação:
A partir do Go 1.21, a distribuição Go consiste em no comando
go
e um conjunto de ferramentas (toolchain
) incluído, que é a biblioteca padrão, bem como o compilador, montador e outras ferramentas. O comandogo
pode usar seutoolchain
padrão, bem como outras versões encontradas no PATH local ou baixadas conforme necessário.
Para validar como funciona essa nova funcionalidade eu fiz uma prova de conceito, que vou descrever neste post.
Na minha máquina pessoal eu tinha a seguinte versão do go
:
go version
go version go1.21.0 darwin/arm64
Criei 3 libs, cada uma com uma versão diferente de requisito. Nos comandos abaixo é possível ver o conteúdo de cada go.mod
das libs:
❯ cd l1 ; cat go.mod; cd ..
module github.com/eminetto/l1
go 1.19.0
❯ cd l2; cat go.mod; cd ..
module github.com/eminetto/l2
go 1.20.0
❯ cd l3; cat go.mod; cd ..
module github.com/eminetto/l3
go 1.21.1
A partir da versão 1.21 a linha go 1.20.0
do arquivo go.mod
, neste exemplo da lib l2
, indica qual é a versão mínima da linguagem que é necessária para que ela seja compilada.
A seguir eu criei um projeto de exemplo, que vai importar as três bibliotecas. O diagrama a seguir mostra as dependências:
O main.go
do proj
contém:
package main
import (
"fmt"
"github.com/eminetto/l1"
"github.com/eminetto/l2"
"github.com/eminetto/l3"
)
func main() {
fmt.Println(l1.Sum(1, 2))
fmt.Println(l2.Sum(1, 2))
fmt.Println(l3.Sum(1, 2))
}
O go.mod
do projeto contém:
module github.com/eminetto/proj
go 1.21.0
require (
github.com/eminetto/l1 v0.0.0-20231012141607-826d3914801f
github.com/eminetto/l2 v0.0.0-20231012141624-28120aab8596
github.com/eminetto/l3 v0.0.0-20231012141624-2810bakab896
)
Executei o projeto e funcionou sem erros:
❯ go run main.go
3
3
3
A seguir eu simulei o processo de atualização de uma das libs.
Para isso eu atualizei a lib l3
para uma versão mais nova do Go:
❯ cd l3; cat go.mod
module github.com/eminetto/l3
go 1.21.3
Fiz o push para o repositório e criei uma nova versão (v0.0.1
).
Segundo a documentação (tradução minha):
O toolchain recusa-se a carregar um módulo ou workspace que declara uma versão mínima de Go maior do que a versão atual do toolchain.
Para validar isso, no projeto eu atualizei a versão da l3
:
❯ go get github.com/eminetto/l3@v0.0.1
go: downloading github.com/eminetto/l3 v0.0.1
go: github.com/eminetto/l3@v0.0.1 requires go >= 1.21.3; switching to go1.21.3
go: upgraded go 1.21.0 => 1.21.3
go: upgraded github.com/eminetto/l3 v0.0.0-20231012141629-a747d5b44b93 => v0.0.1
A novidade é o seguinte trecho:
go: github.com/eminetto/l3@v0.0.1 requires go >= 1.21.3; switching to go1.21.3
go: upgraded go 1.21.0 => 1.21.3
O que aconteceu foi que a versão do Go instalado na minha máquina foi automaticamente atualizado para a versão 1.21.3
:
❯ go version
go version go1.21.3 darwin/arm64
E o go.mod
do projeto também foi atualizado para a versão 1.21.3
:
❯ cat go.mod
module github.com/eminetto/proj
go 1.21.3
require (
github.com/eminetto/l1 v0.0.0-20231012141607-826d3914801f
github.com/eminetto/l2 v0.0.0-20231012141624-28120aab8596
github.com/eminetto/l3 v0.0.1
)
E o projeto continua executando sem erros. O comando go
foi atualizado para a versão mínima necessária para poder importar TODAS as libs e executar sem erros. Neste caso atualizou para a 1.21.3
porque era a necessária para rodar a l3
, já que as demais estão abaixo da versão que eu tinha no momento instalada na minha máquina (1.21.0
). O interessante é que ao invés de quebrar o projeto ele foi adaptado para executar com as versões mínimas necessárias.
Outro ponto que observei é que agora existe uma versão de Go para o projeto e outra para o restante do sistema operacional:
❯ cd proj
Developer/post-toolchain/proj
❯ go version
go version go1.21.3 darwin/arm64
Developer/post-toolchain/proj
❯ cd ..
~/Developer/post-toolchain
❯ go version
go version go1.21.0 darwin/arm64
O comando go
identifica que para o diretório proj
é necessária uma versão específica da linguagem. E todo esse gerenciamento é feito de maneira automática.
Vamos agora pensar em um exemplo um pouco mais complexo. Vamos supor que uma das libs, por exemplo a l2
, fosse atualizada para:
module github.com/eminetto/l2
go 1.24rc1
O comando go
obtém a lista de toolchains
disponíveis e descobre que os lançamentos mais recentes são Go 1.28.3
, Go 1.27.9
, e o Go 1.29rc2
. Nesta situação, o comando go
escolherá Go 1.27.9
.
Se a l2
exigisse Go 1.28
ou posterior, o comando go
escolheria Go 1.28.3
, porque Go 1.27.9
é muito antigo. Se a l2
exigisse Go 1.29
ou posterior, seria escolhida o Go 1.29rc2
, porque as outras opções são muito antigas.
Ao executar o comando go get github.com/eminetto/l2@v0.0.1
(a versão mais nova do pacote), o arquivo go.mod
do projeto seria atualizado e uma nova linha seria adicionada, com o toolchain
escolhido:
❯ cat go.mod
module github.com/eminetto/proj
go 1.27.9
toolchain go1.27.9
require (
github.com/eminetto/l1 v0.0.0-20231012141607-826d3914801f
github.com/eminetto/l2 v0.0.1
github.com/eminetto/l3 v0.0.1
)
Mais detalhes sobre as motivações por trás desta importante feature, a nomenclatura das versões, bem como configurações avançadas de comportamento podem ser vistas no documentação oficial da linguagem.
Este é mais um exemplo do cuidado que a equipe da linguagem tem em manter a compatibilidade entre as versões passadas e futuras. Com esta funcionalidade vai ser possível garantir a longevidade de aplicações escritas em Go, o que é muito importante para o futuro da linguagem em ambientes cada vez mais complexos.
Agradecimentos aos amigos Matheus Mina, Tiago Temporin e Eduardo Hitek pela revisão do texto e sugestões de melhorias.