Es scheint mir, dass die meisten Leute erst dann mit Gradle zu tun haben, wenn etwas zum Projekt hinzugefügt werden muss oder plötzlich etwas kaputt geht - und nachdem das Problem "durch Überarbeitung erworben" gelöst wurde, ist die Erfahrung sicher vergessen. Darüber hinaus ähneln viele Beispiele im Internet hochspezialisierten Zaubersprüchen, die kein Verständnis für das Geschehen vermitteln:
android { compileSdkVersion 28 defaultConfig { applicationId "com.habr.hello" minSdkVersion 20 targetSdkVersion 28 } buildTypes { release { minifyEnabled false } } }
Ich werde nicht im Detail beschreiben, wofür jede Zeile oben ist - dies sind private Details der Implementierung des Android-Plugins. Es gibt etwas Wertvolleres - ein Verständnis dafür, wie alles organisiert ist. Die Informationen sind auf verschiedenen Websites / offiziellen Dokumentationen / Quellen des Hagels und Plugins dafür verteilt - im Allgemeinen ist dies ein etwas universelleres Wissen, das ich nicht vergessen möchte.
Der weitere Text kann als Spickzettel für diejenigen betrachtet werden, die gerade den Gradle beherrschen oder bereits vergessen haben.
Nützliche Links
Konsole
Android Studio / IDEA verbirgt sorgfältig Gradle-Befehle vor dem Entwickler, und selbst wenn die build.gradle-Dateien geändert werden, wird das Projekt dumm oder neu gestartet.
In solchen Fällen ist das Aufrufen von gradle über die Konsole viel einfacher und schneller. Der Grapper-Wrapper gehört normalerweise zum Projekt und funktioniert unter Linux / MacOS / Windows einwandfrei, es sei denn, Sie müssen in letzterem die Bat-Datei anstelle des Wrappers aufrufen.
Aufgaben herausfordern
./gradlew tasks
schreibt verfügbare Aufgaben.
./gradlew subprojectName:tasks --all
Sie können die Aufgaben eines separaten Teilprojekts anzeigen, und selbst mit der Option --all
werden alle Aufgaben, einschließlich der sekundären, angezeigt.
Sie können jede Aufgabe aufrufen, und alle Aufgaben, von denen es abhängt, werden aufgerufen.
./gradlew app:assembleDevelopDebug
Wenn Sie zu faul sind, um den ganzen Namen zu schreiben, können Sie kleine Buchstaben wegwerfen:
./gradlew app:assembleDD
Wenn der Hagel nicht klar erraten kann, welche Aufgabe er vorhatte, wird eine Liste geeigneter Optionen angezeigt.
Protokollierung
Die Menge an Informationen, die beim Starten einer Aufgabe in der Konsole angezeigt werden, hängt stark von der Protokollierungsstufe ab.
Zusätzlich zur Standardeinstellung gibt es -q, -w, -i, -d
, well oder --quiet, --warn, --info, --debug
in zunehmender Informationsmenge. Bei komplexen Projekten kann die Ausgabe mit -d mehr als ein Megabyte in Anspruch nehmen. Daher ist es besser, sie sofort in einer Datei zu speichern und dort bereits nach Schlüsselwörtern zu suchen:
./gradlew app:build -d > myLog.txt
Wenn irgendwo eine Ausnahme ausgelöst wird, wird die Option -s
für die Stapelverfolgung verwendet.
Sie können selbst in das Protokoll schreiben:
logger.warn('A warning log message.')
Der Logger ist eine Implementierung von SLF4J.
Groovy
Was in build.gradle
Dateien passiert, ist nur grooviger Code.
Aus irgendeinem Grund ist Groovy als Programmiersprache nicht sehr beliebt, obwohl es, wie es mir scheint, an sich zumindest ein wenig Studium wert ist. Die Sprache wurde 2003 geboren und entwickelte sich langsam. Interessante Features:
- Fast jeder Java-Code ist ein gültiger Groovy-Code. Es hilft sehr, intuitiv Arbeitscode zu schreiben.
- Gleichzeitig mit der statischen, dynamischen Typisierung wird in der Rille unterstützt. Anstelle von
String a = "a"
Sie sicher def a = "a"
oder sogar def map = ['one':1, 'two':2, 'list' = [1,false]]
schreiben. def map = ['one':1, 'two':2, 'list' = [1,false]]
- Es gibt Abschlüsse, für die Sie den Ausführungskontext dynamisch bestimmen können. Dieselben
android {...}
-Blöcke schließen und führen sie dann für ein Objekt aus. - Es gibt eine Interpolation der Zeichenfolgen
"$a, ${b}"
, der mehrzeiligen Zeichenfolgen """yep, ${c}"""
und gewöhnlicher Java-Zeichenfolgen werden in einfache Anführungszeichen gesetzt: 'text'
- Es gibt einen Anschein von Erweiterungsmethoden. Die Standard-Sprachsammlung enthält bereits Methoden wie any, every, each, findAll. Für mich persönlich scheinen die Namen der Methoden ungewöhnlich, aber die Hauptsache ist, dass sie es sind .
- Köstlicher syntaktischer Zucker, der Code ist viel kürzer und einfacher. Sie müssen keine Klammern um die Argumente der Funktion schreiben. Für die Deklaration von Listen und Hash-
[a,b,c], [key1: value1, key2: value2]
lautet [a,b,c], [key1: value1, key2: value2]
nette Syntax: [a,b,c], [key1: value1, key2: value2]
Im Allgemeinen ist es mir ein Rätsel, warum Sprachen wie Python / Javascript in die Höhe geschossen sind und Groovy nicht. Für seine Zeit, als es in Java nicht einmal Lambdas gab und Alternativen wie Kotlin / Scala nur auftauchten oder noch nicht existierten, musste Groovy wie eine wirklich interessante Sprache aussehen.
Es war die Flexibilität der groovigen Syntax und der dynamischen Typisierung, die es uns ermöglichte, prägnante DSLs in Gradle zu erstellen.
In der offiziellen Gradle-Dokumentation sind Beispiele für Kotlin dupliziert, und es scheint geplant zu sein, darauf umzusteigen, aber der Code sieht nicht mehr so einfach aus und ähnelt eher normalem Code:
task hello { doLast { println "hello" } }
vs.
tasks.register("hello") { doLast { println("hello") } }
Eine Umbenennung in Kradle ist jedoch noch nicht geplant.
Montagestufen
Sie sind in Initialisierung, Konfiguration und Ausführung unterteilt.
Die Idee ist, dass gradle einen azyklischen Abhängigkeitsgraphen sammelt und nur das erforderliche Minimum davon aufruft. Wenn ich das richtig verstehe, erfolgt die Initialisierungsphase in dem Moment, in dem der Code von build.gradle ausgeführt wird.
Zum Beispiel:
copy { from source to dest }
Oder so:
task epicFail { copy{ from source to dest } }
Vielleicht ist dies nicht offensichtlich, aber das Obige verlangsamt die Initialisierung. Um nicht bei jeder Initialisierung Dateien kopieren zu müssen, müssen Sie den doLast{...}
oder doFirst{...}
in der Aufgabe verwenden. Anschließend wird der Code in einen Abschluss eingeschlossen und nach Abschluss der Aufgabe aufgerufen.
task properCopy { doLast { copy { from dest to source } } }
oder so
task properCopy(type: Copy) { from dest to source }
In den alten Beispielen sehen doLast
den Operator <<
anstelle von doLast
, der jedoch später aufgrund des nicht offensichtlichen Verhaltens abgebrochen wurde.
task properCopy << { println("files copied") }
aufgaben.all
Was lustig ist, mit doLast
und doFirst
Sie jede Art von Aktion an jede Aufgabe hängen:
tasks.all { doFirst { println("task $name started") } }
Die IDE schlägt vor, dass tasks
eine whenTaskAdded(Closure ...)
Methode whenTaskAdded(Closure ...)
, aber die all(Closure ...)
Methode all(Closure ...)
funktioniert viel interessanter - Closure wird für alle vorhandenen Aufgaben sowie für neue Aufgaben aufgerufen, wenn sie hinzugefügt werden.
Erstellen Sie eine Aufgabe, die die Abhängigkeiten aller Aufgaben druckt:
task printDependencies { doLast { tasks.all { println("$name dependsOn $dependsOn") } } }
oder so:
task printDependencies { doLast { tasks.all { Task task -> println("${task.name} dependsOn ${task.dependsOn}") } } }
Wenn tasks.all{}
zur Laufzeit (im doLast
Block) doLast
wird, werden alle Aufgaben und Abhängigkeiten doLast
.
Wenn Sie dasselbe ohne doLast
(d. H. Während der Initialisierung), fehlen den gedruckten Aufgaben möglicherweise Abhängigkeiten, da sie noch nicht hinzugefügt wurden.
Oh ja, Sucht! Wenn eine andere Aufgabe von den Ergebnissen unserer Implementierung abhängen soll, lohnt es sich, eine Abhängigkeit hinzuzufügen:
anotherTask.dependsOn properCopy
Oder sogar so:
tasks.all{ task -> if (task.name.toLowerCase().contains("debug")) { task.dependsOn properCopy } }
Eine gemeinsame Aufgabe wird jedes Mal aufgerufen. Wenn Sie angeben, dass eine auf Datei A basierende Aufgabe Datei B generiert, überspringt gradle die Aufgabe, wenn sich diese Dateien nicht geändert haben. Und gradle überprüft nicht das Änderungsdatum der Datei, sondern deren Inhalt.
task generateCode(type: Exec) { commandLine "generateCode.sh", "input.txt", "output.java" inputs.file "input.txt" output.file "output.java" }
Ebenso können Sie Ordner sowie einige Werte inputs.property(name, value)
: inputs.property(name, value)
.
Aufgabenbeschreibung
Beim Aufrufen von ./gradlew tasks --all
Standardaufgaben eine schöne Beschreibung und sind irgendwie gruppiert. Für Ihre Aufgaben wird dies ganz einfach hinzugefügt:
task hello { group "MyCustomGroup" description "Prints 'hello'" doLast{ print 'hello' } }
task.enabled
Sie können die Aufgabe "ausschalten" - dann werden ihre Abhängigkeiten weiterhin aufgerufen, selbst jedoch nicht.
taskName.enabled false
mehrere Projekte (Module)
Multiprojekt-Builds in der Dokumentation
Im Hauptprojekt können Sie mehrere weitere Module platzieren. Dies wird beispielsweise in Android-Projekten verwendet - das Root-Projekt enthält fast nichts, das Android-Plugin ist im Teilprojekt enthalten. Wenn Sie ein neues Modul hinzufügen möchten, können Sie ein weiteres hinzufügen. Dort können Sie beispielsweise auch das Android-Plugin anschließen, aber andere Einstellungen dafür verwenden.
Ein weiteres Beispiel: Wenn Sie ein Projekt mit jitpack veröffentlichen, beschreibt das Stammprojekt, mit welchen Einstellungen ein untergeordnetes Modul veröffentlicht werden soll, das die Veröffentlichung möglicherweise nicht einmal vermutet.
Untergeordnete Module werden in settings.gradle angegeben:
include 'name'
Weitere Informationen zu Abhängigkeiten zwischen Projekten finden Sie hier.
buildSrc
Wenn in build.gradle
viel Code build.gradle
oder dieser dupliziert wird, kann er in ein separates Modul verschoben werden. Wir brauchen einen Ordner mit dem magischen Namen buildSrc
, in dem Sie den Code in groovy oder java platzieren können. ( buildSrc/src/main/java/com/smth/
, oder besser gesagt, in buildSrc/src/main/java/com/smth/
code können Tests zu buildSrc/src/test
hinzugefügt werden.) Wenn Sie beispielsweise etwas anderes möchten, schreiben Sie Ihre Aufgabe auf Scala oder verwenden Sie einige Abhängigkeiten. Dann buildSrc
Sie direkt in buildSrc
erstellen und die erforderlichen Abhängigkeiten darin angeben / Plugins aktivieren.
Leider kann bei einem Projekt in buildSrc
IDE mit Hinweisen dumm sein, dort müssen Sie Importe schreiben und Klassen / Aufgaben von dort müssen build.gradle
es auch in das übliche build.gradle
. import com.smth.Taskname
ist nicht schwierig. Sie müssen sich nur daran erinnern und nicht darüber buildSrc
warum die Aufgabe von buildSrc
nicht gefunden wurde.
Aus diesem Grund ist es praktisch, zuerst etwas zu schreiben, das direkt in build.gradle
, und erst dann den Code an buildSrc
zu buildSrc
.
Eigener Aufgabentyp
Die Aufgabe erbt von DefaultTask
, in dem es viele, viele Felder, Methoden und andere Dinge gibt. Von DefaultTask geerbter AbstractTask-Code.
Nützliche Punkte:
- Anstatt
inputs
und outputs
manuell hinzuzufügen outputs
Sie ihnen Felder und Anmerkungen verwenden: @Input, @OutputFile
usw. - Methode, die ausgeführt wird, wenn die Aufgabe ausgeführt wird:
@TaskAction
. - Praktische Methoden wie
copy{from ... , into... }
können weiterhin aufgerufen werden, müssen jedoch für das Projekt explizit aufgerufen werden: project.copy{...}
Wenn jemand in build.gradle
für unsere Aufgabe schreibt
taskName { ...
Die Methode configure(Closure)
wird für die Task aufgerufen.
Ich bin mir nicht sicher, ob dies der richtige Ansatz ist, aber wenn eine Aufgabe mehrere Felder hat, deren gegenseitiger Zustand mit Getter-Setzern schwer zu kontrollieren ist, erscheint es recht praktisch, die Methode wie folgt neu zu definieren:
override def configure(Closure closure){ def result = super().configure(closure)
Und selbst wenn du schreibst
taskName.fieldName value
dann wird die configure
Methode weiterhin aufgerufen.
Eigenes Plugin
Wie bei einer Aufgabe können Sie Ihr eigenes Plugin schreiben, das etwas konfiguriert oder Aufgaben erstellt. Zum Beispiel ist das, was in android{...}
passiert, ein Verdienst dunkle Magie Android-Plug-In, das zusätzlich eine ganze Reihe von Aufgaben wie die App erstellt: assembleDevelopDebug für alle möglichen Kombinationen von Flavour / Build-Typ / Dimension. Das Schreiben Ihres Plugins ist nicht kompliziert. Zum besseren Verständnis können Sie den Code anderer Plugins sehen.
Es gibt einen dritten Schritt: Sie können den Code nicht in buildSrc
, sondern zu einem separaten Projekt machen. Veröffentlichen Sie dann mit https://jitpack.io oder etwas anderem das Plugin und verbinden Sie es ähnlich mit den anderen.
Das Ende
Die obigen Beispiele können Tippfehler und Ungenauigkeiten enthalten. Schreiben Sie eine persönliche Notiz oder markieren Sie sie mit ctrl+enter
- ich werde sie korrigieren. Spezifische Beispiele werden am besten aus der Dokumentation entnommen. In diesem Artikel finden Sie eine kleine Liste mit Anleitungen.