7 min. reading time

With the new 2.17 version, Xtext starts shipping a new artifact: A Maven Bill of Materials (a.k.a. BOM). Learn about our motivation and how your projects can benefit from it.

Right on the same date as the Eclipse Photon simultaneous release on June 27th 2018, Xtext users and especially users of xtend-maven-plugin were unpleasantly surprised by a suddenly arising SecurityException in their builds, which perfectly worked the day before. Since this broke quite some builds, including our own, analysis was immediately initiated.

On the same day, the problem was identified as a problem with the plugin’s dependencies. The plugin used version ranges, which left it to Maven’s dependency resolution mechanism to compute the actual versions of artifacts that are added to the plugin’s classpath realm. Unfortunately, this might result into resolving incompatible Eclipse core libraries. Luckily, dependencies of a Maven plugin can be overridden or extended by configuration, so users could help themselves using the instructions in the issue comments.

Of course, we do not want users to implement workarounds, nor do we want to have any workarounds in Xtext’s code base. So streamlining dependencies was the logical task. Maven’s dependency mechanism allows declaration of managed dependencies through a <dependencyManagement> section. The most common way is to declare such a section in a parent POM and inherit it from there. A less-known possibility is the Bill of Materials.

About Maven Bill of Materials and Xtext’s BOM

A Maven BOM is basically a simple pom.xml file with packaging type pom, which in its core contains a <dependencyManagement> section. The Xtext BOM declares all third-party dependencies that Xtext uses in Maven or Gradle builds. To gain an overview of the used artifact versions on first sight, the BOM declares version properties and uses them in the managed dependencies below. Here's the contents of Xtext’s BOM:

<project ...>
  <groupId>org.eclipse.xtext</groupId>
  <artifactId>xtext-dev-bom</artifactId>
  <version>[XTEXT_VERSION]</version>
  <packaging>pom</packaging>
  ...
  <properties>
  <!-- ======== -->
  <!-- Versions -->
  <!-- ======== -->
  <!-- 3rd party -->
  <antlr-runtime-version>3.2</antlr-runtime-version>
  <args4j-version>2.33</args4j-version>
  <asm-version>7.0</asm-version>

  <error_prone_annotations-version>2.0.15</error_prone_annotations-version>
  <guava-version>21.0</guava-version>
  <guice-version>3.0</guice-version>
  <icu4j-version>52.1</icu4j-version>
  <javax.annotation-api-version>1.3.2</javax.annotation-api-version>
  <jsr305-version>3.0.2</jsr305-version>
  <junit-version>4.12</junit-version>
  <junit5-version>5.1.0</junit5-version>
  ...
  </properties>
  <dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>args4j</groupId>
      <artifactId>args4j</artifactId>
      <version>${args4j-version}</version>
    </dependency>
    <dependency>
      <groupId>com.google.code.findbugs</groupId>
      <artifactId>jsr305</artifactId>
      <version>${jsr305-version}</version>
    </dependency>
    <dependency>
      <groupId>com.google.errorprone</groupId>
      <artifactId>error_prone_annotations</artifactId>
      <version>${error_prone_annotations-version}</version>
    </dependency>
    ...
  </dependencies>
  </dependencyManagement>
</project>

 

Using the Xtext Bill of Materials

To use the BOM, clients have to declare it as a dependency in their <dependencyManagement> section with scope import:

<dependencyManagement>
  <dependencies>
  <dependency>
    <groupId>org.eclipse.xtext</groupId>
    <artifactId>xtext-dev-bom</artifactId>
    <version>${xtext.version}</version>
    <type>pom</type>
    <scope>import</scope>
  </dependency>
  </dependencies>
</dependencyManagement>

 

Once this is done, all artifacts managed by the BOM can be used without needing to specify a version. For example, if clients want to use JUnit 5 dependencies, they simply declare their dependencies as follows. Please note: Specification of a scope different from the default is still needed. We may evaluate to declare also scopes, e.g., test for testing libraries, and exclusions for transitive dependencies, e.g., log4j, in future versions.

<dependencies>
  <dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter-api</artifactId>
  <scope>test</scope>
  </dependency>
  <dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter-engine</artifactId>
  <scope>test</scope>
  </dependency>
  ...
</dependencies>

 

So, clients do no longer carefully revise each and every dependency, which thus lowers maintenance efforts. Also, clients can be sure that the managed versions of all libraries have been tested to work properly with Xtext and with each other. Of course, in the unlikely case of an incompatibility, clients still have the option to override BOM-managed dependencies by explicit versions.

Using the BOM with Gradle

Although a BOM is a Maven artifact, Gradle builds can also use it. First attempts to integrate BOMs for dependency management with Gradle have been done by the Spring framework with their dependency-management-plugin. Now, with Gradle 5, direct support of BOMs have been added to the Gradle framework.

When we started exploring and using our BOM, we stumbled over some open bugs and incompatibilities, so we sticked with the Spring variant for 2.17.0.M1. But after upgrading to Gradle 5.2.1, things got easier for us and seem to be stable now. To use the Xtext BOM in Gradle builds, declare a platform dependency:

dependencies {
  compile platform("org.eclipse.xtext:xtext-dev-bom:${xtextVersion}")
}

 

As with Maven, it is not necessary to specify explicit versions if using dependencies that are managed within the BOM:

dependencies {
  testCompile 'org.junit.jupiter:junit-jupiter-api'
  testRuntime 'org.junit.jupiter:junit-jupiter-engine'
  ...
}

 

A note on Guava

With introduction of the BOM, we decided to pin all dependency versions. One important dependency here is the Google Guava library, which Xtext supported in the range from version 14.0 to 21.0, which has been pinned to 21.0 now.

As a positive side effect, build times could be reduced, since fewer dependency combinations have to be resolved and evaluated, especially for clients of xtend-maven-plugin.

New & existing projects

As always, we polished Xtext’s New Project Wizard to reflect the latest state of development. So, when you create new Xtext projects with the wizard for the build system of your choice, you can immediately benefit from using the BOM.

For existing projects, we recommend to refactor them, remove the workaround mentioned above and get rid off any unnecessary dependency version. A freshly-created project can serve as a good blueprint for the changes you should apply.

Conclusion

The new Xtext BOM manages all third-party dependencies of Xtext. All existing code has been refactored to make use of the BOM, and it is recommended to upgrade your projects to do the same.

The BOM can be used from Maven and Gradle build configurations. It reduces maintenance efforts for upgrading Xtext projects and can speed up the build process slightly. Most importantly, there is no space for dependency problems if properly using managed dependencies.

Comments