rbenvを使ってみる - すぎゃーんメモの続き。
現時点でのrbenvのバージョンは0.2.1。
rbenvを使っていると.rbenv-version
ファイルの有無でrubyコマンド打ったときに実行されるrubyが違うものになる、というのがちょっと新鮮で、これはどういう仕組みで動いているのだろう?と思って少し調べてみた。
上記記事のようにrbenvの設定をした環境では、
$ which ruby /Users/sugyan/.rbenv/shims/ruby
となり、${RBENV_ROOT}/shims
以下のrubyを指すことになる。ここへのPATHは$HOME/.rbenv/libexec/rbenv-init
に
echo 'export PATH="'${RBENV_ROOT}'/shims:${PATH}"'
と書かれているので、eval "$(rbenv init -)"
してあれば優先して通っているはず。
で、この${RBENV_ROOT}/shims/ruby
の中身を見てみると…
#!/usr/bin/env bash set -e export RBENV_ROOT="/Users/sugyan/.rbenv" exec rbenv exec "${0##*/}" "$@"
とだけ書いてある。これは同ディレクトリにあるgem
やirb
やrake
なども全部同じ内容。結局このPATHに入っているコマンドを呼ばれた場合はすべて同じように処理される、ということになる。
このシェルスクリプトの内容としては
set -e
でエラー時の処理を設定。全然知らなかったけどbashのドキュメントによると
Exit immediately if a pipeline (see Pipelines), which may consist of a single simple command (see Simple Commands), a subshell command enclosed in parentheses (see Command Grouping), or one of the commands executed as part of a command list enclosed by braces (see Command Grouping) returns a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of any command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command's return status is being inverted with !. A trap on ERR, if set, is executed before the shell exits.
http://www.gnu.org/software/bash/manual/bashref.html#The-Set-Builtin
というものらしい。例えば
#!/usr/bin/env bash set -e test -e $0 echo 'finish!'
とかだと正常に最後の"finish!"が出力されるが、
#!/usr/bin/env bash set -e test -d $0 echo 'finish!'
だと3行目のtestコマンドが0でない終了コマンドを返すので、その時点でこのシェルスクリプトが終了して"finish!"は出力されない。
毎行ごとにエラーチェックと終了処理を挟むような場合はset -e
をしておくと良い、ということか。
ちょっと脱線した。肝心なのは
export RBENV_ROOT="/Users/sugyan/.rbenv" exec rbenv exec "${0##*/}" "$@"
の部分。変数展開${0##*/}
は
The word is expanded to produce a pattern just as in filename expansion (see Filename Expansion). If the pattern matches the beginning of the expanded value of parameter, then the result of the expansion is the expanded value of parameter with the shortest matching pattern (the ‘#’ case) or the longest matching pattern (the ‘##’ case) deleted. If parameter is ‘@’ or ‘*’, the pattern removal operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is an array variable subscripted with ‘@’ or ‘*’, the pattern removal operation is applied to each member of the array in turn, and the expansion is the resultant list.
http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion
ということでフルパスから最後の"/"までの部分を取り除いたものを取得することになる。${RBENV_ROOT}/shims/ruby
を
#!/usr/bin/env bash set -e export RBENV_ROOT="/Users/sugyan/.rbenv" echo $0 echo ${0##*/} # exec rbenv exec "${0##*/}" "$@"
と書き換えて${RBENV_ROOT}/shims/ruby
を呼んでみると、
$ ruby /Users/sugyan/.rbenv/shims/ruby ruby
となる。つまり、${RBENV_ROOT}/shims/ruby
を呼ばれた場合は最終的には
exec rbenv exec ruby "$@"
と実行される形になる。rbenv exec
は内部的には${RBENV_ROOT}/libexec/rbenv-exec
をexec
する形になっていて、このrbenv-exec
内では
... RBENV_COMMAND="$1" ... RBENV_COMMAND_PATH="$(rbenv-which "$RBENV_COMMAND")" RBENV_BIN_PATH="${RBENV_COMMAND_PATH%/*}" ... shift 1 export PATH="${RBENV_BIN_PATH}:${PATH}" exec -a "$RBENV_COMMAND" "$RBENV_COMMAND_PATH" "$@"
となっていて、rbenv-which
から取得できた"実行すべきrubyの実体へのPATH”を取得して、exec
して引数をわたしてやる、ということをしているようだ。
で、${RBENV_ROOT}/libexec/rbenv-which
は
... RBENV_VERSION="$(rbenv-version-name)" RBENV_COMMAND="$1" ... if [ "$RBENV_VERSION" = "system" ]; then PATH="$(remove_from_path "${RBENV_ROOT}/shims")" RBENV_COMMAND_PATH="$(command -v "$RBENV_COMMAND")" else RBENV_COMMAND_PATH="${RBENV_ROOT}/versions/${RBENV_VERSION}/bin/${RBENV_COMMAND}" fi ... if [ -x "$RBENV_COMMAND_PATH" ]; then echo "$RBENV_COMMAND_PATH" else ...
というかたちになっていて、実行すべきrubyのバージョンを$(rbenv-version-name)
から取得し、そこへのパスを返している。
${RBENV_ROOT}/libexec/rbenv-version-name
を見てみると
... if [ -z "$RBENV_VERSION" ]; then RBENV_VERSION_FILE="$(rbenv-version-file)" RBENV_VERSION="$(rbenv-version-file-read "$RBENV_VERSION_FILE" || true)" fi if [ -z "$RBENV_VERSION" ] || [ "$RBENV_VERSION" = "system" ]; then echo "system" exit fi ...
のように書いてあり、${RBENV_VERSION}
が設定してあればそれを見て、無ければ$(rbenv-version-file)
から取ってくる、ということをしているのがわかる。つまり最優先されるのは${RBENV_VERSION}
なのでそれが指定されていればglobal, localの指定にも関係なくそれを使うことになる。
$ rbenv global 1.9.3-p0 $ rbenv local system $ ruby -v ruby 1.8.7 (2010-01-10 patchlevel 249) [universal-darwin10.0] $ RBENV_VERSION=1.9.2-p290 ruby -v ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin10.8.0]
で、${RBENV_ROOT}/libexec/rbenv-version-file
では
... root="$RBENV_DIR" while [ -n "$root" ]; do if [ -e "${root}/.rbenv-version" ]; then echo "${root}/.rbenv-version" exit fi root="${root%/*}" done ...
となっていて、${RBENV_DIR}
から上に遡っていって最初に見つかった.rbenv-version
ファイルを使うことになる。
要するに
rbenvを使用する設定をした状態でruby
コマンドを叩くと、
${RBENV_ROOT}/shims/ruby
が呼ばれ、その中でrbenv-exec
が呼ばれ、その中ではrbenv-which
で実行すべきrubyを探すが、それは中でrbenv-version-name
で実行すべきrubyのバージョンを特定し、そいつが中でrbenv-version-file
で.rbenv-version
ファイルがあるかどうか調べる
という流れになる。.rbenv-version
ファイルが見つかれば、
rbenv-version-file
がそのファイルパスを返し、rbenv-version-name
がそこから読み取った${RBENV_VERSION}
を返し、rbenv-which
がそのバージョンに対応する${RBENV_COMMAND_PATH}
を返し、rbenv-exec
がそのコマンドを実際に実行する
というかんじ。なかなか深い…。
ということは
特にruby特有の仕組みを使っておらず、ほぼシェルスクリプトだけで実現されているので、他の似たようなスクリプト言語でもこの仕組みは適用できそう。
ということでPerlで実験してみた。
まずはrbenvとまったく同じものをplenvという名前でcloneする。
$ cd $ git clone git://github.com/sstephenson/rbenv.git .plenv
s/rbenv/plenv/g になるよう全部置換してplenv-*を生成
$ for i in $(find .plenv/libexec -name 'rbenv*'); do perl -pe 's/rbenv/plenv/g;s/RBENV/PLENV/g' $i > ${i/rbenv/plenv}; chmod u+x ${i/rbenv/plenv}; done
シンボリックリンクを作成してやる
$ ln -s ../libexec/plenv .plenv/bin
これでだいたいオッケー。$HOME/.zshenv
にPATHと設定を追記。
# plenv path=($HOME/.plenv/bin(N) $path) eval "$(plenv init -)"
これでplenv
コマンドは使えるようになる。
versions以下から、perlbrewでinstall済みのperlにシンボリックリンクを貼ってやる(新規インストールでも良いはず)。
$ ln -s $HOME/perl5/perlbrew/perls/perl-5.12.1 $HOME/.plenv/versions/5.12.1 $ ln -s $HOME/perl5/perlbrew/perls/perl-5.14.2 $HOME/.plenv/versions/5.14.2
すると、
$ plenv versions 5.12.1 5.14.2
バージョン選択できるようになる。
$ plenv global 5.14.2 $ plenv versions 5.12.1 * 5.14.2 (set by /Users/sugyan/.plenv/version)
global設定完了。最後に、$HOME/.plenv/shims/perl
を
#!/usr/bin/env bash set -e export PLENV_ROOT="/Users/sugyan/.plenv" exec plenv exec "${0##*/}" "$@"
と書いて実行権限を付与する。
$ perl -v This is perl 5, version 14, subversion 2 (v5.14.2) built for darwin-2level ... $ PLENV_VERSION=5.12.1 perl -v This is perl 5, version 12, subversion 1 (v5.12.1) built for darwin-2level ... $ PLENV_VERSION=system perl -v This is perl, v5.10.1 (*) built for darwin-2level ... $ cd ~/temp $ ls .plenv-version ls: .plenv-version: No such file or directory $ perl -v This is perl 5, version 14, subversion 2 (v5.14.2) built for darwin-2level ... $ plenv local 5.12.1 $ ls .plenv-version .plenv-version $ cat .plenv-version 5.12.1 $ perl -v This is perl 5, version 12, subversion 1 (v5.12.1) built for darwin-2level ...
同じようなかんじで切り替えできますね。