Bazel é um ferramenta criada e mantida pela Google que ajuda no processo de build de várias linguagens, sendo uma delas nosso querido Golang.
Duas de suas grandes vantagens são:
- Build de multiplas aplicações em monorepo sem precisar ficar entrando e saindo de pastas.
- Cache remoto das etapas de build (para mais detalhes, leia o post “Como Bazel funciona internamente“).
Embora para maioria das linguagens toda criação e manutenção dos arquivos do Bazel tenha que ser feita manualmente, para o Go temos o gazelle, uma ferramenta que nos auxilia nesse processo.
Se você ainda não tem o Bazel instalado na sua máquina, siga o tutorial do próprio site oficial segundo o OS que você utiliza. Se você já tem, execute um bazel version
para garantir que você está utilizando a última versão (4.2.2).
Como aplicação exemplo, vamos utilizar o mesmo código do post Implementando uma API com gorilla/mux, mas separando as duas funções de “handler” em um novo package chamado handlers.
O estrutura de pastas/arquivos deve ser a seguinte:
- handlers
- hello.go
- person.go
- go.mod
- go.sum
- main.go
Para que você não precise olhar no outro post, segue como devem ficar os arquivos:
handlers/hello.go
package handlers import ( "encoding/json" "fmt" "net/http" "github.com/gorilla/mux" ) func HandleHello(w http.ResponseWriter, rq *http.Request) { vars := mux.Vars(rq) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "message": fmt.Sprintf("Hello %s", vars["name"]), }) }
handlers/person.go
package handlers import ( "encoding/json" "fmt" "net/http" ) type Person struct { Name string `json:"name"` Age uint8 `json:"age"` } func HandlePerson(w http.ResponseWriter, rq *http.Request) { var p Person err := json.NewDecoder(rq.Body).Decode(&p) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } fmt.Fprintf(w, "Nome: %s - Idade: %d\n", p.Name, p.Age) }
main.go
package main import ( "net/http" "github.com/aprendagolang/gorilla/handlers" "github.com/gorilla/mux" ) func main() { r := mux.NewRouter() r.HandleFunc("/{name}", handlers.HandleHello).Methods("GET") r.HandleFunc("/person", handlers.HandlePerson).Methods("POST") http.ListenAndServe(":8080", r) }
Agora é só executar go mod init github.com/aprendagolang/gorilla
para gerar o arquivo go.mod, e go mod tidy
para gerar o go.sum.
Com a API pronta, vamos começar criar os arquivos de configuração do Bazel.
Quando usamos o gazelle para nos auxiliar, basicamente precisamos criar dois arquivos no root do nosso projeto. Esses arquivos são o WORKSPACE, que é responsável por toda gestão das dependências do Bazel, e o arquivo BUILD, que é onde vamos definir algumas regras.
Com a criação desses dois arquivos, nossa estrutura de pastas e arquivos deve ser a seguinte agora.
- handlers
- hello.go
- person.go
- BUILD
- go.mod
- go.sum
- main.go
- WORKSPACE
No arquivo WORKSPACE, antes de qualquer coisa, precisamos importar/fazer load da função do Bazel chamada http_archive
. Essa função será utilizada para fazer download das regras do Go e do gazelle.
Após o download dessas regras, vamos fazer o load das funções go_register_toolchains
e go_rules_dependencies
, que estão presentes nas regras do Go, e as funções gazelle_dependencies
e go_repository
que estão presentes nas regras do gazelle.
Por último, vamos chamar as funções go_rules_dependencies()
, go_register_toolchains(version = "1.17.2")
e gazelle_dependencies()
.
Com todas essas etapas descritas, o arquivo WORKSPACE deve ficar assim:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "io_bazel_rules_go", sha256 = "2b1641428dff9018f9e85c0384f03ec6c10660d935b750e3fa1492a281a53b0f", urls = [ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.29.0/rules_go-v0.29.0.zip", "https://github.com/bazelbuild/rules_go/releases/download/v0.29.0/rules_go-v0.29.0.zip", ], ) http_archive( name = "bazel_gazelle", sha256 = "de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb", urls = [ "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz", "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz", ], ) load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository") go_rules_dependencies() go_register_toolchains(version = "1.17.2") gazelle_dependencies()
Com as dependências definidas e configuradas no WORKSPACE, vamos para o arquivo BUILD.
Nesse arquivo fazer o load da função gazelle
, definir o gazelle:prefix
com o nome do nosso módulo e chamar a função que carregamos no inicio.
load("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:prefix github.com/aprendagolang/gorilla gazelle(name = "gazelle")
Agora vamos executar o comando bazel run gazelle
e ver a mágica acontecer.
Se você olhar na pasta handlers, vai ver que um arquivo BUILD.bazel foi criado com uma função go_library
toda configurada, com os arquivos do package e suas dependências.
Já no arquivo BUILD que havíamos criado no inicio, o gazelle adicionou uma função go_library
para configurar nosso main.go e suas dependências, e uma função go_binary
, onde aponta para a go_library que contém o ponto de entrada do nosso programa, ou seja, a função main.
Antes de executar nossa API, vamos executar um comando do Bazel para gerar/atualizar um arquivo deps.bzl com todas as dependências externas do nosso projeto.
bazel run //:gazelle -- update-repos -from_file=go.mod -to_macro=deps.bzl%go_dependencies
Se você olhar o arquivo WORKSPACE, verá que foram adicionadas as linhas:
load("//:deps.bzl", "go_dependencies") # gazelle:repository_macro deps.bzl%go_dependencies go_dependencies()
Agora que temos tudo configurado, podemos iniciar nossa API com o comando bazel run gorilla
. A primeira execução pode ser um pouco mais lenta, pois ele está gerando o cache das etapas de build. Mas se você parar a API e executar o comando novamente, verá que o build é praticamente instantâneo.
Na próxima parte, vamos adicionar as regras do Bazel para gerar uma imagem docker e subir para um registry. Então se inscreva em nossa newsletter para não ficar de fora!
Deixem suas dúvidas nos comentários.
Até a próxima!
[…] primeira parte desse tutorial, configuramos o Bazel e o gazelle em nosso projeto. Ao final conseguimos utilizar o […]