Un générateur de projet incrémental est un objet qui manipule les ressources dans un projet de manière définie par le générateur lui-même. Les générateurs de projets incrémentaux sont souvent utilisés pour appliquer une transformation à une ressource afin de produire une ressource ou un artefact d'une autre sorte.
Les plug-ins ajoutent des générateurs de projets incrémentaux à la plateforme afin d'implémenter des transformations de ressources spéciales. Par exemple, le plug-in JDT (Java Development Tooling) fourni avec la plateforme SDK définit un compilateur de projet incrémentiel qui compile un fichier source Java dans un fichier classe, chaque fois qu'un fichier est ajouté ou modifié dans un projet Java et recompile tous les autres fichiers affectés par le changement.
La plateforme définit deux types de compilation :
Les générateurs incrémentaux ont pour valeur de départ un delta de changements de ressources. Ce delta est le reflet du réseau de toutes les modifications de ressources depuis la dernière génération du projet par le générateur. Il est similaire à celui utilisé dans des événements de modification de ressources.
Les générateurs peuvent être mieux compris à l'aide d'un exemple. JDT (Java Development Tooling) fournit un compilateur Java dirigé par un compilateur de projet incrémentiel pour recompiler les fichiers affectés par les changements. Lorsqu'une génération complète est déclenchée, la totalité des fichiers .java du projet sont compilés. Tous les problèmes de compilation rencontrés sont ajoutés comme marqueur d'incident dans les fichiers .java affectés. Lorsqu'une génération incrémentale est déclenchée, le générateur sélectionne et recompile les fichiers .java ajoutés, modifiés ou de toute autre façon affectés et décrits dans le delta des ressources et met à jour les marqueurs d'incident en fonction. Tous les fichiers ou marqueurs .class qui ne sont plus appropriés sont supprimés.
La génération incrémentale présente des avantages de performances évidents pour les projets constitués de centaines ou de milliers de ressources, dont la plupart ne changent pas à tout moment.
Le défi technique de la génération incrémentale consiste à déterminer exactement ce qui doit être régénéré. Par exemple, l'état interne géré par le générateur Java inclut des choses telles qu'un graphique de dépendances et la liste des problèmes de compilation rapportés. Ces informations sont utilisées au cours d'une génération incrémentale pour identifier les classes devant être recompilées en réponse à une modification apportée à une ressource Java.
Bien que la structure de base de la génération soit définie dans la plateforme, le travail réel est exécuté dans le plug-in. Les schémas d'implémentation de générateurs incrémentaux complexes sont au-delà de la portée de cette discussion, du fait que l'implémentation est dépendante de la conception spécifique du générateur.
Un générateur peut être invoqué explicitement de l'une des manières suivantes :
En pratique, l'utilisateur du plan de travail déclenche une génération en sélectionnant les commandes correspondantes dans le menu du navigateur de ressources.
Des générateurs de projets incrémentaux sont également appelés de manière implicite par la plateforme au cours d'une auto-génération. Si elles sont activées, les auto-générations s'exécutent chaque fois que l'espace de travail est modifié.
Le point d'extension org.eclipse.core.resources.builders est utilisé pour faire contribuer un générateur de projet incrémental à la plateforme. Les marques suivantes montrent comment le plug-in hypothétique com.example.builders peut ajouter un générateur de projet incrémental.
<extension id="mybuilder" name="My Sample Builder" point="org.eclipse.core.resources.builders"> <builder <run class="com.example.builders.BuilderExample"> <parameter name="optimize" value="true" /> <parameter name="comment" value="Builder comment" /> </run> </builder> </extension>
La classe identifiée dans le point d'extension doit étendre la classe de plateforme IncrementalProjectBuilder.
public class BuilderExample extends IncrementalProjectBuilder { IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { // ajoutez ici votre logique de génération return null; } protected void startupOnInitialize() { // ajoutez ici la logique init du générateur } }
Le traitement de la génération commence par la méthode build() qui inclut des informations sur le type de génération demandé : FULL_BUILD, INCREMENTAL_BUILD ou AUTO_BUILD. Si une génération incrémentale a été demandée, un delta des ressources est fourni pour décrire les changements apportés aux ressources du projet depuis la dernière génération. Le fragment ci-dessous détaille la méthode build().
protected IProject[] build(int kind, Map args, IProgressMonitor monitor throws CoreException { if (kind == IncrementalProjectBuilder.FULL_BUILD) { fullBuild(monitor); } else { IResourceDelta delta = getDelta(getProject()); if (delta == null) { fullBuild(monitor); } else { incrementalBuild(delta, monitor); } } return null; }
Il arrive parfois que lors de la génération d'un projet "X,", un générateur nécessite des informations sur les changements apportés à d'autres projets "Y". (Par exemple, si une classe Java dans X implémente une interface fournie dans Y.)Lors de la génération de X, un delta pour Y est disponible en appelant getDelta(Y). Pour garantir que la plateforme puisse fournir de tels deltas, le générateur de X doit avoir déclaré la dépendance entre X et Y en retournant un tableau contenant Y d'un appel build() antérieur. Si un générateur n'a pas de dépendances, il peut simplement renvoyer la valeur "null". Pour plus d'informations, reportez-vous à IncrementalProjectBuilder.
La logique requise pour traiter une demande de génération complète est spécifique au plug-in. Elle peut impliquer la visite de chaque ressource du projet (si la génération a été déclenchée pour un projet) ou même l'examen d'autres projets si des dépendances existent entre eux. Le fragment ci-dessous suggère comment implémenter une génération complète.
protected void fullBuild(final IProgressMonitor monitor) throws CoreException { try { getProject().accept(new MyBuildVisitor()); } catch (CoreException e) { } }
Le visiteur de génération effectue cette dernière pour la ressource spécifique (et renvoie la valeur "true" pour poursuivre la visite de toutes les ressources enfant).
class MyBuildVisitor implements IResourceVisitor { public boolean visit(IResource res) { //génère la ressource spécifiée. //renvoie true pour poursuivre la visite des enfants. return true; } }
Le processus de visite se poursuit jusqu'à ce que l'arborescence de ressources complète ait été traversée.
Lors de l'exécution d'une génération incrémentale, vous utilisez un delta de modifications de ressources au lieu du projet tout entier.
protected void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException { // le visiteur effectue la tâche. delta.accept(new MyBuildDeltaVisitor()); }
Notez que pour une génération incrémentale, le visiteur de génération utilise l'arborescence d'un delta de ressources au lieu d'une arborescence de ressources complète.
Le processus de visite se poursuit jusqu'à ce que l'arborescence complète du delta de ressources ait été traversée. La nature spécifique des changements est similaire à celle décrite dans la section Implémentation d'un module d'écoute de modifications de ressources. Une différence importante réside dans le fait qu'avec des générateurs incrémentaux, vous travaillez avec un delta de ressources basés sur un projet déterminé, et non sur la totalité de l'espace de travail.
Pour mettre un générateur à la disposition d'un projet donné, il doit être inclus dans les spécifications de génération du projet. Il s'agit d'une liste de commandes à exécuter dans l'ordre, lorsque le projet est généré. Chaque commande désigne un seul générateur de projet incrémental.
Le fragment suivant ajoute un nouveau générateur comme premier générateur de la liste existante des générateurs.
IProjectDescription desc = project.getDescription(); ICommand[] commands = desc.getBuildSpec(); boolean found = false; for (int i = 0; i < commands.length; ++i) { if (commands[i].getBuilderName().equals(BUILDER_ID)) { found = true; break; } } if (!found) { //ajouter un générateur au projet ICommand command = desc.newCommand(); command.setBuilderName(BUILDER_ID); ICommand[] newCommands = new ICommand[commands.length + 1]; // Ajoutez-le avant d'autres générateurs. System.arraycopy(commands, 0, newCommands, 1, commands.length); newCommands[0] = command; desc.setBuildSpec(newCommands); project.setDescription(desc, null); }
La configuration d'un générateur de projet est effectuée une seule fois, en général à la création du projet.