先の記事でJankがMSYS2で動いたという報告をしたが、実際にjankで遊び始めた。
まずはGetting Startedをこなしてみている。
https://book.jank-lang.org/getting-started/02-hello-world.html
leiningen 等は特に問題なく動作していて、それだけでもすごいなと思って、cpp/raw が動いているのがすごく感動。JITでC++が動くというのは CL-CXX-JIT 以来なので、とても楽しい。型とか普通に解決して利用できているのがなんとも不思議でならない。
【追記 3/21】以下は古い情報となりました
https://github.com/ikappaki/jank-win
最新のjank-winでは、インストール方法もprebuiltを自動アップデートする方法に刷新され、以下の問題も全く起こらなくなりました。したがって、以下はあくまで2026/02/05時点ではこうだったよという古い情報です。
leinからdllを呼び出すとき、エラーが発生する。
で、少し詰まっているのが https://book.jank-lang.org/cpp-interop/native-libs.html でleinからdllを呼び出すところ。MSYS2では拡張子を .so ではなく .dll を使う点にも注意が必要だけれど、jank コマンドから例えば native-lib にある libcompress.dll を呼び出すには jank -lcompress -I ./native-lib -L ./native-lib run src/native_lib_tutorial/main.jank などとすればOKなのだけれど、Getting Startedによると
:jank {:include-dirs ["native-lib"]
:library-dirs ["native-lib"]
:linked-libraries ["compress"]}を project.clj にかけばいいよ、と書いてあるものの、最新のjankとミスマッチしているのか、惜しい感じで今ひとつ動かない。
サンプル自体も、C++ JITのコードを何も前置き無しに呼び出しているので当然コンパイルエラーが出ていて、最新のjankでは cpp/ を関数名の前につけるようになっているなど、ドキュメントが追いついてない点も少しあったりする。
たぶん、lein new org.jank-lang/jank native-lib-tutorial で呼び出されている org.jank-lang/jank という名前のテンプレートを、独自にフォークした最新に追従するものに更新する必要があるんだろうな。
【追記】原因判明、run-mainの呼び出し方がおかしいっぽい。
エラーの原因は、run-main および compile の引数の順番だった。元は、jank run-main xxxx yyyy .... と呼ばれているのだけれど、最新のjankなのか、MSYS2版のjankなのかはわからないが、run-main xxxx はすべてのOption引数の後(かつ -- の前)になければ正しく動作しない。
これはテンプレートを変更することでも対応可能だが、以下のようなシェルスクリプトを書くことでも対応できる。
#!/bin/bash
# echo "orig cmd: jank $@"
jank="$HOME/dev/jank-win/compiler+runtime/build/jank"
# 引数を -- で分割
pre_ddash=()
post_ddash=()
found_ddash=0
for arg in "$@"; do
if [[ $found_ddash -eq 0 && "$arg" == "--" ]]; then
found_ddash=1
continue
fi
if [[ $found_ddash -eq 0 ]]; then
pre_ddash+=("$arg")
else
post_ddash+=("$arg")
fi
done
# run-main, compile を探して削除
special_cmds=("run-main" "compile")
special_idx=-1
special_cmd=""
for idx in "${!pre_ddash[@]}"; do
for cmd in "${special_cmds[@]}"; do
if [[ "${pre_ddash[$idx]}" == "$cmd" ]]; then
special_idx=$idx
special_cmd="${pre_ddash[$idx]}"
break 2
fi
done
done
if [[ $special_idx -ge 0 ]]; then
# 配列から削除
tmp=()
for idx in "${!pre_ddash[@]}"; do
if [[ $idx -ne $special_idx ]]; then
tmp+=("${pre_ddash[$idx]}")
fi
done
pre_ddash=("${tmp[@]}")
# 最後のポジション引数(ハイフンなし)の直前を探す
last_pos_idx=-1
for idx in "${!pre_ddash[@]}"; do
if [[ ! "${pre_ddash[$idx]}" == -* ]]; then
last_pos_idx=$idx
fi
done
# 直前にspecial_cmdを挿入
if [[ $last_pos_idx -ge 0 ]]; then
tmp=()
for idx in "${!pre_ddash[@]}"; do
if [[ $idx -eq $last_pos_idx ]]; then
tmp+=("$special_cmd")
fi
tmp+=("${pre_ddash[$idx]}")
done
pre_ddash=("${tmp[@]}")
else
pre_ddash=("$special_cmd" "${pre_ddash[@]}")
fi
fi
#echo "cmd: $jank ${pre_ddash[@]} -- ${post_ddash[@]}" # デバッグ用(普段はコメントアウト)
"$jank" "${pre_ddash[@]}" -- "${post_ddash[@]}"これを jank としてパスに登録しつつ、元のjankをパスから外せばシェル的にはOKなのだけれど、leinテンプレート中で使われているbabashkaのwhichが、シェルスクリプトをうまく判定してくれないので、苦肉の策で、以下のような main.c を作り、gcc main.c -o jank.exe として、さっきのシェルスクリプトと同じパスに置けば、解決する。
#include <windows.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
// MSYS2 bash.exeのパス
const char *bash_path = "C:\\msys64\\usr\\bin\\bash.exe";
// jankシェルスクリプトのパス(スラッシュ区切り)
const char *jank_sh = "C:/msys64/home/funatsu/bin/jank";
// コマンドライン構築
char cmd[4096] = {0};
snprintf(cmd, sizeof(cmd), "\"%s\" \"%s\"", bash_path, jank_sh);
for (int i = 1; i < argc; ++i) {
strcat(cmd, " \"");
strcat(cmd, argv[i]);
strcat(cmd, "\"");
}
// 実行
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
if (CreateProcessA(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
} else {
fprintf(stderr, "Failed to launch: %s\n", cmd);
return 1;
}
}あくまで元のleinテンプレートをいじらずに動かす苦肉の策なので、本当はテンプレート (jank/lein-jank) の下記の該当部から、run-main だけ (?) を書き換えたフォークを作って使うのが一番良いのかもしれない。
https://github.com/jank-lang/jank/blob/main/lein-jank/src/leiningen/jank.clj
どこかで暇を見てフォーク作ってClojarsに登録して