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

  • 5
    Like
  • 0
    Comment
More than 1 year has passed since last update.

Speee Advent Calendar 21日目です。
昨日の記事はkawakuboxさんのコードリーディングの効能でした。

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

そんな前置きに特に関係なく、RubyKaigi2015mrubyについてとても興味を持ったので、「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おられましたが、難しそうなニュアンスを感じました。

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

準備したもの

作業は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 Watchmrubyが動けばいいなと思って始めてみましたが、なかなか難しそうです。

ですが、Pebble WatchのアプリがRubyの文法で書けるようになれば、Pebble Watchのユーザの裾野が広がるし、mrubyを使う場面も増えていいんじゃないかなーと思うので、続けていきたいと思います。

This post is the No.21 article of Speee Advent Calendar 2015