追記
Gemのキャッシュについてはbitrise公式で手順が解説されていたので、こちらをご覧ください。
はじめ
brewのcacheは他のCIでも使えるかも
公式のサポート記事があった。
キャッシュについての記事も分かり易かった。
BitriseではRubyのGemがインストールされるディレクトリを gem environment gemdir
で取得できる。そのディレクトリを環境変数で保存しておいて、最後にCache:pushする一覧に入れておく。
cache:pullではキャッシュされたディレクトリをダウンロードしてくるので、次回の gem install
からはそれを見に行ってる。
数十秒かかっていた gem install xxx
が6秒ぐらいになった
とりあえず投稿して、フォーマットは後で整形します。
#1.Cache設定用スクリプトのステップを追加
ステップの場所: Git cloneとCache:Pullの間
名前は適当にGem cache settings
とかにした。
#!/usr/bin/env bash
# fail if any commands fails
set -e
# debug log
set -x
# cacheのための設定
set -ev
envman add --key GEM_HOME --value "$(gem environment gemdir)"
#2.gem install xxx
スクリプトのステップを追加
ステップの場所: Cache:PullとCache:Pushの間
#!/usr/bin/env bash
# fail if any commands fails
set -e
# debug log
set -x
# cacheのための設定
set -ev
gem install slather #例えば、iOSのカバレッジ出力
#3. Cache:Pushのパス一覧に$GEM_HOMEを追加
Cache paths
というフィールドの最後の行を改行して、 $GEM_HOME
を追加する。
ステップの位置関係まとめ
- Git Clone
- Gem cache settings
- Cache:Pull
- gem install
- Cache:Push
※各ステップの上下には別のステップが入っても問題ない
brewのキャッシュ
記事がなかったのでHomebrewのソースコード読んだ。
どういう仕組みでinstall済みのfomulaを再installせずに済ませているのかを考えたら、
xxx is already installed and up-to-date
To reinstall 1.8.3, run `brew reinstall xxx`
と表示される条件を見つければ良いと思ったので、installコマンドの中身を見に行って、この文章を見つけた。
#多分コマンドラインで引数に渡したformulaを見に行ってeachで取り出してる。
#--forceオプションがbrewにあって、このソースの途中に `unless ARGV.force?` っていう構文あるから多分あってる。
ARGV.formulae.each do |f|
(略
if f.keg_only? && f.any_version_installed? && f.optlinked? && !ARGV.force?
# keg-only install is only possible when no other version is
# linked to opt, because installing without any warnings can break
# dependencies. Therefore before performing other checks we need to be
# sure --force flag is passed.
if f.outdated?
optlinked_version = Keg.for(f.opt_prefix).version
onoe <<~EOS
#{f.full_name} #{optlinked_version} is already installed
To upgrade to #{f.version}, run `brew upgrade #{f.name}`
EOS
elsif ARGV.only_deps?
formulae << f
else
opoo <<~EOS
#{f.full_name} #{f.pkg_version} is already installed and up-to-date
To reinstall #{f.pkg_version}, run `brew reinstall #{f.name}`
EOS
end
(略
一番下のelseに到達できればキャッシュできたと言えそう。
###条件判定メソッド
method | 方策、調査結果 |
---|---|
keg_only? | /usr/local/Cellarには存在するが、/usr/local/bin, /usr/local/lib などにリンクされていないformula |
any_version_installed? | 最低でも一つのバージョンのformulaがインストール済みかどうか。 |
optlinked? | /usr/local/opt 以下にシンボリックリンクが存在するかどうか 。 |
outdated? | 古いパッケージが入ってる |
only?deps? | --only-dependenciesオプションをつけるとこのフラグがtrueになる |
Homebrewのkegとcellarの関係が分かりにくいからkeg_onlyが分かりずらい。
kegとは特定のformulaのインストール先Pathであり、cellarはkegのインストール先パス
つまり**/usr/local/Cellar/keg/**
という関係性。
なのでkeg_only?は、「Cellar以下にしかビルドが存在せず、/usr/local/binや/usr/local/libにシンボリックリンクが作成されていない」という状態を表す。
方策
/usr/local/Cellarをキャッシュすれば行けそう。
/usr/local/opt/をキャッシュすれば行けそう。
結果
できた。
70秒弱かかっていた brew install xxx
が8秒弱になった
1: Cache:Pullの前に、brewがインストール/シンボリックリンク作成/ダウンロードされるパスを環境変数に追加しておく。
envman add --key BREW_LICENSE_PLIST --value "$(brew --cellar)/license-plist"
envman add --key BREW_OPT_LICENSE_PLIST --value "/usr/local/opt/license-plist"
ちなみに以下は追加しなくていい。
envman add --key BREW_CACHE_LICENSE_PLIST --value "$(brew --cache)/license-plist"
homebrewのキャッシュってことで関係あるっぽいけど、コマンドを叩いてみると中身がわかる。 /Library/Caches/Homebrew/
以下には、ダウンロードした圧縮ファイル(tar)が入るだけなので、CI上でのキャッシュ時には必要ない。(Cellarにビルドが入ってる前提なので)
$ ls ` brew --cache `
carthage-0.29.0.high_sierra.bottle.tar.gz
gettext-0.19.8.1.high_sierra.bottle.tar.gz
libidn2-2.0.5.high_sierra.bottle.tar.gz
license-plist-1.8.3.tar.gz
mint-0.9.1.high_sierra.bottle.tar.gz
openssl-1.0.2o_1.high_sierra.bottle.tar.gz
portable-ruby-2.3.3_2.leopard_64.bottle.tar.gz
swagger-codegen-2.3.1.high_sierra.bottle.tar.gz
swiftlint-0.25.1.high_sierra.bottle.tar.gz
wget-1.19.5.high_sierra.bottle.tar.gz
...
2: cache:pullとcache:pushの間でbrew install
brew link xxx
をしておくと、/usr/local/bin/ にシンボリックリンクが作られるので、こうしておく。(再インストールを回避するための条件でないのでキャッシュはしてない)
brew tap mono0926/license-plist
brew install license-plist
brew link license-plist
3: cache:pullにパス追加
自分の場合はこうなった。一番上はデフォ。二番目はgem。それ以下はbrewの特定のfomula用。
$BITRISE_CACHE_DIR
$GEM_HOME
$BREW_LICENSE_PLIST
$BREW_OPT_LICENSE_PLIST
$BREW_CACHE_LICENSE_PLIST
(おまけ)Bitriseの挙動
Cache:Push
Cache:PushはデフォルトではPRでは走らない。
なのでPR時に走らせるように挙動を変えたければ以下のように run_if: ".IsCI"
を追加する必要がある。
- cache-push:
run_if: ".IsCI"
inputs:
- cache_paths: |-
...
キャッシュの意義
キャッシュはAWSのS3で保存されCloudFrontでキャッシュされている。毎回そこからキャッシュをダウンロードしている。
サイズが大きくなるようなキャッシュは、ダウンロード時間も増えるためメリットが少ない。
インストール時間 > ダウンロード時間 のようなパッケージはキャッシュするメリットがある。
/usr/local/Cellar/ のキャッシュ&ダウンロードはものすごく重たいので、このようにformulaを指定している。
envman add --key BREW_LICENSE_PLIST --value "$(brew --cellar)/license-plist"
Vagrantの立ち上げ時にすでに色々入っているので、 /usr/local/Cellar/ のキャッシュを試みたら3GB弱のキャッシュになった 普段は700MB弱。
Homebrewの挙動
brew installが呼ばれた時、すでにインストール済みかどうかなど色々チェックした後に新規インストールが必要であれば、FormulaInstaller
が初期化されてインストールが始まる。
- kegにformulaがインストールされる
- /usr/local/opt/へのシンボリックリンクが作成される
- /usr/local/bin/ へのシンボリックリンクが作成される