johnjohndoe / java-ecosystem-capabilities

Gradle plugin that adds Capabilities to well-known components hosted on Maven Central.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Java Ecosystem Capabilities Gradle plugin

Build Status Gradle Plugin Portal

This plugin adds Capabilities to the metadata of well-known components hosted on Maven Central that are used in many Java projects.

What is a 'Capability' in Gradle and why should I care?

In the videos below, I explain the concept of Capability Conflicts and why they can help you to avoid "dependency hell" in your project. With this plugin, you enable Gradle to detect and automatically resolved typical capability conflicts in the Java Ecosystem.

How to use the plugin?

Apply the plugin to all (sub)projects of your build so that the capability-adding rules are active everywhere. There is nothing else you need to do. The rules will now be considered when dependencies are resolved. For general information about how to structure Gradle builds, and apply community plugins like this one to all subprojects, you can check out my Understanding Gradle video series.

Plugin dependency

Add this to the build file of your convention plugin's build (e.g. build-logic/build.gradle(.kts) or buildSrc/build.gradle(.kts)).

dependencies {
    implementation("org.gradlex:java-ecosystem-capabilities:1.1")
}

Apply the plugin

In your convention plugin, apply the plugin.

plugins {
    ...
    id("org.gradlex.java-ecosystem-capabilities")
}

You can apply the plugin as a project plugin (build.gradle(.kts)) or a setting plugin (settings.gradle(.kts)). If you don't know what the difference is, using it as project plugin (most Gradle plugins are project plugins) is the preferred way.

The following things are to consider:

  • If you use it as a project plugin make sure to apply it to all (sub)projects using a convention plugin
  • If you use it as a settings plugin you may directly apply it in your settings.gradle(.kts)
  • If you use it as a settings plugin, there are no default resolution strategies registered
  • If you write additional component metadata rules, or use other plugins that add more rules, make sure to consistently do either everything in projects or in settings as Gradle has trouble combining both approaches.

Supported Gradle versions

  • Minimal Gradle version when used as project plugin is 6.0
  • Minimal Gradle version when used as settings plugin is 6.8

Note: There are rules in this plugin (in particular for Guava) which work better with Gradle 7+. This is because Gradle 7 added support for the org.gradle.jvm.environment Attribute that allows Gradle to better distinguish between standard-jvm and android projects.

Alternative: Copy rules into your build logic

Instead of applying this plugin, you may also copy selected rules to your own build logic and register them in your convention plugin(s) directly. In the list below, all rule implementations are linked. Here they are implemented in Java, but converting them to Kotlin or Groovy should be straightforward if you prefer.

Deactivating the default resolution strategy for capabilities

The plugin adds a default resolution strategy for each capability (if the plugin is used as project plugin). Which in most cases means that Gradle will automatically pick the highest version of all components in conflict. This is to cover the cases where users just want things to work somehow. But it might not always be the right solution.

If you prefer to get all the conflicts reported and then add the conflict resolution explicitly (see next section), you can deactivate the default resolution for one or all rules through the javaEcosystemCapabilities extension.

// Deactivate default resolution strategy for all rules
javaEcosystemCapabilities {
    deactivatedResolutionStrategies.addAll(allCapabilities)
}

// Deactivate default resolution strategy for selected rules
javaEcosystemCapabilities {
    deactivatedResolutionStrategies.addAll(CGlibRule.CAPABILITY)
    deactivatedResolutionStrategies.addAll(JavaxMailApiRule.CAPABILITY)
}

I use the plugin and now there is a conflict - what now?

Unless deactivated (see above), the plugin configures Gradle to resolve conflicts by selecting the highest version. If Gradle does not know how to resolve a conflict, you get a conflict error.

If you get an error like this:

> Module 'com.sun.mail:jakarta.mail' has been rejected:
     Cannot select module with conflict on capability 'javax.mail:mail:2.0.1' also provided by [com.sun.mail:mailapi:2.0.1(compile)]

It means that you need to make a decision for the given capability - in this case javax.mail:mail - by selecting one of the modules that both provide the capability. In this case, you can decide between com.sun.mail:jakarta.mail (see first line of message) and com.sun.mail:mailapi (see end of second line).

A decision is made by defining a resolution strategy for the capability. This is best done in the place where you applied this plugin (e.g. one of your convention plugins):

configurations.all {
  resolutionStrategy.capabilitiesResolution {
    withCapability("javax.mail:mail") {        // Capability for which to make the decision
      select("com.sun.mail:jakarta.mail:0")    // The component to select
    }
  }
}

One of the rules added by the plugin has an undesired effect - what now?

The goal of this plugin is to enrich the metadata of widely used components from Maven Central to allow Gradle to detect conflicts. The rules implemented in this plugin extend existing metadata with the mindset that the metadata should look like that in the first place. It just doesn't for technical limitations during the development of the component. In most cases, because the component is published with Maven and only published POM metadata which cannot express capability information.

With that in mind, the rules should be usable as they are for almost all Gradle builds. If you encounter a problem with a rule in your build:

  1. Maybe there is a mistake/bug in this plugin. Please open an issue to discuss it.
  2. You might have a very special setup, where one of the rules causes trouble only in that setup.

In the second case, you cannot deactivate one of the rules in the plugin. But you can treat the modified metadata as if it was the original metadata and add another rule on top to modify it further or to revert the effect of the rule in this plugin. For example:

dependencies {
    components.withModule(CGlibRule.MODULES[0]) {
        // Ad-hoc rule to revert the effect of 'CGlibRule'
        allVariants {
            withCapabilities {
                removeCapability(CGlibRule.CAPABILITY_GROUP, CGlibRule.CAPABILITY_NAME)
            }
        }
    }
}

Such additional rules are best added in the place where you applied this plugin (e.g. one of your convention plugins). The snippet above shows how to add a rule without putting it into a separate class. You can put it into a class (written in Java, Kotlin or Groovy) and use the @CacheableRule annotation for better performance. That's how the rules in this plugin are implemented. Consult the Gradle documentation on Component Metadata Rules for more details.

What is the concrete effect of the plugin?

The plugin makes sure that during dependency resolution, you do not end up with two components that 'do the same thing' in the dependency resolution result. That is, you won't have two or more Jars with different names (e.g. jsr311-api-1.1.1.jar, javax.ws.rs-api-2.1.1.jar, jakarta.ws.rs-api-3.0.0.jar and jaxrs-api-3.0.1.Final.jar) but same/similar classes on the classpath. In this example, Gradle will use jaxrs-api in all places. You can see all effects in this build scan from this artificial sample project that includes dependencies to all components covered by rules in this plugin.

Which Components does this plugin affect?

The following list shows all Capabilities and the Components they are added to. Each Capability's GA coordinates correspond to the GA coordinates of the Component that first introduced the Capability.

Something seems to be missing

This plugin collects rules that universally apply in the Java ecosystem. That means, that the information this plugin adds would ideally be already published in the metadata of the corresponding components. The idea is that every Java project can apply this plugin to avoid certain 'dependency hell' situations. Even if the project does not use any of the components this plugin affects directly, transitive dependency might bring in components that cause conflicts.

At the moment this plugin is only covering a fraction of the components on Maven Central that miss capability information. If you encounter more cases, please...

...contribute!

If you use this plugin and think it is missing a rule for a well-known component (or that a rule is incomplete/wrong), please let us know by

Please make sure, you clearly state which Capability it is about and which Components provide the Capability.

Special Case: Logging Libraries

This plugin does not contain rules for logging libraries, which is a specific area in which conflicts occur regularly. There is a separate plugin covering this topic by adding capabilities to the components of well-known logging APIs and implementations. Please apply that plugin in addition to this one:

plugins {
    ...
    id("org.gradlex.java-ecosystem-capabilities")
    id("dev.jacomet.logging-capabilities")
}

I maintain a Component on Maven Central - How can I publish Capability information myself?

It would be great to see more components publishing capability information directly. If you wonder how you could do it, here is how:

Publishing with Gradle

Assuming the component you are publishing is org.ow2.asm:asm. You add the asm:asm capability as follows:

configurations {
    apiElements {
        outgoing {
            capability("${project.group}:${project.name}:${project.version}") // keep default capability 'org.ow2.asm:asm'
            capability("asm:asm:${project.version}")                          // add 'asm:asm'
        }
    }
    runtimeElements {
        outgoing {
            capability("${project.group}:${project.name}:${project.version}") // keep default capability 'org.ow2.asm:asm'
            capability("asm:asm:${project.version}")                          // add 'asm:asm'
        }
    }
}

See also: Documentation in Gradle Manual

Publishing with Maven

Assuming the component you are publishing is org.ow2.asm:asm. You add the asm:asm capability as follows:

<!-- do_not_remove: published-with-gradle-metadata -->

<build>
  <plugins>
    <plugin>
      <groupId>de.jjohannes</groupId>
      <artifactId>gradle-module-metadata-maven-plugin</artifactId>
      <version>0.3.0</version>
      <executions>
        <execution>
          <goals>
            <goal>gmm</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <capabilities>
          <capability>
            <groupId>asm</groupId>
            <artifactId>asm</artifactId>
          </capability>
        </capabilities>
      </configuration>
    </plugin>
  </plugins>
</build>

See also: Documentation of gradle-module-metadata-maven-plugin Maven Plugin

Disclaimer

Gradle and the Gradle logo are trademarks of Gradle, Inc. The GradleX project is not endorsed by, affiliated with, or associated with Gradle or Gradle, Inc. in any way.

About

Gradle plugin that adds Capabilities to well-known components hosted on Maven Central.

License:Apache License 2.0


Languages

Language:Java 80.3%Language:Groovy 17.6%Language:Kotlin 2.1%