Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-interactive chruby-exec fails with zsh:1: command not found: chruby #502

Open
amomchilov opened this issue Mar 3, 2024 · 7 comments
Labels

Comments

@amomchilov
Copy link

amomchilov commented Mar 3, 2024

Description

chruby.sh is typically sourced from ~/.zshrc, which is only run in interactive shells. For non-interactive shells, chruby-exec calls zsh without the -i:

else exec "$SHELL" -l -c "$command"

This causes the ~/.zshrc to never be sourced, and thus, for chruby to be unavailable.

Steps To Reproduce

Steps to reproduce the bug:

  1. Install the latest release (0.39.0 right now) with brew install chruby

  2. Run chruby-exec with a non-terminal STDIN. E.g. with:

    echo "abc" | chruby-exec "ruby-3.3.0" -- ruby --version

Expected Behavior

Run the command successfully, (in this example, ruby --version). It works correctly if you drop the echo "abc" |:

$ chruby-exec "ruby-3.3.0" -- ruby --version
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]

This works fine because it hits this other branch, which include -i and runs ~/.zshrc:

if [[ -t 0 ]]; then exec "$SHELL" -i -l -c "$command"

This also works on the latest master branch, so it looks like it's a packaging/release issue:

$ brew uninstall chruby && brew install chruby --head
$ echo "abc" | chruby-exec "ruby-3.3.0" -- ruby --version
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]

Actual Behavior

$ echo "abc" | chruby-exec "ruby-3.3.0" -- ruby --version
zsh:1: command not found: chruby

Notes

https://github.com/postmodern/chruby/releases/tag/v0.3.9 was release in April 19, 2023, but for some reason, it doesn't include this change to chruby-exec from 2014. I confirmed this in 2 ways:

  1. Inspecting bin/chruby-exec of the chruby-0.3.9.tar.gz downloaded right from the release page
  2. Inspecting /opt/homebrew/bin/chruby-exec installed by homebrew.

It looks to me like the release process just didn't package up this file correctly.

Environment

$ bash --version
GNU bash, version 5.2.26(1)-release (aarch64-apple-darwin23.2.0)
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ zsh --version
zsh 5.9 (x86_64-apple-darwin23.0)
$ chruby --version
Untitled 10.sh: line 8: chruby: command not found
$ ruby --version
ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin23]
$ gem --version
3.0.3.1
$ gem env
RubyGems Environment:
  - RUBYGEMS VERSION: 3.0.3.1
  - RUBY VERSION: 2.6.10 (2022-04-12 patchlevel 210) [universal.arm64e-darwin23]
  - INSTALLATION DIRECTORY: /Library/Ruby/Gems/2.6.0
  - USER INSTALLATION DIRECTORY: /Users/Alex/.gem/ruby/2.6.0
  - RUBY EXECUTABLE: /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby
  - GIT EXECUTABLE: /usr/bin/git
  - EXECUTABLE DIRECTORY: /usr/local/bin
  - SPEC CACHE DIRECTORY: /Users/Alex/.gem/specs
  - SYSTEM CONFIGURATION DIRECTORY: /Library/Ruby/Site
  - RUBYGEMS PLATFORMS:
    - ruby
    - universal-darwin-23
  - GEM PATHS:
     - /Library/Ruby/Gems/2.6.0
     - /Users/Alex/.gem/ruby/2.6.0
     - /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0
  - GEM CONFIGURATION:
     - :update_sources => true
     - :verbose => true
     - :backtrace => false
     - :bulk_threshold => 1000
  - REMOTE SOURCES:
     - https://rubygems.org/
  - SHELL PATH:
     - /opt/homebrew/bin
     - /Users/Alex/.cargo/bin
     - /usr/local/bin
     - /System/Cryptexes/App/usr/bin
     - /usr/bin
     - /bin
     - /usr/sbin
     - /sbin
     - /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin
     - /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin
     - /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin
     - /Library/Apple/usr/bin
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/lib
     - /Users/Alex/.antigen/bundles/marlonrichert/zsh-autocomplete-main
     - /Users/Alex/.antigen/bundles/marlonrichert/zsh-autocomplete-main/functions
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/git
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/last-working-dir
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/macos
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/sudo
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/alias-finder
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/rust
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/colored-man-pages
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/command-not-found
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/common-aliases
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/dircycle
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/encode64
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/extract
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/jsontools
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/swiftpm
     - /Users/Alex/.antigen/bundles/dracula/zsh-syntax-highlighting
     - /Users/Alex/.antigen/bundles/zsh-users/zsh-syntax-highlighting
     - /Applications/CodeRunner.app/Contents/SharedSupport/Developer/bin
@postmodern
Copy link
Owner

postmodern commented Mar 5, 2024

@amomchilov Could you post your ~/.zshrc and /etc/zshrc files? I suspect there's extra logic there that is exiting if the shell is not in interactive mode. On Fedora Linux with zsh 5.9, if I added echo "loaded" to my ~/.zshrc, it shows that the configuration file is loaded when running zsh and zsh -i.

We cannot always specify -i because that would assume you are always running in an interactive shell, which might not always be the case (ex: running chruby-exec in a cronjob).

Also note that chruby-0.3.9 was indeed released 2014-11-23. GitHub releases are separate from the git tags, and I didn't start adding GitHub releases for git tags until much later.

@amomchilov
Copy link
Author

amomchilov commented Mar 6, 2024

Could you post your ~/.zshrc and /etc/zshrc files? I suspect there's extra logic there that is exiting if the shell is not in interactive mode.

My ~/.zshrc was pretty extensive, but I minimized it down to the bare minimum (and still reproduced the issue with the steps in the OP):

zsh_user_path=(
    "/opt/homebrew/bin"
)

export path=($zsh_user_path $path)

if [[ -f"/opt/homebrew/opt/chruby/share/chruby/chruby.sh" ]]; then
	source "/opt/homebrew/opt/chruby/share/chruby/chruby.sh"
fi

My /etc/zshrc is stock from macOS:

/etc/zshrc
# System-wide profile for interactive zsh(1) shells.

# Setup user specific overrides for this in ~/.zshrc. See zshbuiltins(1)
# and zshoptions(1) for more details.

# Correctly display UTF-8 with combining characters.
if [[ "$(locale LC_CTYPE)" == "UTF-8" ]]; then
    setopt COMBINING_CHARS
fi

# Disable the log builtin, so we don't conflict with /usr/bin/log
disable log

# Save command history
HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history
HISTSIZE=2000
SAVEHIST=1000

# Beep on error
setopt BEEP

# Use keycodes (generated via zkbd) if present, otherwise fallback on
# values from terminfo
if [[ -r ${ZDOTDIR:-$HOME}/.zkbd/${TERM}-${VENDOR} ]] ; then
    source ${ZDOTDIR:-$HOME}/.zkbd/${TERM}-${VENDOR}
else
    typeset -g -A key

    [[ -n "$terminfo[kf1]" ]] && key[F1]=$terminfo[kf1]
    [[ -n "$terminfo[kf2]" ]] && key[F2]=$terminfo[kf2]
    [[ -n "$terminfo[kf3]" ]] && key[F3]=$terminfo[kf3]
    [[ -n "$terminfo[kf4]" ]] && key[F4]=$terminfo[kf4]
    [[ -n "$terminfo[kf5]" ]] && key[F5]=$terminfo[kf5]
    [[ -n "$terminfo[kf6]" ]] && key[F6]=$terminfo[kf6]
    [[ -n "$terminfo[kf7]" ]] && key[F7]=$terminfo[kf7]
    [[ -n "$terminfo[kf8]" ]] && key[F8]=$terminfo[kf8]
    [[ -n "$terminfo[kf9]" ]] && key[F9]=$terminfo[kf9]
    [[ -n "$terminfo[kf10]" ]] && key[F10]=$terminfo[kf10]
    [[ -n "$terminfo[kf11]" ]] && key[F11]=$terminfo[kf11]
    [[ -n "$terminfo[kf12]" ]] && key[F12]=$terminfo[kf12]
    [[ -n "$terminfo[kf13]" ]] && key[F13]=$terminfo[kf13]
    [[ -n "$terminfo[kf14]" ]] && key[F14]=$terminfo[kf14]
    [[ -n "$terminfo[kf15]" ]] && key[F15]=$terminfo[kf15]
    [[ -n "$terminfo[kf16]" ]] && key[F16]=$terminfo[kf16]
    [[ -n "$terminfo[kf17]" ]] && key[F17]=$terminfo[kf17]
    [[ -n "$terminfo[kf18]" ]] && key[F18]=$terminfo[kf18]
    [[ -n "$terminfo[kf19]" ]] && key[F19]=$terminfo[kf19]
    [[ -n "$terminfo[kf20]" ]] && key[F20]=$terminfo[kf20]
    [[ -n "$terminfo[kbs]" ]] && key[Backspace]=$terminfo[kbs]
    [[ -n "$terminfo[kich1]" ]] && key[Insert]=$terminfo[kich1]
    [[ -n "$terminfo[kdch1]" ]] && key[Delete]=$terminfo[kdch1]
    [[ -n "$terminfo[khome]" ]] && key[Home]=$terminfo[khome]
    [[ -n "$terminfo[kend]" ]] && key[End]=$terminfo[kend]
    [[ -n "$terminfo[kpp]" ]] && key[PageUp]=$terminfo[kpp]
    [[ -n "$terminfo[knp]" ]] && key[PageDown]=$terminfo[knp]
    [[ -n "$terminfo[kcuu1]" ]] && key[Up]=$terminfo[kcuu1]
    [[ -n "$terminfo[kcub1]" ]] && key[Left]=$terminfo[kcub1]
    [[ -n "$terminfo[kcud1]" ]] && key[Down]=$terminfo[kcud1]
    [[ -n "$terminfo[kcuf1]" ]] && key[Right]=$terminfo[kcuf1]
fi

# Default key bindings
[[ -n ${key[Delete]} ]] && bindkey "${key[Delete]}" delete-char
[[ -n ${key[Home]} ]] && bindkey "${key[Home]}" beginning-of-line
[[ -n ${key[End]} ]] && bindkey "${key[End]}" end-of-line
[[ -n ${key[Up]} ]] && bindkey "${key[Up]}" up-line-or-search
[[ -n ${key[Down]} ]] && bindkey "${key[Down]}" down-line-or-search

# Default prompt
PS1="%n@%m %1~ %# "

# Useful support for interacting with Terminal.app or other terminal programs
[ -r "/etc/zshrc_$TERM_PROGRAM" ] && . "/etc/zshrc_$TERM_PROGRAM"

We cannot always specify -i because that would assume you are always running in an interactive shell, which might not always be the case (ex: running chruby-exec in a cronjob).

Yeah, agreed. This seems like more of a defect in the zsh conventions, but that isn't something we can really change.

If zsh kept the same convention as ~/.bashrc (minimal, always run) vs ~/.bash_profile (more "bloat", for interactive shells only), then we could have just always used non-interactive shells, and rely on the path being set.

But since most people set their $PATH in ~/.zshrc... welp, idk what we can do to reasonably handle that.

I'm also totally confused as to what --head does to make this work, that 0.38.0 didn't.

Also note that chruby-0.3.9 was indeed released 2014-11-23. GitHub releases are separate from the git tags, and I didn't start adding GitHub releases for git tags until much later.

Ah, that explains that part.

@eregon
Copy link
Contributor

eregon commented Mar 11, 2024

FWIW there is ~/.zshenv which is always sourced. That's where I do PATH modifications.
And my ~/.zshrc is then for interative-only stuff like completion, etc.

@amomchilov
Copy link
Author

Hey @eregon,

I agree that's the right approach in the abstract, but regrettably, that's just not the common convention with ZSH.

For example, Homebrew's official installation instructions used to suggest adding to your .zshrc. They only recently switched to using .zprofile in Sept 2023. There's a contention between what's "right" and what's most common in people's real-world usage.

To me, the most puzzling part here is why --head works. I would have expected it to fail with the same issue.

@ascarter
Copy link

@postmodern Please take a look at the chruby-exec installed in homebrew vs what is in HEAD on GitHub. They are not the same. Attaching both here. If I use the one in GitHub HEAD, I don't need to add anything to my shell. I think chruby-exec should be 100% standalone and require no changes to any shell. That would make it work for IDE's and other tools which is my use case.

I think the Homebrew version is not sourcing the full path when executing the shell. I tested the GitHub version and it worked as I expected. I looked at the homebrew recipe and don't see any overrides so I assume the bottle is broken. I'll try to do a source install for homebrew and see what that does.

Diff:

3c3,4
< source "${0%/*}/../share/chruby/chruby.sh"
---
> chruby_sh="${0%/*}/../share/chruby/chruby.sh"
> source "$chruby_sh"
36c37,38
< command="chruby $(printf "%q " "${argv[@]}") && $(printf "%q " "$@")"
---
> shell_opts=("-l")
> [[ -t 0 ]] && shell_opts+=("-i")
38,40c40,45
< if [[ -t 0 ]]; then exec "$SHELL" -i -l -c "$command"
< else                exec "$SHELL"    -l -c "$command"
< fi
---
> source_command="command -v chruby >/dev/null || source $chruby_sh"
> chruby_command="chruby $(printf "%q " "${argv[@]}")"
> sub_command="$(printf "%q " "$@")"
> command="$source_command; $chruby_command && exec $sub_command"
> 
> exec "$SHELL" "${shell_opts[@]}" -c "$command"
# chruby-exec in homebrew
#!/usr/bin/env bash

source "${0%/*}/../share/chruby/chruby.sh"

case "$1" in
	-h|--help)
		echo "usage: chruby-exec RUBY [RUBYOPTS] -- COMMAND [ARGS...]"
		exit
		;;
	-V|--version)
		echo "chruby version $CHRUBY_VERSION"
		exit
		;;
esac

if (( $# == 0 )); then
	echo "chruby-exec: RUBY and COMMAND required" >&2
	exit 1
fi

argv=()

for arg in "$@"; do
	shift

	if [[ "$arg" == "--" ]]; then break
	else                          argv+=($arg)
	fi
done

if (( $# == 0 )); then
	echo "chruby-exec: COMMAND required" >&2
	exit 1
fi

command="chruby $(printf "%q " "${argv[@]}") && $(printf "%q " "$@")"

if [[ -t 0 ]]; then exec "$SHELL" -i -l -c "$command"
else                exec "$SHELL"    -l -c "$command"
fi
# chruby-exec on GitHub HEAD
#!/usr/bin/env bash

chruby_sh="${0%/*}/../share/chruby/chruby.sh"
source "$chruby_sh"

case "$1" in
	-h|--help)
		echo "usage: chruby-exec RUBY [RUBYOPTS] -- COMMAND [ARGS...]"
		exit
		;;
	-V|--version)
		echo "chruby version $CHRUBY_VERSION"
		exit
		;;
esac

if (( $# == 0 )); then
	echo "chruby-exec: RUBY and COMMAND required" >&2
	exit 1
fi

argv=()

for arg in "$@"; do
	shift

	if [[ "$arg" == "--" ]]; then break
	else                          argv+=($arg)
	fi
done

if (( $# == 0 )); then
	echo "chruby-exec: COMMAND required" >&2
	exit 1
fi

shell_opts=("-l")
[[ -t 0 ]] && shell_opts+=("-i")

source_command="command -v chruby >/dev/null || source $chruby_sh"
chruby_command="chruby $(printf "%q " "${argv[@]}")"
sub_command="$(printf "%q " "$@")"
command="$source_command; $chruby_command && exec $sub_command"

exec "$SHELL" "${shell_opts[@]}" -c "$command"

@ascarter
Copy link

Confirmed that brew install -s chruby has the same problem.

@ascarter
Copy link

ascarter commented Dec 29, 2024

The issue here is that master has not released in a long time. Homebrew is running the tag 0.39. I see a note above that releases aren't being done for tags. Should the Homebrew formula be changed to track master or should there be a new release?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants