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 conceitos importantes do
Bazel, como destinos e BUILD arquivos.
Tempo estimado de conclusão: 30 minutos
Antes de começar
Instalar o Bazel
Antes de começar, primeiro 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 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 de usar a 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 amostra
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/examplesO 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 estágio é criado com base no 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 bazel run comando:
$ 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 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ê escreve um arquivo como esse em cada diretório. Para esse projeto, nós
temos um único go_binary destino que cria nosso programa a partir de hello.go.
load("@rules_go//go:def.bzl", "go_binary")
go_binary(
name = "hello",
srcs = ["hello.go"],
)
MODULE.bazel acompanha as dependências do seu projeto. Ele também marca o diretório raiz do projeto. Portanto, você só vai escrever um arquivo MODULE.bazel por projeto. Ele
tem uma finalidade semelhante ao arquivo go.mod do Go. Na verdade, você não precisa de um
go.mod arquivo em um projeto do Bazel, mas ainda pode ser útil ter um para que
você possa 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 de
rules_go, o conjunto de regras do Go. Precisamos
dessa dependência porque o Bazel não tem suporte integrado para o 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. Portanto, é bastante longo e não vamos mostrá-lo aqui. Assim como
go.sum, você precisa confirmar o arquivo MODULE.bazel.lock no controle de origem para
garantir que todos no seu projeto recebam a mesma versão de cada dependência. Não é necessário editar MODULE.bazel.lock manualmente.
Noções básicas sobre o arquivo BUILD
A maior parte da sua interação com o Bazel será por meio de arquivos BUILD (ou
equivalentemente, arquivos BUILD.bazel). Portanto, é 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
destinos. 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 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
go_binary
regra. 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 delas pode ser encontrada na Enciclopédia de build. Você pode encontrar conjuntos de regras para muitas outras linguagens e ferramentas no Registro central do Bazel (BCR, na sigla em inglês).
Adicionar uma biblioteca
Passe para 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 sorte 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. Portanto, 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 segui-la vai ajudar você a manter a
compatibilidade com outras ferramentas do 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
este 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. Portanto, 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"],
)
Você pode criar essa biblioteca com:
$ bazel build //fortune
Em seguida, veja 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
importpath atributo da fortune biblioteca.
Também precisamos declarar essa dependência para o Bazel. Confira o BUILD arquivo no
stage2 diretório.
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 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 são também
especificadas em 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 específica da linguagem integrada.
Noções básicas sobre 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
BUILD atributos de arquivo 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 um
destino de um pacote diferente do Bazel. 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) para o diretório que contém o arquivo BUILD. Um
pacote pode incluir subdiretórios, mas apenas se eles também não contiverem BUILD
arquivos que definam os próprios pacotes.
A maioria dos projetos Go tem um arquivo BUILD por diretório e um pacote Go por
BUILD arquivo. 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 dentro de um pacote.
O nome do destino pode ser omitido se for o mesmo que o último componente do
nome do pacote (portanto, //a/b/c:c é o mesmo que //a/b/c; //fortune:fortune é
o mesmo que //fortune).
Na linha de comando, você pode usar ... como um caractere 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 o projeto
Em seguida, acesse 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.Index(fortunes, msg); i < 0 {
t.Errorf("Get returned %q, not one the expected messages", msg)
}
}
Esse arquivo usa a variável não exportada fortunes. 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. go_test precisa compilar fortune.go e
fortune_test.go juntos com o mesmo comando. Portanto, usamos o atributo embed
aqui 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, o que às vezes é útil para código gerado
código.
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 do rules_go foi introduzido antes do pacote embed do Go. Em vez disso, o 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. Portanto, isso pode detectar erros de compilação mesmo em pacotes que não
têm testes.
$ bazel test //...
Conclusão e leitura adicional
Neste tutorial, criamos e testamos um pequeno projeto 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 outras linguagens.
- Para mais informações sobre o Go, consulte o rules_go, especialmente a documentação das regras principais do Go.
- Para saber mais sobre como trabalhar com módulos do Bazel fora do projeto, consulte Dependências externas. Em particular, para informações sobre como depender de módulos e cadeias de ferramentas do Go pelo sistema de módulos do Bazel, consulte Go com bzlmod.