Quantumleap
3231 words
16 minutes
GitLab CIでApp Store Connectにアプリをデプロイする

GitLab + AppStoreConnect#

普段はGitHabを利用しているのですが、たまたまGitLabでiOSアプリをデプロイする必要が生じたので備忘録としてメモしておきます。

主な手順は公式ドキュメントを参考にしました。

なのでこれがすんなり読める方は多分この記事は不要です。

必要なもの#

  • Apple Developer Programに加入しているApple ID
  • Apple App Store Connect Portalで作成したプライベートキー
    • 作成の手順についてはここを参照
    • URL自体がわからない人はここで直接キーが作成できる

GitLab#

  1. 左のサイドバーからSearch or go toでプロジェクト検索
  2. Settings > Integrationsを選択
  3. Apple App Store Connectを選択
  4. Enable Integrationの下からActiveにチェックを入れる
  5. 以下の情報を入力する
    • Issuer ID
    • Key ID
    • Private key
    • Protected branches and tags only
  6. Save changesで保存する

ここでTest settingsを押してConnection successfulと表示されれば成功です。

これをすれば以下の環境変数が利用可能になります。

  • $APP_STORE_CONNECT_API_KEY_ISSUER_ID
  • $APP_STORE_CONNECT_API_KEY_KEY_ID
  • $APP_STORE_CONNECT_API_KEY_KEY
    • Base64エンコードされた秘密鍵
  • $APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64
    • 常にtrueが入る

.gitlab-ci.ymlに悪意のあるコードがプッシュされると$APP_STORE_CONNECT_API_KEY_KEYなどの変数が外部のサーバーに送信される可能性があるので、注意すること。

Fastlane#

で、ここまできたらFastlaneでこれらのコードを利用したいですよね。

なのでまずはXcodeで適当なプロジェクトを作成します。

今回はTestAppとかいうそのまんまな名前を使いました。作成したらとりあえずGitLabにプッシュしておきます。

iOS Beta deployment using fastlaneという内容が今回求められているものだと思うのでこれを読みましょう。

導入#

まずはfastlaneがインストールされている必要があります。

  • Homebrew(macOS)
    • brew install fastlane
  • System Ruby + RubyGem(macOS/Linux/Windows)
    • sudo gem install fastlane

どちらかのコマンドでインストールできますが、普通はmacOSを使っていると思いますしSystem Rubyを利用すると権限でいろいろうるさいのでHomebrewを大人しく使います。

インストールができたらfastlane initで初期化します。

[✔] 🚀 
[✔] Looking for iOS and Android projects in current directory...
[10:28:57]: Created new folder './fastlane'.
[10:28:57]: Detected an iOS/macOS project in the current directory: 'TestApp.xcodeproj'
[10:28:57]: -----------------------------
[10:28:57]: --- Welcome to fastlane 🚀 ---
[10:28:57]: -----------------------------
[10:28:57]: fastlane can help you with all kinds of automation for your mobile app
[10:28:57]: We recommend automating one task first, and then gradually automating more over time
[10:28:57]: What would you like to use fastlane for?
1. 📸  Automate screenshots
2. 👩‍✈️  Automate beta distribution to TestFlight
3. 🚀  Automate App Store distribution
4. 🛠  Manual setup - manually setup your project to automate your tasks

のような表示が出てきます。今回やりたいのはベータ版をTestFlightにアップロードする作業ですので、2を選択します。

[10:29:42]: -----------------------------------------------------------
[10:29:42]: --- Setting up fastlane for iOS TestFlight distribution ---
[10:29:42]: -----------------------------------------------------------
[10:29:42]: Parsing your local Xcode project to find the available schemes and the app identifier
...
[10:29:45]: --------------------------------
[10:29:45]: --- Login with your Apple ID ---
[10:29:45]: --------------------------------
[10:29:45]: To use App Store Connect and Apple Developer Portal features as part of fastlane,
[10:29:45]: we will ask you for your Apple ID username and password
[10:29:45]: This is necessary for certain fastlane features, for example:
[10:29:45]: 
[10:29:45]: - Create and manage your provisioning profiles on the Developer Portal
[10:29:45]: - Upload and manage TestFlight and App Store builds on App Store Connect
[10:29:45]: - Manage your App Store Connect app metadata and screenshots
[10:29:45]: 
[10:29:45]: Your Apple ID credentials will only be stored in your Keychain, on your local machine
[10:29:45]: For more information, check out
[10:29:45]:     https://github.com/fastlane/fastlane/tree/master/credentials_manager
[10:29:45]: 
[10:29:45]: Please enter your Apple ID developer credentials
[10:29:45]: Apple ID Username:

のような表示が続き、Apple IDの入力を求められます。

ログインを試みると6桁のワンタイムパスワードが要求されるのでそれを入力します。

複数のチームに所属している場合はどのチームを利用するかを選択する必要があります。

[10:32:45]: ✅  Logging in with your Apple ID was successful
[10:32:45]: Checking if the app 'work.tkgstrator.TestApp' exists in your Apple Developer Portal...
[10:32:45]: It looks like the app 'work.tkgstrator.TestApp' isn't available on the Apple Developer Portal
[10:32:45]: for the team ID 'D9HU6JZF2Q' on Apple ID '[email protected]'
[10:32:45]: Do you want fastlane to create the App ID for you on the Apple Developer Portal? (y/n)

今回は適当に作ったアプリなのでApple Developer Portalにまだアプリがないと言われます。ここで作成することもできるのでyを入力してついでに作ってもらいましょう。

+------------------------------------------+
|       Summary for produce 2.219.0        |
+----------------+-------------------------+
| username       | [email protected]     |
| team_id        | D9HU6JZF2Q              |
| itc_team_id    | 118733804               |
| platform       | ios                     |
| app_identifier | work.tkgstrator.TestApp |
| skip_itc       | true                    |
| sku            | 1708911228              |
| language       | English                 |
| skip_devcenter | false                   |
+----------------+-------------------------+

[10:33:49]: App Name: TestApp
[10:34:00]: Creating new app 'TestApp' on the Apple Dev Center
[10:34:02]: Created app GJH5FZTZUD
[10:34:02]: Finished creating new app 'TestApp' on the Dev Center
[10:34:02]: ✅  Successfully created app
[10:34:02]: Checking if the app 'work.tkgstrator.TestApp' exists on App Store Connect...
[10:34:03]: Looks like the app 'work.tkgstrator.TestApp' isn't available on App Store Connect
[10:34:03]: for the team ID '118733804' on Apple ID '[email protected]'
[10:34:03]: Would you like fastlane to create the App on App Store Connect for you? (y/n)

作成できると、App Store Connect用のfastlaneを作成するかと問われます。ついでに作ってもらいたいのでやはりyを入力します。

+------------------------------------------+
|       Summary for produce 2.219.0        |
+----------------+-------------------------+
| username       | [email protected]     |
| team_id        | D9HU6JZF2Q              |
| itc_team_id    | 118733804               |
| platform       | ios                     |
| app_identifier | work.tkgstrator.TestApp |
| skip_devcenter | true                    |
| sku            | 1708911289              |
| language       | English                 |
| skip_itc       | false                   |
+----------------+-------------------------+

[10:34:51]: App Name: TestApp
[10:35:05]: Creating new app 'TestApp' on App Store Connect
[10:35:05]: Sending language name is deprecated. 'English' has been mapped to 'en-US'.
[10:35:05]: Please enter one of available languages: ["ar-SA", "ca", "cs", "da", "de-DE", "el", "en-AU", "en-CA", "en-GB", "en-US", "es-ES", "es-MX", "fi", "fr-CA", "fr-FR", "he", "hi", "hr", "hu", "id", "it", "ja", "ko", "ms", "nl-NL", "no", "pl", "pt-BR", "pt-PT", "ro", "ru", "sk", "sv", "th", "tr", "uk", "vi", "zh-Hans", "zh-Hant"]

言語は勝手にen-USが設定されたのですが利用しているmacOSの設定によっては変わるかもしれません。

この後、作成に失敗したりでよくわからないエラーが発生したので再度fastlane initを実行したので結果が異なるかもしれません。

続けていると以下のようなメッセーが表示されました。

[10:37:46]: ✅  Logging in with your Apple ID was successful
[10:37:46]: Checking if the app 'work.tkgstrator.TestApp' exists in your Apple Developer Portal...
[10:37:47]: ✅  Your app 'work.tkgstrator.TestApp' is available in your Apple Developer Portal
[10:37:47]: Checking if the app 'work.tkgstrator.TestApp' exists on App Store Connect...
[10:37:48]: ✅  Your app 'work.tkgstrator.TestApp' is available on App Store Connect
[10:37:48]: Installing dependencies for you...
[10:37:48]: $ bundle update
[10:37:55]: --------------------------------------------------------
[10:37:55]: --- ✅  Successfully generated fastlane configuration ---
[10:37:55]: --------------------------------------------------------
[10:37:55]: Generated Fastfile at path `./fastlane/Fastfile`
[10:37:55]: Generated Appfile at path `./fastlane/Appfile`
[10:37:55]: Gemfile and Gemfile.lock at path `Gemfile`
[10:37:55]: Please check the newly generated configuration files into git along with your project
[10:37:55]: This way everyone in your team can benefit from your fastlane setup
[10:37:55]: Continue by pressing Enter ⏎

特に重要なところもないので脳死でエンターキーを連打します。

.
├── fastlane/
   ├── Appfile
   └── Fastfile
├── TestApp/
├── TestApp.xcodeproj/
├── TestAppTests/
├── TestAppUITests/
├── Gemfile
└── Gemfile.lock

するとこんな感じのディレクトリ構造になると思います。

Matchfile#

Tutorial: iOS CI/CD with GitLabによるとMatchfileも別途必要だそうです。

fastlane match init

とすると、

[✔] 🚀 
[10:49:05]: fastlane match supports multiple storage modes, please select the one you want to use:
1. git
2. google_cloud
3. s3
4. gitlab_secure_files

と表示されます。GitLabを利用していればSecure Filesが利用できるので4を選択します。

[10:49:55]: Initializing match for GitLab project  on 
[10:49:55]: What is your GitLab Project (i.e. gitlab-org/gitlab): tkgstrator/apple-appstore-connect-test
[10:50:12]: What is your GitLab Host (i.e. https://gitlab.example.com, skip to default to https://gitlab.com): 
[10:50:14]: Successfully created './fastlane/Matchfile'. You can open the file using a code editor.
[10:50:14]: You can now run `fastlane match development`, `fastlane match adhoc`, `fastlane match enterprise` and `fastlane match appstore`
[10:50:14]: On the first run for each environment it will create the provisioning profiles and
[10:50:14]: certificates for you. From then on, it will automatically import the existing profiles.
[10:50:14]: For more information visit https://docs.fastlane.tools/actions/match/

今回、レポジトリ名がhttps://gitlab.com/tkgstrator/apple-appstore-connect-test**だったのでGitLab Projectにはtkgstrator/apple-appstore-connect-test**を指定し、GitLab Hostはデフォルト設定なのでそのままエンターキーを押します。

セルフホストしている場合はホスト名を指定してください。

するとfastlane/Matchfileが作成され、以下のような内容が書いてあります。

gitlab_project("tkgstrator/apple-appstore-connect-test")
gitlab_host("https://gitlab.com")

storage_mode("gitlab_secure_files")

type("development") # The default type, can be: appstore, adhoc, enterprise or development

Project Access Token#

次にGitLabのプロジェクトの設定からアクセストークンを発行します。

RoleでMaintainerを選択してスコープにはapiの権限さえついていれば良いようです。

発行した値を覚えておきましょう。

このままfastlane match developmentで発行してもよいのですがそのままやると環境変数が読み込めないのでMakefileと.envを作成します。

include .env

.PHONY: match
match:
	fastlane match development

AppStore用の証明書が欲しい場合はfastlane match appstoreとするように

また、.envは以下のようにします。

PRIVATE_TOKEN=YOUR_NEW_TOKEN

ここまでできると以下のようなディレクトリ構造になっています。

.
├── fastlane/
   ├── Appfile
   ├── Fastfile
   └── Matchfile
├── TestApp/
├── TestApp.xcodeproj/
├── TestAppTests/
├── TestAppUITests/
├── Gemfile
├── Gemfile.lock
├── Makefile
└── .env

この状態でmake matchを実行すると開発環境用のプロビジョニングが作成できます。

GitLabのプロジェクトページをひらいてSettings > CI/CD > Secure Filesにファイルが作成されていれば成功です。

Xcode#

このままだと自動署名が実行されてしまうのでTestApp.xcodeprojをひらいてAutomatically manage signingのチェックを外します。

外したらProvisioning Profileからmatch Developmentと表示されているものを選択します。

二つあると思うのですが、多分どっちを選んでも大丈夫です。

Fastfile#

ここでfastlane/Fastfileをひらいてみます。

default_platform(:ios)

platform :ios do
  desc "Push a new beta build to TestFlight"
  lane :beta do
    increment_build_number(xcodeproj: "TestApp.xcodeproj")
    build_app(scheme: "TestApp")
    upload_to_testflight
  end
end

こんな感じの内容になっていると思うのでこれを以下のように編集します。

default_platform(:ios)

platform :ios do
  desc "Push a new build to TestFlight"
  lane :beta do
    setup_ci
    match(type: 'appstore', readonly: is_ci)
    app_store_connect_api_key
    increment_build_number(
      build_number: latest_testflight_build_number(initial_build_number: 1) + 1,
      xcodeproj: "TestApp.xcodeproj"
    )
    build_app(scheme: "TestApp")
    upload_to_testflight
  end
end

こうすることでビルド番号を上げながらTestFlightにアップロードができます。

バージョニングを自動でするためにはApple Generic Versioningが有効化されている必要があります

有効化するためにはiOSアプリ開発で面倒なことを解決していくがわかりやすいので参考にしてください。

バージョンもx.x.xの形式になるようにしましょう。

.gitlab-ci.yml#

最後にこれらをソースコードがプッシュされた段階で実行されるようにします。

.gitlab-ci.yamlとすると正しく認識されないので必ず拡張子は.ymlにすること

stages:
  - build
  - beta

cache:
  key:
    files:
      - Gemfile.lock
  paths:
    - vendor/bundle

build_ios:
  image: macos-13-xcode-14
  stage: build
  script:
    - bundle check --path vendor/bundle || bundle install --path vendor/bundle --jobs $(nproc)
    - bundle exec fastlane build
  tags: 
    - saas-macos-medium-m1

beta_ios:
  image: macos-13-xcode-14
  stage: beta
  script:
    - bundle check --path vendor/bundle || bundle install --path vendor/bundle --jobs $(nproc)
    - bundle exec fastlane beta
  tags: 
    - saas-macos-medium-m1
  when: manual
  allow_failure: true
  only:
    refs:
      - master

また、.envがプッシュされないように.gitignoreを作成します。

これはGitHubのSwift向けテンプレートを流用しました。

# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings
xcuserdata/

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

## Obj-C/Swift specific
*.hmap

## App packaging
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm

.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build/

# Accio dependency management
Dependencies/
.accio/

# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/

# Envidonment Variables
.env
.env.*
!.env.example

SaaS runners on macOS#

とはいえこれは実際にはテストできません。

何故ならSaaSのGitLab runnersはプランがプレミアム以上でないと実行できないためです。

なので本当に動くかどうかはわからないのですが、概ねこんな感じで書けると思います。

Local GitLab Runner#

ということでローカルで実行してみます。

ないならセルフホストで動かせばいいじゃないかということですね。

brew install gitlab-runner

のコマンドでインストールしてみます。バージョンを確認したら16.7でした。

$ gitlab-runner -v

Version:      16.7.0
Git revision: 102c81ba
Git branch:   16-7-stable
GO version:   go1.20.10
Built:        2023-12-21T17:01:33+0000
OS/Arch:      darwin/arm64

SasSで実行する前提でイメージが指定されているのでそれを無効化します。

image: ruby:latest

stages:
  - build
  - beta

cache:
  key:
    files:
      - Gemfile.lock
  paths:
    - vendor/bundle

build_ios:
  stage: build
  script:
    - bundle check --path vendor/bundle || bundle install --path vendor/bundle --jobs $(nproc)
    - bundle exec fastlane build
  tags: 
    - saas-macos-medium-m1

beta_ios:
  stage: beta
  script:
    - bundle check --path vendor/bundle || bundle install --path vendor/bundle --jobs $(nproc)
    - bundle exec fastlane beta
  tags: 
    - saas-macos-medium-m1
  when: manual
  allow_failure: true
  only:
    refs:
      - master
GitLab CIでApp Store Connectにアプリをデプロイする
https://fuwari.vercel.app/posts/2024/02/app_store_connect/
Author
tkgling
Published at
2024-02-26