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
.ecore
and.genmodel
file in the files that reference them. - Update the grammar (
.xtext
file) so that the model is no longer generated. - Modify the
MWE2
workflow 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.includes
section.plugin.xml
, where we need to update the path to thegenmodel
file.
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.