8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

グレンジAdvent Calendar 2023

Day 1

Xcode CloudによるアプリバイナリのCloud Build検証

Last updated at Posted at 2023-11-30

この投稿は「グレンジ 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生成の代替手段となり得るか確認することを目的としました。

検証の流れ

以下の流れに沿って、検証を行いました。

  1. 全体の流れを洗い出し&見積もり
  2. 既存事例の調査
  3. 類似サービスの機能比較
  4. 検証サービス決定
  5. ビルド検証
  6. ビルド性能計測

※検証対象のサービスとして次の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に接続します。

次のように表示されたら、GitHubとの接続は完了です。

Nextをクリックします。

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はマスクしています。

workflow.json
{
  "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などはマスクしています。

ci_scripts/ci_post_clone.sh
#!/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のホスト名はマスクしています。

ci_scripts/ci_post_xcodebuild.sh
#!/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で管理できる

参考

脚注

  1. 業務時間のうち最大20%を使って、業務とは直接関係のない内容に関して技術的な挑戦ができるグレンジのエンジニア向け制度

  2. https://wojciechkulik.pl/xcode/xcode-cloud-review-is-it-ready-for-commercial-projects#xcode-cloud-m1

  3. 料金体系がサブスクリプションの場合は固定料金です

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?