In January, I stated some goals that I wanted to focus on this year. The year is almost over, so how did I do?

Proper continuous integration of platforms

I wrote this:

I want to set up proper continuous integration of MPS with mbeddr platform and IETS3 (KernelF). The main/master branch of each library should be regularly built against the latest successful build of MPS master branch. If we can build them regularly, it should be easier to pinpoint compatibility problems because the delta from the previous working build will be smaller.

And I have succeeded in bringing MPS-extensions and mbeddr to build against the master branch of MPS. I haven’t yet implemented this for IETS3 but Alexander Pann is currently working on that.

This required a few bits and pieces:

  1. Repackaging and uploading MPS prereleases (this was already available in January).
  2. Cascading merges from branches for older versions of MPS to newer versions (also available in January).
  3. Automating migrations and running them from the command line as part of the build, in order to make sure that no migrations are pending nor outdated concepts used after the cascading merge.
  4. Setting up Dependabot, and later Renovate, to regularly upgrade the used MPS version (and other dependencies).

After all the above pieces were in place, we could change the master branch of MPS-extensions and mbeddr to build against the MPS prereleases.

Tightening the integration checks

Here is what my goal was:

While simply building a project against the latest MPS version will show some problems, it is also necessary to do more automated checks. The CI builds should also check the projects for any deprecated code, code that wasn’t migrated to the latest version of a language (this is likely to happen due to cascading merges) or other violations. Daniel Ratiu’s mps-qa library provides “linters” with most of these checks, as well as whitelisting mechanisms in order to improve the quality gradually, and I am going to look into adding these linters as part of the build for each library.

My vision here was not only to run migrations, but also to check the project for any new deprecated code that was introduced (or code that became deprecated with the new MPS prerelease). I have looked at mps-qa and while it is a very useful library, I found several things I disliked about the approach:

  1. I would like to keep the “whitelist” of deprecated code that is currently in use in a text file rather than in an MPS model because I would like it to be human-readable and easy to diff/blame/annotate with Git.
  2. I would like to improve on error reporting and I feel that using mps-qa would make it impossible by design because mps-qa integrates into the model checking framework of MPS.

I have prototyped a tool to produce a deprecation report in a YAML-like format but I haven’t implemented it in any projects yet because of limited time and will.

Achievements and new problems

Having the continuous integration with MPS master has helped us to quickly migrate to MPS 2024.1 and will likely help with the migration to 2024.3 as well.

However, as is often the case in software development, solving one problem creates multiple new ones. After automating cascading merges, dependency upgrades, and migration checks, we now have the problem of having many PRs.

We have improved this somewhat by enabling auto-merge for cascading PRs so if there are no migrations necessary, they will be merged automatically as soon as the build is green. But some PRs require mindless but nevertheless manual changes to merge. For example, in a recent version of MPS the format of module descriptors changed slightly so that the order of attributes in a certain XML elements got reversed. This of course produces errors when trying to merge pull requests from the previous version into this one.

A solution to this would be to run migrations automatically on a PR and committing changes. This would immediately “unlock” a new problem: what if a migration is written incorrectly and running it a second time produces more changes? We would need some kind of protection against infinite loops.

Another problem is that there are now several projects for which regularly PRs are being submitted to upgrade them to newer MPS versions, and it is becoming difficult to keep track of the state of all proposed PRs and regularly merge them. A dashboard (and enabling auto-merge for Renovate) would help here.

Incremental builds and modularizing the platform libraries

These were my ideas back in January:

Incremental builds

As more and more checks will be added to the build, it is going to become slower. In order to mitigate this, we will have to look into making the build incremental, so that only affected parts are rebuilt. (Of course, a new MPS version will likely mean that the entire library has to be rebuilt, but a change in one component of a platform should not require rebuilding and reanalyzing unrelated components.)

I have already had some success and gained some experience with incremental builds in a client project but adding incrementality requires providing more information about the dependencies (inputs and outputs) of a particular component from MPS to the build tool. MPS already keeps project information in two places: the .mps directory and the MPS build scripts, and we would be adding a third, so this needs some thinking and automation.

Modularizing the platform libraries

I hope that by improving the tooling around incremental builds, it will also be possible to split the current MPS platforms into smaller components. It is currently impossible, for example, to use just the assessments framework from the mbeddr platform without bringing along most of the rest of the platform. My inspiration here is Spring Framework which is releasing multiple libraries that can be used together but do not have to.

The biggest obstacle to achieving this currently is that this would mean lots of small build scripts, both in MPS and in Gradle, and I would like to avoid having to keep them all in sync. This may also need some changes in the way MPS build scripts describe dependencies (as they cannot handle transitive dependencies very well, for example).

I haven’t made any progress on that, unfortunately. I have an idea to try: reading MPS module descriptors (.msd and .mpl files) to find out the dependencies of each individual module, then leverage Gradle’s support for incremental and parallel builds in order to build modules in parallel, but I’m afraid that modelling Gradle dependencies to match MPS is going to be very tricky, so it’s something that will have to remain a dream for now.

Another problem that would need to be solved is specifying plugins and plugin dependencies. In fact, this is currently the one problem for which the MPS build language offers a unique solution that would be more difficult to replicate outside of MPS. One could create a DSL in Gradle to specify plugins, their contents and dependencies, but checking them for correctness would require knowledge of the plugins bundled with MPS.

Plans for the next year

At the end of the year I can’t help feeling quite exhausted from the build-related work. I feel like it’s important yet at the same time I don’t see anybody else recognize the importance of having a frictionless build and join my efforts. This has led me to gradually spend less and less time on the builds, and in the next year I will likely prioritize writing articles over improving builds.