Instructivo de Bazel: Compila un proyecto de C++

Informar un problema Ver fuente

Introducción

¿Es la primera vez que usas Bazel? Estás en el lugar correcto. Sigue este instructivo de First Build para obtener una introducción simplificada al uso de Bazel. En este instructivo, se definen los términos clave tal como se usan en el contexto de Bazel y se te guiará a través de los conceptos básicos del flujo de trabajo de Bazel. Comenzando con las herramientas que necesitas, compilarás y ejecutarás tres proyectos con una complejidad creciente y aprenderás cómo y por qué se vuelven más complejos.

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

Tiempo estimado de finalización: 30 minutos.

Requisitos previos

Para comenzar, instala Bazel, si aún no lo hiciste. En este instructivo, se usa Git para el control de código fuente, por lo que, a fin de obtener mejores resultados, también instala Git.

A continuación, ejecuta el siguiente comando en la herramienta de línea de comandos que prefieras para recuperar el proyecto de muestra del repositorio de GitHub de Bazel:

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

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

A continuación, echa un vistazo a 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

Existen tres conjuntos de archivos, cada uno de los cuales representa una etapa en este instructivo. En la primera etapa, compilarás un solo destino que reside en un solo paquete. En la segunda etapa, 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 con varios destinos.

Resumen: Introducción

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

Primeros pasos

Cómo configurar el espacio de trabajo

Antes de compilar 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 compilación de Bazel. También contiene los siguientes archivos significativos:

  • El WORKSPACE file , que identifica el directorio y su contenido como un lugar de trabajo de Bazel y se aloja en la raíz de la estructura de directorios 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 más información sobre los paquetes).

En proyectos futuros, crea un archivo vacío llamado WORKSPACE en ese directorio para designar un directorio como un lugar de trabajo de Bazel. Para los fines 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 lugares de trabajo diferentes 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 diferentes de instrucciones 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. Los objetivos también pueden apuntar a otros objetivos.

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 autónomo a partir del archivo de origen hello-world.cc sin dependencias.

Resumen: cómo comenzar

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

Etapa 1: un solo destino, un solo paquete

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

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

Ejecuta lo siguiente para moverte 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 que se ve de la siguiente manera:

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 ubica los resultados de compilación en el directorio bazel-bin, en la raíz del lugar de trabajo.

Ahora prueba tu objeto binario recién compilado, que es el siguiente:

bazel-bin/main/hello-world

Esto generará el mensaje "Hello world" impreso.

Este es el gráfico de dependencia del paso 1:

El gráfico de dependencias de 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. En la siguiente etapa, agregarás complejidad cuando agregues otro destino.

Etapa 2: Varios objetivos de compilación

Si bien un solo objetivo es suficiente para los 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 vuelve a compilar lo que cambió y acelera tus compilaciones compilando varias partes de un proyecto a la vez para acelerarlas. En esta etapa del instructivo, se agrega un destino y la siguiente, un paquete.

Este es el directorio con el que estás trabajando para la etapa 2:

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

A continuación, observa 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 en el destino hello-world le indica a Bazel que se requiere la biblioteca hello-greet para compilar el objeto binario hello-world.

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

cd ../stage2

Ahora puedes compilar el objeto binario nuevo con el siguiente comando que ya conoces:

bazel build //main:hello-world

Una vez más, 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.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 ahora modificas hello-greet.cc y vuelves a compilar el proyecto, Bazel solo vuelve a compilar ese archivo.

Si observas el gráfico de dependencia, 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

Ya compilaste el proyecto con dos objetivos. El destino hello-world compila un archivo de origen y depende de otro destino (//main:hello-greet), que compila dos archivos de origen adicionales. En la siguiente sección, ve un poco más 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. A continuación, observa 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 cada uno contiene un archivo BUILD. Por lo tanto, para 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__"],
)

En el archivo main/BUILD, haz lo siguiente:

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 dependencia:

El gráfico de dependencias de "hello-world" muestra cómo el destino del paquete principal depende del destino del paquete "lib".

Para que la compilación tenga éxito, debes hacer que el destino //lib:hello-time en 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 destinos solo son visibles para otros destinos en el mismo archivo BUILD. Bazel usa la visibilidad objetivo para evitar que las bibliotecas que contienen detalles de implementación se filtren en las APIs públicas.

Ahora, compila esta versión final del proyecto. Ejecuta el siguiente comando para cambiar al directorio cpp-tutorial/stage3:

cd  ../stage3

Vuelve a ejecutar el siguiente comando:

bazel build //main:hello-world

Bazel produce algo que se ve de la siguiente manera:

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 ver un mensaje final Hello world:

bazel-bin/main/hello-world

Resumen: etapa 3

Ya compilaste el proyecto como dos paquetes con tres destinos y comprendes las dependencias entre ellos, lo que te prepara para continuar y compilar proyectos futuros con Bazel. En la siguiente sección, veremos cómo continuar tu recorrido de Bazel.

Próximos pasos

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 compilación!