Eine Erinnerung an die Schritte zum Verwalten eines Multiprojekts mit dem Build-Tool Gradle und zum Erstellen einer ausführbaren JAR als Ergebnis.
Ist das Hauptthema.
Ich hatte die Möglichkeit, eine GUI-Anwendung mit JavaFX [^ JavaFX] zu erstellen. Die Anwendung wird schließlich im EXE-Format von einem Tool namens javapackager [^ javapackager] verteilt. Als ersten Schritt musste jedoch eine JAR generiert werden, die durch Doppelklicken gestartet werden kann.
[^ JavaFX]: 1 Übersicht über JavaFX (Release 8) (https://docs.oracle.com/javase/jp/8/javafx/get-started-tutorial/jfx-overview.htm) [^ javapackager]: In sich geschlossenes Anwendungspaket (https://docs.oracle.com/javase/jp/8/docs/technotes/guides/deploy/self-contained-packaging.html)
Darüber hinaus mussten Anwendungen mehrere Typen mit nahezu derselben internen Verarbeitung und unterschiedlichem Detailverhalten erstellen. Ich wollte common.jar als gemeinsamen Teil und app1.jar und app2.jar (jeweils abhängig von common.jar) als Eingangs-JAR verwenden, habe mich jedoch mit Jenkins in der Firma befasst und nach einem Multiprojekt mit einer ähnlichen Konfiguration gesucht. Selbst als ich es versuchte, gab es kein Skript, das nur mit Gradle fertiggestellt wurde. Es gab nur ein Shell-Skript, das das allgemeine build.gradle und dann das app1 build.gradle aufrief.
Ich hätte den Zweck durch Nachahmung des Shell-Skripts erreichen können, aber zum Lernen habe ich beschlossen, ein Skript zu erstellen, das mit Gradle vervollständigt wird.
Beginnen wir der Einfachheit halber mit nur einem App-Projekt.
Erstellen Sie zunächst ein minimales Multiprojekt und rufen Sie die Aufgabe "Hallo" auf, um den Vorgang zu überprüfen.
Erstellen Sie eine allgemeine Verzeichnis-App, master direkt unter dem Arbeitsverzeichnis, und erstellen Sie build.gradle und settings.gradle direkt unter master [^ tree command on Mac]:
[^ Tree-Befehl auf Mac]: Tree-Befehl auf Mac --Qiita (http://qiita.com/kanuma1984/items/c158162adfeb6b217973)
master$ tree ../
../
├── app
├── common
└── master
├── build.gradle
└── settings.gradle
Geben Sie in settings.gradle das Verzeichnis an, das Sie als Teilprojekt erkennen möchten. Das Stammprojekt ist nicht angegeben, aber das Verzeichnis mit dem Namen master wird automatisch als Stammprojekt erkannt, sodass es nicht angegeben wird [^ Multi-Projekt mit Gradle]:
[^ Multiprojekt mit Gradle]: Multiprojekt mit Gradle-Qiita (http://qiita.com/shiena/items/371fe817c8fb6be2bb1e)
settings.gradle
includeFlat 'app', 'common'
Definieren Sie in build.gradle eine "Hallo" -Aufgabe im "allprojects" -Block [Warum ^ doLast verwenden]:
[Grund für die Verwendung von ^ doLast]: Die Methode leftShift und der Operator << `sind veraltet. (https://docs.gradle.org/3.2/release-notes#the-left-shift-operator-on-the-task-interface)
build.gradle
allprojects {
task hello {
doLast {
println "I`m ${project.name}."
}
}
}
Rufen Sie zu diesem Zeitpunkt die Aufgabe "Hallo" im Hauptverzeichnis auf [Bedeutung ^ q]. Wenn Sie Folgendes sehen, ist es in Ordnung:
[Bedeutung von ^ q]: Die Option -q
wird verwendet, um das Ergebnis von println
besser sichtbar zu machen.
master$ gradle -q hello
I`m master.
I`m app.
I`m common.
Übrigens, wenn Sie die Aufgabe "Hallo" im allgemeinen Verzeichnis aufrufen, wird nur die allgemeine Aufgabe aufgerufen. Sie können diese Eigenschaft auch verwenden, um nur bestimmte Projekte zu erstellen:
common$ gradle -q hello
I`m common.
Fügen Sie das Eclipse-Plug-In zu Ihrem Teilprojekt hinzu. Fügen Sie nach dem Block "Alle Projekte" Folgendes hinzu:
build.gradle
…
subprojects {
apply {
plugin 'eclipse'
}
}
Allein damit können Sie die Aufgabe "Eclipse" verwenden und ein Eclipse-Projekt erstellen:
master$ gradle eclipse
:app:eclipseProject
:app:eclipse
:common:eclipseProject
:common:eclipse
BUILD SUCCESSFUL
Total time: 2.067 secs
Da das src-Verzeichnis jedoch tatsächlich benötigt wird, werde ich es mit der gradle-Task generieren [Grund für die Nichtverwendung der ^ init-Task]. Fügen Sie ein Java-Plug-In hinzu und erstellen Sie eine "initSrcDirs" -Aufgabe wie unten gezeigt.
[Warum nicht die ^ init-Aufgabe verwenden]: Gradle wird mit einer init
-Aufgabe geliefert, aber ich wollte keine zusätzlichen Dateien erstellen, also habe ich sie selbst definiert.
build.gradle
…
subprojects {
apply {
plugin 'eclipse'
plugin 'java'
}
task initSrcDirs {
doLast {
sourceSets.all {
java.srcDirs*.mkdirs()
resources.srcDirs*.mkdirs()
}
}
}
}
Durch Ausführen der Task initSrcDirs
werden Dinge wie src / main / java und src / main / resources erstellt.
Selbst wenn die Dateien bereits in diesen Pfaden vorhanden sind, werden sie niemals leer sein. Daher ist die Verwendung sicher:
master$ gradle initSrcDirs
:app:initSrcDirs
:common:initSrcDirs
BUILD SUCCESSFUL
Total time: 0.909 secs
master$ tree ../
../
├── app
│ └── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ └── resources
├── common
│ └── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ └── resources
└── master
├── build.gradle
└── settings.gradle
Beschreibt die Variablen, die beim Definieren der Task "initSrcDirs" angezeigt wurden.
--sourceSets
ist ein Verzeichnissystem wie src / main / java, das vom Java-Plug-In definiert wird.
--mkdirs ()
ruft die mkdirs-Methode von java.io.File auf, da srcDirs
eine Liste von java.io.File ist.
Wenn Sie an einer anderen Syntax interessiert sind (z. B. "*." Der Iteration), überprüfen Sie bitte die Syntax von Groovy.
Wenn Sie die Aufgabe "Eclipse" erneut mit dem erstellten Verzeichnis "src" aufrufen, wird auch der Klassenpfad ordnungsgemäß übergeben (die Aufgabe "eclipseClaspath", die zuvor nicht vorhanden war, wird aufgerufen):
master$ gradle eclipse
:app:eclipseClasspath
:app:eclipseJdt
:app:eclipseProject
:app:eclipse
:common:eclipseClasspath
:common:eclipseJdt
:common:eclipseProject
:common:eclipse
BUILD SUCCESSFUL
Total time: 0.994 secs
Kommen wir zum Kern dessen, was wir mit dem Multi-Projekt-Management erreichen wollten. Machen Sie die App abhängig von gemeinsam.
Um den Fehlerfall zu reproduzieren, erstellen Sie zunächst die folgenden zwei Klassen, ohne build.gradle zu ändern. Constant.java wird unter common und Main.java unter app erstellt. Natürlich erkennt Eclipse nicht einmal die Abhängigkeiten des anderen, daher kann es nicht ergänzt werden und gibt einen Fehler aus:
Constant.java
package com.github.kazuma1989.dec10_2016.common;
public class Constant {
public static final String NAME = "common";
}
Main.java
package com.github.kazuma1989.dec10_2016.app;
import com.github.kazuma1989.dec10_2016.common.Constant;
public class Main {
public static void main(String[] args) {
System.out.println(Constant.NAME);
}
}
Wenn Sie die Aufgabe "jar" in diesem Zustand ausführen, schlägt der Build wie erwartet fehl. Die Aufgabe "compileJava", die der Aufgabe "jar" vorausgeht, ist fehlgeschlagen:
master$ gradle jar
:app:compileJava
/Users/kazuma/qiita/2016/dec10/app/src/main/java/com/github/kazuma1989/dec10_2016/app/Main.java:3:Error:Paket com.github.kazuma1989.dec10_2016.gemeinsam existiert nicht
import com.github.kazuma1989.dec10_2016.common.Constant;
^
/Users/kazuma/qiita/2016/dec10/app/src/main/java/com/github/kazuma1989/dec10_2016/app/Main.java:7:Error:Symbol kann nicht gefunden werden
System.out.println(Constant.NAME);
^
Symbol:Variable Konstante
Ort:Klasse Main
2 Fehler
:app:compileJava FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:compileJava'.
> Compilation failed; see the compiler error output for details.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 0.966 secs
Dann machen Sie den Build erfolgreich. Fügen Sie am Ende des Blocks "Teilprojekte" Folgendes hinzu, um die Abhängigkeit von common zu definieren:
build.gradle
…
subprojects {
…
//Andere Einstellungen als üblich
if (project.name in ['common']) return
dependencies {
compile project(':common')
}
}
Diesmal ist die Aufgabe "jar" erfolgreich und die erstellte JAR funktioniert einwandfrei. Die allgemeine JAR-Konvertierung wird zuerst ausgeführt, nicht in aufsteigender Reihenfolge der Projektnamen:
master$ gradle jar
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:app:compileJava
:app:processResources UP-TO-DATE
:app:classes
:app:jar
BUILD SUCCESSFUL
Total time: 1.154 secs
master$ java -cp ../app/build/libs/app.jar com.github.kazuma1989.dec10_2016.app.Main
common
Die jar
Aufgabe muss nicht unbedingt vom Master aufgerufen werden, sondern nur die in der App kann aufgerufen werden.
Selbst in diesem Fall wird die App nach dem Kompilieren von Common JARisiert, sodass sie ordnungsgemäß erfolgreich ist:
master$ gradle clean :app:jar
:app:clean
:common:clean
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:app:compileJava
:app:processResources UP-TO-DATE
:app:classes
:app:jar
BUILD SUCCESSFUL
Total time: 0.938 secs
master$ cd ../app/
app$ gradle clean jar
:app:clean
:common:compileJava UP-TO-DATE
:common:processResources UP-TO-DATE
:common:classes UP-TO-DATE
:common:jar UP-TO-DATE
:app:compileJava
:app:processResources UP-TO-DATE
:app:classes
:app:jar
BUILD SUCCESSFUL
Total time: 0.922 secs
Führen Sie nach dem Festlegen der Abhängigkeiten die Task "Eclipse" erneut aus, um den Fehler in Eclipse zu beseitigen und die Code-Vervollständigung zu verwenden.
Legen Sie das Manifest für die Aufgabe "jar" fest und generieren Sie eine ausführbare JAR.
Fügen Sie am Ende von build.gradle einfach eine minimale Anweisung hinzu und versuchen Sie trotzdem, die Aufgabe jar
auszuführen:
build.gradle
…
project(':app') {
jar {
manifest {
attributes 'Main-Class': 'com.github.kazuma1989.dec10_2016.app.Main'
}
}
}
master$ gradle jar
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:app:compileJava
:app:processResources UP-TO-DATE
:app:classes
:app:jar
BUILD SUCCESSFUL
Total time: 3.177 secs
Wenn Sie die erstellte JAR mit dem Befehl java
aufrufen, wird die Hauptklasse aufgerufen:
master$ java -jar ../app/build/libs/app.jar
common
Aber es hat einfach funktioniert. Dies liegt daran, dass die Constant-Klasse nur gefunden werden kann, wenn die Abhängigkeitsinformationen für common.jar im Manifest beschrieben sind. Dieses Mal verweist die Main-Klasse nur auf die Konstante der Konstantenzeichenfolge. Daher ist es wahrscheinlich, dass der Wert zur Kompilierungszeit in der Main-Klasse beibehalten wurde.
Normalerweise ist es NoClassDefFoundError. Die Kompilierung ist erfolgreich, schlägt jedoch zur Laufzeit fehl.
Im Folgenden wird beispielsweise die Basisklasse "AbstractApplication" gemeinsam erstellt und von der App geerbt.
master$ gradle jar
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:app:compileJava
:app:processResources UP-TO-DATE
:app:classes
:app:jar
BUILD SUCCESSFUL
Total time: 1.188 secs
master$ java -jar ../app/build/libs/app.jar
Exception in thread "main" java.lang.NoClassDefFoundError: com/github/kazuma1989/dec10_2016/common/AbstractApplication
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.github.kazuma1989.dec10_2016.app.Main.main(Main.java:9)
Caused by: java.lang.ClassNotFoundException: com.github.kazuma1989.dec10_2016.common.AbstractApplication
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 13 more
master$ tree ../app/build/libs/
../app/build/libs/
└── app.jar
Um dies zu lösen, ändern Sie das Innere des jar
-Blocks wie folgt.
Das Manifest hat einen "Attribute" -Klassenpfad "und der Aufgabe wurde eine" Kopier "-Operation hinzugefügt:
build.gradle
…
project(':app') {
jar {
manifest {
attributes 'Main-Class': 'com.github.kazuma1989.dec10_2016.app.Main'
attributes 'Class-Path': configurations.runtime.collect{it.name}.join(' ')
}
doLast {
copy {
from configurations.runtime
into destinationDir
}
}
}
}
configures.runtime
enthält Informationen zu den Laufzeitabhängigkeiten des App-Projekts.
Es enthält auch Informationen zu dem allgemeinen Projekt, das im Block "Abhängigkeiten" als "Kompilieren" angegeben ist.
Dies liegt daran, dass Kompilierungsabhängigkeiten auch Laufzeitabhängigkeiten sind.
Bei "Attribute" Class-Path "wird der Inhalt von" configuration.runtime "mit Leerzeichen verkettet. Der Inhalt des Manifests lautet also "Klassenpfad: common.jar" (wenn Sie andere Abhängigkeiten als common hinzugefügt haben, ist dies so etwas wie "Klassenpfad: common.jar another.jar extra.jar". ).
Die JAR mit der Angabe "Class-Path: common.jar" im Manifest sucht nach common.jar im selben Verzeichnis wie sie selbst. Kopieren Sie also common.jar in dasselbe Verzeichnis wie app.jar im Block "copy". Ich bin.
Mit dem Hinzufügen des Manifests und der Kopie der abhängigen JAR ist die JAR-Ausführung nun erfolgreich. Wenn Sie das libs-Verzeichnis führen, ist Ihre Anwendung vollständig:
master$ gradle clean jar
:app:clean
:common:clean
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:app:compileJava
:app:processResources UP-TO-DATE
:app:classes
:app:jar
BUILD SUCCESSFUL
Total time: 0.952 secs
master$ java -jar ../app/build/libs/app.jar
common
master$ tree ../app/build/libs/
../app/build/libs/
├── app.jar
└── common.jar
An diesem Punkt können Sie tun, was Sie vorerst tun wollten. Als nächstes möchte ich ein App2-Projekt hinzufügen, um es einem realen Beispiel näher zu bringen, aber zuerst werde ich das Skript organisieren.
Ich wollte es vorerst verschieben, deshalb habe ich die Einstellungen des App-Projekts in build.gradle des Master-Projekts aufgelistet. Wenn jedoch nichts unternommen wird, erhöht sich die Anzahl der Beschreibungen, die master / build.gradle ähneln, mit zunehmender Anzahl von Teilprojekten.
Selbst wenn ich die Anzahl der Teilprojekte erhöhe, möchte ich die Änderungen nur in settings.gradle beibehalten. Daher werden die App-Projekteinstellungen im App-Verzeichnis in build.gradle unterteilt:
master$ tree ../ -L 2
../
├── app
│ ├── build.gradle
│ └── src
├── common
│ └── src
└── master
├── build.gradle
└── settings.gradle
Auf den ersten Blick scheint es erfolgreich zu sein, aber es scheitert.
Geben Sie für app / build.gradle nur die Hauptklasse an, die für jedes Projekt unterschiedlich ist. Verschieben Sie in master / build.gradle das, was im Block "project (": app ")" geschrieben wurde, in den Block "subprojects":
app/build.gradle
jar {
manifest {
attributes 'Main-Class': 'com.github.kazuma1989.dec10_2016.app.Main'
}
}
master/build.gradle
…
subprojects {
…
jar {
manifest {
attributes 'Class-Path': configurations.runtime.collect{it.name}.join(' ')
}
doLast {
copy {
from configurations.runtime
into libsDir
}
}
}
}
// project(':app') {
// }
In Zeile 33 tritt ein Fehler auf (wobei Attribute'Class-Path'
):
master$ gradle clean jar
FAILURE: Build failed with an exception.
* Where:
Build file '/Users/kazuma/qiita/2016/dec10/master/build.gradle' line: 33
* What went wrong:
A problem occurred evaluating root project 'master'.
> Could not resolve all dependencies for configuration ':app:runtime'.
> Project :app declares a dependency from configuration 'compile' to configuration 'default' which is not declared in the descriptor for project :common.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 0.837 secs
Es scheint, dass ein Fehler aufgetreten ist, weil die in der App verwendete configuration.runtime
nicht behoben werden konnte.
Dies liegt daran, dass die Einstellungen des App-Projekts ausgeführt wurden, bevor das Java-Plug-In im allgemeinen Projekt aufgerufen wurde.
groovy:master/build.gradle(app/build.(Vor der Gradle-Trennung)
subprojects {
// app ->Das Projekt ist in der Reihenfolge der gemeinsamen festgelegt (Fortsetzung unten).
apply {
plugin 'java'
…
}
…
}
project(':app') {
//Dieser Block wurde ausgeführt, nachdem das Java-Plug-In auf common angewendet wurde, sodass kein Problem auftrat.
jar {
…
}
}
groovy:master/build.gradle(app/build.Nach Gradle-Trennung)
subprojects {
// app ->Das Projekt ist in der Reihenfolge der gemeinsamen festgelegt.
apply {
plugin 'java'
…
}
// project(':app')Da die Verarbeitung, die in war, in den Teilprojektblock gebracht wurde, wird sie ausgeführt, bevor das Java-Plug-In auf common angewendet wird, und es tritt ein Fehler auf.
jar {
…
}
}
Es funktioniert einwandfrei, wenn Sie den Block "Teilprojekte" in "alle Teilprojekte einschließlich common und app" und "andere Teilprojekte als common" unterteilen. Die Bedeutung des Blocks ist ebenfalls klar, daher ist dies auch für die Lesbarkeit besser:
master/build.gradle
//Alle Teilprojekte einschließlich Common und App
subprojects {
apply {
plugin 'java'
…
}
…
}
//Andere als übliche Teilprojekte
subprojects {
if (project.name in ['common']) return
dependencies {
…
}
jar {
…
}
}
master$ gradle clean jar
:app:clean
:common:clean
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:app:compileJava
:app:processResources UP-TO-DATE
:app:classes
:app:jar
BUILD SUCCESSFUL
Total time: 0.94 secs
master$ java -jar ../app/build/libs/app.jar
common
Da sich die Einstellungen für jedes Projekt nur in den Werten unterscheiden, ersetzen wir app / build.gradle durch gradle.properties. Gradle.properties ist wie build.gradle eine Eigenschaftendatei, die automatisch geladen wird, wenn sie direkt unter dem Projektverzeichnis vorhanden ist:
master$ tree ../ -L 2
../
├── app
│ ├── gradle.properties
│ └── src
├── common
│ └── src
└── master
├── build.gradle
└── settings.gradle
app/gradle.properties
jar.manifest.attributes.Main-Class=com.github.kazuma1989.dec10_2016.app.Main
Rufen Sie in build.gradle den Wert mit der Methode "getProperty" ab.
Die Einschränkung ist, dass Sie den Block "afterEvaluate" verwenden müssen.
Dies liegt daran, dass die Projekte in der Reihenfolge Master-Projekt und App-Projekt geladen werden, sodass app / gradle.properties zum Zeitpunkt von master / build.gradle noch nicht geladen wurde.
Innerhalb des afterEvaluate
-Blocks funktioniert, nachdem jedes Projekt ausgewertet wurde (nach Abschluss des Projekt-Setups), also funktioniert es:
master/build.gradle
…
subprojects {
…
afterEvaluate {
jar {
manifest {
attributes 'Main-Class': getProperty('jar.manifest.attributes.Main-Class')
attributes 'Class-Path': configurations.runtime.collect{it.name}.join(' ')
}
…
}
}
}
Am Ende sieht die Konfiguration für mehrere Projekte folgendermaßen aus:
master$ tree ../ -L 2
../
├── app
│ ├── gradle.properties //Schreiben Sie die Einstellungen für jedes Projekt
│ └── src //Quellcode für jede Anwendung. Kommt auf gemeinsame an
├── common
│ └── src //Gemeinsame Teile
└── master
├── build.gradle //Definieren Sie Aufgaben, die in allen Projekten verwendet werden
└── settings.gradle //Geben Sie Verzeichnisse an, die in mehrere Projekte aufgenommen werden sollen
Alle JARs werden durch Eingabe von "gradle jar" in das Hauptverzeichnis generiert. Sie können in jedem Projektverzeichnis eine separate JAR mit "gradle jar" oder "gradle: common: jar" erstellen. Die generierte JAR ist als JAR organisiert, die vom libs-Verzeichnis abhängt. Sie können sie also so verwenden, wie sie ist, wenn Sie das gesamte Verzeichnis tragen.
Wenn wir die Anzahl der Projekte in Zukunft erhöhen, wird dies wie folgt sein:
master$ tree ../ -L 2
../
├── app
│ ├── gradle.properties
│ └── src
├── app2
│ ├── gradle.properties
│ └── src
├── app3
│ ├── build.gradle //Skripte können platziert werden, wenn spezielle Einstellungen erforderlich sind
│ ├── gradle.properties
│ └── src
├── common
│ ├── build.gradle //Geben Sie die Bibliotheken an, von denen Common abhängt
│ └── src
└── master
├── build.gradle
└── settings.gradle
das ist alles.
Recommended Posts