Speee Advent Calendar 21日目です。
昨日の記事はkawakuboxさんのコードリーディングの効能でした。
さてさて、今週で仕事も終わり!という方も多いのではないでしょうか。
そんな前置きに特に関係なく、RubyKaigi2015でmrubyについてとても興味を持ったので、「Pebble Watchでmrubyを動かせれば組みやすくなるかも?」と思い、手を出してみました。
結論から言うと、まだ動作させられていないので、タダの読み物として読んでいただけると幸いです。
おそらく、リンク時にPebbleのアプリサイズの上限を超えてしまっているのかな?と思います。
/usr/local/Cellar/pebble-toolchain/2.0/arm-cs-tools/bin/../lib/gcc/arm-none-eabi/4.7.2/../../../../arm-none-eabi/bin/ld: /Users/xxx/git/mruby-pebble-test/build/chalk/pebble-app.elf section `.text' will not fit in region `APP'
/usr/local/Cellar/pebble-toolchain/2.0/arm-cs-tools/bin/../lib/gcc/arm-none-eabi/4.7.2/../../../../arm-none-eabi/bin/ld: region `APP' overflowed by 372384 bytes
collect2: error: ld returned 1 exit status
ちなみに、先駆者もscalone/pebble-mrubyおられましたが、難しそうなニュアンスを感じました。
以下の流れで進めていきます。
- 準備
- mrubyの組み込み方
- mrubyのクロスコンパイル
- Pebble Watchアプリのコンパイル
- まとめ
準備したもの
作業はMac環境で、必要なプログラムはhomebrewでインストールしました。
# Pebbleの開発環境の導入
brew tap pebble/pebble-sdk
brew update
brew install pebble-sdk pebble-toolchain
# mrubyのコマンド導入
brew install mruby
pebble-toolchainは、gcc+binutilsのクロスコンパイル環境です。arm-none-eabi-gccなどのツールがインストールされます。
mrubyの組み込み方
そもそも、mrubyに興味を持ったのが最近なので、簡単にmrubyについて理解をしてみました。
Rubyでコードを書き、中間コードを生成し、それをmrubyの実行環境に渡して実行する、という流れのようです。
なんとなくですが、Pebble Watchのアプリに組み込んで使う場合は、mrubyのライブラリと、Rubyで書いたソースの中間コードを一緒にリンクしてしまえば良さそうです。
以下の記事が、とても理解しやすかったです。
組み込みC言語プログラマのためのmruby入門(後編) ―― mrubyの組み込み方とJavaとの違い
上記も参考にしつつ、中間コードを生成してみました。
1. Rubyのコードを書く
puts 'Hello mruby world!'
と書いてtest.rbとして保存しました。
2. 中間コードの生成
mrbcを使って
mrbc -Btest test.rb
を実行しました。
testという変数名に中間コードが配列で表現されたC言語のコードがtest.cとして出てきました。
/* dumped in little endian order.
use `mrbc -E` option for big endian CPU. */
#include <stdint.h>
const uint8_t
#if defined __GNUC__
__attribute__((aligned(4)))
#elif defined _MSC_VER
__declspec(align(4))
#endif
test[] = {
0x45,0x54,0x49,0x52,0x30,0x30,0x30,0x33,0x4f,0xe3,0x00,0x00,0x00,0x6c,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0x4e,0x30,0x30,
0x30,0x30,0x00,0x00,0x00,0x46,0x00,0x01,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x04,
0x06,0x00,0x80,0x00,0x3d,0x00,0x00,0x01,0xa0,0x00,0x80,0x00,0x4a,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x00,0x00,0x12,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x6d,0x72,0x75,
0x62,0x79,0x20,0x77,0x6f,0x72,0x6c,0x64,0x21,0x00,0x00,0x00,0x01,0x00,0x04,0x70,
0x75,0x74,0x73,0x00,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x08,
};
これを、Pebbleのプロジェクトに追加すれば、Rubyで書いたコードを組み込めそうです。
mrubyのクロスコンパイル
mrubyの中間コード生成まで行ったので、今度はPebble上で動くmrubyを作ります。
まずは、mrubyを落としてきて、展開します。
wget https://github.com/mruby/mruby/archive/1.2.0.tar.gz
tar zxvf 1.2.0.tar.gz
これで、mruby-1.2.0というディレクトリができています。
クロスコンパイルは、展開したディレクトリ直下にあるbuild_config.rbを書き換えて行います。
こちらに直接、CCやCFLAGS、ARといった、クロスコンパイル時に指定するtoolchainの設定を書いても良いのですが、別のファイルに分けることができるようですので、分けて書いておきます。
※これがあとで効いてくる…かどうかは、正直まだ分かっていません。
以下、ファイルパスと中身です。
buid_config.rb (ビルドの設定)
MRuby::Build.new do |conf|
toolchain :gcc
enable_debug
conf.gembox 'default'
end
# Define cross build settings
MRuby::CrossBuild.new('pebble') do |conf|
toolchain :pebble
end
tasks/toolchains/pebble.rake
class MRuby::Toolchain::Pebble
DEFAULT_ARCH = 'arm-none-eabi'
DEFAULT_PLATFORM = 'pebble'
DEFAULT_TOOLCHAIN = :gcc
DEFAULT_TOOLCHAIN_HOME = '/usr/local/Cellar/pebble-toolchain/2.0/arm-cs-tools/'
class PebbleToolChainHomeNotFound < StandardError
def message
<<-EOM
Couldn't find Pebble SDK Home.
Set PEBBLE_TOOLCHAIN_HOME environment variable or set :toolchain_home parameter
EOM
end
end
attr_reader :params
def initialize(params)
@params = params
end
def home_path
@home_path ||= Pathname(
params[:sdk_home] ||
ENV['PEBBLE_SDK_HOME'] ||
DEFAULT_TOOLCHAIN_HOME.find{ |path| File.directory?(path) } ||
raise(PebbleSDKHomeNotFound)
)
end
def arch
params.fetch(:arch){ DEFAULT_ARCH }
end
def platform
params.fetch(:platform){ DEFAULT_PLATFORM }
end
def toolchain
params.fetch(:toolchain){ DEFAULT_TOOLCHAIN }
end
def toolchain_path
DEFAULT_TOOLCHAIN_HOME + 'bin/'
end
def bin(command)
toolchain_path + [arch, command].join('-')
end
def cc
bin(:gcc)
end
def cflags
flags = []
flags += %w(-std=c99 -Os)
flags += %w(-mcpu=cortex-m3 -mthumb)
flags += %w(-Wl,--gc-sections -Wl,--warn-common -Wl,--build-id=sha1)
flags += %w(-Wno-unused-parameter -Wno-error=unused-function -Wno-error=unused-variable)
flags += %w(-nostdlib -fPIE)
flags
end
def ld
cc
end
def ldflags
flags = []
flags += %w(-std=c99 -Os)
flags += %w(-mcpu=cortex-m3 -mthumb)
flags += %w(-nostdlib -fPIE)
flags
end
def ar
bin(:ar)
end
end
MRuby::Toolchain.new(:pebble) do |conf, params|
sdk = MRuby::Toolchain::Pebble.new(params)
toolchain sdk.toolchain
[conf.cc, conf.cxx, conf.objc, conf.asm].each do |cc|
cc.command = sdk.cc
cc.flags = sdk.cflags
end
conf.linker.command = sdk.ld
conf.linker.flags = sdk.ldflags
conf.archiver.command = sdk.ar
end
toolchainの設定は、homebrewで入れたパスを基準にしているので、別にしている方は合わせていただければいいと思います。
pebble.rakeは、中身的には同じディレクトリにあったadnroid.rakeを書き換えただけです。
また、先駆者さんのリポジトリ内にあったビルド設定を見て修正したので、特にこれということはしていません。(し、結局動いてないわけですし)
ここまで出来ると、あとは
rake
でコンパイルが始まります。
成功すると、build/pebble/lib以下にライブラリが入っていると思います。
mruby-1.2.0/build/pebble/lib $ ls
libmruby.a libmruby.flags.mak
Pebble Watchアプリのコンパイル
長かったですが、ようやくPebble側の実装に入ります。
とりあえず、mrubyの中間コードを実行するだけのコードを書いて、動かすこと目標にします。
新しく、Pebbleアプリの雛形を作ります。
pebble new-project mruby-pebble-test
すると、mruby-pebble-testというディレクトリの中に、雛形が作られます。
サンプルソースも作られるので、そこにmrubyのコードを埋め込みます。
src/mruby-pebble-test.c
#include <pebble.h>
の後に、
#include <mruby.h>
#include <mruby/irep.h>
extern const uint8_t *test;
を追加し、
static void init(void) {
の後に、
mrb_state *mrb = mrb_open();
if(mrb) mrb_load_irep(mrb, test);
mrb_close(mrb);
を追加しました。
これで、mrubyの中間コードが実行されそうです。
が…
ここで詰まる
コードのコンパイルまでは通るのですが、リンクする際にエラーがでてしまいました。
undefined系のエラーは、ごまかしてみたりして、どうにかリンクエラーを潰していったんですが、
最後の最後、
/usr/local/Cellar/pebble-toolchain/2.0/arm-cs-tools/bin/../lib/gcc/arm-none-eabi/4.7.2/../../../../arm-none-eabi/bin/ld: /Users/xxx/git/mruby-pebble-test/build/chalk/pebble-app.elf section `.text' will not fit in region `APP'
/usr/local/Cellar/pebble-toolchain/2.0/arm-cs-tools/bin/../lib/gcc/arm-none-eabi/4.7.2/../../../../arm-none-eabi/bin/ld: region `APP' overflowed by 372384 bytes
collect2: error: ld returned 1 exit status
というエラーがでて、未解決問題入りしました。お手上げです。
嘘です。諦めてはいません。
*APP overflowed by ...*というエラー自体は、Pebble アプリがサイズ上限を超えたりすると出るようで、おそらく、リンクしたmrubyが大きい、とか、newlibみたいな標準Cライブラリが入り込んじゃってる、とか、そういった話かなーと思っています。ライブラリやコードのサイズ辺りを見直せば、動かせたりしないかなー、と思っています。
こうすればいいよ、的なアドバイス、お待ちしております!
まとめ
ということで、Pebble Watchでmrubyが動けばいいなと思って始めてみましたが、なかなか難しそうです。
ですが、Pebble WatchのアプリがRubyの文法で書けるようになれば、Pebble Watchのユーザの裾野が広がるし、mrubyを使う場面も増えていいんじゃないかなーと思うので、続けていきたいと思います。