mirror of
https://github.com/pyenv/pyenv.git
synced 2026-08-03 19:10:34 +09:00
* Relocation only implemented for Linux (requires patchelf + ldconfig -p) * Shell-completing file name is not possible with current Pyenv completion logic: `pyenv-complete` does not pass info about the word being completed * Fix `save`-produced tarball to be compatible with Python-Build fetch_tarball() * Reuse test_helper from core -- Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
190 lines
6.2 KiB
Bash
Executable File
190 lines
6.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Summary: Generate a python-build definition for a saved binary archive
|
|
#
|
|
# Usage: pyenv binary generate-installer <metadata-file> --archive-url <url> [-o <output>]
|
|
#
|
|
# Reads a metadata file written by `pyenv binary save' and emits a python-build
|
|
# definition. Dropped into python-build's definition directory, it installs the
|
|
# archive like any other version: `pyenv install' downloads it from <url>,
|
|
# checks the platform and the required system libraries, unpacks it, then
|
|
# rewrites rpaths so the copy runs from its prefix.
|
|
#
|
|
# The archive must sit next to the metadata file so its checksum can be baked
|
|
# into the definition; `pyenv binary save' writes the two together.
|
|
#
|
|
# <metadata-file> A .meta file written by `pyenv binary save'.
|
|
# The corresponsing binary package written by `pyenv binary save'
|
|
# needs to be present alongside it.
|
|
# --archive-url <url> The URL for the resulting installation script to download
|
|
# the package from.
|
|
# -o <output> Write the definition here (default: stdout).
|
|
#
|
|
set -e
|
|
[ -n "$PYENV_DEBUG" ] && set -x
|
|
|
|
# Provide pyenv completions
|
|
if [ "$1" = "--complete" ]; then
|
|
echo --archive-url
|
|
echo -o
|
|
exit
|
|
fi
|
|
|
|
metadata=""
|
|
archive_url=""
|
|
output=""
|
|
|
|
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 ;;
|
|
-o )
|
|
[ $# -ge 2 ] || { echo "pyenv-binary: -o needs a value" >&2; exit 1; }
|
|
output="$2"; shift 2 ;;
|
|
-* )
|
|
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
|
|
* )
|
|
[ -z "$metadata" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; }
|
|
metadata="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$metadata" ] || [ -z "$archive_url" ]; then
|
|
pyenv-help --usage binary-generate-installer >&2
|
|
exit 1
|
|
fi
|
|
|
|
version="" os="" arch="" distro="" libc="" archive=""
|
|
deps=""
|
|
while IFS='=' read -r key value; do
|
|
case "$key" in
|
|
version ) version="$value" ;;
|
|
os ) os="$value" ;;
|
|
arch ) arch="$value" ;;
|
|
distro ) distro="$value" ;;
|
|
libc ) libc="$value" ;;
|
|
archive ) archive="$value" ;;
|
|
dep ) deps="${deps:+$deps }$value" ;;
|
|
esac
|
|
done < "$metadata"
|
|
|
|
# Fail here if the metadata is incomplete, rather than bake a blank into the
|
|
# definition where a missing arch would give a confusing platform error.
|
|
for field in version os arch archive; do
|
|
if [ -z "${!field}" ]; then
|
|
echo "pyenv-binary: metadata is missing \`${field}'" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# macOS relocation needs install_name_tool and a different rpath scheme, so it is
|
|
# not supported yet. Other systems relocate with patchelf.
|
|
if [ "$os" = "Darwin" ]; then
|
|
echo "pyenv-binary: macOS archives are not supported yet" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The glibc floor only applies on Linux; require it there so the definition can
|
|
# tell whether the target is new enough to run the binaries.
|
|
if [ "$os" = "Linux" ] && [ -z "$libc" ]; then
|
|
echo "pyenv-binary: metadata is missing \`libc'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# python-build downloads the archive itself, so the checksum has to be baked in.
|
|
# The archive sits next to the metadata `save' wrote it alongside.
|
|
archive_path="$(dirname "$metadata")/${archive}"
|
|
if [ ! -r "$archive_path" ]; then
|
|
echo "pyenv-binary: cannot read the archive \`${archive_path}' to checksum it" >&2
|
|
exit 1
|
|
fi
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256="$(sha256sum "$archive_path")"; sha256="${sha256%% *}"
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
sha256="$(shasum -a 256 "$archive_path")"; sha256="${sha256%% *}"
|
|
elif command -v openssl >/dev/null 2>&1; then
|
|
sha256="$(openssl dgst -sha256 "$archive_path")"; sha256="${sha256##* }"
|
|
else
|
|
echo "pyenv-binary: need sha256sum, shasum or openssl to checksum the archive" >&2
|
|
exit 1
|
|
fi
|
|
|
|
emit() {
|
|
# The values known now go in as %q-quoted assignments so a stray quote or space
|
|
# in the URL or metadata cannot break the definition or inject into it. The body
|
|
# below is a quoted here-doc, verbatim.
|
|
{
|
|
printf '# python-build definition for prebuilt Python %s (%s/%s%s).\n' \
|
|
"$version" "$os" "$arch" "${distro:+, $distro}"
|
|
echo "# Generated by \`pyenv binary generate-installer'."
|
|
echo
|
|
printf 'EXPECT_OS=%q\n' "$os"
|
|
printf 'EXPECT_ARCH=%q\n' "$arch"
|
|
printf 'EXPECT_LIBC=%q\n' "$libc"
|
|
printf 'VERSION=%q\n' "$version"
|
|
printf 'ARCHIVE_URL=%q\n' "$archive_url"
|
|
printf 'SHA256=%q\n' "$sha256"
|
|
printf 'DEPS=%q\n' "$deps"
|
|
}
|
|
cat <<'EOF'
|
|
|
|
os="$(uname -s)"
|
|
arch="$(uname -m)"
|
|
if [ "$os" != "$EXPECT_OS" ] || [ "$arch" != "$EXPECT_ARCH" ]; then
|
|
echo "pyenv-binary: this archive is for ${EXPECT_OS}/${EXPECT_ARCH}, not ${os}/${arch}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Refuse a target whose glibc is older than the one the archive was built on;
|
|
# the dynamic loader would reject the binaries.
|
|
case "$EXPECT_LIBC" in
|
|
"glibc "* )
|
|
build_libc="${EXPECT_LIBC#glibc }"
|
|
target_libc="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)"
|
|
case "$target_libc" in
|
|
"glibc "* )
|
|
target_libc="${target_libc#glibc }"
|
|
older="$(printf '%s\n%s\n' "$build_libc" "$target_libc" | sort -V | head -n1)"
|
|
if [ "$older" = "$target_libc" ] && [ "$target_libc" != "$build_libc" ]; then
|
|
echo "pyenv-binary: archive needs glibc ${build_libc} or newer, but this system has ${target_libc}" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
|
|
# Required system libraries must already be present on the target. ldconfig -p
|
|
# lists each library as "<soname> (...) => <path>"; match the soname column
|
|
# exactly so a longer name like libc.so.6.1 does not satisfy libc.so.6.
|
|
if command -v ldconfig &>/dev/null && cache="$(ldconfig -p)"; then
|
|
missing=""
|
|
for dep in $DEPS; do
|
|
printf '%s\n' "$cache" | awk -v d="$dep" '$1 == d { found = 1 } END { exit !found }' \
|
|
|| missing="${missing} ${dep}"
|
|
done
|
|
if [ -n "$missing" ]; then
|
|
echo "pyenv-binary: missing required system libraries:${missing}" >&2
|
|
exit 1
|
|
fi
|
|
elif [ -n "$DEPS" ]; then
|
|
echo "pyenv-binary: cannot check required system libraries (ldconfig with -p not found)" >&2
|
|
fi
|
|
|
|
build_package_relocate() {
|
|
pyenv binary relocate "$PREFIX_PATH"
|
|
}
|
|
|
|
install_package "Python-${VERSION}-binary" "${ARCHIVE_URL}#${SHA256}" copy relocate
|
|
|
|
EOF
|
|
}
|
|
|
|
if [ -n "$output" ]; then
|
|
emit > "$output"
|
|
echo "Wrote definition to ${output}"
|
|
else
|
|
emit
|
|
fi
|