Configuración de proyecto repetitivo en Gradle con Gradle Script Kotlin

Actualmente estoy tratando de mejorar la forma en que nuestros proyectos comparten su configuration. Tenemos muchos proyectos diferentes de modules múltiples para todas nuestras bibliotecas y microservices (es decir, muchos repositorys git).

Mis principales objectives son:

  • No tener duplicada la configuration de mi repository de Nexus en cada proyecto (también, puedo suponer con security que la URL no cambiará)
  • Para hacer que mis plugins de Gradle personalizados (publicados en Nexus) estén disponibles para cada proyecto con una repetición / duplicación mínima (deben estar disponibles para cada proyecto, y lo único que le importa al proyecto es la versión que está usando)
  • Sin magia: debería ser obvio para los desarrolladores cómo se configura todo.

Mi solución actual es una distribución gradle personalizada con un script de inicio que:

  • agrega mavenLocal() y nuestro repository de Nexus a los repos de proyecto (muy similar al ejemplo de la documentation del script de inicio de Gradle, excepto que agrega repos y también los valida)
  • configura una extensión que permite que nuestros plugins de gradle se agreguen a classpath de buildscript (usando esta solución ). También agrega nuestro repository de Nexus como un repository de compilation ya que es donde se alojan los complementos. Tenemos bastantes complementos (basados ​​en los excelentes plugins de nebulosas de Netflix) para varios estándares: configuration de proyecto estándar (configuration de kotlin, configuration de testing, etc.), publicación, publicación, documentation, etc. y significa que nuestros files build.gradle proyecto son más o less solo para dependencies.

Aquí está el guión de inicio (desinfectado):

 /** * Gradle extension applied to all projects to allow automatic configuration of Corporate plugins. */ class CorporatePlugins { public static final String NEXUS_URL = "https://example.com/repository/maven-public" public static final String CORPORATE_PLUGINS = "com.example:corporate-gradle-plugins" def buildscript CorporatePlugins(buildscript) { this.buildscript = buildscript } void version(String corporatePluginsVersion) { buildscript.repositories { maven { url NEXUS_URL } } buildscript.dependencies { classpath "$CORPORATE_PLUGINS:$corporatePluginsVersion" } } } allprojects { extensions.create('corporatePlugins', CorporatePlugins, buildscript) } apply plugin: CorporateInitPlugin class CorporateInitPlugin implements Plugin<Gradle> { void apply(Gradle gradle) { gradle.allprojects { project -> project.repositories { all { ArtifactRepository repo -> if (!(repo instanceof MavenArtifactRepository)) { project.logger.warn "Non-maven repository ${repo.name} detected in project ${project.name}. What are you doing???" } else if(repo.url.toString() == CorporatePlugins.NEXUS_URL || repo.name == "MavenLocal") { // Nexus and local maven are good! } else if (repo.name.startsWith("MavenLocal") && repo.url.toString().startsWith("file:")){ // Duplicate local maven - remove it! project.logger.warn("Duplicate mavenLocal() repo detected in project ${project.name} - the corporate gradle distribution has already configunetworking it, so you should remove this!") remove repo } else { project.logger.warn "External repository ${repo.url} detected in project ${project.name}. You should only be using Nexus!" } } mavenLocal() // define Nexus repo for downloads maven { name "CorporateNexus" url CorporatePlugins.NEXUS_URL } } } } } 

Luego configuro cada nuevo proyecto añadiendo lo siguiente al file raíz build.gradle:

 buildscript { // makes our plugins (and any others in Nexus) available to all build scripts in the project allprojects { corporatePlugins.version "1.2.3" } } allprojects { // apply plugins relevant to all projects (other plugins are applied where requinetworking) apply plugin: 'corporate.project' group = 'com.example' // allows quickly updating the wrapper for our custom distribution task wrapper(type: Wrapper) { distributionUrl = 'https://com.example/repository/maven-public/com/example/corporate-gradle/3.5/corporate-gradle-3.5.zip' } } 

Si bien este enfoque funciona, permite comstackciones reproducibles (a diferencia de nuestra configuration anterior que aplicaba un script de compilation desde una URL, que en ese momento no era almacenable en caching) y permite trabajar sin connection, lo hace un poco mágico y me preguntaba si podría hacer las cosas mejor.

Todo esto se debió a leer un comentario sobre Github por el desarrollador de Gradle Stefan Oehme que afirmaba que una compilation debería funcionar sin depender de un guión de inicio, es decir, los guiones init deberían ser decorativos y hacer cosas como el ejemplo documentado, evitando repositorys no autorizados, etc.

Mi idea era escribir algunas funciones de extensión que me permitieran agregar nuestro repository Nexus y complementos a una compilation de una manera que pareciera que estaban integrados en gradle (similar a las funciones de extensión gradleScriptKotlin() y kotlin-dsl() proporcionadas por el Gradle Kotlin DSL.

Así que creé mis funciones de extensión en un proyecto de kotlin gradle:

 package com.example import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.api.artifacts.dsl.RepositoryHandler import org.gradle.api.artifacts.repositories.MavenArtifactRepository fun RepositoryHandler.corporateNexus(): MavenArtifactRepository { return maven { with(it) { name = "Nexus" setUrl("https://example.com/repository/maven-public") } } } fun DependencyHandler.corporatePlugins(version: String) : Any { return "com.example:corporate-gradle-plugins:$version" } 

Con el plan para usarlos en build.gradle.kts mi proyecto de la siguiente manera:

 import com.example.corporateNexus import com.example.corporatePlugins buildscript { repositories { corporateNexus() } dependencies { classpath(corporatePlugins(version = "1.2.3")) } } 

Sin embargo, Gradle no pudo ver mis funciones cuando se usaba en el bloque de compilation (no se puede comstackr el guión). Sin embargo, usarlos en repositorys / dependencies de proyectos normales funcionó bien (están visibles y funcionan como se esperaba).

Si esto funcionaba, esperaba agrupar el contenedor en mi distribución personalizada, lo que significa que mi script de inicio podría simplemente hacer una validation simple en lugar de ocultar el complemento mágico y la configuration del repository. Las funciones de extensión no tendrían que cambiar, por lo que no sería necesario liberar una nueva distribución de Gradle cuando cambien los complementos.

Lo que probé:

  • agregar mi jar al classpath del buildscript del proyecto de testing (es decir, buildscript.dependencies ) – no funciona (quizás esto no funciona por layout, ya que no parece correcto agregar una dependencia al buildscript que se hace reference en el mismo bloque )
  • poniendo las funciones en buildSrc (que funciona para deps / repos de proyectos normales pero no para buildscript , pero no es una solución real, ya que solo mueve la plantilla)
  • soltando el jar en la carpeta lib de la distribución

Entonces mi pregunta realmente se networkinguce a:

  • ¿Es posible lo que bash lograr (es posible hacer que las classs / funciones personalizadas sean visibles para el bloque de buildScript )?
  • ¿Existe un mejor enfoque para configurar un repository corporativo de Nexus y hacer que los complementos personalizados (publicados en Nexus) estén disponibles en muchos proyectos separados (es decir, bases de códigos totalmente diferentes) con una configuration mínima de plantilla?

Si desea beneficiarse de todo lo bueno de DSL de Gradle Kotlin, debe esforzarse por aplicar todos los complementos mediante el bloque de plugins {} . Ver https://github.com/gradle/kotlin-dsl/blob/master/doc/getting-started/Configuring-Plugins.md

Puede administrar repositorys de plugins y estrategias de resolución (por ejemplo, su versión) en sus files de configuration. Comenzando con Gradle 4.4, este file se puede escribir usando Kotlin DSL, también conocido como settings.gradle.kts . Ver https://docs.gradle.org/4.4-rc-1/release-notes.html .

Con esto en mente, podría tener un complemento de script de Settings centralizado que configura y aplica en sus files settings.gradle.kts compilation:

 // corporate-settings.gradle.kts pluginManagement { repositories { maven { name = "Corporate Nexus" url = uri("https://example.com/repository/maven-public") } gradlePluginPortal() } } 

y:

 // settings.gradle.kts apply { from("https://url.to/corporate-settings.gradle.kts") } 

Luego, en los scripts de compilation de su proyecto, simplemente puede solicitar complementos desde su repository corporativo:

 // build.gradle.kts plugins { id("my-corporate-plugin") version "1.2.3" } 

Si desea que los scripts de compilation de su proyecto en una compilation multiproyectos no repitan la versión del complemento, puede hacerlo con Gradle 4.3 declarando versiones en su proyecto raíz. Tenga en count que también puede configurar las versiones en settings.gradle.kts usando pluginManagement.resolutionStrategy si tiene todas las versiones con la misma versión de plugins es lo que necesita.

También tenga en count que para que todo esto funcione, sus complementos deben publicarse con su artefacto de marcador de complemento . Esto se hace fácilmente mediante el uso del java-gradle-plugin plugin.

He estado haciendo algo como esto en mi build

 buildscript { project.apply { from("${rootProject.projectDir}/shanetworkingValues.gradle.kts") } val configureRepository: (Any) -> Unit by extra configureRepository.invoke(repositories) } 

En mi file shanetworkingValues.gradle.kts tengo un código como este:

 /** * This method configures the repository handler to add all of the maven repos that your company relies upon. * When trying to pull this method out of the [ExtraPropertiesExtension] use the following code: * * For Kotlin: * ```kotlin * val configureRepository : (Any) -> Unit by extra * configureRepository.invoke(repositories) * ``` * Any other casting will cause a compiler error. * * For Groovy: * ```groovy * def configureRepository = project.configureRepository * configureRepository.invoke(repositories) * ``` * * @param repoHandler The RepositoryHandler to be configunetworking with the company repositories. */ fun repositoryConfigurer(repoHandler : RepositoryHandler) { repoHandler.apply { // Do stuff here } } var configureRepository : (RepositoryHandler) -> Unit by extra configureRepository = this::repositoryConfigurer 

Sigo un patrón similar para configurar la estrategia de resolución para complementos.

Lo bueno de este patrón es que cualquier cosa que configure en shanetworkingValues.gradle.kts también se puede usar desde su proyecto buildSrc , lo que significa que puede reutilizar las declaraciones del repository.


Actualizado:

Puede aplicar otro script desde una URL, por ejemplo haciendo esto:

 apply { // This was actually a plugin that I used at one point. from("http://dl.bintray.com/shemnon/javafx-gradle/8.1.1/javafx.plugin") } 

Simplemente aloje su secuencia de commands que desea que todas sus comstackciones compartan en algún server http (lo recomiendo encarecidamente usar HTTPS para que su construcción no pueda ser el objective de un hombre en el ataque medio).

La desventaja de esto es que no creo que las secuencias de commands aplicadas desde las URL no estén almacenadas en la memory caching, por lo que se volverán a download cada vez que ejecute la compilation. Esto puede haber sido arreglado por ahora, no estoy seguro.

Una solución que me ofreció Stefan Oehme cuando tuve un problema similar fue vender mi propia distribución personalizada de Gradle. Según él, esto es algo común en las grandes empresas.

Simplemente cree una horquilla personalizada del repository de gradle, agregue la salsa especial de su empresa a cada proyecto utilizando esta versión personalizada de gradle.

  • DexFile no contiene classs generadas después de agregar la configuration de Kotlin en Android 4.4
  • Kotlin, JPA y @Transient
  • RxJava2 Publicado
  • ¿Qué significa .indices en kotlin?
  • Declaración de interfaz Java vs Kotlin
  • Android: no se puede agregar el jar creado en java8 con kotlin
  • ¿Cómo get una instancia delegada en Kotlin?
  • Kotlin Char compareTo falla
  • Android / Kotlin Crash en Start 4.1.2 - ExceptionInInitializerError
  • KTor o Spark? ¿Cuál es la producción list para los web services de Kotlin?
  • PubNub suscribe Android Kotlin