Un generatore incrementale di progetto è un oggetto che utilizza le risorse di un progetto in un modo particolare. I generatori incrementali di progetto vengono spesso utilizzati per applicare una trasformazione a una risorsa in modo da produrre una risorsa o un elemento di altro tipo.
I plugin forniscono alla piattaforma i generatori incrementali di progetto per implementare le trasformazioni di risorse specializzate. Ad esempio, il plugin JDT (Java Development Tooling) definisce un generatore incrementale di progetto che effettua la compilazione di un file di origine Java in un file di classe ogni volta che un file viene aggiunto o modificato in un progetto Java. Tiene traccia inoltre dei file dipendenti e li ricompila quando necessario.
Dal punto di vista dell'API, la piattaforma definisce due tipi di generazione base:
Le generazioni incrementali vengono impostate con un delta delle modifiche delle risorse. Il delta riflette l'effetto di rete prodotto da tutte le modifiche apportate alle risorse dal momento in cui il generatore ha eseguito l'ultima generazione del progetto. Questo delta è simile a quello utilizzato negli eventi di modifica delle risorse.
I progetti possono essere periodicamente eliminati dall'utente, per forzare la nuova generazione di un progetto completo la volta successiva che una generazione incrementale viene eseguita su quel progetto. L'eliminazione di un progetto comporta la rimozione delle informazioni di generazione, ad esempio gli indicatori di problemi e i file di classe.
I generatori possono essere descritti in modo migliore attraverso esempi. Il compilatore Java JDT viene guidato da un generatore incrementale di progetto Java per effettuare la ricompilazione di file influenzati da modifiche. Quando viene attivata una generazione completa (o una generazione incrementale dopo un'eliminazione), tutti i file .java del progetto vengono compilati. Eventuali problemi riscontrati vengono aggiunti come indicatori di problema nei file .java interessati. Quando viene attivata una generazione incrementale, il generatore ricompila in modo selettivo i file .java aggiunti, modificati o altrimenti influenzati che sono descritti nel delta delle risorse e aggiorna, se necessario, gli indicatori di problema. I file .class o gli indicatori che non sono più appropriati vengono rimossi.
La generazione incrementale ha dei chiari vantaggi di prestazione per i progetti contenenti centinaia o migliaia di risorse, la maggior parte delle quali resta invariata in un momento specifico.
L'obiettivo da un punto di vista tecnico della funzione di generazione incrementale consiste nel determinare esattamente le risorse che devono essere create nuovamente. Ad esempio, lo stato interno mantenuto dal generatore Java include elementi quali un grafico delle dipendenze e un elenco dei problemi di compilazione segnalati. Queste informazioni vengono utilizzate durante una generazione incrementale per identificare le classi che devono essere ricompilate in risposta a una modifica di una risorsa Java.
Sebbene la struttura di base per la funzione di generazione sia definita nella piattaforma, il lavoro vero e proprio avviene nel codice del generatore. I modelli per l'implementazione di generatori incrementali complessi vanno oltre l'obiettivo di questa discussione, poiché l'implementazione è dipendente dal design dello specifico generatore.
Un generatore può essere richiamato esplicitamente in uno dei seguenti modi:
In pratica, l'utente del workbench attiva una generazione selezionando i comandi corrispondenti nel menu di selezione delle risorse.
I generatori incrementali di progetto vengono anche richiamati esplicitamente dalla piattaforma durante una generazione automatica. Se abilitate, le generazioni automatiche vengono eseguite ogni volta che lo spazio di lavoro viene modificato.
Il punto di estensione org.eclipse.core.resources.builders viene utilizzato per fornire alla piattaforma un generatore incrementale di progetto. Il codice seguente mostra il modo in cui l'ipotetico plugin com.example.builders è in grado di fornire un generatore incrementale di progetto.
<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 identificata nel punto di estensione deve estendere la classe della piattaforma IncrementalProjectBuilder.
public class BuilderExample extends IncrementalProjectBuilder { IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { // aggiungere in questo punto la logica della generazione return null; } protected void startupOnInitialize() { // aggiungere in questo punto la logica di inizializzazione del generatore } protected void clean(IProgressMonitor monitor) { // add builder clean logic here } }
L'elaborazione della generazione inizia con il metodo build(), che comprende informazioni sul tipo di generazione richiesta. La generazione presenta uno dei seguenti valori:
Se è stata richiesta una generazione incrementale, viene fornito un delta delle risorse per descrivere le modifiche avvenute nelle risorse a partire dall'ultima generazione. Il frammento di codice seguente specifica ulteriormente il metodo 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; }
A volte può accadere che, durante la creazione del progetto "X", un generatore abbia bisogno delle informazioni sulle modifiche apportate in un altro progetto "Y". Ad esempio, se una classe Java in X implementa un'interfaccia fornita in Y. Durante la generazione di X, è disponibile un delta per Y mediante la chiamata a getDelta(Y). Per essere certi che la piattaforma sia in grado di fornire questi delta, è necessario che il generatore di X abbia dichiarato la dipendenza tra X e Y restituendo una matrice contenente Y derivato da una precedente chiamata a build(). Se un generatore non ha dipendenze, può semplicemente restituire un valore nullo. Per ulteriori informazioni, fare riferimento a IncrementalProjectBuilder.
La logica necessaria per elaborare una richiesta di generazione completa è specifica per il plugin. Può comportare una visita a tutte le risorsa del progetto oppure l'esame di altri progetti, se esistono dipendenze tra progetti. Il seguente frammento di codice suggerisce la modalità di implementazione di una generazione completa.
protected void fullBuild(final IProgressMonitor monitor) throws CoreException { try { getProject().accept(new MyBuildVisitor()); } catch (CoreException e) { } }
Il visitatore della generazione potrebbe voler eseguire la generazione per la risorsa specifica (e restituire true per continuare l'esame di tutte le risorse secondarie).
class MyBuildVisitor implements IResourceVisitor { public boolean visit(IResource res) { //creare la risorsa specificata. //restituire true per continuare l'esame delle risorse secondarie. return true; } }
Il processo di visita continua fino al completamento dell'esame di tutta la struttura delle risorse.
Quando si esegue una generazione incrementale, il generatore utilizza una struttura di delta di modifica delle risorse anziché una struttura di risorse completa.
protected void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException { // il visitatore esegue il lavoro. delta.accept(new MyBuildDeltaVisitor()); }
Il processo di visita continua fino al completamento dell'esame di tutta la struttura di delta delle risorse. La natura specifica delle modifiche è simile a quanto descritto in Implementazione di un listener di modifica delle risorse. Un'importante differenza consiste nel fatto che con i generatori incrementali di progetto si utilizza un delta delle risorse basato su un particolare progetto, non sull'intero spazio di lavoro.
Il workbench consente agli utenti di eliminare un progetto o un insieme di progetti prima di inizializzare una generazione. Questa funzione consente all'utente di forzare una nuova generazione da zero solo su determinati progetti. I generatori devono implementare questo metodo per eliminare qualsiasi indicatore di problema, nonché le risorse derivate nel progetto.
Per rendere disponibile un generatore per un particolare progetto, il generatore deve essere incluso nella specifica di generazione del progetto. La specifica di generazione di un progetto è un elenco dei comandi da eseguire, in sequenza, quando il progetto viene creato. Ogni comando specifica un singolo generatore incrementale di progetto.
NOTA: il nome generatore in un comando di generazione è l'id completo dell'estensione del generatore. L'id completo di un'estensione viene creato unendo l'id del plugin con l'id estensione semplice nel file plugin.xml. Ad esempio, un generatore con id estensione semplice "mybuilder" nel plugin "com.example.builders" avrà il nome "com.example.builders.mybuilder"
Il seguente frammento di codice aggiunge un nuovo generatore come primo generatore del relativo elenco.
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) { //aggiungere il generatore al progetto ICommand command = desc.newCommand(); command.setBuilderName(BUILDER_ID); ICommand[] newCommands = new ICommand[commands.length + 1]; // Aggiungerlo prima degli altri generatori. System.arraycopy(commands, 0, newCommands, 1, commands.length); newCommands[0] = command; desc.setBuildSpec(newCommands); project.setDescription(desc, null); }
La configurazione del generatore di un progetto avviene una sola volta, generalmente quando il progetto sta per essere creato.