Merge pull request #3516 from macayu17/fix/2334-package-feedback

pyenv-binary-package: check patchelf early and add --verbose
This commit is contained in:
native-api 2026-08-11 05:25:39 +03:00 committed by GitHub
commit d92b6e1bbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 122 additions and 45 deletions

View File

@ -16,7 +16,7 @@ and dependency metadata exist to catch that.
## Commands ## Commands
### `pyenv binary package <version>[:<entry>] --archive-base-url <url>` ### `pyenv binary package [-v|--verbose] <version>[:<entry>] --archive-base-url <url>`
Installs `<version>` from source under a separate name, packages that install Installs `<version>` from source under a separate name, packages that install
with `save`, then emits a python-build definition for it with with `save`, then emits a python-build definition for it with
@ -24,6 +24,8 @@ with `save`, then emits a python-build definition for it with
current platform, platform version and architecture. An explicit entry keeps current platform, platform version and architecture. An explicit entry keeps
the existing custom-build workflow. the existing custom-build workflow.
Pass `-v` to show build progress from `pyenv install`.
```sh ```sh
pyenv binary package 3.12.7 \ pyenv binary package 3.12.7 \
--archive-base-url https://example.com/binaries --archive-base-url https://example.com/binaries

View File

@ -185,6 +185,11 @@ elif [ -n "$DEPS" ]; then
echo "pyenv-binary: cannot check required system libraries (ldconfig cache unavailable)" >&2 echo "pyenv-binary: cannot check required system libraries (ldconfig cache unavailable)" >&2
fi fi
if ! command -v patchelf >/dev/null 2>&1; then
echo "pyenv-binary: need patchelf to relocate the binary" >&2
exit 1
fi
build_package_relocate() { build_package_relocate() {
pyenv binary relocate "$PREFIX_PATH" pyenv binary relocate "$PREFIX_PATH"
} }

View File

@ -2,7 +2,7 @@
# #
# Summary: Create an installable binary package from a Python version # Summary: Create an installable binary package from a Python version
# #
# Usage: pyenv binary package <version>[:<entry>] --archive-base-url <url> # Usage: pyenv binary package [-v|--verbose] <version>[:<entry>] --archive-base-url <url>
# #
# Installs <version> from source under a separate entry name, saves it as a # Installs <version> from source under a separate entry name, saves it as a
# binary package, then emits a python-build definition for that package. # binary package, then emits a python-build definition for that package.
@ -13,6 +13,7 @@
# <entry> The optional name to build under and install the # <entry> The optional name to build under and install the
# binary as. If omitted, a name is generated from the # binary as. If omitted, a name is generated from the
# current platform, platform version and architecture. # current platform, platform version and architecture.
# -v,--verbose Show build progress from `pyenv install'.
# --archive-base-url <url> # --archive-base-url <url>
# Where the archive will be hosted; the definition # Where the archive will be hosted; the definition
# downloads it from <url>/<entry>.tar.gz. # downloads it from <url>/<entry>.tar.gz.
@ -23,23 +24,29 @@ set -e
# Provide pyenv completions # Provide pyenv completions
if [ "$1" = "--complete" ]; then if [ "$1" = "--complete" ]; then
echo --archive-base-url echo --archive-base-url
echo --verbose
exec pyenv-install --list --bare exec pyenv-install --list --bare
fi fi
spec="" spec=""
archive_base_url="" archive_base_url=""
verbose=""
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
--archive-base-url ) --archive-base-url )
[ $# -ge 2 ] || { echo "pyenv-binary: --archive-base-url needs a value" >&2; exit 1; } [ $# -lt 2 ] && { echo "pyenv-binary: --archive-base-url needs a value" >&2; exit 1; }
archive_base_url="$2"; shift 2 ;; archive_base_url="$2"; shift ;;
-v|--verbose)
verbose=1 ;;
-* ) -* )
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;; echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
* ) * )
[ -z "$spec" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; } [ -z "$spec" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; }
spec="$1"; shift ;; spec="$1"
;;
esac esac
shift
done done
if [ -z "$spec" ] || [ -z "$archive_base_url" ]; then if [ -z "$spec" ] || [ -z "$archive_base_url" ]; then
@ -78,7 +85,7 @@ if [ "$os" = "Darwin" ]; then
fi fi
# `pyenv install' puts a `<version>:<alias>' build under versions/<alias>. # `pyenv install' puts a `<version>:<alias>' build under versions/<alias>.
pyenv-install "$spec" pyenv-install ${verbose:+--verbose} "$spec"
pyenv-binary-save "$entry" "$PWD" --name "$entry" pyenv-binary-save "$entry" "$PWD" --name "$entry"
pyenv-binary-generate-installer "${entry}.meta" \ pyenv-binary-generate-installer "${entry}.meta" \

View File

@ -106,6 +106,17 @@ create_meta() {
assert_failure "pyenv-binary: missing required system libraries: libmissing.so.1" assert_failure "pyenv-binary: missing required system libraries: libmissing.so.1"
} }
@test "checks for patchelf before installing the archive" {
local out="${BATS_TEST_TMPDIR}/definition"
pyenv-binary-generate-installer "$(create_meta)" \
--archive-url http://example.com/a.tar.gz -o "$out"
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
create_stub getconf 'echo "glibc 2.31"'
PATH="$(path_without patchelf)" run bash "$out"
assert_failure "pyenv-binary: need patchelf to relocate the binary"
}
@test "fails when the archive is not beside the metadata" { @test "fails when the archive is not beside the metadata" {
local meta="$(create_meta)" local meta="$(create_meta)"
rm "${BATS_TEST_TMPDIR}/3.12.7.tar.gz" rm "${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
@ -125,6 +136,7 @@ create_meta() {
mkdir -p "$cache" mkdir -p "$cache"
cp "$archive" "${cache}/Python-3.12.7-binary.tar.gz" cp "$archive" "${cache}/Python-3.12.7-binary.tar.gz"
create_stub pyenv 'echo "pyenv $*"' create_stub pyenv 'echo "pyenv $*"'
create_path_executable patchelf "exit 0"
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac' create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
PYTHON_BUILD_CACHE_PATH="$cache" run \ PYTHON_BUILD_CACHE_PATH="$cache" run \

View File

@ -6,22 +6,33 @@ load test_helper
# the platform tools report a fixed Linux target so the real `save' and # the platform tools report a fixed Linux target so the real `save' and
# `generate-installer' behave the same on any test host. # `generate-installer' behave the same on any test host.
stub_build_environment() { stub_build_environment() {
create_stub pyenv-install 'mkdir -p "${PYENV_ROOT}/versions/${1##*:}/bin"' create_stub pyenv-install 'echo "${0##*/} $*"; mkdir -p "${PYENV_ROOT}/versions/${1##*:}/bin"'
create_stub pyenv-latest '[ "$1" = "-f" ] && [ "$2" = "-k" ] && shift 2 && echo "$*"' create_stub pyenv-latest 'while (($#)); do case "$1" in -f|-k);; *)break;; esac; shift; done; echo "$*"'
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac' create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
create_stub getconf 'echo "glibc 2.17"' create_stub getconf 'echo "glibc 2.17"'
} }
@test "completion lists the option and definitions provided by another plugin" { @test "-v|--verbose runs pyenv install verbosely" {
mkdir -p "${PYENV_ROOT}/plugins/example/share/python-build" stub_build_environment
touch "${PYENV_ROOT}/plugins/example/share/python-build/3.12.7-example" create_stub pyenv-binary-save true
PATH="${BATS_TEST_DIRNAME}/../../python-build/bin:${PATH}" create_stub pyenv-binary-generate-installer true
for opt in "" -v --verbose; do
run pyenv-binary-package $opt 3.12.7:3.12.7-test \
--archive-base-url http://example.com/binaries
assert_success "pyenv-install ${opt:+--verbose }3.12.7:3.12.7-test"
done
}
@test "completions" {
create_stub pyenv-install 'echo "${0##*/} $*"'
run pyenv-binary-package --complete run pyenv-binary-package --complete
assert_success assert_success <<!
assert_line "--archive-base-url" --archive-base-url
assert_line "3.12.7-example" --verbose
refute_line "Available versions:" pyenv-install --list --bare
!
} }
@test "fails with no arguments" { @test "fails with no arguments" {
@ -76,7 +87,7 @@ stub_build_environment() {
assert_failure "pyenv-binary: macOS archives are not supported yet" assert_failure "pyenv-binary: macOS archives are not supported yet"
} }
@test "writes the archive, metadata and definition under the entry name" { @test "writes the archive, metadata and definition under the entry name (integration)" {
stub_build_environment stub_build_environment
cd "${BATS_TEST_TMPDIR}" cd "${BATS_TEST_TMPDIR}"
@ -90,13 +101,24 @@ stub_build_environment() {
assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test.tar.gz" assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test.tar.gz"
} }
@test "strips a trailing slash from the archive base url" { @test "correctly joins archive base url with a trailing slash" {
stub_build_environment stub_build_environment
cd "${BATS_TEST_TMPDIR}" create_stub pyenv-binary-save true
create_stub pyenv-binary-generate-installer <<'!'
echo -n "${0##*/} "
while (($#)); do
case $1 in
--archive-url)
echo "$1 ${2:?}"
break
;;
esac
shift
done
!
run pyenv-binary-package 3.12.7:3.12.7-test \ run pyenv-binary-package 3.12.7:3.12.7-test \
--archive-base-url http://example.com/binaries/ --archive-base-url http://example.com/binaries/
assert_success assert_success
run grep '^ARCHIVE_URL=' "${BATS_TEST_TMPDIR}/3.12.7-test" assert_line "pyenv-binary-generate-installer --archive-url http://example.com/binaries/3.12.7-test.tar.gz"
assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test.tar.gz"
} }

View File

@ -23,7 +23,8 @@ load test_helper
touch "${PYENV_ROOT}/shims/.pyenv-shim" touch "${PYENV_ROOT}/shims/.pyenv-shim"
#avoid failure due to a localized error message #avoid failure due to a localized error message
LANG=C run pyenv-rehash LANG=C run pyenv-rehash
assert_failure <<! assert_failure
assert_output_glob <<!
pyenv: cannot rehash: couldn't acquire lock ${PYENV_ROOT}/shims/.pyenv-shim for 1 seconds. Last error message: pyenv: cannot rehash: couldn't acquire lock ${PYENV_ROOT}/shims/.pyenv-shim for 1 seconds. Last error message:
*/pyenv-rehash: line *: ${PYENV_ROOT}/shims/.pyenv-shim: cannot overwrite existing file */pyenv-rehash: line *: ${PYENV_ROOT}/shims/.pyenv-shim: cannot overwrite existing file
! !

View File

@ -58,34 +58,64 @@ assert_success() {
if [ "$status" -ne 0 ]; then if [ "$status" -ne 0 ]; then
flunk "command failed with exit status $status" $'\n'\ flunk "command failed with exit status $status" $'\n'\
"output: $output" "output: $output"
elif [ "$#" -gt 0 ]; then
assert_output "$1"
fi fi
_assert_output_if_provided "$@"
} }
assert_failure() { assert_failure() {
if [ "$status" -eq 0 ]; then if [ "$status" -eq 0 ]; then
flunk "expected failed exit status" $'\n'\ flunk "expected failed exit status" $'\n'\
"output: $output" "output: $output"
elif [ "$#" -gt 0 ]; then
assert_output "$1"
fi fi
_assert_output_if_provided "$@"
} }
assert_equal() { assert_equal() {
if [ "$1" != "$2" ]; then _assert_equal_as text "$@"
{ echo "expected: \`$1'" }
echo "actual: \`$2'"
_assert_equal_as() {
if case "$1" in
text)
[[ "$2" != "$3" ]];;
glob)
[[ "$3" != $2 ]];;
*)
echo "invalid comparison type: \`$1'" >&2; return 1;;
esac
then
{ diff -u --label expected --label actual <(echo "$2") <(echo "$3") | cat -te
} | flunk } | flunk
fi fi
} }
_assert_output_if_provided() {
if [ "$#" -gt 0 ]; then
assert_output "$1"
elif [ ! -t 0 ]; then
local expected=$(cat -)
if [[ -n $expected ]]; then
assert_output "$expected"
fi
fi
}
assert_output() { assert_output() {
_assert_output_as text "$@"
}
assert_output_glob() {
_assert_output_as glob "$@"
}
_assert_output_as() {
local kind="${1:?}"; shift
local expected local expected
if [ $# -eq 0 ]; then expected="$(cat -)" if [ $# -eq 0 ]
then expected="$(cat -)"
else expected="$1" else expected="$1"
fi fi
assert_equal "$expected" "$output" _assert_equal_as "$kind" "$expected" "$output"
} }
assert_line() { assert_line() {

View File

@ -90,13 +90,13 @@ IN
} }
@test "skips glob path traversal" { @test "skips glob path traversal" {
cat > my-version <<IN cat > my-version <<'IN'
../* ../*
3.9.3 3.9.3
IN IN
run pyenv-version-file-read my-version run pyenv-version-file-read my-version
assert_success <<OUT assert_success <<'OUT'
pyenv: invalid version \`../\*' ignored in \`my-version' pyenv: invalid version `../*' ignored in `my-version'
3.9.3 3.9.3
OUT OUT
} }

View File

@ -83,8 +83,7 @@ OUT
create_version "3.4.0/envs/bar" create_version "3.4.0/envs/bar"
create_version "3.5.2" create_version "3.5.2"
run pyenv-versions run pyenv-versions
assert_success assert_success <<OUT
assert_output <<OUT
* system (set by ${PYENV_ROOT}/version) * system (set by ${PYENV_ROOT}/version)
2.7.6 2.7.6
3.4.0 3.4.0
@ -95,6 +94,7 @@ OUT
} }
@test "skips envs with --skip-envs" { @test "skips envs with --skip-envs" {
stub_system_python
create_version "3.3.3" create_version "3.3.3"
create_version "3.4.0" create_version "3.4.0"
create_version "3.4.0/envs/foo" create_version "3.4.0/envs/foo"
@ -188,9 +188,7 @@ OUT
create_external_version "moo" create_external_version "moo"
run pyenv-versions --bare --skip-aliases run pyenv-versions --bare --skip-aliases
assert_success assert_success <<OUT
assert_output <<OUT
1.8.7 1.8.7
moo moo
OUT OUT