Tutoriel plugins Eclipse

publicité
Tutoriel Plugins Eclipse (partie 1)
© 2007, Frédéric Peschanski
Dans ce tutoriel de niveau débutant, nous réalisons une petite étude de cas de création de plugins
Eclipse. Nous allons créer en pas à pas deux plugins:
–
plugin timer : un plugin inspiré de l'outil Cron d'Unix, ici en version très simplifiée, et qui
permet d'enregistrer des tâches exécutées à intervalles réguliers. Ce plugin montre l'extension du
Workbench eclipse avec des Action Sets (menus) et illustre également la définition de points
d'extensions
–
plugin clock : exemple d'extension pour le timer. Montre également les bases de la création de
vues en JWT/JFace.
Dans un premier temps, on lancera Eclipse dans sa version 3.1 ou 3.2.
Partie I. Le Plugin timer
Le développement de plugins Eclipse est pris en charge dans l'environnement par l'environnement
de développement de plugins PDE (Plugin Development Environment) qui est fourni avec Eclipse
en standard. Ce dernier est basé sur la boîte à outils de développement Java JDT (Java
Development Toolkit). Les plugins Eclipse sont donc en général implémenté en Java. Ce tutoriel
suppose donc un niveau intermédiaire de programmation orientée-objet en Java, ainsi que des
connaissances minimales de l'environnement Eclipse du point de vue utilisateur/développeur Java.
1) Création du projet
Pour démarrer le développement d'un plugin, on passe par un assistant (Wizard) de création de
projet en choisissant le menu Fichier (File) et Nouveau Projet (New Project) puis en sélectionnant
le type de projet Projet de Plugin (Plug-in Project). Les étapes 1 et 2 sont représentées ci-dessous :
Etape 1 : Nouveau projet
Etape 2 : Projet de Plugin
On doit ensuite choisir le nom du plugin TimerPlugin en séparant de préférence les répertoires src/
et bin/ et en sélectionnant un plugin Eclipse et non Equinoxe1 (cf. étape 3). A l'étape suivante, on
choisira l'identificateur (Plugin ID) du plugin upmc.cps.timer.TimerPlugin ainsi que son
numéro de version (1.0.0 par défaut) et son nom descriptif (Plugin Name) TimerPlugin Plug-in.
On générera un activateur donc le nom de classe sera upmc.cps.timer.Activator et ce plugin
contribuera à l'interface utilisateur d'Eclipse. En revanche, ce n'est pas une application final RCP2
(Rich Client Platform). Les entrées nécessaires sont figurée à l'étape 4 ci-dessous :
Etape 3 : Informations générales
Etape 4 : Contenu du plugin
La page suivante (Next) de l'assistant propose, de façon optionnelle, de suivre des modèles de
Plugins (templates) qui permettent de générer un code d'exemple pour différents types de
contribution à l'environnement Eclipse (cf. étape 5). Ici, nous choisirons uniquement le modèle
Action set « Bonjour le monde » ("Hello world" Action set) qui génère le code minimal nécessaire
pour contribuer un menu global d'Eclipse3 (cf. étape 6).
1 Les plugin Equinoxe sont de bas niveaux et se placent au-dessus du framework OSGI et non d'Eclipse
2 Les plugins RCP sont des applications finales qui se placent au dessus-d'Eclipse mais sans les outils de
développement.
3 Il est déconseillé de contribuer directement un menu global à Eclipse, et nous ne le faisons ici que pour illustrer
l'extension de menus. En pratique, on préférera ajouter soit un menu contextuel soit, au pire, un sous-menu de menu
global.
Etape 5 : les modèles de plugin
Etape 6 : le modèle "Hello World" Action Set
pour contribuer un menu
Finalement, on choisira les options du menu que nous souhaitons ajouter sur la page suivante de
l'assistant. On choisira le nom de paquetage Java (Java Package Name) upmc.cps.timer.actions
(cf. étape 7 mais le nom de paquetage Java doit être corrigé). On trouvera dans ce paquetage toutes
les classes générées par Eclipse pour réagir aux entrées du menu contribué. Le nom de classe
d'action (Action Class Name) est TimerAction et le message d'information sera Timer Activated.
Etape 7 : les options de l'action set
2) Découverte de l'architecture du Plugin
L'assistant se termine après l'étape 7 en cliquant sur Terminer (Finish). Dans ce tutoriel, on choisit
en fait de revenir en arrière et de n'utiliser aucun modèle. On se retrouve donc avec l'arborescence
décrite à l'étape 8 ci-dessous :
Etape 8 : l'arboresence générée pour le plugin TimerPlugin
Cette vue de l'explorateur de paquetage (Package explorer) est composée des éléments suivants:
–
Le répertoire racine du projet TimerPlugin/
–
Le répertoire des sources src/
–
dans le paquetage upmc.cps.timer, la classe d'activation OSGI Activator (cf. ci-dessous)
–
Les dépendances standards Java JRE (Java Runtime Environment)
–
Les dépendances de Plug-in
–
ici nous dépendons de tous les plugins de base d'Eclipse:
–
framework OSGI/Equinoxe: org.eclipse.osgi.* et org.eclipse.equinoxe.*
–
les plugins de base: org.eclipse.core.*
–
les plugins d'interface graphique: org.eclipse.swt.*, org.eclipse.jface.*,
org.eclipse.ui.*
–
Le fichier manifeste MANIFEST.MF (géré par l'assistant de plugin cf. ci-dessous)
–
Le fichier de configuration du plugin plugin.xml (ici non encore généré car nous n'effectuons
encore aucune contribution. Ce fichier est également géré par l'assistant de plugin)
–
Les propriétés de construction build.properties (géré par l'assistant de plugin)
En double-cliquant dans l'explorateur de paquetage sur le fichier manifeste, le fichier de
configuration ou les propriétés de construction, on obtient la vue de l'assistant de configuration de
plugin (cf. Etape 9)
Etape 9 : Vue de l'assistant PDE de configuration de Plugin (onglet aperçu Overview)
C'est dans cette interface de configuration que nous éditerons la configuration du plugin. L'édition
dans un éditeur de texte des fichiers de configuration du plugin est rarement nécessaire, mais la
synchronisation entre l'interface graphique et l'édition textuelle est en général bien gérée par
Eclipse.
Dans cette interface, on peut gérer:
–
les informations générales du plugin
–
l'environnement d'exécution (si besoin de spécifier une version spécifique de Java par exemple)
–
les dépendances (plugins requis)
–
l'environnement d'exécution (par exemple, pour utiliser des bibliothèques tierces)
–
les extensions et points d'extension pour contribuer à la plateforme Eclipse
–
le test du plugin: exécution et déboguage
–
l'export du plugin sous forme binaire
Parmi les fichiers les plus intéressants générés par le PDE, il y a par exemple le manifeste
MANIFEST.MF qui décrit le plugin Eclipse en tant que Bundle OSGI4 dont le contenu est décrit cidessous:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: TimerPlugin Plug-in
Bundle-SymbolicName: upmc.cps.timer.TimerPlugin
Bundle-Version: 1.0.0
Bundle-Activator: upmc.cps.timer.Activator
Bundle-Localization: plugin
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime
Eclipse-LazyStart: true
Ce fichier contient les informations importantes du plugin ainsi que les dépendances en termes de
bundles OSGI. Nous n'entrons pas dans les détails de ces dépendances dans ce tutoriel débutant. La
dernière ligne indique à Eclipse que le plugin n'est pas un plugin de base et doit donc être chargé « à
la demande » (ou de façon paresseuse lazy) pour ne pas occuper inutilement de la mémoire.
Lorsque chargé, le framework Equinoxe/OSGI déclenche l'activation du plugin dont le code est
donné par la méthode start de l'activateur upmc.cps.timer.Activator généré par le PDE. La
méthode stop effectue le travail inverse mais, en version 3.2 en tout cas, Eclipse ne décharge pas
automatiquement les plugins non utilisés, il faut les désactiver à la main.
Le code source de l'activateur OSGI est le suivant:
package upmc.cps.timer;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
/**
* The activator class controls the plug-in life cycle
*/
public class Activator extends AbstractUIPlugin {
// The plug-in ID
public static final String PLUGIN_ID = "upmc.cps.timer.TimerPlugin";
// The shared instance
private static Activator plugin;
public Activator() {
plugin = this;
}
public void start(BundleContext context) throws Exception {
super.start(context);
}
public void stop(BundleContext context) throws Exception {
plugin = null;
super.stop(context);
}
public static Activator getDefault() {
return plugin;
}
}
4 Nous rappelons qu'Eclipse est construit au dessus du modèle de composants binaires OSGI.
En règle générale, on ne touche qu'au code des méthodes start et stop de l'activateur.
3) Contribution de menus : extension Action Set
Nous allons maintenant contribuer à l'environnement Eclipse en ajoutant un nouveau menu global
permettant d'activer ou de désactiver le plugin Timer.
Dans l'assistant de configuration de plugin, sélectionner l'onglet Extensions puis cliquer sur Ajout
(Add) pour ajouter une extension (cf. étape 10). On choisira d'ajouter une extension Action Set
(ensemble d'actions) en sélectionnant org.eclipse.ui.actionSets dans la liste des points
d'extension (extension points) disponibles (cliquer sur Finish) (cf. étape 11).
Etape 10 : Ajout d'extension
Etape 11 : sélection du point d'extension
On saisit ensuite les informations générales de l'extension. L'identificateur de l'extension sera
upmc.cps.timer.TimerActions et le nom d'affichage de l'action sera Time Actions (cf. Etape
12)
Etape 12 : informations générales de l'extension Action Set
Le fichier de configuration plugin.xml est spécifique à Eclipse et concentre la plupart des
informations sur le plugin en termes d'architecture. Une fois l'extension demandée (étape 12
terminée) le fichier plugin.xml généré apparaît avec le contenu XML suivant :
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
id="upmc.cps.timer.TimerActions"
name="Timer Actions"
point="org.eclipse.ui.actionSets">
</extension>
</plugin>
On retrouve ici les informations de l'interface utilisateur. Pour ajouter nos menus, ce fichier doit être
modifié avec le détails des actions à ajouter. Le contenu du plugin.xml devient :
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
id="upmc.cps.timer.TimerActions"
name="Timer Actions"
point="org.eclipse.ui.actionSets">
<actionSet
label="Timer action set"
visible="true"
id="upmc.cps.timer.actionSet">
<menu
label="Timer menu"
id="timerMenu">
<separator
name="timerGroup">
</separator>
</menu>
<action
label="Stop timer"
icon="icons/sample.gif"
class="upmc.cps.timer.actions.StopAction"
tooltip="Stop timer"
menubarPath="timerMenu/timerGroup"
toolbarPath="timerGroup"
id="upmc.cps.timer.actions.StopAction">
</action>
<action
label="Start timer"
icon="icons/sample.gif"
class="upmc.cps.timer.actions.StartAction"
tooltip="Start timer"
menubarPath="timerMenu/timerGroup"
toolbarPath="timerGroup"
id="upmc.cps.timer.actions.StartAction">
</action>
</actionSet>
</extension>
</plugin>
Ici nous ajoutons deux éléments de menu pour respectivement démarrer et stopper le
fonctionnement du plugin Timer. Attention : dans cette version prototype, ceci n'influe pas sur le
chargement/déchargement en mémoire du plugin. Chaque action est identifiée à un élément XML
action associé à un certain nombre d'attributs. L'attribut le plus important est l'attribut class dont
la valeur « point » vers une classe dite « déléguée » (delegate) qui sera instanciée lorsque l'élément
de menu est sélectionné. Toutes les actions se trouvent dans un ensemble d'action actionSet
correspondant à un menu complet. Par défaut un action set se place au niveau global, et il faut
reconstruire le menu à modifier si l'on veut modifier un sous-menu (Eclipse 4.x devrait
« simplifier » cette étape).
4) Code d'interface et code métier
Le
code
source
des
deux classes de déléguées se trouve dans le paquetage
upmc.cps.timer.actions. On donne ci-dessous le code source de l'action de démarrage (classe
StartAction), nous laissons en exercice le code de l'action inverse StopAction.
package upmc.cps.timer.actions;
import
import
import
import
import
org.eclipse.jface.action.IAction;
org.eclipse.jface.dialogs.MessageDialog;
org.eclipse.jface.viewers.ISelection;
org.eclipse.ui.IWorkbenchWindow;
org.eclipse.ui.IWorkbenchWindowActionDelegate;
import upmc.cps.timer.TimerManager;
public class StartAction implements IWorkbenchWindowActionDelegate {
private IWorkbenchWindow window;
public StartAction() {
window = null;
}
public void init(IWorkbenchWindow window) {
this.window = window;
}
public void run(IAction action) {
if(TimerManager.fetchGlobalTimer().isActivated()) {
MessageDialog.openInformation(
window.getShell(),
"Timer",
"Timer already started");
} else {
TimerManager.fetchGlobalTimer().activate();
MessageDialog.openInformation(
window.getShell(),
"Timer",
"Timer started");
}
}
public void selectionChanged(IAction action, ISelection selection) {
// réagir en fonction de la sélection, si besoin est
}
public void dispose() {
// ici, relâcher les ressources si besoin est
}
}
La partie intéressante correspond à la méthode run qui indique le code à exécuter lorsque l'action
est déclenchée par l'utilisteur. Ici, on affiche un message d'information (classe standard d'Eclipse
MessageDialog, cf. documentation) puis si le timer n'est pas encore activé alors on l'active en
invoquant la méthode de classe fetchGlobalTimer() pour récupérer le timer global, puis en
invoquant activate() sur ce dernier.
Remarque : Comme les dépendances entre plugins sont très souvent statiques (dépendances via
plugin.xml par exemple), il est fréquent d'utiliser des singletons (cf. design pattern Singleton) pour
communiquer au sein de la plateforme.
La classe TimerManager correspond au code métier de notre plugin, son code est le suivant:
package upmc.cps.timer;
import java.util.Timer;
import java.util.TimerTask;
public class TimerManager {
private Timer timer;
private boolean activated;
private static TimerManager globalTimer = null;
public TimerManager() {
timer = new Timer("upmc.cps.timer.TimerPlugin");
activated = false;
}
public void activate() {
if(!activated) {
activated = true;
}
}
public void desactivate() {
if(activated) {
timer.cancel();
activated=false;
}
}
public boolean isActivated() {
return activated;
}
/* package */ static void startGlobalTimerManager() {
if(globalTimer==null)
globalTimer = new TimerManager();
}
public static TimerManager fetchGlobalTimer() {
return globalTimer;
}
public static void stopGlobalTimerManager() {
globalTimer.desactivate();
}
public void scheduleTask(TimerTask task, int delay, int period) {
System.out.println("Task to schedule");
if(task!=null) {
System.out.println("Starting task");
timer.schedule(task,delay*1000,period*1000);
} else
System.out.println("Task is null");
}
}
Nous ne détaillons pas ce code qui est du code standard Java (les println sont pour le déboguage
« manuel » et doivent bien sûr être enlevés en « production »). Notons qu'il s'agit plus d'un
prototype très naïf de timer plutôt qu'un code réellement utilisable. Important: ne pas utiliser ce
plugin !
Nous devons également modifier l'activateur pour créer le singleton TimerManager global. Dans la
classe upmc.cps.timer.Activator on modifie donc les méthodes start et stop de la façon
suivante:
public void start(BundleContext context) throws Exception {
super.start(context);
TimerManager.startGlobalTimerManager();
}
public void stop(BundleContext context) throws Exception {
plugin = null;
TimerManager.stopGlobalTimerManager();
super.stop(context);
}
5) Test du plugin
Après résolution des erreurs éventuelles de compilation, il ne nous reste plus qu'à tester notre
plugin. Pour cela il suffit de sélectionner le répertoire principal du projet TimerPlugin dans
l'explorateur de paquetages et d'activer le menu contextuel avec le bouton droit de la souris. Dans ce
menu on choisira Démarrer (Run as) en tant qu'application eclipse (Eclipse Application) pour lancer
un eclipse « fils » dans lequel notre plugin est chargé (cf. étapes 13 et 14).
Etape 13 : lancement du Plugin en tant qu'application Eclipse
Etape 14 : lancement du processus Eclipse « fils » dans lequel le plugin timer est actif
(cf. le menu Timer)
Et finalement nous pouvons activer notre plugin par menu (cf. étape 15)
Etape 15 : activation du timer par menu
Remarque : les affichages sur la sortie standard ou d'erreur effectués par le timer (pour le
déboguage) sont en fait visibles dans l'Eclipse de développement et non l'Eclipse « fils » de test.
Important : ne jamais utiliser l'Eclipse « fils » de test pour autre chose ... du test ! (exemple:
développer un plugin dans le « fils » que l'on testera dans le « fils du fils », etc. ! A éviter !).
6) La suite du tutoriel
Dans la deuxième partie du tutoriel, nous verrons comment:
1. effectuer une contribution de vue à l'environnement Eclipse, dans le cadre d'un plugin Clock qui
affiche une horloge
2. définir un point d'extension, au niveau du plugin Timer pour permettre à d'autres plugins
d'enregistrer des tâches répétitives
3. connecter le plugin Clock au plugin Timer pour renouveler l'affichage de l'heure toutes les
secondes.
© 2007 Frédéric Peschanski, licence Art Libre / Free Art License
Téléchargement