mirror of
https://github.com/pyenv/pyenv.git
synced 2024-11-21 20:47:00 -05:00
Auto-resolve prefixes to the latest version (#2487)
This commit is contained in:
parent
0b5e16add3
commit
a12f947cc3
13 changed files with 258 additions and 13 deletions
8
.github/workflows/macos_build.yml
vendored
8
.github/workflows/macos_build.yml
vendored
|
@ -6,10 +6,10 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
python-version:
|
||||
- 3.7.13
|
||||
- 3.8.13
|
||||
- 3.9.13
|
||||
- 3.10.6
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
8
.github/workflows/ubuntu_build.yml
vendored
8
.github/workflows/ubuntu_build.yml
vendored
|
@ -6,10 +6,10 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
python-version:
|
||||
- 3.7.13
|
||||
- 3.8.13
|
||||
- 3.9.13
|
||||
- 3.10.6
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
78
libexec/pyenv-latest
Executable file
78
libexec/pyenv-latest
Executable file
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env bash
|
||||
# Summary: Print the latest installed or known version with the given prefix
|
||||
# Usage: pyenv latest [-k|--known] [-q|--quiet] <prefix>
|
||||
#
|
||||
# -k/--known Select from all known versions instead of installed
|
||||
# -q/--quiet Do not print a
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
case "$1" in
|
||||
-k|--known)
|
||||
FROM_KNOWN=1
|
||||
shift
|
||||
;;
|
||||
-q|--quiet)
|
||||
QUIET=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
prefix=$1
|
||||
exitcode=0
|
||||
|
||||
IFS=$'\n'
|
||||
|
||||
if [[ -z $FROM_KNOWN ]]; then
|
||||
DEFINITION_CANDIDATES=( $(pyenv-versions --bare) )
|
||||
else
|
||||
DEFINITION_CANDIDATES=( $(python-build --definitions ) )
|
||||
fi
|
||||
# if grep -xFe "$prefix" <<<"${DEFINITION_CANDIDATES[@]}"; then
|
||||
# echo "$prefix"
|
||||
# exit $exitcode
|
||||
# fi
|
||||
# https://stackoverflow.com/questions/11856054/is-there-an-easy-way-to-pass-a-raw-string-to-grep/63483807#63483807
|
||||
prefix_re="$(sed 's/[^\^]/[&]/g;s/[\^]/\\&/g' <<< "$prefix")"
|
||||
# FIXME: more reliable and readable would probably be to loop over them and transform in pure Bash
|
||||
DEFINITION_CANDIDATES=(\
|
||||
$(printf '%s\n' "${DEFINITION_CANDIDATES[@]}" | \
|
||||
grep -Ee "^$prefix_re[-.]" || true))
|
||||
|
||||
DEFINITION_CANDIDATES=(\
|
||||
$(printf '%s\n' "${DEFINITION_CANDIDATES[@]}" | \
|
||||
sed -E -e '/-dev$/d' -e '/-src$/d' -e '/-latest$/d' -e '/(b|rc)[0-9]+$/d'));
|
||||
|
||||
# Compose a sorting key, followed by | and original value
|
||||
DEFINITION_CANDIDATES=(\
|
||||
$(printf '%s\n' "${DEFINITION_CANDIDATES[@]}" | \
|
||||
awk \
|
||||
'{ if (match($0,"^[[:alnum:]]+-"))
|
||||
{ print substr($0,0,RLENGTH-1) "." substr($0,RLENGTH+1) "..|" $0; }
|
||||
else
|
||||
{ print $0 "...|" $0; }
|
||||
}'))
|
||||
DEFINITION_CANDIDATES=(\
|
||||
$(printf '%s\n' "${DEFINITION_CANDIDATES[@]}" \
|
||||
| sort -t. -k1,1r -k 2,2nr -k 3,3nr -k4,4nr \
|
||||
| cut -f2 -d $'|' \
|
||||
|| true))
|
||||
DEFINITION="${DEFINITION_CANDIDATES[0]}"
|
||||
|
||||
if [[ -n "$DEFINITION" ]]; then
|
||||
echo "$DEFINITION"
|
||||
else
|
||||
if [[ -z $QUIET ]]; then
|
||||
echo "pyenv: no $([[ -z $FROM_KNOWN ]] && echo installed || echo known) versions match the prefix \`$prefix'" >&2
|
||||
fi
|
||||
exitcode=1
|
||||
fi
|
||||
|
||||
exit $exitcode
|
|
@ -42,6 +42,7 @@ OLDIFS="$IFS"
|
|||
exit 1
|
||||
fi
|
||||
else
|
||||
version="$(pyenv-latest -q "$version" || echo "$version")"
|
||||
PYENV_PREFIX_PATH="${PYENV_ROOT}/versions/${version}"
|
||||
fi
|
||||
if [ -d "$PYENV_PREFIX_PATH" ]; then
|
||||
|
|
|
@ -34,6 +34,8 @@ OLDIFS="$IFS"
|
|||
versions=("${versions[@]}" "${version}")
|
||||
elif version_exists "${version#python-}"; then
|
||||
versions=("${versions[@]}" "${version#python-}")
|
||||
elif resolved_version="$(pyenv-latest -q "$version")"; then
|
||||
versions=("${versions[@]}" "${resolved_version}")
|
||||
else
|
||||
echo "pyenv: version \`$version' is not installed (set by $(pyenv-version-origin))" >&2
|
||||
any_not_installed=1
|
||||
|
|
|
@ -145,6 +145,10 @@ IFS=$'\n' scripts=(`pyenv-hooks install`)
|
|||
IFS="$OLDIFS"
|
||||
for script in "${scripts[@]}"; do source "$script"; done
|
||||
|
||||
# Try to resolve a prefix if user indeed gave a prefix.
|
||||
# We install the version under the resolved name
|
||||
# and hooks also see the resolved name
|
||||
DEFINITION="$(pyenv-latest -q -k "$DEFINITION" || echo "$DEFINITION")"
|
||||
|
||||
# Set VERSION_NAME from $DEFINITION, if it is not already set. Then
|
||||
# compute the installation prefix.
|
||||
|
|
|
@ -2067,12 +2067,25 @@ DEFINITION_PATH="${ARGUMENTS[0]}"
|
|||
if [ -z "$DEFINITION_PATH" ]; then
|
||||
usage 1 >&2
|
||||
elif [ ! -f "$DEFINITION_PATH" ]; then
|
||||
|
||||
search_definitions() {
|
||||
for DEFINITION_DIR in "${PYTHON_BUILD_DEFINITIONS[@]}"; do
|
||||
if [ -f "${DEFINITION_DIR}/${DEFINITION_PATH}" ]; then
|
||||
DEFINITION_PATH="${DEFINITION_DIR}/${DEFINITION_PATH}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
search_definitions
|
||||
if [ ! -f "$DEFINITION_PATH" ]; then
|
||||
if RESOLVED_DEFINITION_PATH="$(command -v pyenv-latest >/dev/null && pyenv-latest -k -q "$DEFINITION_PATH")"; then
|
||||
DEFINITION_PATH="$RESOLVED_DEFINITION_PATH"
|
||||
unset RESOLVED_DEFINITION_PATH
|
||||
search_definitions
|
||||
fi
|
||||
fi
|
||||
unset search_definitions
|
||||
|
||||
if [ ! -f "$DEFINITION_PATH" ]; then
|
||||
echo "python-build: definition not found: ${DEFINITION_PATH}" >&2
|
||||
|
|
|
@ -60,11 +60,30 @@ NUM_DEFINITIONS="$(find "$BATS_TEST_DIRNAME"/../share/python-build -maxdepth 1 -
|
|||
}
|
||||
|
||||
@test "installing nonexistent definition" {
|
||||
stub pyenv-latest false
|
||||
run python-build "nonexistent" "${TMP}/install"
|
||||
assert [ "$status" -eq 2 ]
|
||||
assert_output "python-build: definition not found: nonexistent"
|
||||
}
|
||||
|
||||
@test "resolves prefixes via pyenv-latest" {
|
||||
stub pyenv-latest "echo 2.7.8"
|
||||
export PYTHON_BUILD_ROOT="$TMP"
|
||||
mkdir -p "${PYTHON_BUILD_ROOT}/share/python-build"
|
||||
echo 'echo 2.7.8' > "${PYTHON_BUILD_ROOT}/share/python-build/2.7.8"
|
||||
run python-build "2.7" "${TMP}/install"
|
||||
assert_success "2.7.8"
|
||||
}
|
||||
|
||||
@test "doesn't resolve prefixes if pyenv-latest is unavailable" {
|
||||
export PATH="$(path_without pyenv-latest)"
|
||||
export PYTHON_BUILD_ROOT="$TMP"
|
||||
mkdir -p "${PYTHON_BUILD_ROOT}/share/python-build"
|
||||
echo 'echo 2.7.8' > "${PYTHON_BUILD_ROOT}/share/python-build/2.7.8"
|
||||
run python-build "2.7" "${TMP}/install"
|
||||
assert_failure "python-build: definition not found: 2.7"
|
||||
}
|
||||
|
||||
@test "sorting Python versions" {
|
||||
export PYTHON_BUILD_ROOT="$TMP"
|
||||
mkdir -p "${PYTHON_BUILD_ROOT}/share/python-build"
|
||||
|
|
|
@ -15,6 +15,7 @@ after_install 'echo after: \$STATUS'
|
|||
OUT
|
||||
stub pyenv-hooks "install : echo '$HOOK_PATH'/install.bash"
|
||||
stub pyenv-rehash "echo rehashed"
|
||||
stub pyenv-latest false
|
||||
|
||||
definition="${TMP}/3.6.2"
|
||||
cat > "$definition" <<<"echo python-build"
|
||||
|
|
|
@ -10,6 +10,7 @@ setup() {
|
|||
|
||||
stub_python_build() {
|
||||
stub python-build "--lib : $BATS_TEST_DIRNAME/../bin/python-build --lib" "$@"
|
||||
stub pyenv-latest " : false"
|
||||
}
|
||||
|
||||
@test "install proper" {
|
||||
|
@ -23,6 +24,19 @@ stub_python_build() {
|
|||
unstub pyenv-rehash
|
||||
}
|
||||
|
||||
@test "install resolves a prefix" {
|
||||
stub_python_build 'echo python-build "$@"'
|
||||
stub pyenv-latest '-q -k 3.4 : echo 3.4.2'
|
||||
pyenv-latest || true # pass through the stub entry added by stub_python_build
|
||||
|
||||
run pyenv-install 3.4
|
||||
assert_success "python-build 3.4.2 ${PYENV_ROOT}/versions/3.4.2"
|
||||
|
||||
unstub python-build
|
||||
unstub pyenv-hooks
|
||||
unstub pyenv-rehash
|
||||
}
|
||||
|
||||
@test "install pyenv local version by default" {
|
||||
stub_python_build 'echo python-build "$1"'
|
||||
stub pyenv-local 'echo 3.4.2'
|
||||
|
|
|
@ -139,3 +139,29 @@ assert_output_contains() {
|
|||
} | flunk
|
||||
}
|
||||
}
|
||||
|
||||
# Output a modified PATH that ensures that the given executable is not present,
|
||||
# but in which system utils necessary for pyenv operation are still available.
|
||||
path_without() {
|
||||
local path=":${PATH}:"
|
||||
for exe; do
|
||||
local found alt util
|
||||
for found in $(PATH="$path" type -aP "$exe"); do
|
||||
found="${found%/*}"
|
||||
if [ "$found" != "${PYENV_ROOT}/shims" ]; then
|
||||
alt="${PYENV_TEST_DIR}/$(echo "${found#/}" | tr '/' '-')"
|
||||
mkdir -p "$alt"
|
||||
for util in bash head cut readlink greadlink; do
|
||||
if [ -x "${found}/$util" ]; then
|
||||
ln -s "${found}/$util" "${alt}/$util"
|
||||
fi
|
||||
done
|
||||
path="${path/:${found}:/:${alt}:}"
|
||||
fi
|
||||
done
|
||||
done
|
||||
path="${path#:}"
|
||||
path="${path%:}"
|
||||
echo "$path"
|
||||
}
|
||||
|
||||
|
|
80
test/latest.bats
Normal file
80
test/latest.bats
Normal file
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
export PATH="${PYENV_TEST_DIR}/bin:$PATH"
|
||||
}
|
||||
|
||||
create_executable() {
|
||||
local name="$1"
|
||||
local bin="${PYENV_TEST_DIR}/bin"
|
||||
mkdir -p "$bin"
|
||||
sed -Ee '1s/^ +//' > "${bin}/$name"
|
||||
chmod +x "${bin}/$name"
|
||||
}
|
||||
|
||||
@test "read from installed" {
|
||||
create_executable pyenv-versions <<!
|
||||
#!$BASH
|
||||
echo 4.5.6
|
||||
!
|
||||
run pyenv-latest 4
|
||||
assert_success
|
||||
assert_output <<!
|
||||
4.5.6
|
||||
!
|
||||
}
|
||||
|
||||
@test "read from known" {
|
||||
create_executable python-build <<!
|
||||
#!$BASH
|
||||
echo 4.5.6
|
||||
!
|
||||
run pyenv-latest -k 4
|
||||
assert_success
|
||||
assert_output <<!
|
||||
4.5.6
|
||||
!
|
||||
}
|
||||
|
||||
@test "installed version not found" {
|
||||
create_executable pyenv-versions <<!
|
||||
#!$BASH
|
||||
echo 3.5.6
|
||||
echo 3.10.8
|
||||
!
|
||||
run pyenv-latest 3.8
|
||||
assert_failure
|
||||
assert_output <<!
|
||||
pyenv: no installed versions match the prefix \`3.8'
|
||||
!
|
||||
}
|
||||
|
||||
@test "known version not found" {
|
||||
create_executable python-build <<!
|
||||
#!$BASH
|
||||
echo 3.5.6
|
||||
echo 3.10.8
|
||||
!
|
||||
run pyenv-latest -k 3.8
|
||||
assert_failure
|
||||
assert_output <<!
|
||||
pyenv: no known versions match the prefix \`3.8'
|
||||
!
|
||||
}
|
||||
|
||||
@test "sort CPython" {
|
||||
create_executable pyenv-versions <<!
|
||||
#!$BASH
|
||||
echo 2.7.18
|
||||
echo 3.5.6
|
||||
echo 3.10.8
|
||||
echo 3.10.6
|
||||
!
|
||||
run pyenv-latest 3
|
||||
assert_success
|
||||
assert_output <<!
|
||||
3.10.8
|
||||
!
|
||||
}
|
|
@ -113,3 +113,10 @@ OUT
|
|||
assert_success
|
||||
assert_output "2.7.11"
|
||||
}
|
||||
|
||||
@test "falls back to pyenv-latest" {
|
||||
create_version "2.7.11"
|
||||
PYENV_VERSION="2.7" run pyenv-version-name
|
||||
assert_success
|
||||
assert_output "2.7.11"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue