La vie avec un projet multi-modules n'est pas si simple. Pour éviter la routine de création d'un nouveau module, nous avons créé notre propre plug-in pour Android Studio. Au cours de la mise en œuvre, nous avons rencontré un manque de documentation pratique, essayé plusieurs approches et trouvé de nombreux écueils. Il s'est avéré deux articles: "Théorie" et "Pratique" . Rencontrez-moi!

De quoi vais-je parler?
- Pourquoi un plugin? Pourquoi un plugin?
- Etablir une liste de contrôle
- Liste des options d'automatisation
- Bases du développement de plugin
- Actions
- Développement d'interface utilisateur dans les plugins
- Conclusions
- Internes IDEA: composants, PSI
- Unité interne IDEA
- PSI
- Conclusions
Pourquoi un plugin? Pourquoi un plugin?
Si vous développez un projet Android multi-modules, alors vous savez quel genre de routine c'est de créer un nouveau module à chaque fois. Vous devez créer un module, y configurer Gradle, ajouter des dépendances, attendre la synchronisation, n'oubliez pas de corriger quelque chose dans le module d'application - tout cela prend beaucoup de temps. Nous voulions automatiser la routine, et nous avons commencé par compiler une liste de contrôle de ce que nous faisons chaque fois que nous créons un nouveau module.
1. Tout d'abord, nous créons le module lui-même via le menu Fichier -> Nouveau -> Nouveau module -> Bibliothèque Android.

2. Nous écrivons les chemins d'accès au module dans le fichier settings.gradle, car nous avons plusieurs types de modules - modules de base et modules de fonctionnalités, qui se trouvent dans des dossiers différents.
Nous écrivons des chemins vers les modules 3. Nous changeons les constantes compileSdk , minSdk , targetSdk dans le build.gradle généré: nous les remplaçons par les constantes définies dans le root build.gradle .
Changer les constantes dans build.gradle du nouveau module Remarque: nous avons récemment supprimé cette partie de notre travail dans notre plugin Gradle, ce qui permet de configurer tous les paramètres nécessaires du fichier build.gradle sur plusieurs lignes.
4. Puisque nous écrivons tout le nouveau code dans Kotlin, nous connectons en standard deux plugins: kotlin-android et kotlin-kapt . Si le module est en quelque sorte connecté à l'interface utilisateur, nous connectons également le module kotlin-android-extensions .
Connecter les plugins Kotlin 5. Nous connectons les bibliothèques générales et les modules principaux. Les modules principaux sont, par exemple, l'enregistreur, l'analytique, certains utilitaires généraux et bibliothèques - RxJava, Moxy et bien d'autres.
Nous connectons des bibliothèques et des modules partagés 6. Configurez kapt pour Toothpick. Le cure - dent est notre infrastructure DI de base. Vous le savez très probablement: pour utiliser la génération de code plutôt que la réflexion dans la version, vous devez configurer le processeur d'annotation afin qu'il comprenne où trouver les usines pour les objets en cours de création:
Configuration du processeur d'annotation pour Toothpick Remarque: dans hh.ru, nous utilisons la première version de Toothpick, dans la seconde, nous avons supprimé la possibilité d'utiliser la génération de code .
7. Nous configurons kapt pour Moxy à l'intérieur du module créé. Moxy est notre framework principal pour créer MVP dans une application, et vous devez le tordre un peu pour qu'il puisse fonctionner dans un projet multi-module. En particulier, enregistrez le package du module créé dans les arguments kapt:
Configuration de kapt pour Moxy Remarque: nous sommes déjà passés à la nouvelle version de Moxy , et cette partie de la génération de code a perdu de sa pertinence.
8. Nous générons un tas de nouveaux fichiers. Je ne parle pas des fichiers qui sont créés automatiquement (AndroidManifest.xml, build.gradle, .gitignore), mais du cadre général du nouveau module: interacteurs, référentiels, modules DI, présentateurs, fragments. Il y a beaucoup de ces fichiers, ils ont la même structure au début, et les créer est une routine.

9. Nous connectons notre module créé au module d'application. Dans cette étape, vous devez vous rappeler de configurer Toothpick dans le fichier build.gradle du module d'application. Pour ce faire, nous ajoutons le package du module créé au processeur d'annotation d'arguments spéciaux - toothpick_registry_children_package_names .
Après cela, nous configurons Moxy dans le module d'application. Nous avons une classe qui est marquée avec l'annotation @RegisterMoxyReflectorPackages - nous y ajoutons le nom du package du module créé:
Configurer MoxyReflectorStub Et, au final, n'oubliez pas de connecter le module créé au bloc de dépendances du module d'application:
Nous avons obtenu une liste de contrôle de neuf points.
Puisqu'il y a de nombreux points, il est probable que l'on oublie quelque chose. Et puis passez des heures à vous demander ce qui s'est passé et pourquoi le projet ne va pas.
Nous avons décidé que vous ne pouvez pas vivre comme ça et que vous devez changer quelque chose.
Liste des options d'automatisation
Après avoir compilé la liste de contrôle, nous avons commencé à chercher des options pour automatiser ses éléments.
La première option était une tentative de faire "Ctrl + C, Ctrl + V" . Nous avons essayé de trouver une implémentation pour créer le module de bibliothèque Android, qui est disponible pour nous «prêt à l'emploi». Dans le dossier avec Android Studio (pour MacOs: / Applications / Android \ Studio.app/Contents/plugins/android/lib/templates/gradle-projects/ ), vous pouvez trouver un dossier spécial avec des modèles de ces projets que vous voyez lorsque vous sélectionnez Fichier - > Nouveau -> Nouveau module. Nous avons essayé de copier le modèle NewAndroidModule en changeant l'ID dans le fichier template.xml.ftl . Ensuite, ils ont lancé l'IDE, commencé à créer un nouveau module, et ... Android Studio s'est écrasé parce que la liste des modules que vous voyez dans le menu pour créer un nouveau module est codée en dur, vous ne pouvez pas le changer avec un copier-coller primitif. Lorsque vous essayez de prendre et d'ajouter, de supprimer ou de modifier un élément, Android Studio se bloque simplement.

La deuxième option pour automatiser la liste de contrôle était le moteur de modèle FreeMarker . Après une tentative de copier-coller infructueuse, nous avons décidé d'examiner de plus près les modèles de module et avons trouvé des modèles FreeMarker sous le capot.
Je ne vous dirai pas en détail ce qu'est FreeMarker - il y a un bon article de RedMadRobot et une vidéo de MosDroid de Lesha Bykov . Mais en bref - c'est un moteur pour générer des fichiers en utilisant des modèles et des objets java Map-ki spéciaux. Vous nourrissez des modèles, des objets et FreeMarker génère du code à la sortie.
Mais regardez à nouveau la liste de contrôle:

Si vous regardez de plus près, vous pouvez voir qu'il est divisé en deux grands groupes de tâches:
- Les tâches de génération d'un nouveau code (1, 3, 4, 5, 6, 7, 8) et
- Tâches de modification d'un code existant (2, 7, 8, 9)
Et si FreeMarker fait face aux tâches du premier groupe avec fracas, alors il ne gère pas du tout le second. À titre d'exemple: dans l'implémentation actuelle de l'intégration de FreeMarker dans Android Studio, lorsque vous essayez d'insérer une ligne dans le fichier settings.gradle qui ne commence pas par le mot «include», le studio se bloque . Ici, nous avons attrapé la tristesse et avons décidé d'abandonner l'utilisation de FreeMarker.
Après un échec avec FreeMarker, l'idée est venue d'écrire mon propre utilitaire de console pour effectuer une liste de contrôle. Dans Intellij IDEA, il est possible d'utiliser un terminal, alors pourquoi pas? Écrivons un script sur le bash, total business:

Mais comme nous voulons pouvoir configurer de manière flexible le module créé, nous devrons entrer de nombreux drapeaux différents, ce qui ne sera pas très pratique pour imprimer dans la console.
Après cela, nous avons pris du recul et nous sommes souvenus que nous travaillions à l'intérieur d'Intellij IDEA. Et comment est-il organisé? Il existe un certain noyau de classes, un moteur auquel de nombreux plug-ins sont attachés, qui ajoutent les fonctionnalités dont nous avons besoin.
Combien d'entre vous voient dans la capture d'écran plus de deux plugins connectés?

Ici, ils sont connectés au moins trois. Si vous travaillez avec Kotlin, le plugin Kotlin est activé. Si vous travaillez dans un projet avec Gradle, le plugin Gradle est également inclus. Si vous travaillez dans un projet avec un système de contrôle de version - Git, SVN ou autre chose - vous avez le plug-in approprié inclus pour intégrer ce VCS.
Nous avons examiné le référentiel officiel des plugins JetBrains, et il s'est avéré qu'il existe déjà plus de 4000 plugins officiellement enregistrés! Presque le monde entier écrit des plugins, et ces plugins peuvent faire n'importe quoi: à partir de l'intégration d'un langage de programmation dans IDEA et se terminant par des outils spécifiques qui peuvent être exécutés à partir d'IDEA.
En bref, nous avons décidé d'écrire notre propre plugin.
Bases du développement de plugin
Nous passons aux bases du développement de plugins. Pour commencer, vous n'avez besoin que de trois choses:
- IntelliJ IDEA , minimum Community Edition (Vous pouvez travailler dans la version Ultimate, mais cela ne donnera pas d'avantages spéciaux lors du développement de plugins);
- Le plugin DevKit qui lui est connecté est un plugin spécial qui ajoute la possibilité d'écrire d'autres plugins;
- Et tout langage JVM dans lequel vous souhaitez écrire un plugin. Cela pourrait être Kotlin, Java, Groovy - n'importe quoi.
Nous commençons par créer un projet de plugin. Nous sélectionnons Nouveau projet , pointons Gradle , nous cochons IntelliJ Platform Plugin et nous créons le projet.

Remarque: Si vous ne voyez pas la case à cocher Plugin de plateforme IntelliJ, cela signifie que vous n'avez pas installé le plugin DevKit.
Après avoir rempli les champs obligatoires, nous verrons une structure de plugin vide.

Examinons-le de plus près. Il se compose de:
- Dossiers dans lesquels vous écrirez le code du futur projet; ( main / java , main / kotlin , etc.);
- fichier build.gradle , dans lequel vous déclarerez les dépendances de votre plugin sur certaines bibliothèques, et configurerez également une chose telle que gradle-intellij-plugin .
gradle-intellij-plugin - Plugin Gradle qui vous permet d'utiliser Gradle comme système de construction de plugins. C'est pratique car presque tous les développeurs Android connaissent Gradle et savent comment l'utiliser. De plus, gradle-intellij-plugin ajoute des tâches gradle utiles à votre projet, en particulier:
- runIde - cette tâche lance une instance IDEA distincte avec un plugin que vous développez afin que vous puissiez le déboguer;
- buildPlugin - collecte l'archive zip de votre plugin afin que vous puissiez la distribuer localement ou via le référentiel IDEA officiel;
- verifyPlugin - cette tâche vérifie votre plugin pour les erreurs grossières qui peuvent ne pas lui permettre de s'intégrer dans Android Studio ou une autre IDEA.
Que donne le plugin gradle-intellij ? Avec son aide, il devient plus facile d'ajouter des dépendances à d'autres plugins, mais nous en parlerons un peu plus tard, mais pour l'instant je peux dire que gradle-intellij-plugin est votre frère, utilisez-le.
Retour à la structure du plugin. Le fichier le plus important de tout plugin est plugin.xml .
plugin.xml <idea-plugin> <id>com.experiment.simple.plugin</id> <name>Hello, world</name> <vendor email="myemail@yourcompany.com" url="http://www.mycompany.com"> My company </vendor> <description><![CDATA[ My first ever plugin - try to open Hello world dialog<br> ]]></description> <depends>com.intellij.modules.lang</depends> <depends>org.jetbrains.kotlin</depends> <depends>org.intellij.groovy</depends> <idea-version since-build="163"/> <actions> <group description="My actions" id="MyActionGroup" text="My actions"> <separator/> <action id="com.experiment.actions.OpenHelloWorldAction" class="com.experiment.actions.OpenHelloWorldAction" text="Show Hello world" description="Open dialog"> <add-to-group group-id="NewGroup" anchor="last"/> </action> </group> </actions> <idea-plugin>
Il s'agit d'un fichier qui contient:
- Les métadonnées de votre plugin: identifiant, nom, description, informations sur le fournisseur, journal des modifications
- Description des dépendances sur d'autres plugins;
- Ici, vous pouvez également spécifier la version IDEA avec laquelle votre plugin fonctionnera correctement
- Les actions sont également décrites ici.
Actions
Qu'est-ce que les actions ? Supposons que vous ouvrez un menu pour créer un nouveau fichier. En fait, chaque élément de ce menu a été ajouté par une sorte de plugin:

Les actions sont les points d'entrée de votre plugin pour les utilisateurs. Chaque fois que l'utilisateur clique sur un élément de menu, vous obtenez le contrôle à l'intérieur du plugin, vous pouvez répondre à ce clic et faire ce qui est nécessaire.
Comment les actions sont-elles créées? Écrivons une action simple qui affichera une boîte de dialogue avec le message "Bonjour tout le monde".
OpenHelloWorldAction class OpenHelloWorldAction : AnAction() { override fun actionPerformed(actionEvent: AnActionEvent) { val project = actionEvent.project Messages.showMessageDialog( project, "Hello world!", "Greeting", Messages.getInformationIcon() ) } override fun update(e: AnActionEvent) { super.update(e)
Pour créer une action, nous créons d'abord une classe qui hérite de la classe AnAction . Deuxièmement, nous devons redéfinir la méthode actionPerformed, d'où vient le paramètre spécial de la classe AnActionEvent . Ce paramètre contient des informations sur le contexte d'exécution de votre action. Le contexte fait référence au projet dans lequel vous travaillez, au fichier qui est maintenant ouvert dans l'éditeur de code de l'utilisateur, aux éléments sélectionnés dans l'arborescence du projet et à d'autres données pouvant vous aider dans le traitement de vos tâches.
Pour afficher la boîte de dialogue "Hello, world", nous obtenons d'abord le projet (uniquement à partir du paramètre AnActionEvent ), puis utilisons la classe d'utilitaire Messages pour afficher la boîte de dialogue.
Quelles fonctionnalités supplémentaires avons-nous dans l'action? Nous pouvons remplacer deux méthodes: update et beforeActionPerformedUpdate .
La méthode de mise à jour est appelée à chaque fois que le contexte d'exécution de votre action change. Pourquoi cela peut vous être utile: par exemple, pour mettre à jour l'élément de menu qui a été ajouté par votre plugin. Supposons que vous ayez écrit une action qui ne peut fonctionner qu'avec des fichiers Kotlin et que l'utilisateur a maintenant ouvert le fichier Groovy. Ensuite, dans la méthode de mise à jour, vous pouvez rendre votre action inaccessible.
La méthode beforeActionPerformedUpdate est similaire à la méthode update, mais elle est appelée juste avant actionPerformed . C'est la dernière occasion d'influencer votre action. La documentation vous recommande de ne rien effectuer de «lourd» dans cette méthode afin qu'elle s'exécute dès que possible.
Vous pouvez également lier des actions à certains éléments de l'interface IDEA et leur définir des combinaisons de touches par défaut pour l'appel - je vous recommande d'en lire plus ici .
Développement d'interface utilisateur dans les plugins
Si vous avez besoin de votre propre conception de boîte de dialogue, vous devrez travailler dur. Nous avons développé notre interface utilisateur parce que nous voulions avoir une interface graphique pratique dans laquelle il serait possible de marquer quelques ticks, d'avoir un sélecteur pour les valeurs d'énumération, etc.
Le plugin DevKit pour le développement de l'interface utilisateur ajoute certaines actions, telles que le formulaire GUI et la boîte de dialogue . Le premier crée un formulaire vide pour nous, le second - un formulaire avec deux boutons: Ok et Annuler .

D'accord, il y a un concepteur de formulaires , mais il est ... moyen. En comparaison, même le concepteur de mise en page dans Android Studio semble confortable et bon. L'interface utilisateur entière est développée sur une bibliothèque telle que Java Swing. Ce concepteur de formulaires génère un fichier XML lisible par l'homme. Si vous ne pouvez pas faire quelque chose dans le concepteur de formulaires (exemple: insérer plusieurs contrôles dans la même cellule de grille et masquer tous les contrôles sauf un), vous devez accéder à ce fichier et le modifier - IDEA reprendra ces modifications.
Presque chaque formulaire se compose de deux fichiers: le premier a l'extension .form , c'est juste le fichier XML, le second est la soi-disant classe Bound , qui peut être écrite en Java, Kotlin, mais tout ce que vous voulez. Il agit comme un contrôleur de formulaire. De façon inattendue, mais l'écriture en Java est beaucoup plus facile que dans d'autres langages. Parce que, par exemple, le réglage de Kotlin n'est pas encore aussi parfait. Lorsque vous ajoutez de nouveaux composants en travaillant avec la classe Java, ces composants sont automatiquement ajoutés à la classe et lorsque vous modifiez le nom du composant dans le concepteur, il est automatiquement extrait. Mais dans le cas de Kotlin, les composants ne sont pas ajoutés - aucune intégration ne se produit, vous pouvez oublier quelque chose et ne pas comprendre pourquoi rien ne fonctionne.
Nous résumons les bases
- Pour créer un plugin, vous aurez besoin: IDEA Community Edition, Plugin DevKit et Java qui lui sont connectés.
- gradle-intellij-plugin est votre frère, il vous simplifiera grandement la vie, je vous recommande de l'utiliser.
- N'écrivez pas votre propre interface utilisateur, sauf si cela est nécessaire. Il existe de nombreuses classes d'utilitaires dans IDEA qui vous permettent de créer votre propre interface utilisateur prête à l'emploi. Si vous avez besoin de quelque chose de compliqué, préparez-vous à travailler dur.
- Le plugin peut avoir un nombre illimité d'actions. Le même plugin peut ajouter beaucoup de fonctionnalités à votre IDEA.
Internes IDEA: composants, PSI
Parlons des intestins d'IDEA, de la façon dont ils sont disposés à l'intérieur. Je vous le dis pour que rien ne s'écroule dans votre tête quand j'explique la partie pratique, et pour que vous compreniez d'où ça vient.
Comment est organisé IDEA? Au premier niveau de la hiérarchie se trouve une classe telle que Application . Il s'agit d'une instance IDEA distincte. Pour chaque instance IDEA, un objet de classe Application est créé. Par exemple, si vous exécutez AppCode, Intellij IDEA et Android Studio en même temps, vous obtiendrez trois instances distinctes de la classe Application. Cette classe est conçue pour gérer le flux d'entrée / sortie.
Placer l'application dans la hiérarchie Le niveau suivant est la classe Project . C'est le concept le plus proche de ce que vous voyez lors de l'ouverture d'un nouveau projet dans IDEA. Le projet est généralement nécessaire pour obtenir d'autres composants dans IDEA: classes utilitaires, gestionnaires et bien plus encore.
Place du projet dans la hiérarchie Le niveau de détail suivant est la classe Module . En général, un module est une hiérarchie de classes regroupées dans un dossier. Mais ici, par modules, nous entendons les modules Maven, les modules Gradle. Cette classe est nécessaire, d'une part, pour déterminer les dépendances entre les modules, et d'autre part, pour rechercher des classes à l'intérieur de ces modules.
Placer le module dans une hiérarchie Le niveau de détail suivant est la classe VirtualFile . Il s'agit d'une abstraction sur le vrai fichier qui se trouve sur votre disque. Plusieurs instances de VirtualFile peuvent correspondre à chaque fichier réel, mais elles sont toutes égales. Dans le même temps, si le fichier réel est supprimé, VirtualFile ne sera pas supprimé seul, mais deviendra simplement invalide.
VirtualFile Hierarchy Place Une entité telle que Document est associée à chaque VirtualFile . Il s'agit d'une abstraction sur le texte de votre fichier. Le document est nécessaire pour que vous puissiez suivre les événements liés aux changements dans le texte du fichier : l'utilisateur a inséré une ligne, supprimé une ligne, etc., etc.
Placer le document dans la hiérarchie Un peu à côté de cette hiérarchie se trouve la classe Editor - c'est un éditeur de code. Chaque projet peut avoir un éditeur . Il est nécessaire pour que vous puissiez suivre les événements liés à l'éditeur de code: l'utilisateur a mis en surbrillance la ligne où se trouve le curseur, etc.
Place de l'éditeur dans la hiérarchie La dernière chose dont je voulais parler est PsiFile . C'est aussi une abstraction sur des fichiers réels, mais du point de vue de la représentation des éléments de code . PSI signifie Program Structure Interface.
Place PsiFile dans la hiérarchie Et en quoi consiste chaque programme? Considérez une classe Java régulière.
Classe Java simple package com.experiment; import javax.inject.Inject; class SomeClass { @Inject String injectedString; public void someMethod() { System.out.println(injectedString); } }
Il consiste à spécifier un package, des importations, des classes, des champs, des méthodes, des annotations, des mots-clés, des types de données, des modificateurs, des identificateurs, des références de méthode, des expressions et des jetons. Et pour chaque élément, il y a une abstraction de PsiElement . Autrement dit, chacun de vos programmes se compose de PsiElements .

PsiFile , à son tour, est une structure arborescente où chaque élément peut avoir un parent et de nombreux descendants.

Je voudrais mentionner que PSI n'est pas égal à un arbre de syntaxe abstraite . Un arbre de syntaxe abstraite est un arbre de représentation de votre programme après que l'analyseur a passé votre programme, et il est détaché de tout langage de programmation. PSI, au contraire, est lié à un langage de programmation spécifique. Lorsque vous travaillez avec une classe Java, vous traitez avec des PsiElements Java. Lorsque vous travaillez avec une classe Groovy - avec Groovy PsiElements, etc. , PSI- - , , , – .
PSI – PSI- IDEA. , , , . .
IDEA
- PSI IDEA;
- PSI- IDEA, ;
- PsiElement-.
, . .
- ,
- Tool window
- IntelliJ IDEA
- IDEA
- IntelliJ IDEA
- , , . , .
- Droidcon Italy 2017 , , , , FreeMarker- . , .
- KotlinConf , Square SQLDelight, Java, Kotlin.