9 min. reading time

In the first part of this blog series we have introduced an example use case of integrating an Xtext DSL in a C# command-line calculator. This post will go into the details of how to use Maven to create a .NET assembly containing the DSL and Xtext runtime classes which are necessary for writing the calculator application.

After starting with an overview of the project structure, we will focus on how to create a .NET assembly by first constructing an Uber JAR that includes all dependencies and then converting it to an assembly using IKVM.NET. The source code of the project is available on GitHub.

Project structure

As build tool Xtext offers the choice between Maven and Gradle. In this example, Maven is employed as the Gradle build has only been added recently and the Maven build is more stable.

The basic project structure conforms to Xtext's default Maven layout and consists of the root project org.eclipse.xtext.example.arithmetics.parent and the Xtext DSL project org.eclipse.xtext.example.arithmetics, which are generated by Xtext's New Project wizard.

The root project is used as a parent project to specify shared configuration items and serves as an aggregator project that provides a single build for all sub-projects. The Xtext DSL project is essentially the Xtext Simple Arithmetics example, adapted for Maven and with a few improvements for the CLI calculator application. The org.eclipse.example.arithmetics.dotnet project was manually created and is responsible for creating a .NET assembly (a dll file) that can be used to consume the DSL in C#.

The project structure consisting of the root project and the child projects org.eclipse.example.arithmetics and org.eclipse.example.arithmetics.dotnet is summarized in the figure below:

Project structure

Creating a .NET assembly for consuming the DSL in C#

Now that we have an overview of the project structure, we can direct our attention to the actual creation of the DLL, which consists of two steps: First creating an Uber JAR from all transitive dependencies of the org.eclipse.example.arithmetics.dotnet project using the Maven Shade plugin and then converting the Uber JAR to a DLL as the project's main artifact using IKVM.NET.

In the following subsections we will provide a more detailed description of the DLL creation process.

Creating a Uber JAR with the Shade plugin

An Uber JAR is a JAR that includes a project and its direct and indirect dependencies and is produced by merging the JARs of the project and all its dependencies into a single, "fat" JAR.
In order to create an Uber JAR from the dependencies of the dotnet project, we include the Shade plugin in the POM of the project, which performs the merging of all Maven dependencies into a Uber JAR.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <executions>
    <execution>
      <id>ikvmShade</id>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <artifactSet>
          <excludes>
            <exclude>org.eclipse.xtext.example.arithmetics:org.eclipse.xtext.example.arithmetics.dotnet</exclude>
          </excludes>
        </artifactSet>
        <createDependencyReducedPom>false</createDependencyReducedPom>
        <outputFile>${project.build.directory}/${project.build.finalName}-shaded.jar</outputFile>
        <filters>
          <filter>
            <artifact>*:*</artifact>
            <excludes>
              <exclude>META-INF/*.SF</exclude>
              <exclude>META-INF/*.DSA</exclude>
              <exclude>META-INF/*.RSA</exclude>
            </excludes>
          </filter>
        </filters>
      </configuration>
    </execution>
  </executions>
</plugin>

As shown in the figure above, we need to exclude the signature files from the creation of the Uber JAR, in order to avoid SecurityExceptions that would be thrown at runtime because the signatures don't match anymore. The <exclude> element for the org.eclipse.example.arithmetics.dotnet project is added to prevent the Shade plugin from producing an error because it can't find the project's own JAR. This JAR doesn't exist due to the pom packaging, which will be explained further below.

Now, in order to include the DSL project in the Uber JAR, we need to specify it as a dependency of the org.eclipse.example.arithmetics.dotnet project, as illustrated in the following figure:

<dependency>
  <groupId>org.eclipse.xtext.example.arithmetics</groupId>
  <artifactId>org.eclipse.xtext.example.arithmetics</artifactId>
  <version>${project.version}</version>
  <optional>true</optional>
</dependency>

The org.eclipse.example.arithmetics dependency is declared as optional because otherwise, the JARs of all of its dependencies would be downloaded unnecessarily when building Maven projects that depend on the org.eclipse.example.arithmetics.dotnet project.

Converting the Uber JAR to a .NET assembly with IKVM

After creating the Uber JAR in the previous step, we can use IKVM.NET to convert it to a .NET assembly. As there is no currently maintained (or working) Maven plugin for IKVM.NET, we use the exec-maven-plugin in order to invoke the IKVM.NET compiler.

As a preparation for this to work, we need to download the latest release of IKVM.NET and extract it to some location.

By default, we use the directory ikvm in the user's home folder, so that ikvmc.exe would be available under C:\Users\<username>\ikvm\bin\ikvmc.exe on Windows systems.
By defining the IKVM.NET location as a Maven property as illustrated below, this default location can be overridden using -Dikvm.home=<directory> later during the invocation of Maven:

<properties>
  <ikvm.home>${user.home}/ikvm</ikvm.home>
  <ikvm.dlls.dir>${ikvm.home}/bin</ikvm.dlls.dir>
  <ikvm.path>${ikvm.home}/bin/ikvmc.exe</ikvm.path>
</properties>

Based on this, we can specify the execution of ikvmc.exe using the exec goal of the exec-maven-plugin plugin.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>ikvmCompile</id>
      <phase>package</phase>
      <goals>
        <goal>exec</goal>
      </goals>
      <configuration>
        <executable>${ikvm.path}</executable>
        <workingDirectory>${project.build.directory}</workingDirectory>
        <arguments>
          <argument>-target:library</argument>
          <argument>-out:${project.artifactId}.dll</argument>
          <argument>${project.build.finalName}-shaded.jar</argument>
        </arguments>
      </configuration>
    </execution>
  </executions>
</plugin>

We specify the project's target directory as working directory and pass parameters to create a DLL from the JAR.

.NET Assembly as Maven artifact

As the DLL is the org.eclipse.example.arithmetics.dotnet project's main artifact, but Maven has no built-in support specific for .NET assemblies, we have to use a workaround:

We specify the project's packaging to be pom, which means that technically the pom.xml itself is the project's primary artifact, and then use the the attach-artifact goal of the build-helper-maven-plugin to tell Maven to attach the DLL as a supplementary artifact. On this way, the DLL gets installed in the Maven repository when the Maven install goal is built and can be used by other projects by adding <type>dll</type> to the dependency declaration.

The resulting plugin configuration is illustrated below:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>ikvmAttach</id>
      <goals>
        <goal>attach-artifact</goal>
      </goals>
      <configuration>
        <artifacts>
          <artifact>
            <file>${project.build.directory}/${project.artifactId}.dll</file>
            <type>dll</type>
          </artifact>
        </artifacts>
      </configuration>
    </execution>
  </executions>
</plugin>

Running the build

Now, we can run the build using the MS Build command prompt from the parent directory:

mvn clean install

There may be several warnings regarding missing classes, which can be safely ignored.
The resulting DLL will be placed in the target directory.

Conclusion

In this part of the series, we have described how to transform an Xtext DSL project into a .NET assembly which is ready for use in a C# application.

The next blog entry will demonstrate how to use this assembly to create a command-line calculator application.

Comments