読者です 読者をやめる 読者になる 読者になる

non vorrei lavorare

ブログ名の通りです。javascript three.js mruby rust OCaml golang julialang blender

外部のライブラリに依存するmrbgemを使ってもmruby-cliでワンバイナリを作成できるようにした

こんにちは、先日、保育園から次男が高熱の為、呼び出しがありましたが、結局のところ、いつものように、お腹にウンチを溜め込んでいたことが原因だったようです。kjunichidです。

背景

mruby-cliをつかうと、LinuxWindowsOSXといった複数のプラットフォーム向けのワンバイナリが

docker-compose run compile

一発で作成される。

しかし、任意のmrbgemsが動くかというと、最近書かれた

に書かれているように、うまく行かない。

残念ながら、この記事では、前述の記事の問題を直接は解決できないが、 もし、mrbgemが依存している各ライブラリがクロスコンパイルに対応している場合、少し mruby-cliに手を加えることで、利用可能となる。

この記事では、OpenCVという比較的大きなマルチプラットフォーム展開されているライブラリに 依存してmrbgemであるmruby-webcam例にやってみた。

mruby-cliで外部ライブラ依存したワンバイナリの作成に必要な事

大きくは以下の2つの対応をすれば、mruby-cliベースで外部ライブラリを使ったmrbgemでも 対応できる。

  • 外部ライブラリのヘッダと.aを適当な場所に配置したDockerイメージ
  • mrbgem側のクロスコンパイル対応

詳細

mrucy-cli側のDockerイメージの対応

ヘッダファイルの配置

WindowsLinuxOSX向けのヘッダファイル、.aファイルを以下の場所に配置する。

ライブラリの配置

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を調べれば良い。

mrucy-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-cliWindows向けのC++コンパイラの設定が誤っている

今回のmruby-webcamC++のコードを使っていた為に分かった。

正しくは

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で用意するつらい。今回も適度に諦めている。

参考資料