Karsten's Blog

September 28, 2011

Moving an Xtend generator into its own plugin

Filed under: Xtext — Tags: , — kthoms @ 9:23 AM

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:

  1. DSL runtime module
  2. org.eclipse.xtext.ui.shared.SharedStateModule
  3. DSL UI module
  4. Generator runtime module
  5. 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.

September 13, 2011

Setting template file encoding in Xpand workflow

Filed under: Xpand — kthoms @ 2:14 PM

A typical problem when running Xpand is file encoding of the Xpand templates. Xpand uses guillemot brackets («»), which are not available in all character sets. Often, ISO-8859-1 and UTF-8 are good choices for encoding Xpand templates. However, on Mac the default encoding is MacRoman, and I would recommend to change that for interoperability reason. I would recommend changing the resource encoding setting on the root folder containing Xpand templates, or even the project, and checking in the settings. This way the encoding is shared amongst team members.

When executing the Xpand generator with MWE it is important that the templates are read with the right encoding. Otherwise you will get an error like this:
org.eclipse.internal.xtend.xtend.parser.ParseException: no viable alternative at character '�' on line 159
Xpand’s Generator component has a property fileEncoding, but this is the encoding used by the outlets to write files. If you need to change the behavior for reading files, you need to set the encoding value of the ResourceManager. The configuration is like this:

MWE1:


    ...




MWE2

  component = org.eclipse.xpand2.Generator {
    ...
    resourceManager = org.eclipse.xtend.expression.ResourceManagerDefaultImpl {
     	fileEncoding = "ISO-8859-1"
    }
  }

The Silver is the New Black Theme Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 375 other followers