Dependencies added only for native hints (example a RuntimeHintsRegistrar) should only be required as compileOnly depedency in a Gradle build.

Example to reproduce: https://github.com/abelsromero/springboot-reactive-native * ApplicationRuntimeHintsRegistrar uses Reflections project to register all classes with custom annotation @RegisterForReflection. * Running processAot (builing native image by extention) fails becase the dependencies required to compile the hint are not found. * Changing compileOnly 'org.reflections:reflections:0.10.2' to implementation 'org.reflections:reflections:0.10.2' in build.gradle fixes it.

This is specially an issue when releasing both JVM and Native versions, since the JVM will include the dependencies in the package (for instance when releasing a Docker/OCI image) increasing the size and even introducing licensing issues.

Comment From: philwebb

I'm not totally sure that we should automatically add compileOnly entries when running the AOT processor. If feels like compileOnly isn't quite right since were not really declaring dependencies that are compile only but rather we need dependencies that are available during compile, processAot and processTestAot but not runtime.

There's probably a way to declare this already, but my Gradle skills are currently failing me. The following works, but is obviously a bit ugly:

compileOnly 'org.reflections:reflections:0.10.2'
processAotClasspath 'org.reflections:reflections:0.10.2'
processTestAotClasspath 'org.reflections:reflections:0.10.2'

Comment From: abelsromero

That's an interesting workarround we can use for now, but having something simpler would be nice.

It also gave me the idea of testing with a sourceSet and apparently that generats a cycle (but no Gradle experter 😅 )

aotImplementation 'org.reflections:reflections:0.10.2'

sourceSets {
    main {
        java {
            compileClasspath += sourceSets.aot.output
        }
    }
    aots {
        java {
            srcDirs("src/aot/java")
        }
    }
}
~/.../springboot-reactive-native >>> ./gradlew processAot

FAILURE: Build failed with an exception.

* What went wrong:
Circular dependency between the following tasks:
:aotClasses
+--- :compileAotJava
|    +--- :classes
|    |    \--- :compileJava
|    |         +--- :aotClasses (*)
|    |         \--- :compileAotJava (*)
|    +--- :compileJava (*)
|    \--- :processAot
|         +--- :classes (*)
|         +--- :compileJava (*)
|         \--- :resolveMainClassName
|              +--- :classes (*)
|              \--- :compileJava (*)
\--- :processAotResources
     \--- :processAot (*)

(*) - details omitted (listed previously)


* 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 582ms

Comment From: bclozel

In the context of a library (like Spring Framework itself), we shouldn't do this as this would put on the classpath dependencies that the library ships optional support for. For example, Spring Framework has a compileOnly dependency on Jackson; during the AOT phase, classpath checks are performed and Condition are processed: we should not activate the Jackson support unless the application has said so. But in the case of libraries, the Spring Boot plugin should not be configured there so this reasoning doesn't apply here.

I guess I find a bit odd that an application is registering custom hints that are so involved that a third party library is added to the compileOnly configuration for that. Right now I don't see any case where adding the compileOnly configuration to the AOT classpath would break things. More importantly, I'm wondering if something is missing from the Spring infrastructure to help developers with custom hints?

Comment From: abelsromero

I guess I find a bit odd that an application is registering custom hints that are so involved that a third party library is added to the compileOnly configuration for that.

For context, we parse swagger content which requires adding a lot of model classes and jackson serializers/deserializers (untill they do officially). For maintenance is much simple to scan for classes in a package and add them all than having to list dozens. It's very similar to what's done to support kubernetes clients https://github.com/kubernetes-client/java/blob/master/spring-aot/src/main/java/io/kubernetes/client/spring/aot/KubernetesBeanFactoryInitializationAotProcessor.java. Furthermore in the later, it scans for user generated code which cannot be referenced directly.

Comment From: scottfrederick

For maintenance is much simple to scan for classes in a package and add them all than having to list dozens.

This is what the Reflective annotation and ReflectiveProcessor interface in Spring Framework are designed to do. There are many usage examples in Framework, Boot, and other Spring projecs. It many not quite work for this use case yet, but I agree with Brian that this seems like something the Spring infrastructure should help with, without requiring another dependency.

Comment From: abelsromero

This is what the Reflective annotation and ReflectiveProcessor interface in Spring Framework are designed to do. There are many usage examples in Framework, Boot, and other Spring projecs.

If I understand that's for Spring projects and it makes sense to me. But I don't think it does for the case of swagger models or any third party that is not part of Spring and does not provide reachability medadata yet.

Comment From: wilkinsona

Dependencies added only for native hints (example a RuntimeHintsRegistrar) should only be required as compileOnly dependency in a Gradle build.

I agree with @philwebb that compileOnly dependencies should not be available during AOT processing. AOT processing, to some extent, runs the application – the context is refreshed to the point that conditions have been evaluated and beans have been defined. It would be unexpected and unwanted for a compileOnly dependency to appear on the classpath by default and affect the evaluation of @ConditionalOn(Missing)Class during AOT processing.

I think Phil's suggestion is a good one if you want to opt into this behavior. If you want to simplify things in terms of reducing repetition you could configure processAotClasspath to extend compileOnly and then only declare the dependency once in the compileOnly configuration:

configurations {
    processAotClasspath.extendsFrom compileOnly
}

processAot succeeds with these three lines in place.

If I understand that's for Spring projects and it makes sense to me. But I don't think it does for the case of swagger models or any third party that is not part of Spring and does not provide reachability medadata yet.

I don't understand the reasoning here. If using Spring Framework features does not make sense for Swagger models, something in Spring Boot which depends on Spring Framework surely does not make sense either. If you're not in a position to use @Reflective as you need to find non-Spring annotations, you can use other existing Framework features such as MergedAnnotations.