node.js を iPhone アプリから動かす

結論から言うと、 jailbreak してないと動きません。詳細はこの辺:

Issue 1312 - v8 - It's time to get iOS supported! - V8 JavaScript Engine - Google Project Hosting

というわけでターゲットがデベロッパー用に絞られてしまいますがやってみましょう!

Node ダウンロード

今回はリリースされたばかりの 0.6.0 を使いました。

http://nodejs.org/#download

からダウンロードして展開しましょう

libuv にパッチを当てる

libev はなにもしないでもそのまま iOS で動きますが、 libuv はそうではないようです。

$ cd deps/uv
$ wget --no-check-certificate https://raw.github.com/gist/1354552/cfb4e9a544185bdbda1a8374aaf1cd5cc812c070/libuv-ios.patch
$ patch -p1 < libuv-ios.patch

なお、このパッチは本家に pull req 送っておきました。取り込まれると良いですね。

V8 にパッチを当てる

V8 自体は ARM に対応しているようですが、iOS SDK でクロスコンパイルしようとすると

ARM EABI support is required.

とか言われてしまいます。iPhone の ARM が EABI をサポートしてないためでしょうか。

ただ、最初にリンクした Goole Code の issue 内で iOS 対応のパッチを投稿している人がいて、そのパッチを当てることでビルド可能になる模様。

$ cd deps/v8/src
$ patch -p0 < ~/Downloads/v8-ios.patch

Node にパッチを当てる

プロセスリストにでるプロセス名を設定する機能が Carbon をつかっているので関数殺しちゃいます。iOS でプロセス名かえられても誰得ですよね。具体的には

process.title = "hoge";

が動かなくなります。

$ wget --no-check-certificate https://raw.github.com/gist/1354570/0c28584c07f0410c5e5608d4e2e9ea68e6d5dbc4/node-ios.patch
$ patch -p1 < node-ios.patch

ビルド

まずもろもろ環境変数をセットし:

export CC=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc
export CFLAGS="-arch armv7 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -I$HOME/dev/iphone/lib/openssl/include"
export CXX=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/g++
export CXXFLAGS=$CFLAGS
export CPP=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/cpp
export AR=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/ar
export LINKFLAGS="-arch armv7 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -L$HOME/dev/iphone/lib/openssl/lib"

ビルド:

./tools/waf-light configure build --product-type=cstaticlib --dest-cpu=arm --without-snapshot

ひっそり、前もってビルド済みの openssl を参照していますが、ない場合は --without-ssl を追加すればいけるはずです。

成功すると

./out/Release/deps/uv/uv.a
./out/Release/libnode.a
./out/Release/libv8.a
./out/Release/libv8preparser.a

といった静的ライブラリが出来てると思うんで、これらをアプリに埋め込んであげればいいわけですね。

armv6, armv7, i386 用それぞれビルドし、lipo でユニバーサルバイナリに仕上げるのが iOS 用静的ライブラリ作成のセオリーですが、ここではめんどいので省略します。

アプリ

だいぶ適当ですが、サンプル作ってみました。

https://github.com/typester/ios-node-test

lib ディレクトリにさっきビルドした .a ファイルを突っ込んであげる必要があります。 また node.h の参照元を nave でインストールした node のパスを設定してあるので、違う環境の人は Header Search Path を環境に合わせて書き換える必要もあります。

20111110195319

こんな感じの画面が出るんで、適当にコード書いて Run おせば動かすことが出来ます。

注意

コードみてもらえばわかりますがだいぶ適当につくっておりまして、node の標準出力などをうけとっておりません。また、二回目実行しようとするとアプリが落ちますw

直していただける方お待ちしております!

by typester / at 2011-11-10T19:12:00 / node · iphone / Comment

node.js C/C++ addons 入門

node.js のドキュメントを見ていたら C/C++ で簡単に拡張が書けそうだったので試してみた。

addons - Node.js Manual & Documentation

ドキュメントに載っている hello.cc をみてみると:

#include <v8.h>

using namespace v8;

extern "C" void
init (Handle<Object> target)
{
  HandleScope scope;
  target->Set(String::New("hello"), String::New("world"));
}

この extern "C" void init (Handle<Object> target) というやつが、jsで require("hello") したときに呼ばれる感じらしい。この関数だけは必ず実装する必要がある。

この中の HandleHandleScopeString といったものは全部 v8.h で定義された js 操作用のクラス。

Handle は JavaScript でのデータ全般(数値、文字列、オブジェクト、配列)を表すクラスで、init 関数には何も入っていない空のオブジェクト(ここでは target)が渡される。

この例ではそのオブジェクトにたいして hello というキーで world という文字列を登録している。

なので、これを require すると、

$ node
> require("hello")
{ hello: 'world' }

こんな感じのオブジェクトが返るっていうわけです。簡単ですね。

アドオンのビルドには付属の node-waf というコマンドを使う。これは waf に node.js アドオン用の機能を追加したものなのかな?

この node-waf は wscript という Makefile みたいな設定ファイルを用意してあげる必要がある。ドキュメントに載っているのはこんな感じ:

srcdir = '.'
blddir = 'build'
VERSION = '0.0.1'

def set_options(opt):
  opt.tool_options('compiler_cxx')

def configure(conf):
  conf.check_tool('compiler_cxx')
  conf.check_tool('node_addon')

def build(bld):
  obj = bld.new_task_gen('cxx', 'shlib', 'node_addon')
  obj.target = 'hello'
  obj.source = 'hello.cc'

で、

$ node-waf configure build

とすることで ./build/defaulthello.node がつくられる。

$ node
> require("./build/default/hello")

とかすればテストできます。

$ node-waf install

で、$NODE_PATH で指定されたとこに(たぶん)インストールされ、そうすると単純に require("hello") ができるようになる。

んでもって、C で書きたい! っていう場合は、

srcdir = '.'
blddir = 'build'
VERSION = '0.0.1'

def set_options(opt):
  opt.tool_options('compiler_cxx')
  opt.tool_options('compiler_cc')

def configure(conf):
  conf.check_tool('compiler_cxx')
  conf.check_tool('compiler_cc')
  conf.check_tool('node_addon')

def build(bld):
  c_obj = bld.new_task_gen('cc')
  c_obj.name = 'c_obj'
  c_obj.target = 'hello'
  c_obj.source = 'foo.c bar.c'

  obj = bld.new_task_gen('cxx', 'shlib', 'node_addon')
  obj.target = 'hello'
  obj.source = 'hello.cc'
  obj.add_objects = 'c_obj'

こんな感じの wscript を書けばいい模様。とはいえ、v8 自体が C++ 製なので、JavaScript とつなぐところは C++ が必要になる。

require 時になんかエスクポートしたりとかもっと良い感じのモジュールにするには

modules - Node.js Manual & Documentation

この辺読めば良さそうですね。

あと、C++ 側は、v8.h、node.h、を中心に include/node にあるヘッダファイルを見るといろいろわかりそう。IOやTimerをつかうときには libev の使い方も知っておく必要がある。 node.js には libev が組み込まれていて、EV_DEFAULT ループを本体がうごかしているから、そこにたいしておもむろに ev_io_start とかしてあげるだけで拡張内で非同期IO使えます。

あとはいろんな拡張を参考に。Google コード検索で 「compiler_cxx node_addon」というので wscript を検索すると node.js 拡張だけが良い感じで引っかかってくるのでおすすめ。

by typester / at 2011-02-18T09:57:00 / node / Comment