Das Gradle-Plug-in application erzeugt eine praktikable Distribution für eine Java-Applikation, was aber wenn mehr Flexibilität gebraucht wird? Zum Beispiel ein über-JAR oder ein JAR das sich durch Doppelklick startet lässt.
Das Plug-in application basiert auf dem Plug-in distribution. Es definiert dass alle JARs ins Unterverzeichnis lib kopiert werden und das Startskripte für Windows und Linux/Unix im Unterverzeichnis bin angelegt werden. Wenn man möchte kann man noch weitere Dateien ergänzen:
plugins {
id 'java'
id 'application'
}
application.mainClassName = 'de.muspellheim.example.App'
distributions {
main {
contents {
from "doc"
}
}
}
Hier werden alle Dateien im Projektverzeichnis doc mit in die Distribution aufgenommen.
Ein uber-JAR erzeugen
Ein uber-JAR ist ein einzelnes JAR, welches die Applikation einschließlich aller ihrer Abhängigkeiten zusammenfasst. Es wird der Inhalt aller JARs schlicht zu einem JAR zusammenkopiert.
Ein uber-JAR erleichtert die Verteilung einer Java-Applikation, da nur noch ein JAR weitergegeben werden muss. Um die Abhängigkeiten muss man sich dabei nicht mehr kümmern, da sie ja enthalten sind.
Mit Gradle kann man das wie folgt machen:
jar {
manifest {
attributes 'Main-Class': mainClassName
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
Hinweis: Ein uber-JAR darf nicht verwendet werden, wenn Dateien in den JARs mehrfach vorkommen. Zum Beispiel wenn man Service Provider Interfaces (SPI) verwendet, liegen im Verzeichnis META-INF/services der JARs gleichnamige Dateien, wenn mehrere JARs eine Implementierung für das gleiche Interface bereitstellen. Diese Dateien würden beim Erstellen des uber-JARs überschrieben, so das zur Laufzeit Implementierungen fehlen.
Wenn man jetzt die Distribution mit dem application-Plug-in baut, hat man ein großes uber-JAR, aber immer noch alle Abhängigkeiten zusätzlichen im lib-Verzeichnis. Um das zu vermeiden, konfigurieren wir das distribution-Plug-in selber.
Als erstes wird das application-Plugin ausgetauscht:
plugins {
id 'java'
id 'distribution'
}
Dann die main-Distribution mit dem erwarteten Inhalt konfigurieren:
distributions {
main {
contents {
from jar
from "$projectDir/src/dist"
}
}
}
An dieser Stelle können wie mit dem application-Plug-in gewohnt, weiter Dateien zum Inhalt der Distribution hinzugefügt werden.
Da wir das application-Plug-in nicht mehr verwenden, gibt es auch keine Definition für die Main-Klasse mehr. Die kann aber einfach als lokale Variable angelegt werden. Dazu muss
application.mainClassName = 'de.muspellheim.example.App'
durch
def mainClassName = 'de.muspellheim.example.App'
ersetzt werden.
Zum Schluss fehlt noch der run-Task zum Starten der Applikation:
task run(type: JavaExec) {
group 'application'
classpath = sourceSets.test.runtimeClasspath
main = mainClassName
}
Nun kann ein Distributionspaket mit dem Gradle-Task distZip erstellt werden.
Ein komplettes Beispiel
Eine vollständiges Beispiel der build.gradle kann so aussehen:
import org.apache.tools.ant.filters.FixCrLfFilter
plugins {
id 'java'
id 'distribution'
}
version = '1.0.0'
def mainClassName = 'de.muspellheim.example.App'
jar {
manifest {
attributes 'Main-Class': mainClassName
attributes 'Implementation-Version': project.version
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
distributions {
main {
contents {
from jar
from(projectDir) {
include 'README.md'
include 'CHANGELOG.md'
rename 'md', 'txt'
filter(FixCrLfFilter, eol: FixCrLfFilter.CrLf.newInstance('crlf'))
}
from "$projectDir/src/dist"
}
}
}
distZip {
version = ''
doLast {
archiveFile.get().asFile.renameTo(destinationDirectory.file("${project.name}-v${project.version}.zip").get().asFile)
}
}
assemble.dependsOn = [distZip]
task run(type: JavaExec) {
group 'application'
classpath = sourceSets.test.runtimeClasspath
main = mainClassName
}
Bei dieser Variante gibt es noch ein paar Erweiterungen:
- Die Version der Applikation wird in das Manifest des JARs geschrieben, so dass diese später zu Beispiel in einem About-Dialog ausgelesen werden kann.
- Die README und CHANGELOG werden mit eingepackt und dabei die Dateiendung zu
txtgeändert und die Zeilenenden für Windowsnutzer angepasst. - Für die ZIP-Datei der Distribution wird die Version entfernt, damit sie im Verzeichnisname in der ZIP-Datei nicht mehr enthalten ist. Nach dem Erzeugen der ZIP-Datei wird diese umbenannt, damit die Version im Namen der ZIP-Datei dagegen noch enthalten ist.
- Der Task
distZipwird als einzige Abhängigkeit vom Taskassemblegesetzt, so dass nur noch ein ZIP und kein TAR mehr erzeugt wird.
