You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
4.7 KiB

  1. #!/usr/bin/env bash
  2. # Summary: Rehash pyenv shims (run this after installing executables)
  3. set -e
  4. [ -n "$PYENV_DEBUG" ] && set -x
  5. SHIM_PATH="${PYENV_ROOT}/shims"
  6. PROTOTYPE_SHIM_PATH="${SHIM_PATH}/.pyenv-shim"
  7. # Create the shims directory if it doesn't already exist.
  8. mkdir -p "$SHIM_PATH"
  9. acquire_lock() {
  10. # Ensure only one instance of pyenv-rehash is running at a time by
  11. # setting the shell's `noclobber` option and attempting to write to
  12. # the prototype shim file. If the file already exists, print a warning
  13. # to stderr and exit with a non-zero status.
  14. local ret
  15. set -o noclobber
  16. echo > "$PROTOTYPE_SHIM_PATH" 2>| /dev/null || ret=1
  17. set +o noclobber
  18. [ -z "${ret}" ]
  19. }
  20. # If we were able to obtain a lock, register a trap to clean up the
  21. # prototype shim when the process exits.
  22. trap release_lock EXIT
  23. remove_prototype_shim() {
  24. rm -f "$PROTOTYPE_SHIM_PATH"
  25. }
  26. release_lock() {
  27. remove_prototype_shim
  28. }
  29. if [ ! -w "$SHIM_PATH" ]; then
  30. echo "pyenv: cannot rehash: $SHIM_PATH isn't writable"
  31. exit 1
  32. fi
  33. unset acquired
  34. start=$SECONDS
  35. while (( SECONDS <= start + ${PYENV_REHASH_TIMEOUT:-60} )); do
  36. if acquire_lock 2>/dev/null; then
  37. acquired=1
  38. break
  39. else
  40. # POSIX sleep(1) doesn't provide subsecond precision, but many others do
  41. sleep 0.1 2>/dev/null || sleep 1
  42. fi
  43. done
  44. if [ -z "${acquired}" ]; then
  45. echo "pyenv: cannot rehash: $PROTOTYPE_SHIM_PATH exists"
  46. exit 1
  47. fi
  48. # The prototype shim file is a script that re-execs itself, passing
  49. # its filename and any arguments to `pyenv exec`. This file is
  50. # hard-linked for every executable and then removed. The linking
  51. # technique is fast, uses less disk space than unique files, and also
  52. # serves as a locking mechanism.
  53. create_prototype_shim() {
  54. cat > "$PROTOTYPE_SHIM_PATH" <<SH
  55. #!/usr/bin/env bash
  56. set -e
  57. [ -n "\$PYENV_DEBUG" ] && set -x
  58. program="\${0##*/}"
  59. export PYENV_ROOT="$PYENV_ROOT"
  60. exec "$(command -v pyenv)" exec "\$program" "\$@"
  61. SH
  62. chmod +x "$PROTOTYPE_SHIM_PATH"
  63. }
  64. # If the contents of the prototype shim file differ from the contents
  65. # of the first shim in the shims directory, assume pyenv has been
  66. # upgraded and the existing shims need to be removed.
  67. remove_outdated_shims() {
  68. local shim
  69. for shim in "$SHIM_PATH"/*; do
  70. if ! diff "$PROTOTYPE_SHIM_PATH" "$shim" >/dev/null 2>&1; then
  71. rm -f "$SHIM_PATH"/*
  72. fi
  73. break
  74. done
  75. }
  76. # List basenames of executables for every Python version
  77. list_executable_names() {
  78. local version file
  79. pyenv-versions --bare --skip-aliases | \
  80. while read -r version; do
  81. for file in "${PYENV_ROOT}/versions/${version}/bin/"*; do
  82. echo "${file##*/}"
  83. done
  84. done
  85. }
  86. # The basename of each argument passed to `make_shims` will be
  87. # registered for installation as a shim. In this way, plugins may call
  88. # `make_shims` with a glob to register many shims at once.
  89. make_shims() {
  90. local file shim
  91. for file; do
  92. shim="${file##*/}"
  93. register_shim "$shim"
  94. done
  95. }
  96. if ((${BASH_VERSINFO[0]} > 3)); then
  97. declare -A registered_shims
  98. # Registers the name of a shim to be generated.
  99. register_shim() {
  100. registered_shims["$1"]=1
  101. }
  102. # Install all shims registered via `make_shims` or `register_shim` directly.
  103. install_registered_shims() {
  104. local shim file
  105. for shim in "${!registered_shims[@]}"; do
  106. file="${SHIM_PATH}/${shim}"
  107. [ -e "$file" ] || cp "$PROTOTYPE_SHIM_PATH" "$file"
  108. done
  109. }
  110. # Once the registered shims have been installed, we make a second pass
  111. # over the contents of the shims directory. Any file that is present
  112. # in the directory but has not been registered as a shim should be
  113. # removed.
  114. remove_stale_shims() {
  115. local shim
  116. for shim in "$SHIM_PATH"/*; do
  117. if [[ ! ${registered_shims["${shim##*/}"]} ]]; then
  118. rm -f "$shim"
  119. fi
  120. done
  121. }
  122. else # Same for bash < 4.
  123. registered_shims=" "
  124. register_shim() {
  125. registered_shims="${registered_shims}${1} "
  126. }
  127. install_registered_shims() {
  128. local shim file
  129. for shim in $registered_shims; do
  130. file="${SHIM_PATH}/${shim}"
  131. [ -e "$file" ] || cp "$PROTOTYPE_SHIM_PATH" "$file"
  132. done
  133. }
  134. remove_stale_shims() {
  135. local shim
  136. for shim in "$SHIM_PATH"/*; do
  137. if [[ "$registered_shims" != *" ${shim##*/} "* ]]; then
  138. rm -f "$shim"
  139. fi
  140. done
  141. }
  142. fi
  143. shopt -s nullglob
  144. # Create the prototype shim, then register shims for all known
  145. # executables.
  146. create_prototype_shim
  147. remove_outdated_shims
  148. # shellcheck disable=SC2046
  149. make_shims $(list_executable_names | sort -u)
  150. # Allow plugins to register shims.
  151. OLDIFS="$IFS"
  152. IFS=$'\n' scripts=(`pyenv-hooks rehash`)
  153. IFS="$OLDIFS"
  154. for script in "${scripts[@]}"; do
  155. source "$script"
  156. done
  157. install_registered_shims
  158. remove_stale_shims