これはSpeee Advent Calendarの1日目の記事です。
iOSアプリエンジニアの@hiragramです。
私がいるチームでは、依存ライブラリ管理にCarthage、CIにTravisCIを使っています。
また、ライブラリのバージョンアップに積極的についていくために、TravisCIのCron Jobsとhubコマンドを使い、定期的に依存ライブラリの最新バージョンをチェックして、アップデートがあればGitHubにPRを投げるような仕組みを構築しています。
実際に投げられるPRはこんな感じです。僕のトークンを使ってるので僕が作ったみたいになってますが、CIのタスクから作られたものです。
パッチやマイナーアップデートの優先度は下がりがちだと思います。しかし、Swiftは言語のバージョンが比較的速いスピードで上がっていくこともあり、ライブラリにバグが残る可能性は大いにあるのでこまめに更新しておきたいところです。
やってみましょう
- TravisCIサポートに連絡してcronを開放してもらう
- cronの設定をする
- 環境変数の設定をする
- cron用のスクリプトを用意する
- PRの本文を作るスクリプトを用意する
このようなステップで準備していきます。
TravisCIでcronを有効にする
Cron Jobsのページにあるように、cron機能はデフォルトでは設定できなくなっているので、サポートに連絡して有効にしてもらう必要があります。
TravisCIのダッシュボード左上の Help -> Email Support から Please enable cron jobs configuration on "Hogehoge" organization. とか送っておけば、翌営業日には対応してくれます。
cronの設定をする
リポジトリを選択して、設定画面を開きます。
サポートから返事が来ていれば、設定項目の中に"Cron Jobs"があるはずです。
ここで定期的に実行するブランチと周期と実行条件を設定します。
私たちは、週に1回masterブランチで実行するようにしています。
ちなみにtipsですが、毎週のタスクを任意の曜日や時間に設定する項目はありませんが、cronの設定をAddした時(金曜17時に設定したなら毎週金曜17時)に実行されるようになります。
タスク内でcarthage update
を実行するため非常に時間がかかります。私たちは月曜の朝7時にしています。
TravisCIに環境変数を設定する
Personal access tokensのページで、TravisCIからPRを作るのに使うトークンを発行します。
TravisCIの設定画面で、GH_TOKEN
に発行したトークンを、GH_USER
にGitHubのユーザー名を書きます。
cronから起動された時用のビルドタスクを書く
各種タスクは.travis.yml
でシェルスクリプトを指定してその中に書くようにしています。
# .travis.yml language: objective-c osx_image: xcode8 script: - ./.travis_scripts/build.sh notifications: slack: ********** before_install: - ./.travis_scripts/before_install.sh before_script: - sudo systemsetup -settimezone Asia/Tokyo
cronによって実行されている時、環境変数$TRAVIS_EVENT_TYPE
がcron
という値になっているので、それをみて処理を切り替えるようにしています。
#!/bin/bash # build.sh if [ $TRAVIS_EVENT_TYPE = cron ] ; then if carthage outdated | grep "All dependencies are up to date." ; then exit 0 else carthage update --platform ios --configuration Debug createPR fi else carthage bootstrap --platform ios --configuration Debug runTest fi
$TRAVIS_EVENT_TYPEがcronだった場合、まずcarthage outdated
でアップデートがあるライブラリを探します。
もしもアップデートがあるようならcarthage update
で更新します。
最後に、自分で定義したcreatePR
を実行します。
createPR
の中身はこんな感じです。
createPR() { CREDENTIAL_FILE="$HOME/.config/git-credential" mkdir -p $HOME/.config echo "https://${GH_TOKEN}:@github.com" > $CREDENTIAL_FILE echo "github.com: - oauth_token: $GH_TOKEN user: $GH_USER" > $HOME/.config/hub which hub if [ $? != 0 ] ; then brew install hub fi git config --global user.name "TravisCIたん" git config --global user.email "hiragram@users.noreply.github.com" git config --global hub.protocol "https" git config --global credential.helper "store --file=$CREDENTIAL_FILE" BRANCH_NAME="dependencies_autoupdate_"`date "+%Y-%m-%d_%H-%M-%S"` git checkout -b $BRANCH_NAME touch pr.txt echo "Detected outdated dependencies." > pr.txt echo "# Outdated dependencies" >> pr.txt; git diff Cartfile.resolved | perl .travis_scripts/check_version.pl >> pr.txt # 後述 git add Cartfile.resolved if git commit -m "[Auto generated] Update dependencies" ; then git push origin $BRANCH_NAME hub pull-request -F pr.txt fi }
処理の流れとしては、
- 環境変数に入れたトークンとユーザーをhubの設定ファイルに書き出す
- hubが入ってなければ入れる
- gitの設定
- ブランチ切る
- PRのタイトル/本文をファイルに書き出す
- リモートにpushする
- PRを作る
という感じです。
PRの本文を作るPerlスクリプトを書く
先程のシェルスクリプトの中に
git diff Cartfile.resolved | perl .travis_scripts/check_version.pl >> pr.txt
とありましたが、これの解説をします。 PRの本文には
## Quick/Nimble v5.0.0 -> [v5.1.0](https://github.com/Quick/Nimble/releases/tag/v5.1.0) [compare](https://github.com/Quick/Nimble/compare/v5.0.0...v5.1.0)
- 導入済みのバージョン
- 最新バージョンとリリースノートへのリンク
- そのバージョン間のdiffへのリンク
が記載されています。 これを作る為にPerlでこのようなスクリプトを書きました。
#!/usr/bin/perl use strict; use warnings; my $input = ""; while(my $line = <STDIN>) { $input .= $line; } while ($input =~ /\-github "(.*?)" "(.*?)"/gms) { my $repository = $1; my $oldVersion = $2; if ($input =~ /\+github "$repository" "(.*?)"/ms) { my $newVersion = $1; my $oldDispVer = $oldVersion; my $newDispVer = $newVersion; if (length($oldVersion) > 25 && length($newVersion) > 25) { $oldDispVer = substr($oldVersion, 0, 6); $newDispVer = substr($newVersion, 0, 6); } print("## $repository\n"); print("$oldDispVer -> [$newDispVer](https://github.com/$repository/releases/tag/$newVersion)\n"); print("[compare](https://github.com/$repository/compare/$oldVersion...$newVersion)\n"); } }
carthage update
を実行すると、アップデートがあった場合にCartfile.resolved
が更新されます。
git diff Cartfile.resolved
の結果を標準入力に渡してやると、先程のPRの本文を生成するスクリプトです。
試しに動かしてみる
準備の手順は以上です。
通常のpushで動くタスクはTRAVIS_EVENT_TYPE
がcron
では無いので、テストの段階ではシェルスクリプトの中でTRAVIS_EVENT_TYPE=cron
としてやるといいでしょう。
まとめ
従来は依存パッケージ7個のビルドだけで20分ほどかかっていましたが、そこが短縮されたのは非常に大きかったです。
細かいアップデートは、放置すればするほど後回しになってしまい、健全さが失われていくと思います。 とはいえ、人間が定期的にアップデートをチェックして更新するのも手間なので、自動化してしまえということで今回の仕組みを構築しました。
この仕組みを導入する前は、気づいたときにGitHubをみて新しいリリースがないかを確認していましたが、導入後はある程度ほったらかしでもアップデートに追随できるようになりました。
今のところは破壊的な変更のないマイナーアップデートまでしか自動化出来ていないので、メジャーアップデートも同じような仕組みで追随できるようにしたいと思っています。