Micronaut 1.1.0 からは、Native Imageのビルドが簡単になってたよ

## 何も指定せずにNative Imageをビルドしても動くのなんでだろう?

この前、Micornautのアプリをnative-imageでビルドして遊んで

bufferings.hatenablog.com

その後に、Micronautとは関係なく、Nettyをnative-imageでビルドして遊んで

bufferings.hatenablog.com

ふと「あれ?MicronautもNetty使ってるはずなんだけど、何も指定せずにNative Imageをビルドしても動くのなんでだろう?」と思って色々見て回ったメモ。

## build-native-image.sh がなくなってる

きしださんのブログや

nowokay.hatenablog.com

kencharosさんのQiita記事を見てると

qiita.com

MicronautがNative Imageビルド用の build-native-image.sh というファイルがあったみたい。なんだけど、僕の生成したプロジェクトの中には見当たらない。

探してみたら、1.1.0.M2までは提供していたんだけど、その次のリリースで削除されたみたいね。

https://github.com/micronaut-projects/micronaut-profiles/blob/v1.1.0.M2/base/features/graal-native-image/skeleton/gradle-build/build-native-image.sh

build-native-image.sh の中身はこうなってる

./gradlew assemble
java -cp build/libs/@app.name@-*.jar io.micronaut.graal.reflect.GraalClassLoadingAnalyzer
native-image --no-server \
             --class-path build/libs/@app.name@-*.jar \
             -H:ReflectionConfigurationFiles=build/reflect.json \
             -H:EnableURLProtocols=http \
             -H:IncludeResources="logback.xml|application.yml" \
             -H:Name=@app.name@ \
             -H:Class=@defaultPackage@.Application \
             -H:+ReportUnsupportedElementsAtRuntime \
             -H:+AllowVMInspection \
             --allow-incomplete-classpath \
             --rerun-class-initialization-at-runtime='sun.security.jca.JCAUtil$CachedSecureRandomHolder,javax.net.ssl.SSLContext' \
             --delay-class-initialization-to-runtime=io.netty.handler.codec.http.HttpObjectEncoder,io.netty.handler.codec.http.websocketx.WebSocket00FrameEncoder,io.netty.handler.ssl.util.ThreadLocalInsecureRandom,com.sun.jndi.dns.DnsClient

それが、現在の最新版の1.1.0ではこんな感じでビルドできる

./gradlew assemble
native-image --no-server -cp build/libs/hello-graal-*.jar

シンプルになったね。だから build-native-image.sh は削除したってことみたい。いいね。でも、じゃあ GraalClassLoadingAnalyzer とか、native-imageのオプションはどこにいったんだろう?

## native-image.properties

mn create-app をするときに --features graal-native-image オプションをつけると、こういうファイルが生成される。(アプリ名は hello-graal にしてる)

❯ cat src/main/resources/META-INF/native-image/hello.graal/hello-graal-application/native-image.properties
Args = -H:IncludeResources=logback.xml|application.yml \
       -H:Name=hello-graal \
       -H:Class=hello.graal.Application

ふむ。この native-image.properties について調べると、ロジコさんが翻訳してくれてるこの記事の後半に書いてあった。

medium.com

GraalVMの最近のリリース以降、ネイティブイメージのビルドについて別の重要な改善をしています。JARファイル中のMETA-INF/native-imageにnative-image.propertiesファイルを埋め込むことができるようになりました。native-imageツールは、このリソースの場所にあるすべてのファイルを自動的に処理し、それらを使用してnative-imageのコマンドライン引数を作成します。

なるほど。

GraalVMのドキュメントを探してみたけど native-image.properties についての記述は見つからなかったので、ソースを見てみた。たぶんここかなぁ。

https://github.com/oracle/graal/blob/vm-1.0.0-rc15/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java#L762-L787

META-INF/native-image ディレクトリーの中で native-image.properties って名前で終わってるファイルをチェックしてるみたいね。

## ということは、Nettyは?

と思ってJARファイルの中を見てみたら、あった。そういうことかー。

f:id:bufferings:20190421131400p:plain:w300

## で、その元になってるファイルはどこにあるん?

と思って、JARを見たら micronaut-http-netty-1.1.0.jar の中に入ってた。

f:id:bufferings:20190421141215p:plain:w600

native-image.properties で「自分と同じディレクトリーにある reflection-config.json を読み込んでくれー」って言ってる。

## なんとなく、ソースも見たい

と思って探してみた。これだな。

https://github.com/micronaut-projects/micronaut-core/blob/v1.1.0/http-netty/src/main/java/io/micronaut/http/netty/channel/NettyThreadFactory.java#L40-L46

@TypeHint(value = {
        NioServerSocketChannel.class,
        NioSocketChannel.class
}, typeNames = {"sun.security.ssl.SSLContextImpl$TLSContext", "sun.nio.ch.SelectorImpl"},
   accessType = {TypeHint.AccessType.ALL_DECLARED_CONSTRUCTORS, TypeHint.AccessType.ALL_DECLARED_FIELDS}
)
public class NettyThreadFactory {

@TypeHint アノテーション@Introspected アノテーションがついてると、Native Image用のプロパティファイルを生成するみたい。

このアノテーションの処理はコンパイルタイムに GraalTypeElementVisitor がやってるっぽいね。

https://github.com/micronaut-projects/micronaut-core/blob/v1.1.0/graal/src/main/java/io/micronaut/graal/reflect/GraalTypeElementVisitor.java#L90

この仕組みができたから GraalClassLoadingAnalyzer の役目は終了したっぽい。 1.1.0 では削除されてる。

## Substitutionsに関して

は、ここだな。この前Netty試したときに書いたやつがそのまま書いてある。

https://github.com/micronaut-projects/micronaut-core/blob/v1.1.0/http-netty/src/main/java/io/micronaut/http/netty/graal/MicronautSubstitutions.java

## ライブラリー側でnative-image対応が可能になる

ロジコさんの記事に戻って見てみると、こう書いてある。

正しく適用すれば、Javaライブラリの作者は上述のメカニズムを使ってライブラリにnative-image互換性を持たせることができます。

なるほどなぁ。

## 気になるのは

こんな風に native-image.properties に書いてある場合

https://github.com/micronaut-projects/micronaut-core/blob/master/inject/src/main/resources/META-INF/native-image/io.micronaut/inject/native-image.properties

Args = -H:+ReportUnsupportedElementsAtRuntime \
       --allow-incomplete-classpath

知らないうちにオプションが適用されてしまってるんだろうなぁというところかな。

あと、Nettyのログは java.util.logging に限定されることになるんかな?(よく分かってない

## まとめ

Micronaut 1.1.0 からは、Native Imageのビルドが簡単になってたよ。面白かった。