J'essaie actuellement d'améliorer la façon dont nos projets partagent leur configuration. Nous avons de nombreux projets de modules multi-modules pour toutes nos bibliothèques et nos microservices (c’est-à-dire de nombreux dépôts git).
Mes objectifs principaux sont:
Ma solution actuelle est une distribution graduée personnalisée avec un script init qui:
mavenLocal()
et notre référentiel Nexus aux mises en pension du projet (très similaire à l'exemple de documentation de script Gradle init , sauf qu'il ajoute des mises en pension et les valide)build.gradle
sont à peu près juste pour les dépendances.Voici le script init (désinfecté):
/**
* 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 les éléments suivants au fichier racine 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 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, elle permet des constructions reproductibles (contrairement à notre configuration précédente qui appliquait un script de construction à partir d’une URL (qui n’était pas caché à l’époque)) et qui permettait de travailler en mode hors connexion, cela rend cela un peu magique et je me demandais si pourrait mieux faire les choses.
Tout cela a été déclenché par la lecture un commentaire sur Github par Gradle dev Stefan Oehme déclarant qu'une construction devrait fonctionner sans recourir à un script d'initialisation, c.-à-d. Que les scripts d'initialisation doivent simplement être décoratifs et faire des choses comme l'exemple documenté - empêchant les dépôts non autorisés. , etc.
Mon idée était d’écrire des fonctions d’extension qui me permettraient d’ajouter notre repo Nexus et nos plugins à une construction de la même manière qu’ils étaient construits dans gradle (similaire aux fonctions d’extension gradleScriptKotlin()
et kotlin-dsl()
fourni par le Gradle Kotlin DSL.
J'ai donc créé mes fonctions d'extension dans un projet 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"
}
Avec l'intention de les utiliser dans le build.gradle.kts
de mon projet comme suit:
import com.example.corporateNexus
import com.example.corporatePlugins
buildscript {
repositories {
corporateNexus()
}
dependencies {
classpath(corporatePlugins(version = "1.2.3"))
}
}
Cependant, Gradle était incapable de voir mes fonctions lorsqu'il était utilisé dans le bloc buildscript
(impossible de compiler le script). Leur utilisation dans les dépôts/dépendances de projet normaux a bien fonctionné (ils sont visibles et fonctionnent comme prévu).
Si cela fonctionnait, j'espérais intégrer le fichier jar dans ma distribution personnalisée, ce qui signifie que mon script init pourrait simplement effectuer une validation simple au lieu de masquer le plug-in magique et la configuration du référentiel. Les fonctions d’extension n’auraient pas besoin de changer, il n’aurait donc pas besoin de publier une nouvelle distribution Gradle lors du changement de plug-in.
Ce que j'ai essayé:
buildscript.dependencies
) - ne fonctionne pas (peut-être que cela ne fonctionne pas de par sa conception, car il ne semble pas correct d'ajouter une dépendance à buildscript
à laquelle il est fait référence dans le même bloc)buildSrc
(qui fonctionne pour les dépôts/repos de projet normaux mais pas buildscript
, mais ce n'est pas une vraie solution car cela ne fait que déplacer le passe-partout)lib
de la distributionDonc, ma question se résume vraiment à:
buildScript
)? J'ai promis à @eskatos de revenir et de commenter sa réponse. Alors la voici!
Ma solution finale consiste à:
settings.gradle.kts
par projet qui configure notre miroir de portail de plug-in Maven Repo et Gradle (tous deux dans Nexus) en tant que référentiels de gestion de plug-ins.Le fichier settings.gradle.kts
contient les éléments suivants:
pluginManagement {
repositories {
// local maven to facilitate easy testing of our plugins
mavenLocal()
// our plugins and their markers are now available via Nexus
maven {
name = "CorporateNexus"
url = uri("https://nexus.example.com/repository/maven-public")
}
// all external gradle plugins are now mirrored via Nexus
maven {
name = "Gradle Plugin Portal"
url = uri("https://nexus.example.com/repository/gradle-plugin-portal")
}
}
}
Cela signifie que tous les plugins et leurs dépendances sont désormais proxy via Nexus, et Gradle trouvera nos plugins par identifiant, car les marqueurs de plugin sont également publiés sur Nexus. La présence de mavenLocal
facilite également le test local des modifications de plug-ins.
Le fichier racine build.gradle.kts
de chaque projet applique ensuite les plugins comme suit:
plugins {
// plugin markers for our custom plugins allow us to apply our
// plugins by id as if they were hosted in gradle plugin portal
val corporatePluginsVersion = "1.2.3"
id("corporate-project") version corporatePluginsVersion
// 'apply false` means this plugin can be applied in a subproject
// without having to specify the version again
id("corporate-publishing") version corporatePluginsVersion apply false
// and so on...
}
Et configure l'encapsuleur Gradle pour qu'il utilise notre distribution en miroir, ce qui, lorsqu'il est combiné à ce qui précède, signifie que tout (gradle, plugins, dépendances) vient tous via Nexus):
tasks {
"wrapper"(Wrapper::class) {
distributionUrl = "https://nexus.example.com/repository/gradle-distributions/gradle-4.7-bin.Zip"
}
}
J'espérais éviter le passe-partout dans les fichiers de paramètres en utilisant la suggestion de @ eskatos d'appliquer un script à partir d'une URL distante dans settings.gradle.kts
. c'est à dire.
apply { from("https://nexus.example.com/repository/maven-public/com/example/gradle/corporate-settings/1.2.3/corporate-settings-1.2.3.kts" }
J'ai même réussi à générer un script basé sur un modèle (publié à côté de nos plugins) qui:
Cependant, même si cela supprimait le standard, cela signifiait que nos versions reposaient sur une connexion à notre référentiel Nexus, car il semble que même si les scripts appliqués à partir d'une URL sont mis en cache, Gradle envoie quand même une requête HEAD pour vérifier pour les changements. Cela rendait également agaçant de tester localement les modifications de plug-in, car je devais le pointer manuellement vers le script de mon répertoire maven local. Avec ma configuration actuelle, je peux simplement publier les plugins sur maven local et mettre à jour la version de mon projet.
Je suis assez satisfait de la configuration actuelle - je pense que la façon dont les plugins sont appliqués est bien plus évidente pour les développeurs. Et il est beaucoup plus facile de mettre à niveau indépendamment Gradle et nos plugins, maintenant qu’il n’ya aucune dépendance entre les deux (et qu’aucune distribution de gradation personnalisée n’est requise).
Si vous souhaitez bénéficier de tous les avantages de Gradle Kotlin DSL, vous devez vous efforcer d’appliquer tous les plugins à l’aide du bloc plugins {}
. Voir https://github.com/gradle/kotlin-dsl/blob/master/doc/getting-started/Configuring-Plugins.md
Vous pouvez gérer les référentiels de plugins et les stratégies de résolution (par exemple, leur version) dans vos fichiers de paramètres. À partir de Gradle 4.4, ce fichier peut être écrit en utilisant le DSL de Kotlin, également appelé settings.gradle.kts
. Voir https://docs.gradle.org/4.4-rc-1/release-notes.html .
En gardant cela à l’esprit, vous pourriez alors avoir un plugin de script Settings
centralisé qui configure tout et l’applique dans vos fichiers settings.gradle.kts
de builds:
// corporate-settings.gradle.kts
pluginManagement {
repositories {
maven {
name = "Corporate Nexus"
url = uri("https://example.com/repository/maven-public")
}
gradlePluginPortal()
}
}
et:
// settings.gradle.kts
apply(from = "https://url.to/corporate-settings.gradle.kts")
Ensuite, dans vos scripts de construction de projet, vous pouvez simplement demander des plugins à partir de votre référentiel d'entreprise:
// build.gradle.kts
plugins {
id("my-corporate-plugin") version "1.2.3"
}
Si vous voulez que vos scripts de construction de projet dans une construction multi-projet ne répète pas la version du plugin, vous pouvez le faire avec Gradle 4.3 en déclarant des versions dans votre projet racine. Notez que vous pouvez également définir les versions dans settings.gradle.kts
à l'aide de pluginManagement.resolutionStrategy
si toutes les versions utilisent la même version de plug-in, c'est ce dont vous avez besoin.
Notez également que pour que tout cela fonctionne, vos plugins doivent être publiés avec leur artefact de marqueur plugin . Ceci est facilement fait en utilisant le plugin Java-gradle-plugin
.
J'ai fait quelque chose comme ça dans ma construction
buildscript {
project.apply {
from("${rootProject.projectDir}/sharedValues.gradle.kts")
}
val configureRepository: (Any) -> Unit by extra
configureRepository.invoke(repositories)
}
Dans mon fichier sharedValues.gradle.kts
, j'ai un code comme celui-ci:
/**
* 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 configured with the company repositories.
*/
fun repositoryConfigurer(repoHandler : RepositoryHandler) {
repoHandler.apply {
// Do stuff here
}
}
var configureRepository : (RepositoryHandler) -> Unit by extra
configureRepository = this::repositoryConfigurer
Je suis un modèle similaire pour configurer la stratégie de résolution pour les plugins.
Le bon côté de ce modèle est que tout ce que vous configurez dans sharedValues.gradle.kts
peut également être utilisé à partir de votre projet buildSrc
, ce qui signifie que vous pouvez réutiliser les déclarations du référentiel.
Mis à jour:
Vous pouvez appliquer un autre script à partir d'une URL, par exemple:
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")
}
Hébergez simplement votre script que vous souhaitez que toutes vos versions partagent sur un serveur http (il est fortement recommandé d'utiliser HTTPS afin que votre version ne puisse pas être ciblée par une attaque entre deux personnes).
L'inconvénient, c'est que je ne pense pas que les scripts appliqués à partir d'URL ne soient pas mis en cache, ils seront donc re-téléchargés chaque fois que vous exécuterez votre build. .
Une solution proposée par Stefan Oehme alors que je rencontrais un problème similaire consistait à vendre ma propre distribution personnalisée de Gradle. Selon lui, c'est une chose courante à faire dans les grandes entreprises.
Créez simplement une fourchette personnalisée du dépôt gradle, ajoutez la sauce spéciale de votre entreprise à chaque projet en utilisant cette version personnalisée de gradle.
J'ai rencontré un problème similaire lorsque la configuration commune est répliquée dans chaque projet. Résolu par une distribution de dégradé personnalisée avec les paramètres communs définis dans le script init.
Création d'un plugin Gradle pour la préparation de telles distributions personnalisées - custom-gradle-dist . Cela fonctionne parfaitement pour mes projets, par exemple. un build.gradle pour un projet de bibliothèque ressemble à ceci (c'est un fichier complet):
dependencies {
compile 'org.springframework.kafka:spring-kafka'
}