Générateurs de projets incrémentaux

Un générateur de projet incrémentiel est un objet qui manipule les ressources d'un projet d'une manière particulière. 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 plate-forme afin d'implémenter des transformations de ressources spéciales. Par exemple, les outils de développement Java (JDT) définissent un générateur de projet incrémentiel qui compile un fichier source Java pour en faire un fichier de classe chaque fois qu'un fichier est ajouté ou modifié dans un projet Java. Il garde également la trace des fichiers dépendants et les recompile lorsque cela est nécessaire.

Du point de vue de l'API, la plate-forme définit deux types de génération standard :

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 projets peuvent être nettoyés périodiquement par l'utilisateur pour forcer une regénération d'un projet complet lors de la génération incrémentielle suivante sur ce projet. Le nettoyage d'un projet supprime les informations de génération comme les marqueurs de problème et les fichiers de classe.

Les générateurs peuvent être mieux compris à l'aide d'un exemple. Le compilateur Java JDT est dirigé par un générateur de incrémentiel Java, qui recompile les fichiers d'un projet qui sont concernés par les modifications. Lorsqu'une génération complète (ou une génération incrémentielle après un nettoyage) est déclenchée, tous les fichiers .java du projet sont compilés. Tous les problèmes de génération rencontrés sont ajoutés comme marqueurs de problème dans les fichiers .java concernés. Lorsqu'une génération incrémentielle 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émentielle 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émentielle consiste à déterminer exactement ce qui doit être régénéré. Par exemple, l'état interne conservé par le générateur Java inclut des éléments comme une représentation graphique des dépendances, ainsi qu'une liste des problèmes de génération signalés. Ces informations sont utilisées lors d'une génération incrémentielle pour identifier les classes qui doivent être recompilées suite à une modification d'une ressource Java.

Même si la structure de base de la génération est définie dans la plate-forme, le travail réel est réalisé dans le code du générateur. 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.

Appel d'une génération

Un générateur peut être invoqué explicitement de l'une des manières suivantes :

Dans la 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 plate-forme 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é.

Définition d'un générateur de projet incrémentiel

Le point d'extension org.eclipse.core.resources.builders est utilisé pour faire contribuer un générateur de projet incrémentiel à la plate-forme. Les marques suivantes montrent comment le plug-in hypothétique com.example.builders peut ajouter un générateur de projet incrémentiel.

   <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 plate-forme 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
      }
      protected void clean(IProgressMonitor monitor) {
         // pour ajouter la logique propre de 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 qui a été demandé. La génération correspond à l'une des valeurs suivantes :

Si une génération incrémentielle a été demandée, un delta de ressource est fourni pour décrire les modifications apportées aux ressources 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 création de X, un delta pour Y est disponible en appelant getDelta(Y).  Pour vous assurer que la plate-forme peut fournir ces deltas, le générateur de X doit avoir déclaré la dépendance entre X et Y en renvoyant un tableau contenant Y à partir d'un appel précédent de la méthode build(). Si un générateur n'a aucune dépendance, il renvoie simplement la valeur null. Pour plus d'informations, reportez-vous à IncrementalProjectBuilder.

Génération complète

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 ou même l'examen d'autres projets s'il existe des dépendances entre les projets. Le fragment de code ci-dessous propose une manière d'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.

Génération incrémentielle

Lors de la réalisation d'une génération incrémentielle, le générateur utilise un delta de ressource au lieu d'une arborescence de ressources complète.

   protected void incrementalBuild(IResourceDelta delta, 
         IProgressMonitor monitor) throws CoreException {
      // le visiteur effectue la tâche.
      delta.accept(new MyBuildDeltaVisitor());
   }

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.

Nettoyage avant une génération

Le plan de travail permet aux utilisateurs de nettoyer un projet ou un ensemble de projets avant de commencer une génération. Cette fonction permet à l'utilisateur de forcer une regénération en partant de rien pour certains projets uniquement. Les générateurs doivent implémenter cette méthode pour nettoyer les marqueurs de problème et les ressources dérivées dans le projet.

Association d'un générateur de projet incrémentiel à un projet

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émentiel.

Remarque : Le nom du générateur dans une commande de génération est l'ID pleinement qualifiée de l'extension du générateur. L'ID pleinement qualifiée d'une extension est créée en combinant l'ID du plug-in avec l'ID simple de l'extension dans le fichier plugin.xml. Par exemple, un générateur avec une ID d'extension simple "mybuilder" dans le plug-in "com.example.builders" aura pour nom "com.example.builders.mybuilder"

Le fragment suivant ajoute un nouveau générateur comme premier générateur de la liste existante des générateurs.

   final String BUILDER_ID = "com.example.builders.mybuilder";
   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.