0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

jextractをGradleのタスクで実行するプラグイン作った

Posted at

タイトル通りの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で以下のように指定できます。

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のコードを書いていきます。

Test.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"});
    }
}
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

というコマンドを実行した時、または

Test.java
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"});
    }
}

というプログラムを実行した時、

test.h
int hoge(void);

というファイルがあれば、out.jarというファイルが生成されます。

out.jar
|-META-INF
| \jextract.properties
\pkg
  \test.class

out.jarの内部構造は以下のようになっており、pkgというパッケージディレクトリが生成されていることが分かります。

jadを使ってTest.classを逆コンパイルすると、以下のようになっていることが分かります。

Test.jad
// 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を指定しないと失敗した。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?