これは、なにをしたくて書いたもの?
こちらのエントリを見て、GraalVMでネイティブイメージを作るMavenプラグインがあるというので、試してみようかと。
Simplifying native-image generation with Maven plugin and embeddable configuration – Logico Inside
お題としては、依存ライブラリ入りの簡単なサンプルアプリケーションを、Native Image Maven Pluginを使ってネイティブイメージに
ビルドしてみたいと思います。
環境
今回の環境は、こちら。
$ mvn -v Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T03:41:47+09:00) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 1.8.0_191, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "4.15.0-47-generic", arch: "amd64", family: "unix"
ここで見ているJavaはこちらですが、
$ java -version openjdk version "1.8.0_191" OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-2ubuntu0.18.04.1-b12) OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode) $ echo $JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64
GraalVM CEも用意しておきます。
$ /usr/local/graalvm-ce/bin/java -version openjdk version "1.8.0_202" OpenJDK Runtime Environment (build 1.8.0_202-20190206132807.buildslave.jdk8u-src-tar--b08) OpenJDK GraalVM CE 1.0.0-rc14 (build 25.202-b08-jvmci-0.56, mixed mode)
この時点での最新版のGraalVM CEは1.0.0-rc15なのですが、Maven Centralにあるプラグインが1.0.0.-rc14なので合わせて
おきました。
サンプルプログラム
それでは、サンプルプログラムを作成していきます。
Mavenの設定は、最初はこんな感じで用意。
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> <dependency> <groupId>commons-cli</groupId> <artifactId>commons-cli</artifactId> <version>1.4</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>com.oracle.substratevm</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>1.0.0-rc14</version> <executions> <execution> <goals> <goal>native-image</goal> </goals> <phase>package</phase> </execution> </executions> </plugin> </plugins> </build>
サンプルプログラム。
src/main/java/org/littlewings/graal/nativeimage/Server.java
package org.littlewings.graal.nativeimage; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import com.sun.net.httpserver.HttpServer; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; public class Server { public static void main(String... args) throws IOException { Options options = new Options(); options.addOption("p", "port", true, "bind port"); options.addOption("m", "message", true, "return message"); CommandLineParser parser = new DefaultParser(); CommandLine cli; try { cli = parser.parse(options, args); } catch (ParseException e) { return; } int port; if (cli.hasOption("port")) { port = Integer.parseInt(cli.getOptionValue("port")); } else { port = 8080; } String message; if (cli.hasOption("message")) { message = cli.getOptionValue("message"); } else { message = "Hello Server"; } System.out.printf("command line arguments: port = %d, message = %s%n", port, message); HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); server.createContext("/", exchange -> { byte[] body = StringUtils.replace("server message = $message", "$message", message).getBytes(StandardCharsets.UTF_8); exchange.sendResponseHeaders(200, body.length); exchange.getResponseBody().write(body); }); System.out.printf("[%s] Server startup.%n", LocalDateTime.now()); server.start(); } }
だいぶわざとらしいですが、Commons CLIとCommons Lang 3を使ったサンプルプログラムです。
JDKのHttpServerも使っています。
動作確認。
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.graal.nativeimage.Server -Dexec.args='-p 9000 -m hello' $ curl localhost:9000 server message = hello
単体ではOKそうです。
ネイティブイメージを作成してみる
それでは、ネイティブイメージを作成してみましょう。
packageゴールを実行した時に、ネイティブイメージが作成されるようにしています。
$ mvn package
エラーになります。
[ERROR] Failed to execute goal com.oracle.substratevm:native-image-maven-plugin:1.0.0-rc14:native-image (default) on project hello-native-image-plugin: JVM in /usr/lib/jvm/java-8-openjdk-amd64/jre does not support JVMCI interface: /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jvmci -> [Help 1]
「jvmci」がないよ、と怒られています。
どうやら、システムプロパティ「java.home」からファイルを探そうとしているようです。
実行には、GraalVMが必要なようですね。
というわけで、GraalVMを使うようにJAVA_HOMEを設定しなおします。
$ export JAVA_HOME=/usr/local/graalvm-ce $ mvn -version Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T03:41:47+09:00) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 1.8.0_202, vendor: Oracle Corporation, runtime: /usr/local/graalvm-ce/jre Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "4.15.0-47-generic", arch: "amd64", family: "unix"
実行。
$ mvn package
怒られました。
[ERROR] Failed to execute goal com.oracle.substratevm:native-image-maven-plugin:1.0.0-rc14:native-image (default) on project hello-native-image-plugin: Execution of /usr/local/graalvm-ce/jre/bin/native-image -cp $HOME/.m2/repository/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar:$HOME/.m2/repository/commons-cli/commons-cli/1.4/commons-cli-1.4.jar:/path/to/hello-native-image-plugin/target/hello-native-image-plugin-0.0.1-SNAPSHOT.jar returned non-zero result -> [Help 1]
その前にあるログを見ると、「エントリポイントとなるメソッドを持ったクラスが指定されていないよ」と言われています。
[INFO] Executing: /usr/local/graalvm-ce/jre/bin/native-image -cp $HOME/.m2/repository/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar:$HOME/.m2/repository/commons-cli/commons-cli/1.4/commons-cli-1.4.jar:/path/to/hello-native-image-plugin/target/hello-native-image-plugin-0.0.1-SNAPSHOT.jar Error: Please specify class containing the main entry point method. (see --help)
プラグインの設定に、mainClassを足してみます。
<plugin> <groupId>com.oracle.substratevm</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>1.0.0-rc14</version> <executions> <execution> <goals> <goal>native-image</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <mainClass>org.littlewings.graal.nativeimage.Server</mainClass> </configuration> </plugin>
ビルド。
$ mvn package
すると、今度はネイティブイメージを作成できるようになります。
[INFO] --- native-image-maven-plugin:1.0.0-rc14:native-image (default) @ hello-native-image-plugin --- [INFO] ImageClasspath Entry: org.apache.commons:commons-lang3:jar:3.8.1:compile (file://$HOME/.m2/repository/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar) [INFO] ImageClasspath Entry: commons-cli:commons-cli:jar:1.4:compile (file://$HOME/.m2/repository/commons-cli/commons-cli/1.4/commons-cli-1.4.jar) [INFO] ImageClasspath Entry: org.littlewings:hello-native-image-plugin:jar:0.0.1-SNAPSHOT (file:///path/to/hello-native-image-plugin/target/hello-native-image-plugin-0.0.1-SNAPSHOT.jar) [INFO] Executing: /usr/local/graalvm-ce/jre/bin/native-image -cp $HOME/.m2/repository/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar:$HOME/.m2/repository/commons-cli/commons-cli/1.4/commons-cli-1.4.jar:/path/to/hello-native-image-plugin/target/hello-native-image-plugin-0.0.1-SNAPSHOT.jar -H:Class=org.littlewings.graal.nativeimage.Server Build on Server(pid: 24568, port: 40653)* [org.littlewings.graal.nativeimage.server:24568] classlist: 1,786.25 ms [org.littlewings.graal.nativeimage.server:24568] (cap): 2,024.12 ms [org.littlewings.graal.nativeimage.server:24568] setup: 3,603.35 ms [org.littlewings.graal.nativeimage.server:24568] (typeflow): 5,870.85 ms [org.littlewings.graal.nativeimage.server:24568] (objects): 3,583.61 ms [org.littlewings.graal.nativeimage.server:24568] (features): 209.84 ms [org.littlewings.graal.nativeimage.server:24568] analysis: 9,884.39 ms [org.littlewings.graal.nativeimage.server:24568] universe: 448.58 ms [org.littlewings.graal.nativeimage.server:24568] (parse): 1,689.78 ms [org.littlewings.graal.nativeimage.server:24568] (inline): 2,445.84 ms [org.littlewings.graal.nativeimage.server:24568] (compile): 10,288.35 ms [org.littlewings.graal.nativeimage.server:24568] compile: 15,091.48 ms [org.littlewings.graal.nativeimage.server:24568] image: 1,066.55 ms [org.littlewings.graal.nativeimage.server:24568] write: 278.20 ms [org.littlewings.graal.nativeimage.server:24568] [total]: 32,382.06 ms [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 35.236 s [INFO] Finished at: 2019-04-07T21:33:36+09:00 [INFO] ------------------------------------------------------------------------
しかし、イメージ名が長いです…。mainメソッドを持ったクラス名から、イメージの名前が作られるからですね。
$ target/org.littlewings.graal.nativeimage.server -p 9000 -m hello command line arguments: port = 9000, message = hello [2019-04-07T21:34:47.858] Server startup.
設定に、imageNameを加えて生成されるネイティブイメージの名前を変えてみましょう。
<plugin> <groupId>com.oracle.substratevm</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>1.0.0-rc14</version> <executions> <execution> <goals> <goal>native-image</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <mainClass>org.littlewings.graal.nativeimage.Server</mainClass> <imageName>server-native-image</imageName> </configuration> </plugin>
ビルド。
$ mvn package
ビルド時の様子。
[INFO] Executing: /usr/local/graalvm-ce/jre/bin/native-image -cp $HOMEa/.m2/repository/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar:$HOME/.m2/repository/commons-cli/commons-cli/1.4/commons-cli-1.4.jar:/path/to/hello-native-image-plugin/target/hello-native-image-plugin-0.0.1-SNAPSHOT.jar -H:Class=org.littlewings.graal.nativeimage.Server -H:Name=server-native-image
イメージ名を変更することができました。
$ target/server-native-image -p 9000 -m hello command line arguments: port = 9000, message = hello [2019-04-07T21:38:55.890] Server startup.
他にも、buildArgsという「native-image」コマンドにオプションを設定できる項目もあるようです。
このあたりの設定の話は、こちらのエントリにも記載されているほか、
Simplifying native-image generation with Maven plugin and embeddable configuration (Japanese)
ソースコードで設定を見たらよい、という話もあるでしょう。
最終的に、どのような引数が「native-image」コマンドに渡されるかは、ビルド時の情報として出力されるのがよいですね。
mainClassは他のMavenプラグインの設定を参照するようにする
ここまでは、Native Image Maven Plugin自身にmainClassの設定を行っていました。
ここでは、その他のMavenプラグインの設定を引き継ぐようにしてみましょう。
Maven Shade Plugin、Maven Assembly Plugin、Maven Jar Plugin(優先順)の設定を利用することができるようです。
Native Image Maven PluginのmainClassをコメントアウトして、Maven Shade Pluginの設定を追加してみます。
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.littlewings.graal.nativeimage.Server</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> <plugin> <groupId>com.oracle.substratevm</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>1.0.0-rc14</version> <executions> <execution> <goals> <goal>native-image</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <!-- <mainClass>org.littlewings.graal.nativeimage.Server</mainClass> --> <imageName>server-native-image</imageName> </configuration> </plugin> </plugins>
ビルド。
$ mvn package
ちなみに、Maven Shade PluginでJARをまとめた結果をネイティブイメージにするというわけではなさそうです。
[INFO] --- native-image-maven-plugin:1.0.0-rc14:native-image (default) @ hello-native-image-plugin --- [INFO] ImageClasspath Entry: org.apache.commons:commons-lang3:jar:3.8.1:compile (file://$HOME/.m2/repository/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar) [INFO] ImageClasspath Entry: commons-cli:commons-cli:jar:1.4:compile (file://$HOME/.m2/repository/commons-cli/commons-cli/1.4/commons-cli-1.4.jar) [INFO] ImageClasspath Entry: org.littlewings:hello-native-image-plugin:jar:0.0.1-SNAPSHOT (file:///path/to/hello-native-image-plugin/target/hello-native-image-plugin-0.0.1-SNAPSHOT.jar) [INFO] Executing: /usr/local/graalvm-ce/jre/bin/native-image -cp $HOME/.m2/repository/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar:$HOME/.m2/repository/commons-cli/commons-cli/1.4/commons-cli-1.4.jar:/path/to/hello-native-image-plugin/target/hello-native-image-plugin-0.0.1-SNAPSHOT.jar -H:Class=org.littlewings.graal.nativeimage.Server -H:Name=server-native-image
Uber JARそのものもできています。
$ java -jar target/hello-native-image-plugin-0.0.1-SNAPSHOT.jar -p 9000 -m hello command line arguments: port = 9000, message = hello [2019-04-07T22:02:52.321] Server startup.
もうひとつ、Maven Assembly Pluginでも試してみましょう。
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.1.1</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>org.littlewings.graal.nativeimage.Server</mainClass> </manifest> </archive> </configuration> <executions> <execution> <id>assemble-all</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>com.oracle.substratevm</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>1.0.0-rc14</version> <executions> <execution> <goals> <goal>native-image</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <!-- <mainClass>org.littlewings.graal.nativeimage.Server</mainClass> --> <imageName>server-native-image</imageName> </configuration> </plugin> </plugins>
ビルド。
$ mvn package
ネイティブイメージのビルドについての結果は変わらず。
[INFO] --- native-image-maven-plugin:1.0.0-rc14:native-image (default) @ hello-native-image-plugin --- [INFO] ImageClasspath Entry: org.apache.commons:commons-lang3:jar:3.8.1:compile (file://$HOME/.m2/repository/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar) [INFO] ImageClasspath Entry: commons-cli:commons-cli:jar:1.4:compile (file://$HOME/.m2/repository/commons-cli/commons-cli/1.4/commons-cli-1.4.jar) [INFO] ImageClasspath Entry: org.littlewings:hello-native-image-plugin:jar:0.0.1-SNAPSHOT (file://path/to/hello-native-image-plugin/target/hello-native-image-plugin-0.0.1-SNAPSHOT.jar) [INFO] Executing: /usr/local/graalvm-ce/jre/bin/native-image -cp $HOME/.m2/repository/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar:$HOME/.m2/repository/commons-cli/commons-cli/1.4/commons-cli-1.4.jar:/path/to/hello-native-image-plugin/target/hello-native-image-plugin-0.0.1-SNAPSHOT.jar -H:Class=org.littlewings.graal.nativeimage.Server -H:Name=server-native-image
実行結果は、割愛します。
とりあえず、いろいろ試して設定のイメージはできるようになりましたよ、と。