こんにちは、先日、保育園から次男が高熱の為、呼び出しがありましたが、結局のところ、いつものように、お腹にウンチを溜め込んでいたことが原因だったようです。@kjunichiです。
背景
mruby-cliをつかうと、Linux、Windows、OSXといった複数のプラットフォーム向けのワンバイナリが
docker-compose run compile
一発で作成される。
しかし、任意のmrbgemsが動くかというと、最近書かれた
に書かれているように、うまく行かない。
残念ながら、この記事では、前述の記事の問題を直接は解決できないが、 もし、mrbgemが依存している各ライブラリがクロスコンパイルに対応している場合、少し mruby-cliに手を加えることで、利用可能となる。
この記事では、OpenCVという比較的大きなマルチプラットフォーム展開されているライブラリに 依存してmrbgemであるmruby-webcam例にやってみた。
mruby-cliで外部ライブラ依存したワンバイナリの作成に必要な事
大きくは以下の2つの対応をすれば、mruby-cliベースで外部ライブラリを使ったmrbgemでも 対応できる。
- 外部ライブラリのヘッダと.aを適当な場所に配置したDockerイメージ
- mrbgem側のクロスコンパイル対応
詳細
mruby-cli側のDockerイメージの対応
ヘッダファイルの配置
Windows、Linux、OSX向けのヘッダファイル、.aファイルを以下の場所に配置する。
- /usr/i686-w64-mingw32/include
- /usr/x86_64-w64-mingw32/include
- /usr/i686-pc-linux-gnu/include
- /usr/x86_64-pc-linux-gnu/include
- /usr/i386-apple-darwin14/include
- /usr/x86_64-apple-darwin14/include
ライブラリの配置
- /usr/i686-w64-mingw32/lib
- /usr/x86_64-w64-mingw32/lib
- /usr/i686-pc-linux-gnu/lib
- /usr/x86_64-pc-linux-gnu/lib
- /usr/i386-apple-darwin14/lib
- /usr/x86_64-apple-darwin14/lib
mrbgem側の対応
実は、mruby-cliでもmruby-ioはクロスコンパイル対応されている。
要は、これと同様のことを行えばよい。
クロスコンパイルされていた場合、前述の所定の位置に配置したヘッダファイルやライブラリを 参照するようにmrbgemのmrbgem.rakeファイルのコンパイラのオプションを設定する。
クロスコンパイルの判定
クロスコンパイルされているかを知るには
if build.kind_of?(MRuby::CrossBuild)
さらにどのOS向けにビルドしているのかは
%w(x86_64-w64-mingw32 i686-w64-mingw32).include?(build.host_target)
という具合に、build.host_targetを調べれば良い。
mruby-cliのmrbgems.rbの対応
x86_64-pc-linuxのビルドもmrubyのビルドシステム上でクロスコンパイル扱いにする。 これにより、mrbgem側で、クロスコンパイル時はlibhoge.aを直指定したリンクの指定を おこない、通常の非mruby-cliでのビルド時は共有ライブラリをリンクするという 設定の記述が可能になる。
MRuby::CrossBuild.new('x86_64-pc-linux-gnu') do |conf| toolchain :gcc conf.host_target = 'x86_64-pc-linux-gnu' gem_config_cross(conf) end
やってみた
Dockerfile
FROM hone/mruby-cli RUN apt-get update && apt-get install -y mingw-w64-tools cmake cmake-curses-gui apt-file wget COPY opencv_i686-w64-mingw32.cmake /root/src/ COPY opencv_x86_64-w64-mingw32.cmake /root/src/ COPY OpenCVCompilerOptions.cmake.patch /root/src/ RUN cd /root/src && git clone --depth 1 https://github.com/Itseez/opencv.git && cd opencv && mkdir build \ && patch -p1 < /root/src/OpenCVCompilerOptions.cmake.patch && cd build \ && cmake -DCMAKE_TOOLCHAIN_FILE=/root/src/opencv_i686-w64-mingw32.cmake -DWITH_IPP=OFF -DBUILD_SHARED_LIBS=OFF .. \ && make -j4 && make install && cp -r install/include/* /usr/i686-w64-mingw32/include/ \ && cp -r install/lib/* /usr/i686-w64-mingw32/lib/ && cp 3rdparty/lib/* /usr/i686-w64-mingw32/lib RUN cd /root/src/opencv && rm -rf build && mkdir build \ && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=/root/src/opencv_x86_64-w64-mingw32.cmake -DWITH_IPP=OFF -DBUILD_SHARED_LIBS=OFF .. \ && make -j4 && make install && cp -r install/include/* /usr/x86_64-w64-mingw32/include/ \ && cp -r install/lib/* /usr/x86_64-w64-mingw32/lib/ && cp 3rdparty/lib/* /usr/x86_64-w64-mingw32/lib \ && cd .. && rm -rf build COPY opencv_x86_64-apple-darwin14.cmake /root/src/ COPY Darwin.cmake /usr/share/cmake-2.8/Modules/Platform/ RUN cd /root/src/opencv && rm -rf build && mkdir build \ && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=/root/src/opencv_x86_64-apple-darwin14.cmake -DWITH_IPP=OFF -DBUILD_SHARED_LIBS=OFF .. \ && make -j4 && make install \ && cp 3rdparty/lib/* /usr/x86_64-apple-darwin14/lib \ && cd .. && rm -rf build COPY opencv_i386-apple-darwin14.cmake /root/src/ RUN cd /root/src/opencv && rm -rf build && mkdir build \ && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=/root/src/opencv_i386-apple-darwin14.cmake -DWITH_IPP=OFF -DBUILD_SHARED_LIBS=OFF .. \ && make -j4 && make install \ && cp 3rdparty/lib/* /usr/i386-apple-darwin14/lib \ && cd .. && rm -rf build RUN mkdir -p /usr/i686-pc-linux-gnu/lib && ln -s /usr/include/x86_64-linux-gnu/zconf.h /usr/include RUN dpkg --add-architecture i386 && apt-get update &&apt-get install libgtk2.0-dev:i386 -y --no-install-recommends RUN cd /root/src/opencv && rm -rf build && mkdir build \ && cd build && cmake -DCMAKE_INSTALL_PREFIX=/usr/i686-pc-linux-gnu -DCMAKE_C_FLAGS=-m32 -DCMAKE_CXX_FLAGS=-m32 -DWITH_IPP=OFF -DBUILD_SHARED_LIBS=OFF .. \ && make -j4 && make install \ && cp 3rdparty/lib/* /usr/i686-pc-linux-gnu/lib \ && cd .. && rm -rf build RUN apt-get install libgtk2.0-dev -y --no-install-recommends RUN cd /root/src/opencv && rm -rf build && mkdir build \ && cd build && cmake -DCMAKE_INSTALL_PREFIX=/usr/x86_64-pc-linux-gnu -DWITH_IPP=OFF -DBUILD_SHARED_LIBS=OFF .. \ && make -j4 && make install \ && cp 3rdparty/lib/* /usr/x86_64-pc-linux-gnu/lib \ && cd .. && rm -rf build RUN \ ln -snf /usr/lib/i386-linux-gnu/libgtk-x11-2.0.so.0 /usr/lib/i386-linux-gnu/libgtk-x11-2.0.so \ && ln -snf /usr/lib/i386-linux-gnu/libgdk-x11-2.0.so.0 /usr/lib/i386-linux-gnu/libgdk-x11-2.0.so \ && ln -snf /usr/lib/i386-linux-gnu/libatk-1.0.so.0 /usr/lib/i386-linux-gnu/libatk-1.0.so \ && ln -snf /usr/lib/i386-linux-gnu/libgio-2.0.so.0 /usr/lib/i386-linux-gnu/libgio-2.0.so \ && ln -snf /usr/lib/i386-linux-gnu/libpangocairo-1.0.so.0 /usr/lib/i386-linux-gnu/libpangocairo-1.0.so \ && ln -snf /usr/lib/i386-linux-gnu/libcairo.so.2 /usr/lib/i386-linux-gnu/libcairo.so \ && ln -snf /usr/lib/i386-linux-gnu/libpangoft2-1.0.so.0 /usr/lib/i386-linux-gnu/libpangoft2-1.0.so \ && ln -snf /usr/lib/i386-linux-gnu/libpango-1.0.so.0 /usr/lib/i386-linux-gnu/libpango-1.0.so \ && ln -snf /usr/lib/i386-linux-gnu/libgdk_pixbuf-2.0.so.0 /usr/lib/i386-linux-gnu/libgdk_pixbuf-2.0.so \ && ln -snf /usr/lib/i386-linux-gnu/libfontconfig.so.1 /usr/lib/i386-linux-gnu/libfontconfig.so \ && ln -snf /lib/i386-linux-gnu/libglib-2.0.so.0 /lib/i386-linux-gnu/libglib-2.0.so \ && ln -snf /usr/lib/i386-linux-gnu/libfreetype.so.6 /usr/lib/i386-linux-gnu/libfreetype.so \ && ln -snf /usr/lib/i386-linux-gnu/libfontconfig.so.1 /usr/lib/i386-linux-gnu/libfontconfig.so \ && ln -snf /usr/lib/i386-linux-gnu/libpng12.so.0 /usr/lib/i386-linux-gnu/libpng12.so \ && ln -snf /usr/lib/i386-linux-gnu/libpng12.so.0 /usr/lib/i386-linux-gnu/libpng.so \ && ln -snf /usr/lib/i386-linux-gnu/libgobject-2.0.so.0 /usr/lib/i386-linux-gnu/libgobject-2.0.so
アプリ本体
mruby-cliの流儀に従い記述すればOK。
def __main__(argv) if argv[1] == "version" puts "v#{App1::VERSION}" else puts "Hello World" cam = Webcam.new cam.capture {|img| # img : JPEG format puts img.length File.open("#{Time.now.to_s.gsub!(' ','').gsub!(':','')}.jpg","wb") { |f| f.write img } } # SPC : capture # ESC : exit cam.start end end
build_config.rb
クロスコンパイル時にのみ、mruby-webcamを指定している。 これは、前述の通り、OpenCVを.aの静的ライブラリとして用意して、 クロスコンパイル用にヘッダ、ライブラリを配置しているため、通常のビルドでは 参照できないので、以下のようにクロスコンパイルの時のみmrbgemを追加している。
def gem_config(conf) # conf.gembox 'default' # be sure to include this gem (the cli app) conf.gem File.expand_path(File.dirname(__FILE__)) end def gem_config_cross(conf) conf.gem github: 'kjunichi/mruby-webcam' gem_config(conf) end MRuby::Build.new do |conf| toolchain :clang conf.enable_bintest conf.enable_debug conf.enable_test gem_config(conf) end MRuby::CrossBuild.new('x86_64-pc-linux-gnu') do |conf| toolchain :gcc conf.host_target = 'x86_64-pc-linux-gnu' gem_config_cross(conf) end MRuby::CrossBuild.new('i686-pc-linux-gnu') do |conf| toolchain :gcc [conf.cc, conf.cxx, conf.linker].each do |cc| cc.flags << '-m32' end conf.host_target = 'i686-pc-linux-gnu' gem_config_cross(conf) end MRuby::CrossBuild.new('x86_64-apple-darwin14') do |conf| toolchain :clang [conf.cc, conf.linker].each do |cc| cc.command = 'x86_64-apple-darwin14-clang' end conf.cxx.command = 'x86_64-apple-darwin14-clang++' conf.archiver.command = 'x86_64-apple-darwin14-ar' conf.build_target = 'x86_64-pc-linux-gnu' conf.host_target = 'x86_64-apple-darwin14' gem_config_cross(conf) end MRuby::CrossBuild.new('i386-apple-darwin14') do |conf| toolchain :clang [conf.cc, conf.linker].each do |cc| cc.command = 'i386-apple-darwin14-clang' end conf.cxx.command = 'i386-apple-darwin14-clang++' conf.archiver.command = 'i386-apple-darwin14-ar' conf.build_target = 'i386-pc-linux-gnu' conf.host_target = 'i386-apple-darwin14' gem_config_cross(conf) end MRuby::CrossBuild.new('x86_64-w64-mingw32') do |conf| toolchain :gcc [conf.cc, conf.linker].each do |cc| cc.command = 'x86_64-w64-mingw32-gcc' end conf.cxx.command = 'x86_64-w64-mingw32-g++' conf.archiver.command = 'x86_64-w64-mingw32-gcc-ar' conf.exts.executable = '.exe' conf.build_target = 'x86_64-pc-linux-gnu' conf.host_target = 'x86_64-w64-mingw32' gem_config_cross(conf) end MRuby::CrossBuild.new('i686-w64-mingw32') do |conf| toolchain :gcc [conf.cc, conf.linker].each do |cc| cc.command = 'i686-w64-mingw32-gcc' end conf.cxx.command = 'i686-w64-mingw32-g++' conf.archiver.command = 'i686-w64-mingw32-gcc-ar' conf.exts.executable = '.exe' conf.build_target = 'i686-pc-linux-gnu' conf.host_target = 'i686-w64-mingw32' gem_config_cross(conf) end
やってみて発生したトラブル
cmakeがOSX向けには動かなかった。
mruby-cliが利用りているDockerイメージのUbutnuに入っているcmakeが原因
を見つけたので、解決できたが、まさかのクロスコンパイルでcmakeが動かない問題だった。
mrubyのビルドシステムの謎
まぁ、自分がよく、rake周りを分からな過ぎなところが多々あるのだが。
MRuby::CrossBuild.new('x86_64-pc-linux-gnu') do |conf|
と記述しても、mrbgem側のmrbgem.rakeでbuild.host_targetには値が設定されない。
mruby-cliのWindows向けのC++コンパイラの設定が誤っている
今回のmruby-webcamはC++のコードを使っていた為に分かった。
正しくは
conf.cxx.command = 'i686-w64-mingw32-g++'
conf.cxx.command = 'x86_64-w64-mingw32-g++'
linuxだけopencv向けのGUIライブラリが必要
opencv本体は、もちろん、mruby-webcamも問題なくビルド出来るも、 いざ、Linuxマシンで実行したらNG。
デバッグ文でGtk入れろとのことで、libgtk2.0-devを入れることに。
まとめ
mrbgemで依存しているライブラリを.aファイルの形で今回の様にクロスコンパイルするか、 各プラットフォームでビルドして、DockerイメージにCOPYして、ちょっと mrbgemのmrbgems.rakeにクロスビルドの設定を追加するだけでかmruby-cliに対応できる。
なお、今回の記事は
の完結編だったりします。
課題
- 全部のライブラ.aで用意するつらい。今回も適度に諦めている。