Gradle Spickzettel

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 } } 

Ein-, Ausgänge und inkrementelle Montage


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 { ... //some code } 

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) //    / - return result; } 

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.

Source: https://habr.com/ru/post/de458046/


All Articles