コマンドラインでストアにビルドをアップロードする

本記事は，[サムザップ Advent Calendar 2019 #1](https://qiita.com/advent-calendar/2019/sumzap1) の12/21の記事です．


# はじめに
株式会社サムザップのとあるプロジェクトでCI環境（jenkins）の構築を担当しています．
Unityプロジェクトのスマホ向けビルド，各種配信プラットフォームへのアップロードなど対応しています．

ストアへのアップロード作業は，頻度が少なくGUIでもなんとかなるので，自動化が先延ばしされがちです．
ただ意外と作業コストがかかったりするので，早めに対応しておくに越したことはないです．
また，属人化を避けるという意味でも，GUIでの設定や操作は減らしたほうが良いでしょう．

この記事では，コマンドラインからAppStoreConnectおよびGooglePlayConsoleへのアップロードする方法例を紹介します．

# 環境
Mac mini（2018）
macOS Mojave 10.14.4
Xcode 11.0

# ipaをAppStoreConnectへアップロードする
コマンドラインでipaをTestFlightへアップロードするには，altoolを使用します．

## 準備
・Xcodeのインストール
・AppStoreConnectにAppを用意
・ipaファイルのビルド
（・XcodeやApplication LoaderなどからAppへのビルドアップロードを試しておく）

## アップロード
shellコードは，下記のような感じになります．

```sh:sample_apple.sh
#!/bin/bash

readonly FILE_PATH="ipaパス"
readonly APPLE_ID="Apple ID"
readonly APPLE_ID_PASSWORD="App用パスワード"

xcrun altool --upload-app -f ${FILE_PATH} -t -ios -u ${APPLE_ID} -p ${APPLE_ID_PASSWORD}
```

altoolのオプションは下記です．

　-f：ファイルパス
　-t：プラットフォーム（osx | ios | appletvos）
　-u：Apple ID
　-p：パスワード．[Apple IDのページから，App用パスワードを取得します](https://support.apple.com/ja-jp/HT204397)

sample_apple.shを実行すると，AppStoreConnectのTestFlightのページにビルドがアップロードされます．
<img width="1269" alt="App_Store_Connect.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212116/639b4f99-0359-aecd-5b91-91b6e063db73.png">

アップロード後，ビルド情報画面から輸出コンプライアンスを提出すれば，TestFlightからインストールできるようになります．
<img width="1424" alt="App_Store_Connect_ビルド情報.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212116/e610aa9d-0d09-db9a-e8ee-4c61598f3741.png">


# apkをGooglePlayConsoleへアップロードする
コマンドラインからapkをGooglePlayConsoleへアップロードするにはいくつか手段がありますが，今回はオーソドックスな[google-api-python-client](https://github.com/googleapis/google-api-python-client)を使った方法を紹介します．

## 準備
・GooglePlayConsoleにアプリを用意
・[サービスアカウントを作成して，EMAILとKEYファイルを取得する](https://developers.google.com/identity/protocols/OAuth2ServiceAccount?hl=ja)
・apkファイルのビルド
・pythonとgoogle-api-python-clientのインストール

```
$ brew install python
$ pip3 install google-api-python-client
$ pip3 install oauth2client
$ pip3 install PyOpenSSL
```
※ google-api-python-clientは，Python3以降がサポートされているので，そちらに準拠します（20191218現在）．

## アップロード
pythonコードは，[ライブラリを利用したサンプル](https://github.com/googlesamples/android-play-publisher-api/tree/master/v3/python)や，[こちらの記事](https://qiita.com/Horie1024/items/0d3d50405f0b4bef1792
)を参考にして，下記のような感じにしてみました．

```python:sample_google.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Lists all the apks for a given app."""

import argparse

from apiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials

import httplib2

SERVICE_ACCOUNT_EMAIL = ('ENTER_YOUR_SERVICE_ACCOUNT_EMAIL_HERE@developer.gserviceaccount.com')
SERVICE_ACCOUNT_KEY = ('ENTER_YOUR_SERVICE_ACCOUNT_KEY_FILE_PATH')

argparser = argparse.ArgumentParser(add_help=False)
argparser.add_argument('package_name',
                       help='The package name. Example: com.android.sample')

argparser.add_argument('apk_file',
                       help='The path to the APK file to upload.')

argparser.add_argument('track_name',
                       help='The track name to upload.')

def main():
  credentials = ServiceAccountCredentials.from_p12_keyfile(
      SERVICE_ACCOUNT_EMAIL,
      SERVICE_ACCOUNT_KEY,
      scopes=['https://www.googleapis.com/auth/androidpublisher'])
  http = httplib2.Http()
  http = credentials.authorize(http)

  service = build('androidpublisher', 'v3', http=http)

  flags = argparser.parse_args()

  package_name = flags.package_name
  apk_file = flags.apk_file
  track_name = flags.track_name

  try:

    edit_request = service.edits().insert(body={}, packageName=package_name)
    result = edit_request.execute()
    edit_id = result['id']

    apk_response = service.edits().apks().upload(
        editId=edit_id,
        packageName=package_name,
        media_body=apk_file).execute()

    print ('Version code {0} has been uploaded'.format(apk_response['versionCode']))

    track_response = service.edits().tracks().update(
        editId=edit_id,
        track=track_name,
        packageName=package_name,
        body={u'releases': [{
            u'versionCodes': [str(apk_response['versionCode'])],
            u'status': u'completed',
        }]}).execute()

    print ('Track {0} is set with releases: {1}'.format(track_response['track'], str(track_response['releases'])))

    commit_request = service.edits().commit(
        editId=edit_id,
        packageName=package_name).execute()

    print ('Edit {0} has been committed'.format(commit_request['id']))

  except Exception as e:
    print (e)

if __name__ == '__main__':
  main()
```

Pythonを実行するshellコードは下記のような感じになります．

```sh:sample_google.sh
#!/bin/bash

readonly BUNDLE_ID="バンドルID"
readonly FILE_PATH="apkパス"
readonly TRACK_NAME="アップロードするトラック名"

python3 sample_goole.py ${BUNDLE_ID} ${FILE_PATH} ${TRACK_NAME}
```

sample_google.shを実行すると，GooglePlayConsoleの指定のトラックにapkがアップロードされます．
GooglePlayConsoleの方は，テスターを設定しておけば，これでストアからインストールできるようになります．
<img width="1420" alt="内部テスト版トラック.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212116/ea47fd86-5c0b-80db-0187-62792a811465.png">

# まとめ
コマンドラインでビルドをストアへアップロードする手法を紹介しました．
必要最低限の機能ですが，リリース前の開発段階であれば事足りるかと思います．

明日は @sumchun さんの記事です．