ruby-buildとrbenvのプラグイン機構
前回の記事 に続いて rbenv ネタ。 今回は、ruby-build と rbenv の関係について。
ruby-build とは何か
ruby-build とは、あらゆるバージョンの ruby を簡単にインストールするためのコマンドラインユーティリティ。本家の README には次のように書かれている。
ruby-build is a command-line utility that makes it easy to install virtually any version of Ruby, from source.
ruby-build は rbenv のプラグインとして使うこともできるし、スタンドアロンなコマンドとして使うこともできる。
多くの ruby ユーザは前者の rbenv のプラグインとして使っていることと思う。自分もそうなので、今回はこちらについて少し深堀っていく。
rbenv install コマンドは ruby-build が提供している
rbenv --help コマンドを実行すると次のような結果が出力される。
❯ rbenv --help
Usage: rbenv <command> [<args>]
Some useful rbenv commands are:
commands List all available rbenv commands
local Set or show the local application-specific Ruby version
global Set or show the global Ruby version
shell Set or show the shell-specific Ruby version
install Install a Ruby version using ruby-build
uninstall Uninstall a specific Ruby version
rehash Rehash rbenv shims (run this after installing executables)
version Show the current Ruby version and its origin
versions List installed Ruby versions
which Display the full path to an executable
whence List all Ruby versions that contain the given executable
See `rbenv help <command>' for information on a specific command.
For full documentation, see: https://github.com/rbenv/rbenv#readme
ここにある install というサブコマンドは、実は rbenv 本体には組み込まれておらず、rbenv プラグインとしての ruby-build が提供しているコマンドになっている。
その証拠に、rbenv core のコマンドが含まれる libexec ディレクトリ以下 を見ても install 機能を提供する実行ファイルは含まれていない。
実際に自分の環境の rbenv/libexec 以下を見ても該当するファイルが存在しないことを確認した (※ rbenv 自体は homebrew で install している)。
❯ ls -1 /usr/local/Cellar/rbenv/1.1.2/libexec/ | grep install
# => 結果なし
rbenv はどのようにしてプラグインを読み込むか
では rbenv 本体はどのようにしてプラグインとしての ruby-build を読み込み、install コマンドをはやしているか?
これを理解するには、rbenv のプラグイン機構について知る必要がある。
rbenv 公式の Wiki を見ると、パスが通っているところに rbenv-COMMAND という形式で配置すれば良い といった旨の記述が見つかる。
つまり、install サブコマンドを生やす ruby-build の場合は rbenv-install という実行ファイルが PATH の通っているどこかに存在するということになる。
実際に自分の環境で調べてみたところ、/usr/local/bin/ 以下に rbenv-install ファイルを見つけることができた。/usr/local/bin にはもちろんパスが通っている。
❯ file /usr/local/bin/rbenv-install
/usr/local/bin/rbenv-install: Bourne-Again shell script text executable, ASCII text
# パスが通っていることを確認
❯ echo $PATH | grep --only-matching '/usr/local/bin'
/usr/local/bin
なお、自分の場合は ruby-build も homebrew でインストールしているので、実際には /usr/local/Cellar/ruby-build/VERSION/bin/rbenv-install へのシンボリックリンクとなっている。
おまけ:さらに深ぼってみる
ここまで来たので、ソースコードレベルでもう少し深ぼってみる。
rbenv-COMMAND という形式で PATH が通っているところに配置すれば実行してくれるのはわかったが、具体的にどうやって探索しているか?を見てみたい。
以下は、rbenv コマンドの実行ファイルの抜粋。
command="$1"
case "$command" in
"" )
{ rbenv---version
rbenv-help
} | abort
;;
-v | --version )
exec rbenv---version
;;
-h | --help )
exec rbenv-help
;;
* )
command_path="$(command -v "rbenv-$command" || true)"
if [ -z "$command_path" ]; then
if [ "$command" == "shell" ]; then
abort "shell integration not enabled. Run \`rbenv init' for instructions."
else
abort "no such command \`$command'"
fi
fi
shift 1
if [ "$1" = --help ]; then
if [[ "$command" == "sh-"* ]]; then
echo "rbenv help \"$command\""
else
exec rbenv-help "$command"
fi
else
exec "$command_path" "$@"
fi
;;
esac
今回の ruby-build を例に考えると rbenv install .. とコマンドを実行したときのことを考える。以下、箇条書きでざっくりまとめる。
command="$1"の部分は、rbenv コマンドの次に渡される引数がくるのでinstallという文字列が代入される- case の最初の3つの条件には当てはまらず、
* )の分岐に入る - この次が肝。
command_path="$(command -v "rbenv-$command" || true)"を実行して、rbenv-installコマンドのパスを取得している。- 実際に自分の環境で
command -v rbenv-installを実行すると/usr/local/bin/rbenv-installという結果が得られた
- 実際に自分の環境で
if [ -z .. ]; thenの部分は、引数の文字列長が0のときに真となるので、該当しないのでスキップshift 1する。これにより次の$1に入る値がrbenv install xxxコマンドを例にするとxxxに当たる。$1は--helpでは無いので else 節へexecでrbenv-installが実行される
結論、command コマンドで rbenv-install のパスを取得したあとは exec に引き渡して実行するだけだった。
まとめ
- ruby-build とは、あらゆるバージョンの ruby を簡単にインストールするためのコマンドラインユーティリティ
- rbenv の install コマンドは、rbenv プラグインとしての ruby-build が提供している
- rbenv は
rbenv-COMMANDという形式の実行可能ファイルを PATH が通っているところに配置しておけば読み込んでくれる