Ich versuche derzeit, die Art und Weise zu verbessern, wie unsere Projekte ihre Konfiguration freigeben. Wir haben viele verschiedene Multi-Modul-Gradle-Projekte für alle unsere Bibliotheken und Microservices (d. H. Viele Git-Repos).
Meine Hauptziele sind:
Meine derzeitige Lösung ist eine benutzerdefinierte Gradel-Distribution mit einem Init-Skript, das:
mavenLocal()
und unser Nexus-Repository zu den Projekt-Repos hinzu (sehr ähnlich dem Skript-Dokumentationsbeispiel von Gradle init , außer dass Repos hinzugefügt und validiert werden).build.gradle
sind so ziemlich nur für Abhängigkeiten.Hier ist das Init-Skript (bereinigt):
/**
* 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
}
}
}
}
}
Dann konfiguriere ich jedes neue Projekt, indem ich der root-Datei build.gradle Folgendes hinzufügen:
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'
}
}
Dieser Ansatz funktioniert zwar und ermöglicht reproduzierbare Builds (im Gegensatz zu unserem vorherigen Setup, bei dem ein Buildskript von einer URL angewendet wurde - was zu dieser Zeit nicht im Cache abgelegt werden konnte), und das Offline-Arbeiten ermöglicht wird. Es macht es jedoch etwas magisch, und ich frage mich, ob ich könnte die Dinge besser machen.
Dies wurde alles durch das Lesen von einem Kommentar zu Github von Gradle-Entwickler Stefan Oehme ausgelöst, der besagt, dass ein Build funktionieren sollte, ohne auf ein Init-Skript angewiesen zu sein. Dh Init-Skripts sollten nur dekorativ sein und Dinge wie das dokumentierte Beispiel tun, um nicht autorisierte Repos zu verhindern , usw.
Meine Idee war, einige Erweiterungsfunktionen zu schreiben, die es mir ermöglichen, unser Nexus-Repo und unsere Plugins zu einem Build hinzuzufügen, das aussah, als wären sie in Gradle eingebaut (ähnlich wie die Erweiterungsfunktionen gradleScriptKotlin()
und kotlin-dsl()
bereitgestellt von der Gradle Kotlin DSL.
Also habe ich meine Erweiterungsfunktionen in einem Kotlin Gradle-Projekt erstellt:
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"
}
Mit dem Plan, sie in build.gradle.kts
meines Projekts wie folgt zu verwenden:
import com.example.corporateNexus
import com.example.corporatePlugins
buildscript {
repositories {
corporateNexus()
}
dependencies {
classpath(corporatePlugins(version = "1.2.3"))
}
}
Gradle konnte meine Funktionen jedoch nicht sehen, wenn sie im buildscript
-Block verwendet wurden (Skript konnte nicht kompiliert werden). Ihre Verwendung in den normalen Projekt-Repos/Abhängigkeiten funktionierte jedoch gut (sie sind sichtbar und funktionieren wie erwartet).
Wenn dies funktionierte, hoffte ich, die Dose in meine benutzerdefinierte Distribution zu bündeln. Das heißt, mein Init-Skript könnte einfach eine einfache Überprüfung durchführen, anstatt die magische Plugin- und Repo-Konfiguration zu verstecken. Die Erweiterungsfunktionen müssen nicht geändert werden, sodass keine neue Gradle-Distribution freigegeben werden muss, wenn Plugins geändert werden.
Was ich probiert habe:
buildscript.dependencies
) - funktioniert nicht (möglicherweise funktioniert das nicht konstruktiv, da es nicht richtig erscheint, buildscript
eine Abhängigkeit hinzuzufügen, auf die im selben Block verwiesen wird).buildSrc
setzen (was für normale Projekt-Deps/Repos aber nicht für buildscript
geeignet ist, aber keine echte Lösung ist, da nur die Speicherplatte verschoben wird)lib
-Ordner der Distribution ablegenMeine Frage lautet also:
buildScript
-Block sichtbar gemacht werden)? Ich habe @eskatos versprochen, dass ich wiederkommen würde und eine Antwort auf seine Antwort geben würde - hier also!
Meine endgültige Lösung besteht aus:
settings.gradle.kts
-Datei pro Projekt, die unseren Maven Repo- und Gradle-Plugin-Portalspiegel (beide in Nexus) als Plugin-Verwaltungs-Repositorys konfiguriert.Die settings.gradle.kts
-Datei enthält Folgendes:
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")
}
}
}
Dies bedeutet, dass alle Plugins und ihre Abhängigkeiten nun über Nexus weitergeleitet werden, und Gradle wird unsere Plugins anhand der ID finden, da die Plugin-Marker auch bei Nexus veröffentlicht werden. Da mavenLocal
vorhanden ist, können Änderungen an Plug-Ins einfach vor Ort getestet werden.
Die build.gradle.kts
-Datei jedes Projekts wendet die Plugins dann wie folgt an:
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...
}
Und konfiguriert den Gradle-Wrapper für die Verwendung unserer gespiegelten Distribution, was in Kombination mit den obigen Angaben bedeutet, dass alles (Gradle, Plugins, Abhängigkeiten) über Nexus kommt:
tasks {
"wrapper"(Wrapper::class) {
distributionUrl = "https://nexus.example.com/repository/gradle-distributions/gradle-4.7-bin.Zip"
}
}
Ich hatte gehofft, die Boilerplate in den Einstellungsdateien zu vermeiden, indem ich den Vorschlag von @ eskatos anwendete, ein Skript von einer entfernten URL in settings.gradle.kts
anzuwenden. d.h.
apply { from("https://nexus.example.com/repository/maven-public/com/example/gradle/corporate-settings/1.2.3/corporate-settings-1.2.3.kts" }
Es ist mir sogar gelungen, ein Templated Script (neben unseren Plugins veröffentlicht) zu generieren, das:
Obwohl die Boilerplate entfernt wurde, bedeutete dies, dass unsere Builds auf eine Verbindung zu unserem Nexus-Repo angewiesen waren, da es scheint, dass obwohl Skripts, die über eine URL angewendet werden, zwischengespeichert werden, Gradle trotzdem eine HEAD -Anfrage zur Überprüfung vornimmt für Änderungen. Ärgerlich war es auch, Plugin-Änderungen lokal zu testen, da ich es manuell auf das Skript in meinem lokalen Verzeichnis verweisen musste. Mit meiner aktuellen Konfiguration kann ich die Plugins einfach in Maven local veröffentlichen und die Version in meinem Projekt aktualisieren.
Ich bin mit dem aktuellen Setup ziemlich zufrieden - ich denke, es ist für Entwickler jetzt viel offensichtlicher, wie die Plugins angewendet werden. Es ist weitaus einfacher, Gradle und unsere Plugins unabhängig voneinander zu aktualisieren, da zwischen den beiden keine Abhängigkeit besteht (und keine benutzerdefinierte Gradle-Verteilung erforderlich ist).
Wenn Sie die volle Güte von Gradle Kotlin DSL nutzen möchten, sollten Sie alle Plugins mit dem plugins {}
-Block anwenden. Siehe https://github.com/gradle/kotlin-dsl/blob/master/doc/getting-started/Configuring-Plugins.md
Sie können Plugin-Repositorys und Auflösungsstrategien (z. B. deren Version) in Ihren Einstellungsdateien verwalten. Ab Gradle 4.4 kann diese Datei mit dem Kotlin DSL, auch settings.gradle.kts
, geschrieben werden. Siehe https://docs.gradle.org/4.4-rc-1/release-notes.html .
In diesem Sinne könnten Sie dann ein zentralisiertes Settings
-Skript-Plugin haben, das die Einstellungen vornimmt und in Ihren Builds settings.gradle.kts
-Dateien anwendet:
// corporate-settings.gradle.kts
pluginManagement {
repositories {
maven {
name = "Corporate Nexus"
url = uri("https://example.com/repository/maven-public")
}
gradlePluginPortal()
}
}
und:
// settings.gradle.kts
apply(from = "https://url.to/corporate-settings.gradle.kts")
In Ihrem Projekterstellungsskript können Sie einfach Plugins von Ihrem Unternehmens-Repository anfordern:
// build.gradle.kts
plugins {
id("my-corporate-plugin") version "1.2.3"
}
Wenn Sie möchten, dass Ihr Projekt Skripts in einem Multiprojekt-Build erstellt, um die Plugin-Version nicht zu wiederholen, können Sie dies mit Gradle 4.3 tun, indem Sie Versionen in Ihrem Stammprojekt deklarieren. Beachten Sie, dass Sie auch die Versionen in settings.gradle.kts
mit pluginManagement.resolutionStrategy
festlegen können, wenn alle Builds dieselbe Plugins-Version verwenden, die Sie benötigen.
Beachten Sie außerdem, dass Ihre Plugins mit ihrem Plugin-Marker-Artefakt veröffentlicht werden müssen, damit dies funktioniert. Dies kann leicht mit dem Java-gradle-plugin
-Plugin durchgeführt werden.
Ich habe in meinem Build so etwas gemacht
buildscript {
project.apply {
from("${rootProject.projectDir}/sharedValues.gradle.kts")
}
val configureRepository: (Any) -> Unit by extra
configureRepository.invoke(repositories)
}
In meiner sharedValues.gradle.kts
-Datei habe ich folgenden Code:
/**
* 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
Ich folge einem ähnlichen Muster für die Konfiguration der Auflösungsstrategie für Plugins.
Das Schöne an diesem Muster ist, dass alles, was Sie in sharedValues.gradle.kts
konfigurieren, auch aus Ihrem buildSrc
-Projekt verwendet werden kann, was bedeutet, dass Sie Repository-Deklarationen wiederverwenden können.
Aktualisierte:
Sie können ein anderes Skript von einer URL anwenden, indem Sie beispielsweise Folgendes ausführen:
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")
}
Hosten Sie einfach Ihr Skript, das alle Ihre Builds auf einem HTTP-Server freigeben sollen (HTTPS wird dringend empfohlen, sodass Ihr Build nicht von einem Mann im mittleren Angriff angegriffen werden kann).
Der Nachteil dabei ist, dass ich glaube nicht, dass aus URLs angewendete Skripts nicht zwischengespeichert werden, so dass sie jedes Mal erneut heruntergeladen werden, wenn Sie Ihren Build ausführen. Dies könnte inzwischen behoben worden sein, da bin ich mir nicht sicher .
Eine Lösung, die mir Stefan Oehme anbot, als ich ein ähnliches Problem hatte, bestand darin, meine eigene Gradle-Distribution zu vertreiben. Seiner Meinung nach ist dies in großen Unternehmen üblich.
Erstellen Sie einfach eine benutzerdefinierte Gabel des Gradle-Repos, fügen Sie Ihrem Unternehmen mit dieser benutzerdefinierten Version von Gradle die spezielle Sauce Ihres Unternehmens hinzu.
Ich habe ein ähnliches Problem festgestellt, wenn die allgemeine Konfiguration in jedem Projekt repliziert wird. Gelöst durch eine benutzerdefinierte Abstufungsverteilung mit den allgemeinen Einstellungen, die in init-Skript definiert sind.
Erstellt ein Gradle-Plugin für die Erstellung solcher benutzerdefinierten Distributionen - custom-gradle-dist . Es funktioniert perfekt für meine Projekte, z. Ein build.gradle für ein Bibliotheksprojekt sieht folgendermaßen aus (dies ist eine vollständige Datei):
dependencies {
compile 'org.springframework.kafka:spring-kafka'
}