pyenv-binary: generate platform-specific package names (#3504)

This commit is contained in:
Ayush 2026-08-05 01:23:34 +05:30 committed by GitHub
parent 3088203e33
commit b9e165271e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 173 additions and 27 deletions

View File

@ -16,21 +16,38 @@ and dependency metadata exist to catch that.
## Commands
### `pyenv binary package <version>:<entry> --archive-base-url <url>`
### `pyenv binary package <version>[:<entry>] --archive-base-url <url>`
Installs `<version>` from source under the separate name `<entry>`, packages
that install with `save`, then emits a python-build definition for it with
`generate-installer`. The archive, metadata and definition land in the current
directory, named after the entry; host the archive under `<url>` and drop the
definition into python-build's definition directory. Keeping the entry name
distinct from the version lets the binary sit alongside a normal source
install of the same version.
Installs `<version>` from source under a separate name, packages that install
with `save`, then emits a python-build definition for it with
`generate-installer`. With no explicit entry, the name is generated from the
current platform, platform version and architecture. An explicit entry keeps
the existing custom-build workflow.
```sh
pyenv binary package 3.12.7:3.12.7-debian-12 \
pyenv binary package 3.12.7 \
--archive-base-url https://example.com/binaries
# writes 3.12.7-debian-12.tar.gz, its .meta file and
# a `3.12.7-debian-12' definition
# On Debian 12 x86_64, writes 3.12.7-debian-12-x86_64.tar.gz,
# its .meta file and a `3.12.7-debian-12-x86_64' definition.
pyenv binary package 3.12.7:company-python \
--archive-base-url https://example.com/binaries
```
The archive, metadata and definition land in the current directory, named after
the entry. Host the archive under `<url>` and drop the definition into
python-build's definition directory.
### `pyenv binary package-name <version>`
Prints the automatically generated entry name without building anything. Linux
uses the distribution name and version, macOS uses the macOS version, and other
systems use the name and release reported by `uname`. All names include the
architecture.
```sh
pyenv binary package-name 3.12.7
# 3.12.7-debian-12-x86_64
```
### `pyenv binary save <version> [<output-dir>] [--name <name>]`

View File

@ -2,18 +2,17 @@
#
# Summary: Create an installable binary package from a Python version
#
# Usage: pyenv binary package <version>:<entry> --archive-base-url <url>
# Usage: pyenv binary package <version>[:<entry>] --archive-base-url <url>
#
# Installs <version> from source under the separate name <entry>, saves it as
# a binary package, then emits a python-build definition for that package.
# Installs <version> from source under a separate entry name, saves it as a
# binary package, then emits a python-build definition for that package.
# The archive, metadata and definition are written to the current directory
# as <entry>.tar.gz, <entry>.meta and <entry>.
#
# <version> A version `pyenv install' knows how to build.
# <entry> The name to build under and to install the binary
# as, e.g. `3.13.14-debian-12'. Keeping it distinct
# from <version> lets the binary sit alongside a
# normal source install of the same version.
# <entry> The optional name to build under and install the
# binary as. If omitted, a name is generated from the
# current platform, platform version and architecture.
# --archive-base-url <url>
# Where the archive will be hosted; the definition
# downloads it from <url>/<entry>.tar.gz.
@ -49,13 +48,9 @@ if [ -z "$spec" ] || [ -z "$archive_base_url" ]; then
fi
case "$spec" in
*?:?* ) ;;
* )
echo "pyenv-binary: expected <version>:<entry>, e.g. \`3.13.14:3.13.14-debian-12'" >&2
exit 1
;;
*?:?* ) entry="${spec##*:}" ;;
* ) entry="$(pyenv-binary-package-name "$spec")"; spec="${spec}:${entry}" ;;
esac
entry="${spec##*:}"
case "$entry" in
# `pyenv install' reads a trailing `:latest' as part of the version rather than

View File

@ -0,0 +1,71 @@
#!/usr/bin/env bash
#
# Summary: Generate a binary package name for the current platform
#
# Usage: pyenv binary package-name <version>
#
# Prints a package name containing the Python version, platform name,
# platform version and architecture.
#
set -e
[ -n "$PYENV_DEBUG" ] && set -x
if [ "$1" = "--complete" ]; then
exec pyenv-install --list --bare
fi
if [ $# -ne 1 ]; then
pyenv-help --usage binary-package-name >&2
exit 1
fi
version="$1"
case "$version" in
*/* | .. | . | *[[:cntrl:]]* )
echo "pyenv-binary: invalid version name \`${version}'" >&2
exit 1
;;
esac
os="$(uname -s)"
arch="$(uname -m)"
distro=""
distro_version=""
case "$os" in
Linux )
if type -p lsb_release >/dev/null; then
distro="$(lsb_release -si)"
distro_version="$(lsb_release -sr)"
elif [ -r /etc/os-release ]; then
distro="$(. /etc/os-release && printf '%s' "${ID:-}")"
distro_version="$(. /etc/os-release && printf '%s' "${VERSION_ID:-}")"
fi
;;
Darwin )
distro="macos"
distro_version="$(sw_vers -productVersion)"
;;
* )
distro="$os"
distro_version="$(uname -r)"
;;
esac
slug() {
printf '%s' "$1" |
tr '[:upper:] ' '[:lower:]-' |
tr -cd 'a-z0-9._-'
}
distro="$(slug "$distro")"
distro_version="$(slug "$distro_version")"
arch="$(slug "$arch")"
if [ -z "$distro" ] || [ -z "$distro_version" ] || [ -z "$arch" ]; then
echo "pyenv-binary: could not determine the package platform" >&2
exit 1
fi
printf '%s-%s-%s-%s\n' "$version" "$distro" "$distro_version" "$arch"

View File

@ -0,0 +1,54 @@
#!/usr/bin/env bats
load test_helper
@test "completion lists installable versions" {
create_stub pyenv-install \
'[ "$*" = "--list --bare" ] && echo 3.13.14'
run pyenv-binary-package-name --complete
assert_success "3.13.14"
}
@test "fails without a version" {
create_stub pyenv-help 'echo usage'
run pyenv-binary-package-name
assert_failure "usage"
}
@test "rejects a second argument" {
create_stub pyenv-help 'echo usage'
run pyenv-binary-package-name 3.13.14 extra
assert_failure "usage"
}
@test "generates a package name for Linux" {
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
create_stub lsb_release 'case "$1" in -si) echo Debian;; -sr) echo 12;; esac'
run pyenv-binary-package-name 3.13.14
assert_success "3.13.14-debian-12-x86_64"
}
@test "generates a package name for macOS" {
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
create_stub sw_vers 'echo 15.5'
run pyenv-binary-package-name 3.13.14
assert_success "3.13.14-macos-15.5-arm64"
}
@test "generates a package name for FreeBSD" {
create_stub uname \
'case "$1" in -s) echo FreeBSD;; -m) echo amd64;; -r) echo 14.2-RELEASE-p3;; esac'
run pyenv-binary-package-name 3.13.14
assert_success "3.13.14-freebsd-14.2-release-p3-amd64"
}
@test "rejects an invalid version name" {
run pyenv-binary-package-name ../3.13.14
assert_failure "pyenv-binary: invalid version name \`../3.13.14'"
}

View File

@ -45,9 +45,18 @@ stub_build_environment() {
assert_failure "pyenv-binary: unexpected argument \`extra'"
}
@test "rejects a bare version without an entry name" {
run pyenv-binary-package 3.12.7 --archive-base-url http://x/b
assert_failure "pyenv-binary: expected <version>:<entry>, e.g. \`3.13.14:3.13.14-debian-12'"
@test "generates an entry name for a bare version" {
stub_build_environment
create_stub lsb_release 'case "$1" in -si) echo Debian;; -sr) echo 12;; esac'
cd "${BATS_TEST_TMPDIR}"
run pyenv-binary-package 3.12.7 --archive-base-url http://example.com/binaries
assert_success
assert [ -d "${PYENV_ROOT}/versions/3.12.7-debian-12-x86_64" ]
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-debian-12-x86_64.tar.gz" ]
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-debian-12-x86_64.meta" ]
run grep '^ARCHIVE_URL=' "${BATS_TEST_TMPDIR}/3.12.7-debian-12-x86_64"
assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-debian-12-x86_64.tar.gz"
}
@test "rejects an entry name containing a slash" {