rfではLinux/Mac/Windows向けのバイナリを提供している。 LinuxとMac向けのバイナリについてはZigを使ってクロスビルドしている。 では、Windows向けはどうしているかというとgccを使ってクロスビルドしている。 何故かというと、rfのWindows対応を開発していた当初もZigを使ったクロスビルドを試していていたのだが、うまくいかなかった。 そのままWindows対応を見送るのはなんとなく嫌だったので、gccを使った方法に舵を切ったという経緯がある。
最近rfのビルド環境を見直していて、ついでにWindows向けのバイナリもZigを使う方法がないか再度調査していた。 結果としてはビルドはできたが意図した通り動かず、そしてそれがどうすればいいかわからなかったので失敗に終わった…。 失敗はしたがまたチャレンジした時の参考にするために備忘として残しておく。
なぜZigでビルドできないか
mrubyのdefaut.gemboxに含まれるmruby-socket*1がgai_strerrorA/gai_strerrorW*2が定義されていないためにエラーになる。 この関数はMSDNのドキュメントにも載っているのでWindows上には存在しているのだが、ws2_32.dllのリンクを追加しても関数名が解決できなくてエラーになる。 種明かしをすると、この関数はインラインで定義されていてws2_32.dllには定義されていないのであった。 そのため、これらの関数を自分で定義することでこのエラーを回避することができる。
gai_strerrorを定義してビルドする
今回は以下のようなファイルを作成した。
/* crossbuild/gai_strerror.c */ #include <winsock2.h> #include <ws2tcpip.h> #ifndef UNICODE char* gai_strerrorA(int errcode) { static char buf[GAI_STRERROR_BUFFER_SIZE + 1]; FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, GAI_STRERROR_BUFFER_SIZE + 1, NULL); return buf; } #else WCHAR* gai_strerrorW(int errcode) { static WCHAR buf[GAI_STRERROR_BUFFER_SIZE + 1]; FormatMessageW( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, GAI_STRERROR_BUFFER_SIZE + 1, NULL); return buf; } #endif
次にbuild_config.rbを以下のように定義した。
def gem_config(conf) conf.gembox 'default' end def build_config(conf, target = nil) [conf.cc, conf.linker].each do |cc| cc.command = "zig cc" cc.flags += ['-target', target] if target end conf.archiver.command = 'zig ar' conf.host_target = target if target end MRuby::Build.new do |conf| build_config(conf) gem_config(conf) end MRuby::CrossBuild.new('windows-amd64') do |conf| build_config(conf, 'x86_64-windows') conf.exts do |exts| exts.object = '.obj' exts.executable = '.exe' end build_root = File.join(build_dir, 'crossbuild') FileUtils.mkdir_p(build_root) `#{conf.cc.command} #{conf.cc.flags.join(' ')} -o #{build_root}/gai_strerror.o -c #{__dir__}/crossbuild/gai_strerror.c` conf.linker.flags << "#{build_root}/gai_strerror.o" conf.host_target = "x86_64-w64-mingw32" # required for `for_windows?` used by `mruby-socket` gem gem_config(conf) end
build_config.rbでは任意のCファイルをビルド出来ないので無理やりコマンドを実行してビルドしている。 最後に以下のコマンドでビルドする。
$ MRUBY_CONFIG=$(pwd)/build_config.rb MRUBY_BUILD_DIR=$(pwd)/build rake -f /path/to/mruby/Rakefile -- snip -- ================================================ Config Name: windows-amd64 Output Directory: build/windows-amd64 Binaries: mrbc Included Gems: -- snip --
ちなみに、ここまでしなくともmruby-socketを使わないのであれば、インストールするGemから外してあげれば普通にビルドはできる。
実行してみる
普通に実行できる。
> .\mruby.exe --version mruby 3.2.0 (2023-02-24) > .\mruby.exe -e 'puts MRUBY_VERSION' 3.2.0
だが、標準エラーに出力しようとするなぜか出力されない…
> .\mruby.exe -e 'warn MRUBY_VERSION' >
しかもMicrosoft DefenderによってTorojan判定されてしまう。。なぜぇ。。。
おわりに
Zigを使って再度Windows向けにクロスビルドしようとしたが失敗に終わった。。 「zig cc Windows corss build」とかで検索してもあまり情報がでてこないので難しい。 また、zigの内部にはllvmが入っているので「llvm mingw crossbuild」とかで検索をかけてもあまり情報がでてこないので手詰まりになってしまった。
一旦はWindows向けはgccでのビルドに戻して、何かのタイミングでZigのことを思い出したら再度挑戦したいと思う。