🍎

TestをGitHub ActionsからXcodeCloudに移行した話

2022/12/13に公開

この記事はLuup Advent Calendarの13日目の記事です。

こんにちは、iOSエンジニアの大瀧です。

今回はGitHub Actionsで行っていたTestの実行をXcodeCloudに移行した話を書いていきます。

XcodeCloudの概要

XcodeCloudはXcodeやAppStoreConnectからGUIでワークフローを設定、証明書の管理やTestFlightへの自動アップロード等、Appleプラットフォームに最適化されたCI/CDサービスです。

料金

GitHub ActionsやBitriseに比べるとかなりお得になっています。

https://developer.apple.com/jp/xcode-cloud/

xcode-cloud-price

GitHub ActionsではTestの実行に1ビルドで40〜50分ほどかかっていて1ビルドにつき$4($0.08×50m)もかかっていてました。さらに常時4人ほどのエンジニアがコードを書いていたので月$700ほどの費用がかかっていました。

XcodeCloudを導入後は、1ビルドが20分未満になり$49.99/月100時間のプランで済ませられるようになりました。料金はなんと1/14に削減できています。

Testの実行時間がかなり短縮されているのはSwiftPackageManagerの依存解決が最適化されたことによる部分が大きいと思います。

導入方法

Xcodeのメニューから Product > Xcode Cloud > Create Workflow を選択します。

create-workflow

続いてワークフローの各種設定を行なっていきます。

edit-workflow

Start Condition

ワークフローのトリガーを設定

  • Branch Changes
  • Pull Request Changes
  • Tag Changes

Environment

ビルドするMacOSバージョンや環境変数を設定

Actions

実行するアクションを設定

  • Build
  • Test
  • Archive
  • Analyze

Post-Actions

TestFlightへのアップロードやSlackやメール通知ができます

上記を設定後、リポジトリーの設定をして完了です。
びっくりするほど簡単でした。

ci_scripts

上記の手順でワークフローの設定は完了しました。

ただ、ほとんどのアプリではビルド前にCarthageやCocoaPodsのコマンドを走らせたり、RswiftやSwiftGen等のスクリプトを走らせる必要があると思います。そういったprebuild scriptsを実行できるのがci_post_clone.shです。

以前の記事でも触れましたがLUUPアプリではSPMに完全対応していますのでSwiftGenと環境変数の設定スクリプトを実行しています。

!/bin/sh

cd $CI_WORKSPACE

# SwiftGen
brew install swiftgen
swiftgen config run --config "$CI_WORKSPACE/DesignSystems/swiftgen.yml"
swiftgen config run --config "$CI_WORKSPACE/Resources/swiftgen.yml"

# 環境変数の設定
...

開発環境のビルド時はAggregate Targetのビルドフェイズを使ってSwiftGenや環境変数の設定スクリプトを手動で走らせていたので、ci_post_clone.shでも下記のようにxcodebuildコマンドで実行していました。しかし、XcodeCloudでのビルド時はxcodebuildコマンドが走るたびにSwiftPackageManagerの依存解決が走ってしまいビルド時間が無駄に伸びていました。

なので、Aggregate Targetのビルドフェイズに書いていたスクリプトをci_post_clone.shに直接書くことで対応しました。その結果ビルド時間が5分ほど短縮できました。xcodebuildコマンドが増えれば増えるほど実行時間は伸びていくので直でスクリプトを書くことをお勧めします。

#!/bin/sh

# SwiftGen
xcodebuild -workspace LUUP.xcworkspace -scheme swift-gen

xcodebuild -workspace LUUP.xcworkspace -scheme generate-env

困ったこと

SwiftLintPluginが実行できない

SwiftPackagePluginを利用してSwiftLintを実行していたのですがなぜかXcodeCloudでのビルド時のみPermissionがないといったエラーが出てビルドできず...。

swiftlint-error

ワークアラウンドとしてXcodeCloudでのCI以外でSwiftLintPluginを使うようにしました。

let package = Package(
    name: "LUUP",
    ...
    products: [...],
    dependencies: [...],
    targets: [
        .binaryTarget(
            name: "SwiftLintBinary",
            url: "https://github.com/realm/SwiftLint/releases/download/0.49.1/SwiftLintBinary-macos.artifactbundle.zip",
            checksum: "227258fdb2f920f8ce90d4f08d019e1b0db5a4ad2090afa012fd7c2c91716df3"
        ),
        .plugin(
            name: "SwiftLintPlugin",
            capability: .buildTool(),
            dependencies: ["SwiftLintBinary"],
            path: "Plugins/Lint"
        ),
        .target(
            name: "App",
            dependencies: [...],
            path: "LUUP",
            plugins: []
        )
    ]
)

// CI以外でSwiftLintPluginを追加する
if ProcessInfo.processInfo.environment["CI"] != "TRUE" {
    package.targets.first(where: { $0.name == "App" })!.plugins! += [
        Target.PluginUsage(stringLiteral: "SwiftLintPlugin"),
    ]
}              

実際に使ってみて

良かったところ

  • BitriseやGitHub Actionsでは自前で書いていたSPM周りのキャッシュ処理をやってくれる
  • 証明書周りを気にしなくて良い
  • GUI上でのワークフロー設定が楽

悪かったところ

  • AppStoreConnectAPI経由でのビルドが最低限しかできず(ブランチ指定ができない等)やや不便
  • GUIでの操作しか対応してないのでチーム開発時の差分管理ができない

値段がリーズナブルな所やSwiftPackageManagerとの相性も良さそうでかなり満足しています。今後はBitriseで行っているCDも移行していければと思っています。

最後に

Luupでは常に最新の技術のキャッチアップ&導入にチャレンジしています。
事業自体の進化に追従しながらエンジニアにとってより良い開発環境を構築できるように日々精進しています!

Luupでは現在iOSエンジニア採用を積極的に行っていますので、課題と挑戦がいっぱいのLuupでのiOS開発に少しでも興味を持っていただけた方は、是非お気軽にご連絡ください!
https://recruit.luup.sc/

Luup Developers Blog

Discussion