CLOVER🍀

That was when it all began.

Javaのコマンドラむンアプリケヌション向けのフレヌムワヌク、picocliで遊ぶ

これは、なにをしたくお曞いたもの

ちょっず前から、picocliずいうJavaでのコマンドラむンアプリケヌション向けのフレヌムワヌクがあるのが気になっおいたしお。

picocli - a mighty tiny command line interface

これたでJavaのコマンドラむンアプリケヌション向けのラむブラリなどずいえば、こちらを䜿っおいたした。

args4j parent - Args4j

Commons CLI – Home

個人的にはargs4jをよく䜿っおいたしたね。

picocliもかなり良さそうなので、1床詊しおおこうかなず。

picocli

繰り返したすが、Javaのコマンドラむンアプリケヌション向けのフレヌムワヌクです。

picocli - a mighty tiny command line interface

GitHub - remkop/picocli: Picocli is a modern framework for building powerful, user-friendly, GraalVM-enabled command line apps with ease. It supports colors, autocompletion, subcommands, and more. In 1 source file so apps can include as source & avoid adding a dependency. Written in Java, usable from Groovy, Kotlin, Scala, etc.

GNU、POSIX、MS-DOSなどのコマンドラむン構文のサポヌト、カラヌ、ヘルプの生成から、オプション、匕数パラメヌタヌ、
サブコマンドも扱えるそうで。

さらに、GraalVMによるネむティブむメヌゞの䜜成もサポヌトしおいるので、よりCLIフレンドリヌですね。

ドキュメントがずおも充実しおいるのもポむントで、かなり機胜があるこずがよくわかりたす。

picocli - a mighty tiny command line interface

picocli 4.2.0 API

参考蚘事もGitHub䞊でリンクされおいたり。

Articles & Presentations

Articles & Presentations / 日本語

説明はこれくらいにしお、詊しおいっおみたしょう。

環境

今回の環境は、こちら。

$ java --version
openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment GraalVM CE 19.3.1 (build 11.0.6+9-jvmci-19.3-b07)
OpenJDK 64-Bit Server VM GraalVM CE 19.3.1 (build 11.0.6+9-jvmci-19.3-b07, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.6, vendor: Oracle Corporation, runtime: /usr/local/graalvm-ce
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.3.0-40-generic", arch: "amd64", family: "unix"

ネむティブむメヌゞも䜜ろうず思うので、GraalVMを䜿いたす。

準備ずお題

たずはシンプルに。Maven䟝存関係はこちら。

        <dependency>
            <groupId>info.picocli</groupId>
            <artifactId>picocli</artifactId>
            <version>4.2.0</version>
        </dependency>

お題ですが、すごく簡易なcat、grepコマンドを䜜り、さらにcat、grepをサブコマンドにしたランチャヌを䜜りたす。最埌にネむティブ
むメヌゞを䜜っお完了ずしたしょうか。

Getting Started的に

簡単なCatコマンドをマネたものを䜜っおみたしょう。

匕数ずしおファむルを任意数指定可胜、ファむルを指定しなかったら暙準入力から読み、オプションずしおは「-n」の代わりに
「-p true」ずするず行番号を衚瀺するようにしたす。オプションに冗長感ありたすけど、たずはパラメヌタヌありのオプション
ずいうこずで。

匕数やオプションなどはアノテヌションで衚珟しおいくのですが、基本はこちらを芋るずわかる気がしたす。

Options and Parameters

䜜成した゜ヌスコヌドは、こちら。
src/main/java/org/littlewings/picocli/Cat.java

package org.littlewings.picocli;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Stream;

import picocli.CommandLine;

@CommandLine.Command(name = "cat")
public class Cat implements Callable<Integer> {
    @CommandLine.Option(names = {"-h", "--help"}, description = "show this help", usageHelp = true)
    boolean showHelp;

    @CommandLine.Option(names = {"-p", "--print-line-number"}, paramLabel = "true or false", description = "number all output lines", defaultValue = "false")
    String printLineNumber;

    @CommandLine.Parameters(paramLabel = "FILE")
    List<Path> paths;

    int lineCount = 1;

    @Override
    public Integer call() throws Exception {
        if (paths != null) {
            for (Path path : paths) {
                try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
                    if ("true".equals(printLineNumber)) {
                        lines.forEach(line -> System.out.printf("%d: %s%n", lineCount++, line));
                    } else {
                        lines.forEach(line -> System.out.printf("%s%n", line));
                    }
                }
            }
        } else {
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
            if ("true".equals(printLineNumber)) {
                reader.lines().forEach(line -> System.out.printf("%d: %s%n", lineCount++, line));
            } else {
                reader.lines().forEach(line -> System.out.printf("%s%n", line));
            }

        }

        return 0;
    }

    public static void main(String... args) {
        System.exit(new CommandLine(new Cat()).execute(args));
    }
}

Callableを実装したクラスを䜜成。型パラメヌタヌには、Integerを指定。callの戻り倀をコマンドの終了ステヌタスずしお䜿いたす。

@CommandLine.Command(name = "cat")
public class Cat implements Callable<Integer> {

@Commandアノテヌションを付䞎しお、nameを「cat」ずしたす。このnameの倀が、サブコマンドやヘルプで䜿われたす。

@Optionアノテヌションで文字通りオプションを、@Parametersでコマンド匕数を衚したす。

    @CommandLine.Option(names = {"-h", "--help"}, description = "show this help", usageHelp = true)
    boolean showHelp;

    @CommandLine.Option(names = {"-p", "--print-line-number"}, paramLabel = "true or false", description = "number all output lines", defaultValue = "false")
    String printLineNumber;

    @CommandLine.Parameters(paramLabel = "FILE")
    List<Path> paths;

アノテヌションを付䞎したフィヌルドには、オプションや匕数で指定した倀が入りたす。

String以倖の型でも受け取れたす。

Strongly Typed Everything

Listや配列などを䜿うず、耇数個指定できるオプションや匕数ずしお䜿えわれたす。

@Optionで「usageHelp」をtrueにするず、ヘルプオプションずしお扱われたす。

あずはcallメ゜ッドの䞭を実装しお

    @Override
    public Integer call() throws Exception {
        if (paths != null) {
            for (Path path : paths) {
                try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
                    if ("true".equals(printLineNumber)) {
                        lines.forEach(line -> System.out.printf("%d: %s%n", lineCount++, line));
                    } else {
                        lines.forEach(line -> System.out.printf("%s%n", line));
                    }
                }
            }
        } else {
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
            if ("true".equals(printLineNumber)) {
                reader.lines().forEach(line -> System.out.printf("%d: %s%n", lineCount++, line));
            } else {
                reader.lines().forEach(line -> System.out.printf("%s%n", line));
            }

        }

        return 0;
    }

mainメ゜ッドを䜜成。

    public static void main(String... args) {
        System.exit(new CommandLine(new Cat()).execute(args));
    }

このCallableのむンスタンスを、CommandLineのコンストラクタに枡しおexecuteを呌び出したす。この時に、mainメ゜ッドの匕数を
枡すず@Optionや@Parametersアノテヌションを付䞎したフィヌルドに倀が入った状態でcallメ゜ッドが呌び出されたす。

executeの戻り倀は、callメ゜ッドの戻り倀になりたす。

では、詊しおみたしょう。

サンプルのファむルを䜜成。

$ python3 -c 'import this' > zen_of_python.txt

実行。倧着しおたすが、「mvn exec:java」で実行したす。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Cat -Dexec.args='zen_of_python.txt'

〜省略〜

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

行番号を衚瀺しおみたす。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Cat -Dexec.args='-p true zen_of_python.txt'

〜省略〜

1: The Zen of Python, by Tim Peters
2: 
3: Beautiful is better than ugly.
4: Explicit is better than implicit.
5: Simple is better than complex.
6: Complex is better than complicated.
7: Flat is better than nested.
8: Sparse is better than dense.
9: Readability counts.
10: Special cases aren't special enough to break the rules.
11: Although practicality beats purity.
12: Errors should never pass silently.
13: Unless explicitly silenced.
14: In the face of ambiguity, refuse the temptation to guess.
15: There should be one-- and preferably only one --obvious way to do it.
16: Although that way may not be obvious at first unless you're Dutch.
17: Now is better than never.
18: Although never is often better than *right* now.
19: If the implementation is hard to explain, it's a bad idea.
20: If the implementation is easy to explain, it may be a good idea.
21: Namespaces are one honking great idea -- let's do more of those!

OKそうですね。

もうひず぀ファむルを増やしおみたしょう。

$ lsb_release -a > ubuntu_version.txt

耇数のファむルを察象にしおみたす。オプションは、ロングオプションで指定しおみたす。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Cat -Dexec.args='--print-line-number true zen_of_python.txt ubuntu_version.txt'

〜省略〜

1: The Zen of Python, by Tim Peters
2: 
3: Beautiful is better than ugly.
4: Explicit is better than implicit.
5: Simple is better than complex.
6: Complex is better than complicated.
7: Flat is better than nested.
8: Sparse is better than dense.
9: Readability counts.
10: Special cases aren't special enough to break the rules.
11: Although practicality beats purity.
12: Errors should never pass silently.
13: Unless explicitly silenced.
14: In the face of ambiguity, refuse the temptation to guess.
15: There should be one-- and preferably only one --obvious way to do it.
16: Although that way may not be obvious at first unless you're Dutch.
17: Now is better than never.
18: Although never is often better than *right* now.
19: If the implementation is hard to explain, it's a bad idea.
20: If the implementation is easy to explain, it may be a good idea.
21: Namespaces are one honking great idea -- let's do more of those!
22: Distributor ID: Ubuntu
23: Description:    Ubuntu 18.04.4 LTS
24: Release:    18.04
25: Codename:   bionic

ファむルを指定せずに、暙準出力から入力しおみたしょう。

$ cat ubuntu_version.txt | mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Cat

〜省略〜

Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

最埌に、ヘルプを衚瀺。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Cat -Dexec.args='-h'

〜省略〜

Usage: cat [-h] [-p=true or false] [FILE...]
      [FILE...]
  -h, --help      show this help
  -p, --print-line-number=true or false
                  number all output lines

ちなみに、このヘルプはカラヌで芋れたす。

OKですね。

もうひず぀サンプルを

続いお、grepもどきを䜜成しおみたしょう。

゜ヌスコヌドは、こちら。
src/main/java/org/littlewings/picocli/Grep.java

package org.littlewings.picocli;

import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import picocli.CommandLine;

@CommandLine.Command(name = "grep")
public class Grep implements Callable<Integer> {
    @CommandLine.Option(names = {"-h", "--help"}, description = "show this help", usageHelp = true)
    boolean showHelp;

    @CommandLine.Option(names = {"-n", "--line-number"}, description = "display match line number", defaultValue = "false")
    boolean printLineNumber;

    @CommandLine.Option(names = {"-v", "--invert-match"}, description = "print invert match")
    boolean printInvertMatch;

    @CommandLine.Parameters(index = "0", paramLabel = "PATTERN")
    String pattern;

    @CommandLine.Parameters(index = "1", paramLabel = "FILE")
    Path path;

    @Override
    public Integer call() throws Exception {
        Pattern p = Pattern.compile(pattern);

        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            String line;
            int lineCount = 1;

            while ((line = reader.readLine()) != null) {
                lineCount++;

                Matcher m = p.matcher(line);

                boolean match = m.find();

                if (match && !printInvertMatch) {
                    if (printLineNumber) {
                        System.out.printf("%d: %s%n", lineCount, line);
                    } else {
                        System.out.printf("%s%n", line);
                    }
                } else if (!match && printInvertMatch) {
                    if (printLineNumber) {
                        System.out.printf("%d: %s%n", lineCount, line);
                    } else {
                        System.out.printf("%s%n", line);
                    }
                }
            }
        }

        return 0;
    }

    public static void main(String... args) {
        System.exit(new CommandLine(new Grep()).execute(args));
    }
}

先ほどず違うのは、booleanのオプションを蚭けお、指定すればtrueずなるようにしたした。オプションずしおは、「-n」ず「-v」を定矩
しおいたす。

    @CommandLine.Option(names = {"-n", "--line-number"}, description = "display match line number", defaultValue = "false")
    boolean printLineNumber;

    @CommandLine.Option(names = {"-v", "--invert-match"}, description = "print invert match")
    boolean printInvertMatch;

Options and Parameters

たた、匕数の䜍眮も指定するようにしおみたす。入力ずなるファむルは、耇数指定できないようにしたした。たた、ファむルを
指定しないず暙準入力から読み蟌むような動きもしたせん。

    @CommandLine.Parameters(index = "0", paramLabel = "PATTERN")
    String pattern;

    @CommandLine.Parameters(index = "1", paramLabel = "FILE")
    Path path;

Positional Parameters

その他は、そんなにcatの䟋ず倉わらないので、説明は省略。

確認。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Grep -Dexec.args='better zen_of_python.txt'

〜省略〜

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Now is better than never.
Although never is often better than *right* now.

行番号を出力。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Grep -Dexec.args='-n better zen_of_python.txt'

〜省略〜

4: Beautiful is better than ugly.
5: Explicit is better than implicit.
6: Simple is better than complex.
7: Complex is better than complicated.
8: Flat is better than nested.
9: Sparse is better than dense.
18: Now is better than never.
19: Although never is often better than *right* now.

いわゆる「-v」をロングオプションで指定。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Grep -Dexec.args='-n --invert-match better zen_of_python.txt'

〜省略〜

2: The Zen of Python, by Tim Peters
3: 
10: Readability counts.
11: Special cases aren't special enough to break the rules.
12: Although practicality beats purity.
13: Errors should never pass silently.
14: Unless explicitly silenced.
15: In the face of ambiguity, refuse the temptation to guess.
16: There should be one-- and preferably only one --obvious way to do it.
17: Although that way may not be obvious at first unless you're Dutch.
20: If the implementation is hard to explain, it's a bad idea.
21: If the implementation is easy to explain, it may be a good idea.
22: Namespaces are one honking great idea -- let's do more of those!

ヘルプ。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Grep -Dexec.args='--help'

〜省略〜

Usage: grep [-hnv] PATTERN FILE
      PATTERN
      FILE
  -h, --help           show this help
  -n, --line-number    display match line number
  -v, --invert-match   print invert match

サブコマンドを䜿う

䜜成するコマンドの最埌ずしお、サブコマンドを䜿っおみたしょう。

Subcommands

先ほど䜜ったcat、grepをサブコマンドにしたものを䜜っおみたす。

こんな感じで。 src/main/java/org/littlewings/picocli/Launcher.java

package org.littlewings.picocli;

import java.util.concurrent.Callable;

import picocli.CommandLine;

@CommandLine.Command(name = "launcher", subcommands = {Cat.class, Grep.class})
public class Launcher implements Callable<Integer> {
    @CommandLine.Option(names = {"-h", "--help"}, description = "show this help", usageHelp = true)
    boolean showHelp;

    @Override
    public Integer call() throws Exception {
        return 0;
    }

    public static void main(String... args) {
        System.exit(new CommandLine(new Launcher()).execute(args));
    }
}

@Commandアノテヌションのsubcommandsに、先ほどたで䜜成しおいたコマンドを、サブコマンドずしお登録したす。

@CommandLine.Command(name = "launcher", subcommands = {Cat.class, Grep.class})
public class Launcher implements Callable<Integer> {

callメ゜ッドの䞭身は、特になにもなくおも動きたす。

ずりあえず、ヘルプを芋おみたしょう。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Launcher -Dexec.args='-h'

〜省略〜

Usage: launcher [-h] [COMMAND]
  -h, --help   show this help
Commands:
  cat
  grep

ヘルプでサブコマンドが芋れたすね。

サブコマンドで「grep」を指定しお、ヘルプを芋おみたしょう。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Launcher -Dexec.args='grep -h'

〜省略〜

Usage: launcher grep [-hnv] PATTERN FILE
      PATTERN
      FILE
  -h, --help           show this help
  -n, --line-number    display match line number
  -v, --invert-match   print invert match

grepのヘルプが芋れたすね。

もちろん、サブコマンドを指定しお実行もできたす。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.picocli.Launcher -Dexec.args='grep -n Python zen_of_python.txt'

〜省略〜

2: The Zen of Python, by Tim Peters

補足

今回はコマンドの䞭身をCallable#callずしお実装したしたが、その他にもRunnableずIExitCodeGeneratorむンタヌフェヌスを実装したりする
方法もあったり

Executing Commands

オプション、匕数のパヌス結果をもっず现かくコントロヌルする方法もあったりするようです。

DIY Command Execution

どっちにしろ、機胜がずおも倚くお曞ききれないので、今回はこのくらいにしおおきたす。

ネむティブむメヌゞを䜜る

最埌に、GraalVMを䜿っおネむティブむメヌゞを䜜っおみたしょう。

GraalVM Native Image

Pluggable Annotation Processing APIを䜿ったモゞュヌルがあるので、こちらをpom.xmlに蚭定したす。

Annotation Processor

Maven Compiler Pluginの、annotationProcessorPathsずしお指定したす。

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>info.picocli</groupId>
                            <artifactId>picocli-codegen</artifactId>
                            <version>4.2.0</version>
                        </path>
                    </annotationProcessorPaths>
                    <compilerArgs>
                        <arg>-Aproject=${groupId}/${artifactId}</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>

この状態でビルドするず

$ mvn compile

「compilerArgs」で指定したパス「META-INF/native-image/picocli-generated」配䞋ですがに、ファむルが生成されたす。

$ find target/classes/META-INF/native-image/picocli-generated/org.example/picocli-example -type f
target/classes/META-INF/native-image/picocli-generated/org.example/picocli-example/proxy-config.json
target/classes/META-INF/native-image/picocli-generated/org.example/picocli-example/reflect-config.json
target/classes/META-INF/native-image/picocli-generated/org.example/picocli-example/resource-config.json

今回は、リフレクションに関する蚭定だけ、意味のある倀が生成されたした。
target/classes/META-INF/native-image/picocli-generated/org.example/picocli-example/reflect-config.json

[
  {
    "name" : "org.littlewings.picocli.Cat",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "fields" : [
      { "name" : "paths" },
      { "name" : "printLineNumber" },
      { "name" : "showHelp" }
    ]
  },
  {
    "name" : "org.littlewings.picocli.Grep",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "fields" : [
      { "name" : "path" },
      { "name" : "pattern" },
      { "name" : "printInvertMatch" },
      { "name" : "printLineNumber" },
      { "name" : "showHelp" }
    ]
  },
  {
    "name" : "org.littlewings.picocli.Launcher",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "fields" : [
      { "name" : "showHelp" }
    ]
  }
]

あずは、GraalVMのNative Image Maven Pluginを䜿いたす。

Integration with Maven

こんな感じで。

                <plugins>
                    <plugin>
                        <groupId>org.graalvm.nativeimage</groupId>
                        <artifactId>native-image-maven-plugin</artifactId>
                        <version>19.3.1</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>native-image</goal>
                                </goals>
                                <phase>package</phase>
                            </execution>
                        </executions>
                        <configuration>
                            <skip>false</skip>
                            <imageName>cat</imageName>
                            <buildArgs>
                                --no-fallback
                            </buildArgs>
                            <mainClass>org.littlewings.picocli.Cat</mainClass>
                        </configuration>
                    </plugin>
                </plugins>

コマンドが3぀あるので、今回はProfileで分けたした。

    <profiles>
        <profile>
            <id>native-cat</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.graalvm.nativeimage</groupId>
                        <artifactId>native-image-maven-plugin</artifactId>
                        <version>19.3.1</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>native-image</goal>
                                </goals>
                                <phase>package</phase>
                            </execution>
                        </executions>
                        <configuration>
                            <skip>false</skip>
                            <imageName>cat</imageName>
                            <buildArgs>
                                --no-fallback
                            </buildArgs>
                            <mainClass>org.littlewings.picocli.Cat</mainClass>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>native-grep</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.graalvm.nativeimage</groupId>
                        <artifactId>native-image-maven-plugin</artifactId>
                        <version>19.3.1</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>native-image</goal>
                                </goals>
                                <phase>package</phase>
                            </execution>
                        </executions>
                        <configuration>
                            <skip>false</skip>
                            <imageName>grep</imageName>
                            <buildArgs>
                                --no-fallback
                            </buildArgs>
                            <mainClass>org.littlewings.picocli.Grep</mainClass>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>native-launcher</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.graalvm.nativeimage</groupId>
                        <artifactId>native-image-maven-plugin</artifactId>
                        <version>19.3.1</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>native-image</goal>
                                </goals>
                                <phase>package</phase>
                            </execution>
                        </executions>
                        <configuration>
                            <skip>false</skip>
                            <imageName>launcher</imageName>
                            <buildArgs>
                                --no-fallback
                            </buildArgs>
                            <mainClass>org.littlewings.picocli.Launcher</mainClass>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

では、ビルド。

$ mvn -P native-cat package && mvn -P native-grep package && mvn -P native-launcher package

確認。

$ ./target/cat -p true zen_of_python.txt 
1: The Zen of Python, by Tim Peters
2: 
3: Beautiful is better than ugly.
4: Explicit is better than implicit.
5: Simple is better than complex.
6: Complex is better than complicated.
7: Flat is better than nested.
8: Sparse is better than dense.
9: Readability counts.
10: Special cases aren't special enough to break the rules.
11: Although practicality beats purity.
12: Errors should never pass silently.
13: Unless explicitly silenced.
14: In the face of ambiguity, refuse the temptation to guess.
15: There should be one-- and preferably only one --obvious way to do it.
16: Although that way may not be obvious at first unless you're Dutch.
17: Now is better than never.
18: Although never is often better than *right* now.
19: If the implementation is hard to explain, it's a bad idea.
20: If the implementation is easy to explain, it may be a good idea.
21: Namespaces are one honking great idea -- let's do more of those!


$ ./target/grep -v Ubuntu ubuntu_version.txt 
Release:    18.04
Codename:   bionic


$ ./target/launcher --help
Usage: launcher [-h] [COMMAND]
  -h, --help   show this help
Commands:
  cat
  grep

OKですね。

たずめ

Javaのコマンドラむンアプリケヌション向けのフレヌムワヌクである、picocliを詊しおみたした。

最初はドキュメントの量を芋おすごいなヌず思いたしたが、わかりやすく曞かれおいおずっ぀きやすかったず思いたす。

GraalVMによる、ネむティブむメヌゞの䜜成に察応しおいるずころもよいですね。

CLIを䜜る時には、今床からこちらを掻甚しおいきたしょう。