Este tutorial apresenta os conceitos básicos do Bazel mostrando como criar
um projeto Go (Golang). Você vai aprender a configurar seu espaço de trabalho, criar um pequeno
programa, importar uma biblioteca e executar o teste dela. Ao longo do caminho, você vai aprender os principais
conceitos do Bazel, como targets e arquivos BUILD
.
Tempo estimado de conclusão: 30 minutos
Antes de começar
Instalar o Bazel
Antes de começar, instale o Bazel, se ainda não tiver feito isso.
Para verificar se o Bazel está instalado, execute bazel version
em qualquer diretório.
Instalar o Go (opcional)
Não é necessário instalar o Go para criar projetos do Go com o Bazel. O conjunto de regras do Bazel Go faz o download automático e usa uma cadeia de ferramentas Go em vez da instalada na sua máquina. Isso garante que todos os desenvolvedores em um projeto sejam criados com a mesma versão do Go.
No entanto, talvez você ainda queira instalar uma cadeia de ferramentas do Go para executar comandos como go
get
e go mod tidy
.
Para verificar se o Go está instalado, execute go version
em qualquer diretório.
Fazer o download do projeto de exemplo
Os exemplos do Bazel são armazenados em um repositório do Git. Portanto, será necessário instalar o Git se você ainda não fez isso. Para fazer o download do repositório de exemplos, execute este comando:
git clone https://github.com/bazelbuild/examples
O projeto de exemplo deste tutorial está no diretório examples/go-tutorial
.
Confira o que ele contém:
go-tutorial/
└── stage1
└── stage2
└── stage3
Há três subdiretórios (stage1
, stage2
e stage3
), cada um para uma
seção diferente deste tutorial. Cada etapa é baseada na anterior.
Criação com o Bazel
Comece no diretório stage1
, onde vamos encontrar um programa. Podemos
criá-lo com bazel build
e, em seguida, executá-lo:
$ cd go-tutorial/stage1/
$ bazel build //:hello
INFO: Analyzed target //:hello (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:hello up-to-date:
bazel-bin/hello_/hello
INFO: Elapsed time: 0.473s, Critical Path: 0.25s
INFO: 3 processes: 1 internal, 2 darwin-sandbox.
INFO: Build completed successfully, 3 total actions
$ bazel-bin/hello_/hello
Hello, Bazel! 💚
Também podemos criar e executar o programa com um único comando bazel run
:
$ bazel run //:hello
bazel run //:hello
INFO: Analyzed target //:hello (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:hello up-to-date:
bazel-bin/hello_/hello
INFO: Elapsed time: 0.128s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/hello_/hello
Hello, Bazel! 💚
Noções básicas sobre a estrutura do projeto
Confira o projeto que acabamos de criar.
hello.go
contém o código-fonte Go do programa.
package main
import "fmt"
func main() {
fmt.Println("Hello, Bazel! 💚")
}
BUILD
contém algumas instruções para o Bazel, informando o que queremos criar.
Normalmente, você grava um arquivo como este em cada diretório. Para este projeto, temos um único destino go_binary
que cria nosso programa a partir de hello.go
.
load("@rules_go//go:def.bzl", "go_binary")
go_binary(
name = "hello",
srcs = ["hello.go"],
)
O MODULE.bazel
rastreia as dependências do projeto. Ele também marca o diretório raiz
do projeto, então você só vai gravar um arquivo MODULE.bazel
por projeto. Ele
serve a uma finalidade semelhante ao arquivo go.mod
do Go. Você não precisa de um arquivo
go.mod
em um projeto do Bazel, mas pode ser útil ter um para
continuar usando go get
e go mod tidy
para o gerenciamento de dependências. O
conjunto de regras do Bazel Go pode importar dependências de go.mod
, mas vamos abordar isso em
outro tutorial.
Nosso arquivo MODULE.bazel
contém uma única dependência em
rules_go, o conjunto de regras do Go. Precisamos
dessa dependência porque o Bazel não tem suporte integrado para Go.
bazel_dep(
name = "rules_go",
version = "0.50.1",
)
Por fim, MODULE.bazel.lock
é um arquivo gerado pelo Bazel que contém hashes
e outros metadados sobre nossas dependências. Ele inclui dependências implícitas
adicionadas pelo próprio Bazel, então é bastante longo e não será mostrado aqui. Assim como
go.sum
, confirme o arquivo MODULE.bazel.lock
no controle de origem para
garantir que todos no projeto tenham a mesma versão de cada dependência. Não
é necessário editar MODULE.bazel.lock
manualmente.
Entender o arquivo BUILD
A maior parte da sua interação com o Bazel será feita por arquivos BUILD
(ou
equivalentes, arquivos BUILD.bazel
). Por isso, é importante entender o que eles
fazem.
Os arquivos BUILD
são escritos em uma linguagem de script chamada
Starlark, um subconjunto limitado do Python.
Um arquivo BUILD
contém uma lista de
alvos. Um destino é algo
que o Bazel pode criar, como um binário, uma biblioteca ou um teste.
Um destino chama uma função de regra com uma lista de
atributos para descrever o que
precisa ser criado. Nosso exemplo tem dois atributos: name
identifica o destino na
linha de comando, e srcs
é uma lista de caminhos de arquivos de origem (separados por barra,
relativos ao diretório que contém o arquivo BUILD
).
Uma regra informa ao Bazel como criar um
destino. No nosso exemplo, usamos a regra
go_binary
. Cada regra define ações
(comandos) que geram um conjunto de arquivos de saída. Por exemplo, go_binary
define
ações de compilação e vinculação do Go que produzem um arquivo de saída executável.
O Bazel tem regras integradas para algumas linguagens, como Java e C++. A documentação está disponível na Encyclopedia do Build (link em inglês). Você pode encontrar conjuntos de regras para muitos outros idiomas e ferramentas no registro central do Bazel (BCR, na sigla em inglês).
Adicionar uma biblioteca
Vá para o diretório stage2
, onde vamos criar um novo programa que
imprime sua fortuna. Esse programa usa um pacote Go separado como uma biblioteca que
seleciona uma previsão de uma lista predefinida de mensagens.
go-tutorial/stage2
├── BUILD
├── MODULE.bazel
├── MODULE.bazel.lock
├── fortune
│ ├── BUILD
│ └── fortune.go
└── print_fortune.go
fortune.go
é o arquivo de origem da biblioteca. A biblioteca fortune
é um
pacote Go separado, então os arquivos de origem estão em um diretório separado. O Bazel
não exige que você mantenha pacotes Go em diretórios separados, mas é uma
convenção forte no ecossistema Go, e seguir essa regra ajuda a manter a
compatibilidade com outras ferramentas Go.
package fortune
import "math/rand"
var fortunes = []string{
"Your build will complete quickly.",
"Your dependencies will be free of bugs.",
"Your tests will pass.",
}
func Get() string {
return fortunes[rand.Intn(len(fortunes))]
}
O diretório fortune
tem o próprio arquivo BUILD
, que informa ao Bazel como criar
esse pacote. Usamos go_library
aqui em vez de go_binary
.
Também precisamos definir o atributo importpath
como uma string com a qual a
biblioteca pode ser importada para outros arquivos de origem do Go. Esse nome precisa ser o
caminho do repositório (ou caminho do módulo) concatenado com o diretório dentro do
repositório.
Por fim, precisamos definir o atributo visibility
como ["//visibility:public"]
.
visibility
pode ser definido em qualquer
destino. Ele determina quais pacotes do Bazel podem depender desse destino. No nosso
caso, queremos que qualquer destino possa depender dessa biblioteca. Por isso, usamos o
valor especial //visibility:public
.
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "fortune",
srcs = ["fortune.go"],
importpath = "github.com/bazelbuild/examples/go-tutorial/stage2/fortune",
visibility = ["//visibility:public"],
)
É possível criar essa biblioteca com:
$ bazel build //fortune
Em seguida, confira como print_fortune.go
usa esse pacote.
package main
import (
"fmt"
"github.com/bazelbuild/examples/go-tutorial/stage2/fortune"
)
func main() {
fmt.Println(fortune.Get())
}
print_fortune.go
importa o pacote usando a mesma string declarada no
atributo importpath
da biblioteca fortune
.
Também precisamos declarar essa dependência para o Bazel. Este é o arquivo BUILD
no
diretório stage2
.
load("@rules_go//go:def.bzl", "go_binary")
go_binary(
name = "print_fortune",
srcs = ["print_fortune.go"],
deps = ["//fortune"],
)
Você pode executar isso com o comando abaixo.
bazel run //:print_fortune
O destino print_fortune
tem um atributo deps
, uma lista de outros destinos
dos quais ele depende. Ele contém "//fortune"
, uma string de rótulo que se refere ao destino
no diretório fortune
com o nome fortune
.
O Bazel exige que todos os destinos declarem as dependências explicitamente com
atributos como deps
. Isso pode parecer complicado, já que as dependências também
são especificadas nos arquivos de origem, mas a clareza do Bazel dá uma vantagem. O Bazel
cria um gráfico de ações
que contém todos os comandos, entradas e saídas antes de executar qualquer comando,
sem ler nenhum arquivo de origem. O Bazel pode armazenar em cache os resultados de ação ou enviar
ações para execução remota sem a lógica
integrada específica da linguagem.
Como compreender os rótulos
Um rótulo é uma string que o Bazel usa
para identificar um destino ou um arquivo. Os rótulos são usados em argumentos de linha de comando e em
atributos de arquivo BUILD
, como deps
. Já vimos alguns, como //fortune
,
//:print-fortune
e @rules_go//go:def.bzl
.
Um rótulo tem três partes: um nome de repositório, um nome de pacote e um nome de destino (ou arquivo).
O nome do repositório é escrito entre @
e //
e é usado para se referir a um
destino de um módulo Bazel diferente. Por motivos históricos, módulo e
repositório às vezes são usados como sinônimos. No rótulo,
@rules_go//go:def.bzl
, o nome do repositório é rules_go
. O nome do repositório
pode ser omitido ao se referir a destinos no mesmo repositório.
O nome do pacote é escrito entre //
e :
e é usado para se referir a um
destino de um pacote Bazel diferente. No rótulo @rules_go//go:def.bzl
,
o nome do pacote é go
. Um pacote
do Bazel é um conjunto de arquivos e
alvos definidos por um arquivo BUILD
ou BUILD.bazel
no diretório de nível superior.
O nome do pacote é um caminho separado por barra do diretório raiz do módulo
(que contém MODULE.bazel
) para o diretório que contém o arquivo BUILD
. Um
pacote pode incluir subdiretórios, mas apenas se eles não contêm arquivos BUILD
que definem os próprios pacotes.
A maioria dos projetos Go tem um arquivo BUILD
por diretório e um pacote Go por
arquivo BUILD
. O nome do pacote em um rótulo pode ser omitido ao se referir a
destinos no mesmo diretório.
O nome do destino é escrito após :
e se refere a um destino em um pacote.
O nome de destino pode ser omitido se for igual ao último componente do
nome do pacote. Por exemplo, //a/b/c:c
é igual a //a/b/c
, //fortune:fortune
é igual a //fortune
.
Na linha de comando, é possível usar ...
como um curinga para se referir a todos os destinos
em um pacote. Isso é útil para criar ou testar todos os destinos em um
repositório.
# Build everything
$ bazel build //...
Testar seu projeto
Em seguida, vá para o diretório stage3
, onde vamos adicionar um teste.
go-tutorial/stage3
├── BUILD
├── MODULE.bazel
├── MODULE.bazel.lock
├── fortune
│ ├── BUILD
│ ├── fortune.go
│ └── fortune_test.go
└── print-fortune.go
fortune/fortune_test.go
é o novo arquivo de origem de teste.
package fortune
import (
"slices"
"testing"
)
// TestGet checks that Get returns one of the strings from fortunes.
func TestGet(t *testing.T) {
msg := Get()
if i := slices.Index(fortunes, msg); i < 0 {
t.Errorf("Get returned %q, not one the expected messages", msg)
}
}
Esse arquivo usa a variável fortunes
não exportada, então ele precisa ser compilado
no mesmo pacote Go que fortune.go
. Confira o arquivo BUILD
para saber
como isso funciona:
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "fortune",
srcs = ["fortune.go"],
importpath = "github.com/bazelbuild/examples/go-tutorial/stage3/fortune",
visibility = ["//visibility:public"],
)
go_test(
name = "fortune_test",
srcs = ["fortune_test.go"],
embed = [":fortune"],
)
Temos um novo destino fortune_test
que usa a regra go_test
para compilar e
vincular um executável de teste. O go_test
precisa compilar fortune.go
e
fortune_test.go
com o mesmo comando. Portanto, usamos o atributo embed
aqui para incorporar os atributos do destino fortune
em
fortune_test
. O embed
é mais usado com go_test
e go_binary
,
mas também funciona com go_library
, que às vezes é útil para código
gerado.
Você pode estar se perguntando se o atributo embed
está relacionado ao pacote
embed
do Go, que é usado para acessar arquivos de dados
copiados para um executável. Essa é uma colisão de nome infeliz: o atributo embed
do rules_go foi introduzido antes do pacote embed
do Go. Em vez disso, rules_go
usa o embedsrcs
para listar arquivos que podem ser carregados com o pacote embed
.
Tente executar nosso teste com bazel test
:
$ bazel test //fortune:fortune_test
INFO: Analyzed target //fortune:fortune_test (0 packages loaded, 0 targets configured).
INFO: Found 1 test target...
Target //fortune:fortune_test up-to-date:
bazel-bin/fortune/fortune_test_/fortune_test
INFO: Elapsed time: 0.168s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
//fortune:fortune_test PASSED in 0.3s
Executed 0 out of 1 test: 1 test passes.
There were tests whose specified size is too big. Use the --test_verbose_timeout_warnings command line option to see which ones these are.
Você pode usar o curinga ...
para executar todos os testes. O Bazel também vai criar destinos
que não são testes, para que erros de compilação possam ser detectados mesmo em pacotes que não
têm testes.
$ bazel test //...
Conclusão e leitura complementar
Neste tutorial, criamos e testamos um pequeno projeto Go com o Bazel e aprendemos alguns conceitos principais do Bazel.
- Para começar a criar outros aplicativos com o Bazel, consulte os tutoriais para C++, Java, Android e iOS.
- Você também pode conferir a lista de regras recomendadas para outros idiomas.
- Para mais informações sobre Go, consulte o módulo rules_go, especialmente a documentação Core Go rules.
- Para saber mais sobre como trabalhar com módulos do Bazel fora do seu projeto, consulte dependências externas. Para saber como depender de módulos e toolchains do Go pelo sistema de módulo do Bazel, consulte Go com bzlmod.