rbenv/rbenvのコードリーディングの備忘録。バージョンは1.1.0-2-g4f8925a です。
bin/rbenvはrcファイルで明示的にパスを通したメインの実行ファイルです。rcスクリプト内で eval “$(rbenv init -)”
を呼び出すところが起点になります。
まず~/.rbenv/libexecにパスを通します。libexecディレクトリ内に各サブコマンドの実体が入っています。
bin_path="$(abs_dirname "$0")"
for plugin_bin in "${RBENV_ROOT}/plugins/"*/bin; do
PATH="${plugin_bin}:${PATH}"
done
export PATH="${bin_path}:${PATH}"
環境変数など諸々の設定が終わった後、$command_pathが実行されます。initの場合はlibexec/rbenv-initが実行されます。
command_path="$(command -v "rbenv-$command" || true)"
...
exec "$command_path" "$@"
rbenv init -
を実行すると、以下のようなスクリプトが文字列として返されるので、rcスクリプト内でevalします。これによって.rbenv/shimsにパスを通したりcompletionsのスクリプトを実行して補完が効くようにするなどの初期化が行われます。また、bin/rbenvコマンドがラップされたrbenv関数が定義されます。
export PATH="/Users/mtajitsu/.rbenv/shims:${PATH}"
export RBENV_SHELL=zsh
source '/Users/mtajitsu/.rbenv/libexec/../completions/rbenv.zsh'
command rbenv rehash 2>/dev/null
rbenv() {
local command
command="$1"
if [ "$#" -gt 0 ]; then
shift
fi
case "$command" in
rehash|shell)
eval "$(rbenv "sh-$command" "$@")";;
*)
command rbenv "$command" "$@";;
esac
}
rbenv init - で実行されるrehashの処理を追っていきます。実体はlibexec/rbenv-rehashです。中身は以下のようになっており、shimsディレクトリ内のshimファイルを作っています。shimファイルとは各バージョン差異を埋めるためのファイルであり、rbenvの場合は、ruby・irb・gemなどのファイル名で作成されるシェルスクリプトになります。これらのシェルスクリプトが各バージョンの実行ファイルを実行するような作りとなっています。
...
create_prototype_shim
remove_outdated_shims
make_shims $(list_executable_names | sort -u)
...
# Allow plugins to register shims.
OLDIFS="$IFS"
IFS=$'\n' scripts=(`rbenv-hooks rehash`)
IFS="$OLDIFS"
for script in "${scripts[@]}"; do
source "$script"
done
install_registered_shims
remove_stale_shims
create_prototype_shim関数を見るとわかるとおり、全てのshimファイルは同じスクリプトであり、呼び出された名前によって呼び出す実行ファイルを切り替えています。
create_prototype_shim() {
cat > "$PROTOTYPE_SHIM_PATH" <<SH
#!/usr/bin/env bash
set -e
[ -n "\$RBENV_DEBUG" ] && set -x
program="\${0##*/}"
if [ "\$program" = "ruby" ]; then
for arg; do
case "\$arg" in
-e* | -- ) break ;;
*/* )
if [ -f "\$arg" ]; then
export RBENV_DIR="\${arg%/*}"
break
fi
;;
esac
done
fi
export RBENV_ROOT="$RBENV_ROOT"
exec "$(command -v rbenv)" exec "\$program" "\$@"
SH
chmod +x "$PROTOTYPE_SHIM_PATH"
}
このファイルをプロトタイプとして作成し、install_registerd_shimsによってコピーを行い、rubyやgemのファイル名でシェルスクリプトを作成します。
install_registered_shims() {
local shim file
for shim in $registered_shims; do
file="${SHIM_PATH}/${shim}"
[ -e "$file" ] || cp "$PROTOTYPE_SHIM_PATH" "$file"
done
}
このプロトタイプが呼び出すrbenv execの実体はlibexec/rbenv-execです。バージョンから適切な実行ファイルのパスを取得し、execコマンドで実行しています。
RBENV_VERSION="$(rbenv-version-name)"
RBENV_COMMAND="$1"
...
RBENV_COMMAND_PATH="$(rbenv-which "$RBENV_COMMAND")"
RBENV_BIN_PATH="${RBENV_COMMAND_PATH%/*}"
...
if [ "$RBENV_VERSION" != "system" ]; then
export PATH="${RBENV_BIN_PATH}:${PATH}"
fi
exec -a "$RBENV_COMMAND" "$RBENV_COMMAND_PATH" "$@"
rbenv-version-nameはRBENV_VERSIONの環境変数を取得するか、無ければrbenv-version-file、rbenv-version-file-readによってバージョンを取得します。
if [ -z "$RBENV_VERSION" ]; then
RBENV_VERSION_FILE="$(rbenv-version-file)"
RBENV_VERSION="$(rbenv-version-file-read "$RBENV_VERSION_FILE" || true)"
fi
rbenv-version-file関数は.ruby-versionファイルを指定のディレクトリから階層を上にたどって検索してパスを返します。なければ~/.rbenv/version(globalで指定したバージョン)を返します。
find_local_version_file() {
local root="$1"
while ! [[ "$root" =~ ^//[^/]*$ ]]; do
if [ -e "${root}/.ruby-version" ]; then
echo "${root}/.ruby-version"
return 0
fi
[ -n "$root" ] || break
root="${root%/*}"
done
return 1
}
if [ -n "$target_dir" ]; then
find_local_version_file "$target_dir"
else
find_local_version_file "$RBENV_DIR" || {
[ "$RBENV_DIR" != "$PWD" ] && find_local_version_file "$PWD"
} || echo "${RBENV_ROOT}/version"
fi
rbenv-version-file-readはrbenv-version-fileで取得したファイルパスからバージョンを取得します。
#!/usr/bin/env bash
# Usage: rbenv version-file-read <file>
set -e
[ -n "$RBENV_DEBUG" ] && set -x
VERSION_FILE="$1"
if [ -e "$VERSION_FILE" ]; then
# Read the first non-whitespace word from the specified version file.
# Be careful not to load it whole in case there's something crazy in it.
IFS="${IFS}"$'\r'
words=( $(cut -b 1-1024 "$VERSION_FILE") )
version="${words[0]}"
if [ -n "$version" ]; then
echo "$version"
exit
fi
fi
exit 1
rbenv localを使うと.ruby-versionファイルが書き込まれたり、現在のバージョンを表示できます。
RBENV_VERSION="$1"
if [ "$RBENV_VERSION" = "--unset" ]; then
rm -f .ruby-version
elif [ -n "$RBENV_VERSION" ]; then
rbenv-version-file-write .ruby-version "$RBENV_VERSION"
else
if version_file="$(rbenv-version-file "$PWD")"; then
rbenv-version-file-read "$version_file"
else
echo "rbenv: no local version configured for this directory" >&2
exit 1
fi
fi
ちなみにrbenv自体のバージョンはgit describeの結果(タグ+タグからのコミット数+コミット番号)を表示しています。
version="1.1.0"
git_revision=""
if cd "${BASH_SOURCE%/*}" 2>/dev/null && git remote -v 2>/dev/null | grep -q rbenv; then
git_revision="$(git describe --tags HEAD 2>/dev/null || true)"
git_revision="${git_revision#v}"
fi
echo "rbenv ${git_revision:-$version}"