binstub の Ruby Shebang ディレクティブ
最終更新日 2020年07月16日(木)
“shebang” 行は次のようになります。
#!/usr/bin/env ruby
不正な shebang が binstub に含まれていると、アプリケーションが正常に動作しない可能性があります。以下は、アプリケーションに不正な Ruby shebang 行がある場合に発生する可能性があるエラーの例です。
"Your Ruby version is 2.5.1, but your Gemfile specified 2.6.6"
"Activating bundler (>= 0.a) failed ... Could not find 'bundler' (>= 0.a)"
You have already activated bundler 1.17.2, but your Gemfile requires bundler 1.17.3
この記事では、binstub と shebang 行について、また、適切に生成されなかった binstub が原因で発生する一般的なエラーについて説明します。
binstub とは
“binstub” は “バイナリスタブ” の略です。アプリに含まれる小さなスクリプトで、一般に bundler または Web フレームワークによって生成されるものです。Ruby の一般的な慣習では、アプリケーションの bin/
ディレクトリに “binstub” を配置します。Rails 6 プロジェクトの binstub の例を次に示します。
$ cat bin/rails
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
上記の binstub の 1 行目は次のとおりです。
#!/usr/bin/env ruby
これは shebang 行として知られるものです。ファイルの実行方法を UNIX ベースのオペレーティングシステムに指示するマジックコメントです。先頭の #!
は、後続の記述で実行可能ファイルを指定することを表します。それに続く /usr/bin/env
は、UNIX オペレーティングシステムの env
プログラムの絶対パスです。最後の ruby
は、実行のためにスクリプトで使用するプログラムの名前です。/usr/bin/env ruby
の組み合わせは、基本的には、PATH
の先頭にある ruby
実行可能ファイルを使用するようオペレーティングシステムに指示しています。
binstub では Ruby 以外も使用できます。その他の一般的な言語には bash
、sh
、 python
があります。
以下に、Ruby の shebang 行の例をいくつか示しています。
Ruby の shebang の例 | 正しい/不正 |
---|---|
#!/usr/bin/env ruby |
正しい |
#!/usr/bin/env ruby2.5 |
不正 |
#!/usr/bin/env ruby2.4 |
不正 |
“shebang” 行の形式が正しくないか、存在しないバイナリまたは想定と異なるバイナリを参照している場合、プログラムはクラッシュするか、異常な動作をします。次に示すのは、よくある不正な binstub の例です。
#!/usr/bin/env ruby2.5
これが “不正な” binstub である理由は、アプリケーションを Heroku にデプロイすると、Ruby バイナリは ruby2.5
ではなく ruby
として PATH にインストールされるからです。Heroku で使用する Ubuntu オペレーティングシステム (“スタック” と呼ばれる) では、この実行可能ファイルが PATH に含まれている場合があり、事態をより複雑にしています。たとえば、Heroku-18 スタックでは次のとおりです。
$ heroku run bash
$ which ruby2.5
/usr/bin/ruby2.5
このシナリオでは、Ruby 2.7.1 を使用することをアプリケーションで Gemfile
によって指定しているのに対して shebang 行では ruby2.5
を使用している場合、アプリケーションは正しくない Ruby バージョンの使用を強制されることになります。通常はこのバグによって、クラッシュと、理解が困難な bundler のエラーが発生します。
不正な shebang 行の修正
不正な “shebang” 行を手動で次のように編集できます。
#!/usr/bin/env ruby
この修正が終わったら必ず、結果を Git に再コミットしてください。
$ git add .
$ git commit -m "fixing shebang lines"
$ git push heroku master
Ruby のエコシステムに binstub が存在する理由
bin/rails
のようなファイルが Ruby のエコシステムに導入された理由は、bundle exec rails
のように完全なコマンドを記述する必要がある冗長な呼び出しを減らすためです。その代わりに、このスクリプトは、Rails のバージョンのロードを試みる前に、手動で bundler をロードして bundler の設定を呼び出します。ファイルの本来の意図はこのようなものでしたが、その機能は前述のものに限定されていません。たとえば、以前のバージョンでは spring
gem のロードと設定も行っていましたが、これは過去に、バグを含む binstub のバージョンと関連したときに、特に問題の原因になってきました。
不正な shebang 行の存在に加えて、binstub にバグが含まれている場合もあります。これらのファイルはそのうち変更されるかもしれませんが、最初の rails new <app-name>
コマンドによって生成される性質上、古くなっている可能性もあります。Rails プロジェクトの binstub を更新する場合、そのためのコマンドは次のとおりです。
$ bin/rake app:update:bin
exist bin
identical bin/rails
identical bin/rake
conflict bin/setup
Overwrite /app/bin/setup? (enter "h" for help) [Ynaqdhm] n
skip bin/setup
identical bin/yarn
bundler binstub の生成を強制する
次の Docker ベースのプロジェクト生成でこの問題が発生しないよう、ファイルを生成する前に Dockerfile でこれを設定することができます。
ENV BUNDLE_SHEBANG=ruby
これは生成済みの binstub には影響しません
shebang 行は Rails 由来の場合もあれば、bundler 由来の場合もあります。次に示すのは、bundler テンプレートファイルに固有のコミットリファレンスです。
#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %>
bundler は、カスタマイズされた “shebang” 設定をこのテンプレートから探し、見つからない場合はコード RbConfig::CONFIG["ruby_install_name"]
を実行します。Docker イメージでプロジェクトを生成した場合や、Ruby のインストールが “名前付き” であった場合、これは設定済みである可能性があります。
$ docker run -it --rm heroku/heroku:18-build bash
root@58980a533208:/# ruby -e 'puts RbConfig::CONFIG["ruby_install_name"]'
ruby2.5