AndroidでJavaScriptCoreを使えるようにビルドスクリプトを作った
iOSはiOS 7からApple公式にJavaScriptCore.frameworkが提供されています。これはJavaScriptを実行するためのライブラリで、JavaScriptをアプリ内スクリプトとして使えるようになります。さらに、JavaScript実行環境に対してC言語の関数を繋ぎこむ機能もあるため、それを使ってアプリ側の機能を呼び出すといった事もできます。
Androidでも同じことができれば、そのスクリプトはiOS/Android両方で動かせるので、いろいろと活用できると思います。AndroidでもJavaScriptCoreを動かそうとしたところ、いろいろとやらないといけないことがありました。最終的にビルドできるようにできたので、それらを整えて再利用しやすい形にまとめました。
JavaScriptCoreについて
JavaScriptCore(以下JSC)はWebKitのサブプロジェクトで、Appleが主に開発しているオープンソースのライブラリです。SafariでのJavaScript実行にも使われています。iOSのSafariやUIWebViewの内部でも使われており、iOS 7からはこれがユーザからも使えるようになりました。
一方AndroidのブラウザのJavaScript実行エンジンはV8が使われています。Chromeは元々WebKitで作られていましたが1、JSについてはJSCでは無く、V8が使われていました。そのため、JavaScriptCoreはAndroidには搭載されていないし、今後公式から提供される見込みもありません。ソースを取ってきて自分でビルドする必要があります。
先行事例
検索するといくつかの事例が見つかりました。
ericwlangeさんの事例
以下はJSCをAndroidに取り込み、さらにJavaのラッパーAPIを提供したプロジェクトです。
このプロジェクトにおいて、JSC本体は、同じ作者のHermoidというツールでビルドしているようです。
下記がHermoidのURLです。
HermoidはMac OS XのHomebrewのAndroid版、という事だそうです。
ビルドの設計としては、ソースコードを取ってきて、パッチをあてて、NDKツールチェインの元でcmakeを実行してビルドする、という感じです。
取ってくる部分についての定義は下記にあります。
パッチについては下記にあります。
結構スマートな設計だと思います。特にcmakeをそのまま実行できるところが強いです。JSCのビルドでは、C++をコンパイルする前に、いろいろとソースコードなどを自動生成するためのスクリプトを実行する必要があります。これは本家ではCMakeListとして実行手順が実装されているので、これをそのまま実行できています。実行する際に、cmakeの環境変数を書き換えて、gccといったツールチェインをNDKのものに切り替える事で実現しています。
NDKツールチェインの単体利用については、下記にドキュメントがあります。
スマートだとは思うのですが、個人的にはあまり利用したくないです。ツールチェインの単体利用はAndroid公式に認められてるものの、一番基本となるインターフェースはやはりAndroid.mkとndk-buildです。ndk-buildのレイヤーで、ビルド時に考慮されている細かい設定などが、直叩きの場合は対応できなくなるため、ndk-buildを使っていなかった事に起因した問題が出た場合に対応が困難です。
iOSでWebRTCをビルドした時も、ビルドスクリプトがclangを直接叩いていたために、新しいxcodebuildでは考慮されるコンパイルスイッチを、ビルドスクリプトの方に手動で反映させる必要があったりしました。
ndk-buildもxcodebuildと似たような位置づけだと思っていて、自分はndk-buildを使った構成のほうが良いと思います。
aogilvieさんの事例
以下はJSCをAndroid.mkベースでビルドしているプロジェクトです。
JSCのソースコードを直接リポジトリに突っ込んであり、おそらくパッチ作業とスクリプト実行も終わった状態です。
Cocos-2DXでJavaScriptを使うためのサブプロジェクトとして開発されたようです。iOSとWindows向けのビルドも提供されているようです。
2年以上保守されていないようですし、JSC本体ソースとビルド系が混ざってしまっているので、JSC自体のアップデートがおそらく結構難しいです。
保守を考えると本家とビルド系は綺麗に分けて、本家への操作は最小限にしたいです。
facebookの事例
以下はFacebook製のJSCをビルドしているプロジェクトです。
ReactNativeプロジェクトのサブプロジェクトとして開発されたようです。ReactNativeのJSエンジンについては下記に文書があります。
実際にビルドで取り込んでいる記述は下記で見つかります。
構成を見てみると、BUCKというビルドシステム向けのスクリプトとして実現されています。
BUCKはFacebook製のビルドシステムです。
今回BUCKについても軽く調べたところよくできていると思いました。ただ、ホスト言語がpythonなのが好みでは無かったです。
動作としては、まず公式のソースコードを取ってきて、それにパッチして、もろもろのスクリプト実行をして、コンパイルという感じです。
パッチする部分については、マクロ定義とヘッダー差し替えだけで対応していて、ギリギリcppファイル書き換えは行っていませんでした。どうしてもcpp書き換えが必要な場合には、パッチする機能もあるんでしょうかね。
差し替えているヘッダーはこちらです。
スクリプト実行については、もともとのCMakeListに書かれている定義を再実装しています。
BUCK自体にももちろん依存解決機能があるので、本家のcmakeと同様に、これらスクリプトの実行についても、依存にもとづいて行えるようです。
設計自体はとても綺麗に思ったのですが、まずBUCKを使っているところのハードルが高いです。自分が今後BUCKを使っていくならありですが、特にそのつもりもないので学習が面倒です。
また、1つ目の事例と同じく、BUCKから直接gcc等を呼び出しており、ndk-buildベースでは無いのが微妙でした。
設計
上記先行事例を踏まえつつ、本体ソースとパッチ+ビルド系を分離する事、ndk-buildをベースにする、という設計にしました。
中間スクリプトの実行は、依存関係の解決は諦めつつ、実行用のRubyスクリプトを用意することにします。C++のビルドと比べたらそんなに重いフェーズでは無いので、大して問題では無いかなと思います。
成果物
完成したプロジェクトを下記に公開しています。
手順は半自動化してあります。
やっかいだったのが、一度バイナリをコンパイルした後、そのバイナリを再びスクリプトにかけてデータ化し、ソースコードに埋め込むフェーズです。JITの実装のためのアセンブリコードの抽出のためにそのような処理をしていました。
ndk-buildを実行するところは隠蔽したくなかったので、スクリプトを2段階にわける必要がありました。
JSCのアップデート対応をする場合、ソースを更新した後、CMakeListのdiffを見て、それに合わせてAndroid.mk等を修正する形になります。
-
今はWebKitからフォークしたBlinkが使われています。 ↩