Configuraci贸n de proyectos de varios m贸dulos

Antecedentes



A veces, cuando pospongo las cosas, hago la limpieza: limpio la mesa, saco las cosas, arreglo la habitaci贸n. De hecho, pongo el ambiente en orden, te da energ铆a y te prepara para trabajar. Con la programaci贸n tengo la misma situaci贸n, solo que limpio el proyecto: realizo refactorizaciones, hago varias herramientas y hago todo lo posible para hacerme la vida m谩s f谩cil a m铆 y a mis compa帽eros.



Hace alg煤n tiempo, en el equipo de Android decidimos hacer uno de nuestros proyectos, Wallet , multimodular. Esto gener贸 una serie de ventajas y problemas, uno de los cuales es la necesidad de configurar cada m贸dulo desde cero. Por supuesto, puede simplemente copiar la configuraci贸n de un m贸dulo a otro, pero si queremos cambiar algo, tendremos que iterar sobre todos los m贸dulos.



No me gusta esto, al equipo no le gusta, y estos son los pasos que hemos tomado para simplificar nuestras vidas y hacer que las configuraciones sean m谩s f谩ciles de mantener.







Primera iteraci贸n: extracci贸n de versiones de la biblioteca



De hecho, esto ya estaba en el proyecto antes que yo, y es posible que conozca este enfoque. A menudo veo que los desarrolladores lo usan.



El enfoque es que es necesario mover las versiones de las bibliotecas a propiedades globales separadas del proyecto, luego est谩n disponibles en todo el proyecto, lo que ayuda a reutilizarlas. Esto generalmente se hace en el archivo build.gradle a nivel del proyecto, pero a veces estas variables se toman en un archivo .gradle separado y se incluyen en el build.gradle principal.



Lo m谩s probable es que ya haya visto dicho c贸digo en el proyecto. No tiene nada de m谩gico, es solo una de las extensiones de Gradle llamada ExtraPropertiesExtension . En resumen, es solo Map <String, Object>, disponible por ext en el objeto del proyecto, y todo lo dem谩s, trabajando como con un objeto, bloques de configuraci贸n, etc., la magia de Gradle. Ejemplos:

.gradle .gradle.kts
// creation
ext {
  dagger = '2.25.3'
  fabric = '1.25.4'
  mindk = 17
}

// usage
println(dagger)
println(fabric)
println(mindk)


// creation
val dagger by extra { "2.25.3" }
val fabric by extra { "1.25.4" }
val minSdk by extra { 17 }

// usage
val dagger: String by extra.properties
val fabric: String by extra.properties
val minSdk: Int by extra.properties




Lo que me gusta de este enfoque es que es extremadamente simple y ayuda a evitar que las versiones se rompan. Pero tiene inconvenientes: debe asegurarse de que los desarrolladores usen versiones de este conjunto, y esto no simplifica mucho la creaci贸n de nuevos m贸dulos, porque todav铆a tiene que copiar muchas cosas.



Por cierto, se puede lograr un efecto similar usando gradle.properties en lugar de ExtraPropertiesExtension, solo tenga cuidado : sus versiones pueden ser anuladas al construir usando las banderas -P, y si se refiere a una variable simplemente por su nombre en groovy-scripts, entonces gradle.properties ser谩 reemplazado y ellos. Ejemplo con gradle.properties y override:



// grdle.properties
overriden=2

// build.gradle
ext.dagger = 1
ext.overriden = 1

// module/build.gradle
println(rootProject.ext.dagger)   // 1
println(dagger)                   // 1

println(rootProject.ext.overriden)// 1
println(overriden)                // 2


Segunda iteraci贸n - project.subprojects



Mi curiosidad, que recordaba mi falta de voluntad para copiar el c贸digo y ocuparme de la configuraci贸n de cada m贸dulo, me llev贸 al siguiente paso: record茅 que en el root build.gradle hay un bloque que se genera por defecto - allprojects .



allprojects {
    repositories {
        google()
        jcenter()
    }
}


Fui a la documentaci贸n y descubr铆 que era posible pasarle un bloque de c贸digo que configurar铆a este proyecto y todos los proyectos anidados. Pero esto no es exactamente lo que necesitaba, as铆 que me desplac茅 m谩s y encontr茅 subproyectos , un m茅todo para configurar todos los proyectos anidados a la vez. Tuve que agregar algunos cheques, y esto es lo que sucedi贸 .



Ejemplo de configuraci贸n de m贸dulos mediante project.subprojects
subprojects { project ->
    afterEvaluate {
        final boolean isAndroidProject =
            (project.pluginManager.hasPlugin('com.android.application') ||
                project.pluginManager.hasPlugin('com.android.library'))

        if (isAndroidProject) {
            apply plugin: 'kotlin-android'
            apply plugin: 'kotlin-android-extensions'
            apply plugin: 'kotlin-kapt'
            
            android {
                compileSdkVersion rootProject.ext.compileSdkVersion
                
                defaultConfig {
                    minSdkVersion rootProject.ext.minSdkVersion
                    targetSdkVersion rootProject.ext.targetSdkVersion
                    
                    vectorDrawables.useSupportLibrary = true
                }

                compileOptions {
                    encoding 'UTF-8'
                    sourceCompatibility JavaVersion.VERSION_1_8
                    targetCompatibility JavaVersion.VERSION_1_8
                }

                androidExtensions {
                    experimental = true
                }
            }
        }

        dependencies {
            if (isAndroidProject) {
                // android dependencies here
            }
            
            // all subprojects dependencies here
        }

        project.tasks
            .withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile)
            .all {
                kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
            }
    }
}




Ahora, para cualquier m贸dulo con el complemento com.android.application o com.android.library conectado, podemos configurar cualquier cosa: complementos de complementos, configuraciones de complementos, dependencias.



Todo estar铆a bien, si no fuera por un par de problemas: si queremos anular algunos par谩metros especificados en subproyectos en un m贸dulo, entonces no podremos hacerlo, porque el m贸dulo se configura antes de aplicar los subproyectos (gracias a afterEvaluate ). Y adem谩s, si queremos no aplicar esta configuraci贸n autom谩tica en m贸dulos individuales, entonces empezar谩n a aparecer muchas comprobaciones adicionales en el bloque de subproyectos. Entonces comenc茅 a pensar m谩s.



Tercera iteraci贸n: buildSrc y plugin



Hasta este punto, hab铆a o铆do hablar de buildSrc varias veces y vi ejemplos en los que buildSrc se usaba como alternativa al primer paso de este art铆culo. Y tambi茅n escuch茅 sobre los complementos de Gradle, as铆 que comenc茅 a investigar en esta direcci贸n. Todo result贸 muy simple: Gradle tiene documentaci贸n para desarrollar complementos personalizados , en la que todo est谩 escrito.



Despu茅s de entender un poco, hice un complemento que puede configurar todo lo que necesita cambiarse con la capacidad de cambiar si es necesario.



C贸digo del complemento
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project

class ModulePlugin implements Plugin<Project> {
    @Override
    void apply(Project target) {
        target.pluginManager.apply("com.android.library")
        target.pluginManager.apply("kotlin-android")
        target.pluginManager.apply("kotlin-android-extensions")
        target.pluginManager.apply("kotlin-kapt")

        target.android {
            compileSdkVersion Versions.sdk.compile

            defaultConfig {
                minSdkVersion Versions.sdk.min
                targetSdkVersion Versions.sdk.target

                javaCompileOptions {
                    annotationProcessorOptions {
                        arguments << ["dagger.gradle.incremental": "true"]
                    }
                }
            }

            // resources prefix: modulename_
            resourcePrefix "${target.name.replace("-", "_")}_"

            lintOptions {
                baseline "lint-baseline.xml"
            }

            compileOptions {
                encoding 'UTF-8'
                sourceCompatibility JavaVersion.VERSION_1_8
                targetCompatibility JavaVersion.VERSION_1_8
            }

            testOptions {
                unitTests {
                    returnDefaultValues true
                    includeAndroidResources true
                }
            }
        }

        target.repositories {
            google()
            mavenCentral()
            jcenter()
            
            // add other repositories here
        }

        target.dependencies {
            implementation Dependencies.dagger.dagger
            implementation Dependencies.dagger.android
            kapt Dependencies.dagger.compiler
            kapt Dependencies.dagger.androidProcessor

            testImplementation Dependencies.test.junit
            
            // add other dependencies here
        }
    }
}




Ahora la configuraci贸n del nuevo proyecto parece aplicar el complemento: 鈦犫仩鈦犫仩鈦犫仩鈦犫仩鈦犫仩鈦犫仩'ru.yandex.money.module ' y eso es todo. Puedes hacer tus propias adiciones al bloque android o de dependencias, puedes agregar plugins o personalizarlos, pero lo principal es que el nuevo m贸dulo se configura en una l铆nea, y su configuraci贸n siempre es relevante y el desarrollador del producto ya no necesita pensar en configurarlo.



De las desventajas, se帽alar铆a que esta soluci贸n requiere tiempo adicional y estudio del material, pero, desde mi punto de vista, vale la pena. Si desea mover el complemento como un proyecto separado en el futuro, no recomendar铆a configurar dependencias entre m贸dulos en el complemento .



Un punto importante: si est谩 utilizando el complemento gradle de Android por debajo de 4.0, algunas cosas son muy dif铆ciles de hacer en los scripts de kotlin; al menos el bloque de Android es m谩s f谩cil de configurar en los scripts maravillosos. Existe un problema con el hecho de que algunos tipos no est谩n disponibles en el momento de la compilaci贸n, y groovy se escribe din谩micamente, y no le importa =)



Siguiente: complemento independiente o monorepo



Por supuesto, el tercer paso no lo es todo. No hay l铆mite para la perfecci贸n, por lo que hay opciones sobre a d贸nde ir a continuaci贸n.



La primera opci贸n es el complemento independiente para gradle. Despu茅s del tercer paso, ya no es tan dif铆cil: necesita crear un proyecto separado, transferir el c贸digo all铆 y configurar la publicaci贸n.



Ventajas: el complemento se puede buscar entre varios proyectos, lo que simplificar谩 la vida no en un proyecto, sino en el ecosistema.



Contras: control de versiones: al actualizar un complemento, tendr谩 que actualizar y verificar su funcionalidad en varios proyectos a la vez, y esto puede llevar tiempo. Por cierto, mis colegas del desarrollo backend tienen una excelente soluci贸n en este tema, la palabra clave es modernizador, una herramienta que recorre los repositorios y actualiza las dependencias. No me detendr茅 en esto por mucho tiempo, es mejor que se lo digan ellos mismos.



Monorepo: suena fuerte, pero no tengo experiencia con 茅l, pero solo hay consideraciones de que un proyecto, como buildSrc, se puede usar en varios otros proyectos a la vez, y esto podr铆a ayudar a resolver el problema con el control de versiones. Si de repente tienes experiencia con monorepo, comp谩rtelo en los comentarios para que yo y otros lectores podamos aprender algo al respecto.



Total



En un proyecto nuevo, haga el tercer paso de inmediato (buildSrc y plugin), ser谩 m谩s f谩cil para todos, especialmente porque he adjuntado el c贸digo . Y el segundo paso, project.subprojects, se utiliza para conectar m贸dulos comunes entre s铆.



Si tienes algo que agregar u objetar, escribe en los comentarios o b煤scame en las redes sociales.



All Articles