tâches
Le code suivant montre trois tâches Gradle, et nous expliquerons les différences entre ces trois plus tard.
tâche mytask {println "Bonjour, monde!" } tâche mytask {Dolast {println "Bonjour, monde!" }} tâche mytask << {println "Bonjour, monde!" }Mon objectif est de créer une tâche qui imprime "Bonjour, monde!" quand il s'exécute. Lorsque j'ai créé la tâche pour la première fois, j'ai deviné que cela devrait être écrit comme ceci:
tâche mytask {println "Bonjour, monde!" }Maintenant, essayez d'exécuter ce mytask, entrez Gradle Mytask sur la ligne de commande et imprimez-le comme suit:
Utilisateur $ Gradle Mytask Hello, World! : Mytask à jour
Cette tâche semble fonctionner. Il imprime "Bonjour, monde!".
Cependant, ce n'est pas vraiment ce à quoi nous nous attendions. Jetons un coup d'œil à pourquoi. Entrez les tâches Gradle sur la ligne de commande pour afficher toutes les tâches disponibles.
Utilisateur $ gradle tâches Bonjour, monde! : Tâches ---------------------------------------------------------------- Toute les tâches passant à partir du projet racine -------------------------------------------------------- Build Tasks Configuration --------------------- INIT - Initialise une nouvelle version Gradle. [incubation] ..........
Attendez, pourquoi est "bonjour, monde!" imprimé? Je voulais juste voir quelles tâches sont disponibles et je n'ai exécuté aucune tâche personnalisée!
La raison est en fait très simple. Gradle Task a deux étapes principales dans son cycle de vie: l'étape de configuration et l'étape d'exécution.
Peut-être que mes mots ne sont pas très précis, mais cela peut vraiment m'aider à comprendre les tâches.
Gradle doit configurer la tâche avant de l'exécuter. La question est alors de savoir comment savoir quel code de ma tâche est exécuté pendant le processus de configuration et quel code est exécuté lorsque la tâche est exécutée? La réponse est que le code au niveau supérieur de la tâche est le code de configuration, tel que:
tâche mytask {def name = "pavel" // <- Cette ligne de code exécutera println "Bonjour, monde!" //// <- Cette ligne de code s'exécutera également dans l'étape de configuration}C'est pourquoi lorsque j'exécute des tâches Gradle, j'imprime "Hello, World!" - Parce que le code de configuration est exécuté. Mais ce n'est pas l'effet que je veux, je veux "bonjour, monde!" Pour l'imprimer uniquement lorsque j'appelle explicitement MyTask. Pour réaliser cet effet, le moyen le plus simple est d'utiliser la méthode Task # Dolast ().
tâche mytask {def text = 'bonjour, monde!' // Configure ma tâche Dolast {println text // Ceci est exécuté lorsque ma tâche est appelée}}Maintenant, "Bonjour, monde!" ne sera imprimé que lorsque j'exécute Gradle Mytask. Cool, maintenant je sais comment configurer et faire faire la tâche faire la bonne chose. Il y a une autre question. Dans l'exemple initial, que signifie le symbole << de la troisième tâche?
tâche mytask2 << {println "Bonjour, monde!" }Il s'agit en fait d'une version de sucre syntaxique de Dolast. Il a le même effet que la méthode d'écriture suivante:
tâche mytask {Dolast {println 'bonjour, monde!' // Ceci est exécuté lorsque ma tâche est appelée}}Cependant, tous les codes de cette manière sont exécutés et il n'y a pas de partie de configuration du code, ils conviennent donc plus à ces tâches simples qui ne nécessitent pas de configuration. Une fois que votre tâche doit être configurée, vous devez toujours utiliser la version Dolast.
grammaire
Les scripts Gradle sont écrits dans la langue groovy. La syntaxe de Groovy est un peu comme Java, j'espère que vous pourrez l'accepter.
Si vous connaissez déjà Groovy, vous pouvez ignorer cette partie.
Il y a un concept très important dans Groovy que vous devez comprendre la fermeture (fermeture)
Fermetures
La fermeture est la clé de notre compréhension de Gradle. La fermeture est un bloc de code distinct qui peut recevoir des paramètres, renvoyer des valeurs ou être affectés aux variables. Il est similaire à l'interface appelable en Java et Future, et c'est aussi comme un pointeur de fonction, ce qui est facile à comprendre. . .
La clé est que ce code sera exécuté lorsque vous l'appelez, pas lorsqu'il est créé. Voir un exemple de fermeture:
Def myClosure = {println 'Hello World!' } // Exécuter notre ClosingClosure () # Output: Hello World!Voici une fermeture qui reçoit des paramètres:
def myClosure = {String str -> println str} // Exécuter notre clôture ('Hello World!') # Output: Hello World!Si la fermeture ne reçoit qu'un seul paramètre, vous pouvez l'utiliser pour référencer ce paramètre:
def myClosure = {println it} // Exécuter notre clôture ('Hello World!') # Output: Hello World!Fermeture qui reçoit plusieurs paramètres:
def myClosure = {String str, int num -> println "$ str: $ num"} // exécuter notre rénovationyclosure ('ma chaîne', 21) #Output: ma chaîne: 21De plus, le type de paramètre est facultatif, et l'exemple ci-dessus peut être abrégé comme suit:
def myClosure = {str, num -> println "$ str: $ num"} // exécuter notre clôture ('ma chaîne', 21) #Output: ma chaîne: 21Ce qui est cool, c'est que les variables du contexte actuel peuvent être utilisées dans la fermeture. Par défaut, le contexte actuel est la classe où la fermeture a été créée:
def myvar = 'Hello World!' def myClosure = {println myvar} myclosure () # output: Hello World!Un autre point sympa est que le contexte de la fermeture peut être modifié, via la fermeture # setDelegate (). Cette fonctionnalité est très utile:
def myClosure = {println myvar} // je fais référence à myvar de MyClass classmyclass m = new myclass () myclosure.setdelegate (m) myClosure () class myClass {def myvar = 'bonjour de myClass!'} # Sortie: bonjour de myclass!Comme vous pouvez le voir, MyVar n'existe pas lors de la création de fermeture. Il n'y a aucun problème avec cela car lorsque nous exécutons la fermeture, Myvar existe dans le contexte de la fermeture. Dans cet exemple. Parce que j'ai changé son contexte en m avant d'exécuter la fermeture, Myvar existe.
Passer la fermeture en tant que paramètre
L'avantage de la fermeture est qu'il peut être transmis à différentes méthodes, ce qui peut nous aider à découpler la logique d'exécution. Dans l'exemple précédent, j'ai montré comment passer la fermeture à une instance d'une classe. Ci-dessous, nous examinerons diverses méthodes qui reçoivent la fermeture en tant que paramètres:
1. Recevoir un seul paramètre et le paramètre est la méthode de fermeture: MyMethod (myClosure)
2. Si la méthode ne reçoit qu'un seul paramètre, les parenthèses peuvent être omises: MyMethod Myclosure
3. Vous pouvez utiliser la fermeture en ligne: MyMethod {println 'Hello World'}
4. Méthode pour recevoir deux paramètres: MyMethod (Arg1, MyClosure)
5. Semblable à 4, la fermeture singulière est en ligne: MyMethod (arg1, {println 'Hello World'})
6. Si le dernier paramètre est la fermeture, il peut être retiré des supports: MyMethod (Arg1) {println 'Hello World'}
Ici, je veux juste vous rappeler si l'écriture de 3 et 6 semble familière?
Gradle Exemple
Maintenant que nous avons compris la syntaxe de base, comment l'utiliser dans les scripts Gradle? Jetons un coup d'œil à l'exemple suivant:
buildScript {Repositories {jCenter ()} dépendances {classpath 'com.android.tools.build:gradle:1.2.3'}} AllProjects {Repositories {jCenter ()}} Une fois que vous connaissez la syntaxe de Groovy, est-il facile de comprendre l'exemple ci-dessus?
Le premier est une méthode BuildScript, qui reçoit une fermeture:
Def Buildscript (fermeture de fermeture)
Ensuite, la méthode AllProjects, qui reçoit également un paramètre de fermeture:
def allprojects (fermeture de fermeture)
Les autres sont similaires. . .
Cela semble beaucoup plus facile maintenant, mais je ne comprends pas une chose, c'est-à-dire où ces méthodes sont-elles définies? La réponse est le projet
Projet
C'est une clé pour comprendre les scripts Gradle.
Les blocs d'instruction au niveau supérieur du script de construction seront délégués aux instances de projet, ce qui montre que le projet est exactement là où je recherche.
La recherche de la méthode BuildScript sur la page de document du projet trouvera le bloc de script BuildScript {} (bloc de script). etc. Qu'est-ce que c'est que le bloc de script? Selon la documentation:
Le bloc de script est une méthode qui ne reçoit que la fermeture en tant que paramètre. Continuez à lire la documentation BuildScript. Le document indique que les délégués à: Scripthandler de BuildScript. Autrement dit, nous passons la fermeture à la méthode BuildScript, et le contexte d'exécution final est le scripthandler. Dans l'exemple ci-dessus, notre fermeture transmise à BuildScript appelle les référentiels (fermeture) et les méthodes de dépendances (fermeture). Étant donné que la fermeture a été confiée à Scripthandler, nous rechercherons des méthodes de dépendances dans Scripthandler.
Des dépendances vides (configuration de fermeture) ont été trouvées. Selon la documentation, les dépendances sont utilisées pour configurer les dépendances de script. Et les dépendances ont finalement été confiées au DependencyHandler.
J'ai vu à quel point Gradles est largement utilisé. Il est très important de comprendre la condamnation.
Blocs de script
Par défaut, de nombreux blocs de scripts sont prédéfinis dans le projet, mais le plugin Gradle nous permet de définir de nouveaux blocs de script nous-mêmes!
Cela signifie que si vous publiez des {…} au niveau supérieur du script de construction, mais que vous ne trouvez pas ce bloc ou méthode de script dans la documentation de Gradle, dans la plupart des cas, il s'agit de blocs de script définis dans le plugin.
bloc de script Android
Jetons un coup d'œil au fichier Android App / build.gradle par défaut:
Appliquer le plugin: 'com.android.application'Android {compilesDkversion 22 buildToolsVersion "22.0.1" DefaultConfig {applicationID "com.trickyandroid.testApp" MINSDKVersion 16 TargetsDkversion 22 version Version 1 version "1.0"} BuildTypeS {release {MinifyEnabled False Proving Provarardfiles getDefaultProGuardFile ('Proguard-android.txt'), 'Proguard-Rules.pro'}}}Ordre des tâches
J'ai remarqué que la plupart des problèmes que j'ai rencontrés lors de l'utilisation de Gradle sont liés à l'ordre d'exécution de la tâche. De toute évidence, si ma construction fonctionnerait mieux si ma tâche est exécutée au bon moment. Examinons de plus près comment modifier l'ordre d'exécution des tâches.
dépendons
Je pense que le moyen le plus direct d'expliquer la façon dont vous comptez sur d'autres tâches lors de l'exécution de votre tâche est d'utiliser la méthode DependSon.
Par exemple, dans le scénario suivant, la tâche A existe déjà. Nous voulons ajouter une tâche B, et son exécution doit être après l'exécution de A:
Il s'agit d'un scénario très simple, en supposant que la définition de A et B est la suivante:
Tâche A << {println 'Hello from a'} tâche b << {println 'bonjour de b'} Appelez simplement B.Dependantson A et c'est bien.
Cela signifie que tant que j'exécute la tâche B, la tâche A exécutera d'abord.
paveldudka $ gradle b: ahello de a: bhello de b
De plus, vous pouvez déclarer ses dépendances dans la zone de configuration des tâches:
Tâche A << {println 'Hello From A'} Tâche b {DelateSon A Dolast {println 'Hello From B'}} Et si nous voulons insérer notre tâche dans une dépendance à la tâche existante?
Le processus est similaire à ce qui est tout à l'heure. Supposons que les dépendances de tâches suivantes existent déjà:
Tâche A << {println 'Hello From A'} Task B << {println 'Hello From B'} Tâche C << {println 'Hello From C'} B.Dependmentson AC.Dependantson BRejoignez notre nouvelle tâche
tâche b1 << {println 'bonjour de b1'} b1.dependson bc.DependSon b1Sortir:
paveldudka $ gradle C: Ahello de a: bhello de b: b1hello de b1: chello de c
Notez que DepenSon ajoute des tâches à la collection dépendante, il n'est donc pas un problème de s'appuyer sur plusieurs tâches.
tâche b1 << {println 'bonjour de b1'} b1.dependson bb1.dependSon qSortir:
paveldudka $ gradle b1: ahello de a: bhello de b: qhello de q: b1hello de b1
mutiner
Supposons maintenant que j'ai une autre tâche, qui dépend des deux autres tâches. Ici, j'utilise un vrai scénario où j'ai deux tâches, une tâche testée unité et une tâche testée sur l'interface utilisateur. Il y a aussi une tâche qui exécute tous les tests, ce qui dépend des deux tâches précédentes.
Unité de tâche << {println 'Hello From Unit Tests'} Task ui << {println 'Hello From UI Tests'} Task Tests << {println 'bonjour de tous les tests!Sortir:
Paveldudka $ Gradle Tests: Uihello à partir des tests d'interface utilisateur: Unithello des tests unitaires: TestShello de tous les tests!
Bien que le test Unist et UI s'exécutera avant la tâche de test, l'ordre d'exécution de l'unité et de l'interface utilisateur ne peut pas être garanti. Bien qu'il soit exécuté dans l'ordre de l'alphabet maintenant, cela dépend de l'implémentation de Gradle, et vous ne devez pas vous fier à cet ordre dans votre code.
Étant donné que le temps de test de l'interface utilisateur est beaucoup plus long que le temps de test unitaire, je veux que le test unitaire soit exécuté en premier. Une solution consiste à faire en sorte que la tâche d'interface utilisateur dépend de la tâche unitaire.
Unité des tâches << {println 'Hello From Unit Tests'} Task Ui << {println 'Hello From UI Tests'} Task Tests << {println 'Hello From All Tests!'} Tests.DependSon UNITTESTS.DependSon UIUI.DependSon Unit // <- J'ai ajouté cette dépendance.Sortir:
Paveldudka $ Gradle Tests: Unithello des tests unitaires: Uihello à partir des tests d'interface utilisateur: TestShello de tous les tests!
Maintenant, le test unitaire sera exécuté avant le test d'interface utilisateur.
Mais il y a un problème très dégoûtant ici. Mon test d'interface utilisateur ne s'appuie pas en fait sur le test unitaire. J'espère pouvoir exécuter un test d'interface utilisateur séparément, mais ici chaque fois que j'exécute le test d'interface utilisateur, je vais d'abord exécuter le test unitaire.
Mustrunafter est nécessaire ici. Mustrunafter n'ajoute pas de dépendances, il indique à Gradle la priorité de l'exécution si deux tâches existent en même temps. Par exemple, nous pouvons spécifier l'unité UI.Mustrunafter ici. De cette façon, si la tâche d'interface utilisateur et la tâche unitaire existent en même temps, Gradle exécutera d'abord le test unitaire, et si seulement Gradle UI est exécuté, la tâche unitaire ne sera pas exécutée.
Unité de tâche << {println 'Hello From Unit Tests'} Task UI << {println 'Hello From UI Tests'} Task Tests << {println 'Hello From All Tests!'} Tests.DependSon UNITTESTS.DEPENDSON UIUI.MUSTUNAFTER UNITSortir:
Paveldudka $ Gradle Tests: Unithello des tests unitaires: Uihello à partir des tests d'interface utilisateur: TestShello de tous les tests!
La relation de dépendance est la suivante:
Mustrunafter est actuellement une caractéristique expérimentale de Gradle 2.4.
finalisé par
Nous avons maintenant deux tâches, unité et une interface utilisateur, en supposant que les deux tâches publieront des rapports de test, maintenant je souhaite fusionner ces deux rapports de test en un seul:
Unité des tâches << {println 'Hello From Unit Tests'} tâche UI << {println 'Hello From UI Tests'} Task Tests << {println 'Hello From Tests!'} Task Mergereports << {println 'Merging Test Reports'} Tests.DependSson Unittests.DependSon UIUI.MustRunaMaintenant, si je souhaite obtenir des rapports de test pour l'interface utilisateur et l'unité, exécutez les tâches Mergereports.
Paveldudka $ gradle Mergereports: Unithello des tests unitaires: Uihello à partir des tests d'interface utilisateur: TestShello de tous les tests!: Mergereportsmerging Test Reports
Cette tâche fonctionne, mais elle a l'air si stupide. Mergereports ne se sent pas particulièrement bien du point de vue d'un utilisateur. Je souhaite exécuter la tâche de tests pour obtenir le rapport de test sans avoir à connaître l'existence de Mergereports. Bien sûr, je peux déplacer la logique de fusion dans la tâche des tests, mais je ne veux pas rendre la tâche des tests trop gonflée, donc je continuerai à mettre la logique de fusion dans la tâche Mergereports.
Finalizey est là pour sauver la scène. Comme son nom l'indique, FinalizeBy est la tâche à exécuter après l'exécution de la tâche. Modifiez notre script comme suit:
Unité de tâche << {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.DependSson Unittests.DependSon UIUI.MustRuna Tests.Finalized par MergereportsExécutez maintenant la tâche des tests et vous pouvez obtenir le rapport de test:
Paveldudka $ Gradle Tests: Unithello à partir des tests unitaires: Uihello à partir des tests d'interface utilisateur: TestShello de tous les tests !: Mergereportsmerging Test Reports