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

h3pei
フリーランスのソフトウェアエンジニア。Ruby / Rails アプリケーションの開発が得意領域。設計・実装・運用まで含めてプロダクト開発が好きです。
MENTAでアプリケーション開発や学習の支援・伴走もしています。