From db143bb654fe447a462b0d91ba931e0524f234f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Thu, 20 Jun 2013 17:22:15 +0200 Subject: [PATCH] rbenv exec: avoid mutating PATH Enables shelling out from a ruby process started with rbenv to a ruby process with a different RBENV_VERSION. Fixes #121 This removes the workaround created for #15 and solves `ruby -S` support by setting RUBYPATH. PATH is never changed. To illustrate how RUBYPATH changes in various configurations: PATH=~/bin:~/.rbenv/shims:/usr/bin:/bin RBENV_VERSION=1.8 ruby -S rake #=> executes ~/.rbenv/versions/1.8/bin/rake #=> RUBYPATH=~/bin:~/.rbenv/versions/1.8/bin:/usr/bin:/bin RBENV_VERSION=2.0 ruby -S rake #=> executes ~/.rbenv/versions/2.0/bin/rake #=> RUBYPATH=~/bin:~/.rbenv/versions/2.0/bin:/usr/bin:/bin RBENV_VERSION=system ruby -S rake #=> executes /usr/bin/rake #=> RUBYPATH=~/bin:/rbenv_shims_were_here:/usr/bin:/bin RBENV_VERSION=1.8 ruby -S rake #=> executes ~/.rbenv/versions/1.8/bin/rake #=> RUBYPATH=~/bin:~/.rbenv/versions/1.8/bin:/usr/bin:/bin --- libexec/rbenv-exec | 64 +++++++++--- libexec/rbenv-rehash | 4 +- test/exec.bats | 238 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 272 insertions(+), 34 deletions(-) diff --git a/libexec/rbenv-exec b/libexec/rbenv-exec index a9309d0e..f3908f8d 100755 --- a/libexec/rbenv-exec +++ b/libexec/rbenv-exec @@ -2,24 +2,59 @@ # # Summary: Run an executable with the selected Ruby version # -# Usage: rbenv exec [arg1 arg2...] +# Usage: rbenv exec [args...] # -# Runs an executable by first preparing PATH so that the selected Ruby -# version's `bin' directory is at the front. +# Runs an executable contained by the currently selected Ruby's bin +# directory. Rough equivalent of: # -# For example, if the currently selected Ruby version is 1.9.3-p327: -# rbenv exec bundle install -# -# is equivalent to: -# PATH="$RBENV_ROOT/versions/1.9.3-p327/bin:$PATH" bundle install +# exec "$(rbenv prefix)/bin/$command" args... set -e [ -n "$RBENV_DEBUG" ] && set -x +rubypath="" # Provide rbenv completions -if [ "$1" = "--complete" ]; then - exec rbenv shims --short -fi +while true; do + case "$1" in + "--complete" ) + exec rbenv shims --short + ;; + "--rubypath" ) + rubypath=1 + shift 1 + ;; + * ) + break + ;; + esac +done + +# Replace any "RBENV_ROOT/shims" or "RBENV_ROOT/versions/*/bin" paths in the +# list with the given path. If no replacements were made, prepend the path onto +# the list. +replace_shims_path() { + local path="$1" + local dir="$2" + # fake directory that serves as a placeholder for shims location in RUBYPATH: + local placeholder="/rbenv_shims_were_here" + local found="" + local result="" + local -a paths + IFS=: paths=($path) + + for path in "${paths[@]}"; do + if [[ $path = "${RBENV_ROOT}/shims" || $path == "${RBENV_ROOT}/versions/"*/bin || $path = $placeholder ]]; then + found=1 + result="${result}${dir:-$placeholder}:" + else + result="${result}${path}:" + fi + done + + # if no rbenv paths were replaced, simply prepend the path + [ -n "$found" -o -z "$dir" ] || result="${dir}:${path}" + echo "${result%:}" +} RBENV_VERSION="$(rbenv-version-name)" RBENV_COMMAND="$1" @@ -31,7 +66,6 @@ fi export RBENV_VERSION RBENV_COMMAND_PATH="$(rbenv-which "$RBENV_COMMAND")" -RBENV_BIN_PATH="${RBENV_COMMAND_PATH%/*}" OLDIFS="$IFS" IFS=$'\n' scripts=(`rbenv-hooks exec`) @@ -41,7 +75,9 @@ for script in "${scripts[@]}"; do done shift 1 -if [ "$RBENV_VERSION" != "system" ]; then - export PATH="${RBENV_BIN_PATH}:${PATH}" +if [ -n "$rubypath" ]; then + bindir="" + [ "$RBENV_VERSION" != "system" ] && bindir="${RBENV_COMMAND_PATH%/*}" + export RUBYPATH="$(replace_shims_path "${RUBYPATH:-$PATH}" "$bindir")" fi exec -a "$RBENV_COMMAND" "$RBENV_COMMAND_PATH" "$@" diff --git a/libexec/rbenv-rehash b/libexec/rbenv-rehash index b220329a..d8154279 100755 --- a/libexec/rbenv-rehash +++ b/libexec/rbenv-rehash @@ -45,10 +45,12 @@ create_prototype_shim() { set -e [ -n "\$RBENV_DEBUG" ] && set -x +opt="" program="\${0##*/}" if [ "\$program" = "ruby" ]; then for arg; do case "\$arg" in + -S* ) opt=--rubypath ;; -e* | -- ) break ;; */* ) if [ -f "\$arg" ]; then @@ -61,7 +63,7 @@ if [ "\$program" = "ruby" ]; then fi export RBENV_ROOT="$RBENV_ROOT" -exec "$(command -v rbenv)" exec "\$program" "\$@" +exec "$(command -v rbenv)" exec \$opt "\$program" "\$@" SH chmod +x "$PROTOTYPE_SHIM_PATH" } diff --git a/test/exec.bats b/test/exec.bats index 889eb897..f82a42ef 100644 --- a/test/exec.bats +++ b/test/exec.bats @@ -3,15 +3,37 @@ load test_helper create_executable() { - name="${1?}" + local file="${1?}" + [[ $file == */* ]] || file="${RBENV_ROOT}/versions/${RBENV_VERSION}/bin/$file" shift 1 - bin="${RBENV_ROOT}/versions/${RBENV_VERSION}/bin" - mkdir -p "$bin" + mkdir -p "${file%/*}" { if [ $# -eq 0 ]; then cat - else echo "$@" fi - } | sed -Ee '1s/^ +//' > "${bin}/$name" - chmod +x "${bin}/$name" + } | sed -Ee '1s/^[[:space:]]+//' > "$file" + chmod +x "$file" +} + +# Fake ruby executable that emulates `ruby -S ' behavior by running the +# first `cmd' found in RUBYPATH/PATH as bash script. +create_ruby_executable() { + create_executable "${1:-ruby}" </dev/null; then + \$BASH "\$found" + else + echo "ruby: no Ruby script found in input (LoadError)" >&2 + exit 1 + fi +else + echo 'ruby (rbenv test)' +fi +SH } @test "fails with invalid version" { @@ -82,26 +104,56 @@ ${RBENV_ROOT}/versions/2.0/bin/ruby OUT } -@test "supports ruby -S " { +@test "doesn't mutate PATH" { export RBENV_VERSION="2.0" + create_executable "ruby" </dev/null; then - \$BASH "\$found" - else - echo "ruby: no Ruby script found in input (LoadError)" >&2 - exit 1 - fi -else - echo 'ruby 2.0 (rbenv test)' -fi +echo \$RUBYPATH SH + RUBYPATH="" run rbenv-exec ruby + assert_success + assert_output "" +} + +@test "allows subprocesses to select a different RBENV_VERSION" { + RBENV_VERSION=1.8 create_executable "rake" <" { + export RBENV_VERSION="2.0" + + create_ruby_executable create_executable "rake" <