Gradle Cheat Sheet

Il me semble que la plupart des gens ne commencent à travailler avec Gradle que lorsque quelque chose doit être ajouté au projet ou que quelque chose se brise soudainement - et après avoir résolu le problème "acquis par le surmenage", l'expérience est oubliée en toute sécurité. De plus, de nombreux exemples sur Internet sont similaires à des sorts hautement spécialisés qui n'ajoutent pas à la compréhension de ce qui se passe:


android { compileSdkVersion 28 defaultConfig { applicationId "com.habr.hello" minSdkVersion 20 targetSdkVersion 28 } buildTypes { release { minifyEnabled false } } } 

Je ne vais pas décrire en détail à quoi sert chaque ligne ci-dessus - ce sont des détails privés de la mise en œuvre du plugin Android. Il y a quelque chose de plus précieux - une compréhension de la façon dont tout est organisé. Les informations sont dispersées sur divers sites / documentation officielle / sources du berceau et des plugins pour celui-ci - en général, c'est un peu plus de connaissance universelle que je ne veux pas oublier.


Le texte suivant peut être considéré comme une feuille de triche pour ceux qui maîtrisent le gradle ou qui ont déjà oublié.


Liens utiles


  • La documentation officielle est assez volumineuse, mais Ă  certains endroits, il peut y avoir un manque de dĂ©tails.
  • codes sources sur github , javadoc - en raison de la frappe dynamique dans groovy, l'environnement de dĂ©veloppement est loin d'ĂŞtre toujours en mesure de donner une liste des champs / mĂ©thodes disponibles, et par des noms courts de mĂ©thodes et types d'arguments (fermeture de fermeture), il n'est pas toujours possible de comprendre pourquoi ils sont nĂ©cessaires.
  • un article sur un hub avec un tas d'exemples est une traduction du deuxième chapitre du livre "Construire et tester avec gradle". Le livre peut Ă©galement ĂŞtre lu, il est disponible gratuitement.
  • un autre article - sur buildSrc

La console


Android studio / IDEA cache minutieusement les commandes gradle du développeur, et même lors de la modification des fichiers build.gradle, il commence à être stupide ou à redémarrer le projet.


Dans de tels cas, appeler gradle depuis la console est beaucoup plus facile et plus rapide. Le wrapper de grappin est généralement fourni avec le projet et fonctionne correctement dans linux / macos / windows, à moins que dans ce dernier, vous ayez besoin d'appeler le fichier bat au lieu du wrapper.


Tâches de défi


 ./gradlew tasks 

écrit les tâches disponibles.


 ./gradlew subprojectName:tasks --all 

Vous pouvez afficher les tâches d'un sous-projet distinct, et même avec l'option --all , toutes les tâches, y compris les tâches secondaires, seront affichées.


Vous pouvez appeler n'importe quelle tâche et toutes les tâches dont elle dépend seront appelées.


 ./gradlew app:assembleDevelopDebug 

Si vous êtes trop paresseux pour écrire le nom complet, vous pouvez jeter des petites lettres:


 ./gradlew app:assembleDD 

Si la grêle ne peut pas deviner clairement quelle tâche elle avait en tête, elle affichera une liste d'options appropriées.


Journalisation


La quantité d'informations affichées dans la console lors du démarrage d'une tâche dépend fortement du niveau de journalisation.
En plus de celui par défaut, il existe -q, -w, -i, -d , bien, ou --quiet, --warn, --info, --debug dans une quantité croissante d'informations. Sur les projets complexes, la sortie avec -d peut prendre plus d'un mégaoctet, et il est donc préférable de l'enregistrer immédiatement dans un fichier et d'y regarder en recherchant des mots clés:


 ./gradlew app:build -d > myLog.txt 

Si une exception est levée quelque part, l'option -s pour stacktrace.


Vous pouvez écrire vous-même dans le journal:


 logger.warn('A warning log message.') 

l'enregistreur est une implémentation de SLF4J.


Groovy


Ce qui se passe dans les fichiers build.gradle est juste du code groovy.


Pour une raison quelconque, Groovy, en tant que langage de programmation, n'est pas très populaire, bien qu'il me semble qu'il mérite en soi au moins une petite étude. La langue est née en 2003 et s'est lentement développée. Caractéristiques intéressantes:


  • Presque n'importe quel code java est un code groovy valide. Cela aide beaucoup Ă  Ă©crire intuitivement du code de travail.
  • SimultanĂ©ment, le typage statique et dynamique est pris en charge dans le groove, au lieu de String a = "a" vous pouvez Ă©crire en toute sĂ©curitĂ© def a = "a" ou mĂŞme def map = ['one':1, 'two':2, 'list' = [1,false]]
  • Il existe des fermetures pour lesquelles vous pouvez dĂ©terminer dynamiquement le contexte d'exĂ©cution. Ces mĂŞmes blocs android {...} prennent des fermetures puis les exĂ©cutent pour un objet.
  • Il y a interpolation des chaĂ®nes "$a, ${b}" , des chaĂ®nes multilignes """yep, ${c}""" , et les chaĂ®nes java ordinaires sont encadrĂ©es par des guillemets simples: 'text'
  • Il y a un semblant de mĂ©thodes d'extension. La collection de langues standard a dĂ©jĂ  des mĂ©thodes comme any, every, each, findAll. Personnellement, les noms des mĂ©thodes me semblent inhabituels, mais l'essentiel est qu'ils le sont .
  • DĂ©licieux sucre syntaxique, le code est beaucoup plus court et plus simple. Vous n'avez pas besoin d'Ă©crire des crochets autour des arguments de la fonction, pour la dĂ©claration des listes et des [a,b,c], [key1: value1, key2: value2] hachage [a,b,c], [key1: value1, key2: value2] syntaxe agrĂ©able est: [a,b,c], [key1: value1, key2: value2]

En général, pourquoi des langues comme Python / Javascript ont monté en flèche et Groovy pas - c'est un mystère pour moi. Pour l'époque, alors qu'il n'y avait même pas de lambdas en java, et que des alternatives comme kotlin / scala apparaissaient ou n'existaient pas encore, Groovy devait ressembler à une langue vraiment intéressante.


C'est la flexibilité de la syntaxe groovy et du typage dynamique qui nous a permis de créer des DSL concis en gradle.


Maintenant, dans la documentation officielle de Gradle, des exemples sont dupliqués sur Kotlin, et il semble qu'il soit prévu de passer à celui-ci, mais le code ne semble plus aussi simple et ressemble davantage à du code normal:


 task hello { doLast { println "hello" } } 

vs


 tasks.register("hello") { doLast { println("hello") } } 

Cependant, le changement de nom à Kradle n'est pas encore prévu.


Étapes d'assemblage


Ils sont divisés en initialisation, configuration et exécution.


L'idée est que gradle collecte un graphe de dépendance acyclique et n'en appelle que le minimum requis. Si je comprends bien, l'étape d'initialisation se produit au moment où le code de build.gradle est exécuté.


Par exemple, ceci:


 copy { from source to dest } 

Ou comme ça:


 task epicFail { copy{ from source to dest } } 

Ce n'est peut-être pas évident, mais ce qui précède ralentira l'initialisation. Afin de ne pas gérer la copie des fichiers à chaque initialisation, vous devez utiliser le doLast{...} ou doFirst{...} dans la tâche - le code sera alors enveloppé dans une fermeture et sera appelé lorsque la tâche sera terminée.


 task properCopy { doLast { copy { from dest to source } } } 

ou alors


 task properCopy(type: Copy) { from dest to source } 

Dans les anciens exemples, doLast pouvez voir l'opérateur << au lieu de doLast , mais ils l'ont abandonné plus tard en raison du comportement non évident.


 task properCopy << { println("files copied") } 

tasks.all


Ce qui est drôle, avec doLast et doFirst vous pouvez suspendre une sorte d'action sur n'importe quelle tâche:


 tasks.all { doFirst { println("task $name started") } } 

L'IDE suggère que les tasks ont une whenTaskAdded(Closure ...) , mais la méthode all(Closure ...) fonctionne beaucoup plus intéressant - la fermeture est appelée pour toutes les tâches existantes, ainsi que pour les nouvelles tâches lorsqu'elles sont ajoutées.


Créez une tâche qui imprime les dépendances de toutes les tâches:


 task printDependencies { doLast { tasks.all { println("$name dependsOn $dependsOn") } } } 

ou alors:


 task printDependencies { doLast { tasks.all { Task task -> println("${task.name} dependsOn ${task.dependsOn}") } } } 

Si tasks.all{} appelé au moment de l'exécution (dans le bloc doLast ), nous verrons toutes les tâches et dépendances.
Si vous faites de même sans doLast (c'est-à-dire lors de l'initialisation), les tâches imprimées peuvent ne pas avoir de dépendances, car elles n'ont pas encore été ajoutées.


Oh oui, les dépendances! Si une autre tâche doit dépendre des résultats de notre implémentation, alors il vaut la peine d'ajouter une dépendance:


 anotherTask.dependsOn properCopy 

Ou même comme ça:


 tasks.all{ task -> if (task.name.toLowerCase().contains("debug")) { task.dependsOn properCopy } } 

entrées, sorties et assemblage incrémental


Une tâche commune sera appelée à chaque fois. Si vous spécifiez qu'une tâche basée sur le fichier A génère le fichier B, gradle sautera la tâche si ces fichiers n'ont pas changé. Et gradle ne vérifie pas la date de modification du fichier, mais son contenu.


 task generateCode(type: Exec) { commandLine "generateCode.sh", "input.txt", "output.java" inputs.file "input.txt" output.file "output.java" } 

De même, vous pouvez spécifier des dossiers, ainsi que certaines valeurs: inputs.property(name, value) .


description de la tâche


Lorsque vous appelez des ./gradlew tasks --all les tâches standard ont une belle description et sont en quelque sorte regroupées. Pour vos tâches, cela s'ajoute très simplement:


 task hello { group "MyCustomGroup" description "Prints 'hello'" doLast{ print 'hello' } } 

task.enabled


vous pouvez "désactiver" la tâche - alors ses dépendances seront toujours appelées, mais elle ne le sera pas.


 taskName.enabled false 

plusieurs projets (modules)


builds multi-projets dans la documentation


Dans le projet principal, vous pouvez placer plusieurs modules supplémentaires. Par exemple, cela est utilisé dans les projets Android - il n'y a presque rien dans le projet racine, le plugin Android est inclus dans le sous-projet. Si vous souhaitez ajouter un nouveau module, vous pouvez en ajouter un autre, et là, par exemple, vous pouvez également connecter le plugin Android, mais utiliser d'autres paramètres pour cela.


Autre exemple: lors de la publication d'un projet à l'aide de jitpack, le projet racine décrit avec quels paramètres publier un module enfant qui pourrait même ne pas suspecter la publication.


Les modules enfants sont spécifiés dans settings.gradle:


 include 'name' 

En savoir plus sur les dépendances entre les projets ici.


buildSrc


S'il build.gradle beaucoup de code dans build.gradle ou s'il est dupliqué, il peut être déplacé vers un module séparé. Nous avons besoin d'un dossier avec le nom magique buildSrc , dans lequel vous pouvez placer le code en groovy ou java. (enfin, ou plutôt, dans buildSrc/src/main/java/com/smth/ code, des tests peuvent être ajoutés à buildSrc/src/test ). Si vous voulez autre chose, par exemple, écrivez votre tâche sur scala ou utilisez des dépendances, alors directement dans buildSrc vous devez créer build.gradle et spécifier les dépendances nécessaires dans it / enable plugins.


Malheureusement, avec un projet dans buildSrc IDE peut être stupide avec des indices, là vous devrez écrire des importations et des classes / tâches à partir de là, build.gradle devrez également l'importer dans le build.gradle habituel. import com.smth.Taskname n'est pas difficile, il suffit de s'en souvenir et de ne pas se buildSrc pourquoi la tâche de buildSrc pas buildSrc trouvée).


Pour cette raison, il est pratique d'écrire d'abord quelque chose qui fonctionne directement dans build.gradle , puis de transférer ensuite le code vers buildSrc .


Type de tâche propre


La tâche hérite de DefaultTask , dans laquelle il y a beaucoup, beaucoup de champs, de méthodes et d'autres choses. Code AbstractTask hérité de DefaultTask.


Points utiles:


  • au lieu d'ajouter manuellement des inputs et des outputs vous pouvez leur utiliser des champs et des annotations: @Input, @OutputFile , etc.
  • mĂ©thode qui sera exĂ©cutĂ©e lorsque la tâche sera exĂ©cutĂ©e: @TaskAction .
  • des mĂ©thodes pratiques comme copy{from ... , into... } peuvent toujours ĂŞtre appelĂ©es, mais vous devez les appeler explicitement pour le projet: project.copy{...}

Quand quelqu'un dans build.gradle écrit pour notre tâche


 taskName { ... //some code } 

la méthode configure(Closure) est appelée sur la tâche.


Je ne sais pas si c'est la bonne approche, mais si une tâche a plusieurs champs dont l'état mutuel est difficile à contrôler avec les getter-setters, alors il semble assez pratique de redéfinir la méthode comme suit:


 override def configure(Closure closure){ def result = super().configure(closure) //    / - return result; } 

Et même si vous écrivez


 taskName.fieldName value 

alors la méthode configure sera toujours appelée.


Propre plugin


Comme une tâche, vous pouvez écrire votre propre plugin, qui configurera quelque chose ou créera des tâches. Par exemple, ce qui se passe dans android{...} est entièrement un mérite magie noire Plug-in Android, qui crée en outre un tas de tâches comme l'application: assembleDevelopDebug pour toutes les combinaisons possibles de saveur / type de construction / dimenstion. Il n'y a rien de compliqué à écrire votre plugin, pour une meilleure compréhension vous pouvez voir le code des autres plugins.


Il y a une troisième étape - vous pouvez placer le code non dans buildSrc , mais en faire un projet distinct. Ensuite, en utilisant https://jitpack.io ou autre chose, publiez le plugin et connectez-le de la même manière aux autres.


La fin


Les exemples ci-dessus peuvent inclure des fautes de frappe et des inexactitudes. Écrivez une note personnelle ou marquez avec ctrl+enter - je vais la corriger. Des exemples spécifiques sont mieux tirés de la documentation, et regardez cet article comme une petite liste de "comment faire".

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


All Articles