前回のブログ記事では、テストと統合の自動化の重要性について書きました。これは完全に自動化されたCICDパイプラインの一部として行われます。Github Actionsワークフローのymlファイルのコードスニペットを共有しました。
この記事では、これを正確にどのように達成するかを実証します。コードを表示するには、こちらをクリックしてください。
このワークフローは以下のことを行います。
パイプライン環境変数
再利用される変数をグローバルパイプライン変数としてキャプチャするのは常に良い習慣です
コード:
env:
ARCHIVE_SCHEME: "Your Project Name (e.g. Falling Sky)"
APP_NAME: "Your App Name (e.g. FallingSky)"
BUNDLE_ID: "Your Bundle Id (e.g. com.ka.fallingsky)"
ワークスペースのクリーンアップ(自己ホストランナーに適しています)
コード:
- name: Clean Workspace
run: |
rm -rf $GITHUB_WORKSPACE/*
rm -rf $RUNNER_TEMP/*
独自のビルドエージェントを実行している場合、各ビルドが互いに完全に分離して実行されることを確認したいと思うでしょう。ここで行っていることは、ビルドエージェントがビルドの一部として使用する2つのフォルダ内のファイルを単に削除するだけです。
コードをチェックアウトする
コード:
- name: Checkout
uses: actions/checkout@v3
これにはGitHubアクションのチェックアウトステップを使用し、コードの最新バージョンをチェックアウトします。
デフォルトのスキームを設定する
コード:
- name: Set Default Scheme
run: |
scheme_list=$(xcodebuild -list -json | tr -d "\n")
default=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]")
echo $default | cat >default
echo Using default scheme: $default
このステップは、Xcodeプロジェクトのスキームのリストを取得し、最初のターゲット(デフォルトのスキームと想定されている)を抽出し、デフォルトのスキームの名前を「default」という名前のファイルに保存し、デフォルトのスキームの使用を示すログメッセージも提供します。これは、将来のパイプラインステップで特定のXcodeスキームがデフォルトとして設定されていることに依存する場合に役立ちます。
自動化されたテストのビルドと実行
コード:
- name: Build for test
env:
scheme: ${{ 'default' }}
platform: ${{ 'iOS Simulator' }}
run: |
# xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959)
device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"`
if [ $scheme = default ]; then scheme=$(cat default); fi
if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi
file_to_build=`echo $file_to_build | awk '{$1=$1;print}'`
xcodebuild clean build-for-testing -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device"
- name: Test
timeout-minutes: 30
env:
scheme: ${{ 'default' }}
platform: ${{ 'iOS Simulator' }}
run: |
# xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959)
device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"`
if [ $scheme = default ]; then scheme=$(cat default); fi
if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi
file_to_build=`echo $file_to_build | awk '{$1=$1;print}'`
xcodebuild test-without-building -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" -test-iterations 3 -retry-tests-on-failure
これは、パイプラインがテストに失敗した場合に停止することを保証する重要なステップです。これは、本番環境でのリグレッションを防ぐのに理想的です。
証明書とプロビジョニングプロファイルをインストールする
コード:
- name: Install the Apple certificate and provisioning profile
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.DIST_CERT_BASE64 }}
P12_PASSWORD: ${{ secrets.DIST_CERT_P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.PROVISIONING_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
これは、アプリをアーカイブしてTestFlightに配布し、その後App Storeに送信する準備ができるようにするために必要です。このステップには、多くのGitHubリポジトリレベルのシークレットが必要です。これらの設定方法について以下で詳しく説明します。詳細については、https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development をご覧ください。
DIST_CERT_BASE64 & DIST_CERT_P12_PASSWORD
Base64として格納されたp12ビルド証明書とその署名に使用されるパスワードです。次の手順に従ってp12証明書を作成したら、次のコマンドを実行してBase64テキストを取得する必要があります:
base64 -i BUILD_CERTIFICATE.p12 | pbcopy
そして、この情報をリポジトリのシークレットとして保存します。
PROVISIONING_PROFILE_BASE64
これは配布に必要なプロビジョニングプロファイルです。プロファイリングプロファイルを作成したら(プロビジョニングプロファイルを作成する必要がありますhttps://developer.apple.com)、次のコマンドを実行してBase64テキストを取得します:
base64 -i PROVISIONING_PROFILE.mobileprovision | pbcopy
そして、リポジトリシークレットとして保存します。
KEYCHAIN_PASSWORD
各実行で一時キーチェーンが作成されるため、これはランダムに生成された文字列であっても問題ありません。
バージョン番号を増やす
コード:
- uses: yanamura/ios-bump-version@v1
with:
version: 1.1.0
build-number: ${{ github.run_number }}
GitHubの実行番号をバージョンの「ユニーク」部分として使用します。これにより、関連するファイルが更新され、App Store Connectで新しいビルドとして利用可能になります。
アプリのビルドとアーカイブ
コード:
- name: Build and archive app
run: |
if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi
file_to_build=`echo $file_to_build | awk '{$1=$1;print}'`
xcodebuild archive -"$filetype_parameter" "$file_to_build" -scheme "$ARCHIVE_SCHEME" -archivePath "$APP_NAME" -configuration Release
Creating an IPA file
コード:
- name: Create IPA file
env:
EXPORT_OPTIONS_PLIST: ${{ secrets.EXPORT_OPTIONS_PLIST_BASE64 }}
run: |
EXPORT_OPTIONS_PLIST_PATH=$RUNNER_TEMP/ExportOptions.plist
echo -n "$EXPORT_OPTIONS_PLIST" | base64 -d -o $EXPORT_OPTIONS_PLIST_PATH
xcodebuild -exportArchive -archivePath $GITHUB_WORKSPACE/$APP_NAME.xcarchive -exportPath $RUNNER_TEMP/build -exportOptionsPlist $EXPORT_OPTIONS_PLIST_PATH
mv "$RUNNER_TEMP/build/$ARCHIVE_SCHEME.ipa" $RUNNER_TEMP/build/$APP_NAME.ipa
これにより、TestFlightにアップロードする準備が整ったIPAファイルが作成されます
EXPORT_OPTIONS_PLIST_BASE64シークレットを使用します。これはExportOptions.plistのBase64です。これを多くのオンラインの例と共に手動で作成できます。次の手順を実行してExportOptions.plistファイルのBase64をキャプチャし、リポジトリシークレットとして保存します:
base64 -i ExportOptions.plist | pbcopy
TestFlightにデプロイする
コード:
- name: Upload to TestFlight
env:
API_KEY_BASE64: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}
run: |
mkdir -p ./private_keys
echo -n "$API_KEY_BASE64" | base64 --decode -o "./private_keys/AuthKey_${{ secrets.APPSTORE_API_KEY_ID }}.p8"
xcrun altool --validate-app -f ${{ runner.temp }}/build/${{ env.APP_NAME }}.ipa -t ios --apiKey ${{ secrets.APPSTORE_API_KEY_ID }} --apiIssuer ${{ secrets.APPSTORE_ISSUER_ID }}
xcrun altool --upload-app -f ${{ runner.temp }}/build/${{ env.APP_NAME }}.ipa -t ios --apiKey ${{ secrets.APPSTORE_API_KEY_ID }} --apiIssuer ${{ secrets.APPSTORE_ISSUER_ID }}
これにより、生成されたIPAファイルがTestFlightにアップロードされます。完了すると、Appleが処理するのに数分かかりますが、デバイスでテスト用に利用可能になります。
このステップにはAPPSTORE_API_PRIVATE_KEY、APPSTORE_API_KEY_ID、APPSTORE_ISSUER_IDの3つのシークレットが必要です。これはApp Store Connectで作成されたAPIキーから生成されます。キーをダウンロードし、.p8ファイルのBase64をキャプチャして、APPSTORE_API_PRIVATE_KEYリポジトリシークレットとして保存します。キーIDと発行者IDはApp Store Connectで見つかります。
サポート方法
このコンテンツは常に無料で提供されます。価値があると感じた場合、他の人と共有を検討してください. さらに、私たちのゲームをダウンロードして正直なレビューを残すことは、私たちを大いにサポートします. 質問やフィードバックがある場合は、お気軽にお問い合わせください。できる限りお答えします.
今すぐApple App StoreからFalling Skyをダウンロード: https://apps.apple.com/app/id6446787964
以下はymlファイル全体のコードです:
name: My Automated CI/CD Pipeline
on:
push:
branches: [ "main" ]
workflow_dispatch:
env:
ARCHIVE_SCHEME: "Your Project Name (e.g. Falling Sky)"
APP_NAME: "Your App Name (e.g. FallingSky)"
BUNDLE_ID: "Your Bundle Id (e.g. com.ka.fallingsky)"
jobs:
build:
name: Build and Test default scheme using any available iPhone simulator
runs-on: self-hosted
steps:
- name: Clean Workspace
run: |
rm -rf $GITHUB_WORKSPACE/*
rm -rf $RUNNER_TEMP/*
- name: Checkout
uses: actions/checkout@v3
- name: Set Default Scheme
run: |
scheme_list=$(xcodebuild -list -json | tr -d "\n")
default=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]")
echo $default | cat >default
echo Using default scheme: $default
- name: Build for test
env:
scheme: ${{ 'default' }}
platform: ${{ 'iOS Simulator' }}
run: |
# xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959)
device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"`
if [ $scheme = default ]; then scheme=$(cat default); fi
if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi
file_to_build=`echo $file_to_build | awk '{$1=$1;print}'`
xcodebuild clean build-for-testing -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device"
- name: Test
timeout-minutes: 30
env:
scheme: ${{ 'default' }}
platform: ${{ 'iOS Simulator' }}
run: |
# xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959)
device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"`
if [ $scheme = default ]; then scheme=$(cat default); fi
if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi
file_to_build=`echo $file_to_build | awk '{$1=$1;print}'`
xcodebuild test-without-building -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" -test-iterations 3 -retry-tests-on-failure
- name: Install the Apple certificate and provisioning profile
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.DIST_CERT_BASE64 }}
P12_PASSWORD: ${{ secrets.DIST_CERT_P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.PROVISIONING_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- uses: yanamura/ios-bump-version@v1
with:
version: 1.1.0
build-number: ${{ github.run_number }}
- name: Build and archive app
run: |
if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi
file_to_build=`echo $file_to_build | awk '{$1=$1;print}'`
xcodebuild archive -"$filetype_parameter" "$file_to_build" -scheme "$ARCHIVE_SCHEME" -archivePath "$APP_NAME" -configuration Release
- name: Create IPA file
env:
EXPORT_OPTIONS_PLIST: ${{ secrets.EXPORT_OPTIONS_PLIST_BASE64 }}
run: |
EXPORT_OPTIONS_PLIST_PATH=$RUNNER_TEMP/ExportOptions.plist
echo -n "$EXPORT_OPTIONS_PLIST" | base64 -d -o $EXPORT_OPTIONS_PLIST_PATH
xcodebuild -exportArchive -archivePath $GITHUB_WORKSPACE/$APP_NAME.xcarchive -exportPath $RUNNER_TEMP/build -exportOptionsPlist $EXPORT_OPTIONS_PLIST_PATH
mv "$RUNNER_TEMP/build/$ARCHIVE_SCHEME.ipa" $RUNNER_TEMP/build/$APP_NAME.ipa
- name: Upload to TestFlight
env:
API_KEY_BASE64: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}
run: |
mkdir -p ./private_keys
echo -n "$API_KEY_BASE64" | base64 --decode -o "./private_keys/AuthKey_${{ secrets.APPSTORE_API_KEY_ID }}.p8"
xcrun altool --validate-app -f ${{ runner.temp }}/build/${{ env.APP_NAME }}.ipa -t ios --apiKey ${{ secrets.APPSTORE_API_KEY_ID }} --apiIssuer ${{ secrets.APPSTORE_ISSUER_ID }}
xcrun altool --upload-app -f ${{ runner.temp }}/build/${{ env.APP_NAME }}.ipa -t ios --apiKey ${{ secrets.APPSTORE_API_KEY_ID }} --apiIssuer ${{ secrets.APPSTORE_ISSUER_ID }}