One of the nice things that you get when starting an Xtext project is an Xtend based generator that is automatically invoked when you save an Xtext model file. The Xtend generator for your language resides in the .generator subpackage of your language. The problem with that is that it is usually no good idea to have the generator bundled with your language. It is a completely seperate feature which is reasonable to put it in an own plugin. Further, the DSL plugins must not depend on the generator plugin, the dependency must be vice versa. This article describes the steps that need to be done for this.
As a reference take the sources from project Spray. There you can find an concrete example where the described steps have been applied.
Create the generator plugins
Create two additional plugins: One for the runtime part of the generator, one for the UI contributions.
Generator runtime plugin
Move everything from the .generator subpackage of your DSL runtime project to the generator plugin. (In Spray this is org.eclipselabs.spray.generator.graphiti). The dependencies are the same as in the DSL runtime project, but add the DSL runtime plugin as additional dependency (see this MANIFEST.MF as reference). Don’t forget to add an xtend-gen source folder to the project.
Guice module for runtime plugin
Create a Module class extending AbstractGenericModule in the runtime plugin. In this module at least the IGenerator implementation class must be bound.
public class GraphitiGeneratorModule extends AbstractGenericModule {
public Class<? extends org.eclipse.xtext.generator.IGenerator> bindIGenerator() {
return SprayGenerator.class;
}
...
}
Generator UI plugin
The UI plugin must basically bind the JavaProjectBasedBuilderParticipant in its own module and register it through the org.eclipse.xtext.builder.participant extension point. These steps are required:
UI Guice Module
Create a module class extending AbstractGenericModule and bind JavaProjectBasedBuilderParticipant:
public class GraphitiGeneratorUIModule extends AbstractGenericModule {
private final AbstractUIPlugin plugin;
public GraphitiGeneratorUIModule (AbstractUIPlugin plugin) {
this.plugin = plugin;
}
@Override
public void configure(Binder binder) {
super.configure(binder);
binder.bind(AbstractUIPlugin.class).toInstance(plugin);
binder.bind(IDialogSettings.class).toInstance(plugin.getDialogSettings());
}
/**
* Bind the JavaProjectBasedBuilderParticipant in order to invoke the generator during the build.
*/
public Class<? extends org.eclipse.xtext.builder.IXtextBuilderParticipant> bindIXtextBuilderParticipant() {
return org.eclipse.xtext.builder.JavaProjectBasedBuilderParticipant.class;
}
...
}
Activator class
Create a class Activator which creates a Guice injector from the modules:
- DSL runtime module
- org.eclipse.xtext.ui.shared.SharedStateModule
- DSL UI module
- Generator runtime module
- Generator UI module
public class Activator extends AbstractUIPlugin {
private Injector injector;
private static Activator INSTANCE;
public Injector getInjector() {
return injector;
}
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
INSTANCE = this;
try {
injector = Guice.createInjector(Modules2.mixin(new SprayRuntimeModule(), new SharedStateModule(), new SprayUiModule(this), new GraphitiRuntimeModule(), new GraphitiGeneratorModule(), new GraphitiGeneratorUIModule(this)));
} catch (Exception e) {
Logger.getLogger(getClass()).error(e.getMessage(), e);
throw e;
}
}
@Override
public void stop(BundleContext context) throws Exception {
injector = null;
super.stop(context);
}
public static Activator getInstance() {
return INSTANCE;
}
}
Open the manifest editor. On the “Overview” page enter the Activator class name. Also check the options “Activate this plug-in when one of its classes is loaded” and “This plug-in is a singleton”.
ExecutableExtensionFactory
Create a class ExecutableExtensionFactory. Since this class won’t be public API it is a good approach to put it into an internal package.
public class ExecutableExtensionFactory extends AbstractGuiceAwareExecutableExtensionFactory {
@Override
protected Bundle getBundle() {
return Activator.getInstance().getBundle();
}
@Override
protected Injector getInjector() {
return Activator.getInstance().getInjector();
}
}
plugin.xml
Add a plugin.xml with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.xtext.builder.participant">
<participant
class="org.eclipselabs.spray.generator.graphiti.ui.internal.ExecutableExtensionFactory:org.eclipse.xtext.builder.IXtextBuilderParticipant">
</participant>
</extension>
</plugin>
Check that the ExecutableExtensionFactory class name matches yours.
Remove the GeneratorFragment
Open the .mwe2 workflow of your DSL project. Remove the entry for the GeneratorFragment:
// Code generator
fragment = generator.GeneratorFragment {
generateJavaMain = false
generateMwe = false
generatorStub = true
}
Now regenerate the DSL.
Add binding for IWorkspaceRoot
The generator fragment contributes a binding for IWorkspaceRoot to the UI Module of your DSL, which is required by the builder participant. Therefore this binding must be added manually to your UI Module.
public class SprayUiModule extends AbstractSprayUiModule {
...
public org.eclipse.core.resources.IWorkspaceRoot bindIWorkspaceRootToInstance() {
return org.eclipse.core.resources.ResourcesPlugin.getWorkspace().getRoot();
}
}
Result
As a result your DSL plugins should have no dependencies on your generator plugin. When you save a model file in your Eclipse instance with the deployed plugins the code generator should be invoked. The pattern described here would also allow to create multiple generator plugins for the same DSL which are invoked independently when building the project. Each of them registers its own builder participant and invokes its own generator.


