diff --git a/plugins/pyenv-binary/README.md b/plugins/pyenv-binary/README.md index 8e940022..5e3ba20a 100644 --- a/plugins/pyenv-binary/README.md +++ b/plugins/pyenv-binary/README.md @@ -16,21 +16,38 @@ and dependency metadata exist to catch that. ## Commands -### `pyenv binary package : --archive-base-url ` +### `pyenv binary package [:] --archive-base-url ` -Installs `` from source under the separate name ``, 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 `` 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 `` 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 `` and drop the definition into +python-build's definition directory. + +### `pyenv binary package-name ` + +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 [] [--name ]` diff --git a/plugins/pyenv-binary/libexec/pyenv-binary-package b/plugins/pyenv-binary/libexec/pyenv-binary-package index 42294ed1..88a0283f 100755 --- a/plugins/pyenv-binary/libexec/pyenv-binary-package +++ b/plugins/pyenv-binary/libexec/pyenv-binary-package @@ -2,18 +2,17 @@ # # Summary: Create an installable binary package from a Python version # -# Usage: pyenv binary package : --archive-base-url +# Usage: pyenv binary package [:] --archive-base-url # -# Installs from source under the separate name , saves it as -# a binary package, then emits a python-build definition for that package. +# Installs 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 .tar.gz, .meta and . # # A version `pyenv install' knows how to build. -# The name to build under and to install the binary -# as, e.g. `3.13.14-debian-12'. Keeping it distinct -# from lets the binary sit alongside a -# normal source install of the same version. +# 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 # Where the archive will be hosted; the definition # downloads it from /.tar.gz. @@ -49,13 +48,9 @@ if [ -z "$spec" ] || [ -z "$archive_base_url" ]; then fi case "$spec" in -*?:?* ) ;; -* ) - echo "pyenv-binary: expected :, 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 diff --git a/plugins/pyenv-binary/libexec/pyenv-binary-package-name b/plugins/pyenv-binary/libexec/pyenv-binary-package-name new file mode 100755 index 00000000..7fbd9bf3 --- /dev/null +++ b/plugins/pyenv-binary/libexec/pyenv-binary-package-name @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# +# Summary: Generate a binary package name for the current platform +# +# Usage: pyenv binary package-name +# +# 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" diff --git a/plugins/pyenv-binary/test/package-name.bats b/plugins/pyenv-binary/test/package-name.bats new file mode 100644 index 00000000..dadb4caf --- /dev/null +++ b/plugins/pyenv-binary/test/package-name.bats @@ -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'" +} diff --git a/plugins/pyenv-binary/test/package.bats b/plugins/pyenv-binary/test/package.bats index 806e80da..a3d7ceb1 100644 --- a/plugins/pyenv-binary/test/package.bats +++ b/plugins/pyenv-binary/test/package.bats @@ -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 :, 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" {