J'essaie actuellement d'améliorer la façon dont nos projets partagent leur configuration. Nous avons beaucoup de projets gradle multi-modules différents pour toutes nos bibliothèques et microservices (c'est-à-dire de nombreux dépôts git).
Mes principaux objectifs sont :
- Pour ne pas avoir la configuration de mon dépôt Nexus dupliquée dans chaque projet (je peux également supposer que l'URL ne changera pas).
- Mettre mes plugins Gradle personnalisés (publiés sur Nexus) à la disposition de tous les projets avec un minimum d'erreurs et de duplications (ils devraient être disponibles pour tous les projets, et la seule chose dont le projet se soucie est la version qu'il utilise).
- Pas de magie - la façon dont tout est configuré doit être évidente pour les développeurs.
Ma solution actuelle est une distribution gradle personnalisée avec un init script qui :
- ajoute
mavenLocal()
et notre dépôt Nexus aux dépôts du projet (très similaire à la méthode Gradle init script exemple de documentation (sauf qu'il ajoute les dépôts et les valide). - configure une extension qui permet à nos plugins gradle d'être ajoutés au classpath de buildscript (à l'aide de cette solution de contournement ). Il ajoute également notre repo Nexus comme repo buildscript car c'est là que les plugins sont hébergés. Nous avons un certain nombre de plugins (construits à partir de l'excellent outil de Netflix, le plugins nebula ) pour diverses tâches : configuration standard du projet (configuration de Kotlin, configuration des tests, etc.), libération, publication, documentation, etc.
build.gradle
sont essentiellement destinés aux dépendances.
Voici l'init script (sanitisé) :
/**
* 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 configured 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
}
}
}
}
}
Ensuite, je configure chaque nouveau projet en ajoutant ce qui suit au fichier build.gradle de la racine :
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 required)
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'
}
}
Bien que cette approche fonctionne, permette des constructions reproductibles (contrairement à notre configuration précédente qui appliquait une construction script à partir d'une URL - qui, à l'époque, n'était pas cachable), et permette de travailler hors ligne, elle rend les choses un peu magiques et je me demandais si je pouvais faire mieux.
Tout cela a été déclenché par la lecture un commentaire sur Github par Stefan Oehme, développeur Gradle, indiquant qu'une construction devrait fonctionner sans dépendre d'un init script, c'est-à-dire que les init script devraient juste être décoratifs et faire des choses comme l'exemple documenté - empêcher les dépôts non autorisés, etc.
Mon idée était d'écrire quelques fonctions d'extension qui me permettraient d'ajouter notre repo Nexus et nos plugins à une construction d'une manière qui ressemblerait à ce qu'ils ont été intégrés dans gradle (similaire aux fonctions d'extension gradleScriptKotlin()
y kotlin-dsl()
fourni par le DSL Kotlin de Gradle.
J'ai donc créé mes fonctions d'extension dans un projet gradle kotlin :
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"
}
avec l'intention de les utiliser dans mon projet. build.gradle.kts
comme suit :
import com.example.corporateNexus
import com.example.corporatePlugins
buildscript {
repositories {
corporateNexus()
}
dependencies {
classpath(corporatePlugins(version = "1.2.3"))
}
}
Cependant, Gradle n'a pas pu voir mes fonctions lorsqu'elles ont été utilisées dans l'application buildscript
bloc (impossible de compiler script). Les utiliser dans les dépôts/dépendances normaux du projet a pourtant bien fonctionné (ils sont visibles et fonctionnent comme prévu).
Si cela fonctionne, j'espérais regrouper le jar dans ma distribution personnalisée, ce qui signifie que mon init script pourrait simplement faire une validation simple au lieu de cacher le plugin magique et la configuration du repo. Les fonctions d'extension n'auraient pas besoin de changer, donc il ne serait pas nécessaire de publier une nouvelle distribution Gradle lorsque les plugins changent.
Ce que j'ai essayé :
- ajouter mon jar au classpath du buildscript du projet de test (i.e.
buildscript.dependencies
) - ne fonctionne pas (peut-être que cela ne fonctionne pas à dessein car il ne semble pas correct d'ajouter une dépendance àbuildscript
à laquelle il est fait référence dans le même bloc) - en mettant les fonctions dans
buildSrc
(ce qui fonctionne pour les dépôts/récupérations normaux du projet mais pas pour lesbuildscript
mais ce n'est pas une vraie solution car elle ne fait que déplacer le boilerplate). - en laissant tomber le bocal dans le
lib
du dossier de la distribution
Donc ma question se résume vraiment à :
- Est-ce que ce que j'essaie de faire est possible (est-il possible de rendre les classes/fonctions personnalisées visibles pour l'utilisateur ?
buildScript
) ? - Existe-t-il une meilleure approche pour configurer un repo Nexus d'entreprise et rendre les plugins personnalisés (publiés dans Nexus) disponibles dans de nombreux projets distincts (c'est-à-dire des bases de code totalement différentes) avec un minimum de configuration passe-partout ?
0 votes
Je pense que pour résumer ce que vous essayez de faire, vous voulez ajouter des extensions à l'application
buildscript
Blocage ? Avez-vous une limite inférieure de la version de Gradle que vous utilisez ?0 votes
@mkobit non, je suis en train de passer à la version 4.1. Je suppose que j'essaie vraiment d'améliorer la façon dont nos constructions configurent nexus, et de rendre nos plugins disponibles pour le projet. La solution actuelle (comme documenté au début de cette question) fonctionne, mais la configuration des plugins en particulier se sent comme un hack !
0 votes
Merci de clarifier. Il y a peut-être quelques moyens qui peuvent fonctionner ou non ou améliorer ce que vous avez déjà fait. L'une d'elles serait d'écrire un init script plugin que vous pouvez appliquer dans le
settings.gradle
. Dans l'actuel4.2-rc
il existe également un soutien pour Les plugins script sont mis en cache et ne sont téléchargés que lorsque cela est nécessaire, au lieu de l'être à chaque build. qui pourrait améliorer certains problèmes que vous rencontrez. Une autre idée pourrait être de fournir un portail de plugin personnalisé comme ( github.com/linkedin/custom-gradle-plugin-portal ).0 votes
En ce qui concerne le commentaire sur "La construction devrait fonctionner sans dépendre d'un init script" que l'on peut trouver à l'adresse suivante github.com/gradle/gradle/issues/745#issuecomment-278302497 .
0 votes
Génial, merci @mkobit ! Je savais que c'était quelque part :)
1 votes
J'ai discuté avec Rodrigo (alias Bamboo) à la KotlinConf à ce sujet. Il m'a suggéré de publier un plugin bootstrap sur le portail de plugins gradle. Nous avons également discuté de la façon dont les plugins script distants pourraient ne plus être si mauvais maintenant qu'ils sont mis en cache. Ce serait génial si quelqu'un proposait une approche vraiment propre et convaincante - je suis encore un peu indécis !
1 votes
Salut @mkobit Je viens d'ajouter une réponse avec la solution que j'ai fini par adopter. J'ai essayé d'appliquer la configuration de gestion des plugins à partir d'un script distant, mais même s'il est mis en cache, il a toujours essayé de faire une requête HEAD contre le script qui a échoué lorsque je n'avais pas de connexion à notre repo Nexus (où le script était hébergé). J'ai donc fini par avoir un peu de texte passe-partout dans chaque fichier
settings.gradle.kts
mais ce n'est pas si mal !