この記事は、Classi Advent Calendar 2020 12/21担当の記事です。
Classi プロダクト開発部 iOSエンジニアの@yokoyanです。
モバイルアプリチームでは、CIの運用が、初期に構築したメンバーが退職していることもあり、ブラックボックスになりかけていたので、整備をおこなったところ、あまり事例がなさそうな方法で改善してみたので、iOSエンジニアにも役に立ちそうなネタが出来たかなと思ったので、それを紹介したいと思います。
なぜ、改善することになったか
きっかけは、Carthageのセグメンタルエラーが発生する事象への対応です。
Carthage crashes with Segmentation Fault: 11 · Issue #2760 · Carthage/Carthage
このエラーは、Carthageのバージョンを0.35.0以上にあげれば、解消されます。
CircleCIは、Carthageがデフォルトでインストールされている
発生時点で、CircleCIのCarthageのバージョンは0.33.0でした。
方法は二つありました。
- デフォルトでインストールされているCarthageをアップデートする
- パッケージ管理ツールを使って任意のバージョンをインストール出来るようにする
方法1 デフォルトでインストールされているCarthageをアップデートする
commands:
setup_homebrew:
description: "Setup Homebrew with caches"
steps:
- restore_cache:
keys:
- v1-brew-{{ epoch }}
- v1-brew-
- run: brew update
- run:
name: Homebrew install or upgrade Carthage
command: bash ./script/carthage-install.sh
- run: brew cleanup
- save_cache:
key: v1-brew-{{ epoch }}
paths:
- /Users/distiller/Library/Caches/Homebrew
- /usr/local/Homebrew
setup_carthage:
description: "Setup carthage with caches"
steps:
- run:
name: Set Xcode Version
command: echo `xcodebuild -version` > XCODE_VERSION
- restore_cache:
keys:
- v1-ca-{{ checksum "XCODE_VERSION" }}-{{ checksum "Cartfile.resolved" }}
- v1-ca-
- run:
name: Carthage bootstrap
command: carthage bootstrap --platform iOS --cache-builds
- save_cache:
key: v1-ca-{{ checksum "XCODE_VERSION" }}-{{ checksum "Cartfile.resolved" }}
paths:
- Carthage
#!/bin/sh -x
which carthage;
if [ $? -eq "0" ]; then
brew outdated carthage || brew upgrade carthage
else
brew install carthage
fi
基本的には、brew upgrade
でCarthageのバージョンを上げられるので、極論、↓でいいとは思います。
- run: command: |
brew update
brew upgrade
brew cleanup
ただ、この方法では、全てのライブラリがアップデートされてしまい、関係ないライブラリがアップデートされ、別の問題が発生する可能性もあるので、一旦、Carthageだけアップデートするようにした方がいいです。
それと、carthage-install.sh というスクリプトを作って、
brew outdated carthage || brew upgrade carthage
しているのは、バージョンが最新であれば、余計なコマンド実行をしないようにしています。
方法2 パッケージ管理ツールを使って任意のバージョンをインストール出来るようにする
次に、MintやSwift Package Manager などのパッケージ管理ツールを使えば、Carthageの任意のバージョンを指定してインストールする場合です。方法1に少し、手順を追加したものです。
自分が、担当しているアプリは、Mintを使っているので、Mintでの方法になります。
commands:
setup_homebrew:
description: "Setup Homebrew with caches"
steps:
- restore_cache:
keys:
- v1-brew-{{ epoch }}
- v1-brew-
- run: brew update
- run:
name: Homebrew install or upgrade Mint
command: bash ./script/mint-install.sh
- run: brew cleanup
- save_cache:
key: v1-brew-{{ epoch }}
paths:
- /Users/distiller/Library/Caches/Homebrew
- /usr/local/Homebrew
setup_mint:
description: "Setup Mint with caches"
steps:
- restore_cache:
keys:
- v1-mint-{{ checksum "Mintfile" }}
- v1-mint-
- run:
name: Mint install
command: |
mkdir -p $MINT_PATH/{lib,bin}
mint bootstrap --link
environment:
MINT_PATH: /Users/distiller/output/mint
MINT_LINK_PATH: /Users/distiller/output/mint/bin
- save_cache:
key: v1-mint-{{ checksum "Mintfile" }}
paths:
- mint
setup_carthage:
description: "Setup carthage with caches"
steps:
- run:
name: Set Xcode Version
command: echo `xcodebuild -version` > XCODE_VERSION
- restore_cache:
keys:
- v1-ca-{{ checksum "XCODE_VERSION" }}-{{ checksum "Cartfile.resolved" }}
- v1-ca-
- run:
name: Carthage bootstrap
command: $MINT_PATH/bin/carthage bootstrap --platform iOS --cache-builds
- save_cache:
key: v1-ca-{{ checksum "XCODE_VERSION" }}-{{ checksum "Cartfile.resolved" }}
paths:
- Carthage
#!/bin/sh -x
which mint;
if [ $? -eq "0" ]; then
brew outdated mint || brew upgrade mint
else
brew install mint
fi
Carthage/Carthage@0.36.0
ポイントは、Carthageのインストールを、Mintで行っているところです。
こうすることにより、Carthageのバージョンを指定できます。
※この場合、CircleCIのデフォルトでインストールされているCarthageを誤って使ってしまわないように注意
Mintの使い方は、以下のサイトをご参照ください。
Swift製コマンドラインツールのパッケージ管理ツール「Mint」のセットアップ&操作方法 - Qiita
Homebrewを利用するのをやめて、Swift Package Managerでインストールする
Carthageをアップデートすることは出来るようになりましたが、HomebrewはキャッシュをRestoreするだけでも30secかかるので、Homebrew以外の方法がないかを検討してみました。
Mintのインストール方法としては、Homebrewでインストールする方法の他に、MakeやSwift Package Managerでインストールする方法があります。
https://github.com/yonaskolb/Mint#installing
このうち、Makeは、その時点で、問題があったので、今後、Swift製のツールをまとめることも考えて、Swift Package Managerを、使った方法も紹介します。
commands:
setup_mint_tools:
description: "Setup Mint tools with caches"
steps:
- restore_cache:
name: SPM restore cache
keys:
- v1-spm-mint-{{ arch }}-{{ checksum "MintTools/Package.resolved" }}
- run:
name: Build SPM for Mint
command: ./MintTools/.build/release/mint --version || swift run -c release --package-path MintTools --build-path MintTools/.build mint --version
- save_cache:
name: SPM save cache
key: v1-spm-mint-{{ arch }}-{{ checksum "MintTools/Package.resolved" }}
paths:
- MintTools
その他、やったこと
- aliasesをやめて、commandにする(それに伴って、config.ymlのバージョンも2から2.1にアップ)
- bundle install に、並列処理の設定(BUNDLE_JOBS: 4)を追加
- 対応するキャッシュが見つからなかった場合に、保存されている中で最新のキャッシュを取得するようにした
- キャッシュのキーのプレフィックスに「v1」を追加
- Xcodeのバージョンをアップする時にエラーにならないように、Carthageのキャッシュのキーにxcodeのバージョンを追加
- マシンが変わってもエラーにならないようにする
aliasesからcommandsに変更(それに伴って、config.ymlのバージョンも2から2.1にアップ)
紹介した、commandsの中のsetup_carthageは、もともと、以下のように、aliasesで書いてました
これだと、扱いづらく、説明文も記載できないので、commandsに変更しています。もともと、ymlのバージョンを2だったのを、2.1に変更して、commandsに書き換えました。
aliases:
- &step_restore_carthage_cache
restore_cache:
key: ca-{{ checksum "Cartfile.resolved" }}
- &step_carthage_bootstrap
run:
name: Carthage
command: $MINT_PATH/bin/carthage bootstrap --platform iOS --cache-builds
- &step_save_carthage_cache
save_cache:
key: ca-{{ checksum "Cartfile.resolved" }}
paths:
- Carthage
bundle install に、並列処理の設定(BUNDLE_JOBS: 4)を追加
iOSアプリの開発では、cocoapodsのバージョン管理をするために、bundlerを使うことがよくあるかと思いますが、
その、bundle installを、オプションで並列処理の設定をすると、高速化できるらしいので設定されてなかったのを設定したりしました。
安全で高速なbundle install(ディレクトリを指定&並列化&省略オプション) - Qiita
対応するキャッシュが見つからなかった場合に、保存されている中で最新のキャッシュを取得するようにした
参考
依存関係のキャッシュ - CircleCI
【ruby×circleci】はじめてのCI環境の構築④ / キャッシュを使ってビルドを効率化する - 日々の学びのアウトプットするブログ
キャッシュのキーのプレフィックスに「v1」を追加
キャッシュは上書き不可の為、依存関係管理ツールのバージョンや言語のバージョンが変更された場合、プロジェクトから依存関係が削除された場合などに、キャッシュを参照すると問題が出た場合など、数字を変更してキャッシュをクリア(参照しない)できるように
参考
依存関係のキャッシュ - CircleCI
Xcodeのバージョンをアップする時にエラーにならないように、Carthageのキャッシュのキーにxcodeのバージョンを追加
参考
ios circleciでcarthageのキャッシュが効かなかった原因と対応 - とりあえずphpとか
マシンが変わってもエラーにならないようにする
最初、キャッシュの上書きが出来ないので、epoch を使って、毎回、キャッシュを作成し、リストア時には、前方一致で最新のキャッシュを取り出してました
- restore_cache:
keys:
- v1-brew-
- save_cache:
key: v1-brew-{{ epoch }}
これだと、マシンが変わったときに、問題になりそうだということで、archを追加してみました。
- restore_cache:
keys:
- v1-brew-{{ arch }}-
- save_cache:
key: v1-brew-{{ arch }}-{{ epoch }}
ただ、これも無駄が多いので、最終的に、Homebrewをやめ、Swift Package Managerを使って、Package.resolved を使ったchecksumで、必要な分のキャッシュが作成され、ピンポイントでリストアすることができるようになったので、何をリストアしているのか明確に把握できるようになりました。
まとめ
この記事を見たかたが、参考になったらな嬉しいなと思いますが、詳細は書きれていないので、もし、質問があれば、出来るだけ回答しようと思っています。
今回、いろいろ改修したおかげで、可読性も保守性も、かなり改善しました。
おかげで、いろいろ勉強になりました。
これをやる直前まで、Bitriseに乗り換えも検討していましたが、慣れると、CircleCIの方が、設定ファイルを見れば、何をしているかがシンプルで分かりやすいので、いいなと感じました。