From 94fcf1e269e0f8d609b8e6b12f8eb37ceed1aa34 Mon Sep 17 00:00:00 2001 From: macayu17 Date: Tue, 28 Jul 2026 11:43:49 +0530 Subject: [PATCH] pyenv-binary: rename `build` to `package` Delegate completion to `pyenv install --list`, use the explicit entry for the archive and metadata names, and distinguish the base URL from `generate-installer`'s full archive URL. --- plugins/pyenv-binary/README.md | 16 +++--- ...yenv-binary-build => pyenv-binary-package} | 55 ++++++++----------- .../pyenv-binary/libexec/pyenv-binary-save | 46 ++++++++++++++-- .../test/{build.bats => package.bats} | 49 +++++++++-------- plugins/pyenv-binary/test/save.bats | 25 ++++++++- 5 files changed, 122 insertions(+), 69 deletions(-) rename plugins/pyenv-binary/libexec/{pyenv-binary-build => pyenv-binary-package} (51%) rename plugins/pyenv-binary/test/{build.bats => package.bats} (58%) diff --git a/plugins/pyenv-binary/README.md b/plugins/pyenv-binary/README.md index 4c0b2bc0..8e940022 100644 --- a/plugins/pyenv-binary/README.md +++ b/plugins/pyenv-binary/README.md @@ -16,10 +16,10 @@ and dependency metadata exist to catch that. ## Commands -### `pyenv binary build : --archive-url ` +### `pyenv binary package : --archive-base-url ` -Builds `` from source under the separate name ``, packages it -with `save`, then emits a python-build definition for it with +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 @@ -27,16 +27,18 @@ distinct from the version lets the binary sit alongside a normal source install of the same version. ```sh -pyenv binary build 3.12.7:3.12.7-debian-12 --archive-url https://example.com/binaries -# writes 3.12.7-debian-12-linux-x86_64.tar.gz, its .meta file and +pyenv binary package 3.12.7:3.12.7-debian-12 \ + --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 ``` -### `pyenv binary save []` +### `pyenv binary save [] [--name ]` Packs an installed version into `-.tar.gz` (relative paths) and writes `-.meta` describing the build platform (OS, arch, -distro and libc version) and the system libraries the build links against. +distro and libc version) and the system libraries the build links against. Use +`--name` to set a different base name for both files. ```sh pyenv binary save 3.12.7 ./dist diff --git a/plugins/pyenv-binary/libexec/pyenv-binary-build b/plugins/pyenv-binary/libexec/pyenv-binary-package similarity index 51% rename from plugins/pyenv-binary/libexec/pyenv-binary-build rename to plugins/pyenv-binary/libexec/pyenv-binary-package index 95093e7f..678d08a5 100755 --- a/plugins/pyenv-binary/libexec/pyenv-binary-build +++ b/plugins/pyenv-binary/libexec/pyenv-binary-package @@ -1,48 +1,41 @@ #!/usr/bin/env bash # -# Summary: Build a Python version and package it as an installable binary +# Summary: Create an installable binary package from a Python version # -# Usage: pyenv binary build : --archive-url +# Usage: pyenv binary package : --archive-base-url # -# Builds from source under the separate name , packages the -# result with `pyenv binary save', then emits a python-build definition for -# it with `pyenv binary generate-installer'. The archive, metadata and -# definition are written to the current directory, named after . +# Installs from source under the separate 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. -# --archive-url Where the archive will be hosted; the definition -# downloads it from /. +# --archive-base-url +# Where the archive will be hosted; the definition +# downloads it from /.tar.gz. # set -e [ -n "$PYENV_DEBUG" ] && set -x -# Provide pyenv completions. Only the definitions are useful here: the options -# `pyenv install' takes are not accepted. Include definitions from other -# plugins just as `pyenv install' does. +# Provide pyenv completions if [ "$1" = "--complete" ]; then - shopt -s nullglob - for plugin_path in "$PYENV_ROOT"/plugins/*/share/python-build; do - PYTHON_BUILD_DEFINITIONS="${PYTHON_BUILD_DEFINITIONS}:${plugin_path}" - done - export PYTHON_BUILD_DEFINITIONS - shopt -u nullglob - - echo --archive-url - exec python-build --definitions + echo --archive-base-url + pyenv-install --list | sed '1d; s/^ //' + exit fi spec="" -archive_url="" +archive_base_url="" while [ $# -gt 0 ]; do case "$1" in - --archive-url ) - [ $# -ge 2 ] || { echo "pyenv-binary: --archive-url needs a value" >&2; exit 1; } - archive_url="$2"; shift 2 ;; + --archive-base-url ) + [ $# -ge 2 ] || { echo "pyenv-binary: --archive-base-url needs a value" >&2; exit 1; } + archive_base_url="$2"; shift 2 ;; -* ) echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;; * ) @@ -51,8 +44,8 @@ while [ $# -gt 0 ]; do esac done -if [ -z "$spec" ] || [ -z "$archive_url" ]; then - pyenv-help --usage binary-build >&2 +if [ -z "$spec" ] || [ -z "$archive_base_url" ]; then + pyenv-help --usage binary-package >&2 exit 1 fi @@ -84,7 +77,7 @@ esac os="$(uname -s)" # `generate-installer' refuses a macOS archive, so the last step here cannot -# succeed on one. Give up before the build rather than after it. +# succeed on one. Give up before compiling rather than after it. if [ "$os" = "Darwin" ]; then echo "pyenv-binary: macOS archives are not supported yet" >&2 exit 1 @@ -92,9 +85,7 @@ fi # `pyenv install' puts a `:' build under versions/. pyenv-install "$spec" -pyenv-binary-save "$entry" +pyenv-binary-save "$entry" "$PWD" --name "$entry" -# Name the metadata and the archive the way `save' just named them. -platform="$(printf '%s' "$os" | tr '[:upper:]' '[:lower:]')-$(uname -m)" -pyenv-binary-generate-installer "${entry}-${platform}.meta" \ - --archive-url "${archive_url%/}/${entry}-${platform}.tar.gz" -o "$entry" +pyenv-binary-generate-installer "${entry}.meta" \ + --archive-url "${archive_base_url%/}/${entry}.tar.gz" -o "$entry" diff --git a/plugins/pyenv-binary/libexec/pyenv-binary-save b/plugins/pyenv-binary/libexec/pyenv-binary-save index 7a20b5c4..32df661b 100755 --- a/plugins/pyenv-binary/libexec/pyenv-binary-save +++ b/plugins/pyenv-binary/libexec/pyenv-binary-save @@ -2,7 +2,7 @@ # # Summary: Save an installed Python version as a relocatable archive # -# Usage: pyenv binary save [] +# Usage: pyenv binary save [] [--name ] # # Packs an installed version into a relocatable .tar.gz (relative paths) and # writes a metadata file listing the build platform and the system libraries @@ -10,20 +10,46 @@ # # An installed version, as listed by `pyenv versions --bare'. # Where to write the archive and metadata (default: `.'). +# --name Use as the archive and metadata base name instead of +# -. # set -e [ -n "$PYENV_DEBUG" ] && set -x # Provide pyenv completions if [ "$1" = "--complete" ]; then + echo --name exec pyenv-versions --bare fi -version="$1" -output_dir="${2:-$PWD}" +version="" +output_dir="" +package_name="" + +while [ $# -gt 0 ]; do + case "$1" in + --name ) + [ $# -ge 2 ] && [ -n "$2" ] || { echo "pyenv-binary: --name needs a value" >&2; exit 1; } + package_name="$2"; shift 2 ;; + -* ) + echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;; + * ) + if [ -z "$version" ]; then + version="$1" + elif [ -z "$output_dir" ]; then + output_dir="$1" + else + echo "pyenv-binary: unexpected argument \`$1'" >&2 + exit 1 + fi + shift ;; + esac +done + +output_dir="${output_dir:-$PWD}" if [ -z "$version" ]; then - echo "Usage: pyenv binary save []" >&2 + echo "Usage: pyenv binary save [] [--name ]" >&2 exit 1 fi @@ -36,6 +62,13 @@ case "$version" in ;; esac +case "$package_name" in +*/* | .. | . ) + echo "pyenv-binary: invalid package name \`${package_name}'" >&2 + exit 1 + ;; +esac + prefix="${PYENV_ROOT}/versions/${version}" if [ ! -d "${prefix}/bin" ]; then echo "pyenv-binary: version \`${version}' is not installed" >&2 @@ -85,8 +118,9 @@ system_deps() { } mkdir -p "$output_dir" -archive="${version}-${platform}.tar.gz" -metadata="${version}-${platform}.meta" +package_name="${package_name:-${version}-${platform}}" +archive="${package_name}.tar.gz" +metadata="${package_name}.meta" tar -C "$(dirname "$prefix")" -czf "${output_dir}/${archive}" "$(basename "$prefix")" diff --git a/plugins/pyenv-binary/test/build.bats b/plugins/pyenv-binary/test/package.bats similarity index 58% rename from plugins/pyenv-binary/test/build.bats rename to plugins/pyenv-binary/test/package.bats index a862a840..806e80da 100644 --- a/plugins/pyenv-binary/test/build.bats +++ b/plugins/pyenv-binary/test/package.bats @@ -11,79 +11,82 @@ stub_build_environment() { create_stub getconf 'echo "glibc 2.17"' } -@test "completion lists definitions provided by another plugin" { +@test "completion lists the option and definitions provided by another plugin" { mkdir -p "${PYENV_ROOT}/plugins/example/share/python-build" touch "${PYENV_ROOT}/plugins/example/share/python-build/3.12.7-example" PATH="${BATS_TEST_DIRNAME}/../../python-build/bin:${PATH}" - run pyenv-binary-build --complete + run pyenv-binary-package --complete assert_success - assert_line "--archive-url" + assert_line "--archive-base-url" assert_line "3.12.7-example" + refute_line "Available versions:" } @test "fails with no arguments" { create_stub pyenv-help "echo usage" - run pyenv-binary-build + run pyenv-binary-package assert_failure "usage" } -@test "fails without an archive url" { +@test "fails without an archive base url" { create_stub pyenv-help "echo usage" - run pyenv-binary-build 3.12.7:3.12.7-test + run pyenv-binary-package 3.12.7:3.12.7-test assert_failure "usage" } -@test "fails when --archive-url has no value" { - run pyenv-binary-build 3.12.7:3.12.7-test --archive-url - assert_failure "pyenv-binary: --archive-url needs a value" +@test "fails when --archive-base-url has no value" { + run pyenv-binary-package 3.12.7:3.12.7-test --archive-base-url + assert_failure "pyenv-binary: --archive-base-url needs a value" } @test "rejects a second positional argument" { - run pyenv-binary-build 3.12.7:3.12.7-test extra --archive-url http://x/b + run pyenv-binary-package 3.12.7:3.12.7-test extra --archive-base-url http://x/b assert_failure "pyenv-binary: unexpected argument \`extra'" } @test "rejects a bare version without an entry name" { - run pyenv-binary-build 3.12.7 --archive-url http://x/b + 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 "rejects an entry name containing a slash" { - run pyenv-binary-build "3.12.7:foo/bar" --archive-url http://x/b + run pyenv-binary-package "3.12.7:foo/bar" --archive-base-url http://x/b assert_failure "pyenv-binary: invalid entry name \`foo/bar'" } @test "rejects \`latest' as an entry name" { - run pyenv-binary-build 3.12:latest --archive-url http://x/b + run pyenv-binary-package 3.12:latest --archive-base-url http://x/b assert_failure "pyenv-binary: \`latest' cannot be used as an entry name" } -@test "refuses to build on macOS before compiling anything" { +@test "refuses to package on macOS before compiling anything" { create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac' - run pyenv-binary-build 3.12.7:3.12.7-test --archive-url http://x/b + run pyenv-binary-package 3.12.7:3.12.7-test --archive-base-url http://x/b assert_failure "pyenv-binary: macOS archives are not supported yet" } -@test "builds under the entry name and writes the archive and definition" { +@test "writes the archive, metadata and definition under the entry name" { stub_build_environment cd "${BATS_TEST_TMPDIR}" - run pyenv-binary-build 3.12.7:3.12.7-test --archive-url http://example.com/binaries + run pyenv-binary-package 3.12.7:3.12.7-test \ + --archive-base-url http://example.com/binaries assert_success assert [ -d "${PYENV_ROOT}/versions/3.12.7-test" ] - assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-test-linux-x86_64.tar.gz" ] - assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-test-linux-x86_64.meta" ] + assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-test.tar.gz" ] + assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-test.meta" ] run grep '^ARCHIVE_URL=' "${BATS_TEST_TMPDIR}/3.12.7-test" - assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test-linux-x86_64.tar.gz" + assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test.tar.gz" } -@test "strips a trailing slash from the archive url" { +@test "strips a trailing slash from the archive base url" { stub_build_environment cd "${BATS_TEST_TMPDIR}" - run pyenv-binary-build 3.12.7:3.12.7-test --archive-url http://example.com/binaries/ + run pyenv-binary-package 3.12.7:3.12.7-test \ + --archive-base-url http://example.com/binaries/ assert_success run grep '^ARCHIVE_URL=' "${BATS_TEST_TMPDIR}/3.12.7-test" - assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test-linux-x86_64.tar.gz" + assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test.tar.gz" } diff --git a/plugins/pyenv-binary/test/save.bats b/plugins/pyenv-binary/test/save.bats index 97c9f12a..03616856 100644 --- a/plugins/pyenv-binary/test/save.bats +++ b/plugins/pyenv-binary/test/save.bats @@ -12,7 +12,7 @@ platform() { @test "fails with no version given" { run pyenv-binary-save - assert_failure "Usage: pyenv binary save []" + assert_failure "Usage: pyenv binary save [] [--name ]" } @test "fails for a version that is not installed" { @@ -44,6 +44,29 @@ platform() { assert_line 0 "3.12.7/" } +@test "uses an explicit package name" { + create_version "3.12.7" + local out="${BATS_TEST_TMPDIR}/dist" + + run pyenv-binary-save "3.12.7" "$out" --name "custom" + assert_success "Saved custom.tar.gz and custom.meta to $out" + assert [ -f "${out}/custom.tar.gz" ] + run grep '^archive=' "${out}/custom.meta" + assert_success "archive=custom.tar.gz" +} + +@test "fails when --name has no value" { + run pyenv-binary-save "3.12.7" --name + assert_failure "pyenv-binary: --name needs a value" +} + +@test "rejects an invalid package name" { + create_version "3.12.7" + + run pyenv-binary-save "3.12.7" --name "foo/bar" + assert_failure "pyenv-binary: invalid package name \`foo/bar'" +} + @test "records the platform in the metadata" { create_version "3.12.7" local out="${BATS_TEST_TMPDIR}/dist"