はじめに
個人開発のiOSアプリでCI/CDを構築したので書いていきます。以下の仕様です。
- GitHubActionsを用いる
- ブランチ戦略
- main: 開発統合ブランチ
- release: リリースブランチ
- feature/XXX: 開発作業ブランチ
- タイミングと内容
- mainのpush/PR, releaseのPR
- ビルド
- テストは未実装のため行わないが代わりにビルドできるかを確認する
- ビルド
- releaseへのpush
- アーカイブ
- AppStoreConnectへのアップロード
- mainのpush/PR, releaseのPR
- その他使用パッケージ
- SPM
- TCA (The Composable Architecture)
- CocoaPod
- AWSMobileClient
- SPM
参考文献
こちらの記事が非常に役立ちました。著者のYahiroさんありがとうございます。
参考文献との大きな違いはCocoapodとSwift Macrosを導入している点でした。
環境変数
参考文献の準備編がとてもわかりやすいです。
ワークフロー
プロジェクト名はMyAppにしているので一斉置換してください
mainブランチ用
name: main
on:
push:
branches: ["main"]
pull_request:
branches: ["main", "release"]
workflow_dispatch:
jobs:
build:
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Xcode 16.4
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.4"
- name: Cache SPM
uses: actions/cache@v3
with:
path: |
~/Library/Caches/org.swift.swiftpm
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- name: Cache Pods
uses: actions/cache@v3
with:
path: |
Pods
~/Library/Caches/CocoaPods
~/.cocoapods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Cache DerivedData
uses: actions/cache@v3
with:
path: .derivedData
key: ${{ runner.os }}-derived-${{ hashFiles('MyApp.xcodeproj/project.pbxproj', '**/*.swift', '**/*.h') }}
restore-keys: |
${{ runner.os }}-derived-
- name: Install CocoaPods
run: |
gem install cocoapods
pod install
- name: Build
run: |
xcodebuild build \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-sdk iphoneos \
-configuration Release \
-destination 'generic/platform=iOS' \
-derivedDataPath .derivedData \
-skipMacroValidation \
CODE_SIGNING_ALLOWED=NO \
ENABLE_SWIFT_MACROS=YES
変更点は以下の通りです。
- XCodeのバージョン指定
- キャッシュ追加
- build時にマクロに関する引数追加
release用
name: release
on:
push:
branches: ["release"]
workflow_dispatch:
jobs:
release-archive:
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Xcode 16.4
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.4"
- name: Cache SPM
uses: actions/cache@v3
with:
path: |
~/Library/Caches/org.swift.swiftpm
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- name: Cache Pods
uses: actions/cache@v3
with:
path: |
Pods
~/Library/Caches/CocoaPods
~/.cocoapods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Cache DerivedData
uses: actions/cache@v3
with:
path: .derivedData
key: ${{ runner.os }}-derived-${{ hashFiles('MyApp.xcodeproj/project.pbxproj', '**/*.swift', '**/*.h') }}
restore-keys: |
${{ runner.os }}-derived-
- name: Install CocoaPods
run: |
gem install cocoapods
pod install
- name: Build
run: |
xcodebuild archive \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-archivePath MyApp.xcarchive \
-sdk iphoneos \
-configuration Release \
-destination 'generic/platform=iOS' \
-skipMacroValidation \
CODE_SIGNING_ALLOWED=NO \
ENABLE_SWIFT_MACROS=YES
- name: Create ExportOptions.plist
run: |
echo '${{ secrets.EXPORT_OPTIONS }}' > ExportOptions.plist
cat ExportOptions.plist
- name: Create Private Key
run: |
mkdir private_keys
echo -n '${{ secrets.APPLE_API_KEY_BASE64 }}' | base64 --decode > ./private_keys/AuthKey_${{ secrets.APPLE_API_ISSUE_ID }}.p8
- name: Export IPA
run: |
xcodebuild -exportArchive -archivePath MyApp.xcarchive -exportOptionsPlist ExportOptions.plist -exportPath MyApp.ipa -allowProvisioningUpdates -authenticationKeyPath `pwd`/private_keys/AuthKey_${{ secrets.APPLE_API_ISSUE_ID }}.p8 -authenticationKeyID ${{ secrets.APPLE_API_KEY_ID }} -authenticationKeyIssuerID ${{ secrets.APPLE_API_ISSUE_ID }}
- name: Upload to App Store Connect
run: |
xcrun altool --upload-app -f MyApp.ipa/MyApp.ipa -t ios -u ${{ secrets.APPLE_ID }} -p ${{ secrets.APP_SPECIFIC_PASSWORD }} --type ios
考察
もともとGitHubActions + Fastlane + Firebase AppDistributionを使った自動テスト配布を行っていましたが、AWSの認証周りと不整合が起きたので直近では専ら手動アップロードしてTestFlightを使っていました。そこでGitHubActionsのみでAppStoreConnectにアップロードする形へ切り替えたわけです。
切り替え前は12~20分とかなりばらつきがあり、しかも長い時間がかかっていました。
切り替え直後もあまり変わりませんでしたが以下の対応でかなり短縮されました
- podのクリーンインストール
- これは本当に効果あったか微妙だが、実行前はAWSCoreのビルドにかなり時間がかかっていた
- Firebase-iOS-SDKの削除
- AppDistributionを使用しなくなったため削除
これによりmainのビルドは4分半、releaseのアーカイブも10分半になりました。
またreleaseの手動アップロード時はせっかくPRしたのにプルし忘れたままアーカイブ取ることが多発していたので、これによりかなり改善されるでしょう。