タイトル通りのgradleプラグインを作りました。
gradleのリポジトリには現在申請中です。
ソースコードはこちら
jnr-ffi Plugin
このプラグインはProject Panamaで開発中(Java13で実装予定)の機能を使いやすくするものです。
特に、JEP 191: Foreign Function Interfaceの利便化を目的としています。
JNR関連記事はこちらのQiitaにまとめたりしているのでよければどうぞ
動作環境
システムのJDK・プロジェクトのJDKともにバージョン13以上?
動作保証のJDKは Project Panama Early-Access Builds
だけです。
JDKのバージョンが12以下の場合警告文、jextractがシステムのJDKに存在しない場合にはエラー文を出します。
jextract
jextractとは?
jextractとは、C言語などの.hファイルから対応するJavaのインターフェースを生成するツールです。
early-access版のJDKに付属しており、ServiceLoaderを利用することでプログラムからも呼び出すことができます。
jextractタスク
このプラグインではjextractというタスクが定義されています。
jextractタスクでは、指定されたフォルダ内にある.hファイルからjextractでjarファイル(対応するインターフェース)を生成するものです。
(入れ子になっているファイルも検索し、ディレクトリをパッケージと対応させてjarを生成します)
jextractタスクのオプションは、build.gradleで以下のように指定できます。
jextract{
// .hファイルを含むディレクトリのパス
// 初期値は"src/main/resources/"
sourceRoot = "head/"
// 生成したjarを置くディレクトリのパス
// 初期値は"libs/"
outPath = "jar"
// 生成するインターフェースが属するパッケージのルート名
// <packageRoot>.<ディレクトリ名>.<ディレクトリ名>という風に命名される
// 初期値は""
packageRoot = "pkg"
// パッケージ名にsourceRootディレクトリを含むかどうか
// falseにすると、SourceRoot直下の.hファイルのパッケージはpackageRootとなる
// 初期値はfalse
includeRoot = false
}
制作過程
jextractを呼び出す
ServiceLoaderを利用してjextractを呼び出します。
ちなみに、com.sun.tools.jextract.Main$JextractToolProviderを直接呼び出そうとするとコンパイルエラーとなります。
(resources/Message.propertiesのせい?)
では、Javaのコードを書いていきます。
public class Test{
public static void main(String[] args){
// ServiceLoaderでToolProviderの実装一覧を取得する。
// jshellやjextractなど、JDKのbinに入っているやつらが返ってくる。
// jextractのツール名は"jextract"なので一致するものを利用する。
ServiceLoader<ToolProvider> providers = load(ToolProvider.class);
ToolProvider provider = null;
for (ToolProvider tool : providers) {
if (tool.name().equals("jextract")) provider = tool;
}
if(provider == null) System.out.println("エラー: jextractが存在しません。");
// 最後の引数の配列にjextractを呼び出すときのコマンドライン引数を渡す。
// 空白で区切る代わりに要素を分ける。
provider.run(System.out, System.err, String[]{"src/main/resources/test.h"});
}
}
int hoge(void);
src/main/resources/にtest.hを作成してTest.mainを実行すると、プロジェクトの直下にtest.h.jarというファイルが作成されます。
また、今回利用したオプションを抜粋します。
オプション | 引数 | 説明 |
---|---|---|
-t | パッケージ名 | パッケージに生成したインターフェースが属するようになる |
-o | ファイル名(パスも含む) | ファイル名(パス)に生成したjarファイルが置かれる |
ファイル名(パスも含む) | 変換したい.hファイルのファイル名(パス) 複数選択可能だが、必ず同一パッケージとして生成される |
例えば、
jextract -t pkg -o out.jar test.h
というコマンドを実行した時、または
public class Test{
public static void main(String[] args){
ServiceLoader<ToolProvider> providers = load(ToolProvider.class);
ToolProvider provider = null;
for (ToolProvider tool : providers) {
if (tool.name().equals("jextract")) provider = tool;
}
provider.run(System.out, System.err, String[]{"-o", "out.jar", "-t", "pkg", "test.h"});
}
}
というプログラムを実行した時、
int hoge(void);
というファイルがあれば、out.jarというファイルが生成されます。
out.jar
|-META-INF
| \jextract.properties
\pkg
\test.class
out.jarの内部構造は以下のようになっており、pkgというパッケージディレクトリが生成されていることが分かります。
jadを使ってTest.classを逆コンパイルすると、以下のようになっていることが分かります。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
package pkg;
public interface test
{
public abstract int hoge();
}
しっかり.hファイルがinterfaceにトランスパイルされていますね。
あと、プラグインにしたときにServiceLoaderがjextractを見つけてくれなかったのでシステムのJDKを13にした後、呼び出しを少し変えました。
public class Test{
public static void main(String[] args){
// システムのクラスローダーを使う
ServiceLoader<ToolProvider> providers = load(ToolProvider.class, ClassLoader.getSystemClassLoader());
ToolProvider provider = null;
for (ToolProvider tool : providers) {
if (tool.name().equals("jextract")) provider = tool;
}
provider.run(System.out, System.err, String[]{"-o", "out.jar", "-t", "pkg", "test.h"});
}
}
プラグインを作る
こちらの記事を参考にしました。
気を付けたこと
- テストプロジェクトでjarをプラグインに追加するとき、依存関係のあるライブラリもクラスパスに追加した。
- テストプロジェクトとシステムのJDKも13にした。
- gradleにプラグインを上げるときにgroupを指定しないと失敗した。