





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-256import 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 CheckSumMissing 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.002sC:\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