Pebble Watchでmrubyを試す（まだ動かせて無いよ編）

[Speee Advent Calendar](http://qiita.com/advent-calendar/2015/speee) **21**日目です。
昨日の記事はkawakuboxさんの[コードリーディングの効能](http://kawakubox.hatenablog.com/entry/2015/12/20/102547)でした。

さてさて、今週で仕事も終わり！という方も多いのではないでしょうか。

そんな前置きに特に関係なく、**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](https://github.com/scalone/pebble-mruby)おられましたが、難しそうなニュアンスを感じました。

以下の流れで進めていきます。
1. 準備
2. mrubyの組み込み方
3. mrubyのクロスコンパイル
4. Pebble Watchアプリのコンパイル
5. まとめ

# 準備したもの
作業はMac環境で、必要なプログラムはhomebrewでインストールしました。

```bash
# 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との違い](http://www.kumikomi.net/archives/2012/12/ep17mrb3.php)

上記も参考にしつつ、中間コードを生成してみました。
## 1. Rubyのコードを書く

```rb
puts 'Hello mruby world!'
```
と書いて*test.rb*として保存しました。

## 2. 中間コードの生成  
mrbcを使って

```bash
mrbc -Btest test.rb
```
を実行しました。

*test*という変数名に中間コードが配列で表現されたC言語のコードが*test.c*として出てきました。

```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を落としてきて、展開します。

```bash
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* (ビルドの設定)

```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*

```rb
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を書き換えただけです。
また、先駆者さんのリポジトリ内にあったビルド設定を見て修正したので、特にこれということはしていません。(し、結局動いてないわけですし）

ここまで出来ると、あとは

```bash
rake
```
でコンパイルが始まります。
成功すると、*build/pebble/lib*以下にライブラリが入っていると思います。

```
mruby-1.2.0/build/pebble/lib $ ls
libmruby.a		libmruby.flags.mak
```

# Pebble Watchアプリのコンパイル

長かったですが、ようやくPebble側の実装に入ります。
とりあえず、mrubyの中間コードを実行するだけのコードを書いて、動かすこと目標にします。

新しく、Pebbleアプリの雛形を作ります。

```bash
pebble new-project mruby-pebble-test
```

すると、*mruby-pebble-test*というディレクトリの中に、雛形が作られます。

サンプルソースも作られるので、そこにmrubyのコードを埋め込みます。

*src/mruby-pebble-test.c*

```c
#include <pebble.h>
```
の後に、

```c
#include <mruby.h>
#include <mruby/irep.h>

extern const uint8_t *test;
```
を追加し、

```c
static void init(void) {
```
の後に、

```c
  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**を使う場面も増えていいんじゃないかなーと思うので、続けていきたいと思います。