Configuration

Each of the extensions (see architecture) can be supplied with configuration. Revapi natively uses JBoss DMR to represent the configuration in the code and supports JSON to read the configuration from files. Revapi can be made to also understand XML (as is done in the Revapi Maven plugin and standalone CLI).

Each extension is uniquely identified using its extension ID, which is supposed to be a dot-separated hierarchical name, e.g. revapi.java, revapi.osgi, etc. This ID is used to locate the configuration for the given extension.

Revapi has been around for a little bit and over the time it has evolved. Originally (up until Revapi API 0.8.0), each extension was instantiated exactly once and therefore also configured exactly once. Since Revapi API 0.8.0, supported by Revapi Maven plugin 0.9.0 and Revapi CLI 0.6.1 and onwards, there can be multiple configurations for each extension (and the extension can be therefore instantiated multiple times). This brings the ability to e.g. have 2 differently configured text reporter instances, each generating a different kind of output. Unfortunately, this complicates the configuration, because it is no longer possible to have a single "configuration tree" where extensions would read their configurations from their declared locations.

Therefore, since Revapi API 0.8.0 there is a new kind of JSON format for configuration (which in turn also enables the maven plugin and CLI to support XML configuration btw). To ease the migration to the new versions, the old configuration format is still supported (but mixing the two formats can lead to unresolvable situations, see the Multi-file Configuration section for more details).

XML Configuration Format

An XML document containing Revapi configuration is assumed to contain a number of child elements of the root element, each of which is supposed to be a configuration of a single extension instance. The root element can have any name as it is basically ignored and used only as a starting point for reading the actual configuration of the extensions.

For example:

<revapiExtensionsOrWhatever>
  <revapi.java>
    ...
  </revapi.java>
  <revapi.ignore>
    ...
  </revapi.ignore>
  <my.extension>
    ...
  </my.extension>
</revapiExtensionsOrWhatever>

Each of the children, representing the extension instance, has a name that corresponds to the extension ID of the extension being configured.

If you want to configure a single extension multiple times, you can do so very simply like this:

<revapiExtensionsOrWhatever>
  <revapi.reporter.text>
    ...
  </revapi.reporter.text>
  <revapi.reporter.text>
    ...
  </revapi.reporter.text>
  ...
</revapiExtensionsOrWhatever>

Simply having multiple child elements with the same name is enough. If you want to read the revapi configuration from multiple files, you might want to declare an additional "extension instance ID" (as opposed to just the extension ID) which you can use to mark certain configurations as targeting the same "thing" when merging the configuration from the multiple files. Please consult Multi-file Configuration for more details.

JSON Configuration Format

As explained above, each extension can be configured multiple times. To support this in JSON, the JSON configuration looks like this:

[
  {
    "extension": "revapi.reporter.text",
    "id": "optional-id",
    "configuration": {
      ... the actual configuration of the extension according to its schema ...
    }
  },
  {
    "extension": "revapi.reporter.text",
    "configuration": {
      ...
    }
  },
  {
    "extension": "revapi.ignore",
    "configuration": {
      ...
    }
  },
  ...
]

The configuration is a list. The members of the list are individual configuration objects for the extensions. The extension being configured is specified by the extension key and the configuration (conforming to the schema specified by the extension) is present under the configuration key.

The optional id key is useful if there are multiple configuration sources (see multi file configuration for example) as it affects how the configurations from the different sources are merged together.

The Legacy JSON Configuration Format

This describes the obsolete JSON configuration format that cannot handle multiple configurations per extension. If you still use it, rest assured that it is still supported (with the exception of certain scenarios during merging of multiple configuration sources) but you are encouraged to start using the new configuration format.

The JSON data contains the configuration of all the extensions. Each of the extensions declares a "root" in the JSON data from which it reads its configuration (for example, ignoring specific problems found during the analysis can be done using the IgnoreDifferenceTransform extension from the basic features under the root revapi.ignore).

So, without further ado, let’s configure the java extension to report the classes it finds missing from the API rather than failing the analysis upon encountering them and also only include the archives with com.acme groupId in the analysis:

{
  "revapi": {
    "java": {
      "missing-classes": {
        "behavior": "report"
      }
    },
    "filter": {
      "archives": {
        "include": ["com\\.acme:.*"]
      }
    }
  }
}

The configuration options of the various extensions can be found in their respective docs: basic features documentation, java extension documentation.

Multi-file Configuration

Sometimes it can be handy to split the configuration of Revapi analysis in separate files - this can be done for various reasons - you might want to keep the config of different extensions separate or you want to compose the config from various contributing locations, etc. This is supported by both the Maven plugin and the CLI. Please consult their respective documentations for the details.

When the analysis configuration is split amongst several files, it needs to be merged together before it is applied to Revapi. This process is slightly complex with the ability for a single extension to be configured multiple times but in the end is somewhat similar to the way Maven merges the executions of a plugin - as long as the executions are defined once in the effective POM, they don’t need to be assigned IDs. If there are multiple executions and you override them in child POMs, they need to have the IDs assigned so that it is clear what executions in child POM need to be merged with what executions in the parent POM.

In Revapi, too, an extension configuration can optionally have an ID. In JSON this is expressed like this:

...
    {
      "extension": "my.extension",
      "id": "id",
      "configuration": ...
    }
...

and in XML like so:

...
    <my.extension id="myid">
      ...
    </my.extension>
...

When merging configurations without an explicit ID, everything works as long as there is at most a single configuration for each extension in each configuration file to be merged. As soon as there is more than one configuration for some extension in one of the configuration files, you need to assign IDs to the configurations of that extension so that it is clear what configs should be merged with what.

Pipeline

As described in the architecture, the analysis forms a simple pipeline comprised of the different extensions. The behavior and composition of the pipeline itself can also be configured (in addition to configuring the extensions themselves as described above).

The pipeline configuration is completely separate from the analysis configuration. The Maven plugin uses pipelineConfiguration element for specifying it (as opposed to the analysisConfiguration for the configuration of the analysis performed by the extensions) and the CLI supports this by the explicit list of extensions to use and the transform-blocks commandline argument.

Allowed Extensions

Each of the extension types - analyzers, filters, transforms and reporters can be configured to only include or exclude extensions with certain extension IDs.

E.g. in Maven plugin :

<build>
  <plugins>
    <plugin>
      <groupId>org.revapi</groupId>
      <artifactId>revapi-maven-plugin</artifactId>
      <version>...</version>
      <configuration>
        <pipelineConfiguration>
          <analyzers>
            <include>
              <item>my.scala.analyzer</item>
            </include>
          </analyzers>
          <filters>
            <exclude>
              <item>my.funky.filter</item>
              <item>revapi.java.filter.annotated</item>
            </exclude>
          </filters>
          <transforms>
            ...
          </transforms>
          <reporters>
            ...
          </reporters>
        </pipelineConfiguration>
      </configuration>
    </plugin>
  </plugins>
</build>

In the above, you can see that each type of the Revapi extensions can separately specify which extensions of that certain type to include and which to exclude (when include is not present, all extensions from the classpath are included. The exclude only excludes from the included extensions). In the example above, only the analyzers and filters have a concrete configuration, but the rest of the extension types follows the same logic. An extension type pipeline configuration can in fact have both include and exclude sections but that doesn’t make much sense, because the exclude would only exclude from the list provided in the include. This might come in handy though in a more complex scenarios in Maven where a child pom inherits configuration from parent pom and would like to modify it (parent pom defines a set of of extensions to use but the child pom wants to constrain it).

Transform Blocks

New in Revapi API 0.11.0 (supported by Maven plugin 0.11.0 and CLI 0.9.0 onwards) is the ability to group transformations into blocks which can help in situations where one needs to "prepare" the differences using one transform before being passed to the other (the architecture has more details on this).

The transformation blocks are configured, as the allowed extensions, in the pipeline configuration.

<build>
  <plugins>
    <plugin>
      <groupId>org.revapi</groupId>
      <artifactId>revapi-maven-plugin</artifactId>
      <version>...</version>
      <configuration>
        <pipelineConfiguration>
          <transformBlocks>
            <block>
              <item>...extension ID or extension instance ID of a transform...</item>
              <item>...extension ID or extension instance ID of a transform...</item>
              ...
            <block>
            ...
          </transformBlocks>
          ...
        </pipelineConfiguration>
      </configuration>
    </plugin>
  </plugins>
</build>

as explained in the architecture, the transform blocks enable multiple transform to "act as one".

Criticality

Revapi comes with a default set of criticalities that roughly describe common situations when dealing with API changes. The criticality describes how severe the API change is and its effect on the state of the code. There is a default set of criticalities that Revapi comes with.

  • allowed - API changes with this criticality are allowed and might not even be reported.

  • documented - API changes with this criticality are documented so that users of the API are informed about them.

  • highlight - API changes with this criticality are documented and highlighted as very important.

  • error - API changes with this criticality are not allowed and should result in a build or an error.

The above is just a default configuration and one is free to completely reconfigure both what criticalities are available as well as the default mapping of difference severities to criticalities that is used when no explicit criticality is assigned to a difference during the analysis.

The criticalities available in the analyses are defined in the plugin configuration:

<build>
  <plugins>
    <plugin>
      <groupId>org.revapi</groupId>
      <artifactId>revapi-maven-plugin</artifactId>
      <version>...</version>
      <configuration>
        <pipelineConfiguration>
          <criticalities>
            <criticality>
              <name>OK</name>
              <level>0</level>
            </criticality>
            <criticality>
              <name>KO</name>
              <level>1</level>
            </criticality>
          </criticalities>
          ...
        </pipelineConfiguration>
      </configuration>
    </plugin>
  </plugins>
</build>

Here, we define that the only 2 criticalities are available in our analysis: OK and KO. The higher the level, the more severe the criticality is.

The default mapping of severities to criticalities can be defined in the maven configuration as well. Note that if you define custom criticalities, the mapping is required:

<build>
  <plugins>
    <plugin>
      <groupId>org.revapi</groupId>
      <artifactId>revapi-maven-plugin</artifactId>
      <version>...</version>
      <configuration>
        <pipelineConfiguration>
          <criticalities>
            <criticality>
              <name>OK</name>
              <level>0</level>
            </criticality>
            <criticality>
              <name>KO</name>
              <level>1</level>
            </criticality>
          </criticalities>
          <severityMapping>
            <equivalent>OK</equivalent>
            <nonBreaking>OK<nonBreaking>
            <potentiallyBreaking>KO</potentiallyBreaking>
            <breaking>KO</breaking>
          </severityMapping>
          ...
        </pipelineConfiguration>
      </configuration>
    </plugin>
  </plugins>
</build>