Descripción general de la compilación de Gradle

Por lo general, las aplicaciones para Android se compilan con el sistema de compilación 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 compilació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 archivo 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 puedes configurar la compilación de forma más detallada a través de un lenguaje específico del 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 de 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 código arbitrario y declaraciones de tareas en tus archivos de compilación, esto puede dificultar que las herramientas comprendan tu compilación y que la mantengas. Por ejemplo, puedes escribir pruebas para el código dentro de los complementos, pero no en los archivos de compilación. En su lugar, debes restringir la lógica de compilación y las declaraciones de tareas a los complementos (que tú o alguien más definan) y declarar cómo quieres 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 classpaths que contienen tus archivos de compilación y los complementos aplicados. Esta fase se enfoca en un archivo de configuración en el que declaras los proyectos que se compilarán y las ubicaciones desde las que se recuperarán los complementos y las bibliotecas.
  • La configuración registra 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 o 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 del 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, puedan contribuir 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 al 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 de la DSL está representado por una función que toma una lambda para configurarla 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 especificación, 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. Especificas qué repositorios buscar, las versiones de las dependencias que deseas usar y el sistema de compilación las descarga 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 que todos los usuarios los compartan, y los repositorios de la empresa mantienen las dependencias internas en la empresa.

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 se aíslan las partes que se deben volver a compilar y se separan mejor las responsabilidades 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 se componen de tipos de compilación y variantes de producto.

Los tipos de compilación varían las opciones de compilación declaradas. De forma predeterminada, AGP configura los tipos de compilación "release" y "debug", pero puedes ajustarlos y agregar más (tal vez 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 la 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 instalados.

Con las variantes de producto, 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 variantes, las variantes se nombrarán según 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.