every Tech Blog

株式会社エブリーのTech Blogです。

ヘルシカにおけるXcode Cloudの実行時間を50%削減した話

はじめに

こんにちは。開発部でiOSエンジニアをしている野口です。

ヘルシカのiOSアプリではXcode Cloudを使用して開発環境・本番環境への配布を行っています。本記事では、配布にかかっていた実行時間を約50%削減した方法を紹介します。

背景と課題

削減前のXcode Cloudの実行時間は約30分かかっていました。これを削減できれば、開発スピードの向上やQAから修正へのサイクルが回しやすくなり、品質の向上が期待できると考えました。

各ステップの実行時間はApp Store Connectのダッシュボードから確認できます。調査したところ、ci_post_clone.shの実行が全体の約62%を占めており、ここがボトルネックであることがわかりました。

ステップ 時間 割合
Run ci_post_clone.sh script 16分46.9秒 62.34%
Run xcodebuild archive 6分17.9秒 23.39%
Resolve package dependencies 2分2秒 7.55%
その他(環境設定・取得・Export・後処理など) 1分48.5秒 6.72%
Build Archive 合計 26分55.3秒 100.00%
Prepare Build for App Store Connect 44.8秒
総合計 約27分40秒

※主要なステップ以外は「その他」にまとめています。

原因の分析

ci_post_clone.shとは

ci_post_clone.shは、Xcode Cloudがリポジトリをクローンした直後に自動で実行されるシェルスクリプトです。ビルドに必要な追加ツールのインストールや設定ファイルの書き換えなど、ビルド前の準備処理を記述します。以下の画像のPost-cloneと記述されている箇所でci_post_clone.shが動きます。

引用元:

実行していた処理

ci_post_clone.shでは、以下の処理を行っていました。

# ci_post_clone.sh
#!/bin/sh

brew install mint
mint bootstrap -m ../Mintfile --overwrite y
# Mintfile
realm/SwiftLint@0.52.4
mono0926/LicensePlist@3.24.11
kiliankoe/swift-outdated@0.8.0
nicklockwood/SwiftFormat@0.53.3

Mintを使用して、以下のツールをインストールしていました。

  • SwiftLint: コードの静的解析
  • SwiftFormat: コードのフォーマット
  • LicensePlist: ライセンスの管理
  • swift-outdated: 依存関係の更新確認

改善のアプローチ

これらのツールはいずれも開発時に使用するものであり、Xcode Cloudでの配布時には実行する必要がありません。Xcode Cloudの役割はCDであり、配布作業のみ行えれば十分だからです。

そこで、「不要なツールのインストールをやめる」ことで実行時間を削減する方針としました。

キャッシュによる高速化を採用しなかった理由

「Mintのインストールをキャッシュすれば速くなるのでは?」と考えるかもしれませんが、Xcode Cloudのキャッシュ機能には制約があります。

GitHub ActionsやCircleCIなどのCI/CDツールでは、任意のパスやフォルダを指定してキャッシュできます。例えば~/.mintをキャッシュしておけば、2回目以降のインストールを高速化できます。

一方、Xcode Cloudのキャッシュ対象はDerivedData配下のみに限定されています。具体的にキャッシュされるのは以下の2つです。

  • Xcodeのビルドキャッシュ(インクリメンタルビルド用の中間成果物)
  • Swift Package Managerで取得・ビルドされたライブラリ

任意のパスを指定する機能は提供されていないため、Homebrew経由でインストールしたMintなどはキャッシュの対象外です。つまり、ci_post_clone.shでのインストール処理は毎回フルで実行されることになります。

To reduce the amount of time it takes to perform a build, Xcode Cloud stores each build's derived data and other cached information for reuse in a secure and private way.

Xcode Cloud workflow reference | Apple Developer Documentation

このキャッシュの制約があるからこそ、キャッシュで高速化するのではなく、そもそも不要な処理をXcode Cloudから取り除くアプローチが有効になります。

具体的には、ci_post_clone.shを削除してXcode CloudでのMintインストール自体をやめ、各ツールの実行はGitHub Actionsやローカル環境に移行しました。

各ツールの対応内容

SwiftLint・SwiftFormat

ローカル環境でのビルド(Build Phase)とGitHub Actionsでのみ実行するようにしました。

GitHub Actionsの設定

# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches:
      - develop

jobs:
  swift_format:
    name: SwiftFormat
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - name: Cache Mint packages
        uses: actions/cache@v4
        with:
          path: ~/.mint
          key: ${{ runner.os }}-mint-${{ hashFiles('Mintfile') }}
          restore-keys: |
            ${{ runner.os }}-mint-

      - name: Install Mint
        run: brew install mint

      - name: Run SwiftFormat lint
        run: mint run swiftformat healthcare Packages --lint

  swift_lint:
    name: SwiftLint
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - name: Cache Mint packages
        uses: actions/cache@v4
        with:
          path: ~/.mint
          key: ${{ runner.os }}-mint-${{ hashFiles('Mintfile') }}
          restore-keys: |
            ${{ runner.os }}-mint-

      - name: Install Mint
        run: brew install mint

      - name: Run SwiftLint
        run: mint run swiftlint lint

ローカル環境(Xcode Build Phase)の設定

XcodeのRun Script(Build Phase)でSwiftFormatとSwiftLintを実行します。Run ScriptはXcode Cloud上のビルドでも実行されるため、環境変数CITRUEのときはスキップするようにしています(Xcode CloudではCI=TRUEが設定されます)。

if [ "$CI" = "TRUE" ]; then
  exit 0
fi

if [ -d "/opt/homebrew/bin" ]; then
  export PATH="$PATH:/opt/homebrew/bin"
fi

mint run swiftformat healthcare Packages
mint run swiftlint lint

LicensePlist

もともとBuild PhasesのRun Scriptでビルドのたびにライセンス情報を自動生成していたため、生成物をGit管理していませんでした。今回の対応で生成物をGit管理に含め、Build PhasesからRun Scriptを削除しました。パッケージを変更した際にはローカルでライセンス情報を再生成する運用としています。

swift-outdated

もともとXcode Cloudでは実行していなかったため、対応は不要でした。

結果

これらの対応により、Xcode Cloudの実行時間を約30分から約15分へ、約50%削減することができました。ビルドやパッケージ解決の設定を変えたわけではなく、ci_post_clone.shの処理を見直しただけでこれだけの効果が得られました。

まとめ

Xcode Cloudの実行時間を削減するために、CDとして不要なツールのインストール処理を見直しました。Xcode CloudのキャッシュはDerivedData配下のみという制約があるため、キャッシュで高速化するのではなく、そもそも不要な処理をXcode Cloudから取り除くアプローチを採用しています。各ツールの実行はGitHub Actionsやローカル環境に移行し、CI/CDの役割を明確に分離しました。

Xcode Cloudの実行時間に課題を感じている方の参考になれば幸いです。