CLOVER🍀

That was when it all began.

Javaソースコードでライブラリ依存関係を解決しつつスクリプトとして実行する、JBangを試す

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

JBangという、Javaプログラムをスクリプト的に実行できるツールがあると知りまして。

ちょっと気になる分野なので、試してみることにしました。

JBang

JBangは、Javaプログラムをスクリプトのように実行できるツールです。

GitHub - jbangdev/jbang: Unleash the power of Java for shell scripting

要件としては、Java(最低8、推奨11)がインストールされていることのようです。

Requirements

機能としては、

  • .java拡張子や.jsh拡張子のファイルを実行できる
  • マルチプラットフォーム
  • いくつかのパッケージマネージャーでインストール可能
  • ライブラリの依存関係を解決可能
  • (実験的)ネイティブイメージのサポート
  • 利用するJavaのバージョンを指定することができ、そのバージョンがOSにインストールされていない場合は自動的にダウンロードする

などなど。もうちょっと書かれているのですが、詳しくは以下を参照してください。

Features

ポイントは、Mavenなどを使う動機のひとつである、依存関係の解決ができることがこちらに注目した理由ですね。

なお、このツールはこんな感じで、作者の勉強目的で作られているようです。

And to be honest I built jbang just to see if I could and get my Java skills refreshed for the newer features in the language. Use it at your own risk :)

FAQ

まあ、ちょっと試してみましょう。

環境

今回の環境は、こちら。

$ java --version
openjdk 11.0.8 2020-07-14
OpenJDK Runtime Environment (build 11.0.8+10-post-Ubuntu-0ubuntu120.04)
OpenJDK 64-Bit Server VM (build 11.0.8+10-post-Ubuntu-0ubuntu120.04, mixed mode, sharing)


$ sdk version

SDKMAN 5.9.0+555

インストール

SDKMANを使ったインストールが可能なようなので、今回はこちらを使用します。

Installation / SDKMan

$ sdk install jbang

その他のインストール方法もあるようですが、使っている環境やツールが条件を満たさない場合は、バイナリをダウンロードして
インストールしましょう。

Releases · jbangdev/jbang · GitHub

今回のバージョンは、こちら。

$ jbang version
0.47.1

使う前に、BashのAuto Completionを有効にしておきましょう。

Bash/Zsh auto-completion

$ source <(jbang completion)

ヘルプ。

$ jbang -h
jbang is a tool for building and running .java/.jsh scripts and jar packages.
Usage: jbang [-h] [--verbose] [COMMAND]

  jbang init hello.java [args...]
        (to initialize a script)
  or  jbang edit --live=code hello.java
        (to edit a script in IDE with live updates)
  or  jbang hello.java [args...]
        (to run a .java file)
  or  jbang gavsearch@jbangdev [args...]
        (to run a alias from a catalog)
  or  jbang group-id:artifact-id:version [args...]
        (to run a .jar file found with a GAV id)

  -h, --help      Display help/info
      --verbose   jbang will be verbose on what it does.

Essentials:
  run         Builds and runs provided script.
  build       Compiles and stores script in the cache.

Editing:
  init        Initialize a script.
  edit        Setup a temporary project to edit script in an IDE.

Caching:
  cache       Manage compiled scripts in the local cache.
  jdk         Manage Java Development Kits installed by jbang.

Configuration:
  trust       Manage which domains you trust to run scripts from.
  alias       Manage aliases for scripts.
  catalog     Manage Catalogs of aliases.

Other:
  completion  Output auto-completion script for bash/zsh.
              Usage: source <(jbang completion)
  version     Display version info.
  wrapper     Manage jbang wrapper for a folder.

Copyright: 2020 Max Rydahl Andersen and jbang.dev contributors, License: MIT
Website: https://jbang.dev

Javaプログラムを動かしてみる

まずは、軽く使ってみます。

Usage

最初の1文を見ると、スクリプトは1ファイルである必要がありそうですね。

A script is just a single .java file with a classic static main method or a .jsh file which will be passed to jshell.

複数のソースコードを扱うのは、実験的機能になっています。

Multiple source files (Experimental)

こんなJavaソースコードを用意。
HelloWorld.java

public class HelloWorld {
    public static void main(String... args) {
        String word;

        if (args.length == 0) {
            word = "World";
        } else {
            word = args[0];
        }

        System.out.printf("Hello %s!!%n", word);
    }
}

このファイル、今ならjavaコマンドで実行できますけどね。

$ java HelloWorld.java
Hello World!!


$ java HelloWorld.java Scripting
Hello Scripting!!

JBangで実行してみます。

$ jbang HelloWorld.java 
[jbang] Building jar...
Hello World!!

なんか、JARファイルを作ってそうなログが出ています…。

2回目からは、出なくなりました。

$ jbang HelloWorld.java 
Hello World!!

引数もふつうに使えます。

$ jbang HelloWorld.java Scripting
Hello Scripting!!

さて、いきなりビルドしている雰囲気があったのでどうなっているのか?ですが、Cachingのところを見るとよいようです。

Caching

デフォルトでは、$HOME/.jbang/cacheにいろいろできるそうな。ちょっと見てみましょう。

$ find ~/.jbang/cache -type f
$HOME/.jbang/cache/jars/HelloWorld.java.a86770d39e60af63573a609bb669a610f5fa51605fb23be6383278d17f81423b/META-INF/maven/g/a/v/pom.xml
$HOME/.jbang/cache/jars/HelloWorld.java.a86770d39e60af63573a609bb669a610f5fa51605fb23be6383278d17f81423b/HelloWorld.class
$HOME/.jbang/cache/jars/HelloWorld.java.a86770d39e60af63573a609bb669a610f5fa51605fb23be6383278d17f81423b.jar

classファイルとか、JARファイルとかありますね…。

ここで、少しファイルを更新してみます。!!を増やしてみました。
HelloWorld.java

public class HelloWorld {
    public static void main(String... args) {
        String word;

        if (args.length == 0) {
            word = "World";
        } else {
            word = args[0];
        }

        System.out.printf("Hello %s!!!!%n", word);
    }
}

すると、再度ビルドされます。

$ jbang HelloWorld.java 
[jbang] Building jar...
Hello World!!!!

JARファイル等自体が増えました。

$ find ~/.jbang/cache -type f
$HOME/.jbang/cache/jars/HelloWorld.java.a86770d39e60af63573a609bb669a610f5fa51605fb23be6383278d17f81423b/META-INF/maven/g/a/v/pom.xml
$HOME/.jbang/cache/jars/HelloWorld.java.a86770d39e60af63573a609bb669a610f5fa51605fb23be6383278d17f81423b/HelloWorld.class
$HOME/.jbang/cache/jars/HelloWorld.java.a86770d39e60af63573a609bb669a610f5fa51605fb23be6383278d17f81423b.jar
$HOME/.jbang/cache/jars/HelloWorld.java.319a2e49920a81a167dcf3c65b688c995dc8b2cb1cdfb52756e73335b035ead2.jar
$HOME/.jbang/cache/jars/HelloWorld.java.319a2e49920a81a167dcf3c65b688c995dc8b2cb1cdfb52756e73335b035ead2/META-INF/maven/g/a/v/pom.xml
$HOME/.jbang/cache/jars/HelloWorld.java.319a2e49920a81a167dcf3c65b688c995dc8b2cb1cdfb52756e73335b035ead2/HelloWorld.class

なんかハッシュ値っぽいものが見えますが、これはファイルのSHA256ですね。

$ sha256sum HelloWorld.java 
a86770d39e60af63573a609bb669a610f5fa51605fb23be6383278d17f81423b  HelloWorld.java

これらのファイルは、jbang cache clearでキャッシュクリアすることができます。

$ jbang cache clear
[jbang] Clearing cache for urls
[jbang] Clearing cache for jars
[jbang] Clearing cache for scripts
[jbang] Clearing cache for stdins

サブコマンドだけを指定すると、説明が見えるようです。jbang cache

$ jbang cache
Missing required subcommand
Usage: jbang cache [--verbose] [COMMAND]
Manage compiled scripts in the local cache.
      --verbose   jbang will be verbose on what it does.
Commands:
  clear  Clear cache of dependency list and temporary projects. By default this
           will clear the JAR, script, stdin and URL caches

キャッシュのディレクトリなどは、環境変数で設定できそうですね。

https://github.com/jbangdev/jbang/blob/v0.47.1/src/main/java/dev/jbang/Settings.java

なんか、脱線してきましたが、雰囲気が少しわかった気がします。

JShellで動かす

JBangは、スクリプトをJShellで動かす機能があります。.jsh拡張子のファイルを与えると、JShellで実行しようとします。

Using .jsh for jshell

試してみましょう。
HelloWorld.jsh

String word;

if (args.length == 0) {
    word = "World";
} else {
    word = args[0];
}

System.out.printf("Hello %s!!%n", word);

意味的には、先ほどのJavaソースコードと同等のものです。

確認。

$ jbang HelloWorld.jsh JBang
Hello JBang!!


$ jbang HelloWorld.jsh Scripting
Hello Scripting!!

実行できました。

--interactiveオプションを使うことで、インタラクティブに実行することもできるようですが、こちらの形態は
これくらいにしておきます。

実行可能ファイルとして扱う

スクリプトに対してShebangスタイル…の代わりに//を使い、実行権限を与えることで、スクリプトを実行可能ファイルとして
動作させることができます。

Usage

こんな感じですね。
HelloWorld.java

//usr/bin/env jbang "$0" "$@" ; exit $? 

public class HelloWorld {
    public static void main(String... args) {
        String word;

        if (args.length == 0) {
            word = "World";
        } else {
            word = args[0];
        }

        System.out.printf("Hello %s!!%n", word);
    }
}

実行権限を与えて。

$ chmod a+x HelloWorld.java

実行。

$ ./HelloWorld.java 
[jbang] Building jar...
Hello World!!

こんな感じで、実行できますよ、と。これも、ここまでにしておきます。

依存関係を解決する

では、スクリプト内で依存関係を使用してみましょう。

Declare dependencies

ソースコード内に、//DEPSと記述することで依存関係を解決してくれます。

Using //DEPS

//DEPSで、間にスペースなどは入れてはいけません。

https://github.com/jbangdev/jbang/blob/v0.47.1/src/main/java/dev/jbang/Script.java#L30

こんな感じで、Commons Lang3を使用するソースコードを用意。
HelloWorldDeps.java

//DEPS org.apache.commons:commons-lang3:3.11

import org.apache.commons.lang3.StringUtils;

public class HelloWorldDeps {
    public static void main(String... args) {
        String word;

        if (args.length == 0) {
            word = "World";
        } else {
            word = args[0];
        }

        System.out.println(StringUtils.join("Hello", " ", word, "!!"));
    }
}

実行。

$ jbang HelloWorldDeps.java 
[jbang] [WARN] Detected missing dependencies in cache.
[jbang] Resolving dependencies...
[jbang]     Resolving org.apache.commons:commons-lang3:3.11...Done
[jbang] Dependencies resolved
Hello World!!


$ jbang HelloWorldDeps.java Scripting
Hello Scripting!!

初回は、ビルドおよびライブラリのダウンロードが行われます。

キャッシュディレクトリの中身は、こんな感じになりました。

$ find ~/.jbang/cache -type f
$HOME/.jbang/cache/jars/HelloWorldDeps.java.5f6cde3812d30c4eaecfe79d12e46f78dcd5ee9bf94768abeaea5aaaa171f5e5.jar
$HOME/.jbang/cache/jars/HelloWorldDeps.java.5f6cde3812d30c4eaecfe79d12e46f78dcd5ee9bf94768abeaea5aaaa171f5e5/HelloWorldDeps.class
$HOME/.jbang/cache/jars/HelloWorldDeps.java.5f6cde3812d30c4eaecfe79d12e46f78dcd5ee9bf94768abeaea5aaaa171f5e5/META-INF/maven/g/a/v/pom.xml

JBangは、Mavenリポジトリとしてはデフォルトでjcenterを使用するようです。

また、//REPOSを使うことで他のリポジトリも利用することができるようです。

Repositories

jcenterではなくMaven Centralがいいぞ、と思ったらこんな感じですかね。

//REPOS central=https://repo1.maven.org/maven2/
//DEPS org.apache.commons:commons-lang3:3.11

その他、Gitリポジトリを参照できるとか@Grab形式(Grape)で書けるとかあるようですが、いいかなぁと。

Using links to Git sources

Using @Grab

依存関係解決の方法は?

ちょっと気になるのが、JBangの依存関係の解決方法です。

答えとしては、ShrinkWrapです。

https://github.com/jbangdev/jbang/blob/v0.47.1/src/main/java/dev/jbang/DependencyUtil.java

https://github.com/shrinkwrap/resolver/tree/3.1.4/maven

なので、依存するライブラリのダウンロード先はMavenのローカルリポジトリになりますし、settings.xmlも参照されます。

プロキシを設定するとしたら、settings.xmlになるんでしょうね。

JBangは、どのJavaで実行しているんだ?

これも、気になるところですよね。

--verboseオプションを付けて実行すると、確認することができます。

$ jbang --verbose HelloWorld.java 
[jbang] System Java version detected as 11
[jbang] System Java version matches requested version 11
[jbang] Building jar...
[jbang] compile: /usr/lib/jvm/default/bin/javac -d $HOME/.jbang/cache/jars/HelloWorld.java.c1a78dc36160dee8e968a9778b5fc17f8d74f633116e237206383be03aeeb42b HelloWorld.java
[jbang] System Java version matches requested version 11
[jbang] run: /usr/lib/jvm/default/bin/java -classpath $HOME/.jbang/cache/jars/HelloWorld.java.c1a78dc36160dee8e968a9778b5fc17f8d74f633116e237206383be03aeeb42b.jar: HelloWorld
Hello World!!

というわけで

いろいろ確認できた感じですね。

ちょっとしたところで使えたらいいかなーと。