Descripción general de la compilación de Gradle

Por lo general, las aplicaciones para Android se compilan con el sistema de compilación de Gradle. Antes de analizar los detalles para configurar tu compilación, exploraremos los conceptos que la sustentan para que puedas ver el sistema en su totalidad.

¿Qué es una construcción?

Un sistema de compilación transforma tu código fuente en una aplicación ejecutable. Las compilaciones suelen incluir varias herramientas para analizar, compilar, vincular y empaquetar tu aplicación o biblioteca. Gradle usa un enfoque basado en tareas para organizar y ejecutar estos comandos.

Las tareas encapsulan comandos que traducen sus entradas en resultados. Los complementos definen las tareas y su configuración. Cuando aplicas un complemento a tu compilación, se registran sus tareas y se conectan con sus entradas y salidas. Por ejemplo, si aplicas el complemento de Android para Gradle (AGP) a tu archivo de compilación, se registrarán todas las tareas necesarias para compilar un APK o una biblioteca de Android. El complemento java-library te permite compilar un jar a partir del código fuente de Java. Existen complementos similares para Kotlin y otros lenguajes, pero otros complementos están diseñados para extender complementos. Por ejemplo, el complemento protobuf está diseñado para agregar compatibilidad con protobuf a complementos existentes, como AGP o java-library.

Gradle prefiere la convención a la configuración, por lo que los complementos tendrán buenos valores predeterminados listos para usar, pero también puedes configurar la compilación a través de un lenguaje específico de dominio (DSL) declarativo. La DSL está diseñada para que puedas especificar qué compilar, en lugar de cómo compilarlo. La lógica de los complementos administra el “cómo”. Esa configuración se especifica en varios archivos de compilación en tu proyecto (y subproyectos).

Las entradas de tareas pueden ser archivos y directorios, así como otra información codificada como tipos de Java (números enteros, cadenas o clases personalizadas). Los resultados solo pueden ser directorios o archivos, ya que se deben escribir en el disco. Si conectas el resultado de una tarea a la entrada de otra, se vinculan las tareas para que una se ejecute antes que la otra.

Si bien Gradle admite la escritura de declaraciones de tareas y código arbitrarios en tus archivos de compilación, esto puede dificultar que las herramientas comprendan tu compilación y tu mantenimiento. Por ejemplo, puedes escribir pruebas para código dentro de los complementos, pero no en los archivos de compilación. En cambio, debes restringir la lógica de compilación y las declaraciones de tareas a los complementos (definidos por ti o por otra persona) y declarar cómo deseas usar esa lógica en tus archivos de compilación.

¿Qué sucede cuando se ejecuta una compilación de Gradle?

Las compilaciones de Gradle se ejecutan en tres fases. Cada una de estas fases ejecuta diferentes partes del código que defines en tus archivos de compilación.

  • La inicialización determina qué proyectos y subproyectos se incluyen en la compilación y configura las instrucciones de clase que contienen los archivos de compilación y los complementos aplicados. Esta fase se centra en un archivo de configuración en el que se declaran los proyectos que se compilarán y las ubicaciones desde las que se recuperarán los complementos y las bibliotecas.
  • La configuración registra las tareas para cada proyecto y ejecuta el archivo de compilación para aplicar la especificación de compilación del usuario. Es importante comprender que tu código de configuración no tendrá acceso a los datos ni a los archivos producidos durante la ejecución.
  • La ejecución realiza la "compilación" real de tu aplicación. El resultado de la configuración es un grafo acíclico dirigido (DAG) de tareas, que representa todos los pasos de compilación necesarios que solicitó el usuario (las tareas proporcionadas en la línea de comandos o como valores predeterminados en los archivos de compilación). Este gráfico representa la relación entre las tareas, ya sea explícita en la declaración de una tarea o en función de sus entradas y salidas. Si una tarea tiene una entrada que es el resultado de otra tarea, debe ejecutarse después de la otra tarea. En esta fase, se ejecutan tareas desactualizadas en el orden definido en el gráfico. Si las entradas de una tarea no cambiaron desde su última ejecución, Gradle la omitirá.

Para obtener más información, consulta el ciclo de vida de compilación de Gradle.

DSL de configuración

Gradle usa un lenguaje específico de dominio (DSL) para configurar compilaciones. Este enfoque declarativo se enfoca en especificar tus datos en lugar de escribir instrucciones paso a paso (imperativas). Puedes escribir tus archivos de compilación con Kotlin o Groovy, pero te recomendamos que uses Kotlin.

Los DSL intentan facilitar que todos, expertos en dominios y programadores, contribuyan a un proyecto definiendo un lenguaje pequeño que represente los datos de una manera más natural. Los complementos de Gradle pueden extender la DSL para configurar los datos que necesitan para sus tareas.

Por ejemplo, la configuración de la parte de Android de tu compilación podría verse de la siguiente manera:

Kotlin

android {
    namespace = "com.example.app"
    compileSdk = 34
    // ...

    defaultConfig {
        applicationId = "com.example.app"
        minSdk = 34
        // ...
    }
}

Groovy

android {
    namespace 'com.example.myapplication'
    compileSdk 34
    // ...

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdk 24
        // ...
    }
}

En segundo plano, el código DSL es similar a lo siguiente:

fun Project.android(configure: ApplicationExtension.() -> Unit) {
    ...
}

interface ApplicationExtension {
    var compileSdk: Int
    var namespace: String?

    val defaultConfig: DefaultConfig

    fun defaultConfig(configure: DefaultConfig.() -> Unit) {
        ...
    }
}

Cada bloque en el DSL está representado por una función que toma una lambda para configurarlo y una propiedad con el mismo nombre para acceder a ella. Esto hace que el código de tus archivos de compilación se sienta más como una especificación de datos.

Dependencias externas

El sistema de compilación de Maven introdujo un sistema de almacenamiento y administración de dependencias. Las bibliotecas se almacenan en repositorios (servidores o directorios), con metadatos que incluyen su versión y dependencias en otras bibliotecas. Debes especificar qué repositorios buscar y las versiones de las dependencias que quieres usar. El sistema de compilación las descargará durante la compilación.

Los artefactos de Maven se identifican por el nombre del grupo (empresa, desarrollador, etcétera), el nombre del artefacto (el nombre de la biblioteca) y la versión de ese artefacto. Por lo general, se representa como group:artifact:version.

Este enfoque mejora significativamente la administración de compilaciones. A menudo, escucharás que estos repositorios se llaman “repositorios de Maven”, pero todo se relaciona con la forma en que se empaquetan y publican los artefactos. Estos repositorios y metadatos se reutilizaron en varios sistemas de compilación, incluido Gradle (y Gradle puede publicar en estos repositorios). Los repositorios públicos permiten compartir contenido para que todos los usen, y los de la empresa mantienen las dependencias internas de forma interna.

También puedes modularizar tu proyecto en subproyectos (también conocidos como "módulos" en Android Studio), que también se pueden usar como dependencias. Cada subproyecto produce resultados (como archivos JAR) que pueden ser consumidos por subproyectos o tu proyecto de nivel superior. Esto puede mejorar el tiempo de compilación, ya que aísla qué partes se deben volver a compilar, así como mejores responsabilidades separadas en la aplicación.

Analizaremos con más detalle cómo especificar dependencias en Cómo agregar dependencias de compilación.

Variantes de compilación

Cuando creas una aplicación para Android, por lo general, querrás compilar varias variantes. Las variantes contienen código diferente o se compilan con opciones diferentes, y constan de tipos de compilación y variantes de productos.

Los tipos de compilación varían las opciones de compilación declaradas. De forma predeterminada, AGP configura tipos de compilación de "versión" y "depuración", pero puedes ajustarlos y agregar más (quizás para pruebas internas o de etapa de pruebas).

Una compilación de depuración no reduce ni ofusca tu aplicación, lo que acelera su compilación y conserva todos los símbolos tal como están. También marca la aplicación como "depurable", la firma con una clave de depuración genérica y habilita el acceso a los archivos de aplicación instalados en el dispositivo. Esto permite explorar los datos guardados en archivos y bases de datos mientras se ejecuta la aplicación.

Una compilación de lanzamiento optimiza la aplicación, la firma con tu clave de lanzamiento y protege los archivos de la aplicación instalada.

Con las variantes de productos, puedes cambiar la fuente incluida y las variantes de dependencia de la aplicación. Por ejemplo, puedes crear variantes "demo" y "completa" para tu aplicación, o quizás variantes "gratuita" y "pagada". Escribes tu fuente común en un directorio de conjunto de orígenes "principal" y reemplazas o agregas la fuente en un conjunto de orígenes que se nombra según el tipo de producto.

AGP crea variantes para cada combinación de tipo de compilación y variante de producto. Si no defines tipos, las variantes reciben los nombres de los tipos de compilación. Si defines ambos, la variante se llamará <flavor><Buildtype>. Por ejemplo, con los tipos de compilación release y debug, y los perfiles demo y full, AGP creará las siguientes variantes:

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

Próximos pasos

Ahora que conoces los conceptos de compilación, observa la estructura de compilación de Android en tu proyecto.