Generation plan
Generation plan
Generation plans allow developers to specify the desired order of generation for their models explicitly and thus gain better control over the generation process.
Motivation
Specifying mutual generator priorities may become cumbersome for larger projects. Additionally, in order to specify the priorities the involved languages need to know about one another by declaring appropriate mutual dependencies, which breaks their (sometimes desired) independence. Generation plans put the responsibility of proper ordering of generation steps into a single place - the generation plan. They allow language designers to provide intuitive means for end-user models to be processed in a desired order. Generation plans list all the languages that should be included in the generation process, order them appropriately and optionally specify checkpoints, at which the generator shall preserve the current transient models. These models can then be used for automatic cross-model reference resolution further down the generation process.
Defining a generation plan
In order to create a generation plan, you first need to create a model. You may consider giving the model a genplan stereotype to easily distinguish it from ordinary models, but this is not mandatory.
After importing the jetbrains.mps.lang.generator.plan and jetbrains.mps.lang.smodel languages, you can create root node of the Plan concept, which will represent your generation plan:
The generation plan consists of transforms and checkpoints.
It is also possible to specify the required generators explicitly.
Transform represents a generation step. The transform statement is parametrized with a concrete language and with either of these options:
Transform - generate the language specified.
Target - run all generators for languages that manifest the specified language as their 'target' language (in langauge dependencies).
Extend - run all generators for languages that manifest the specified language as their 'extended' language (in langauge dependencies).
Apply represents an explicit invocation of a particular generator. The apply with extended statement applies in a single step the specified generators and those that extend them. This allows the language designer to accommodate for possible extensions.
Fork with enables the plan to fork the generation process and fork off another generation plan.
The referenced generation plan will start with a copy of the model in the state it was at the fork step and proceeds as a regular transformation. From this point the two generators will run independently.
Checkpoints represent points during the generation, at which the intermediate models should be preserved. References that will be resolved later in the generation will be able to look-up nodes in the stored intermediate models for their resolution through mapping labels. You can view these checkpoint models in the Project View tool window:
These intermediate checkpoint models are preserved until you shutdown MPS or until you rebuild the models or the models that they depend on. Alternatively you can remove them manually:
Checkpoints provide synchronization points between different plans. The checkpoint models are denoted with a stereotype that matches the name of a checkpoint the model has been created with. Models are persisted alongside the generated sources using the naming scheme of <plan-name>-<checkpoint name>.
Distinct statements allow for capturing different aspects of a checkpoint.
declare checkpoint <name> statement - specify a label that generator plans could share among themselves. This statement does not record/persist the state of the transformed model, it is a mere declaration that other generation plans will be able to refer to.
checkpoint <checkpoint> - records/persists the state of the transformed model. It can either declare a checkpoint in-place or refer to a declared checkpoint.
synchronize with <checkpoint> statements - instructs the generation plan to look up the target nodes in persisted models of the specified checkpoint, but do not persist its own nodes (read-only access to the check-point). This statement doesn't introduce any new state, but references a checkpoint declared elsewhere.
Specifying a generation plan for models
Modules that should have their models built following a generation plan need to enable the Custom generation facet and point to the actual desiredgeneration plan:
Verifying the generation plan
The Show generation plan action in the models' pop-up menu will correctly take the generation plan into account when building the outline for the generation:
Details on the origin of the generation plan as well as clickable link to the actual plan are included it the report:
If any of the used languages is not taken care by the generation plan, it is mentioned in the plan, as well:
To view the original, on generator priorities based, generation plan that would be used without the explicit generation plan script, hold the Alt key while clicking the Show Generation Plan menu entry:
Note that the report states in the header that it is not the currently active plan.
Using DevKits to associate a generation plan
DevKits can associate a generation plan, as well.
First add dependencies on languages and solutions that the DevKit should be wrapping. Then specify the Generation plan from within the imported solutions, which will be associated with the DevKit. Any model that imports that DevKit will get the DevKit's associated generation plan applied to it.
Cross-model generation
Model is the unit of generation in MPS. All entities in a single model are generated together and references between the nodes can be resolved with reference macros and mapping labels. Mapping labels, however, are not by default accessible from other models. This complicates generation of references that refer to nodes from other models. Fortunately, regular mapping labels can support mutually independent generation of models with cross-references, if the models share a generation plan. The mechanism leverages checkpoints to capture the intermediate transient models and then use them for reference resolution.
In essence, to preserve cross-model references when generating multiple models, make sure that your models share a generation plan. That generation plan must define checkpoints at moments, when the mapping labels that are used for cross-model reference resolution have been populated. The rest will be taken care of automatically. The reference macros can resolve nodes from mapping labels through the usual genContext.get output by label and input (for nodes generated through reduction or root mapping rules) or genContext.get output for model (for nodes generated through conditional root mapping rules) ways.
Linking checkpoint models
Models created at checkpoints now keep a reference to the previous checkpoint model in the sequence. This helps the Generator discover mapped nodes matching input that spans several generator phases.
Debug information in the checkpoint models
To ease debug of cross-model generation scenarios, a dedicated root inside each checkpoint model lists the mapping label names along with pointers to the stored input and output nodes. Investigation of the mapping labels exposed at each checkpoint can substantially help to debug cross-model generation scenarios and fix unresolved references. Thus, next time your cross-model reference doesn't resolve, inspect corresponding checkpoint model to see if there's indeed a label for your input.
Generating language descriptor models
Generation plans have been enhanced to generate descriptor models for languages (known as <language.name>@descriptor
). The structure, textgen, typesystem, dataflow and constraints aspects are now generated with generation plans and they use the new cross-model reference resolution mechanism.
Custom aspects defined by language authors can join the generation plan, as well. If you got a custom aspect, you should make sure that its generator extends the generator of jetbrains.mps.lang.descriptor
language, as this is the way to get custom extensions activated for the plan.