Builder

Aus Eclipse
Wechseln zu: Navigation, Suche

Wie jede moderne IDE bietet auch Eclipse dem Anwender die Möglichkeit Quellcode in ausführbaren Code zu übersetzen. Allerdings ist diese Funktionalität nicht Teil des Eclipse-Frameworks, da z.B. der jeweilige Compiler spezifisch für die jeweilige Programmiersprache ist und selbige vom jeweiligen IDE-Plug-In abhängt (z.B. das Eclipse Java-Plug-In) das Eclipse-Framework steuert in diesem Fall nur ein abstraktes Modell, der zu einem Build-System, gehörenden Komponenten und deren (ebenfalls abstrakte) Koordinierungslogik bei.

Build & Kompilieren

Um die Verwendung des Build-Systems nicht auf Code-Dateien einzuschränken, abstrahiert Eclipse den Kompiliervorgangs weiter auf den Begriff des Build. In Eclipse wird mit dem Begriff Build ein abstrakter Vorgang bezeichnet, der aus Quell-Ressourcen jeglicher Art sogenannte "abgeleitete Ressourcen" (engl. derived resources) erstellt. Abgeleitete Ressourcen sind Ausgabedateien die sich jederzeit aus den Quellen wiederherstellen (ableiten) lassen. Dabei können die Quell-Ressourcen nicht nur Quellcodedateien sein sondern z.B. auch Konfigurationsdateien und andere. Gleiches gilt auch für die abgeleiteten Ressourcen , diese können nicht nur ausführbare Code-Dateien sondern z.B. Dokumentationen und andere sein.

Deklaration und Funktionsweise

Ein Builder besteht aus einem oder mehreren Java-Classfiles die die Funktionalität des Builders ausmachen, und weiters aus einer Zuweisung an einen Exstension Point in der Datei plugin.xml. Dabei muss eine der Java-Classfiles, die der Builer bereitstellt eine Klassen implementieren die von der abstrakten Klasse IncrementalProjectBuilder ableitet. Der Eintrag in plugin.xml gibt dem Eclipse-Framework bekannt das ein spezifischer Builder für einen Extensionspoint angemeldet wird. Dazu enthält der entsprechende Abschnitt in der Datei plugin.xml eine Id und den jeweiligen Extension-Point des Eclipse-Frameworks "org.eclipse.core.resources.builders". Weiters wird in der Deklaration das Classfile, das die Implementierung des Builders enthält, angegeben und ob dieser Builder einer Nature zugeordnet ist. Das Eclipse Framework stellt die zur Verfügung stehenden Builders aufgrund der Einträge in der plugin.xml fest. Es gibt allerdings auch die Möglichkeit, unter Umgehung der Deklaration in plugin.xml, einen Builder direkt bei einem Projekt oder Workspace zu registrieren.

Beispiel für eine Deklaration eines Builders

 <extension
         id="myBuilder"
         name="My Project Builder"
         point="org.eclipse.core.resources.builders">
       <builder
            hasNature="true">
         <run
            class="besi.builderdemo.plugin.builder.MyBuilder">
         </run>
       </builder>
   </extension>

Erläuterung der Basisklasse IncrementalProjectBuilder Die Basisklasse IncrementalProjectBuilder stellt aus Sicht des Eclipse-Frameworks die Schnittstelle zu einem spezifischen Builder dar. Die darin enthaltenen Methoden werden vom Eclipse-Framework aufgerufen, wobei die wichtigsten die Methoden build und clean sind. Dabei ist für die Methode clean bereits eine Default-Implementierung vorhanden (die allerdings nichts macht). Zusätzlich sind in dieser Klasse die Konstanten für die verschiedenen Arten (Kind) von Builds definiert. Eine konkrete Implementierung eines Builders muss somit mindestens die Methode IProject[] build(int kind, Map args, IProgressMonitor monitor){ implementieren.

Dabei gibt kind eben die Art des Builds an der angestossen wurde (FULL_BUILD,AUTO_BUILD,INCREMENTAL_BUILD,CLEAN_BUILD), args gibt allfällige Build-Parameter an, das Argument IProgressMonitor ermöglicht es in der Build-Logik eine Fortschrittsanzeige zu aktualisieren.


Die Funktionsweise des Builder ist, wie die Basisklasse IncrementalProjectBuilder vermuten lässt, auf inkrementelle Builds ausgerichtet. Der Builder wird vom Eclipse-Framework über alle Änderungen benachrichtigt, die an Ressourcen vorgenommen werden, die sich in Projekten befinden die dieselbe Nature haben wie der jeweilige Builder. Das Eclipse-Framework übergibt mit der Benachrichtigung detaillierte Informationen über die Ressourcen die sich geändert haben (Dateinamen) und die Art der Änderung (New,Delete,Update). Die Entscheidung ob der Builder aktiv wird bleibt dem Builder überlassen, in der Regel werden aber immer nur die Ressourcen neu übersetzt die sich geändert haben. Besonders bei Quellcode-Dateien muss ein Inkrementeller Builder immer prüfen ob auch abhängige Classfiles neu compiliert werden müssen , wenn sich z.B. die Schnittstelle zu einer Klasse geändert hat. Eine Kernfunktion bei der Implementierung eines inkrementellen Builders ist somit die korrekte Führung des Build-State, nur so kann sichergestellt werden dass die Quell-Ressourcen und die abgeleiteten Resourcen immer synchron sind. So findet man z.B. bei Eclipse keinen Menüpunkt der eine explizite "Compile" - Funktion hat, da eben davon ausgegangen wird dass der Compiler alle Änderungen die vom Programmierer vorgenommen werden, sofort und im Hintergrund kompiliert, und somit ein Projekt nach jeder kleinen Änderung in einem vollständig übersetzten Zustand ist. Zusätzlich definiert die Schnittstelle zum Builder allerdings auch die Möglichkeit ein "Full-Build" zu machen, quasi als Workaround falls ein Builder nicht in alle Fällen in der Lage ist den Build-State synchron zu halten. Eclipse macht beispielsweise ein "Fullbuild" dann wenn die Funktion "Clean" aufgerufen wird.

Um eine Vorstellung von der Komplexität der Abläufe in einem Inkrementellen Builder zu bekommen, kann man den Java-Builder von Eclipse als Beispiel heranziehen: Der Buildstate eines Projekts beinhaltet eine Liste aller Typen die in diesem Projekt enthalten sind und jeweils eine Liste von Typen die auf einen Typ verweisen. Stellt der Compiler nun eine strukturelle Änderung an einem Typ fest , wird im Buildstate nachgeschaut welche Dateien auf den veränderten Typ verweisen um diese dann in eine Build-Queue aufzunehmen, die im Anschluss in einem Batch-Build abgearbeitet wird.

Folgende Übersicht zeigt die wesentlichen Interaktionen zwischen dem Eclipse-Framework und dem Builder-Plug-In. Dabei bezeichnet die Nummerierung die zeitliche Reihenfolge der Aufrufe.

Interaktion zwischen Eclipse Framework und dem Java-Builder-Plugin

Beispiel für eine Ableitung von IncrementalProjectBuilder

  public class MyBuilder extends IncrementalProjectBuilder{   
 
    @Override
    protected IProject[] build(int kind, Map args, IProgressMonitor monitor){
 
             //Änderungen an den Ressourcen feststellen
             IResourceDelta resDelta = this.getDelta(getProject());
	     if(resDelta == null)
			return null;
 
             DeltaVisitor visitor = new DeltaVisitor();
 
	     try{
	        resDelta.accept(visitor);
	     } catch (CoreException e) {			
	         e.printStackTrace();
	     }
 
             //weitere Build-Logik
             ...
    }
 
  }

Das Interface IResourceDelta Die Builder-Basisklasse IncrementalProjectBuilder ermöglicht über die Methode getDelta die Abfrage aller geänderten Ressourcen, die einem konkreten Builder (über die assoziierten Projekte und den gemeinsamen Natures) zugeordnet sind. Das damit zurückgebene Objekt vom Typ IResourceDelta ist der Wurzelknoten eines Objekt-Baumes dessen Kind-Knoten wiederum Objekte vom Typ IResourceDelta sind. IResourceDelta stellt über die Methode accept dem | Visitor-Pattern folgend, eine komfortable Möglichkeit zur Verfügung den Baum zu traversieren. Dazu ist ein entsprechender Visitor zu bereitzustellen der das Interface IResourceDeltaVisitor implementiert. Dieser bekommt dann für jeden Knoten im Baum einen Aufruf der Methode visit.

public class DeltaVisitor implements IResourceDeltaVisitor {
 
 
    public boolean visit(IResourceDelta delta) {
        switch (delta.getKind()) {
        case IResourceDelta.ADDED :
            // handle added resource
            break;
        case IResourceDelta.REMOVED :
            // handle removed resource
            break;
        case IResourceDelta.CHANGED :
            // handle changed resource
            break;
        }
 
        //spezifische Logik für die Protokollierung
        ...
 
        return true;
    }
 
    //Weitere Methoden
    ... 
}