この投稿は「グレンジ Advent Calendar 2023」の 1日目の記事です。
こんにちは、株式会社グレンジでサーバーサイドエンジニアをしています。
島田(@konnyaku256)です。
先日、グレンジの「20%ルール 1」という制度を活用して「アプリバイナリのCloud Build検証」を行いました。
この投稿では、その手順と結果をまとめます。
検証の目的
グレンジではアプリバイナリ(apk、ipa)のビルド方法として次の2種類が運用されています。
- 物理macOSマシンによるビルド
- 運用プロジェクトのapk生成
- 運用プロジェクトのipa生成
- 新規プロジェクトのipa生成
- GitHub Actions(Cycloud-hosted runner for macOS) によるビルド
- 新規プロジェクトのapk生成
- 新規プロジェクトのxcodeproj生成
※Cycloud-hosted runner については、以下の記事で詳しく紹介されています。
https://developers.cyberagent.co.jp/blog/archives/35729/
また、アプリバイナリ(apk、ipa)の社内向け配布環境としてEMLauncherというソフトウェアをセルフホストして運用しています。
上記の環境を構築する際に、「GitHub Actions(Cycloud-hosted runner for macOS) 」を用いたipa生成の検証が行われました。
検証結果として、ipaの生成やビルド性能については、実用レベルであるという結論を得られましたが、Actionsを利用してスポットでビルドを行うには費用面での課題がありました。
そこで、新たに「Xcode Cloudによるビルド」などに代表される、クラウド上のmacOSマシンを使ったビルドについて検証し、費用も含めてipa生成の代替手段となり得るか確認することを目的としました。
検証の流れ
以下の流れに沿って、検証を行いました。
- 全体の流れを洗い出し&見積もり
- 既存事例の調査
- 類似サービスの機能比較
- 検証サービス決定
- ビルド検証
- ビルド性能計測
※検証対象のサービスとして次の3つを決定しましたが、現時点ではXcode Cloudのみビルド検証とビルド性能計測が実施済みとなっています。
- Xcode Cloud
- Unity Build Automation
- Codemagic
ワークフローの作成手順
事前準備
Xcode Cloudを使うには次の条件を満たす必要があります。
- Xcode 13.4.1以降
- Apple Developer Programのメンバーシップに登録済みのApple ID
- Account HolderまたはAdminまたはApp Managerのロール(App Store Connectにアプリを新規作成する場合)
Apple Developer Programのメンバーシップに登録済みのApple IDがない場合は登録します。
xcodeprojの設定確認
Signing & Capabilities > SIgning > Bundle Identifierを確認します。
今回は検証のため、個人Teamを使用したため、Bundle Identifierを次のように変更しました。
Build Settings > User-Defined > PRODUCT_NAME_APPを確認します。
今回は検証のため、個人Teamを使用したため、PRODUCT_NAME_APPを次のように変更しました。
XcodeのアカウントにApple IDを追加
Xcode > Settingsから設定ウィンドウを開きます。
設定ウィンドウのAccountsタブを開き、Apple Developer Programのメンバーシップに登録済みのApple IDを追加します。
新規ワークフローの作成
Product > Xcode Cloud > Create Workflow...から新規作成ウィンドウを開きます。
Xcode Cloudのウェルカムページが表示されたら、Nextをクリックします。
GitHubのリポジトリが表示されたら、Grant Access...をクリックして、Xcode CloudとGitHubとの接続を開始します。
以下、ガイドに沿ってXcode CloudをGitHubに接続します。
Select a Productウィンドウが表示されたら、Teamを選択したあと、Productを選択して、Nextをクリックします。
CompleteをクリックしてApp Store Connectに新規アプリを作成します。
(App Store Connectの既存アプリにXcode Cloudを設定する場合、この手順は不要です。アプリは一度作成すると削除できないため注意します。)
次のような画面が表示されたら、Xcode Cloudの初回設定は完了です。
Start Buildをクリックして、初回ビルドを実行します。
ワークフローのステータスはGitHubのPull Request上に反映されます。
初回設定が完了すると、Xcode上からワークフローを操作できるようになります。
App Store Connectの該当アプリのXcode Cloudタブからも同様にワークフローを操作できます。
成果物
今回の検証では、以下のワークフローとカスタムビルドスクリプトを作成しました。
ワークフローの設定内容(App Store Connet API の Read Xcode Cloud Workflow Information のレスポンスボディより)
※idはマスクしています。
{
"data" : {
"type" : "ciWorkflows",
"id" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"attributes" : {
"name" : "Default",
"description" : "",
"branchStartCondition" : {
"source" : {
"isAllMatch" : false,
"patterns" : [ {
"pattern" : "develop",
"isPrefix" : false
}, {
"pattern" : "feature/37",
"isPrefix" : false
} ]
},
"filesAndFoldersRule" : null,
"autoCancel" : true
},
"tagStartCondition" : null,
"pullRequestStartCondition" : null,
"scheduledStartCondition" : null,
"manualBranchStartCondition" : null,
"manualTagStartCondition" : null,
"manualPullRequestStartCondition" : null,
"actions" : [ {
"name" : "Archive - iOS",
"actionType" : "ARCHIVE",
"destination" : null,
"buildDistributionAudience" : null,
"testConfiguration" : null,
"scheme" : "Unity-iPhone",
"platform" : "IOS",
"isRequiredToPass" : true
} ],
"isEnabled" : true,
"isLockedForEditing" : false,
"clean" : false,
"containerFilePath" : "unity-client/Sample/build/sample/Unity-iPhone.xcodeproj",
"lastModifiedDate" : "2023-10-24T07:30:29.685Z"
},
"relationships" : {
"repository" : {
"links" : {
"self" : "https://api.appstoreconnect.apple.com/v1/ciWorkflows/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/relationships/repository",
"related" : "https://api.appstoreconnect.apple.com/v1/ciWorkflows/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/repository"
}
},
"buildRuns" : {
"links" : {
"self" : "https://api.appstoreconnect.apple.com/v1/ciWorkflows/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/relationships/buildRuns",
"related" : "https://api.appstoreconnect.apple.com/v1/ciWorkflows/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/buildRuns"
}
}
},
"links" : {
"self" : "https://api.appstoreconnect.apple.com/v1/ciWorkflows/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
},
"links" : {
"self" : "https://api.appstoreconnect.apple.com/v1/ciWorkflows/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}
※環境変数(シークレット含む)については「環境変数」のセクションで補足しています。
※BUCKET_NAMEなどはマスクしています。
#!/bin/bash
BUCKET_NAME="xxxxx"
OBJECT_NAME="xxxxx.tar.gz"
# JWTのヘッダーをBase64エンコード
HEADER=$(echo -n '{"alg":"RS256","typ":"JWT"}' | openssl base64 -e -A | sed 's/+/-/g; s/\//_/g; s/=//g')
# 現在のUNIXタイムスタンプと1時間後のUNIXタイムスタンプを取得
NOW=$(date +%s)
EXP=$(($NOW + 3600))
# JWTのペイロードをBase64エンコード
PAYLOAD=$(echo -n "{\"iss\":\"$GC_SA_EMAIL\",\"scope\":\"https://www.googleapis.com/auth/devstorage.read_only\",\"aud\":\"$GC_TOKEN_URI\",\"exp\":$EXP,\"iat\":$NOW}" | openssl base64 -e -A | sed 's/+/-/g; s/\//_/g; s/=//g')
# プライベートキーを一時ファイルに出力
echo -e $GC_SA_PRIVATE_KEY > gc_sa_private_key.pem
# 署名を作成
SIGNATURE=$(echo -n "$HEADER.$PAYLOAD" | openssl dgst -sha256 -sign gc_sa_private_key.pem -binary | openssl base64 -e -A | sed 's/+/-/g; s/\//_/g; s/=//g')
# 一時ファイルを削除
rm gc_sa_private_key.pem
# JWTを作成
JWT="$HEADER.$PAYLOAD.$SIGNATURE"
# JWTを使用してアクセストークンを取得
ACCESS_TOKEN=$(curl -s -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$JWT" $GC_TOKEN_URI | grep -o '"access_token":"[^"]*"' | cut -d':' -f2 | tr -d '"')
# Google Cloud Storageからxcodeprojファイルをダウンロード
curl -o "$CI_PRIMARY_REPOSITORY_PATH/$OBJECT_NAME" -H "Authorization: Bearer $ACCESS_TOKEN" "https://storage.googleapis.com/storage/v1/b/$BUCKET_NAME/o/$(echo $OBJECT_NAME | sed 's/\//%2F/g')?alt=media"
# 圧縮ファイルを解凍して配置
tar -xzf $CI_PRIMARY_REPOSITORY_PATH/$OBJECT_NAME -C $CI_PRIMARY_REPOSITORY_PATH
※EMLauncherのホスト名はマスクしています。
#!/bin/bash
ipa="$CI_APP_STORE_SIGNED_APP_PATH/$CI_PRODUCT.ipa"
title=$(echo "$CI_BUILD_ID")
description=$(echo "Git SHA: $CI_COMMIT")
# upload ipa
curl --retry 2 --digest -u $EMLAUNCHER_USER:$EMLAUNCHER_PASSWORD -s https://example.com/api/upload -F api_key=$EMLAUNCHER_API_KEY -F file=@"$ipa" -F title="$title" -F description="$description"
上記の構成でワークフローのビルドを実行して、Cloud Storageからxcodeprojをダウンロードし、xcodeprojからipaを生成し、ipaをEMLauncherにアップロードするところまで確認できました。
ビルド時間
Xcode Cloudのワークフロー全体の実行時間の計測結果を表にまとめました。
サービス名 | macOS | Xcode | マシンスペック | iOSビルド時間(最小) | iOSビルド時間(最大) | iOSビルド時間(中央) | iOSビルド時間(平均) |
---|---|---|---|---|---|---|---|
Xcode Cloud | macOS Sonoma 14.1 RC | Xcode 15.0.1 | Intel 2GHz 4 Core / 16GB 2 | 0:02:45 | 0:03:08 | 0:02:53 | 0:02:53 |
Xcode Cloud 計測データ詳細
iOSビルド時間(秒)1回目 | 2回目 | 3回目 | 4回目 | 5回目 |
---|---|---|---|---|
168 | 188 | 173 | 175 | 165 |
検証結果
クラウド上のmacOSマシンを使ったビルドのうち、「Xcode Cloudによるビルド」について検証した結果、機能面でipa生成の手段となり得ることがわかりました。
また、「GitHub Actions(Cycloud-hosted runner for macOS) によるビルド」で得られていた検証結果と比較することで、性能面でCycloud-hosted runner for macOSよりビルド時間が短いこともわかりました。
補足
App Store Connect API ciWorkflows
Xcode CloudのワークフローはApp Store Connect APIを使うことで操作できます。
今回の検証ではAPIによるワークフローの作成・更新は検証していません。
カスタムビルドスクリプト
Xcode Cloudのワークフローではカスタムビルドスクリプト(シェルスクリプト)が使用できます。
スクリプトは実行タイミングごとに次の3種類があります。
- ci_post_clone.sh
- ci_pre_xcodebuild.sh
- ci_post_xcodebuild.sh
今回の検証では次の2つを作成しました。
- ci_post_clone.sh
- ci_post_xcodebuild.sh
ci_post_clone.shはUnityビルド済みのxcodeprojファイルのダウンロードのため、ci_post_xcodebuild.shはipaファイルのアップロードのために使いました。
カスタムビルドスクリプトの導入手順
カスタムビルドスクリプトはxcodeprojまたはxcodeworkspace以下のci_scriptsディレクトリに配置します。
スクリプトのファイルを作成したら、次のようにコマンドを実行して、スクリプトに実行権限を付与しておきます。
実行権限を付与していないと、ワークフローの実行時に実行権限がなくてエラーになってしまいます。
chmod +x ci_post_clone.sh
今回の検証では、Unityプロジェクトを前提としていてxcodeprojはgit管理下になかったため、ci_scripts以下のみgit管理して、ワークフローの実行時にスクリプトがクローンされるようにしました。
環境変数
Xcode Cloudのカスタムビルドスクリプト内では、環境変数が使用できます。
環境変数には標準で設定されるものと、追加で設定できるものがあります。
今回の検証で使用した環境変数は次の通りです。
標準
- CI_PRIMARY_REPOSITORY_PATH
- CI_APP_STORE_SIGNED_APP_PATH
- CI_PRODUCT
- CI_BUILD_ID
- CI_COMMIT
追加
- GC_SA_EMAIL
- GC_TOKEN_URI
- GC_SA_PRIVATE_KEY
- EMLAUNCHER_USER
- EMLAUNCHER_PASSWORD
- EMLAUNCHER_API_KEY
GC_SA_EMAIL、GC_TOKEN_URI、GC_SA_PRIVATE_KEYはそれぞれGoogle CloudのサービスアカウントのJSONキーに含まれているclient_email、token_uri、private_keyを設定しました。設定したサービスアカウントはxcodeprojが配置されているCloud Storageの読み取りが許可されています。
また、EMLAUNCHER_USER、EMLAUNCHER_PASSWORD、EMLAUNCHER_API_KEYはそれぞれEMLauncherのDigest認証のためのID、パスワード、アプリ個別のAPI Keyを設定しました。
シークレット
なお、GC_SA_PRIVATE_KEY、EMLAUNCHER_USER、EMLAUNCHER_PASSWORD、EMLAUNCHER_API_KEYは秘匿情報なので、シークレットのチェックをオンにして設定しました。
シークレットのチェックをオンにしないと、ワークフローのログなどに中身が残ってしまうため、注意が必要です。
依存関係
Xcode Cloudのワークフロー実行環境にはHomebrewがプリインストールされているので、カスタムビルドスクリプトから使用することができます。
そのため、HomebrewでCocoaPodsやCarthageなどのサードパーティーの依存関係マネージャーをインストールして使うこともできます。
今回の検証では、検証の過程でHomebrewによるgcloud CLIのインストールを試しましたが、Pythonの依存関係によるエラーとPythonをインストールする際にsudo権限を要求するエラーが発生したため、断念しました。
そのため、Homebrewによるツールのインストールは検証していません。
類似サービスとの比較
Xcode Cloudと類似サービスの比較結果を表にまとめました。
Xcode Cloud以外については、調査不足・未検証な部分が含まれます。
機能比較
サービス名 | 料金体系 | マシンスペック | Androidビルド | iOSビルド | Xcode Version指定 | Cocoa Pods | 並列実行 | ワークフローのコード管理 | Google Play Console UP | AppStore Connect UP | Privateリポジトリ連携 | GitHub Actions連携 | Slack連携 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Xcode Cloud | サブスクリプション | Intel 2GHz 4 Core / 16GB | ✕ | ◯ | ◯ | ◯ | ◯ | △ | ✕ | ◯ | ◯ | △ | ◯ |
Unity Build Automation | 従量課金制 | ? | ◯ | ◯ | ◯ | ◯ | ◯ | ✕ | △ | △ | ◯ | △ | ◯ |
Codemagic | 従量課金制orサブスクリプション | M1 3.2GHz 4 Core / 8GB, Intel 2.3GHz 4 Core / 8GB | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | △ | ◯ |
Appcircle | サブスクリプション | M1, Intel | ◯ | ◯ | ◯ | ◯ | ◯ | ✕ | ◯ | ◯ | ◯ | △ | ◯ |
Visual Studio App Center | サブスクリプション | ? | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | △ | ◯ |
Bitrise | サブスクリプション | M1 3.2GHz 4 Core / 6GB, Intel 3.2GHz 4 vCPU 19GB | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | △ | ◯ |
CircleCI/GameCI | 従量課金制andサブスクリプション | M1, Intel | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ | △ | ◯ |
料金体系
調査結果のうち、一部を抜粋しています。
内容は2023年10月27日時点のものです。最新の情報は各自で参照ください。
サービス名 | プラン | 内訳 | compute時間料金[USD]/分 | サブスクリプション料金[USD]/月 3 |
---|---|---|---|---|
Xcode Cloud | 25時間/月 | 2023/12まで | 0.00000000 | |
25時間/月 | 2024/1以降 | 0.00999333 | 14.99 | |
100時間/月 | 0.00833167 | 49.99 | ||
250時間/月 | 0.00666600 | 99.99 | ||
1000時間/月 | 0.00666650 | 399.99 | ||
Unity Build Automation | 無料利用枠 | Windows Compute時間(200分) | 0.00000000 | |
標準価格 | Mac Compute時間 | 0.07000000 | ||
標準価格 | Windows Compute時間 | 0.02000000 | ||
Codemagic | 個人向け | Mac M1 Compute時間 | 0.00000000 | |
チーム向け(従量課金制) | Mac M1 / Intel-based Compute時間 | 0.09500000 | ||
チーム向け(従量課金制) | Linux / Windows Compute時間 | 0.04500000 | ||
チーム向け(サブスクリプション) | Mac M1 / Intel-based / Linux / Windows Compute時間 | 0.00769676 | 332.50 | |
エンタープライズ向け | 0.02314815 | 1000.00 | ||
Appcircle | 無料利用枠 | 0.00000000 | ||
開発者 | 0.00008681 | 49.00 | ||
プロフェッショナル | 0.00031829 | 199.00 | ||
Visual Studio App Center | 無料利用枠 | ビルド | 0.00000000 | |
有料プラン | ビルド | 0.00092593 | 40.00 | |
Bitrise | Hobby(無料利用枠) | Mac M1 | 0.00000000 | |
Teams | Mac M1 | 0.00126000 | 31.50 | |
Velocity | Mac M1 Max | 0.00034722 | 208.33 | |
CircleCI | Free(無料利用枠) | 0.00000000 | ||
Performance | 0.00250000 | 15.00 | ||
Scale | 0.04629630 | 2000.00 |
Xcode Cloudと検証予定サービス(Unity Build Automation、Codemagic)との比較
Unity Build AutomationとCodemagicについては未検証のため、予想です。
Xcode Cloud
- サブスクリプション
- 無料利用枠あり(2023/12まで)
- xcodeprojのビルドを前提としているため、Unityのビルドには対応していない
- Unityなどのアプリをビルドしたい場合、今回検証した手法のように別環境でビルドしたxcodeprojをXcode Cloudに持ってくる必要がある
Unity Build Automation
- 従量課金制
- 無料利用枠あり
- Unity公式のサービスなので、Unityとの親和性が高い
- Unityのビルドからアプリバイナリ(apk、ipa)のビルドを一貫して実行できる
Codemagic
- 従量課金制またはサブスクリプション
- 無料利用枠あり
- Unityのビルドからアプリバイナリ(apk、ipa)のビルドを一貫して実行できる
- ワークフローをyamlで管理できる
参考
- Xcode Cloud は銀の弾丸になるのか - Qiita
- Xcode Cloud の導入前に考慮すべき4つのポイント / GO TechTalk 18 - Speaker Deck
- Xcode Cloudを使ってみた - FLINTERS Engineer's Blog
- Xcode Cloudを個人開発で利用した感想 2022年11月版
- Xcode Cloud でプロジェクトをビルドしてみる - Qiita
- Xcode Cloud を試してみたけど、まだダメだった
脚注
-
業務時間のうち最大20%を使って、業務とは直接関係のない内容に関して技術的な挑戦ができるグレンジのエンジニア向け制度 ↩
-
https://wojciechkulik.pl/xcode/xcode-cloud-review-is-it-ready-for-commercial-projects#xcode-cloud-m1 ↩
-
料金体系がサブスクリプションの場合は固定料金です ↩