Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate GraalVM native image config #5005

Merged
merged 8 commits into from
Aug 18, 2023

Conversation

trustin
Copy link
Member

@trustin trustin commented Jul 3, 2023

Motivation:

Armeria and its dependencies (e.g. Netty, JCTools and Caffeine) use
fair amount of reflections, JNI and other advanced features of JVM,
which require explicit configuration when building a native image with
GraalVM. We should provide native image configuration in our artifacts
so that a user can easily build a native image out of the box.

Modifications:

  • Added a new project flag native. For a project with native flag,
    we add a task nativeImageTrace that runs select test cases annotated
    with a GenerateNativeImageTrace to collect the trace files generated
    by native-image-agent.
  • Added a new project :native-image-config, which:
    • aggregates all traces into native image config; and
    • cleans up the noise from the generated native image config
  • Added the initial version of native image config to
    META-INF/native-image/com.linecorp.armeria/armeria.
  • Renamed and relocated some test classes so that they are not included
    in the native image config

Result:

  • A user can now easily build a native image of an Armeria application.
  • Future works:
    • Configure our build pipieline to generate native image config for
      different platforms automatically (macOS, Linux epoll, Linux io_uring
      and Windows).
    • Use annotation processors rather than reflection for scanning
      annotations.
    • Add entries for Windows and Linux io_uring.

@trustin trustin marked this pull request as draft July 5, 2023 07:27
@trustin trustin force-pushed the generate-native-image-config branch 5 times, most recently from 53e05a3 to f476714 Compare July 12, 2023 12:28
@trustin trustin changed the title Generate GraalVM native image config with -PnativeImageConfig Generate GraalVM native image config Jul 12, 2023
@trustin trustin force-pushed the generate-native-image-config branch 12 times, most recently from 99a3460 to bead3b3 Compare July 18, 2023 12:53
@trustin trustin force-pushed the generate-native-image-config branch 8 times, most recently from 07587ae to 3fc921f Compare July 20, 2023 17:29
@trustin trustin force-pushed the generate-native-image-config branch 3 times, most recently from e9449a3 to 5f6cff1 Compare July 21, 2023 10:19
@trustin trustin added this to the 1.25.0 milestone Jul 21, 2023
@trustin trustin force-pushed the generate-native-image-config branch from 5f6cff1 to 1087aac Compare July 21, 2023 10:32
@trustin trustin marked this pull request as ready for review July 21, 2023 10:32
@codecov
Copy link

codecov bot commented Aug 11, 2023

Codecov Report

Patch coverage has no change and project coverage change: +0.01% 🎉

Comparison is base (5da5ea4) 74.30% compared to head (53d496d) 74.32%.
Report is 2 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5005      +/-   ##
============================================
+ Coverage     74.30%   74.32%   +0.01%     
- Complexity    19598    19605       +7     
============================================
  Files          1682     1682              
  Lines         72252    72252              
  Branches       9241     9241              
============================================
+ Hits          53690    53703      +13     
+ Misses        14221    14216       -5     
+ Partials       4341     4333       -8     

see 19 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some questions but looks good overall. Thanks @trustin 🙇 👍 🚀


// Remove 'jetty-alpn-agent' and add 'native-image-agent'.
jvmArgs = jvmArgs.stream().filter {
!(it.startsWith('-javaagent:') && it.endsWith('jetty-alpn-agent.jar'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit; I believe this isn't needed following #5060

relocatedProjects.each { p ->
if (projectsWithNativeImageSupport.contains(p)) {
p.tasks.getByName('nativeImageTrace').configure {
classpath += p.files(p.configurations.getByName('shadedJarTestRuntime').resolve())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I understood we have to add this here in a separate afterEvaluate because the shadedJarTestRuntime configuration itself is evaluated in a afterEvaluate.

native-image-config/README.md Outdated Show resolved Hide resolved

private val resourceRegex = """^(?:[._a-zA-Z0-9:]*)\\Q(.*)\\E$""".toRegex()

private fun filterResources(fileName: String, obj: JsonNode, baseResourcePatterns: Iterable<Regex>) {
Copy link
Contributor

@jrhee17 jrhee17 Aug 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question) I don't think I understood the intention of baseResourceConfigFile 😅 Wouldn't the file be used eventually in the nativeImageConfig task anyways?

Copy link
Member Author

@trustin trustin Aug 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct, but they are meant for being used when our 'user' builds a native image. We just use the same set of rules to filter out irrelevant and/or duplicate resource configurations in our native image config.

`GenerateNativeImageTrace` with [`native-image-agent`](https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/).

```
$ ../gradlew nativeImageConfig
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When running from my local env. I found the following logs:

Warning: Error processing trace entry map(size=5, {(tracer,reflect),(function,getEnclosingMethod),(caller_class,org.awaitility.core.AssertionCondition),(result,false),(args,[])}): java.lang.StringIndexOutOfBoundsException: begin 0, end -1, length 5
        at java.base/java.lang.String.checkBoundsBeginEnd(String.java:4606)
        at java.base/java.lang.String.substring(String.java:2709)
        at org.graalvm.nativeimage.configure/com.oracle.svm.configure.trace.ReflectionProcessor.addFullyQualifiedDeclaredMethod(ReflectionProcessor.java:293)
        at org.graalvm.nativeimage.configure/com.oracle.svm.configure.trace.ReflectionProcessor.processEntry(ReflectionProcessor.java:247)
        at org.graalvm.nativeimage.configure/com.oracle.svm.configure.trace.TraceProcessor.processEntry(TraceProcessor.java:87)
        at org.graalvm.nativeimage.configure/com.oracle.svm.configure.trace.TraceProcessor.processTrace(TraceProcessor.java:63)
        at org.graalvm.nativeimage.configure/com.oracle.svm.configure.trace.TraceProcessor.process(TraceProcessor.java:58)
        at org.graalvm.nativeimage.configure/com.oracle.svm.configure.command.ConfigurationGenerateCommand.generate(ConfigurationGenerateCommand.java:293)
        at org.graalvm.nativeimage.configure/com.oracle.svm.configure.command.ConfigurationGenerateCommand.apply(ConfigurationGenerateCommand.java:60)
        at org.graalvm.nativeimage.configure/com.oracle.svm.configure.ConfigurationTool.main(ConfigurationTool.java:84)

Just want to make sure this is normal/expected

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was reported to oracle/graal#6980

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They seem to be harmless although they look scary. 😅

@@ -75,7 +79,7 @@ allprojects {
// `disallow` in Java 18.
// - https://openjdk.org/jeps/411
// - https://bugs.openjdk.org/browse/JDK-8270380
if (project.ext.testJavaVersion >= 17) {
if (project.ext.testJavaVersion >= 17 || it.name == 'nativeImageTrace') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional) What do you think of setting testJavaVersion of nativeImageTrace to 17?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testJavaVersion is specified on per-project basis rather than on per-task basis, so I guess that's not possible? (nativeImageTrace is a task that is created for each project.) Please correct me if I'm wrong.

build.gradle Show resolved Hide resolved
`GenerateNativeImageTrace` with [`native-image-agent`](https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/).

```
$ ../gradlew nativeImageConfig
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was reported to oracle/graal#6980

@ikhoon
Copy link
Contributor

ikhoon commented Aug 14, 2023

Side note:

I failed to build our SAML example as a native image due to the following exception:

Exception in thread "main" java.lang.IllegalArgumentException: URI scheme is not "file"
        at java.base@17.0.8/java.io.File.<init>(File.java:423)
        at example.armeria.server.saml.sp.Main.main(Main.java:99)

After digging into the issue, it turned out that the scheme of URI was file: on JVM but resource: on the native image. Consequently, new File(URI) raised IllegalArgumentException for the non-file scheme.
https://github.com/openjdk/jdk/blob/4164693f3bf15a2f3e03dee72e1ca3fb8d82582c/src/java.base/share/classes/java/io/File.java#L422

.tls(new File(ClassLoader.getSystemResource("localhost.crt").toURI()),
new File(ClassLoader.getSystemResource("localhost.key").toURI()))

I was able to run the SAML example after fixing the code to use InputStream.

.tls(Main.class.getClassLoader().getResourceAsStream("localhost.crt"),
     Main.class.getClassLoader().getResourceAsStream("localhost.key"))

Co-authored-by: jrhee17 <guins_j@guins.org>
@trustin
Copy link
Member Author

trustin commented Aug 16, 2023

@ikhoon wrote:

I was able to run the SAML example after fixing the code to use InputStream.

Do you think we should update the example, then? 🤔

Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look great and left a small suggestion. 👍

newCommandLine += "generate"
nativeImageTraceFiles.forEach {
if (Files.exists(it)) {
newCommandLine += "--trace-input=$it"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we also add the files to inputs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Task input must be specified at configuration time, but we only know whether the trace file exists at execution time, so we cannot add every one of them at configuration time. (Gradle will error when we add a missing file as an input unfortunately.) Instead, I added the directory that contains the trace files as an input, so all trace files in the directory are hashed.

}
}

abstract class CleanupNativeImageConfigTask : DefaultTask() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps FilterAndCopyNativeImageConfigTask?
For me, the word clean in a Gradle task means removing the generated files. 🤔

@ikhoon
Copy link
Contributor

ikhoon commented Aug 17, 2023

@ikhoon wrote:

I was able to run the SAML example after fixing the code to use InputStream.

Do you think we should update the example, then? 🤔

Not today. 😆
Someday, I believe we need a CI to build Armeria examples as native images and run them with minimal setup to verify native image configurations are sensible enough. Let's fix our examples then.

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @trustin! 🚀🚀

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @trustin! 🚀🚀

Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @trustin! 🚀 👍 💯

@trustin
Copy link
Member Author

trustin commented Aug 18, 2023

Let me merge this after the build passes AND I confirm the native image configuration actually works manually.

@trustin trustin merged commit d34cee7 into line:main Aug 18, 2023
@trustin trustin deleted the generate-native-image-config branch August 18, 2023 07:26
@ikhoon ikhoon mentioned this pull request Mar 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants