Enhancing 3rd-Party Library Support in GraalVM Native Image with Shared Metadata

原文はこちら。
The original article was written by Vojin Jovanovic (Senior Research Manager at Oracle Labs).
https://medium.com/graalvm/enhancing-3rd-party-library-support-in-graalvm-native-image-with-shared-metadata-9eeae1651da4

GraalVM Native Imageでサードパーティライブラリに依存するアプリケーションのコンパイルが簡単になりました! 人気のあるライブラリの “到達可能性メタデータ “を提供するGitHubリポジトリができました。

GraalVM Reachability Metadata Repository
https://github.com/oracle/graalvm-reachability-metadata

しかし、リフレクション、リソースアクセス、dynamic proxy(動的プロキシ)、シリアライゼーションなどのJavaの動的機能により、一部の要素(クラス、メソッド、フィールドなど)の到達可能性が発見できない場合があります。到達可能でない要素は生成された実行ファイルに含まれないので、実行時のエラーにつながる可能性があります。

到達性が発見できない要素を含めるために、Native Image Builderでは、外部から提供される(以前はconfigurationとして知られていた)reachability metadata(到達性メタデータ)を必要とします。

Reachability Metadata
https://www.graalvm.org/22.2/reference-manual/native-image/metadata/

これまで、ユーザーは自分のアプリケーションだけでなく、アプリケーションが依存するライブラリについても、到達可能性メタデータを手動で定義し、維持する必要がありました。サードパーティーライブラリーには動的な言語機能が広く使われているため、これは大変な作業になる可能性があるのですが、ほとんどの開発者は自分が使っているライブラリの内部構造を知らないため、正しい到達性メタデータを定義することは困難です。

サードパーティ依存のメタデータを維持する負担を共有するために、私たちは、ユーザーがJavaエコシステム内のライブラリやフレームワークのNative Imageメタデータを共有し再利用できるように、reachability metadata repository(到達性メタデータリポジトリ)を作成しました。ライブラリやフレームワークのメンテナーが、そのようなメタデータを自分で出荷するのが理想的ですが、新しいリポジトリを使用して彼らのソフトウェアの古いバージョンのためのメタデータを後付けすることもできます。

この新しいリポジトリは、バージョン0.9.12からGraalVM Native Build Toolsに統合されています。

Native Build Tools
https://github.com/graalvm/native-build-tools

Gradleプロジェクトでメタデータリポジトリの自動使用を有効にするには、単に次のように指定します。

graalvmNative {
  metadataRepository {
    enabled = true
  }
}

【訳注】Mavenの場合はこちら。

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <version>${native.maven.plugin.version}</version>
    <configuration>
        ...
        <metadataRepository>
            <enabled>true</enabled>
            <url>${metadata.url}</url>
        </metadataRepository>
        ...
    </configuration>
</plugin>

metadataRepositoryブロックは、Native Build Toolsが依存関係のメタデータをリポジトリで検索し、メタデータをダウンロードし、自動的にビルドに含めるようにするためのものです。リポジトリの設定の詳細については、MavenとGradleのビルドツールのドキュメントを参照してください。

GraalVM Reachability Metadata Support
https://graalvm.github.io/native-build-tools/0.9.13/maven-plugin.html#metadata-support
GraalVM Reachability Metadata Support
https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html#metadata-support

メタデータリポジトリは、GraalVM、Micronaut、Spring Boot、およびQuarkusチームのコラボレーションの成果であり、愛情を込めて構築されました。

GraalVM
https://www.graalvm.org/
Micronaut
https://micronaut.io/
Spring Boot
https://spring.io/projects/spring-boot
Quarkus
https://quarkus.io/

このコラボレーションによって貢献されたライブラリの初期セットは、metadataフォルダにあります。

graalvm-reachability-metadata
https://github.com/oracle/graalvm-reachability-metadata/tree/master/metadata

Why a Common Repository?

ネイティブ・イメージは新しい技術であり、ライブラリの作者は、ライブラリのメタデータを維持するために余計な作業をすることを時としてためらうことがあります。このため、個人やフレームワークの作者は、メタデータを自分のプロジェクトに限定していますが、このことが、不必要な繰り返し、不完全なメタデータ、コミュニティの断片化などを引き起こしています。さらに、レガシーライブラリを使用している場合、外部から提供されるメタデータがなければ、Native Imageのサポートは不可能です。

Spring FrameworkとSpring BootのコミッターであるBrian Clozelによる、このトピックに関するコミュニティの見解も紹介します。

With the reachability metadata, the GraalVM team kicks the native-image adoption into high gear. Spring projects have lots of integrations with the Java ecosystem, so it’s important to provide a wide support scope for the native experience. As a framework developer, maintaining all that metadata on my side is not sustainable nor efficient for the Java community. The reachability metadata repository gives an easy way for all Java developers to collaborate and bring the entire ecosystem up to speed.

(reachability metadataで、GraalVMチームはネイティブイメージの採用を本格化させる。Springのプロジェクトは、Javaエコシステムと多く統合しているため、ネイティブ体験のための広いサポート範囲を提供することが重要である。フレームワーク開発者として、Spring側ですべてのメタデータを維持することは、Javaコミュニティにとって持続可能でも効率的でもない。すべてのJava開発者が協力し、エコシステム全体をスピードアップさせるための簡単な方法こそが、reachability metadata repositoryである)

Brian Clozel (@bclozel)

リポジトリで提供されるメタデータは、Native Imageが最初のユースケースではありますが、Native Imageだけを対象としているわけではありません。Reachability metadataは、使用対象のすべての要素を事前に知っていることが必要な(つまりclosed worldが必要な)他のユースケースにも利用できます。

Contributing Metadata to the Repository

Step 1: Collect Metadata

リポジトリにライブラリの到達性メタデータがない場合、Native Image Agentを有効にして、ライブラリのテストスイートを実行すると、最も簡単にメタデータを生成できます。エージェントは native-image と一緒にインストールされ、静的解析では発見できない、アプリケーション実行時に使用される要素を特定するために使用できます。

Gradle(および将来的にはMaven)を使用しているライブラリは、Native Build Toolsを利用して自動的にエージェントを有効にし、適切なタスク/ターゲットからメタデータを収集できます。

Support the conditional configuration mode of the native-image-agent #206
https://github.com/graalvm/native-build-tools/issues/206

Gradleユーザーは以下のブロックを追加することでエージェントを設定できます。

graalvmNative {
  agent {
    defaultMode = “conditional”
    modes {
      conditional {
        userCodeFilterPath = “user-code-filter.json”
      }
    }
  metadataCopy {
     mergeWithExisting = true
     inputTaskNames.add(“test”)
     outputDirectories.add(
       “src/main/resources/META-INF/native-image/acme”)
     }
  }
}

テストタスクをエージェントとともに実行するには、以下のコマンドを実行します。

./gradlew -Pagent test

これにより、プロジェクトのビルド出力ディレクトリ(build/native/agent/test以下)にメタデータが生成されます。

metadataCopyタスクは、出力ディレクトリで指定したプロジェクトに、メタデータをコピー、時にはマージします。このタスクはコマンドラインで設定することもできます。

./gradlew metadataCopy –task test –dir src/main/resources/META-INF/native-image/acme

条件付きモードの場合、どのクラスがターゲットライブラリに属するかをエージェントに伝えるエージェントフィルタファイルを渡す必要があります。このフィルタファイルは、他のエージェントフィルタファイルと同じフォーマットです。条件は、このフィルタによって名前が一致したクラスに対してのみ生成されます。

以下は、ライブラリのフィルタファイルのサンプル (user-code-filter.json) ですが、このフィルタファイルでは、com.acme に含まれるすべてのライブラリのクラスを対象にする、というものです。

{
  ”rules”: [
    {”excludeClasses”: ”**”},
    {”includeClasses”: ”com.acme.**”}
  ]
}

収集されたメタデータは、agentブロックの extraFilterPath を設定すると、さらにフィルタリングできます。

graalvmNative {
  agent {
    modes {
      conditional {
        userCodeFilterPath = “user-code-filter.json”
        extraFilterPath = “extra-code-filter.json”
    }
  }
}

このフィルタでは、マッチした条件とメタデータを除外します。これにより、無関係なクラス、例えばMockitoのモックのようにテスト時にのみ使用されるクラスを簡単にフィルタリングできます。

他のビルドシステムについては以下のURLにある手順に従ってください。

Other Build Systems
https://github.com/oracle/graalvm-reachability-metadata/blob/master/docs/CollectingMetadata.md#other-build-systems

Step 2: Contribute Metadata

コミュニティのために最も良いことは、生成されたメタデータとビルドファイルの変更をライブラリ自体に含めることです。ライブラリへのPRを作成する前に、以下を実施してください。

  1. メタデータをプロジェクトのソースの META-INF/native-image/artifact.id の下に metadataCopy でコピー
  2. 新たに追加されたメタデータを使ってライブラリをテストするCIタスクの導入

ライブラリのメンテナーがこの変更を受け入れない場合、次善の策として、ライブラリの課題追跡システム(issue tracker)でチケットを開き、コミュニティがこの機能を支持することです。その間に、reachability-metadata リポジトリに対するPRを出してください。

ビルドの破損を避けるために、このリポジトリに貢献する上でいくつかのルールがあります。

  1. このリポジトリにはメタデータしか含まれていません。これは、メタデータは誰かのビルドを壊すことはできず、イメージに新しい要素を追加するだけだからです。
  2. メタデータは条件付きでなければなりません。無条件のメタデータは安全ですが、イメージのサイズを不必要に大きくする可能性があります。
  3. メタデータはリポジトリでテストされている必要があります。テストは、テストスイート全体のサブセットでもかまいませんが、信頼性を高め、ライブラリのコア機能の偶発的な破損を避けるために、機能を十分にテストする必要があります。

詳細については、コントリビューションガイドを参照してください。

Contributing
https://github.com/oracle/graalvm-reachability-metadata/blob/master/CONTRIBUTING.md

FAQ

Q: Can the metadata from the repo break my build? (リポジトリからのメタデータは自分のビルドを壊す可能性があるか?)

メタデータはイメージに新規要素を追加するだめなので、それはないはずです。ビルドの破壊が発生する場合、GraalVM Issue TrackerにIssueを作成してください。

Issues
https://github.com/oracle/graal/issues

Q: Can the contents of the repo use Native Image internal APIs (e.g., substitutions)? (リポジトリの内容は、Native Image内部APIを利用できますか?例えば置換とか。。)

いいえ、置換はJDKの機能のみを対象としています。

Q: Does the metadata include any of the flags for the Native Image build, e.g., --initialize-at-build-time? (メタデータには Native Image のビルドのためのフラグ(例:--initialize-at-build-time)が含まれていますか?)

いいえ、フラグは合成されないため、コミュニティを分断する可能性があります。つまり、いくつかのライブラリの組み合わせのビルドを破壊する可能性があり、一方で、それぞれの個別のライブラリはそれ自体で機能する、という可能性があります。

Q: Does the metadata include Native Image Features? (メタデータにはNatice ImageのFeaturesが含まれていますか?)

いいえ、入っていません。誤用するとビルドを破壊する可能性があるためです。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中