Tutorial do Bazel: criar um projeto em C++

Reportar um problema Ver código-fonte Nightly · 8.0 . 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Introdução

Você é novo no Bazel? Você está no lugar certo. Siga este tutorial de Primeiro build para uma introdução simplificada ao uso do Bazel. Este tutorial define os principais termos conforme são usados no contexto do Bazel e explica os conceitos básicos do fluxo de trabalho do Bazel. Começando com as ferramentas necessárias, você vai criar e executar três projetos com complexidade crescente e aprender como e por que eles ficam mais complexos.

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

Tempo estimado de conclusão: 30 minutos.

Pré-requisitos

Comece instalando o Bazel, se ainda não tiver feito isso. Este tutorial usa o Git para controle de origem. Para melhores resultados, instale o Git também.

Em seguida, extraia o projeto de exemplo do repositório do GitHub do Bazel executando o seguinte na ferramenta de linha de comando de sua preferência:

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

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

Confira como ele está estruturado:

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

Há três conjuntos de arquivos, cada um representando uma etapa neste tutorial. Na primeira etapa, você vai criar um único alvo em um único pacote. Na segunda etapa, você vai criar um binário e uma biblioteca em um único pacote. Na terceira e última etapa, você vai criar um projeto com vários pacotes e vários destinos.

Resumo: Introdução

Ao instalar o Bazel (e o Git) e clonar o repositório para este tutorial, você estabeleceu 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.

Primeiros passos

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 projeto e as saídas de build do Bazel. Ele também contém estes arquivos importantes:

  • O arquivo MODULE.bazel, que identifica o diretório e o conteúdo como um espaço de trabalho do Bazel e fica na raiz da estrutura de diretórios do projeto. É também onde você especifica suas dependências externas.
  • Um ou mais arquivos BUILD, 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. Mais sobre pacotes mais adiante neste tutorial.

Em projetos futuros, para designar um diretório como um espaço de trabalho do Bazel, crie um arquivo vazio chamado MODULE.bazel nesse diretório. Para este tutorial, um arquivo MODULE.bazel já está presente em cada etapa.

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 desejadas, como binários executáveis ou bibliotecas. Cada instância de uma regra de build no arquivo BUILD é chamada de target e aponta para um conjunto específico de arquivos de origem e dependências. Um destino também pode apontar para outros destinos.

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

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

No nosso exemplo, o destino hello-world instancia a regra cc_binary integrada do Bazel. A regra informa ao Bazel para criar um binário executável independente do arquivo de origem hello-world.cc> sem dependências.

Resumo: Introdução

Agora você já conhece alguns termos importantes e o que eles significam no contexto deste projeto e do Bazel em geral. Na próxima seção, você vai criar e testar a fase 1 do projeto.

Fase 1: um público-alvo e um pacote

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

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

Execute o seguinte para mover para 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. O Bazel coloca as saídas de build 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

Isso resulta na impressão da mensagem "Hello world".

Confira o gráfico de dependências da fase 1:

O gráfico de dependências para hello-world mostra um único destino com um único arquivo
de origem.

Resumo: etapa 1

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

Fase 2: vários destinos de build

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

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

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

Confira 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 esse arquivo BUILD, o Bazel primeiro cria a biblioteca hello-greet (usando a regra cc_library integrada 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 de diretório, alternando para o diretório cpp-tutorial/stage2 executando:

cd ../stage2

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

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 você pode testar o binário recém-criado, que retorna outro "Hello world":

bazel-bin/main/hello-world

Se você modificar hello-greet.cc e recriar o projeto, o Bazel vai recompilá-lo.

Analisando o gráfico de dependência, é possível ver 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 do arquivo.

Resumo: etapa 2

Agora 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 adicionais. Na próxima seção, vamos dar um passo adiante e adicionar outro pacote.

Fase 3: vários pacotes

Esta próxima etapa adiciona outra camada de complicação e cria um projeto com vários pacotes. Confira 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
   └── MODULE.bazel

Agora há dois subdiretórios, e cada um deles 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__"],
)

E 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 (daí o rótulo de destino //lib:hello-time). O Bazel sabe isso pelo atributo deps. Isso é refletido no gráfico de dependência:

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

Para que o build seja bem-sucedido, torne o destino //lib:hello-time em lib/BUILD explicitamente visível para destinos em main/BUILD usando o atributo de visibilidade. Isso acontece porque, por padrão, as segmentações só ficam visíveis para outras no mesmo arquivo BUILD. O Bazel usa a visibilidade de destino para evitar problemas, como bibliotecas que contêm detalhes de implementação vazados para APIs públicas.

Agora crie a 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: fase 3

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

Próximas etapas

Você concluiu seu primeiro build básico com o Bazel, mas isso é apenas o começo. Confira outros recursos para continuar aprendendo com o Bazel:

Boa criação!