2012年12月15日土曜日

aaptを修正してres配下でディレクトリ階層を構築できるようにして、AOSPにrepo uploadした話

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

photo by statuelibrtynps

Android Advent Calendar 2012 12/15(土)担当の@sys1yagiです。

どうしてもAOSPにコントリビュートしたいと日々悶々としていましたがどうコントリビュったらいいのか思いつかず今まで放置してきました。やはり最初に「コントリビュートしたい」があるともうコントリビュるのが目的になってしまってダメですね。なので現状自分が開発の中で感じた不便を改善するという方向性で考えてみました。

はじめに


resの下が全部フラットなのがすげーめんどいじゃないですか。例えば、layoutとか特定画面で数個使ったりする場合もあるし、そもそも機能単位で分けたいじゃないすか。例えばアプリ内課金の画面フローがあったとしたらそれに関連するレイアウトをディレクトリに切って管理したいじゃないですか。一人でやっててもあのlayout配下に増え続けるファイルを管理するのは大変だってのに、複数人で開発やるってなるともうてんやわんやですね。

これは僕はAndroidを触り始めた1.5辺りの頃からずっと感じていました。なんでフラットやねん、と。でiPhoneアプリケーションの開発をやる事になってXCodeを触り始めてますます強く感じるようになりました。XCodeではプロジェクト内のリソースを論理的にグループ化できたんですよ!Androidェ・・・

res配下はビルド時に解析され、res配下の内容を元にR.javaが生成されます。R.javaに生成されたR.layout.mainとかはintの定数なんで、別にもともとファイルが置かれているパスとかどーでもいいはずです。更にバイナリ化されたresファイルはR.javaのIDとマッピングされているはずなので実XMLファイルがどこにあったかなど問題にならんはずなんですよ!



1.こうしたい


こうしたい!まとめたいよ!


現状だとres/layout/settings配下は無視されてR.layoutにsettings_mainは出てこないんですねー

2.改善するには?


だれがR.javaをgenerateしているんだろうか!?aaptだよ!
aaptのソースは以下のパスにいます
/frameworks/base/tools/aapt

このaaptがresディレクトリ以下をスキャンする処理を修正すればいいのではないか。


3.改善する


改善の要件として「修正によって既存のAndroidアプリケーションのコードに影響を及ぼさない」という事を考えると、res/layout/storeというディレクトリがあったとして、R.javaがR.layout.store.mainという風に書き方が変わってしまうのは避けたいですね。するとres/layout配下はXCodeにおけるグループの様に扱うとよさそうです。

つまりディレクトリで分かれているけどres/layout配下のファイル名はユニークである必要があるという事。生成されるR.javaもres/layout配下のディレクトリ名は無視し、res/layout/store/main.xmlというものがあったらR.layout.mainを生成します。他のres/drawableなども同じです。


修正したファイル


修正したファイルはこれ
#android-4.2_r1
/frameworks/base/tools/aapt/Resource.cpp
385行目のcollect_files関数でXMLパースの対象となるファイルの準備をやっています。
//元のコード
static void collect_files(const sp<AaptDir>& dir,
        KeyedVector<String8, sp<ResourceTypeSet> >* resources)
{
    const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
    int N = groups.size();
    for (int i=0; i<N; i++) {
        String8 leafName = groups.keyAt(i);
        const sp<AaptGroup>& group = groups.valueAt(i);
        const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
                = group->getFiles();

        if (files.size() == 0) {
            continue;
        }
...

ここにlayout配下のサブディレクトリも対象に含める様に再帰処理を追加しました。

//追加後
static void collect_files(const sp<AaptDir>& dir,
        KeyedVector<String8, sp<ResourceTypeSet> >* resources)
{
    //begin
    const DefaultKeyedVector<String8, sp<AaptDir> >& subDirs = dir->getDirs();
    int n = subDirs.size();
    for(int i = 0; i < n; i++){
        const sp<AaptDir>& subDir = subDirs.valueAt(i);
        collect_files(subDir, resources);
    }
    //end
    const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
    int N = groups.size();
    for (int i=0; i<N; i++) {
        String8 leafName = groups.keyAt(i);
        const sp<AaptGroup>& group = groups.valueAt(i);
        const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
                = group->getFiles();

        if (files.size() == 0) {
            continue;
        }
...
collect_files()が呼ばれる前段でresディレクトリ内を走査してAaptDirなどのツリーを作成しています。
この段階ではサブディレクトリ内のファイルなどもツリーが作成されていました。
ところがcollect_files()内ではサブディレクトリの情報は利用せずスルーしていたんですねー。
幸いにもXMLパースの対象にサブディレクトリを含めるだけで「修正によって既存のAndroidアプリケーションのコードに影響を及ぼさない」という要件は満たせました。

※上記コードは一回repo upload後rejectを食らってます。真の修正コードは一番下の方のパッチへのリンクから見ることが出来ます

4.ビルドして使う


repo syncしたディレクトリのルートで、以下のコマンドを叩けばaaptだけビルド出来ます。
make aapt
ビルドが済むと以下の場所にaaptの実行ファイルが吐かれます。
/out/host/darwin-x86/bin/aapt
darwin-x86はMacでの出力先なのでWindowsやLinuxでは別のパスになるはずですが大体同じです。
出来上がったaaptをAndroid SDK内のaaptと入れ替えればオッケーです。
android-sdk-mac_x86/platform-tools/aapt
あるいは
adt-bundle-mac/sdk/platform-tools/aapt

こんなプロジェクトをビルドしてみます
出来た!

5.修正パッチを送る


まずはAOSPに入門!
Android Open Source Project
この辺のドキュメントって日本語化されてないのかなー。
実際コントリビュートする人達はある程度英語読めるだろうから無いという事なんだろうか?!

AOSPに修正パッチを送るにはrepo uploadをする必要があります。というかrepo syncして持って来てたらあとは簡単らしい。ここにパッチを投げる手順が・・・!
Submitting Patches
http://source.android.com/source/submit-patches.html

送ったった・・・!手順に沿えば結構簡単だった。

aapt : Support sub directory resources


6.レビューを待つ


どうなるか全裸待機していたら・・・
あっーー!


ち、ちくしょおおおおお!!



という事で見直し。
エラーログを見ると
/framework/base/packages/SystemUI/res/values-sw600dp-land/dimens.xml
で死んでいるらしい。
どれどれ


なんじゃこりゃああああああ!!
res/values-sw600dp-land
の下にvalues-sw600dp-landがあるやん。
でdimens.xmlが入っている。衝突してエラーになったらしい。
なんでこんな所にこんなディレクトリ切ってんの。なんでそれが採用されてマージされてんねん。

7.エラーを直す


"Duplicate file"とメッセージ出ているので、重複を検出してエラーにしている所があるようです。
そこを探し出して修正。重複があった場合エラー吐いて死んでましたが、warningにする様にしました。
makeも無事通り、再度repo upload!

aapt : Support sub directory resources and ignore duplicated files.

8.レビューを待つ2


パッチを投げて悶々とする日々。実に10日が過ぎようとしていました。
パッチが通るにはコードレビューと検証が必要です。コードレビューってパッチがキューに積まれてGoogleの中の人が順次やってるんやろなーとイメージしていたんですがどうも違う様子です。どうも自分でレビュアーを追加するか、誰かがふらりとレビューしてくれる必要があるようです。

「あ、あかん、放置や・・・」と思ったのでadtの開発についてやりとりをしているadt-devというMLにメールを送らないとなーと思っていた、矢先

Deckardからメールがキテタ━━━━(゚∀゚)━━━━!!。



そしてVerifiedにチェックマークついてたーーーー!


いずれマージされるような気がする・・・!

9.試したい人


aapt : Support sub directory resources and ignore duplicated files.
このパッチを適用してmake aaptし、作成されたaaptを既存のaaptと置き換えればいけます。
MacでしかやってないけどLinuxはすぐできるでしょう。
aaptは普通のネイティブプログラムなんでWindowsではあれかなーCygwinでやるのかなーしらねーよ!

10.終わりに


修正はたったの数行でしたが、そこにたどり着くまでに相当ソースを読みました。
ソース読んでログ仕込んでmakeして動かしてまたソース読んでログ仕込んで・・・の繰り返し。
何度か心が折れかけましたが執拗くmakeを繰り返しておりましたら遂に修正すべき箇所を発見。
そこからは早かったです。

「AOSPにコントリビュートしてわいも世界デビューやでぇ!」
と思ってましたが結構あっけなかったです。
日々AOSP Gerrit Serverにぶん投げられるパッチの一つに過ぎんな、と。

せっかくコントリビュったので継続的にちょくちょくrepo uploadしようかなと思います。

意外と簡単にrepo uploadれるので皆やりなよ!

でも別に儲からないけどね。

誰か焼き肉奢って下さい。


完.



※2012/12/17(月) 追記!

続き


@vvakameからこんな指摘が!





ぎ、ぎにゃ〜〜〜!!

でもありがとおおおお!!

あが…ん
adt-devにやはりメールか。

続き2


という事でおれたちのたたかいはまだはじまったばかりだぜ。
多分別エントリになる。



0 件のコメント:

コメントを投稿