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が通っているところに配置しておけば読み込んでくれる