Reserva la fecha: BazelCon 2023 se celebrará el 24 y 25 de octubre en Google Múnich. Más información

Instructivo de Bazel: Compila un proyecto de C++

Informa un problema Ver código fuente

Introducción

¿Eres nuevo en Bazel? Está en el lugar correcto. Sigue este instructivo de Primera compilación para obtener una introducción simplificada al uso de Bazel. En este instructivo, se definen los términos clave, ya que se usan en el contexto de Bazel y se explican los conceptos básicos del flujo de trabajo de Bazel. Comenzarás con las herramientas que necesitas para compilar y ejecutar tres proyectos con una complejidad creciente, y aprender cómo y por qué se vuelven más complejos.

Si bien Bazel es un sistema de compilación compatible con compilaciones de varios idiomas, en este instructivo, se usa un proyecto de C++ como ejemplo y se proporcionan los lineamientos y el flujo generales que se aplican a la mayoría de los lenguajes.

Tiempo de finalización estimado: 30 minutos.

Requisitos previos

Primero, instala Bazel si aún no lo has hecho. En este instructivo, se usa Git para el control de la fuente, por lo que, para obtener mejores resultados, también debes instalar Git.

A continuación, recupera el proyecto de muestra del repositorio de GitHub de Bazel mediante la siguiente herramienta de línea de comandos de tu elección:

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

El proyecto de muestra de este instructivo se encuentra en el directorio examples/cpp-tutorial.

A continuación, se muestra su estructura:

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

En este instructivo, existen tres conjuntos de archivos que representan una etapa. En la primera etapa, compilarás un solo objetivo que resida en un solo paquete. En la segunda, compilarás un objeto binario y una biblioteca a partir de un solo paquete. En la tercera y última etapa, compilarás un proyecto con varios paquetes y lo compilarás con varios destinos.

Resumen: Introducción

Si instalas Bazel (y Git) y clonas el repositorio de este instructivo, sentaste las bases de tu primera compilación con Bazel. Continúa con la siguiente sección para definir algunos términos y configurar tu lugar de trabajo.

Primeros pasos

Configure el lugar de trabajo

Antes de crear un proyecto, debes configurar su lugar de trabajo. Un lugar de trabajo es un directorio que contiene los archivos de origen de tu proyecto y los resultados de la compilación de Bazel. También contiene los siguientes archivos:

  • El WORKSPACE file , que identifica el directorio y su contenido como un lugar de trabajo de Bazel y se encuentra en la raíz de la estructura del directorio del proyecto.
  • Uno o más BUILD files , que le indican a Bazel cómo compilar diferentes partes del proyecto. Un directorio dentro del lugar de trabajo que contiene un archivo BUILD es un paquete. Más adelante en este instructivo, encontrarás información adicional sobre los paquetes.

En proyectos futuros, crea un archivo vacío llamado WORKSPACE en ese directorio para designar un directorio como lugar de trabajo de Bazel. A los efectos de este instructivo, ya hay un archivo WORKSPACE en cada etapa.

NOTA: Cuando Bazel compila el proyecto, todas las entradas deben estar en el mismo lugar de trabajo. Los archivos que residen en diferentes lugares de trabajo son independientes entre sí, a menos que estén vinculados. Puedes encontrar información más detallada sobre las reglas del lugar de trabajo en esta guía.

Comprende el archivo BUILD

Un archivo BUILD contiene varios tipos de instrucciones diferentes para Bazel. Cada archivo BUILD requiere al menos una regla como conjunto de instrucciones, que le indica a Bazel cómo compilar los resultados deseados, como bibliotecas o objetos binarios ejecutables. Cada instancia de una regla de compilación en el archivo BUILD se denomina destino y apunta a un conjunto específico de archivos de origen y dependencias. Un objetivo también puede apuntar a otros destinos.

Observa el archivo BUILD en el directorio cpp-tutorial/stage1/main:

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

En nuestro ejemplo, el destino hello-world crea una instancia del cc_binary rule integrado de Bazel. La regla le indica a Bazel que compile un objeto binario ejecutable independiente del archivo fuente hello-world.cc sin dependencias.

Resumen: cómo empezar

Ahora conoces algunos términos clave y lo que significan en el contexto de este proyecto y Bazel en general. En la siguiente sección, compilarás y probarás la etapa 1 del proyecto.

Etapa 1: destino único, paquete único

Es momento de crear la primera parte del proyecto. Para una referencia visual, la estructura de la sección de la etapa 1 del proyecto es la siguiente:

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

Ejecuta lo siguiente para ir al directorio cpp-tutorial/stage1:

cd cpp-tutorial/stage1

Luego, ejecute el siguiente comando:

bazel build //main:hello-world

En la etiqueta de destino, la parte //main: es la ubicación del archivo BUILD en relación con la raíz del lugar de trabajo, y hello-world es el nombre del destino en el archivo BUILD.

Bazel produce algo similar a lo siguiente:

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

Acabas de compilar tu primer destino de Bazel. Bazel coloca los resultados de la compilación en el directorio bazel-bin en la raíz del lugar de trabajo.

Ahora, pruebe su objeto binario recién compilado, que es el siguiente:

bazel-bin/main/hello-world

Se mostrará el mensaje "Hello world" impreso.

Este es el gráfico de dependencias de la etapa 1:

El gráfico de dependencias para hello-world muestra un solo destino con un solo archivo fuente.

Resumen: etapa 1

Ahora que completaste tu primera compilación, tienes una idea básica de cómo se estructura una compilación. En la próxima etapa, agregarás otro objetivo para agregar complejidad.

Etapa 2: Varios destinos de compilación

Si bien un solo destino es suficiente para proyectos pequeños, te recomendamos dividir los proyectos más grandes en varios destinos y paquetes. Esto permite compilaciones incrementales rápidas, es decir, Bazel solo reconstruye los cambios y acelera tus compilaciones mediante la compilación de varias partes de un proyecto a la vez. En esta etapa del instructivo, se agrega un destino y, en la siguiente, se agrega un paquete.

Este es el directorio con el que trabajas para la etapa 2:

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

Observa a continuación el archivo BUILD en el directorio 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",
    ],
)

Con este archivo BUILD, Bazel primero compila la biblioteca hello-greet (mediante el cc_library rule integrado de Bazel) y, luego, el objeto binario hello-world. El atributo deps del destino hello-world le indica a Bazel que se necesita la biblioteca hello-greet para compilar el objeto binario hello-world.

Antes de compilar esta versión nueva del proyecto, debes cambiar los directorios y cambiar al directorio cpp-tutorial/stage2 ejecutando lo siguiente:

cd ../stage2

Ahora puedes compilar el objeto binario nuevo con el siguiente comando conocido:

bazel build //main:hello-world

Una vez más, Bazel produce algo como lo siguiente:

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

Ahora puedes probar tu objeto binario recién compilado, que muestra otro "Hello world":

bazel-bin/main/hello-world

Si modificas hello-greet.cc y vuelves a compilar el proyecto, Bazel solo volverá a compilar ese archivo.

En el gráfico de dependencias, puedes ver que hello-world depende de una entrada adicional llamada hello-greet:

El gráfico de dependencias de “hello-world” muestra los cambios de dependencia después de modificar el archivo.

Resumen: etapa 2

Acabas de compilar el proyecto con dos destinos. El destino hello-world compila un archivo fuente y depende de otro destino (//main:hello-greet), que compila dos archivos fuente adicionales. En la siguiente sección, ve más allá y agrega otro paquete.

Etapa 3: varios paquetes

En la siguiente etapa, se agrega otra capa de complicación y se compila un proyecto con varios paquetes. Observa a continuación la estructura y el contenido del directorio cpp-tutorial/stage3:

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

Puedes ver que ahora hay dos subdirectorios y que cada uno contiene un archivo BUILD. Por lo tanto, en Bazel, el lugar de trabajo ahora contiene dos paquetes: lib y main.

Observa el archivo lib/BUILD:

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

Y en el archivo 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",
    ],
)

El destino hello-world del paquete principal depende del destino hello-time en el paquete lib (por lo tanto, la etiqueta de destino //lib:hello-time). Bazel lo sabe a través del atributo deps. Puedes ver esto reflejado en el gráfico de dependencias:

El gráfico de dependencias para “hello-world” muestra cómo el objetivo del paquete principal depende del objetivo en el paquete “lib”.

Para que la compilación se realice de forma correcta, debes hacer que el objetivo //lib:hello-time de lib/BUILD sea visible de forma explícita para los objetivos de main/BUILD mediante el atributo de visibilidad. Esto se debe a que, de forma predeterminada, los objetivos solo son visibles para otros destinos en el mismo archivo BUILD. Bazel usa la visibilidad de destino para evitar problemas como las bibliotecas que contienen detalles de implementación que se filtran a las APIs públicas.

Ahora, compila esta versión final del proyecto. Para cambiar al directorio cpp-tutorial/stage3, ejecuta lo siguiente:

cd  ../stage3

Una vez más, ejecute el siguiente comando:

bazel build //main:hello-world

Bazel produce algo similar a lo siguiente:

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

Ahora, prueba el último objeto binario de este instructivo para obtener un mensaje Hello world final:

bazel-bin/main/hello-world

Resumen: etapa 3

Compilaste el proyecto como dos paquetes con tres destinos y comprendes las dependencias entre ellos, lo que te permite avanzar y compilar futuros proyectos con Bazel. En la siguiente sección, observa cómo continuar tu recorrido de Bazel.

Próximos pasos

Ya completaste tu primera compilación básica con Bazel, pero este es solo el comienzo. Estos son algunos recursos más para seguir aprendiendo con Bazel:

¡Feliz edificio!