gradlex-org/extra-java-module-info

Unable to require module when plugin is used

Closed this issue · 4 comments

I'm using Java 14 with Gradle 6.8.1. I have two projects, cli and backend. The CLI is a Spring Boot application, whereas backend is a Java library (which also uses Spring Boot, with jar enabled and bootJar disabled). I'm using JMPS to include backend from within cli.

As soon as I add id 'de.jjohannes.extra-java-module-info' version '0.5' to the plugins section of my subproject backend, I can't build the consuming project cli anymore, as shown below. Sadly, I also need to use the plugin, as I have to use a library in backend which does not define a module descriptor (OpenFeign/feign#1357 and spring-cloud/spring-cloud-openfeign#469).

I also use your plugin in cli for another library without a module descriptor (spring-projects/spring-shell#315).

$ ./gradlew build

FAILURE: Build failed with an exception.

* What went wrong:
Could not determine the dependencies of task ':cli:bootJar'.
> Could not resolve all task dependencies for configuration ':cli:runtimeClasspath'.
   > Could not resolve project :backend.
     Required by:
         project :cli
      > The consumer was configured to find a runtime of a library compatible with Java 14, packaged as a jar, and its dependencies declared externally, as well as attribute 'javaModule' with value 'true'. However we cannot choose between the following variants of project :backend:
          - productionRuntimeClasspath
          - runtimeElements
        All of them match the consumer attributes:
          - Variant 'productionRuntimeClasspath' capability de.cotto:backend:0.0.1-SNAPSHOT declares a runtime of a component, packaged as a jar, and its dependencies declared externally, as well as attribute 'javaModule' with value 'true':
              - Unmatched attributes:
                  - Doesn't say anything about its component category (required a library)
                  - Doesn't say anything about its target Java version (required compatibility with Java 14)
          - Variant 'runtimeElements' capability de.cotto:backend:0.0.1-SNAPSHOT declares a runtime of a library compatible with Java 14, packaged as a jar, and its dependencies declared externally:
              - Unmatched attribute:
                  - Doesn't say anything about javaModule (required 'true')
        The following variants were also considered but didn't match the requested attributes:
          - Variant 'apiElements' capability de.cotto:backend:0.0.1-SNAPSHOT declares a library compatible with Java 14, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares an API of a component and the consumer needed a runtime of a component
              - Other compatible attribute:
                  - Doesn't say anything about javaModule (required 'true')

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 541ms

@C-Otto I think this is an effect not specific to this plugin but a result of combining plugins that do not setup everything "well enough" for variant-aware dependency resolution which was not supported in earlier Gradle version.

In this case productionRuntimeClasspath, added by the spring boot plugin, is confusing the resolution. It thinks it is something that might match. It works coincidentally when you do not use this plugin (which adds the javaModule attribute) because Gradle tries to make a decision based on the number of attributes that it can/cannot match. And if there is a perfect match, it would prefer it. Which covers most cases where things are not well enough defined.

In this case, I think the spring boot plugin should set productionRuntimeClasspath.canBeConsumed = false. Then it won't interfere with the resolution anymore. You can fix that in your build by adding this line:

// Groovy DSL build.gradle in your 'backend' project:
configuration.productionRuntimeClasspath.canBeConsumed = false

I haven't been able to reproduce this myself. Which Spring Boot version are you using? Maybe it works with newer versions.

Thanks for the explanation, I'll try to understand it. I'm using Spring Boot configured as follows:

implementation 'org.springframework.boot:spring-boot-gradle-plugin:2.4.2'
implementation 'io.spring.gradle:dependency-management-plugin:1.0.11.RELEASE'

I understand that Spring Boot does quite a lot of stuff that isn't Gradle-y (spring-projects/spring-boot#25126, gradle/gradle#16059 for a recent example), which is rather unfortunate. Could you open an issue in the Spring Boot project? I'm not comfortable enough with Gradle internals, and I'd like to avoid pointing fingers without a good technical explanation.

@C-Otto those are Gradle plugins and you should be including them in the plugins section of the build.gradle DSL. From what I see, you tried to include them as project dependencies. Moreover, the former plugin already includes the latter plugin, so you have to specify just one of the plugins depending on your use case.

If you are interested in modularizing Spring Boot apps, you may find this PR useful. However, be ready to face more issues than you currently have with your configuration ;-)

@C-Otto I am happy to open a Spring Boot issue, but I need to reproduce the problem to fully understand what it is. Would you be able to share a complete small project reproducing what you are seeing?

Does adding this to the build file of backend solve the problem for you?

configuration.productionRuntimeClasspath.canBeConsumed = false