Xtext Tip: Migrating to manual EMF model
Xtext is very good at inferring a metamodel from a grammar but it has its limitations. After reaching a certain complexity, I think it is important to decouple the grammar from the metamodel. This is allows to separate concerns: the metamodel becomes the “business model layer”, and the grammar becomes the “presentation layer”. To do this, we need to transform our project to this new setup. In this post I explain how. The full code for this example can be found here.
Overview
There are several steps to perform this migration:
- Move the generated model from the
model/generated/folder. - Update the location of the
.ecoreand.genmodelfile in the files that reference them. - Update the grammar (
.xtextfile) so that the model is no longer generated. - Modify the
MWE2workflow to process automatically generate the model.
Move the generated model
Xtext generates an .ecore and a .genmodel file from the grammar at folder model/generated/. Since the file is no longer generated, it is a good idea to move it to another folder, for example, the model/folder. It is important remove the original .ecore and .genmodel files, since there might be strange errors in the editors if there are two .ecore files with the same identifiers.
Update the location
There are several files in the project that reference the files in the model/generated folder. These are:
build.properties, where we need to update the path in thebin.includessection.plugin.xml, where we need to update the path to thegenmodelfile.
Update the grammar
If you are using a generated ecore file, then you have a line like this in your .xtext file:
generate block "http://www.idiomaticsoft.com/dsl/block/Block"
This line needs to be replaced by the following line:
import "http://www.idiomaticsoft.com/dsl/block/Block"
Thus indicating Xtext that it needs to import the metamodel with that identifier.
Modify the MWE2 workflow
The mwe2 workflow is heavily modified. We start from the the mwe2 file of the In a block language and transform it as follows:
module com.idiomaticsoft.dsl.block.GenerateBlock
import org.eclipse.xtext.xtext.generator.*
import org.eclipse.xtext.xtext.generator.model.project.*
import org.eclipse.emf.mwe.utils.*
var rootPath = ".."
var projectName = "com.idiomaticsoft.dsl.block"
var runtimeProject = "../${projectName}"
Workflow {
// This cleans the directory before generating
component = DirectoryCleaner {
directory = "${runtimeProject}/src-gen"
}
// This handles the generation of the model classes
component = org.eclipse.emf.mwe2.ecore.EcoreGenerator {
genModel = "platform:/resource/com.idiomaticsoft.dsl.block/model/Block.genmodel"
srcPath = "platform:/resource/com.idiomaticsoft.dsl.block/src-gen"
}
// This handles the generation of the standalone setub
bean = org.eclipse.emf.mwe.utils.StandaloneSetup {
scanClassPath = true
platformUri = "${rootPath}"
}
component = XtextGenerator {
// We tell the xtext generator to avoid cleaning, because otherwise it
// would remove the classes generated by the ecore
cleaner = {
enabled = false
}
configuration = {
project = StandardProjectConfig {
baseName = "com.idiomaticsoft.dsl.block"
rootPath = rootPath
runtimeTest = {
enabled = true
}
eclipsePlugin = {
enabled = true
}
eclipsePluginTest = {
enabled = true
}
createEclipseMetaData = true
}
code = {
encoding = "UTF-8"
lineDelimiter = "\n"
fileHeader = "/*\n * generated by Xtext \${version}\n */"
preferXtendStubs = false
}
}
language = StandardLanguage {
name = "com.idiomaticsoft.dsl.block.Block"
fileExtensions = "block"
// we tell the generator that we are using our Block.genmodel file
referencedResource = "platform:/resource/com.idiomaticsoft.dsl.block/model/Block.genmodel"
serializer = {
generateStub = false
}
validator = {
composedCheck = "org.eclipse.xtext.validation.NamesAreUniqueValidator"
// Generates checks for @Deprecated grammar annotations, an IssueProvider and a corresponding PropertyPage
generateDeprecationValidation = true
}
generator = {
generateXtendStub = true
}
junitSupport = {
junitVersion = "5"
}
}
}
}
Conclusion
After all those modifications, we can modifying our .ecore files separately and they will be integrated with our Xtext grammar.