CLOVER🍀

That was when it all began.

Native Image Maven Pluginを使って、GraalVMでネイティブイメージを作る

これは、なにをしたくて書いたもの?

こちらのエントリを見て、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」からファイルを探そうとしているようです。

https://github.com/oracle/graal/blob/vm-1.0.0-rc14/substratevm/src/native-image-maven-plugin/src/main/java/com/oracle/substratevm/NativeImageMojo.java#L293-L295

実行には、GraalVMが必要なようですね。

https://github.com/oracle/graal/blob/vm-1.0.0-rc14/substratevm/src/native-image-maven-plugin/src/main/java/com/oracle/substratevm/NativeImageMojo.java#L158

というわけで、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)

ソースコードで設定を見たらよい、という話もあるでしょう。

https://github.com/oracle/graal/blob/vm-1.0.0-rc14/substratevm/src/native-image-maven-plugin/src/main/java/com/oracle/substratevm/NativeImageMojo.java#L69-L85

最終的に、どのような引数が「native-image」コマンドに渡されるかは、ビルド時の情報として出力されるのがよいですね。

mainClassは他のMavenプラグインの設定を参照するようにする

ここまでは、Native Image Maven Plugin自身にmainClassの設定を行っていました。

ここでは、その他のMavenプラグインの設定を引き継ぐようにしてみましょう。

Maven Shade Plugin、Maven Assembly Plugin、Maven Jar Plugin(優先順)の設定を利用することができるようです。

https://github.com/oracle/graal/blob/vm-1.0.0-rc14/substratevm/src/native-image-maven-plugin/src/main/java/com/oracle/substratevm/NativeImageMojo.java#L338-L346

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

実行結果は、割愛します。

とりあえず、いろいろ試して設定のイメージはできるようになりましたよ、と。