Erstellen einer lauffähigen JAR mit Gradle

147

Bisher habe ich über die Eclipse-Funktion "Exportieren ..." ausführbare JAR-Dateien erstellt. Jetzt bin ich zur Build-Automatisierung zu IntelliJ IDEA und Gradle gewechselt.

Einige Artikel hier schlagen das Plugin "Anwendung" vor, aber dies führt nicht vollständig zu dem erwarteten Ergebnis (nur eine JAR, keine Startskripte oder ähnliches).

Wie kann ich mit dem Dialogfeld "Exportieren ..." das gleiche Ergebnis erzielen, das Eclipse erzielt?

Hannes
quelle

Antworten:

164

Eine ausführbare JAR-Datei ist nur eine JAR-Datei, deren Manifest einen Eintrag der Hauptklasse enthält. Sie müssen also nur die JAR-Aufgabe konfigurieren , um diesen Eintrag in sein Manifest aufzunehmen:

jar {
    manifest {
        attributes 'Main-Class': 'com.foo.bar.MainClass'
    }
}

Möglicherweise müssen Sie dem Manifest auch Klassenpfadeinträge hinzufügen, dies erfolgt jedoch auf die gleiche Weise.

Siehe http://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html

JB Nizet
quelle
5
Dies scheint das zu sein, wonach ich gesucht habe; aber: Ich habe bereits Abhängigkeiten in der build.gradle deklariert. Muss ich den Klassenpfad wirklich manuell hinzufügen oder kann ich meine Abhängigkeitsdeklaration wiederverwenden?
Hannes
Sie sollten in der Lage sein, die Bibliotheken in der 'Laufzeit'-Konfiguration zu durchlaufen und sie zu verketten, um den Klassenpfad-Attributwert zu erstellen.
JB Nizet
3
Was meinst du mit "inseid the" runtime "configuration"? Entschuldigung für dumme Fragen, ich bin ziemlich neu in Gradle ...
Hannes
Das Gradle Java Plugin definiert 4 "Konfigurationen", die 4 verschiedenen Klassenpfaden entsprechen: compile (zum Kompilieren der Java-Dateien), testCompile (zum Kompilieren der Test-Java-Quelldateien), runtime (zum Ausführen der Anwendung) und testRuntime (wird zum Ausführen der Tests verwendet). Siehe gradle.org/docs/current/userguide/…
JB Nizet
Ah, danke - als ich es richtig verstanden habe und es geschafft habe, die JAR zu erstellen, bin ich jetzt damit beschäftigt, den Klassenpfad zu erstellen ;-) Vielen Dank für Ihre Hilfe!
Hannes
97

Die Antworten von JB Nizet und Jorge_B sind korrekt.

In seiner einfachsten Form müssen Sie zum Erstellen einer ausführbaren JAR mit Gradle nur die entsprechenden Einträge zum Manifest hinzufügen . Es kommt jedoch viel häufiger vor, dass Abhängigkeiten in den Klassenpfad aufgenommen werden müssen, was diesen Ansatz in der Praxis schwierig macht.

Das Anwendungs-Plugin bietet einen alternativen Ansatz. Anstatt eine ausführbare JAR zu erstellen, bietet es:

  • Eine runAufgabe, die das einfache Ausführen der Anwendung direkt vom Build aus erleichtert
  • Eine installDistAufgabe, die eine Verzeichnisstruktur generiert, einschließlich der erstellten JAR, aller JARs, von denen sie abhängt, und eines Startskripts, das alles zu einem Programm zusammenfasst, das Sie ausführen können
  • distZipund distTarAufgaben, die Archive erstellen, die eine vollständige Anwendungsverteilung enthalten (Startskripte und JARs)

Ein dritter Ansatz besteht darin, eine sogenannte "Fat JAR" zu erstellen, eine ausführbare JAR, die nicht nur den Code Ihrer Komponente, sondern auch alle ihre Abhängigkeiten enthält. Es gibt einige verschiedene Plugins, die diesen Ansatz verwenden. Ich habe Links zu einigen hinzugefügt, die mir bekannt sind. Ich bin sicher, es gibt noch mehr.

davidmc24
quelle
Ich habe gerade versucht, Schatten und ein Glas. Ich bleibe bei einem Glas: Es ist einfacher und einfacher zu bedienen. Cool! danke
Jako
Leider funktioniert One-Jar nicht mit neueren Versionen von Gradle. Siehe zum Beispiel github.com/rholder/gradle-one-jar/issues/34
Pharsicle
Hier ist eine Einzeiler-Lösung zum Erstellen eines Ein-Glases durch Ändern der Glas-Aufgabe. Ich habe festgestellt, dass dies am bequemsten ist. Beachten Sie, dass Sie configurations.runtimezur Bündelung von Laufzeitabhängigkeiten in der einzelnen JAR hinzufügen müssen .
Quazi Irfan
Ich fand das "Anwendungs-Plugin" für meine Bedürfnisse geeignet und flexibel genug. Es ermöglicht auch, alles zu packen, um zu komprimieren und zusätzliche Dateien in diese Komprimierung einzuschließen / auszuschließen. Es ist auch möglich, mithilfe einer benutzerdefinierten Aufgabe ein weiteres Startskript mit zusätzlichem Einstiegspunkt hinzuzufügen.
kinORnirvana
Wie würde ich die generierten .tar/ .zipDateien ausführen ?
Tobiq
35

Wie andere angemerkt haben, muss der Einstiegspunkt der Anwendung im Main-ClassAttribut der Manifestdatei festgelegt werden, damit eine JAR-Datei ausführbar ist . Wenn die Abhängigkeitsklassendateien nicht zusammengestellt werden, müssen sie im Class-PathEintrag der Manifestdatei festgelegt werden.

Ich habe alle Arten von Plugin-Kombinationen ausprobiert und was nicht für die einfache Aufgabe, ein ausführbares JAR zu erstellen und irgendwie die Abhängigkeiten einzuschließen. Alle Plugins scheinen auf die eine oder andere Weise zu fehlen, aber schließlich habe ich es so bekommen, wie ich es wollte. Keine mysteriösen Skripte, keine Millionen verschiedener Mini-Dateien, die das Build-Verzeichnis verschmutzen, eine ziemlich saubere Build-Skript-Datei und vor allem: Nicht eine Million ausländischer Klassendateien von Drittanbietern, die in meinem JAR-Archiv zusammengeführt wurden.

Das Folgende ist ein Kopieren und Einfügen von hier für Ihre Bequemlichkeit.

[How-to] Erstellen Sie eine Distributions-Zip-Datei mit Abhängigkeitsgläsern im Unterverzeichnis /libund fügen Sie alle Abhängigkeiten zum Class-PathEintrag in der Manifestdatei hinzu:

apply plugin: 'java'
apply plugin: 'java-library-distribution'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.apache.commons:commons-lang3:3.3.2'
}

// Task "distZip" added by plugin "java-library-distribution":
distZip.shouldRunAfter(build)

jar {
    // Keep jar clean:
    exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'

    manifest {
        attributes 'Main-Class': 'com.somepackage.MainClass',
                   'Class-Path': configurations.runtime.files.collect { "lib/$it.name" }.join(' ')
    }
    // How-to add class path:
    //     /programming/22659463/add-classpath-in-manifest-using-gradle
    //     https://gist.github.com/simon04/6865179
}

Hosted als Kern hier .

Das Ergebnis finden Sie in build/distributionsund der entpackte Inhalt sieht folgendermaßen aus:

lib / commons-lang3-3.3.2.jar
MyJarFile.jar

Inhalt von MyJarFile.jar#META-INF/MANIFEST.mf:

Manifest-Version: 1.0
Hauptklasse: com.somepackage.MainClass
Klassenpfad: lib / commons-lang3-3.3.2.jar

Martin Andersson
quelle
Das aktuelle Anwendungsglas befindet sich ebenfalls im Verzeichnis 'lib' der Distribution. Sie müssen es entweder verschieben, indem Sie es in das oberste Verzeichnis verschieben, oder diese Zeile ändern: 'Klassenpfad': configuration.runtime.files.collect {"lib / $ it.name"} .join ('')} in dieses: 'Klassenpfad': configuration.runtime.files.collect {"$ it.name"} .join ('')}
Marc Nuri
1
@MarcNuri Bist du sicher? Ich habe versucht, diesen Ansatz für meine Anwendung zu verwenden, und das aktuelle Anwendungsglas befand sich nicht im libVerzeichnis der erstellten zip / tar-Datei, sondern im libübergeordneten Verzeichnis, wie diese Antwort nahelegt. Diese Lösung schien perfekt für mich zu funktionieren.
Thejonwithnoh
1
@thejonwithnoh Es tut mir leid, du hast recht. Ich habe die vorgeschlagene Lösung zur Verwendung des Plugins "Java-Library-Distribution" nicht gesehen. In meinem Fall verwende ich einfach das Plugin "application", das den gleichen Job ausführt, mit dem Hauptunterschied, dass sich alle JAR-Dateien ( einschließlich des JARs der Anwendung) im Verzeichnis "lib" befinden. Ein Wechsel "lib/$it.name"zu "$it.name"wird also den Job erledigen.
Marc Nuri
27

Die Lösung mit dem geringsten Aufwand bestand für mich darin, das Gradle-Shadow-Plugin zu verwenden

Neben dem Anwenden des Plugins muss nur Folgendes getan werden:

Konfigurieren Sie die JAR-Aufgabe so, dass Ihre Hauptklasse manifestiert wird

jar {
  manifest {
   attributes 'Main-Class': 'com.my.app.Main'
  }
}

Führen Sie die Gradle-Aufgabe aus

./gradlew shadowJar

Nehmen Sie die app-version-all.jar aus build / libs /

Und schließlich führen Sie es aus über:

java -jar app-version-all.jar
Ostkontentitan
quelle
2
Hier ist ein vollständiges build.gradleBeispiel .
Brad Turek
Wenn ich diesen Build mache, erhalte ich BUILD SUCCESSFUL, aber wenn ich versuche, eine JAR-Datei mit Java -jar build / libs / core-all-1.0.jar auszuführen, erhalte ich den folgenden Fehler: Fehler: Die Hauptklasse scanners.exchange konnte nicht gefunden oder geladen werden. Hauptursache: java.lang.ClassNotFoundException: scanners.exchange.Main Wissen Sie, wie ich das lösen kann?
Luka Lopusina
@LukaLopusina Die von Ihnen angegebene Klasse befindet sich nicht in Ihrer JAR-Datei. Wenn Sie Kotlin verwenden, müssen Sie sagen 'com.my.app.MainKt'. Ohne weitere Informationen kann ich Ihnen nicht weiter helfen.
Bis zum
Das aktuelle Plugin Shadow v5. + Ist nur mit Gradle 5.0+ und Java 7+ kompatibel.
Zon
5

Haben Sie die 'installApp'-Aufgabe ausprobiert? Erstellt es nicht ein vollständiges Verzeichnis mit einer Reihe von Startskripten?

http://www.gradle.org/docs/current/userguide/application_plugin.html

Jorge_B
quelle
Soweit ich installAppweiß , wird keine META-INF/MANIFEST.MFDatei erstellt. Mache ich etwas falsch?
Advait
1
Ich sehe keine installAppAufgabe in der Aufgabenliste des Anwendungs-Plugins . Meinten Sie installDiststattdessen?
Quazi Irfan
2
Ja, installAppwurde installDistin Gradle 3.0 umbenannt. Hier ist der Versionshinweis .
Quazi Irfan
4

Danke Konstantin, es hat wie ein Zauber mit wenigen Nuancen funktioniert. Aus irgendeinem Grund funktionierte die Angabe der Hauptklasse als Teil des JAR-Manifests nicht ganz und wollte stattdessen das Attribut mainClassName. Hier ist ein Ausschnitt aus build.gradle, der alles enthält, damit es funktioniert:

plugins {
  id 'java' 
  id 'com.github.johnrengelman.shadow' version '1.2.2'
}
...
...
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'
...
...
mainClassName = 'com.acme.myapp.MyClassMain'
...
...
...
shadowJar {
    baseName = 'myapp'
}

Nach dem Ausführen von gradle shadowJar erhalten Sie myapp- {version} -all.jar in Ihrem Build-Ordner, der als java -jar myapp- {version} -all.jar ausgeführt werden kann.

Alex Yavorskiy
quelle
3

Sie können ein JAR-Artefakt in den Moduleinstellungen (oder in der Projektstruktur) definieren.

  • Klicken Sie mit der rechten Maustaste auf das Modul> Moduleinstellungen öffnen> Artefakte> +> JAR> aus Modulen mit Abhängigkeiten.
  • Stellen Sie die Hauptklasse ein.

Das Erstellen eines Glases ist dann so einfach wie das Klicken auf "Artefakt erstellen ..." im Menü "Erstellen". Als Bonus können Sie alle Abhängigkeiten in einem einzigen Glas verpacken.

Getestet auf IntelliJ IDEA 14 Ultimate.

Mondain
quelle
2

Ich habe einige Links auf die Lösung überprüft und schließlich die unten genannten Schritte ausgeführt, um sie zum Laufen zu bringen. Ich benutze Gradle 2.9.

Nehmen Sie die folgenden Änderungen in Ihrer Build-Gradle-Datei vor:

  1. Plugin erwähnen:

    apply plugin: 'eu.appsatori.fatjar'
  2. Stellen Sie das Buildscript bereit:

    buildscript {
    repositories {
        jcenter()
    }
    
    dependencies {
        classpath "eu.appsatori:gradle-fatjar-plugin:0.3"
    }
    }
  3. Geben Sie die Hauptklasse an:

    fatJar {
      classifier 'fat'
      manifest {
        attributes 'Main-Class': 'my.project.core.MyMainClass'
      }
      exclude 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.SF'
    }
  4. Erstellen Sie den Fatjar:

    ./gradlew clean fatjar
  5. Führen Sie den Fatjar aus / build / libs /:

    java -jar MyFatJar.jar
Sandeep Sarkar
quelle
1
Ab 2019: Dieser Vorschlag funktioniert hier nicht. Im Fatjar
Carl
1

Sie können das SpringBoot-Plugin verwenden:

plugins {
  id "org.springframework.boot" version "2.2.2.RELEASE"
}

Erstellen Sie das Glas

gradle assemble

Und dann lass es laufen

java -jar build/libs/*.jar

Hinweis: Ihr Projekt muss KEIN SpringBoot-Projekt sein, um dieses Plugin verwenden zu können.

Topera
quelle