Instructivo de Bazel: Compila un proyecto de C++

Denuncia un problema Ver fuente Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Introducción

¿Es la primera vez que usas 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 tal como se usan en el contexto de Bazel y se explican los conceptos básicos del flujo de trabajo de Bazel. A partir de 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

Comienza por instalar Bazel, si aún no lo hiciste. En este instructivo, se usa Git para el control de código fuente, por lo que, para obtener los mejores resultados, instala Git.

A continuación, recupera el proyecto de muestra del repositorio de GitHub de Bazel. Para ello, ejecuta lo siguiente en la herramienta de línea de comandos que elijas:

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

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

Observa cómo está estructurada:

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

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

Resumen: Introducción

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

Cómo comenzar

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

  • El archivo MODULE.bazel, que identifica el directorio y su contenido como un lugar de trabajo de Bazel y se encuentra en la raíz de la estructura de directorios del proyecto También es donde especificas tus dependencias externas.
  • Uno o más archivos BUILD, 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, para designar un directorio como lugar de trabajo de Bazel, crea un archivo vacío llamado MODULE.bazel en ese directorio. Para los fines de este instructivo, ya hay un archivo MODULE.bazel presente en cada etapa.

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 que deseas, como las bibliotecas o los objetos binarios ejecutables. Cada instancia de una regla de compilación en el archivo BUILD se denomina objetivo y apunta a un conjunto específico de archivos de origen y dependencias. Un objetivo también puede apuntar a otros objetivos.

Consulta 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 de la regla cc_binary integrada de Bazel. La regla le dice a Bazel que compile un ejecutable binario independiente a partir del archivo fuente hello-world.cc> sin dependencias.

Resumen: Cómo comenzar

Ahora conoces algunos términos clave y su significado 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: Un solo objetivo y un solo paquete

Es hora de crear la primera parte del proyecto. A modo de 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
       └── MODULE.bazel

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

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

bazel-bin/main/hello-world

Esto generará un mensaje "Hello world" impreso.

Este es el gráfico de dependencias de la etapa 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 una compilación. En la siguiente etapa, agregarás complejidad con otro objetivo.

Etapa 2: varios destinos de compilación

Si bien un solo objetivo es suficiente para proyectos pequeños, es posible que desees dividir proyectos más grandes en varios objetivos y paquetes. Esto permite compilaciones incrementales rápidas (es decir, Bazel solo vuelve a compilar lo que cambió) y acelera las compilaciones compilando varias partes de un proyecto a la vez. En esta etapa del instructivo, se agrega un destino y, en la siguiente, un paquete.

Este es el directorio con el que trabajarás en la etapa 2:

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

Consulta 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 (con la regla cc_library integrada 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 de directorio y, para ello, ejecuta el siguiente comando para cambiar al directorio cpp-tutorial/stage2:

cd ../stage2

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

bazel build //main:hello-world

Una vez más, 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.399s, Critical Path: 0.30s

Ahora puedes probar el 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 volverá a compilar ese archivo.

Si observas 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 la modificación del archivo.

Resumen: etapa 2

Ahora compilaste 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, irás un paso más allá y agregarás otro paquete.

Etapa 3: varios paquetes

En esta etapa, se agrega otra capa de complicación y se compila un proyecto con varios paquetes. 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
   └── MODULE.bazel

Puedes ver que ahora hay dos subdirectorios, y cada uno contiene un archivo BUILD. Por lo tanto, para Bazel, el espacio 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 en el 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 de "hello-world" muestra cómo el destino en el paquete principal depende del destino en el paquete "lib".

Para que la compilación se realice correctamente, debes hacer que el objetivo //lib:hello-time de lib/BUILD sea explícitamente visible 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 del objetivo para evitar problemas, como bibliotecas que contienen detalles de implementación que se filtran en las APIs públicas.

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

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

bazel-bin/main/hello-world

Resumen: etapa 3

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

Próximos pasos

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

¡Buena compilación!