NDKは嫌だと思う人へ：NDKを便利に利用するツールandroid-gcc-toolchain

>直感で、一個コマンドだけで、テスト用のC/C++ファイルをAndroid版にコンパイルしたい：
`android-gcc-toolchain gcc a.c`

NDKは独自のmkファイルは便利ですが、世の中、いろんなオープンソースは伝統的なMakefileやautoconfやCMakeやGYPなどで構成されています。
これらのプロジェクトをAndroid版にコンパイルするには、Standalone Toolchain(むしろ標準toolchainといった方が良い)の登場です。

NDKは、確かに`$NDK/build/tools/make_standalone_toolchain.py`(古いバージョンは`$NDK/build/tools/make-standalone-toolchain.sh`)を提供していた。
これで標準toolchainを作成できますが、いくつか不便なところがあります：

- toolchainを作成した後、自分でPATHや、CCなど環境変数をいちいち設定しないといけない
- toolchain作成は遅い
- arm,arm64などarchの間での切り替えは不便
- ccache取り込んでいない
- ... つまり、もっと自動化できるのに...　


私は我慢できませんので、ツール[android-gcc-toolchain](https://github.com/sjitech/android-gcc-toolchain)を作成しました。

何ができるの？ 以下、android arm64版を例として簡単に説明します。arm64を省略すれば普通のarm(32bit)になります。

### 1. Androidのgccなどコマンドを簡単に実行できる.

- コマンドの前にandroid-gcc-toolchainを入れば良い。arch(arm,arm64,x86,x64,mips,mip64)を指定できます。デフォルトはarm(32bit)。

    ```
    android-gcc-toolchain arm64 gcc a.c
    android-gcc-toolchain arm64 g++ -v a.cc
    android-gcc-toolchain arm64 g++ --help
    android-gcc-toolchain arm64 clang++ -c a.c
    android-gcc-toolchain arm64 which gcc
    android-gcc-toolchain arm64 objdump -h a.out
    android-gcc-toolchain arm64 sh -c 'git checkout v1.0 && make clean && make'
    ```
    gccなどコマンドのパラメータは元のコマンドのと変わらない。

    実際は、どんなコマンドでも良いが、以下のgcc関連コマンドは全部Android版にリライレクトされます：
    - gcc g++ cc c++ clang clang++ ar as ranlib ld strip nm ...
    - readelf objdump c++filt elfedit objcopy strings size ...
   
### 2. クロスコンパイル用bashに簡単に入れます

- 対話式のbashなどshellに入る、gccなど関連コマンドをご利用いただけます。

    ```
    $ android-gcc-toolchain arm64
     -------------------------------------------------------
     android-9-arm toolchain is ready! The bin dir($BIN) is:
      $NDK/std-toolchains/android-9-arm/bin
 
     PATH=$BIN:$PATH. Toolchain commands can be used directly:
      gcc g++ cc c++ clang clang++ ar as ranlib ld strip nm ...
      readelf objdump c++filt elfedit objcopy strings size ...
 
    [android-9-arm] このプロムプトの後ろにコマンドを入力してください。
    ```
    上記でガイド表示完了。gccの場所を確認してみたり、コンパイルしたり試してみます：

    ```
    [android-9-arm] which gcc
    /Users/q/Library/Android/sdk/ndk-bundle/std-toolchains/android-9-arm/bin/gcc
    [android-9-arm] gcc a.c
    ```
    もちろん、後ろにzshなどを追加すれば、bashの代わりにzshにも入れます。

    ```
    android-gcc-toolchain arm64 zsh
    ```

    また、PATHでgccなどオーバライドするの代わりに、次のように、$CC系環境変数、$CC_target系環境変数設定済みの環境にも入れます。


- $CCなどを設定済みの環境にも入れます；

    ```
    android-gcc-toolchain arm64 -c
    ```
    これで自動的に以下の変数が設定されます：

    ```
    export BIN=$NDK/std-toolchains/android-21-arm64/bin
    export CC=$BIN/gcc
    export CXX=$BIN/g++
    export LINK=$BIN/g++
    export AR=$BIN/ar
    export AS=$BIN/as
    export RANLIB=$BIN/ranlib
    export LD=$BIN/ld
    export STRIP=$BIN/strip
    export NM=$BIN/nm
    ```
    $BINは当時のtoolchainのbinフォルダです。

- $CC...の代わりに、$CC_target...などを自動的に設定する環境にも入れます。
    
    ```
    android-gcc-toolchain arm64 -C
    ```
    CC_target CXX_target LINK_target AR_target AS_target RANLIB_target LD_target STRIP_target NM_targetは設定されます。

### 3. 普通のMakefileや、AUTOCONF project(e.g. ffmpeg)は簡単にビルドできます.

- gccリダイレクトされた環境でビルド。例

    Makefileあり：

    ```
    android-gcc-toolchain arm64 make
    ```
    autoconf系：

    ```
    OTHER_FFMPEG_OPTIONS="--disable-everything --disable-doc --enable-protocol=pipe --enable-filter=scale --enable-filter=crop --enable-filter=transpose --enable-demuxer=rawvideo --enable-decoder=rawvideo --enable-muxer=image2 --enable-muxer=image2pipe --enable-muxer=mjpeg --enable-encoder=mjpeg --enable-encoder=png"
    android-gcc-toolchain arm64 <<< "./configure --enable-cross-compile --target-os=linux --arch=arm64 $OTHER_FFMPEG_OPTIONS && make"
    ```

- $CC...設定済の環境でビルドしてもいいです。

    ```
    android-gcc-toolchain arm64 -c make
    ```

### 4. GYPで構成されたプロジェクト(NodeJSやV8)を簡単にビルドできます

幾つか方法があります：

- gccリダイレクトされた環境でビルド

    ```
    android-gcc-toolchain arm64 <<< "./configure --dest-cpu=arm64 --dest-os=android --without-snapshot --without-inspector --without-intl && make"
    ```
    
- $CC`...設定済の環境でビルド.

    ```
    android-gcc-toolchain arm64 -c <<< "./configure --dest-cpu=arm64 --dest-os=android --without-snapshot --without-inspector --without-intl && make"
    ```
        
- `$CC_target`...設定済の環境でビルド.

    ```
    android-gcc-toolchain arm64 -C <<< "./configure --dest-cpu=arm64 --dest-os=android && make"
    ```
    
    このモード、Host側(ローカルマシン)実行ファイルとtarget(Android)側実行ファイル両方を作成する場合に必須です。ちなみに、上記のコマンドはNodeJSの設定エラーで通らないので、詳しくは[build-nodejs-for-android-perfectly](https://github.com/sjitech/build-nodejs-for-android)を参照ください。

### 5. 自動的にAndroid API levelのmin/maxを取得する。

- デフォルトはminですが、`--api max`を指定すれば実際のNDKフォルダ構造からそのarchのmax Android API levelを取得する。例：

    ```
    $ android-gcc-toolchain arm64 --api max
      ...
      android-24-arm64 toolchain is ready! ...
    ```

### 6. 初めてはToolchainを自動的に作成します.

- 既存の$NDK/build/tools/make_standalone_toolchain.pyのオプションを継承しています：
`--arch`,`--api`, `--stl`,`--force`,`--verbose`.

### 7. Toolchain作成はすごく早い！ハードリンクを利用するようにハッキングした。

- This is done by run a modified py file on-the-fly instead of original $NDK/build/tools/make_standalone_toolchain.py. 
- The modified py file replace shutil.copy2 and shutil.copytree with customized copy2 and copytree which use hard link.
- You can specify `--copy` to force use traditional copy mode. 

### 8. コンパイルキャッシュツールCCACHEをサポート

- 繰り返してclean&makeの場合はCCACHEをお勧めです。brew install ccacheあるいはsudo apt-get -y install ccache。そして一回`export USE_CCACHE=1`で済む。 (これでandroid-gcc-toolchainに「CCACHEを利用する」と伝えます）

すごく速くなります！

### 9. Docker image準備できました(Docker image id: `osexp2000/android-gcc-toolchain`)。サイズはお得です！

このDocker imageの中に、NDKは入っていますが、膨大な1.3Gのplatformsフォルダをわずか27Mのplatforms.7zに圧縮した、フォルダを空っぽにした。デフォルト以外のtoolchainを利用しようとする時に、動的にで展開＆削除。

これで、Docker imageのダンロードサイズを約1Gから410Mに縮小しました。(7zありがとう！）

このDocker imageを利用する方法はご自由に、例えば：

現在フォルダ($PWD)下にあるファイルa.ccをコンパイルしたい：

- gccリライレクトされた対話環境で：
    
    ```
    $ docker run -it -v $PWD:/work osexp2000/android-gcc-toolchain arm64
    [android-21-arm64] cd /work && gcc a.cc -o result_exe
    ```

- one-lineで

    ```
    docker run -v $PWD:/work osexp2000/android-gcc-toolchain arm64 g++ /work/a.cc -o /work/result_exe 
    ```

- 複数コンパイルをコマンンド併用

    ```
    docker run -i -v $PWD:/work osexp2000/android-gcc-toolchain arm64 <<< "cd /work && g++ a.cc -o result_exe" 
    ```

- 複数コンパイルをコマンンド併用(Here Document)

    ```
    docker run -i -v $PWD:/work osexp2000/android-gcc-toolchain arm64 <<EOF
    cd /work && g++ a.cc -o result_exe
    EOF
    ```

    *Windows上Docker-machineを利用している場合、MINGW(例えばGit Bash)を利用ください。$PWDは必ずC:¥Usersの配下にしてください(例C:¥Users¥q¥Downloads)。また、環境変数MSYS_NO_PATHCONV=1も必要です。*

### 10. すべてのコンパイル関連のコマンドの実際のパラメータを表示する。これは極便利な機能です。
 
- 環境変数`AGCC_VERBOSE`を1に設定する(`export AGCC_VERBOSE=1`)または、`-v` or `--verbose`オプションを`android-gcc-toolchain`コマンンドに渡せばいい。

    コンパイル関連コマンドとは、gcc g++ cc c++ clang clang++ ar as ranlib ld strip nmです。（当然、android-gcc-toolchainから直接か間接的に起動されたもののみ）

    出力サンプル。

    ```
    $___ ccache '....../arm-linux-androideabi-c++' \
    $___     '-D_GLIBCXX_USE_C99_MATH' \
    $___     '-I../deps/gtest' \
    $___     '-I../deps/gtest/include' \
    $___     '-Wall' \
    $___     '-Wextra' \
    $___     '-Wno-unused-parameter' \
    $___     '-Wno-missing-field-initializers' \
    $___     '-O3' \
    $___     '-fno-omit-frame-pointer' \
    $___     '-fPIE' \
    $___     '-fno-rtti' \
    $___     '-fno-exceptions' \
    $___     '-std=gnu++0x' \
    $___     '-MMD' \
    $___     '-MF' \
    $___     '....../gtest-filepath.o.d.raw' \
    $___     '-c' \
    $___     '-o' \
    $___     '....../gtest-filepath.o' \
    $___     '../deps/gtest/src/gtest-filepath.cc'
    ```
    $___はgrepしやすいためです。どうせ空ですから、コピーペにも問題ありません。

...  実際は、もっとあります。

詳しくはhttps://github.com/sjitech/android-gcc-toolchainへ。

ちなみに、Windows版でのご利用はMINGW(例えば[GIT Bash](https://git-for-windows.github.io/))が必要です。