xtext - export dsl as runnable jar
If you write an xtext-dsl you get a great editor and generator inside of eclipse. But there are times you want the generator to generate the code outside of eclipse. e.g. for using in a automated workflow.
I'm not sure what new options were introduced since XText 2.8 but I guess it is worth mentioning nonetheless.
Here are the steps to take:
Make sure to add generateJavaMain = true to your DSL's mwe2-file:
fragment = generator.GeneratorFragment auto-inject { generateJavaMain = true }
Next time you rerun mwe2 (right-click on your mwe2-file => run-as mwe2) a file called Main.java will be generated in the Generator-Folder
Create a Run-Configuration to start the generated Main.java
- Export your dsl as runable-jar:
- right-click on your dsl-project => export=>Java=>Runnable Jar File
- Select the Launch-Configuration that is starting the Main.java
- Select the output-Folder
Using the Save as Ant-Script is optional but becomes handy if you are testing a lot.
Once you have create this you can call it to pack the jar again. (It will not build, this is usually done automatically by eclipse) e.g. like thisant -f packjar.xml
Your basically done. You can now create your generated output by calling:
java --jar mydsl-runnable.jar input.mydsl
This will usually create the output in a src-gen-folder. If you want to override this behaviour you can use OutputConfigurations.
Optional:
To change the outputFolder you can change the outputPath in the Main.java:
// configure and start the generator fileAccess.setOutputPath("src-gen/");
More advanced are output-configurations. In this you can e.g. tell the dsl's filesystem how to behave if a file is already generated. In one project I wanted some files to overwrite old versions and some files to be created once and never overwrite those (e.g. because I want the user to add their logic in there). Here is an example. You have to replace the setOutputPath-Part with those lines in Main.java:
String outputFolder = "./out";
OutputConfiguration defaultOutput = new OutputConfiguration(IFileSystemAccess.DEFAULT_OUTPUT);
defaultOutput.setDescription("Output Folder");
defaultOutput.setOutputDirectory(outputFolder+"/src-gen");
// tell the fsa to override(!) existing resources!!
defaultOutput.setOverrideExistingResources(true);
defaultOutput.setCreateOutputDirectory(true);
defaultOutput.setCleanUpDerivedResources(true);
defaultOutput.setSetDerivedProperty(true);
OutputConfiguration genonceOutput = new OutputConfiguration("gen-once");
genonceOutput.setDescription("Generate Once Output Folder");
genonceOutput.setOutputDirectory(outputFolder+"/src-nodes");
// tell the fsa to NOT override existing resources!!
genonceOutput.setOverrideExistingResources(false);
genonceOutput.setCreateOutputDirectory(true);
genonceOutput.setCleanUpDerivedResources(false);
genonceOutput.setSetDerivedProperty(false);
Map<String,OutputConfiguration> fsaConfs = new HashMap<>();
fsaConfs.put(IFileSystemAccess.DEFAULT_OUTPUT, defaultOutput);
fsaConfs.put("gen-once",genonceOutput);
fileAccess.setOutputConfigurations(fsaConfs);
You can access the different Configurations like this:
fsa.generateFile(String fileName,String outputConfigurationName,CharSequence contents);
Obviously this all makes only sense if you also created something like this in your eclipse-runtime.
Sry, for the redundant code here. You actually should have one solution that get's called at an appropriate time. Ok, here we go:
- Create an xtend-class 'CustomOutputConfigurationProvider.xtend' in your dsl's main-folder with this content (make sure to adapt package and other things that might differ):
package org.tt.gen
import java.util.Set
import org.eclipse.xtext.generator.OutputConfiguration
import org.eclipse.xtext.generator.IOutputConfigurationProvider
import org.eclipse.xtext.generator.IFileSystemAccess
public class CustomOutputConfigurationProvider implements IOutputConfigurationProvider {
public static final String GEN_ONCE_OUTPUT = "gen-once";
/**
* @return a set of {@link OutputConfiguration} available for the generator
*/
override def Set<OutputConfiguration> getOutputConfigurations() {
val OutputConfiguration defaultOutput = new OutputConfiguration(IFileSystemAccess.DEFAULT_OUTPUT);
defaultOutput.setDescription("Output Folder");
defaultOutput.setOutputDirectory("./src-gen");
defaultOutput.setOverrideExistingResources(true);
defaultOutput.setCreateOutputDirectory(true);
defaultOutput.setCleanUpDerivedResources(true);
defaultOutput.setSetDerivedProperty(true);
val OutputConfiguration readonlyOutput = new OutputConfiguration(GEN_ONCE_OUTPUT);
readonlyOutput.setDescription("Read-only Output Folder");
readonlyOutput.setOutputDirectory("./src-nodes");
readonlyOutput.setOverrideExistingResources(false);
readonlyOutput.setCreateOutputDirectory(true);
readonlyOutput.setCleanUpDerivedResources(false);
readonlyOutput.setSetDerivedProperty(false);
return newHashSet(defaultOutput, readonlyOutput);
}
}
Now we need to tell the runtime-version to use our new custom outputconfiguration-provider. (Viva dependency injection!!).
Add following lines in your DSL's runtime-module-class' configure-method that is located in your DSL's main-folder:binder.bind(IOutputConfigurationProvider.class) .to(CustomOutputConfigurationProvider.class) .in(Singleton.class);
That should be it.