Configuration

Each of the extensions (see architecture) can be supplied with configuration. Revapi natively uses Jackson to represent the configuration in the code and supports JSON or XML to read the configuration from files.

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.

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.

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>

The transform blocks enable multiple transforms to "act as one".

What is this good for?

You can notice that it is hard (read impossible without transformation blocks) to "prepare" differences using one transform and then produce the final difference using a different transform.

As an example, let’s suppose that we would like to use Revapi for checking semantic versioning of our code, but we would only like to base our semantic version on the binary compatibility of the code, disregarding any source or semantic incompatibilities.

Such a thing would be impossible without transformation blocks because the transformation algorithm makes sure each transform sees all the differences and all changes to the original differences are transferred to the next "transformation round".

So, how would we use transformation blocks and how would we configure Revapi to only consider binary compatibility?

Let’s use Maven for our example:

<analysisConfiguration>
  <revapi.semver.ignore>
    <enabled>true</enabled>
  </revapi.semver.ignore>
  <revapi.differences>
      <classify>
        <SOURCE>EQUIVALENT</SOURCE>
        <SEMANTIC>EQUIVALENT</SEMANTIC>
        <OTHER>EQUIVALENT</OTHER>
      </classify>
      <differences>
        <item>
          <regex>true</regex>
          <code>.*</code>
        </item>
      </differences>
  </revapi.differences>
</analysisConfiguration>
<pipelineConfiguration>
  <transformBlocks>
    <block>
      <item>revapi.differences</item>
      <item>revapi.semver.ignore</item>
    </block>
  </transformBlocks>
</pipelineConfiguration>

What have we done there? The analysis configuration looks "normal". We enable the revapi.semver.ignore extension and leave it with the default configuration. We additionally configure revapi.differences to tone down any difference (with any code, by using .* as the regex to match any difference code) to EQUIVALENT, effectively "switching them off" for all compatibility types but BINARY.

The new thing is in the pipelineConfiguration. This tells Revapi to group the two transforms together and consider them as one - the "output" difference of revapi.reclassify is used as "input" difference to revapi.semver.ignore and "output" of that is used for the reporting purposes. The important thing is that revapi.semver.ignore never sees the original differences as reported by the analyzer. It only ever sees the differences first transformed by revapi.differences.

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>

The default mapping for converting a difference severity to criticality is rather conservative:

  • EQUIVALENT is assumed to have allowed criticality

  • NON_BREAKING is assumed to have documented criticality

  • POTENTIALLY_BREAKING is assumed to have error criticality

  • BREAKING is assumed to have error criticality

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 support for old JSON configuration format will be removed with Revapi 1.0.0.

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 onward, 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).

The JSON data in the old format 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:.*"]
      }
    }
  }
}