Javaで単一の実行可能ファイル!
JITコンパイル:Oracle Java 8(Java 11は
もうすぐ)と置き換えて使用可能。早い!
Truffle フレームワークで「polyglot」多言語対応:JavaScript, R, Ruby, Python, C, C++, JVM言語, …
AOT (Ahead Of Time) コンパイル:
ネイティブイメージ!
Oracle の提供。オープンソース。Community版とEnterprise版がある。
「閉世界仮説」:ビルド時に全ての到達可能なコードが見える前提
動的なJava機能はコンフィギュレーションが必要:リフレクション、リソース、プロキシ、JNI
実行時にクラスロードはできない
AOTはコマンドラインアプリにピッタリでは?
$ echo "hi" > hi.txt
$ checksum -a md5 hi.txt
764efa883dda1e11db47671c4a3bbd9e
$ checksum -a sha1 hi.txt
55ca6286e3e4f4fba5d0448333fa99fc5a404a73
標準的なオプションとパラメータ
例えば、UNIX:
import picocli.CommandLine.Option;
class CheckSum {
@Option(names = {"-a", "--algorithm"}, (1)
description = "MD-5, SHA-1, SHA-256, ...") (2)
String algorithm = "MD-5"; (3)
1 | 名前付きのオプション |
2 | ヘルプ時に表示する説明 |
3 | デフォルト値 |
import picocli.CommandLine.Parameters;
class CheckSum {
// ...
@Parameters(index= "0", (1)
description= "The file whose checksum to calculate.")
File file; (2)
1 | 位置パラメーターの位置 |
2 | 型変換:文字列➝ java.io.File |
標準なヘルプオプション:
使い方のヘルプ: -h
, --help
バージョン情報: -V
, --version
@Command(name = "myapp", version = "myapp 1.0") (1)
class MyApp {
@Option(names = {"-h", "--help"}, usageHelp = true, (2)
description = "Show this help message and exit.")
boolean isHelpRequested;
@Option(names = {"-V", "--version"}, versionHelp = true, (3)
description = "Print version information and exit.")
boolean isVersionRequested;
1 | バージョン情報 |
2 | usageHelp 特別なオプション |
3 | versionHelp 特別なオプション |
public static void main(String... args) {
MyApp myApp = new MyApp();
CommandLine cmd = new CommandLine(myApp);
cmd.parseArgs(args);
if (cmd.isUsageHelpRequested()) { (1)
cmd.usage(System.out);
} else if (cmd.isVersionHelpRequested()) { (2)
cmd.printVersionHelp(System.out);
} else {
myApp.run();
}
}
1 | ユーザが usageHelp = true の オプションを指定した場合 |
2 | ユーザが versionHelp = true の オプションを指定した場合 |
@Command(name = "myapp", version = "myapp 3.0",
mixinStandardHelpOptions = true) (1)
class MyApp {
//@Option(names = "--help", usageHelp = true)... (2)
//@Option(names = "--version", versionHelp = true)... (3)
1 | 標準なヘルプオプションを混ぜ入れる |
2 | 不要 |
3 | 不要 |
@Command(name = "myapp", version = "myapp 4.0",
mixinStandardHelpOptions = true)
class MyApp implements Runnable { (1)
public void run() {
// ビジネスロジック (2)
}
public static void main(String... args) {
new CommandLine(new MyApp()).execute(args); (3)
}
1 | Runnable または Callable を実装 |
2 | run または call メソッドでビジネスロジックを書く |
3 | 一行で実行 |
@Command(name = "myapp", mixinStandardHelpOptions = true,
version = { (1)
"@|bold,underline Versioned Command 4.0|@", (2)
"Picocli " + picocli.CommandLine.VERSION,
"JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})",
"OS: ${os.name} ${os.version} ${os.arch}" (3)
})
class MyApp {
@Option(description = "ディレクトリー ${user.home}", (3)
// ...
1 | 複数行にわたる |
2 | ANSI カラーは使用可能 |
3 | システムプロパティ、環境変数、リソースバンドルのキーは変数として使用可能 |
System.out
と System.err
の使い方
終了コード
execute
メソッドpublic static void main(String... args) {
int exitCode = new CommandLine(new MyApp()).execute(args);
System.exit(exitCode);
}
--help
ユーザ依頼なら、ヘルプを System.out
に出力
不正なパラメータ入力の場合、エラー
メッセージとヘルプを System.err
に出力
終了コード:0=正常終了、1=ビジネスロジックに例外発生、2=不正なパラメータ
「合理的なデフォルト、
でもコンフィグレーション可能」
# デフォルト値の設定ファイルの例
# /home/remko/.checksum.properties
algorithm = SHA-256
import picocli.CommandLine.PropertiesDefaultProvider;
@Command(defaultValueProvider =
PropertiesDefaultProvider.class, //... (1)
class CheckSum {
@Option(names = {"-a", "--algorithm"}) (2)
String algorithm = "MD-5";
@Parameters(index = "0",
descriptionKey = "file", //... (2)
File file;
1 | picocli 4.1 からビルトイン・プロバイダ |
2 | キーはオプション名、または descriptionKey |
$ java CheckSum
Missing required parameter: <file>
Usage: checksum [-hV] [-a=<algorithm>] <file>
Prints the checksum (MD5 by default) of a file to STDOUT.
<file> The file whose checksum to calculate.
-a, --algorithm=<algorithm>
MD5, SHA-1, SHA-256, ...
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
execute
メソッドがpicocliの execute
メソッドは、
不正なパラメータ入力があった場合、
自動的にエラーメッセージとヘルプを表示する。
public static void main(String... args) {
System.exit(new CommandLine(new CheckSum()).execute(args));
}
オートコンプリート
BashやZSHの入力補完スクリプト生成
JLineシェル内のオートコンプリート
import picocli.AutoComplete;
import picocli.CommandLine;
//...
CommandLine cmd = new CommandLine(new CheckSum());
String script = picocli.AutoComplete.bash("checksum", cmd);
入力補完スクリプト生成
generate-completion
サブコマンドに
自動補完スクリプトを生成させよう
import picocli.AutoComplete.GenerateCompletion;
@Command(subcommands = GenerateCompletion.class, //...
public class CheckSum { //...
ユーザが一行で入力補完スクリプトを
インストールできる:
$ source <(checksum generate-completion)
すると、オートコンプリートが使える!
$ ./checksum <TAB><TAB>
格好よくしよう: ASCIIカラーとASCIIアート!
@Command(name = "日本語対応デモ", mixinStandardHelpOptions = true,
usageHelpWidth = 60,
description = {
"123456789012345678901234567890123456789012345678901234567890",
"123456789012345678901234567890",
"@|red 漢字|@、@|green ひらがな|@、@|blue カタカナ|@などは" +
"ローマ字よりも幅が長い。Picocliはそういう文字に" +
"@|bold,blink 2倍の幅|@を与えます。"})
public class JapaneseDemo implements Runnable {
@Override public void run() { }
public static void main(String[] args) {
new CommandLine(new JapaneseDemo()).execute(args);
}
}
レイアウトは指定された幅に従います。
そう言えば、GraalVMの話をつづきましょう
[
{
"name" : "CheckSum",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true,
"fields" : [
{ "name" : "algorithm" },
{ "name" : "file" }
]
},
{
"name" : "picocli.CommandLine$AutoHelpMixin",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true,
"fields" : [
{ "name" : "helpRequested" },
{ "name" : "versionRequested" }
]
}
]
$ mkdir classes
$ javac -cp .:picocli-4.1.0.jar:picocli-codegen-4.1.0.jar \
-d classes CheckSum.java
$ tree classes
classes
├── CheckSum.class
└── META-INF
└── native-image
└── picocli-generated
├── proxy-config.json
├── reflect-config.json
└── resource-config.json
$ /usr/lib/jvm/graalvm/bin/native-image \
-cp classes:picocli-4.1.0.jar --no-server \
--static -H:Name=checksum CheckSum
サイズは 11MB
$ ll -h checksum
-rwxrwxrwx 1 remko remko 11M Sep 4 01:28 checksum*
$ time java -cp classes:picocli-4.1.0.jar CheckSum hi.txt
real 0m0.415s ← 通常のJavaなら 415ミリ秒で起動
user 0m0.609s
sys 0m0.313s
$ time ./checksum hi.txt
real 0m0.004s ← ネイティブイメージは 4ミリ秒で起動
user 0m0.002s
sys 0m0.002s
C:\apps\graalvm-ce-19.2.1\bin\native-image ^
-cp picocli-4.1.0.jar --static -jar checksum.jar
しかし、別のマシーンで実行すると、
エラーが出る場合がある。
msvcr100.dll
をアプリと一緒に配布が必要
dependencies {
compile 'info.picocli:picocli:4.1.0'
compile 'info.picocli:picocli-jansi-graalvm:1.1.0'
compile 'org.fusesource.jansi:jansi:1.18'
annotationProcessor 'info.picocli:picocli-codegen:4.1.0'
}
import picocli.jansi.graalvm.AnsiConsole;
class CheckSum {
public static void main(String... args) {
int exitCode;
try (AnsiConsole ansi = AnsiConsole.windowsInstall()) {
exitCode=new CommandLine(new CheckSum()).execute(args);
}
System.exit(exitCode);
}
// ...
ちゃんと動きます!
Gradle
apply plugin: 'com.palantir.graal'
graal {
mainClass 'picocli.nativeimage.demo.CheckSum'
outputName 'checksum'
}
Maven
<plugin>
<groupId>com.oracle.substratevm</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<configuration>
<mainClass>picocli.nativeimage.demo.CheckSum</mainClass>
<imageName>checksum</imageName>
</configuration>
コマンドラインアプリをJavaで作ろう!
単一の実行可能ファイルとして配布
PicocliでときめくCLIが簡単にできる
picocli を気に入ったら、GitHubで星をつけて、
友達に連携してください! ;-)
よろしくお願いします。 @RemkoPopma