Android
mruby
Android-x86
mrubyDay 20

mrubyとAndroid.mk 2013年冬ver.

More than 5 years have passed since last update.


[おさらい] mruby のビルドシステム

mruby のビルドシステムは,GNU make→CMake と変遷し,最近は minirake という Rake のサブセットが採用されています.

現在も Makefile は存在していますが,中身を見れば判る通り,minirake を呼び出す薄皮でしかありません.

# mruby is using Rake (http://rake.rubyforge.org) as a build tool.

# We provide a minimalistic version called minirake inside of our
# codebase.

RAKE = ruby ./minirake

.PHONY : all
all :
$(RAKE)

.PHONY : test
test : all
$(RAKE) test

.PHONY : clean
clean :
$(RAKE) clean

minirake (Rake) は,Ruby が持つ DSL向きの性質を活かしたビルドシステムです.

Ruby を知っていれば,なんでもできます.

この性質は,Ruby そのものには興味がなくビルドして製品に組み込むだけが目的の大多数技術者にとって極めてアタマの痛いものRuby で世の中全てを記述したい志の高いマジョリティ達にとっては夢の様な環境です.

大抵のユースケースでは,Ruby に詳しくないユーザでも minirake でビルドすることに困難を感じないでしょう.

make コマンドも使えますし,大抵の開発環境は,(minirake が必要とする) Ruby も入っています.

しかし,システムイメージ全体をビルドするための特殊な環境となっている場合には,make ベースなら要らない余計な一手間がかかります.

今回は,Android PDK を例に,その一手間をどう解決するのかを示してみようと思います.


[おさらい] Android PDK と NDK の違い,mruby との関係

Android には,いくつかの開発キットが存在しています.

それらのうち,ネイティブアプリに関連するキットに,PDK と NDK が存在しています.


NDK

NDK は,Java (Dalvik)で記述されたアプリから呼び出す DLL の作成,またはコマンドラインから呼び出すネイティブアプリのために用意されているものです.

DLL の作成時には,Android.mk と呼ばれる,GNUmakefile の書式に準じた断片を用意することになっています.

Android.mk は,ndk-build という GNU make のラッパーによって解釈され,最小限の記述量で誤りの少ないバイナリビルドを実現します.

さらにJava (Dalvik)で記述されたアプリケーションと併せ,配布可能なアプリケーションパッケージ (apk)の作成を支援するようなバイナリ配置を行ったりもします.

これらのメリットのその対価として,ビルドステップのカスタマイズが難しくなっています.

NDK の Android.mk を用いた mruby の DLL 作成は,既に成功例があります.

(参照: http://d.hatena.ne.jp/tarosay/20130320/1363800215 )

また,NDK は,Android.mk の縛りを受けないクロスコンパイラとして活用することも可能です.

このような NDK の利用形態においては,mruby は既にビルドのためのサポートが入っています.

(参照: http://crimsonwoods.hatenablog.com/entry/2013/03/15/012550 )


PDK

PDK は,Android のシステムイメージを作成するための開発キットです.

Android に必要な,Linuxカーネル,各種アプリケーション(Javaで記述されていたり,C/C++での記述だったり)を適切にビルドし,動作時に求められるディレクトリに展開し,イメージとして固めます.

対象は,Android端末そのものの開発に従事するプロ開発者,俗にカスタムROM と呼ばれるものを作る趣味人,またAndroid-x86 のような PC 上で動作するAndroidに興味を動作を持つ好事家,

いずれにしても Android 開発者のなかでもごく一部の低レイヤ好きしか触る機会がないものと言えます.

NDK など,他の Android 開発キットを束ねる役割を果たします.

ネイティブアプリの作成においては,NDK を用いています.

しかしながら,制約があります.

DLL にしたくなくても,Android.mk の縛りを受けないクロスコンパイラを利用することはできません.

常に Android.mk の配下でビルドを行う必要があります.

つまり,既述の NDK を用いたビルド法では,mruby を Android のシステムイメージに組み込むことはできません.


本題

いくつかの解法はあると思います.

minirake が行っていることを,Android.mk で書きなおすというのは,一つの手です.

しかし,minirake の中で gcc のコマンドラインオプションでマクロを定義していたりすると,ビルドは通るが挙動が違う,という悲惨なことが起こりかねません.

そこで,今回は Android.mk の枠の中に沿っているように見せかけて,実際のビルドは minirake が行う というアクロバットな手法で切り抜けました.

まず,mruby 側, build_config.rb です.

MRuby::CrossBuild.new('android-pdk') do |conf|

toolchain :gcc

conf.cc.command = ENV['REPO_ROOT'] + ENV['TARGET_CC']
conf.cc.include_paths << ENV['TARGET_C_INCLUDES'].split.map { | path | ENV['REPO_ROOT'] + path }
conf.archiver.command = ENV['REPO_ROOT'] + ENV['TARGET_AR']
conf.linker.command = conf.cc.command
conf.linker.flags << ENV['TARGET_LDFLAGS']

conf.gembox 'default'
end

Advent Calendar 1日目で,@masuidrive さんがビルドの解説をしているので…すが,クロスコンパイル関連はスルーなので,困り…ましたが,まあ,何をやっているのかは,コードを読んで 察してくださいコード嫁.それがmrubyの流儀です.

続いて,Android.mk

LOCAL_PATH:= $(call my-dir)

REPO_ROOT := $(realpath $(LOCAL_PATH)/../../)/

#
# Build mruby
#
include $(CLEAR_VARS)

clobber: clean
(cd $(LOCAL_PATH) && ./minirake clean)

LOCAL_SRC_FILES := mirb mrbc
LOCAL_MODULE := mruby
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := debug
include $(BUILD_PREBUILT)

GENERATE_MRUBY_FILES := $(addprefix $(LOCAL_PATH)/, $(LOCAL_MODULE) $(LOCAL_SRC_FILES))
$(GENERATE_MRUBY_FILES):
(cd $(LOCAL_PATH) && \
REPO_ROOT=$(REPO_ROOT) \
TARGET_CC="$(TARGET_CC)" \
TARGET_C_INCLUDES="$(TARGET_C_INCLUDES)" \
TARGET_AR="$(TARGET_AR)" \
TARGET_LDFLAGS="--sysroot $(REPO_ROOT)/$(dir $(TARGET_CRTBEGIN_DYNAMIC_O))/.. -L $(REPO_ROOT)/$(TARGET_OUT_INTERMEDIATE_LIBRARIES)" \
./minirake && \
cp build/android-pdk/bin/* .)

# Any prebuilt files with default TAGS can use the below:
prebuilt_files := $(LOCAL_SRC_FILES)

$(call add-prebuilt-files, EXECUTABLES, $(prebuilt_files))

NDKでAndroid.mk を書いたことがある人でも,何やっているのかアタマを抱えるかもしれません.

PDKでは,事前にビルドが済んでいる(prebuilt)ファイルを,単にユーザランドにコピーすることができます.

include $(BUILD_PREBUILT) とか $(call add-prebuilt-files, EXECUTABLES, $(prebuilt_files)) とかがその辺りを担っています.

ちなみに,このテクニックは,gdbserver 辺りの Android.mk を読むと,よりシンプルに理解することができます.

しかし,mruby のソースリポジトリにおいて,mruby, mirb, mrbc は prebuilt ではありません.

そこで, mruby, mirb, mrbc が LOCAL_PATH の直下にあるはずだということにして,mruby, mirb, mrbc のビルドルールで minirake を呼び出します.

このテクニックは,既述の NDK を用いた DLL のビルドに関する記事でも Android.mk の中に入っています.

ただし,minirake は,ツールチェインがどのように配置されているかは知りませんので,PDK が make変数として与えている各種情報を環境変数として minirake に渡します.


注意点,メモ

Android.mk の最初に方に REPO_ROOT := $(realpath $(LOCAL_PATH)/../../)/ という行がありますが,ちょっとこれはイマイチです.でも良い方法が思い浮かびませんでした.

mruby のビルドが minirake で行われることの副作用については,Android にかぎらず,おそらく組込み製品向けのPOSIX系ディストリビューションでは概ね起こるだろうと思います.

NetBSD とか,OpenWRT とか.

その辺はそれぞれで解決のためのノウハウがあるはずで,そのうち公開されることもあるのでしょう.

もし,今も未解決で悩んでいる方がいらっしゃいましたら,この記事が少しでもヒントになれば幸いです.