Closed dragetd closed 10 months ago
This seems to be caused by the compilation of the AOT generated code not seeing dependencies of scope runtime
. spring-modulith-starter-core
pulls in spring-modulith-core
in runtime
scope and some code generated in the AOT phase refers to types of that JAR. I'll have to investigate with our Gradle experts to find out how that can be fixed.
Running ./gradlew build
against 23f9ed6 results in the AOT-generated main code failing to compile:
> Task :compileAotJava FAILED
/Users/awilkinson/dev/temp/temp_modulith_graal_bug/build/generated/aotSources/org/springframework/modulith/core/config/ApplicationModuleInitializerRuntimeVerification__BeanDefinitions.java:14: error: cannot find symbol
RootBeanDefinition beanDefinition = new RootBeanDefinition(ApplicationModuleInitializerRuntimeVerification.class);
^
symbol: class ApplicationModuleInitializerRuntimeVerification
location: class ApplicationModuleInitializerRuntimeVerification__BeanDefinitions
/Users/awilkinson/dev/temp/temp_modulith_graal_bug/build/generated/aotSources/org/springframework/modulith/core/config/ApplicationModuleInitializerRuntimeVerification__BeanDefinitions.java:15: error: cannot find symbol
beanDefinition.setInstanceSupplier(ApplicationModuleInitializerRuntimeVerification::new);
^
symbol: class ApplicationModuleInitializerRuntimeVerification
location: class ApplicationModuleInitializerRuntimeVerification__BeanDefinitions
2 errors
Looking at the aotCompileClasspath
, we can see that, as Ollie described above, spring-modulith-core
is missing:
aotCompileClasspath - Compile classpath for null/aot.
+--- org.jetbrains.kotlin:kotlin-stdlib:1.9.20
| \--- org.jetbrains:annotations:13.0
\--- org.springframework.modulith:spring-modulith-starter-core -> 1.1.0
+--- org.springframework.modulith:spring-modulith-api:1.1.0
+--- org.springframework.modulith:spring-modulith-moments:1.1.0
| \--- org.jmolecules:jmolecules-events:1.9.0
\--- org.springframework.boot:spring-boot-starter:3.2.0
+--- org.springframework.boot:spring-boot:3.2.0
| +--- org.springframework:spring-core:6.1.1
| | \--- org.springframework:spring-jcl:6.1.1
| \--- org.springframework:spring-context:6.1.1
| +--- org.springframework:spring-aop:6.1.1
| | +--- org.springframework:spring-beans:6.1.1
| | | \--- org.springframework:spring-core:6.1.1 (*)
| | \--- org.springframework:spring-core:6.1.1 (*)
| +--- org.springframework:spring-beans:6.1.1 (*)
| +--- org.springframework:spring-core:6.1.1 (*)
| +--- org.springframework:spring-expression:6.1.1
| | \--- org.springframework:spring-core:6.1.1 (*)
| \--- io.micrometer:micrometer-observation:1.12.0
| \--- io.micrometer:micrometer-commons:1.12.0
+--- org.springframework.boot:spring-boot-autoconfigure:3.2.0
| \--- org.springframework.boot:spring-boot:3.2.0 (*)
+--- org.springframework.boot:spring-boot-starter-logging:3.2.0
| +--- ch.qos.logback:logback-classic:1.4.11
| | +--- ch.qos.logback:logback-core:1.4.11
| | \--- org.slf4j:slf4j-api:2.0.7 -> 2.0.9
| +--- org.apache.logging.log4j:log4j-to-slf4j:2.21.1
| | +--- org.apache.logging.log4j:log4j-api:2.21.1
| | \--- org.slf4j:slf4j-api:1.7.36 -> 2.0.9
| \--- org.slf4j:jul-to-slf4j:2.0.9
| \--- org.slf4j:slf4j-api:2.0.9
+--- jakarta.annotation:jakarta.annotation-api:2.1.1
+--- org.springframework:spring-core:6.1.1 (*)
\--- org.yaml:snakeyaml:2.2
Converting the sample to Java fixes this compilation problem and we see a test failure instead:
org.springframework.modulith.core.Violations: - Module 'root:com.example.modulithgraalbug' depends on non-exposed type com.example.modulithgraalbug.somemodule.internal.SomeThing__TestContext001_BeanDefinitions within module 'somemodule'!
Method <com.example.modulithgraalbug.ApplicationTests__TestContext001_BeanFactoryRegistrations.registerBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)> calls method <com.example.modulithgraalbug.somemodule.internal.SomeThing__TestContext001_BeanDefinitions.getSomeThingBeanDefinition()> in (ApplicationTests__TestContext001_BeanFactoryRegistrations.java:56)
at app//org.springframework.modulith.core.Violations.and(Violations.java:128)
at java.base@17.0.9/java.util.stream.ReduceOps$1ReducingSink.accept(ReduceOps.java:80)
at java.base@17.0.9/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base@17.0.9/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779)
at java.base@17.0.9/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735)
at java.base@17.0.9/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base@17.0.9/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base@17.0.9/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base@17.0.9/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base@17.0.9/java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:657)
at app//org.springframework.modulith.core.ApplicationModules.detectViolations(ApplicationModules.java:400)
at app//org.springframework.modulith.core.ApplicationModules.verify(ApplicationModules.java:365)
at app//com.example.modulithgraalbug.ModularityTests.verifiesModularStructure(ModularityTests.java:12)
at java.base@17.0.9/java.lang.reflect.Method.invoke(Method.java:568)
at java.base@17.0.9/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base@17.0.9/java.util.ArrayList.forEach(ArrayList.java:1511)
The compilation failure has been fixed because spring-modulith-core
is now on the AOT compilation classpath:
aotCompileClasspath - Compile classpath for source set 'aot'.
\--- org.springframework.modulith:spring-modulith-starter-core -> 1.1.0
+--- org.springframework.modulith:spring-modulith-api:1.1.0
+--- org.springframework.modulith:spring-modulith-core:1.1.0
| +--- org.springframework.modulith:spring-modulith-api:1.1.0
| +--- com.tngtech.archunit:archunit:1.1.0
| | \--- org.slf4j:slf4j-api:2.0.7 -> 2.0.9
| \--- org.springframework:spring-core:6.1.1
| \--- org.springframework:spring-jcl:6.1.1
+--- org.springframework.modulith:spring-modulith-moments:1.1.0
| \--- org.jmolecules:jmolecules-events:1.9.0
\--- org.springframework.boot:spring-boot-starter:3.2.0
+--- org.springframework.boot:spring-boot:3.2.0
| +--- org.springframework:spring-core:6.1.1 (*)
| \--- org.springframework:spring-context:6.1.1
| +--- org.springframework:spring-aop:6.1.1
| | +--- org.springframework:spring-beans:6.1.1
| | | \--- org.springframework:spring-core:6.1.1 (*)
| | \--- org.springframework:spring-core:6.1.1 (*)
| +--- org.springframework:spring-beans:6.1.1 (*)
| +--- org.springframework:spring-core:6.1.1 (*)
| +--- org.springframework:spring-expression:6.1.1
| | \--- org.springframework:spring-core:6.1.1 (*)
| \--- io.micrometer:micrometer-observation:1.12.0
| \--- io.micrometer:micrometer-commons:1.12.0
+--- org.springframework.boot:spring-boot-autoconfigure:3.2.0
| \--- org.springframework.boot:spring-boot:3.2.0 (*)
+--- org.springframework.boot:spring-boot-starter-logging:3.2.0
| +--- ch.qos.logback:logback-classic:1.4.11
| | +--- ch.qos.logback:logback-core:1.4.11
| | \--- org.slf4j:slf4j-api:2.0.7 -> 2.0.9
| +--- org.apache.logging.log4j:log4j-to-slf4j:2.21.1
| | +--- org.apache.logging.log4j:log4j-api:2.21.1
| | \--- org.slf4j:slf4j-api:1.7.36 -> 2.0.9
| \--- org.slf4j:jul-to-slf4j:2.0.9
| \--- org.slf4j:slf4j-api:2.0.9
+--- jakarta.annotation:jakarta.annotation-api:2.1.1
+--- org.springframework:spring-core:6.1.1 (*)
\--- org.yaml:snakeyaml:2.2
If the Kotlin plugins are then applied again, but the code is left as Java, the compilation problem returns because org.springframework.modulith:spring-modulith-core
is missing once again. I have pushed the commits that I used for this experimentation to my fork of the original sample.
I think the compilation problem is a bug in Kotlin's Gradle plugins that will need to be fixed by JetBrains.
Closing this as there's nothing we can do about this here.
Is there something to be done for the test failure that occurs with Java? The AOT processing seems to "corrupt" the state of the module such that the constraints are violated:
org.springframework.modulith.core.Violations: - Module 'root:com.example.modulithgraalbug' depends on non-exposed type com.example.modulithgraalbug.somemodule.internal.SomeThing__TestContext001_BeanDefinitions within module 'somemodule'!
Method <com.example.modulithgraalbug.ApplicationTests__TestContext001_BeanFactoryRegistrations.registerBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)> calls method <com.example.modulithgraalbug.somemodule.internal.SomeThing__TestContext001_BeanDefinitions.getSomeThingBeanDefinition()> in (ApplicationTests__TestContext001_BeanFactoryRegistrations.java:56)
at app//org.springframework.modulith.core.Violations.and(Violations.java:128)
at java.base@17.0.9/java.util.stream.ReduceOps$1ReducingSink.accept(ReduceOps.java:80)
at java.base@17.0.9/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base@17.0.9/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779)
at java.base@17.0.9/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735)
at java.base@17.0.9/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base@17.0.9/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base@17.0.9/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base@17.0.9/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base@17.0.9/java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:657)
at app//org.springframework.modulith.core.ApplicationModules.detectViolations(ApplicationModules.java:400)
at app//org.springframework.modulith.core.ApplicationModules.verify(ApplicationModules.java:365)
at app//com.example.modulithgraalbug.ModularityTests.verifiesModularStructure(ModularityTests.java:12)
at java.base@17.0.9/java.lang.reflect.Method.invoke(Method.java:568)
at java.base@17.0.9/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base@17.0.9/java.util.ArrayList.forEach(ArrayList.java:1511)
In fact, there is. I didn't see that originally, as I assumed it'd be an intended violation. I think the culprit here is that the generated …__BeanFactoryRegistrations
is located in the project root and Spring Modulith 1.1 tightened the verification to disallow code residing there to reach into the actual application modules' internals.
With manually written code this usually works well or at least can be worked around by configuration code in the application root package referring to an interface, gathering all beans of that type and the implementations hidden in the modules then implementing that interface.
I wonder if it makes sense to exclude the AOT generated files from the analysis. It looks like they all feature a double underscore in their classnames. Until we find a better means of identifying them, it might be a good enough way of detecting them? Do you think we could annotate these classes with @javax.annotation.processing.Generated
?
A quick spike adding the ignore of types containing a __
makes the sample project build fine again (post the tweaks necessary to get the compiler work, of course).
I wonder if it makes sense to exclude the AOT generated files from the analysis
Yes, I think it does.
It looks like they all feature a double underscore in their classnames. Until we find a better means of identifying them, it might be a good enough way of detecting them?
Sounds reasonable to me given what's currently available.
Do you think we could annotate these classes with
@javax.annotation.processing.Generated
?
WDYT, @snicoll? There's also jakarta.annotation.Generated
which, judging by the package name alone, is perhaps less tied to annotation processing but may not always be on the classpath.
Alright, with this being the second case for us to do this, I've triaged the existing issue and put a higher priority on it, see https://github.com/spring-projects/spring-framework/issues/30824
At face value, I am not a huge fan of using any of those annotations but we can discuss the pros and cons of a custom annotation of ours in the issue. Thanks!
I have encountered a strange bug that made the modules.verify() always fail is there is a Spring component in an internal package, even if it is not used by another module, even for very simple Spring Boot applications.
My main development language is Kotlin with Gradle + Kotlin DSL. But I believe this also happens in Java. The issue seems to be with Gradle… maybe with the org.graalvm.buildtools.native plugin for gradle.
When using this plugin AND an application contextLoads() test, the modules.verify() test tails. Either removing the contextLoads() test or removing the native Plugin makes the test pass.
I struggle to understand how this happens, as I thought the plugin would only do thing when doing native builds. I tried the same setup with Maven + Java and could not reproduce this bug.
Here is a minimal example project where you can test this behavior: https://github.com/dragetd/temp_modulith_graal_bug/tree/main