Android Studio で Android Framework (AOSP) のソースコードを読む

# TL;DR

- macOS上のAndroid StudioでAOSPのソースコードを読む環境を構築したので、方法をシェアします
    - AOSPに含まれる [IDEGen](https://github.com/android/platform_development/tree/master/tools/idegen) というツールでIntelliJ IDEAプロジェクトファイル `android.ipr` を生成して、Android Studioで開く、というのが基本的な手順です
    - プロジェクトを開いた後、ソースコード間でクラスが適切に参照されるように、SDK構成の設定や、ソースパス・参照順序の調整が必要です

# はじめに

Android Open Source Project (AOSP) のソースコードを読む場合、一般的には、[Developer Collaboration Project](https://sites.google.com/site/devcollaboration/) が公開している[Androidソースコード検索サービス](https://sites.google.com/site/devcollaboration/codesearch)を使うのが便利です。ソースコードをダウンロードする必要もないし、色々なOSバージョンのソースコードをすぐ読めます。[OpenGlok](https://opengrok.github.io/OpenGrok/) というソースコード検索エンジンが使われていて、ソースコードの検索機能も充実しています。

ただ、Android Studioでソースコードを見られれば、クラスやメソッドの参照先・被参照元もさくさく探せて最高なはず。というわけで、Android StudioでAOSPを読む環境を構築しました。

# 筆者の環境

- macOS (Sierra: 10.12.4)
- Android Studio 2.3.1

# AOSPソースコードをダウンロードする

まずは、AOSPのソースコードをダウンロードしてきます。

## Case-sensitiveなファイルシステムについて

AOSPのビルドにはcase-sensitiveなファイルシステムが必要です。そのため、[Establishing a Build Environment](https://source.android.com/source/initializing#creating-a-case-sensitive-disk-image) に書かれているように、macOSではcase-sensitiveなファイルシステムのディスクイメージを作成し、そこにAOSPのソースコードをダウンロードする必要があります。ただ、ソースコードを読むだけなら別に問題ないだろう、ということで、筆者はmacOSのファイルシステムに直接ソースコードをダウンロードしました (今のところ無問題です)。

## repo コマンドのインストール

AOSPのソースコードは多くのGitレポジトリに分かれているので、それらをいい感じにまとめて扱ってくれる `repo` コマンド (中身はPythonスクリプト) をインストールします。

[Downloading the Source](https://source.android.com/source/downloading#installing-repo) で説明されているように、`curl` コマンドで `repo` スクリプトをダウンロードするのが定石ですが、Homebrew を使ってもインストールできます。

```bash
brew install repo
```

## ソースコードのダウンロード

[Downloading the Source](https://source.android.com/source/downloading) の説明に従いつつ、`repo` コマンドを使ってソースコードをダウンロードします。

デフォルトだと `master` ブランチのソースコードがダウンロードされますが、タグを指定してダウンロードするのがよいと思います。OSバージョンとタグの関係は [Codenames, Tags, and Build Numbers](https://source.android.com/source/build-numbers#source-code-tags-and-builds) を見るとわかります。

私は、2017年4月14日現在で最新だった `android-7.1.2_r6` をダウンロードしました。

```bash
# ソースコードを格納するディレクトリを作成し、その中に移動する
mkdir android-7.1.2_r6
cd android-7.1.2_r6

# ブランチ android-7.1.2_r6 を指定して、repo クライアントを初期化する
repo init -u https://android.googlesource.com/platform/manifest -b android-7.1.2_r6

# ソースコードをダウンロードする
repo sync
```

通信環境にもよりますが、ソースコードのダウンロードには数時間かかります。気長に待ちましょう。

# IntelliJ IDEAプロジェクトファイルを生成する

ソースコードがダウンロードできたら、ダウンロードしたソースコードがまるっと含まれる、IntelliJ IDEAプロジェクトファイル (.ipr) ファイルを生成します。IPRファイルの生成には、AOSPに含まれるIDEGenというツールを使います。

基本的には、IDEGenの [README](https://github.com/android/platform_development/blob/master/tools/idegen/README) に書かれているように、AOSPソースツリーのルートで以下のコマンドを実行するだけです。

```bash
make idegen
development/tools/idegen/idegen.sh
```

が、コマンドを実行する前に、ちょっとだけMakefileを修正します。

## ファイルシステムのcase-sensitiveness検査をスキップする

AOSPのソースコードをcase-insensitiveなファイルシステムにダウンロードした場合、そのままだと、Make中に行われる検査にひっかかりエラーとなります。

```
build/core/main.mk:159: ************************************************************
build/core/main.mk:160: You are building on a case-insensitive filesystem.
build/core/main.mk:161: Please move your source tree to a case-sensitive filesystem.
build/core/main.mk:162: ************************************************************
build/core/main.mk:163: *** Case-insensitive filesystems not supported.
```

検査にひっかかったとしてもMakeの実行が継続されるように、`build/core/main.mk` で、`Case-insensitive filesystems not supported` というエラーメッセージを出力している行をコメントアウトします。

```diff
diff --git a/core/main.mk b/core/main.mk
index a612f835d..035788200 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -160,7 +160,7 @@ $(warning ************************************************************)
 $(warning You are building on a case-insensitive filesystem.)
 $(warning Please move your source tree to a case-sensitive filesystem.)
 $(warning ************************************************************)
-$(error Case-insensitive filesystems not supported)
+# $(error Case-insensitive filesystems not supported)
 endif

 # Make sure that there are no spaces in the absolute path; the
```

## android.ipr を生成する

あとは、AOSPソースツリーのルートディレクトリで以下のコマンドを実行してください。実行に成功した場合、同じディレクトリに `android.ipr` というファイルが生成されます。

```bash
make idegen
development/tools/idegen/idegen.sh
```

筆者は `make idegen` の実行で何箇所かつまづきました。次の節で、筆者が遭遇した問題とその解決方法を説明しているので、もし同じ問題にぶち当たったら参考にしてください。

## `make idegen` のトラブルシューティング

### `xcode-select: error` と言われる場合

`make idegen` の実行時に以下のようなエラーメッセージが出る場合

```
xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance
build/core/combo/mac_version.mk:26: none of the installed SDKs (ac_sdk_versions_installed) match supported versions (10.8 10.9 10.10 10.11), trying 10.8
build/core/combo/mac_version.mk:36: no SDK 10.8 at /Library/Developer/CommandLineTools/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk, trying legacy dir
build/core/combo/mac_version.mk:40: *****************************************************
build/core/combo/mac_version.mk:41: * Can not find SDK 10.8 at /Developer/SDKs/MacOSX10.8.sdk
build/core/combo/mac_version.mk:42: *****************************************************
build/core/combo/mac_version.mk:43: *** Stop..  Stop.
```

アクティブなデベロッパーディレクトリが、コマンドラインツールのものになっていることが原因です。`xcode-select` コマンドを使って、デベロッパーディレクトリの場所を `Xcode.app` 内のディレクトリに変更してください。

```bash
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
```

### インストールされているSDKがない、と言われる場合

`make idegen` の実行時に以下のようなエラーメッセージが出る場合

```
build/core/combo/mac_version.mk:26: none of the installed SDKs (ac_sdk_versions_installed) match supported versions (10.8 10.9 10.10 10.11), trying 10.8
build/core/combo/mac_version.mk:36: no SDK 10.8 at /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk, trying legacy dir
build/core/combo/mac_version.mk:40: *****************************************************
build/core/combo/mac_version.mk:41: * Can not find SDK 10.8 at /Developer/SDKs/MacOSX10.8.sdk
build/core/combo/mac_version.mk:42: *****************************************************
build/core/combo/mac_version.mk:43: *** Stop..  Stop.
```

AOSPのMakefileが前提にしているmacOS SDKのバージョンと、マシンにインストールされているSDKのバージョンが一致していないことが原因です。まず、インストールされているmacOS SDKのバージョンを確認してください。筆者の場合は、10.12のみでした。

```
$ ls /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs
MacOSX.sdk      MacOSX10.12.sdk
```

このバージョン番号を、`build/core/combo/mac_version.mk` で定義されている変数 `mac_sdk_versions_supported` に追加すれば、上記のエラーが発生しなくなります。

```diff
diff --git a/core/combo/mac_version.mk b/core/combo/mac_version.mk
index 51394c64b..8fa08c6e6 100644
--- a/core/combo/mac_version.mk
+++ b/core/combo/mac_version.mk
@@ -9,7 +9,7 @@ ifndef build_mac_version

 build_mac_version := $(shell sw_vers -productVersion)

-mac_sdk_versions_supported :=  10.8 10.9 10.10 10.11
+mac_sdk_versions_supported :=  10.8 10.9 10.10 10.11 10.12
 ifneq ($(strip $(MAC_SDK_VERSION)),)
 mac_sdk_version := $(MAC_SDK_VERSION)
 ifeq ($(filter $(mac_sdk_version),$(mac_sdk_versions_supported)),)
```

### `could not find jdk tools.jar` と言われる場合

`make idegen` の実行時に以下のようなエラーメッセージが出る場合

```bash
build/core/config.mk:601: *** Error: could not find jdk tools.jar at /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/../lib/tools.jar, please check if your JDK was installed correctly.  Stop.
```

`JAVA_HOME` に相当するディレクトリのパスを、`ANDROID_JAVA_HOME` 環境変数として定義してください。

```bash
# 設定例
export ANDROID_JAVA_HOME=`/usr/libexec/java_home -v 1.8`
```

# Android StudioでAOSPソースツリーを開く

## android.ipr ファイルを指定してプロジェクトを開く

`android.ipr` ファイルが生成できたら、Android Studioで `android.ipr` ファイルを開きます。AOSPのルートディレクトリを開くのではなく、そこに配置されている `android.ipr` ファイルを開くように注意してください。

プロジェクトを開くと、ファイルのインデックスなどでしばらくビジーになるので、処理が終わるのを待ちます。

## プロジェクト設定を変更する

開いたプロジェクトはこのままだといくつか問題があるので、メニュー "File" - "Project Structure..." からプロジェクト設定を変更します。

### JDKを設定する

"Project Structure" ウィンドウ左部分にある "SDKs" を選択して、このプロジェクトに設定するためのSDK構成を作成します。SDKの種類は "JDK" で、JDK home pathにはマシンにインストールされているJDKのHomeディレクトリを指定してください (`/usr/libexec/java_home` コマンドで確認できます)。また、AOSPのソースコードからJDKのクラスが参照されないよう、"Classpath" を全て削除します。

![Screen Shot 2017-04-16 at 1.08.11.png](https://qiita-image-store.s3.amazonaws.com/0/80557/a1811a72-6dee-ec91-4589-f444444c0f3c.png)

新しいSDK構成が作成できたら、それをプロジェクトのSDKとして設定します。ウィンドウ左部分から再度 "Project" を選択して、作成したSDK構成を選択します。

![Screen Shot 2017-04-16 at 1.11.07.png](https://qiita-image-store.s3.amazonaws.com/0/80557/a88c8ba2-2084-4342-7d48-3d687bce9bc4.png)

### "Android" モジュールを削除する

"Android" という名前のモジュールが自動的に作成されてしまっていますが、不要なので、"Modules" にて削除します。

![Screen Shot 2017-04-16 at 1.26.53.png](https://qiita-image-store.s3.amazonaws.com/0/80557/fafb7908-ad92-0fe8-5339-68e19f61ea64.png)

### external/libgdx 内にある emu ディレクトリをソースパスから除外する

external/libgdx 内の `emu` ディレクトリに格納されているJavaクラスが、libcore-luni が提供するJava標準クラスと衝突しているため、`emu` ディレクトリをソースパスから除外します。

"Modules" で "android" モジュールを選択した後、"Sources" タブで該当ディレクトリを選択して、 "Excluded" マークをONに変更します。

![Screen Shot 2017-04-16 at 1.45.51.png](https://qiita-image-store.s3.amazonaws.com/0/80557/8480cb0e-7271-3d07-6617-5f40a93ee072.png)

変更が必要なディレクトリは (筆者がダウンロードした `android-7.1.2_r6` ブランチのソースツリーでは) 以下の3つでした。

- external/libgdx/backends/gdx-backends-gwt/src/com/badlogic/gdx/backends/gwt/emu
- external/libgdx/extensions/gdx-box2d/gdx-box2d-gwt/src/com/badlogic/gdx/physics/box2d/gwt/emu
- external/libgdx/extensions/gdx-controllers/gdx-controllers-gwt/src/com/badlogic/gdx/controllers/gwt/emu

### ソースコードをJarファイルよりも優先して参照するようにする

AOSPのソースツリーにはJarファイルが多数含まれており、デフォルトでは、それらのJarファイルがソースファイルよりも優先して参照されるようになっています。

このままだと、コードリーディングしているときにソースファイル間を上手くジャンプできないので、ソースファイルが最優先で参照されるように依存順序を変更します。"Modules" で "android" モジュールを選択した後、"Dependencies" タブで依存の順序を変更し、"\<Module source\>" が最初に参照されるようにします。

![Screen Shot 2017-04-16 at 1.52.30.png](https://qiita-image-store.s3.amazonaws.com/0/80557/19bf3cb7-e43c-9336-7014-bf3284611eef.png)

気が遠くなるくらい大変なので、Alt+上カーソルキー のショートカットを使うことをお勧めします。

# Happy Code Reading!!

これで、一通りのAOSPのソースコードがAndroid Studioで読めるようになります。例えば、Android Studioのナビゲーション機能を使えば、あるクラスを継承しているクラスの一覧、なども簡単に調べられるようになります。

![Screen Shot 2017-04-16 at 2.11.28.png](https://qiita-image-store.s3.amazonaws.com/0/80557/d8e43677-49f2-1af7-8256-78691762b2a1.png)

# 残課題

今回はAOSPソースツリーをビルドしていないので、AIDLファイルから生成されるインターフェースなど、ビルドにより生成されるものが存在しておらず、それらの参照ができない状態なのが残念なところです。