Aufgaben
Der folgende Code zeigt drei Abschlussaufgaben, und wir werden die Unterschiede zwischen diesen drei später erklären.
Aufgabe mytask {println "Hallo, Welt!" } Task mytask {dolast {println "Hallo, Welt!" }} Task Mytask << {println "Hallo, Welt!" }Mein Ziel ist es, eine Aufgabe zu erstellen, die "Hallo, Welt!" Ausdrucke, ausdruckt. Wenn es ausgeführt wird. Als ich die Aufgabe zum ersten Mal erstellt habe, sollte sie so geschrieben werden:
Aufgabe mytask {println "Hallo, Welt!" }Versuchen Sie nun, diesen Mytask auszuführen, Gradle Mytask in die Befehlszeile einzugeben und wie folgt zu drucken:
Benutzer $ Gradle Mytask Hallo, Welt! : Mytask aktuell
Diese Aufgabe sieht so aus. Es druckt "Hallo, Welt!".
Es ist jedoch nicht das, was wir erwartet hatten. Schauen wir uns an, warum. Geben Sie Gradle -Aufgaben in die Befehlszeile ein, um alle verfügbaren Aufgaben anzuzeigen.
Benutzer $ Gradle Aufgaben Hallo, Welt! :tasks ------------------------------------------------------------ All tasks runnable from root project ------------------------------------------------------------ Build Setup tasks ----------------- init - Initializes a new Gradle build. [Inkubieren] ..........
Warten Sie, warum ist "Hallo, Welt!" ausgedruckt? Ich wollte nur sehen, welche Aufgaben verfügbar sind und keine benutzerdefinierten Aufgaben ausgeführt!
Der Grund ist eigentlich sehr einfach. Gradle Task hat zwei Hauptstufen in ihrem Lebenszyklus: Konfigurationsphase und Ausführungsstufe.
Vielleicht sind meine Worte nicht sehr genau, aber das kann mir wirklich helfen, Aufgaben zu verstehen.
Gradle muss die Aufgabe vor der Ausführung konfigurieren. Dann ist die Frage: Woher weiß ich, welcher Code in meiner Aufgabe während des Konfigurationsprozesses ausgeführt wird und welcher Code wird ausgeführt, wenn die Aufgabe ausgeführt wird? Die Antwort ist, dass der Code auf der oberen Ebene der Aufgabe der Konfigurationscode ist, wie z. B.:
Aufgabe mytask {def name = "pavel" // <- Diese Codezeile wird println ausgeführt "Hallo, Welt!" //// <- Diese Codezeile wird auch in der Konfigurationsphase ausgeführt}Deshalb drucke ich, wenn ich Gradle -Aufgaben ausführe, "Hallo, Welt!" Ausgeschnittene Ausdrucke aus. - Da der Konfigurationscode ausgeführt wird. Aber das ist nicht der Effekt, den ich will, ich will "Hallo, Welt!" Um es nur auszudrucken, wenn ich Mytask ausdrücklich anrufe. Um diesen Effekt zu erzielen, besteht der einfachste Weg, die Methode der Aufgabe#Dolast () zu verwenden.
Aufgabe mytask {def text = 'Hallo, Welt!' // Konfigurieren Sie meine Aufgabe Dolast {println text // Dies wird ausgeführt, wenn meine Aufgabe}} genannt wirdJetzt "Hallo, Welt!" wird nur ausdrucken, wenn ich Gradle Mytask ausführe. Cool, jetzt weiß ich, wie man die Aufgabe konfigurieren und das Richtige macht. Es gibt eine andere Frage. Was bedeutet im ersten Beispiel das << Symbol der dritten Aufgabe?
Aufgabe mytask2 << {println "Hallo, Welt!" }Dies ist eigentlich nur eine syntaktische Zuckerversion von Dolast. Es hat den gleichen Effekt wie die folgende Schreibmethode:
Aufgabe mytask {dolast {println 'Hallo, Welt!' // Dies wird ausgeführt, wenn meine Aufgabe}} genannt wirdAuf diese Weise werden jedoch alle Codes ausgeführt und es gibt keinen Konfigurationsteil des Codes, sodass sie für die einfachen Aufgaben besser geeignet sind, für die keine Konfiguration erforderlich ist. Sobald Ihre Aufgabe konfiguriert werden muss, müssen Sie die Dolast -Version weiterhin verwenden.
Grammatik
Gradle -Skripte sind in der groovigen Sprache geschrieben. Groovys Syntax ist ein bisschen wie Java, ich hoffe, Sie können es akzeptieren.
Wenn Sie bereits mit Groovy vertraut sind, können Sie diesen Teil überspringen.
Es gibt ein sehr wichtiges Konzept in Groovy, das Sie verstehen müssen, um den Verschluss zu verstehen (Verschluss)
Schließungen
Schließung ist der Schlüssel zu unserem Verständnis von Gradle. Der Verschluss ist ein separater Codeblock, der Parameter empfangen, Werte oder Variablen zugewiesen werden kann. Es ähnelt der Callable -Schnittstelle in Java und Zukunft und ist auch wie ein Funktionszeiger, der leicht zu verstehen ist. . .
Der Schlüssel ist, dass dieser Code ausgeführt wird, wenn Sie ihn aufrufen, nicht, wenn er erstellt wird. Siehe ein Beispiel für den Schließen:
Def MycLosed = {println 'Hallo Welt!' } // Führen Sie unsere Schließung aus ()#Ausgabe: Hallo Welt!Hier ist eine Schließung, die Parameter empfängt:
Def MycLosure = {String str -> println str} // Führen Sie unsere Schließung aus ('Hallo Welt!')#Ausgabe: Hallo Welt!Wenn der Verschluss nur einen Parameter empfängt, können Sie diesen Parameter verweisen:
Def MycLoScusion = {println it} // Führen Sie unsere Schließung aus ('Hallo Welt!')#Ausgabe: Hallo Welt!Schließung, der mehrere Parameter empfängt:
Def MycLosen
Zusätzlich ist der Typ des Parameters optional und das obige Beispiel kann wie folgt abgekürzt werden:
Def MycLosen
Cool ist, dass Variablen aus dem aktuellen Kontext im Verschluss verwendet werden können. Standardmäßig ist der aktuelle Kontext die Klasse, in der die Schließung erstellt wurde:
def myvar = 'Hallo Welt!' def MycLosen- = {println myvar} mycLosure ()#output: Hallo Welt!Ein weiterer cooler Punkt ist, dass der Kontext des Verschlusses über den Verschluss#setDelegate () geändert werden kann. Diese Funktion ist sehr nützlich:
Def MycLosure = {println myvar} // Ich beziehe mich auf myvar aus mycass classmyclass m = new myclass () myclosure.setDelegate (m) mycLosenge () Klasse MyClass {def myvar = 'Hallo aus MyClass!'} Ausgabe: Hallo aus Myclass!Wie Sie sehen können, existiert MyVar bei der Erstellung von Schließungen nicht. Es gibt kein Problem damit, denn wenn wir den Verschluss ausführen, existiert MyVar im Zusammenhang mit der Schließung. In diesem Beispiel. Weil ich seinen Kontext in m geändert habe, bevor ich den Schließung durchführt, existiert MyVar.
Verschluss als Parameter
Der Vorteil der Schließung besteht darin, dass es an verschiedene Methoden übergeben werden kann, was uns helfen kann, die Ausführungslogik zu entkoppeln. Im vorherigen Beispiel habe ich gezeigt, wie ich die Schließung an eine Instanz einer Klasse übergeben kann. Im Folgenden werden wir uns verschiedene Methoden ansehen, die Schließungen als Parameter erhalten:
1. Empfangen Sie nur einen Parameter und der Parameter ist die Verschlussmethode: MyMethod (MycLosure)
2. Wenn die Methode nur einen Parameter empfängt, können Klammern weggelassen werden: MyMethod Myclose
3.. Sie können Inline -Verschluss verwenden: MyMethod {println 'Hello World'}
4. Methode zum Empfang von zwei Parametern: MyMethod (Arg1, MycLosure)
5. Ähnlich 4 ist der einzigartige Verschluss inline: MyMethod (arg1, {println 'Hello World'})
6. Wenn der letzte Parameter Schließung ist, kann er aus Klammern herausgenommen werden: MyMethod (arg1) {println 'Hello World'}
Hier möchte ich Sie nur daran erinnern, ob das Schreiben von 3 und 6 vertraut aussieht.
Gradle -Beispiel
Nachdem wir die grundlegende Syntax verstanden haben, wie verwenden wir sie in Gradle -Skripten? Schauen wir uns das folgende Beispiel an:
Buildscript {Repositories {jcenter ()} Abhängigkeiten {classPath 'com.android.tools.build:gradle:1.2.3'}} AllProjects {Repositories {jCenter ()}} Ist es leicht, das obige Beispiel zu verstehen, wenn Sie die Syntax von Groovy kennen?
Erstens ist eine Buildscript -Methode, die eine Schließung erhält:
Def Buildscript (Schließungsverschluss)
Als nächstes kommt die AllProjects -Methode, die auch einen Verschlussparameter empfängt:
Def AllProjects (Verschlussabschluss)
Die anderen sind ähnlich. . .
Es scheint jetzt viel einfacher, aber ich verstehe keine Sache, das heißt, wo sind diese Methoden definiert? Die Antwort ist das Projekt
Projekt
Dies ist ein Schlüssel zum Verständnis von Gradle -Skripten.
Die Anweisungsblöcke auf der obersten Ebene des Build -Skripts werden an Projektinstanzen delegiert, was zeigt, dass das Projekt genau dort ist, wo ich suche.
Durch die Suche nach der Buildscript -Methode auf der Dokumentseite des Projekts finden Sie den Buildscript {} -Skriptblock (Skriptblock). usw. Was zum Teufel ist Skriptblock? Nach der Dokumentation:
Skriptblock ist eine Methode, die nur Schließung als Parameter empfängt. Lesen Sie die Buildscript -Dokumentation fort. Das Dokument besagt, dass Delegierte an: ScriptHandler aus Buildscript. Das heißt, wir übergeben die Schließung an die Buildscript -Methode, und der endgültige Ausführungskontext ist der Skripthandler. Im obigen Beispiel wurde unser Schließen an Buildscript -Aufrufe der Repositories (Verschluss) und Abhängigkeiten (Verschluss) übergeben. Da Scripthandler dem Verschluss anvertraut wurde, werden wir nach Abhängigkeiten in ScriptHandler suchen.
Es wurden Hohlraumabhängigkeiten (Verschlusskonfiguration) gefunden. Gemäß der Dokumentation werden Abhängigkeiten verwendet, um Skriptabhängigkeiten zu konfigurieren. Und Abhängigkeiten wurden schließlich der Abhängigkeitshandler anvertraut.
Ich habe gesehen, wie weit verbreitet Gradles sind. Das Verständnis der Verantwortung ist sehr wichtig.
Skriptblöcke
Standardmäßig sind viele Skriptblöcke im Projekt vordefiniert, aber das Gradle -Plugin ermöglicht es uns, neue Skriptblöcke selbst zu definieren!
Dies bedeutet, dass, wenn Sie einige {…} auf der oberen Ebene des Build -Skripts veröffentlichen, diesen Skriptblock oder diese Methode in den Dokumentation von Gradle jedoch in den meisten Fällen nicht finden können. Dies sind einige Skriptblöcke, die im Plugin definiert sind.
Android -Skriptblock
Schauen wir uns die Standard -Android -App/Build -Datei an.
apply plugin: 'com.android.application'android { compileSdkVersion 22 buildToolsVersion "22.0.1" defaultConfig { applicationId "com.trickyandroid.testapp" minSdkVersion 16 targetSdkVersion 22 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile ('proguard-android.txt'), 'proguard-rules.pro'}}}Aufgabenreihenfolge
Ich bemerkte, dass die meisten Probleme, die ich bei der Verwendung von Gradle begegnet bin, mit der Ausführungsreihenfolge der Aufgabe zusammenhängen. Wenn mein Build besser funktionieren würde, wenn meine Aufgabe zum richtigen Zeitpunkt ausgeführt würde. Schauen wir uns genauer an, wie Sie die Ausführungsreihenfolge der Aufgaben ändern können.
abhängig
Ich denke, der direkteste Weg, um die Art und Weise zu erklären, wie Sie sich bei der Ausführung Ihrer Aufgabe auf andere Aufgaben verlassen, besteht darin, die Abhängigkeitsmethode zu verwenden.
Zum Beispiel existiert im folgenden Szenario eine Aufgabe A. Wir möchten eine Aufgabe B hinzufügen, und seine Ausführung muss sein, nachdem A ausgeführt wurde:
Dies ist ein sehr einfaches Szenario, vorausgesetzt, die Definition von A und B lautet wie folgt:
Aufgabe A << {println 'Hallo aus a'} Aufgabe B << {println 'Hallo aus B'} Nennen Sie einfach B. DePendentson A und das ist in Ordnung.
Dies bedeutet, dass Aufgabe A, solange ich Aufgabe B ausführe, zuerst ausführen wird.
paveldudka $ gradle b: ahello von a: bhello von b
Darüber hinaus können Sie seine Abhängigkeiten im Aufgabenkonfigurationsbereich deklarieren:
Aufgabe A << {println 'Hallo aus A'} Aufgabe B {Depel a Dolast {println 'Hallo aus B'}} Was ist, wenn wir unsere Aufgabe in eine vorhandene Aufgabenabhängigkeit einfügen möchten?
Der Prozess ähnelt dem, was gerade jetzt ist. Angenommen, es gibt bereits die folgenden Aufgabenabhängigkeiten:
Aufgabe A << {println 'Hallo aus A'} Aufgabe B << {println 'Hallo aus B'} Aufgabe c << {println 'Hallo aus C'} b.DependSon ac.Dependenton BTreten Sie unserer neuen Aufgabe bei
Aufgabe B1 << {println 'Hallo aus B1'} b1.dependson bc.dependson b1Ausgabe:
Paveldudka $ Gradle C: Ahello von A: Bhello von B: B1hello von B1: Chello von C.
Beachten Sie, dass Depellson der abhängigen Sammlung Aufgaben hinzufügt. Daher ist es kein Problem, sich auf mehrere Aufgaben zu verlassen.
Aufgabe B1 << {println 'Hallo aus B1'} B1.Dependson Bb1.Dependson Q.Ausgabe:
Paveldudka $ Gradle B1: Ahello von A: Bhello von B: Qhello von q: B1hello von B1
Musttrunafter
Nehmen wir nun an, ich habe eine andere Aufgabe, die von den beiden anderen Aufgaben abhängt. Hier benutze ich ein echtes Szenario, in dem ich zwei Aufgaben, eine getestete Aufgabe und eine UI -getestete Aufgabe habe. Es gibt auch eine Aufgabe, die alle Tests ausführt, die von den beiden vorherigen Aufgaben abhängt.
Task -Einheit << {println 'Hallo aus Unit -Tests'} Task UI << {println 'Hallo aus UI -Tests'} Task -Tests << {println 'Hallo aus allen Tests!'} tests.dependson unittests.dependson uiAusgabe:
Paveldudka $ Gradle -Tests: UIHello von UI -Tests: Unithello aus Unit -Tests: Testshello aus allen Tests!
Obwohl der Unitest- und UI -Test vor der Testaufgabe ausgeführt wird, kann die Ausführungsreihenfolge von Einheit und UI nicht garantiert werden. Obwohl es jetzt in der Reihenfolge des Alphabets ausgeführt wird, hängt dies von der Implementierung von Gradle ab, und Sie dürfen sich nicht auf diese Reihenfolge in Ihrem Code verlassen.
Da die UI -Testzeit viel länger als die Unit -Testzeit ist, möchte ich, dass der Unit -Test zuerst durchgeführt wird. Eine Lösung besteht darin, die UI -Aufgabe von der Einheitsaufgabe abhängt.
Task-Einheit << {println 'Hallo aus Unit-Tests'} Task UI << {println 'Hallo aus UI-Tests'} Task-Tests << {println 'Hallo aus allen Tests!'} tests.dependson unittests.dependson uiui.Dependson-Einheit // <- Ich habe hinzugefügt.Ausgabe:
Paveldudka $ Gradle -Tests: Unithello aus Unit -Tests: UIHello von UI -Tests: Testshello aus allen Tests!
Jetzt wird der Unit -Test vor dem UI -Test durchgeführt.
Aber hier gibt es ein sehr ekelhaftes Problem. Mein UI -Test beruht tatsächlich nicht auf Unit -Test. Ich hoffe, dass ich den UI -Test separat ausführen kann, aber hier werde ich jedes Mal, wenn ich den UI -Test ausführe, zuerst den Unit -Test durchführen.
Musttrunafter wird hier benötigt. Mustrunafter fügt keine Abhängigkeiten hinzu, sondern zeigt Gradle nur die Priorität der Ausführung, wenn zwei Aufgaben gleichzeitig vorhanden sind. Zum Beispiel können wir hier die UI.mustrunafter -Einheit angeben. Wenn die UI -Aufgabe und die Aufgabe der Einheit gleichzeitig vorhanden sind, führt Gradle zuerst den Unit -Test aus, und wenn nur Gradle UI ausgeführt wird, wird die Einheitsaufgabe nicht ausgeführt.
Task -Einheit << {println 'Hallo aus Unit -Tests'} Task Ui << {println 'Hallo aus UI -Tests'} Task -Tests << {println 'Hallo aus allen Tests!'} tests.dependson unittests.dependson uiui.mustrunafter EinheitAusgabe:
Paveldudka $ Gradle -Tests: Unithello aus Unit -Tests: UIHello von UI -Tests: Testshello aus allen Tests!
Die Abhängigkeitsbeziehung lautet wie folgt:
Mustrunafter ist derzeit ein experimentelles Merkmal in Gradle 2.4.
abgeschlossen
Jetzt haben wir zwei Aufgaben, Einheit und Benutzeroberfläche, vorausgesetzt, beide Aufgaben werden Testberichte ausgeben. Jetzt möchte ich diese beiden Testberichte in einen zusammenführen:
task unit << {println 'Hello from unit tests'}task ui << {println 'Hello from UI tests'}task tests << {println 'Hello from all tests!'}task mergeReports << {println 'Merging test reports'}tests.dependsOn unittests.dependsOn uiui.mustRunAfter unitmergeReports.dependsOn testsWenn ich nun Testberichte für Benutzeroberfläche und Einheit erhalten möchte, führen Sie Task Mergerports aus.
Paveldudka $ Gradle Mergereports: Unithello aus Unit -Tests: UIHello aus UI -Tests: Testshello aus allen Tests!: MergerePortsmerging -Testberichte
Diese Aufgabe funktioniert, aber sie sieht so dumm aus. Mergerports fühlt sich aus der Sicht eines Benutzers nicht besonders gut an. Ich möchte die Testaufgabe ausführen, um den Testbericht zu erhalten, ohne die Existenz von Mergereports kennen zu müssen. Natürlich kann ich die Merge -Logik in die Testaufgabe verschieben, aber ich möchte die Testaufgabe nicht zu aufblähen lassen, daher werde ich die Merge -Logik weiterhin in die Mergerports -Aufgabe einfügen.
FinalizeBy ist hier, um die Szene zu retten. Wie der Name schon sagt, ist FinalizeBy die Aufgabe, nach der Ausführung der Aufgabe ausgeführt zu werden. Ändern Sie unser Skript wie folgt:
task unit << {println 'Hello from unit tests'}task ui << {println 'Hello from UI tests'}task tests << {println 'Hello from all tests!'}task mergeReports << {println 'Merging test reports'}tests.dependsOn unittests.dependsOn uiui.mustRunAfter unitmergeReports.dependsOn tests.finalized by mergereportsFühren Sie nun die Testaufgabe aus und Sie können den Testbericht erhalten:
Paveldudka $ Gradle -Tests: Unithello aus Unit -Tests: UIHello von UI -Tests: Testshello aus allen Tests!: MergerePortsmerging -Testberichte