feat: GraalVM support
Description
This PR will enable the github-api to be supported by GraalVM. There have been two options discussed.
- Use Spring Boot RuntimeHintsRegistrar / Quarkus @BuildStep to generate the reflect-config.json / serialization-config.json in separate artifacts.
- Add the low level GraalVM metadata directly to the project.
@bitwiseman preferred option 2 as it reduces the dependencies and lower the costs.
The See https://github.com/hub4j/github-api/issues/1908 for more information
Before submitting a PR:
- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, CONTRIBUTING.md for details.
- [ ] Add JavaDocs and other comments explaining the behavior.
- [ ] When adding or updating methods that fetch entities, add
@linkJavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . - [ ] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See CONTRIBUTING.md for details.
- [ ] Run
mvn -D enable-ci clean install sitelocally. If this command doesn't succeed, your change will not pass CI. - [x] Push your changes to a branch other than
main. You will create your PR from that branch.
When creating a PR:
- [x] Fill in the "Description" above with clear summary of the changes. This includes:
- [ ] If this PR fixes one or more issues, include "Fixes #
" lines for each issue. - [ ] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.
- [ ] If this PR fixes one or more issues, include "Fixes #
- [ ] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, "Reaching this particular exception is hard and is not a particular common scenario."
- [x] Enable "Allow edits from maintainers".
What I did:
- I ran my application with the execution
aot-processofspring-boot-maven-pluginwith1.323ofgithub-apias dependency. - Because of the
RuntimeHintsRegistrarmentioned here https://github.com/hub4j/github-api/issues/1908#issuecomment-2296488149 all reflection-config and serialization-config hints are created at ./target/spring-aot/main/resources/META-INF/groupid/artifactid/*.json - I copied all information which belongs to
org.kohsuke.githubpackages into those configs provided by this PR.
Hey @gsmet - it would be very nice if you could check if I missed something in the configs. 😃
Codecov Report
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 81.36%. Comparing base (
338de9a) to head (dd53d7c). Report is 4 commits behind head on main.
Additional details and impacted files
@@ Coverage Diff @@
## main #1914 +/- ##
=========================================
Coverage 81.36% 81.36%
Complexity 2464 2464
=========================================
Files 239 239
Lines 7471 7471
Branches 401 401
=========================================
Hits 6079 6079
Misses 1146 1146
Partials 246 246
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
Important: If there are any new classes that requires reflections those need to be added to the configs as well as they are not auto generated via Spring / Quarkus
I had to force push as I forgot the native-image folder inside of META-INF
@klopfdreh
So, we need test to ensure the file stay up-to-date.
Also, it looks like you're registering all the classes in org.kohsuke.github for reflection, which is not accurate. Many classes in there are not reflected on. I'm guessing that including them has consequences for performance?
The difficulty here is that we don't have a way of indicating what classes should and shouldn't be in the file.
Added some notes to #1908 just for reference...
Here's my thought on how to proceed:
- Add a file called
graalvm-no-reflectto thetest/resources. - List classes in there that are under
org.kohsuke.githubdo not use reflection (start with a few just as proof of concept) - Add a test based on your
aot-process** You could even use thespring-bootdependency if you like (as atestdependency). But I'm not sure you need it. ** The test would load the currentreflect-config.jsonand thegraalvm-no-reflectand then check the contents ofreflect-config.jsonis correct. ** If the contents are not correct, the test will fail with meanful instructions saying, "Add classz.x.yto one the two files:graalvm-no-reflectorreflect-config.json." - Same for serialize-config as there are plenty of classes that don't need special serialization behavior either. Are they the same ones? Not sure.
This will ensure the files are maintained but also have low cost to maintain them.
How does this sound to you?
For me this sounds a bit odd that you want to handle both classes with/without reflections, as you already know from the reflect-config.json which classes use them, meaning all others don't use reflection.
You can't get a list with classes that require reflection as this is something you need to know and define via spring-aot.
I agree that it is much nicer to find out if you missed a class in the reflect-config.json during build time, but the only way to see if it is working is to start a native image in your pipeline (which is expensive).
Note: It is not that big of a deal to add all classes to reflect and serialization as it is like in the JVM. The performance impact is also very low (I tested it already).
What I suggest would be to add a test that uses spring-aot and checks via Registrar if all classes are mentioned in the reflect-config.json.
To use reachable meta data for generating the reflect-config.json is risky as you have to have a 100% test coverage all the time.
In the Quarkus extension, we have a test that approximates things a bit, meaning we have a list of excluded classes and any time a class gets added, we have to decide if we exclude it or add it: https://github.com/quarkiverse/quarkus-github-api/blob/main/deployment/src/test/java/io/quarkiverse/githubapi/deployment/GitHubApiDotNamesTest.java#L28 .
It has worked well enough for us even if not ideal.
BTW, I still think that having an annotation and an annotation processor would make things a bit easier.
People wouldn't have to adjust the JSON file, which can be error prone, they would just have to add an annotation.
@klopfdreh
What I suggest would be to add a test that uses spring-aot and checks via Registrar if all classes are mentioned in the reflect-config.json
I agree to this suggestion. It is a bit on the bare minimum side, but I would accept this.
Can the test also update the json file (and then report failure)? That would address @gsmet's concern about maintaining the json file.
In the Quarkus extension, we have a test that approximates things a bit, meaning we have a list of excluded classes and any time a class gets added, we have to decide if we exclude it or add it: https://github.com/quarkiverse/quarkus-github-api/blob/main/deployment/src/test/java/io/quarkiverse/githubapi/deployment/GitHubApiDotNamesTest.java#L28 .
This is basically lines up with what I was thinking around not registering classes for reflection which don't need it.
What this test shows is that having an exclude list is not expensive and it lets us check "all classes except a specific list are mentioned in the reflect-config.json". If you're willing to do this @klopfdreh, that would be even better but not absolute required.
@bitwiseman - I am going to add the test, tomorrow.
Even if this is a manual effort to add the entry into the reflect-config.json I would not let a test change the source code. (It is still only a test)
Then If possible make the output when the test fails clear instructions about what to change.
Hey @bitwiseman - I added the test now.
Things which are important:
- The test build requires java 17 for Spring Boot 3.x so I removed java 11 and added java 21 in matrix build
- ensure-java-1.8-class-library can't be used for animal-sniffer-maven-plugin anymore as Spring Boot uses Java 17
- The test has to be run with mvn because the process-test-aot is used of spring-boot-maven-plugin to generate all stuff into
./target/spring-aot/test/.... - I had to add
junit-vintage-engineso that junit 4 and junit 5 tests are executed through maven-surefire-plugin - I was not able to get the
test Java 8action back to work as the logback dependency is required with a higher version from Spring Boot right now. So I shifted some tests require logback toslow-or-flaky-tests.txt - I had to change the codeql-analysis-yml as it was running with java 8 and requires java 17, now.
- Github does not refresh the status report for matrix builds so there is still java 11 mentioned at the end of this PR.
I have Java > 17 installed locally and already tested that if you run mvn clean verify and not add a class to reflect-config.json / serialization-config.json or no-reflect-and-serialization-list the following error occurs
The class "org.kohsuke.github.AbstractBuilder" needs to be configured or excluded for reflection / serialization and was not mentioned in one of the following resources:
src/main/resources/META-INF/reflect-config.json - example:
{
"name": "org.kohsuke.github.AbstractBuilder",
"allPublicFields": true,
"allDeclaredFields": true,
"queryAllPublicConstructors": true,
"queryAllDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredConstructors": true,
"queryAllPublicMethods": true,
"queryAllDeclaredMethods": true,
"allPublicMethods": true,
"allDeclaredMethods": true,
"allPublicClasses": true,
"allDeclaredClasses": true
}
src/main/resources/META-INF/serialization.json - example:
{
"name": "org.kohsuke.github.AbstractBuilder"
}
src/test/resources/no-reflect-and-serialization-list - example:
org.kohsuke.github.AbstractBuilder
Please add it to either no-reflect-and-serialization-list or to serialization.json and / or reflect-config.json
I'm going to merge this. @gsmet if you have any changes, please file an issue and tag @klopfdreh .