Last active
October 29, 2023 00:13
-
-
Save jaysoffian/3c67711d3f00c364365905d877cc4af4 to your computer and use it in GitHub Desktop.
Like a Python venv, but for installing Ruby gems
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# gem-venv.sh | |
# ~~~~~~~~~~~ | |
# Summary: Like a Python venv, but for installing Ruby gems. | |
# | |
# Create a directory and gem wrapper script for installing gems self-contained | |
# to that directory. Control which `gem` is used via the `-G/--gem` switch, | |
# defaulting to whatever `gem` is found in PATH. | |
# | |
# Optionally takes a list of gems to install using the just created directory | |
# and gem wrapper. | |
# | |
# Development notes: | |
# - Formatted with "shfmt -i 2 -ci -w gem-venv.sh" | |
# - Checked with "shellcheck gem-venv.sh" | |
set -euo pipefail | |
say() { | |
printf '%s\n' "$*" | |
} | |
emit_usage_header() { | |
cat <<'__EOF__' | |
Usage: gem-venv.sh [options] GEM_DIR [GEM_NAME...] | |
-G, --gem GEM Use GEM instead of the `gem` found in PATH | |
-h, --help Show help and quit | |
__EOF__ | |
} | |
emit_usage_and_exit() { | |
cat >&2 <<__EOF__ | |
$(emit_usage_header) | |
This is not the full help. Use "--help" for more information. | |
__EOF__ | |
exit 1 | |
} | |
emit_help_and_exit() { | |
cat <<__EOF__ | |
$(emit_usage_header) | |
Create GEM_DIR and wrapper script GEM_DIR/bin/gem for installing gems | |
self-contained to GEM_DIR. | |
With one or more GEM_NAMES, run GEM_DIR/bin/gem install GEM_NAMES... after | |
creating GEM_DIR. | |
__EOF__ | |
exit 0 | |
} | |
abspath() { | |
(cd "$1" && pwd -P) | |
} | |
# emit a script which wraps gem itself | |
emit_gem_wrapper() { | |
local gem_dir | |
gem_dir=$(abspath "$1") | |
cat <<__EOF__ | |
#!/bin/bash | |
# shellcheck disable=SC2016 | |
set -eu | |
shopt -s nullglob | |
GEM_DIR="$gem_dir" | |
GEM_INSTALL_ARGS=( | |
--no-document | |
--no-user-install | |
--bindir "\$GEM_DIR/libexec" | |
) | |
wrap_binstubs() { | |
local path | |
for path in "\$GEM_DIR"/libexec/*; do | |
local name | |
name=\$(basename "\$path") | |
test gem = "\$name" && continue | |
( | |
printf '#!/bin/sh\\n' | |
printf 'GEM_DIR="%s" \\n' "\$GEM_DIR" | |
printf 'PATH="\$GEM_DIR/bin:\$PATH"' | |
test fastlane = "\$name" && printf ' FASTLANE_SELF_CONTAINED="true"' | |
printf ' GEM_HOME="\$GEM_DIR"' | |
printf ' GEM_PATH="\$GEM_DIR"' | |
printf ' exec "\$GEM_DIR/libexec/%s" "\$@"\\n' "\$name" | |
) > "\$GEM_DIR/bin/\$name" | |
chmod 0755 "\$GEM_DIR/bin/\$name" | |
done | |
} | |
command= | |
case "\${1:-}" in | |
install|update) | |
command="\$1" | |
shift | |
;; | |
esac | |
if test -n "\$command"; then | |
GEM_HOME="\$GEM_DIR" \\ | |
GEM_PATH="\$GEM_DIR" \\ | |
"\$GEM_DIR/libexec/gem" "\$command" "\${GEM_INSTALL_ARGS[@]}" "\$@" | |
wrap_binstubs | |
else | |
GEM_HOME="\$GEM_DIR" \\ | |
GEM_PATH="\$GEM_DIR" \\ | |
"\$GEM_DIR/libexec/gem" "\$@" | |
fi | |
__EOF__ | |
} | |
main() { | |
local gem_exe="" gem_dir="" | |
local -a gems | |
while test "$#" -gt 0; do | |
case "$1" in | |
-h | --help) emit_help_and_exit ;; | |
--gem=*) gem_exe="${1/#--gem=/}" ;; | |
-G | --gem) | |
shift | |
gem_exe=$1 | |
;; | |
*) | |
if test -n "$gem_dir"; then | |
gems+=("$1") | |
else | |
gem_dir=$1 | |
fi | |
;; | |
esac | |
shift | |
done | |
test -n "$gem_dir" || emit_usage_and_exit | |
local gem_command | |
if test -f "$gem_dir/gem-venv.cfg"; then | |
say "Updating $gem_dir" | |
gem_command=update | |
if test -n "$gem_exe"; then | |
say "NOTICE: --gem option is ignored when updating" | |
fi | |
else | |
: "${gem_exe:="$(type -P gem)"}" | |
say "Creating $gem_dir using $gem_exe" | |
gem_command=install | |
mkdir -p "$gem_dir/libexec" | |
ln -s "$gem_exe" "$gem_dir/libexec/gem" | |
mkdir -p "$HOME/.gem/cache" | |
ln -s "$HOME/.gem/cache" "$gem_dir/cache" | |
fi | |
local gem_wrapper="$gem_dir/bin/gem" | |
mkdir -p "$gem_dir/bin" | |
emit_gem_wrapper "$gem_dir" >"$gem_wrapper" | |
chmod +x "$gem_wrapper" | |
"$gem_wrapper" env > "$gem_dir/gem-venv.cfg" | |
if test "${#gems[@]}" -gt 0; then | |
say "Running gem $gem_command ${gems[*]}" | |
"$gem_wrapper" "$gem_command" "${gems[@]}" | |
fi | |
} | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment