Este tutorial apresenta os conceitos básicos do Bazel mostrando como criar um projeto em 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 conceitos importantes do Bazel, como destinos e arquivos BUILD
.
Tempo estimado de conclusão: 30 minutos
Antes de começar
Instalar o Bazel
Antes de começar, instale o Bazel, caso ainda não tenha 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 em Go com o Bazel. O conjunto de regras do Bazel Go faz o download e usa automaticamente uma cadeia de ferramentas do Go em vez da cadeia de ferramentas instalada na sua máquina. Isso garante que todos os desenvolvedores de um projeto criem 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.
Acessar o projeto de exemplo
Os exemplos do Bazel são armazenados em um repositório Git. Portanto, instale o Git se ainda não tiver feito isso. Para fazer o download do repositório de exemplos, execute este comando:
git clone https://github.com/bazelbuild/examples
O projeto de amostra 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 se baseia na anterior.
Criação com o Bazel
Comece no diretório stage1
, onde vamos encontrar um programa. Podemos
criar e executar com bazel build
:
$ 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 é possível 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! 💚
Entender 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 em 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 escrever um arquivo MODULE.bazel
por projeto. Ele
tem uma finalidade semelhante ao arquivo go.mod
do Go. Não é necessário ter 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 gerenciamento de dependências. O conjunto de regras do Bazel Go pode importar dependências de go.mod
, mas isso será abordado em outro tutorial.
Nosso arquivo MODULE.bazel
contém uma única dependência de
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 = &q
uot;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. Por isso, é bastante longo e não será mostrado aqui. Assim como o
go.sum
, é necessário confirmar 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á por arquivos BUILD
(ou, de forma equivalente, 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 de Python.
Um arquivo BUILD
contém uma lista de
metas. 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 deve 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 barras, 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 executável.
O Bazel tem regras integradas para algumas linguagens, como Java e C++. Encontre a documentação na Build Encyclopedia (em inglês). Você pode encontrar conjuntos de regras para muitas outras linguagens e ferramentas no Registro central do Bazel (BCR).
Adicionar uma biblioteca
Acesse o diretório stage2
, onde vamos criar um novo programa que
imprime sua sorte. Esse programa usa um pacote Go separado como uma biblioteca que
seleciona uma mensagem de uma lista predefinida.
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 dela estão em um diretório separado. O Bazel não exige que você mantenha os pacotes Go em diretórios separados, mas é uma convenção forte no ecossistema Go, e segui-la vai ajudar você 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 {
retu
rn 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
em vez de go_binary
.
Também precisamos definir o atributo importpath
como uma string com que a
biblioteca pode ser importada para outros arquivos de origem do Go. Esse nome precisa ser o caminho do repositório (ou 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&q
uot;,
visibility = ["//visibility:public"],
)
É possível criar essa biblioteca com:
$ bazel build //fortune
Em seguida, veja como o print_fortune.go
usa esse pacote.
package main
import (
"fmt"
"github.com/bazelbuild/examples/go-tutorial/stage2/fortune"
)
func main() {
fmt.Print
ln(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 ao 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 de que ele depende. Ele contém "//fortune"
, uma string de rótulo que se refere ao destino
no diretório fortune
chamado 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 explicitude do Bazel oferece 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. Em seguida, o Bazel pode armazenar em cache os resultados da ação ou enviar ações para execução remota sem 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 diferente do Bazel (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 uma
meta 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 destinos definidos por um arquivo BUILD
ou BUILD.bazel
no diretório de nível superior.
O nome do pacote é um caminho separado por barras do diretório raiz do módulo (que contém MODULE.bazel
) até o diretório que contém o arquivo BUILD
. Um
pacote pode incluir subdiretórios, mas apenas se eles não contiverem 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 depois de :
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 (então //a/b/c:c
é igual a //a/b/c
; //fortune:fortune
é igual a //fortune
).
Na linha de comando, use ...
como um caractere curinga para se referir a todas as metas em um pacote. Isso é útil para criar ou testar todos os destinos em um
repositório.
# Build everything
$ bazel build //...
Testar o 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
é nosso 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.Inde<x(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. Portanto, 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. Por isso, usamos o atributo embed
para incorporar os atributos do destino fortune
em
fortune_test
. embed
é mais usado com go_test
e go_binary
,
mas também funciona com go_library
, que às vezes é útil para código
gerado.
Talvez você esteja se perguntando se o atributo embed
está relacionado ao pacote embed
do Go, que é usado para acessar arquivos de dados copiados em um executável. Essa é uma colisão de nomes infeliz: o atributo embed
de 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 caractere curinga ...
para executar todos os testes. O Bazel também vai criar destinos que não são testes. Assim, é possível detectar erros de compilação até mesmo em pacotes que não têm testes.
$ bazel test //...
Conclusão e sugestões de leitura
Neste tutorial, criamos e testamos um pequeno projeto em Go com o Bazel e aprendemos alguns conceitos básicos do Bazel ao longo do caminho.
- 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 Regras principais do Go.
- Para saber mais sobre como trabalhar com módulos do Bazel fora do seu projeto, consulte dependências externas. Em especial, para informações sobre como depender de módulos e toolchains do Go pelo sistema de módulos do Bazel, consulte Go com bzlmod.