Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
563bcff
Improve performance of pyenv-virtualenvs
samdoran Feb 13, 2025
97e1333
Shellcheck fixes, mostly quoting to avoid word splitting
samdoran Feb 13, 2025
6d2cb28
Add --only-aliases argument
samdoran Feb 13, 2025
ad14880
Only show Python versions for completion when creating new virtual en…
samdoran Feb 13, 2025
39f3584
Only show aliases as completions for `pyenv activate`
samdoran Feb 13, 2025
6443dea
Update tests
samdoran Feb 13, 2025
13b0d85
Add skip-aliases to complete output
samdoran Mar 12, 2025
36eb43e
Use conditional expressions
samdoran Mar 12, 2025
4d2fd5d
Remove unused code
samdoran Mar 12, 2025
9ea8bb6
Use the newer [[ command
samdoran Mar 12, 2025
d7b3787
Remove --only-aliases option for later discussion
samdoran Mar 12, 2025
f9d1686
Correct test setup so aliases are properly symlinked
samdoran Apr 15, 2026
f966c97
Update tests and add more test cases
samdoran Apr 15, 2026
8444dad
Revert `activate` completion hint change
native-api Apr 16, 2026
44c4090
Refactor
native-api Apr 16, 2026
5fd4975
Include aliases in completetions
samdoran Apr 20, 2026
fed3b04
Remove warning for no virtualenvs
samdoran Apr 20, 2026
5ac3b3a
Check for existence of venv directory
samdoran Apr 23, 2026
c483e44
Fix incorrect IFS handling
samdoran Apr 23, 2026
d8c1a67
Remove pyenv-prefix stubs from tests
samdoran Apr 24, 2026
3546afd
Add a test for version resolution
native-api Apr 24, 2026
28a8b0f
fix test
native-api Apr 24, 2026
6fb433c
- dead code
native-api Apr 24, 2026
b52417e
Remove unnecessary test
samdoran Apr 24, 2026
a105a58
Add a helper for creating virtual environments
samdoran Apr 24, 2026
7e89747
Change the interface of create_m_venv to match setup_m_venv
samdoran Apr 24, 2026
19efcdc
Show venvs created from system Python
samdoran Apr 24, 2026
5e134e1
Remove counter that is no longer used
samdoran Apr 24, 2026
ba8598a
Add setup function for creating a mock system venv
samdoran Apr 27, 2026
c6cb3ed
Force creation of symlink if it exists
samdoran Apr 28, 2026
6391e0a
Add a performance regression test
samdoran Apr 28, 2026
372737b
Add a few missed unstub calls
samdoran Apr 28, 2026
34531d4
Remove performance test
samdoran Apr 28, 2026
006e798
test_prefix.bats: - remove_*
native-api Apr 28, 2026
adc9e5e
+ refactor notes
native-api Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/pyenv-virtualenv
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fi

# Provide pyenv completions
if [ "$1" = "--complete" ]; then
exec pyenv-versions --bare
exec pyenv-versions --bare --skip-envs
fi

unset PIP_REQUIRE_VENV
Expand Down
28 changes: 23 additions & 5 deletions bin/pyenv-virtualenv-prefix
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ if [ -z "$PYENV_ROOT" ]; then
PYENV_ROOT="${HOME}/.pyenv"
fi

OLDIFS="$IFS"
IFS=:
if [ -n "$1" ]; then
versions=($@)
IFS=: PYENV_VERSION="${versions[*]}"
# $@ is not affected by IFS
versions=("$@")
PYENV_VERSION="${versions[*]}"
export PYENV_VERSION
else
IFS=: versions=($(pyenv-version-name))
versions=($(pyenv-version-name))
fi
IFS="$OLDIFS"

append_virtualenv_prefix() {
if [ -d "${VIRTUALENV_PREFIX_PATH}" ]; then
Expand All @@ -49,7 +53,18 @@ for version in "${versions[@]}"; do
echo "pyenv-virtualenv: version \`${version}' is not a virtualenv" 1>&2
exit 1
fi
PYENV_PREFIX_PATH="$(pyenv-prefix "${version}")"

# In the vast majority of cases, there's a direct match and
# not spawning `pyenv-prefix' saves about half the invocation time
# with a signle argument which accumulates when called repeatedly
# (e.g. from `pyenv-virtualenvs').
# `pyenv-prefix' also does not have hooks to worry about.
# XXX: refactor the test into a shared module?
PYENV_PREFIX_PATH="${PYENV_ROOT}/versions/${version}"
if [[ ! -d "$PYENV_PREFIX_PATH" ]]; then
PYENV_PREFIX_PATH="$(pyenv-prefix "${version}")"
fi

if [ -x "${PYENV_PREFIX_PATH}/bin/python" ]; then
if [ -f "${PYENV_PREFIX_PATH}/bin/activate" ]; then
if [ -f "${PYENV_PREFIX_PATH}/bin/conda" ]; then
Expand Down Expand Up @@ -94,4 +109,7 @@ for version in "${versions[@]}"; do
fi
done

IFS=: echo "${VIRTUALENV_PREFIX_PATHS[*]}"
OLDIFS="$IFS"
IFS=:
echo "${VIRTUALENV_PREFIX_PATHS[*]}"
IFS="$OLDIFS"
125 changes: 68 additions & 57 deletions bin/pyenv-virtualenvs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,9 @@
# List all virtualenvs found in `$PYENV_ROOT/versions/*' and its `$PYENV_ROOT/versions/envs/*'.

set -e
[ -n "$PYENV_DEBUG" ] && set -x
if [ -L "${BASH_SOURCE}" ]; then
READLINK=$(type -p greadlink readlink | head -1)
if [ -z "$READLINK" ]; then
echo "pyenv: cannot find readlink - are you missing GNU coreutils?" >&2
exit 1
fi
resolve_link() {
$READLINK -f "$1"
}
script_path=$(resolve_link ${BASH_SOURCE})
else
script_path=${BASH_SOURCE}
fi

. ${script_path%/*}/../libexec/pyenv-virtualenv-realpath
[[ -n $PYENV_DEBUG ]] && set -x

if [ -z "$PYENV_ROOT" ]; then
if [[ -z $PYENV_ROOT ]]; then
PYENV_ROOT="${HOME}/.pyenv"
fi

Expand All @@ -47,28 +32,26 @@ done

versions_dir="${PYENV_ROOT}/versions"

if [ -d "$versions_dir" ]; then
versions_dir="$(realpath "$versions_dir")"
fi

if [ -n "$bare" ]; then
hit_prefix=""
miss_prefix=""
current_versions=()
unset print_origin
include_system=""
if [[ ${BASH_VERSINFO[0]} -gt 3 ]]; then
declare -A current_versions
else
current_versions=()
fi
if [[ -z $bare ]]; then
hit_prefix="* "
miss_prefix=" "
OLDIFS="$IFS"
IFS=: current_versions=($(pyenv-version-name || true))
IFS=:
if [[ ${BASH_VERSINFO[0]} -gt 3 ]]; then
for i in $(pyenv-version-name || true); do
current_versions["$i"]="1"
done
else
read -r -a current_versions <<< "$(pyenv-version-name || true)"
fi
IFS="$OLDIFS"
print_origin="1"
include_system=""
fi

num_versions=0

exists() {
local car="$1"
local cdar
Expand All @@ -82,39 +65,67 @@ exists() {
}

print_version() {
if exists "$1" "${current_versions[@]}"; then
echo "${hit_prefix}${1}${print_origin+$2}"
local version="${1:?}"
if [[ -n $bare ]]; then
echo "$version"
return
fi
local path="${2:?}"
if [[ -L "$path" ]]; then
version_repr="$version --> $(readlink "$path")"
else
version_repr="$version (created from $(pyenv-virtualenv-prefix "$version" 2>/dev/null))"
fi
if [[ ${BASH_VERSINFO[0]} -gt 3 && ${current_versions["$1"]} ]] || \
{ [[ ${BASH_VERSINFO[0]} -le 3 ]] && exists "$1" "${current_versions[@]}"; }; then
echo "${hit_prefix}${version_repr} (set by $(pyenv-version-origin))"
else
echo "${miss_prefix}${1}${print_origin+$2}"
echo "${miss_prefix}${version_repr}"
fi
num_versions=$((num_versions + 1))
}

shopt -s dotglob
shopt -s nullglob
for path in "$versions_dir"/*; do
if [ -d "$path" ]; then
if [ -n "$skip_aliases" ] && [ -L "$path" ]; then
target="$(realpath "$path")"
[ "${target%/*/envs/*}" != "$versions_dir" ] || continue
fi
virtualenv_prefix="$(pyenv-virtualenv-prefix "${path##*/}" 2>/dev/null || true)"
if [ -d "${virtualenv_prefix}" ]; then
print_version "${path##*/}" " (created from ${virtualenv_prefix})"
fi
for venv_path in "${path}/envs/"*; do
venv="${path##*/}/envs/${venv_path##*/}"
virtualenv_prefix="$(pyenv-virtualenv-prefix "${venv}" 2>/dev/null || true)"
if [ -d "${virtualenv_prefix}" ]; then
print_version "${venv}" " (created from ${virtualenv_prefix})"
version_dir_entries=("$versions_dir"/*)
venv_dir_entries=("$versions_dir"/*/envs/*)

if sort --version-sort </dev/null >/dev/null 2>&1; then
# system sort supports version sorting
OLDIFS="$IFS"
IFS='||'

read -r -a version_dir_entries <<< "$(
printf "%s||" "${version_dir_entries[@]}" |
sort --version-sort
)"

read -r -a venv_dir_entries <<< "$(
printf "%s||" "${venv_dir_entries[@]}" |
sort --version-sort
)"

IFS="$OLDIFS"
fi

for env_path in "${venv_dir_entries[@]}"; do
if [[ -d ${env_path} ]]; then
print_version "${env_path#"${PYENV_ROOT}"/versions/}" "${env_path}"
fi
done

for env_path in "${version_dir_entries[@]}"; do
if [[ -d ${env_path} ]]; then
if [[ -L ${env_path} ]]; then
if [[ -z $skip_aliases ]]; then
print_version "${env_path#"${PYENV_ROOT}"/versions/}" "${env_path}"
fi
done
# Mimics the test from pyenv-virtualenv-prefix
# XXX: refactor itto a shared module ?
elif [[ -f "${env_path}/bin/activate" ]]; then
print_version "${env_path#"${PYENV_ROOT}"/versions/}" "${env_path}"
fi
fi
done

shopt -u dotglob
shopt -u nullglob

if [ "$num_versions" -eq 0 ] && [ -n "$include_system" ]; then
echo "Warning: no Python virtualenv detected on the system" >&2
exit 1
fi
4 changes: 0 additions & 4 deletions test/conda-prefix.bats
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ setup() {
@test "display conda root" {
setup_conda "anaconda-2.3.0"
stub pyenv-version-name "echo anaconda-2.3.0"
stub pyenv-prefix "anaconda-2.3.0 : echo \"${PYENV_ROOT}/versions/anaconda-2.3.0\""

PYENV_VERSION="anaconda-2.3.0" run pyenv-virtualenv-prefix

Expand All @@ -19,14 +18,12 @@ ${PYENV_ROOT}/versions/anaconda-2.3.0
OUT

unstub pyenv-version-name
unstub pyenv-prefix
teardown_conda "anaconda-2.3.0"
}

@test "display conda env" {
setup_conda "anaconda-2.3.0" "foo"
stub pyenv-version-name "echo anaconda-2.3.0/envs/foo"
stub pyenv-prefix "anaconda-2.3.0/envs/foo : echo \"${PYENV_ROOT}/versions/anaconda-2.3.0/envs/foo\""

PYENV_VERSION="anaconda-2.3.0/envs/foo" run pyenv-virtualenv-prefix

Expand All @@ -36,6 +33,5 @@ ${PYENV_ROOT}/versions/anaconda-2.3.0/envs/foo
OUT

unstub pyenv-version-name
unstub pyenv-prefix
teardown_conda "anaconda-2.3.0" "foo"
}
Loading
Loading