Docker for Mac でコンテナからホスト側に接続したAndroidデバイスにadbする方法

[Appium](http://appium.io/) のテストを作っているのだけど、Docker化したくなってやっていたときに結構ハマったのでメモ。

完全に自分用メモ

## 前提

* Docker for Macを動かしているOSは Mojave
* コンテナはalpineベース

## 前提の前提：ADBの仕組みをざっくり

https://blog.csdn.net/xgbing/article/details/52096880 あたりに説明があるのを絵で書くと、

![Slice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/78e396cf-aff2-80a4-4c60-01261be089f1.png)

* adbはサーバーとクライアントがあって
* サーバープロセスがUSBドライバを通じて
* 実機のADBデーモン(adbd)と対話する

みたいなイメージ。

### adbクライアントとadbサーバーは別PCでも大丈夫

ポイントとしては、クライアントとサーバー間は単純なTCP通信なので、

![Slice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/0b41df2a-0330-4318-0e58-3dd4550a2b97.png)

adbサーバーが立っているマシンとadbクライアントを操作するマシンは別であってもよい。

* adbクライアントのlocalhost:5037 -> adbサーバのlocalhost:5037 のポートフォワーディングをする
* adbの `-H` オプションを使う

のいずれかの方法でTCP通信ができるようにしてやればよい。
（ちなみに `-H` オプションは今回のお試しをやるまで私は知らなかった :sweat_smile: ）

## そもそもどーやってadbを入れたらいいのか

alpineにadbコマンドを使えるようにする方法は２つある。

### android-toolsパッケージ

https://github.com/sorccu/docker-adb/issues/9 をみると、

```
apk --update-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ add android-tools
```

すればいけるよ！とある。

たしかにこれでいける。いけるんだけど、後述する理由で、採用には至らなかった。

### 頑張ってダウンロードしてインストールする

https://qiita.com/kichinaga/items/66872432747e76d72af7

最終的にはこれでいけた。理由は後述。

## adbをインストール

`apk --update-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ add android-tools` でやってみる。

## adb devices で何も出ない

そりゃそうだ。コンテナ（alpine linux）には何のデバイスも刺さって＆ポートフォワーディングもしていないんだから。

adbクライアントが動いているコンテナから、adbサーバーが動いているホスト（Mac）まで5037のポートフォワードができればいいのだが、Dockerではコンテナ→ホストのポートフォワーディングはできない。さて、どうしたものか・・・（これが今回の話のきっかけだった）

## /dev/bus/usb をポート指定すればよい？

`--privileged -v /dev/bus/usb:/dev/bus/usb` してやれば、コンテナからホストOSのUSBデバイスに直アクセスできるよ♪
https://codeday.me/jp/qa/20190301/305118.html

うん。ホストがLinuxならね。

Docker for Macだと無理だこれ・・・。

## adbの `-H` オプションを使うと良い！

adbクライアントはDockerコンテナ側で動かし、adbサーバーはホスト側のを使う作戦。

```
$ adb
Android Debug Bridge version 1.0.41
Version 29.0.1-5644136
Installed as /Users/yusuke-iwaki/Library/Android/sdk/platform-tools/adb

global options:
 -a         listen on all network interfaces, not just localhost
 -d         use USB device (error if multiple devices connected)
 -e         use TCP/IP device (error if multiple TCP/IP devices available)
 -s SERIAL  use device with given serial (overrides $ANDROID_SERIAL)
 -t ID      use device with given transport id
 -H         name of adb server host [default=localhost]
 -P         port of adb server [default=5037]
 -L SOCKET  listen on given socket for adb server [default=tcp:localhost:5037]

```

`-H         name of adb server host [default=localhost]` これだ！

必然的に、Mac側にもAndroid SDKを入れないといけないわけだが、Androidアプリ開発している人なら入っているでしょう（え？ｗ

で、やってみると・・・

```
/usr/src/app # adb -H docker.for.mac.localhost devices
List of devices attached
adb server version (41) doesn't match this client (40); killing...
ADB server didn't ACK
Full server startup log: /tmp/adb.0.log
Server had pid: 20
--- adb starting (pid 20) ---
adb I 07-17 04:52:43    20    20 main.cpp:56] Android Debug Bridge version 1.0.40
adb I 07-17 04:52:43    20    20 main.cpp:56] Version 9.0.0_r33
adb I 07-17 04:52:43    20    20 main.cpp:56] Installed as /usr/bin/adb
adb I 07-17 04:52:43    20    20 main.cpp:56] 
error: could not install *smartsocket* listener: listening on specified hostname currently unsupported

* failed to start daemon
error: cannot connect to daemon
```

Ω＼ζ°)ﾁｰﾝ

TCPレベルでの通信には成功したのだが、
**`apk --update-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ add android-tools` でインストールされるadbはバージョンが古い！！** ので、このままではadbプロトコルでの通信ができない。

## adbを入れ直す

https://qiita.com/kichinaga/items/66872432747e76d72af7 を頼りに、愚直にSDKをzipダウンロードしてきてPATHとか通して、ANDROID_HOMEとか設定して、

みたいなことをやる。Dockerfileの断片を書くと

```
# Android SDK
RUN apk add --no-cache openjdk8-jre wget unzip
RUN wget https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip && unzip -d /usr/local/android sdk-tools-linux-4333796.zip && rm sdk-tools-linux-4333796.zip
ENV JAVA_HOME /usr/lib/jvm/default-jvm
ENV ANDROID_HOME /usr/local/android
ENV PATH /usr/local/android/tools:/usr/local/android/tools/bin:/usr/local/android/platform-tools:/usr/lib/jvm/default-jvm/bin:$PATH
RUN mkdir -p ~/.android && touch ~/.android/repositories.cfg
RUN yes | sdkmanager --licenses
RUN sdkmanager "tools" "platform-tools"

# add glibc (see: https://hub.docker.com/r/frolvlad/alpine-glibc/dockerfile)
RUN ALPINE_GLIBC_BASE_URL="https://github.com/sgerrand/alpine-pkg-glibc/releases/download" && \
    ALPINE_GLIBC_PACKAGE_VERSION="2.29-r0" && \
    ALPINE_GLIBC_BASE_PACKAGE_FILENAME="glibc-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \
    ALPINE_GLIBC_BIN_PACKAGE_FILENAME="glibc-bin-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \
    ALPINE_GLIBC_I18N_PACKAGE_FILENAME="glibc-i18n-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \
    apk add --no-cache --virtual=.build-dependencies wget ca-certificates && \
    echo \
        "-----BEGIN PUBLIC KEY-----\
        MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApZ2u1KJKUu/fW4A25y9m\
        y70AGEa/J3Wi5ibNVGNn1gT1r0VfgeWd0pUybS4UmcHdiNzxJPgoWQhV2SSW1JYu\
        tOqKZF5QSN6X937PTUpNBjUvLtTQ1ve1fp39uf/lEXPpFpOPL88LKnDBgbh7wkCp\
        m2KzLVGChf83MS0ShL6G9EQIAUxLm99VpgRjwqTQ/KfzGtpke1wqws4au0Ab4qPY\
        KXvMLSPLUp7cfulWvhmZSegr5AdhNw5KNizPqCJT8ZrGvgHypXyiFvvAH5YRtSsc\
        Zvo9GI2e2MaZyo9/lvb+LbLEJZKEQckqRj4P26gmASrZEPStwc+yqy1ShHLA0j6m\
        1QIDAQAB\
        -----END PUBLIC KEY-----" | sed 's/   */\n/g' > "/etc/apk/keys/sgerrand.rsa.pub" && \
    wget \
        "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_I18N_PACKAGE_FILENAME" && \
    apk add --no-cache \
        "$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_I18N_PACKAGE_FILENAME" && \
    \
    rm "/etc/apk/keys/sgerrand.rsa.pub" && \
    /usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 "$LANG" || true && \
    echo "export LANG=$LANG" > /etc/profile.d/locale.sh && \
    \
    apk del glibc-i18n && \
    \
    rm "/root/.wget-hsts" && \
    apk del .build-dependencies && \
    rm \
        "$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_I18N_PACKAGE_FILENAME"
```

こんなかんじ。

glibcのところをちゃんとやらないと、

```
/usr/src/app # adb -H docker.for.mac.localhost devices
/bin/sh: adb: not found
/usr/src/app # which adb
/usr/local/android/platform-tools/adb
/usr/src/app # ldd /usr/local/android/platform-tools/adb
	/lib64/ld-linux-x86-64.so.2 (0x7f8010899000)
	libdl.so.2 => /lib64/ld-linux-x86-64.so.2 (0x7f8010899000)
	libpthread.so.0 => /lib64/ld-linux-x86-64.so.2 (0x7f8010899000)
	libm.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7f8010899000)
	librt.so.1 => /lib64/ld-linux-x86-64.so.2 (0x7f8010899000)
	libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x7f801063f000)
	libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7f8010899000)
Error relocating /usr/local/android/platform-tools/adb: __vfprintf_chk: symbol not found
Error relocating /usr/local/android/platform-tools/adb: __fprintf_chk: symbol not found
Error relocating /usr/local/android/platform-tools/adb: __vsnprintf_chk: symbol not found
Error relocating /usr/local/android/platform-tools/adb: group_member: symbol not found
Error relocating /usr/local/android/platform-tools/adb: __syslog_chk: symbol not found
Error relocating /usr/local/android/platform-tools/adb: __fdelt_chk: symbol not found
Error relocating /usr/local/android/platform-tools/adb: __snprintf_chk: symbol not found
Error relocating /usr/local/android/platform-tools/adb: __strdup: symbol not found
Error relocating /usr/local/android/platform-tools/adb: __asprintf_chk: symbol not found
Error relocating /usr/local/android/platform-tools/adb: __strcat_chk: symbol not found
Error relocating /usr/local/android/platform-tools/adb: __vasprintf_chk: symbol not found
Error relocating /usr/local/android/platform-tools/adb: strtoll_l: symbol not found
Error relocating /usr/local/android/platform-tools/adb: strtoull_l: symbol not found
```

こんな感じで、adbコマンドが有るのに not foundとか言われて、結構ハマることになる。

## 気を取り直して・・・

そんなわけで、adbを最新のものにしたら

```
/usr/src/app # adb -H docker.for.mac.localhost devices
List of devices attached
emulator-5554	device
```

ｷﾀ━━━━(ﾟ∀ﾟ)━━━━!!