En este instructivo, se explican los conceptos básicos de la compilación de aplicaciones Java con
Bazel. Configurarás tu lugar de trabajo y compilarás un proyecto de Java simple que
ilustre conceptos clave de Bazel, como destinos y archivos BUILD
.
Tiempo estimado de finalización: 30 minutos.
Qué aprenderás
En este instructivo, aprenderás a realizar las siguientes acciones:
- Crea un destino
- Visualiza las dependencias del proyecto
- Divide el proyecto en varios destinos y paquetes
- Controlar la visibilidad de destino en paquetes
- Destinos de referencia a través de etiquetas
- Implementa un destino
Antes de comenzar
Instala Bazel
Para prepararte para el instructivo, primero instala Bazel si no lo tienes instalado.
Instala el JDK
Instala Java JDK (la versión preferida es 11; sin embargo, se admiten las versiones 8 y 15).
Configure la variable de entorno JAVA_HOME para que apunte al JDK.
En Linux o macOS:
export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
En Windows:
- Abre el panel de control.
- Ve a "Sistema y seguridad" > "Sistema" > "Configuración avanzada del sistema" > pestaña "Avanzada" > "Variables de entorno...". .
- En la lista "Variables de usuario" (la de la parte superior), haga clic en "Nueva...".
- En el campo "Nombre de la variable", ingrese
JAVA_HOME
. - Haz clic en "Explorar directorio...".
- Navega al directorio de JDK (por ejemplo,
C:\Program Files\Java\jdk1.8.0_152
). - Haz clic en "Aceptar" en todas las ventanas de diálogo.
Obtén el proyecto de muestra
Recupera el proyecto de muestra del repositorio de GitHub de Bazel:
git clone https://github.com/bazelbuild/examples
El proyecto de muestra de este instructivo se encuentra en el directorio examples/java-tutorial
y tiene la siguiente estructura:
java-tutorial
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── cmdline
│ │ ├── BUILD
│ │ └── Runner.java
│ ├── Greeting.java
│ └── ProjectRunner.java
└── WORKSPACE
Compila con Bazel
Configure el lugar 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 incluye archivos que Bazel reconoce como especiales:
El archivo
WORKSPACE
, 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.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 archivoBUILD
es un paquete. Aprenderás sobre los paquetes más adelante en este instructivo).
Para designar un directorio como un lugar de trabajo de Bazel, crea un archivo vacío llamado
WORKSPACE
en ese directorio.
Cuando Bazel compila el proyecto, todas las entradas y dependencias 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, lo cual está fuera del alcance de este instructivo.
Información sobre el archivo BUILD
Un archivo BUILD
contiene varios tipos diferentes de instrucciones para Bazel.
El tipo más importante es la regla de compilación, que le indica a Bazel cómo compilar los resultados deseados, como objetos binarios o bibliotecas 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.
Observa el archivo java-tutorial/BUILD
:
java_binary(
name = "ProjectRunner",
srcs = glob(["src/main/java/com/example/*.java"]),
)
En nuestro ejemplo, el destino ProjectRunner
crea una instancia de la regla java_binary
integrada de Bazel. La regla le indica a Bazel que compile un archivo .jar
y una secuencia de comandos de shell de wrapper (ambos con el nombre del destino).
Los atributos del destino establecen de manera explícita sus dependencias y opciones.
Si bien el atributo name
es obligatorio, muchos son opcionales. Por ejemplo, en el
destino de la regla ProjectRunner
, name
es el nombre del destino, srcs
especifica
los archivos de origen que Bazel usa para compilar el destino y main_class
especifica
la clase que contiene el método principal. (Quizá hayas notado que nuestro ejemplo usa glob para pasar un conjunto de archivos de origen a Bazel en lugar de enumerarlos uno por uno).
Compila el proyecto
Para compilar tu proyecto de muestra, navega al directorio java-tutorial
y ejecuta lo siguiente:
bazel build //:ProjectRunner
En la etiqueta de destino, la parte //
es la ubicación del archivo BUILD
en relación con la raíz del lugar de trabajo (en este caso, la raíz en sí) y ProjectRunner
es el nombre del destino en el archivo BUILD
. (Aprenderás sobre las etiquetas de destino con más detalle al final de este instructivo).
Bazel produce un resultado similar al siguiente:
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 1.021s, Critical Path: 0.83s
¡Felicitaciones! Acabas de crear tu primer destino de Bazel. Bazel coloca los resultados
de compilación en el directorio bazel-bin
en la raíz del lugar de trabajo. Explora
su contenido a fin de obtener una idea para la estructura de resultados de Bazel.
Ahora, pruebe su objeto binario recién compilado:
bazel-bin/ProjectRunner
Revisa el gráfico de dependencias
Bazel requiere que las dependencias de compilación se declaren explícitamente en los archivos BUILD. Bazel usa esas declaraciones para crear el grafo de dependencia del proyecto, que permite compilaciones incrementales precisas.
Para visualizar las dependencias del proyecto de muestra, puedes generar una representación de texto del grafo de dependencia si ejecutas este comando en la raíz del lugar de trabajo:
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph
El comando anterior le indica a Bazel que busque todas las dependencias para el //:ProjectRunner
de destino (excepto las dependencias implícitas y de host) y que formatee el resultado como un grafo.
Luego, pega el texto en GraphViz.
Como puedes ver, el proyecto tiene un solo objetivo que compila dos archivos de origen sin dependencias adicionales:
Después de configurar tu lugar de trabajo, compilar tu proyecto y examinar sus dependencias, puedes agregar complejidad.
Define mejor tu compilación de Bazel
Si bien un solo destino es suficiente para proyectos pequeños, te recomendamos dividir los proyectos más grandes en varios destinos y paquetes a fin de permitir compilaciones incrementales rápidas (es decir, solo volver a compilar los cambios) y acelerar tus compilaciones mediante la compilación de varias partes de un proyecto a la vez.
Especifica varios destinos de compilación
Puedes dividir la compilación del proyecto de muestra en dos destinos. Reemplaza el contenido del archivo java-tutorial/BUILD
por lo siguiente:
java_binary(
name = "ProjectRunner",
srcs = ["src/main/java/com/example/ProjectRunner.java"],
main_class = "com.example.ProjectRunner",
deps = [":greeter"],
)
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
)
Con esta configuración, Bazel primero compila la biblioteca greeter
y, luego, el objeto binario ProjectRunner
. El atributo deps
en java_binary
le indica a Bazel que
se requiere la biblioteca greeter
para compilar el objeto binario ProjectRunner
.
Para compilar esta versión nueva del proyecto, ejecuta el siguiente comando:
bazel build //:ProjectRunner
Bazel produce un resultado similar al siguiente:
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 2.454s, Critical Path: 1.58s
Ahora, pruebe su objeto binario recién compilado:
bazel-bin/ProjectRunner
Si ahora modificas ProjectRunner.java
y vuelves a compilar el proyecto, Bazel solo
volver a compilar ese archivo.
Si observas el gráfico de dependencias, puedes ver que ProjectRunner
depende de las mismas entradas que antes, pero la estructura de la compilación es diferente:
Acabas de compilar el proyecto con dos destinos. El destino ProjectRunner
compila dos archivos de origen y depende de otro objetivo (:greeter
), que compila un archivo de origen adicional.
Usa varios paquetes
Ahora, dividamos el proyecto en varios paquetes. Si observas el directorio src/main/java/com/example/cmdline
, verás que también contiene un archivo BUILD
y algunos archivos de origen. Por lo tanto, para Bazel, el lugar de trabajo ahora contiene dos paquetes, //src/main/java/com/example/cmdline
y //
(ya que hay un archivo BUILD
en la raíz del lugar de trabajo).
Observa el archivo src/main/java/com/example/cmdline/BUILD
:
java_binary(
name = "runner",
srcs = ["Runner.java"],
main_class = "com.example.cmdline.Runner",
deps = ["//:greeter"],
)
El destino runner
depende del destino greeter
en el paquete //
(por lo que
la etiqueta de destino //:greeter
). Bazel lo sabe a través del atributo deps
.
Observa el gráfico de dependencias:
Sin embargo, para que la compilación se realice de forma correcta, debes otorgar de forma explícita el destino runner
en //src/main/java/com/example/cmdline/BUILD
a los objetivos en //BUILD
mediante el atributo visibility
. Esto se debe a que, de forma predeterminada, solo los demás destinos del mismo archivo BUILD
pueden verlos. (Bazel usa la visibilidad de destino para evitar problemas como las bibliotecas que contienen detalles de implementación que se filtran a API públicas).
Para ello, agrega el atributo visibility
al destino greeter
en java-tutorial/BUILD
, como se muestra a continuación:
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)
Ahora puedes compilar el paquete nuevo si ejecutas el siguiente comando en la raíz del lugar de trabajo:
bazel build //src/main/java/com/example/cmdline:runner
Bazel produce un resultado similar al siguiente:
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner.jar
bazel-bin/src/main/java/com/example/cmdline/runner
INFO: Elapsed time: 1.576s, Critical Path: 0.81s
Ahora, pruebe su objeto binario recién compilado:
./bazel-bin/src/main/java/com/example/cmdline/runner
Modificaste el proyecto para que se compile como dos paquetes, cada uno con un destino, y comprendas las dependencias entre ellos.
Usa etiquetas para hacer referencia a objetivos
En los archivos BUILD
y en la línea de comandos, Bazel usa etiquetas de destino para hacer referencia
a los destinos, por ejemplo, //:ProjectRunner
o
//src/main/java/com/example/cmdline:runner
. Su sintaxis es la siguiente:
//path/to/package:target-name
Si el destino es un destino de reglas, path/to/package
es la ruta de acceso al directorio que contiene el archivo BUILD
, y target-name
es lo que nombraste al destino en el archivo BUILD
(el atributo name
). Si el destino es un destino de archivo, entonces path/to/package
es la ruta de acceso a la raíz del paquete y target-name
es el nombre del archivo de destino, incluida su ruta completa.
Cuando hagas referencia a destinos en la raíz del repositorio, la ruta del paquete estará vacía, solo usa //:target-name
. Cuando hagas referencia a destinos dentro del mismo archivo BUILD
, incluso puedes omitir el identificador raíz del lugar de trabajo //
y solo usar :target-name
.
Por ejemplo, para los objetivos del archivo java-tutorial/BUILD
, no tuviste que especificar una ruta de paquete, ya que la raíz del lugar de trabajo es un paquete (//
) y las dos etiquetas de destino son solo //:ProjectRunner
y //:greeter
.
Sin embargo, para los objetivos del archivo //src/main/java/com/example/cmdline/BUILD
, tuviste que especificar la ruta de acceso completa del paquete de //src/main/java/com/example/cmdline
, y la etiqueta de destino era //src/main/java/com/example/cmdline:runner
.
Empaqueta un destino de Java para la implementación
Ahora empaquetamos un destino Java para su implementación mediante la compilación del objeto binario con todas sus dependencias de entorno de ejecución. Esto te permite ejecutar el objeto binario fuera del entorno de desarrollo.
Como recuerdas, la regla de compilación java_binary genera una .jar
y una secuencia de comandos de shell de wrapper. Observa el contenido de runner.jar
con este comando:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar
El contenido es:
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
Como puedes ver, runner.jar
contiene Runner.class
, pero no su dependencia, Greeting.class
. La secuencia de comandos runner
que genera Bazel agrega greeter.jar
a la ruta de clase, por lo que, si la dejas de esta manera, se ejecutará de manera local, pero
no se ejecutará de forma independiente en otra máquina. Por suerte, la regla java_binary
te permite compilar un objeto binario autónomo que se puede implementar. Para compilarlo, agrega _deploy.jar
al nombre del destino:
bazel build //src/main/java/com/example/cmdline:runner_deploy.jar
Bazel produce un resultado similar al siguiente:
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.700s, Critical Path: 0.23s
Acabas de compilar runner_deploy.jar
, que puedes ejecutar de forma independiente de tu entorno de desarrollo, ya que contiene las dependencias de entorno de ejecución necesarias. Observa el contenido de este JAR independiente con el mismo comando que antes:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
El contenido incluye todas las clases necesarias para ejecutar:
META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class
Lecturas adicionales
Para obtener más información, consulta los siguientes recursos:
rules_jvm_external para que las reglas administren dependencias de Maven transitivas.
Dependencias externas para obtener más información sobre cómo trabajar con repositorios locales y remotos.
Las otras reglas para obtener más información sobre Bazel.
El instructivo de compilación de C++ para comenzar a compilar proyectos de C++ con Bazel.
El Instructivo de aplicación para Android y el Instructivo de aplicación para iOS) a fin de comenzar a compilar aplicaciones para dispositivos móviles destinadas a iOS y Android con Bazel.
¡Feliz edificio!