Tutorial do Bazel: criar um projeto em C++

Informar um problema Acessar a origem

Introdução

Ainda não conhece o Bazel? Você está no lugar certo. Siga este tutorial do primeiro build para uma introdução simplificada ao uso do Bazel. Neste tutorial, definimos os termos-chave conforme eles são usados no contexto do Bazel e orientamos você nos princípios básicos do fluxo de trabalho do Bazel. Começando com as ferramentas necessárias, você criará e executará três projetos com complexidade crescente e aprenderá como e por que eles se tornam mais complexos.

Embora o Bazel seja um sistema de build compatível com builds em vários idiomas, este tutorial usa um projeto em C++ como exemplo e fornece as diretrizes gerais e o fluxo que se aplicam à maioria das linguagens.

Tempo estimado de conclusão: 30 minutos.

Pré-requisitos

Comece instalando o Bazel, caso ainda não tenha feito isso. Este tutorial usa o Git para controle de origem. Portanto, para melhores resultados, instale o Git.

Em seguida, recupere o projeto de amostra do repositório GitHub do Bazel executando o seguinte na ferramenta de linha de comando de sua escolha:

git clone https://github.com/bazelbuild/examples

O projeto de exemplo deste tutorial está no diretório examples/cpp-tutorial.

Confira abaixo a estrutura do recurso:

examples
└── cpp-tutorial
    ├──stage1
    │  ├── main
    │  │   ├── BUILD
    │  │   └── hello-world.cc
    │  └── WORKSPACE
    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   └── hello-greet.h
    │  └── WORKSPACE
    └──stage3
       ├── main
       │   ├── BUILD
       │   ├── hello-world.cc
       │   ├── hello-greet.cc
       │   └── hello-greet.h
       ├── lib
       │   ├── BUILD
       │   ├── hello-time.cc
       │   └── hello-time.h
       └── WORKSPACE

Há três conjuntos de arquivos, e cada um deles representa um estágio neste tutorial. No primeiro estágio, você vai criar um único destino que residirá em um único pacote. Na segunda etapa, você criará um binário e uma biblioteca a partir de um único pacote. Na terceira e última etapa, você vai criar um projeto com vários pacotes, com vários destinos.

Resumo: introdução

Ao instalar o Bazel (e o Git) e clonar o repositório deste tutorial, você criou a base para seu primeiro build com o Bazel. Continue na próxima seção para definir alguns termos e configurar seu espaço de trabalho.

Como começar

Configurar o espaço de trabalho

Antes de criar um projeto, você precisa configurar o espaço de trabalho dele. Um espaço de trabalho é um diretório que contém os arquivos de origem do seu projeto e as saídas de build do Bazel. Ele também contém estes arquivos importantes:

  • O WORKSPACE file , que identifica o diretório e o conteúdo dele como um espaço de trabalho do Bazel e se encontra na raiz da estrutura de diretórios do projeto.
  • Uma ou mais BUILD files , que informam ao Bazel como criar diferentes partes do projeto. Um diretório no espaço de trabalho que contém um arquivo BUILD é um pacote (em inglês). (Saiba mais sobre pacotes posteriormente neste tutorial.)

Em projetos futuros, para designar um diretório como um espaço de trabalho do Bazel, crie um arquivo vazio chamado WORKSPACE nesse diretório. Para os fins deste tutorial, um arquivo WORKSPACE já está presente em cada estágio.

OBSERVAÇÃO: quando o Bazel cria o projeto, todas as entradas precisam estar no mesmo espaço de trabalho. Os arquivos que residem em espaços de trabalho diferentes são independentes uns dos outros, a menos que estejam vinculados. Confira informações mais detalhadas sobre as regras do espaço de trabalho neste guia.

Entender o arquivo BUILD

Um arquivo BUILD contém vários tipos diferentes de instruções para o Bazel. Cada arquivo BUILD requer pelo menos uma regra como um conjunto de instruções, que informa ao Bazel como criar as saídas pretendidas, como binários ou bibliotecas executáveis. Cada instância de uma regra de build no arquivo BUILD é chamada de destino e aponta para um conjunto específico de arquivos de origem e dependências. Um destino também pode apontar para outros destinos.

Observe o arquivo BUILD no diretório cpp-tutorial/stage1/main:

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

Em nosso exemplo, o destino hello-world instancia o cc_binary rule integrado do Bazel. A regra diz ao Bazel para criar um binário executável independente do arquivo de origem hello-world.cc sem dependências.

Resumo: primeiros passos

Agora você está familiarizado com alguns termos-chave e o que eles significam no contexto deste projeto e do Bazel em geral. Na próxima seção, você vai criar e testar o estágio 1 do projeto.

Estágio 1: destino único, pacote único

É hora de construir a primeira parte do projeto. Para uma referência visual, a estrutura da seção da Fase 1 do projeto é:

examples
└── cpp-tutorial
    └──stage1
       ├── main
       │   ├── BUILD
       │   └── hello-world.cc
       └── WORKSPACE

Execute o comando a seguir para acessar o diretório cpp-tutorial/stage1:

cd cpp-tutorial/stage1

Depois execute:

bazel build //main:hello-world

No rótulo de destino, a parte //main: é o local do arquivo BUILD em relação à raiz do espaço de trabalho, e hello-world é o nome de destino no arquivo BUILD.

O Bazel produz algo parecido com isto:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.267s, Critical Path: 0.25s

Você acabou de criar seu primeiro destino do Bazel. Ele coloca saídas de compilação no diretório bazel-bin na raiz do espaço de trabalho.

Agora, teste o binário recém-criado, que é:

bazel-bin/main/hello-world

O resultado é uma mensagem "Hello world" impressa.

Veja o gráfico de dependências do Estágio 1:

O gráfico de dependência do hello-world exibe um único destino com um único arquivo de origem.

Resumo: estágio 1

Agora que concluiu seu primeiro build, você tem uma ideia básica de como ele é estruturado. Na próxima etapa, você vai aumentar a complexidade adicionando outro destino.

Estágio 2: vários destinos de build

Embora um único destino seja suficiente para projetos pequenos, é recomendável dividir projetos maiores em vários destinos e pacotes. Isso permite builds incrementais rápidos, ou seja, o Bazel apenas recria o que mudou, e acelera os builds criando várias partes de um projeto de uma só vez. Esta etapa do tutorial adiciona um destino e o próximo, um pacote.

Este é o diretório com que você está trabalhando para o Estágio 2:

    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   └── hello-greet.h
    │  └── WORKSPACE

Confira abaixo o arquivo BUILD no diretório cpp-tutorial/stage2/main:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

Com este arquivo BUILD, o Bazel primeiro cria a biblioteca hello-greet (usando o cc_library rule integrado do Bazel) e, em seguida, o binário hello-world. O atributo deps no destino hello-world informa ao Bazel que a biblioteca hello-greet é necessária para criar o binário hello-world.

Antes de criar essa nova versão do projeto, você precisa mudar os diretórios, alternando para o diretório cpp-tutorial/stage2, executando:

cd ../stage2

Agora você pode criar o novo binário usando o seguinte comando:

bazel build //main:hello-world

Mais uma vez, o Bazel produz algo parecido com isto:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.399s, Critical Path: 0.30s

Agora é possível testar o binário recém-criado, que retorna outro "Hello world":

bazel-bin/main/hello-world

Se você modificar hello-greet.cc e recompilar o projeto, o Bazel só vai recompilar esse arquivo.

No gráfico de dependência, é possível notar que hello-world depende de uma entrada extra chamada hello-greet:

O gráfico de dependências de "hello-world" mostra as mudanças de dependência após a modificação no arquivo.

Resumo: etapa 2

Você criou o projeto com dois destinos. O destino hello-world cria um arquivo de origem e depende de outro destino (//main:hello-greet), que cria dois arquivos de origem extras. Na próxima seção, dê um passo adiante e adicione outro pacote.

Estágio 3: vários pacotes

Esta próxima etapa adiciona outra camada de complicação e cria um projeto com vários pacotes. Confira abaixo a estrutura e o conteúdo do diretório cpp-tutorial/stage3:

└──stage3
   ├── main
   │   ├── BUILD
   │   ├── hello-world.cc
   │   ├── hello-greet.cc
   │   └── hello-greet.h
   ├── lib
   │   ├── BUILD
   │   ├── hello-time.cc
   │   └── hello-time.h
   └── WORKSPACE

Veja que agora há dois subdiretórios e que cada um contém um arquivo BUILD. Portanto, para o Bazel, o espaço de trabalho agora contém dois pacotes: lib e main.

Confira o arquivo lib/BUILD:

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

No arquivo main/BUILD:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "//lib:hello-time",
    ],
)

O destino hello-world no pacote principal depende do destino hello-time no pacote lib (por isso, o rótulo de destino //lib:hello-time). O Bazel sabe isso pelo atributo deps. Observe isso refletido no gráfico de dependências:

O gráfico de dependência de "hello-world" mostra como o destino no pacote principal depende do destino no pacote "lib".

Para que o build funcione, torne o destino //lib:hello-time em lib/BUILD explicitamente visível para os destinos em main/BUILD usando o atributo de visibilidade. Isso ocorre porque, por padrão, os destinos só ficam visíveis para outros destinos no mesmo arquivo BUILD. O Bazel usa a visibilidade de destino para evitar problemas, como bibliotecas que contêm detalhes de implementação, vazando para APIs públicas.

Agora, crie esta versão final do projeto. Alterne para o diretório cpp-tutorial/stage3 executando:

cd  ../stage3

Mais uma vez, execute o seguinte comando:

bazel build //main:hello-world

O Bazel produz algo parecido com isto:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.167s, Critical Path: 0.00s

Agora, teste o último binário deste tutorial para uma mensagem Hello world final:

bazel-bin/main/hello-world

Resumo: etapa 3

Você criou o projeto como dois pacotes com três destinos e entendeu as dependências entre eles, o que prepara para criar futuros projetos com o Bazel. Na próxima seção, confira como continuar sua jornada do Bazel.

Próximas etapas

Você concluiu seu primeiro build básico com o Bazel, mas este é apenas o início. Confira mais alguns recursos para continuar aprendendo com o Bazel:

Divirta-se!