| <?xml version="1.0" encoding="UTF-8"?> |
| <!-- *************************************************************************** |
| * Copyright (c) 2009 - 2010 eXXcellent solutions gmbh and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| ******************************************************************************/ |
| |
| @author Achim Demelt - Initial contribution |
| |
| --> |
| |
| <chapter id="incrementalGeneration"> |
| <title>Incremental Generation</title> |
| |
| <para> |
| As projects become larger, so typically do their models. However, the larger |
| the models are, the longer the code generation process takes. In a mature |
| project, a developer typically changes only a small part of a large model. |
| Performing a full code generation process for the whole model slows down the |
| development cycle considerably due to various factors: |
| |
| <itemizedlist> |
| <listitem> |
| The whole model must be traversed, and each statement in the Xpand |
| templates must be executed. The larger the model is and the more Xpand |
| templates you have, the higher the negative impact is. |
| </listitem> |
| <listitem> |
| All generated files are written to disk. The I/O operation itself is |
| one major contributor to the overall elapsed time. What's more, files |
| are typically post-processed by beautifiers, which is another time |
| consuming operation. If you are working with protected regions, the |
| impact is even more dramatic. |
| </listitem> |
| <listitem> |
| Since every file has a new timestamp after code generation, typically |
| a compiler will pick up these new files and start compilation, which |
| adds more CPU and I/O cycles to the process. |
| </listitem> |
| </itemizedlist> |
| |
| Considering that for a small change in the model only a fraction of the |
| generated files actually do change their contents, performing a full |
| generation is obviously a waste of time. |
| </para> |
| |
| <para> |
| Beginning with the Helios release train (Eclipse 3.6, Xpand 0.8), Xpand |
| now ships with an incremental generation facility. This works very similar |
| to the incremental Java compiler in Eclipse. It detects which parts of a |
| model have changed since the last generation process. It then |
| determines which files need to be generated due to that change and which |
| are unaffected by it. Only the former are the regenerated, while the |
| latter remain untouched. |
| </para> |
| |
| <para> |
| The following sections explain how this incremental generation feature |
| works and how you can use it. |
| </para> |
| |
| <section id="incrementalGeneration_background"> |
| <title>Technical Background</title> |
| |
| <para> |
| The key to incremental generation lies in knowing which element in a model |
| was used to generate which file. This information can easily be computed |
| during generation, by tracking which parts of the model are |
| accessed in the context of any given |
| <code>«FILE»</code> statement. A callback for the |
| Xpand generator does this job and builds up a so-called |
| <emphasis>trace model</emphasis> on-the-fly. |
| </para> |
| |
| <para> |
| The second important information is the actual change that has occurred |
| in a model. There are basically two ways to compute this. One is to |
| attach a change listener when editing the model and capture the change |
| as it happens. The other way is to keep a backup copy of the model and |
| compare the old version with the current version to compute the change. |
| See <xref linkend="incrementalGeneration_performance" /> |
| for pros and cons of each of the two ways. In either |
| case, the result is a so-called <emphasis>diff model</emphasis>. |
| </para> |
| |
| <para> |
| When we know which parts of a model have changed, and we also know which |
| files have been produced based upon these parts of the model, we can then |
| skip the generation of all other files, thus performing incremental |
| generation. |
| </para> |
| </section> |
| |
| <section id="incrementalGeneration_usage"> |
| <title>Using Incremental Generation</title> |
| |
| <section id="incrementalGeneration_usage_facade"> |
| <title>The Incremental Generation Facade</title> |
| |
| <para> |
| The easiest way to benefit from incremental generation is to use the |
| <emphasis>IncrementalGenerationFacade</emphasis> workflow component: |
| </para> |
| |
| <programlisting> |
| <workflow> |
| <component id="incremental" |
| class="org.eclipse.xpand2.incremental.IncrementalGenerationFacade"> |
| <newModelFile value="path/to/your/model.file" /> |
| <oldModelFile value="path/to/backup/model.file" /> |
| <traceModelFile value="path/to/store/trace/model.trace" /> |
| <outlet path="path/to/your/outlet/" overwrite="true"/> |
| </component> |
| |
| <component id="generator" class="org.eclipse.xpand2.Generator"> |
| <expand value="your::template::Root FOR model" /> |
| <outlet path="temp/" overwrite="true"/> |
| <metaModel class="org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel" /> |
| <vetoableCallback idRef="incremental" /> |
| </component> |
| </workflow> |
| </programlisting> |
| |
| <para> |
| The <emphasis>IncrementalGenerationFacade</emphasis> takes four |
| parameters: |
| |
| <itemizedlist> |
| <listitem> |
| <para> |
| The <emphasis>newModelFile</emphasis> is the file |
| path where the model to generate is stored. This file is stored |
| in a model slot named <emphasis>model</emphasis> |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| The <emphasis>oldModelFile</emphasis> is the file |
| path where a copy of the previous state of the model is stored. |
| The model is automatically copied to this location after the |
| generation process and kept between generator invocations. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| The <emphasis>traceModelFile</emphasis> is the file |
| path where the <emphasis>trace model</emphasis> of the generation |
| process is stored between generator invocations. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| A regular <emphasis>outlet</emphasis> list that must match the |
| one given for the regular generator invocation. |
| </para> |
| </listitem> |
| </itemizedlist> |
| |
| The <emphasis>IncrementalGenerationFacade</emphasis> component must then |
| be passed as a <emphasis>vetoableCallback</emphasis> parameter to the |
| invocation of the Xpand <emphasis>Generator</emphasis>. |
| </para> |
| |
| <para> |
| With the simple workflow given above, you should be able to observe |
| that for any given change in the model, only the files affected by |
| that change are regenerated, while all others remain untouched. Even |
| deleting elements will result in specific (previously generated) files |
| being deleted from the hard disk. |
| </para> |
| |
| <para> |
| Note that you have to use file paths for all models because they |
| are physically copied on the hard disk. Passing locations that |
| can only be resolved from the classpath is not possible. |
| </para> |
| </section> |
| |
| <section id="incrementalGeneration_usage_callback"> |
| <title>The Incremental Generation Callback</title> |
| |
| <para> |
| While the <emphasis>IncrementalGenerationFacade</emphasis> is easy |
| to use, it is rather restricted in its capabilities and fixed in the |
| operations it performs. Using the |
| <emphasis>IncrementalGenerationCallback</emphasis> gives you more |
| control over the steps involved. A typical workflow for incremental |
| generation needs to perform the following tasks: |
| |
| <orderedlist> |
| <listitem> |
| <para> |
| Read the (current) model into a slot. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Read the previous state of the model into another slot. This |
| may, of course, not exist, e.g. for the very first invocation. |
| Full generation must be performed in this case. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Compute the changes between the two versions of the model |
| (if possible) and |
| put that <emphasis>diff model</emphasis> into a slot. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Read the <emphasis>trace model</emphasis> computed during the |
| previous generator invocation and put it into a slot. |
| As with the old state of the model, this may not exist, which |
| also leads to full generation. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Initialize the <emphasis>IncrementalGenerationCallback</emphasis> |
| with the <emphasis>diff model</emphasis> and the |
| <emphasis>trace model</emphasis>. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Run the Xpand <emphasis>Generator</emphasis> component with |
| the <emphasis>IncrementalGenerationCallback</emphasis>. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Clean obsolete files, i.e. files that need to be deleted because |
| the corresponding elements in the model have been deleted. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Write the new <emphasis>trace model</emphasis> computed during |
| code generation to the hard disk so that it is available |
| for the next generation process. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Make a backup copy of the model so that it can be compared with |
| the next version upon subsequent generator invocation. |
| </para> |
| </listitem> |
| </orderedlist> |
| </para> |
| |
| <para> |
| This is a sample workflow that performs all these steps: |
| </para> |
| |
| <programlisting> |
| <workflow> |
| <!-- read new model --> |
| <component id="modelreader" class="org.eclipse.emf.mwe.utils.Reader" |
| uri="model/my.model" |
| firstElementOnly="true" |
| modelSlot="model" |
| /> |
| <!-- read old model, copied from previous run. may not exist, so ignore missing model --> |
| <component id="oldmodelreader" class="org.eclipse.emf.mwe.utils.Reader" |
| uri="temp/old.model" |
| firstElementOnly="true" |
| ignoreMissingModel="true" |
| modelSlot="oldmodel" |
| /> |
| |
| <!-- compute diff. --> |
| <component id="compare" class="org.eclipse.xpand2.incremental.compare.EmfCompare" |
| oldModelSlot="oldmodel" |
| newModelSlot="model" |
| diffModelSlot="diff" |
| /> |
| |
| <!-- read trace model, produced by previous run. may not exist, so ignore missing model --> |
| <component id="tracemodelreader" class="org.eclipse.emf.mwe.utils.Reader" |
| uri="temp/trace.trace" |
| firstElementOnly="true" |
| ignoreMissingModel="true" |
| modelSlot="oldtrace" |
| /> |
| |
| <!-- this is the actual incremental generation callback --> |
| <component id="incremental" |
| class="org.eclipse.xpand2.incremental.IncrementalGenerationCallback" |
| diffModelSlot="diff" |
| oldTraceModelSlot="oldtrace" |
| newTraceModelSlot="trace" |
| /> |
| |
| <!-- generate code --> |
| <component id="generator" class="org.eclipse.xpand2.Generator"> |
| <expand value="resources::templates::Test::Test FOR model" /> |
| <outlet path="somewhere/" overwrite="true"/> |
| <metaModel class="org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel" /> |
| <vetoableCallback idRef="incremental" /> |
| </component> |
| |
| <!-- clean obsolete files --> |
| <component id="cleaner" class="org.eclipse.xpand2.incremental.FileCleaner"> |
| <oldTraceModelSlot value="oldtrace" /> |
| <newTraceModelSlot value="trace" /> |
| <outlet path="somewhere/" overwrite="true"/> |
| </component> |
| |
| <!-- write trace model --> |
| <component id="tracemodelwriter" class="org.eclipse.emf.mwe.utils.Writer" |
| modelSlot="trace" |
| uri="temp/trace.trace" |
| /> |
| |
| <!-- make backup copy of model --> |
| <component id="copier" class="org.eclipse.emf.mwe.utils.FileCopy" |
| sourceFile="model/my.model" |
| targetFile="temp/old.model" |
| /> |
| </workflow> |
| </programlisting> |
| |
| </section> |
| |
| </section> |
| |
| <section id="incrementalGeneration_notes"> |
| <title>Additional Notes</title> |
| |
| <section id="incrementalGeneration_limitations"> |
| <title>Limitations</title> |
| <para> |
| The incremental generation process can only be used with EMF-based models. |
| That's because all intermediate artifacts |
| (<emphasis>diff model</emphasis> and <emphasis>trace model</emphasis>) |
| which reference the original models are also stored as EMF models. It is |
| therefore not possible to refer to other model formats. Moreover, you |
| should make sure that your model has stable IDs for the individual |
| model elements so that the model comparison doesn't run into any |
| ambiguities. |
| </para> |
| |
| <para> |
| Also note that at the moment, Xpand cannot track access to model elements |
| from JAVA extensions. This can lead to cases where a change in a specific |
| model element should trigger a specific (set of) |
| file(s) to be regenerated, but it actually doesn't. |
| That's because Xpand didn't know about the |
| model element being accessed during the original file generation, |
| so it has no indication that a regeneration is required. For that reason |
| you should try to access your model as much as possible from Xpand or |
| Xtend, and only resort to JAVA code when it is unavoidable. |
| </para> |
| </section> |
| |
| <section id="incrementalGeneration_performance"> |
| <title>Performance Considerations</title> |
| <para> |
| The main performance benefits of incremental generation come from |
| <emphasis>not</emphasis> doing things that are not necessary. Given |
| the workflow from <xref linkend="incrementalGeneration_usage_callback" />, |
| it may seem counterproductive to first |
| perform a costly model comparison operation before it can even be |
| determined whether a file has to be generated or not. |
| </para> |
| |
| <para> |
| While it is true that model comparison is a very expensive operation, |
| it turns out that it still outweighs the costs of unnecessarily |
| generating files, even if no postprocessing or subsequent compiler |
| invocation is involved. |
| </para> |
| |
| <para> |
| That said, it is definitely preferrable to do without a model comparison |
| and rather capture the changes to the model on-the-fly. So whenever |
| you are working in a controlled environment, you may want to consider a |
| customized integration of the generator invocation with the |
| model editors. |
| </para> |
| </section> |
| |
| </section> |
| </chapter> |