Convention plugins
⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.
r8vnhill/echo-app-kt
En proyectos con múltiples módulos, es común y recomendable contar con un módulo dedicado a centralizar configuraciones compartidas que puedan ser reutilizadas por el resto del proyecto. En el contexto de Gradle, a este tipo de módulo se le conoce como un convention plugin, y también se asocia con el patrón conocido como buildSrc
.
Estos plugins permiten consolidar configuraciones comunes, lo que mejora la consistencia entre módulos y simplifica el mantenimiento general del proyecto.
Más adelante profundizaremos en el uso de plugins personalizados dentro del ecosistema Gradle. Por ahora, comenzaremos con una configuración básica.
🧩 Configuración de un Convention Plugin
Lo primero que debemos hacer es definir un módulo que se cargue antes que los demás. Esto se configura en el archivo settings.gradle.kts
, asegurando que el módulo de plugins esté disponible para el resto del proyecto desde el inicio.
// ...
pluginManagement {
includeBuild("convention-plugins")
repositories {
mavenCentral()
gradlePluginPortal()
}
}
- [2–8]: Esta sección configura la gestión de plugins en Gradle. Aquí es posible personalizar cómo se resuelven y desde dónde se descargan.
- [3]: Se incluye el módulo
convention-plugins
como un build externo (includeBuild
). Este módulo contendrá las configuraciones comunes que serán aplicadas automáticamente en el resto del proyecto. Al estar declarado aquí, se garantiza que se cargue antes que los demás módulos. - [4–7]: Se definen los repositorios desde los cuales se obtendrán los plugins:
- [5]:
mavenCentral()
: Repositorio central de Maven, ampliamente utilizado para dependencias y plugins. - [6]:
gradlePluginPortal()
: Repositorio oficial de Gradle para plugins.
- [5]:
🗂️ Configuración de Convention Plugins
El siguiente paso es crear la estructura de directorios necesaria para definir nuestros convention plugins. Estos plugins encapsulan configuraciones comunes que podrán ser reutilizadas en otros módulos del proyecto.
En este ejemplo, omitiremos el directorio de pruebas, ya que este tipo de módulo no suele requerir código testeable: su propósito es únicamente configurar comportamientos comunes.
🏗️ Crear la Estructura de Directorios
Primero, crea el directorio donde ubicaremos el código fuente del plugin de convenciones:
- Windows
- Windows (corto)
- Linux/macOS
New-Item -Path "convention-plugins\src\main\kotlin" `
-ItemType "Directory" `
-Force
Este comando crea el directorio convention-plugins\src\main\kotlin
, incluyendo todas las carpetas intermedias si no existen. El parámetro -ItemType "Directory"
indica que se debe crear un directorio, y -Force
asegura que no falle si alguna parte del path ya existe. La barra invertida invertida (`
) al final de cada línea permite dividir el comando en varias líneas para mayor legibilidad.
md "convention-plugins\src\main\kotlin" -f
Este comando utiliza el alias md
(abreviatura de mkdir
) para crear el directorio convention-plugins\src\main\kotlin
, incluyendo cualquier carpeta intermedia que no exista. El parámetro -f
(de force) evita errores si el directorio ya existe. Esta es una forma más corta y comúnmente usada en PowerShell para crear directorios.
mkdir -p convention-plugins/src/main/kotlin
Este comando crea el directorio convention-plugins/src/main/kotlin
y, gracias al parámetro -p
, también crea automáticamente cualquier directorio intermedio que no exista. Es una forma segura y habitual de crear estructuras de carpetas en sistemas basados en Unix (Linux y macOS), sin generar errores si los directorios ya existen.
⚙️ Configuración de settings.gradle.kts
Dentro de la carpeta principal convention-plugins
, debemos crear el archivo settings.gradle.kts
. Este archivo configura el nombre del proyecto, la gestión de plugins y la resolución de dependencias para este módulo.
rootProject.name = "convention-plugins"
// Gestión de plugins y definición de repositorios para su descarga
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
// Configuración para la resolución de dependencias
@Suppress("UnstableApiUsage")
dependencyResolutionManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
// Definición de un catálogo centralizado de versiones de dependencias
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
Este archivo settings.gradle.kts
define la configuración principal del módulo convention-plugins
. En la línea 1 se establece el nombre del proyecto, necesario para que Gradle lo identifique correctamente como un build independiente.
La sección pluginManagement
(líneas 4–9) indica desde qué repositorios deben descargarse los plugins utilizados en este módulo. Aquí se usan mavenCentral()
y gradlePluginPortal()
, que cubren la mayoría de los plugins comunes en proyectos Java y Kotlin.
La sección dependencyResolutionManagement
(líneas 12–25) configura cómo se resolverán las dependencias declaradas en los archivos build.gradle.kts
del módulo. Se especifican los mismos repositorios, y además se declara un catálogo de versiones (versionCatalogs
) en la línea 20, apuntando al archivo libs.versions.toml
ubicado fuera del módulo. Este catálogo permite mantener versiones centralizadas y consistentes en todos los módulos del proyecto.
@Suppress("UnstableApiUsage")
Se incluye la anotación @Suppress("UnstableApiUsage")
porque la propiedad repositories
dentro de dependencyResolutionManagement
se considera inestable en las versiones actuales de Gradle, y su uso genera un warning en compilaciones con Kotlin DSL.
¿Por qué aparece el warning sobre "UnstableApiUsage"?
En el archivo settings.gradle.kts
del módulo convention-plugins
, se utiliza la anotación @Suppress("UnstableApiUsage")
antes de la configuración de dependencyResolutionManagement
. Esto se debe a que, en las versiones actuales de Gradle, al invocar repositories
dentro de dependencyResolutionManagement
, se genera una advertencia indicando que la función está marcada como inestable con la anotación @Incubating
.
La anotación @Incubating
en Gradle señala que una característica o API está en fase de incubación, lo que significa que aún está en desarrollo y podría sufrir cambios en futuras versiones. Aunque estas funcionalidades son utilizables, es importante ser consciente de su estado y de la posibilidad de modificaciones. Para suprimir las advertencias relacionadas con el uso de APIs inestables, se emplea @Suppress("UnstableApiUsage")
, lo que permite mantener el código limpio y sin advertencias innecesarias.
Es recomendable revisar periódicamente la documentación oficial de Gradle para estar al tanto del estado de las APIs utilizadas y ajustar el código en consecuencia a medida que evolucionan hacia versiones estables.
📑 Catálogo de Versiones
Aunque no es obligatorio, es una buena práctica centralizar las versiones de las dependencias de un proyecto utilizando un archivo .toml
. Este enfoque garantiza consistencia en las versiones utilizadas en todo el proyecto, evitando problemas de incompatibilidad y facilitando el mantenimiento.
El catálogo de versiones se organiza en cuatro secciones principales:
- Versions: Especifica las versiones de las dependencias, permitiendo su reutilización en las secciones de bibliotecas y plugins.
- Libraries: Define las bibliotecas necesarias para el proyecto, asociándolas con las versiones especificadas en la sección de versiones.
- Bundles: Agrupa varias bibliotecas bajo un mismo nombre, facilitando la inclusión de conjuntos de dependencias relacionadas.
- Plugins: Configura los plugins del proyecto, también vinculados a las versiones centralizadas.
Este enfoque te ayuda a tener un control centralizado sobre las versiones, evitando la repetición de versiones en múltiples archivos y garantizando que todas las dependencias y componentes utilicen las mismas versiones.
Ejemplo de archivo libs.versions.toml
:
Definiremos un catálogo de versiones en un archivo libs.versions.toml
para centralizar las versiones de las dependencias de nuestro proyecto:
[versions]
kotlin = "2.1.10"
echo = "1.0.0" # Versión de nuestro proyecto
[libraries]
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
- La sección
[versions]
define las versiones que serán utilizadas en las dependencias. En este caso, se especifica la versión de Kotlin y una versión para nuestro proyecto (echo
). - En la sección
[libraries]
, definimos la bibliotecakotlin-gradle-plugin
y vinculamos su versión a la referenciakotlin
especificada anteriormente en la sección[versions]
.
libs.versions.toml
El nombre libs.versions.toml
es solo una convención y puedes elegir cualquier nombre que refleje su propósito,
incluso dividirlo en varios archivos si es necesario. En este curso utilizaremos esta convención de nombre, ya que es
común en la comunidad de Gradle.
Al usar esta estructura, cualquier actualización futura de la versión de Kotlin solo requiere modificar el archivo
libs.versions.toml
en un único lugar, manteniendo la consistencia en todo el proyecto.
🛠️ Crear el archivo de construcción
Dentro de la carpeta convention-plugins
, crea un archivo de construcción llamado build.gradle.kts
. Este archivo define cómo se compila y configura el módulo de plugins de convención.
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation(libs.kotlin.gradle.plugin)
}
Para que los plugins de convención escritos con Kotlin DSL sean detectados correctamente por Gradle, es necesario aplicar el plugin kotlin-dsl
.
- [2]
`kotlin-dsl`
: Se aplica el plugin usando backticks debido a que el nombre contiene un guion, lo que lo hace inválido como identificador normal en Kotlin. Este plugin habilita la escritura de scripts en Kotlin y permite que los plugins precompilados sean descubiertos automáticamente por el sistema de compilación. - [5–8]
repositories
: Especifica de dónde se obtendrán las dependencias y plugins necesarios. Se utilizanmavenCentral()
ygradlePluginPortal()
por su amplio soporte. - [11]
implementation(libs.kotlin.gradle.plugin)
: Agrega el plugin de Kotlin para Gradle como dependencia. Esta línea asume que el proyecto utiliza un catálogo de versiones (libs.versions.toml
), lo cual permite gestionar versiones de forma centralizada y coherente.
Backticks en el nombramiento de variables y funciones
En Kotlin, los backticks (`...`
) permiten declarar identificadores que normalmente no serían válidos, como aquellos que contienen guiones (-
) o espacios.
Esto es especialmente útil en archivos build.gradle.kts
, donde algunos plugins tienen nombres con guiones. Por ejemplo:
plugins {
`kotlin-dsl` // válido gracias a los backticks
}
Sin los backticks, kotlin-dsl
no sería un identificador válido porque contiene un guion (-
).
Este mecanismo existe principalmente por compatibilidad con Groovy, el lenguaje original usado por Gradle. En Groovy, los nombres de plugins como kotlin-dsl
se declaraban como cadenas:
plugins {
id "kotlin-dsl"
}
Para mantener una sintaxis similar en Kotlin DSL, se permite el uso de backticks alrededor del nombre. Así, la migración y la legibilidad entre ambos DSLs es más natural.
- Úsalos solo cuando sea necesario: Los backticks deben reservarse para nombres que no serían válidos como identificadores normales.
- Evita abusar de nombres arbitrarios: Aunque Kotlin permite cualquier texto entre backticks, como
`some weird name`
, eso puede volver tu código menos mantenible. - Prefiere nombres válidos sin backticks si es posible, para aprovechar el soporte completo de autocompletado y verificación estática.
🧪 Mi Primer Plugin
Plugin
Un plugin es un componente de software que extiende o modifica la funcionalidad de un sistema existente.
En el contexto de Gradle, un plugin es un conjunto de tareas, configuraciones y comportamientos que se pueden aplicar a un proyecto para automatizar su construcción y administración.
Gradle permite definir plugins de distintas formas. Una de las más utilizadas es mediante plugins de convención, que encapsulan configuraciones comunes aplicadas automáticamente a todos los subproyectos de un proyecto.
En esta sección, crearemos un plugin de convención básico para proyectos basados en la JVM. Comenzaremos con una configuración mínima y la iremos expandiendo en las siguientes secciones.
✨ Creando el Plugin
Primero, vamos a crear el archivo que contendrá nuestro plugin. Dentro de la carpeta convention-plugins/src/main/kotlin
, crea un archivo llamado jvm.conventions.gradle.kts
con el siguiente contenido:
plugins {
kotlin("jvm")
}
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
Este archivo define un plugin de convención que aplica automáticamente el plugin kotlin("jvm")
y configura la versión del compilador de Java a través del bloque jvmToolchain
.
- [2]: Aplica el plugin de Kotlin para la JVM. Al estar definido en un archivo de convención, se aplicará de forma implícita a todos los módulos que utilicen este plugin.
- [5–9]: Configura la herramienta de compilación (
jvmToolchain
) para usar Java 21 como versión del lenguaje. Esto garantiza que todos los proyectos que usen este plugin compilen con la misma versión de Java, asegurando compatibilidad y consistencia.
❓ ¿Por qué no especificamos la versión del plugin?
Aunque es común declarar el plugin con una versión explícita, como en el siguiente ejemplo:
plugins {
kotlin("jvm") version "2.1.20"
}
en este caso no es necesario, ya que hemos centralizado la gestión de versiones mediante el archivo libs.versions.toml
. Esta estrategia asegura que todas las dependencias y plugins de Kotlin utilicen la misma versión, facilitando su actualización y manteniendo coherencia en todo el proyecto.
📦 Usando el Plugin
Una vez creado nuestro plugin de convenciones, podemos aplicarlo en los subproyectos donde queramos que se utilicen esas configuraciones comunes. En este ejemplo, lo aplicaremos en dos subproyectos: app
y lib
.
Primero, necesitamos crear las carpetas correspondientes para cada subproyecto. Puedes hacerlo con los siguientes comandos según tu sistema operativo:
- Windows
- Windows (corto)
- Linux/macOS
"src\main\kotlin" | ForEach-Object {
"app\$_", "lib\$_" | ForEach-Object {
New-Item -Path $_ -ItemType "Directory" -Force
}
}
Este fragmento utiliza dos bucles ForEach-Object
anidados para crear de forma concisa los directorios app\src\main\kotlin
y lib\src\main\kotlin
.
- En el primer pipeline (
"src\main\kotlin"
), se define el segmento de ruta común que será añadido a cada módulo. - En el segundo pipeline (
"app\$_", "lib\$_"
), se generan las rutas completas para ambos subproyectos combinando los nombres de los módulos (app
,lib
) con la ruta común. New-Item
crea los directorios y el parámetro-Force
asegura que no se generen errores si alguno ya existe.
Este patrón permite mantener el código limpio y fácilmente extensible en caso de que se agreguen más módulos o rutas.
"src\main\kotlin" | % { "app\$_", "lib\$_" | % { md $_ -f } }
Este comando usa una forma abreviada y funcional de crear múltiples directorios en PowerShell utilizando el alias %
para ForEach-Object
.
"src\main\kotlin"
es la ruta común que se desea agregar a los módulosapp
ylib
.- En el primer
%
, se toma ese valor ($_
) y se construyen dos rutas:"app\$_"
y"lib\$_"
, es decir,app\src\main\kotlin
ylib\src\main\kotlin
. - El segundo
%
recorre esas rutas y ejecutamd
(alias demkdir
) con el parámetro-f
(alias de-Force
), que crea los directorios y no lanza error si ya existen.
Es una forma compacta y efectiva de crear varias rutas en un solo comando.
printf "%s\n" app lib | xargs -I{} mkdir -pv "{}/src/main/kotlin"
Este comando crea las carpetas app/src/main/kotlin
y lib/src/main/kotlin
de forma compacta y funcional:
printf "%s\n" app lib
imprime cada nombre de módulo (app
ylib
) en una línea separada.xargs -I{}
toma cada línea y reemplaza{}
en el comandomkdir -pv "{}/src/main/kotlin"
.mkdir -p
crea todos los directorios intermedios si no existen.-v
(verbose) muestra en consola qué directorios se están creando.
Esto asegura que se creen correctamente dos rutas independientes, evitando el error común de que se cree una carpeta llamada "app lib"
si se usa echo
sin separación de líneas.
Si no se imprime nada en consola, es porque las carpetas ya existían. En ese caso, mkdir -p
no realiza ninguna acción y suprime cualquier salida, incluso con -v
.
En el subproyecto app
:
plugins {
id("jvm.conventions")
}
En el subproyecto lib
:
plugins {
id("jvm.conventions")
}
Al aplicar el plugin jvm.conventions
en los subproyectos app
y lib
, aseguramos que ambos compartan una base común de configuraciones para proyectos basados en la JVM. En su forma actual, este plugin aplica automáticamente el plugin de Kotlin para JVM y configura la versión del compilador.
A medida que el plugin crezca, podremos extenderlo con reglas, convenciones y dependencias adicionales que se aplicarán de forma coherente en todos los subproyectos, facilitando el mantenimiento y promoviendo la consistencia del proyecto.
🔍 Verificar que el Plugin se Aplica Correctamente
Una vez aplicado el plugin en los subproyectos, puedes verificar que las configuraciones se hayan aplicado correctamente utilizando algunos comandos de Gradle:
- Windows
- Windows (corto)
- Linux/Mac
.\gradlew --quiet app:dependencies | Select-String "kotlin"
Este comando permite verificar si el plugin kotlin("jvm")
fue aplicado correctamente en el subproyecto app
.
.\gradlew app:dependencies
imprime el árbol de dependencias del subproyecto.--quiet
reduce el ruido en la salida, omitiendo mensajes como"Task :convention-plugins:checkKotlinGradlePluginConfigurationErrors SKIPPED"
.| Select-String "kotlin"
filtra la salida mostrando solo las líneas relacionadas con Kotlin, lo que permite confirmar si las dependencias del compilador y estándar de Kotlin están presentes.
Esto es útil para validar que el plugin de convenciones está funcionando correctamente.
Como se usó la opción --quiet
, no se mostrarán mensajes durante la ejecución del comando, a menos que haya errores o advertencias. Por eso, podría parecer que no ocurre nada, pero en realidad se está filtrando la salida para mostrar solo las dependencias relacionadas con Kotlin. ¡Paciencia y confianza! 😉
.\gradlew -q app:dependencies | sls "kotlin"
Este comando compacto verifica si el plugin kotlin("jvm")
fue aplicado correctamente en el subproyecto app
:
.\gradlew -q app:dependencies
imprime las dependencias de forma silenciosa, omitiendo mensajes irrelevantes.sls "kotlin"
es un alias deSelect-String
y filtra la salida para mostrar solo las líneas que contienen la palabrakotlin
.
Esto permite confirmar rápidamente que las dependencias de Kotlin están presentes sin ruido adicional.
Como se usó la opción -q
, no se mostrarán mensajes durante la ejecución del comando, a menos que haya errores o advertencias. Por eso, podría parecer que no ocurre nada, pero en realidad se está filtrando la salida para mostrar solo las dependencias relacionadas con Kotlin. ¡Paciencia y confianza! 😉
./gradlew --quiet app:dependencies | grep kotlin
Este comando permite verificar si el plugin kotlin("jvm")
fue aplicado correctamente al subproyecto app
:
./gradlew --quiet app:dependencies
ejecuta la tarea que imprime el árbol de dependencias del subproyecto, y la opción--quiet
reduce la salida omitiendo tareas informativas y mensajes innecesarios.| grep kotlin
filtra la salida para mostrar únicamente las líneas que contienen la palabrakotlin
, facilitando la identificación de dependencias del compilador, estándar u otras herramientas de Kotlin.
Este comando es útil para confirmar que el plugin de convenciones está funcionando como se espera.
Al usar la opción --quiet
, no se mostrarán mensajes durante la ejecución del comando, a menos que haya errores o advertencias. Por eso, podría parecer que no ocurre nada, pero en realidad se está filtrando la salida para mostrar solo las dependencias relacionadas con Kotlin. ¡Paciencia y confianza! 😉
Si la salida del comando muestra dependencias relacionadas con Kotlin, significa que el plugin se aplicó correctamente y que las configuraciones comunes están siendo compartidas entre los subproyectos. En caso contrario, es probable que el plugin no se haya aplicado correctamente, por lo que conviene revisar los pasos anteriores.
🧱 Estructura Final del Proyecto
Después de ejecutar el comando de construcción, tu proyecto debería tener una estructura de directorios similar a la siguiente:
🎯 Conclusiones
En esta lección aprendimos a crear y aplicar un plugin de convenciones en un proyecto Gradle con múltiples módulos. Exploramos cómo encapsular configuraciones comunes dentro de un módulo especializado, reutilizable y fácilmente mantenible.
Este enfoque no solo promueve la coherencia entre los subproyectos, sino que también reduce la repetición, centraliza las decisiones de configuración y facilita el crecimiento del proyecto a medida que evoluciona.
🔑 Puntos clave
- Un plugin de convención encapsula comportamientos, dependencias y configuraciones comunes que pueden aplicarse automáticamente en múltiples módulos.
- La instrucción
includeBuild("convention-plugins")
permite que el módulo esté disponible desde el inicio del proceso de construcción. - La combinación de convenciones con un catálogo de versiones (
libs.versions.toml
) ayuda a mantener dependencias actualizadas, coherentes y fáciles de gestionar. - Gradle detecta automáticamente los plugins definidos en archivos como
jvm.conventions.gradle.kts
si se respeta la convención de nombres. - Verificar la aplicación del plugin asegura que el comportamiento esperado se esté aplicando en cada subproyecto.
🧰 ¿Qué nos llevamos?
Adoptar plugins de convención desde las primeras etapas de un proyecto no es solo una cuestión de orden: es una decisión estratégica. Esta práctica convierte decisiones repetitivas en configuraciones automáticas, alivia la carga cognitiva de cada submódulo, y favorece una arquitectura escalable y profesional.
A medida que el proyecto crezca, contar con una base sólida y coherente en su configuración hará una gran diferencia, tanto para quienes lo mantienen como para quienes se incorporen más adelante.
📖 Referencias
🔥 Recomendadas
- 🌐 Sharing build logic between subprojects Sample. (s. f.). Recuperado 27 de marzo de 2025, de https://docs.gradle.org/current/samples/sample_convention_plugins.html
🔹 Adicionales
- 📚 Build script essentials. (2014). En B. Muschko & H. Dockter, Gradle in action (pp. 75–104). Manning.