MPS 2024.1 Help

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 should 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.

genplan4.png

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:

genplan5.png

The generation plan consists of transforms and checkpoints.

genplan1.png

It is also possible to specify the required generators explicitly.

Gen2.png

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.

Gen2.png

fork with enables the plan to fork the generation process and fork off another generation plan.

ForkG.png

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.

The optional as property of fork with is a string modifier, which will serve as a trigger for the fork. The fork will only happen if the model that is being generated is owned by a module that has a facet (must be a generation target facet) with a facet id matching the string trigger.

fork as is an optional property of the generation plan hooks the current plan so that it forks any other generation plan without actually modifying them. The effective result is that the generation plan marked as a fork of a “main” generation plan will be evaluated as if it was referred to explicitly with the standard “fork” statement inserted at the very beginning of the “main” generation plan. The actual value of the property is a string that serves as a trigger for the fork to happen. Identically to the as property of a Fork with statement, the fork will only happen if the model that is being generated is owned by a module that has a facet (must be a generation target facet) with a facet id matching the string guard.

Fork as in gen plan

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:

genplan9.png

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:

genplan10.png

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.

Gen1.png

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 desired generation plan:

genplan3.png

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:

genplan7.png

Details on the origin of the generation plan as well as clickable link to the actual plan are included it the report:

genplan6.png

If any of the used languages is not taken care by the generation plan, it is mentioned in the plan, as well:

genplan61.png

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:

genplan7.png
genplan8.png

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.

devkit1.png

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.

devkit2.png

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.

checkpoints1.png

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.

Last modified: 19 July 2024