開発中の Gem に同梱するコマンドが CircleCI の Bundler に認識されない現象と解決方法

Circle CI でテストしながら Gem を開発している際に、Gem に追加したコマンドが認識されない現象と原因、解決方法について。

シチュエーション

以下のようなcircle.ymlの下で Gem を開発しているとする。

dependencies:
  pre:
    - gem install bundler

Gem に同梱するコマンドとしてexe/fooを追加(.gemspecbindirにはexeを指定済み)し、コマンドが正常動作するかを CircleCI 上で確認するためにcircle.ymlに下記を追加する。

test:
  post:
    - bundle exec foo

ここまでの変更を push して CircleCI 上でジョブを起動すると、testステップで以下のようなメッセージと共に失敗する。

$ bundle exec foo

bundler: command not found: foo
Install missing gem executables with `bundle install`

bundle exec foo returned exit code 127

実行コマンドとして追加した筈のfooが認識すらされていない。

原因

恐らくだが、CircleCI のキャッシュが原因。

bundle execでコマンドを実行する場合、開発中の Gem のコード(lib/**/*.rb)はレポジトリ内のパスを参照するが、実行ファイル(exe/foo)はbundle installでインストールしたパス(vendor/bundle)を参照する。

CircleCI はビルド開始時に依存ライブラリをvendor/bundleにダウンロードし、ビルド終了後にその内容をキャッシュする。次回ビルド時にはbundle checkで依存ライブラリに変更があるかを確認し、変更がある時だけbundle installを再実行する。これは CircleCI の dependency ステップのコマンドを確認するとわかる。

$ bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3 

開発中Gemへの実行ファイル追加はGemfile.lockへの変更とはならない、つまり依存ライブラリの変更とは見なされない。したがってbundle installが実行されず追加した実行ファイルもvendor/bundle以下に入らない。

確認してないが、たぶん追加だけではなく、実行ファイルのコードを修正した場合でも修正が反映されない事象が起きる気がする。

解決方法

もし開発しているGemの依存ライブラリに修正が入れば、そのタイミングでbundle installが実行されるので解決する。が、もちろんそう都合良くはいかないので、CircleCI の UI からキャッシュを削除して再実行すればいい。各ビルド結果のページの右上から「Rebuild without cache」を選択すれば、その場でキャッシュ(≒ vendor/bundle)を削除して再ビルドされる(そしてfooがコマンドとして認識される筈)。

f:id:autopp:20170502174032p:plain