mirror of
https://github.com/pyenv/pyenv.git
synced 2026-09-10 15:46:17 -04:00
Add tests that feed realistic ldd and otool listings through save and check only the libraries resolving outside the prefix end up in the metadata, since that filtering is the fiddliest part of the command. Narrow the version guard to reject `.' and `..' rather than any name containing a dot-dot, now that a slash is already refused, and note why the find still matches *.so.* even though CPython does not produce them.
108 lines
3.4 KiB
Bash
Executable file
108 lines
3.4 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Summary: Save an installed Python version as a relocatable archive
|
|
#
|
|
# Usage: pyenv binary save <version> [<output-dir>]
|
|
#
|
|
# Packs an installed version into a relocatable .tar.gz (relative paths) and
|
|
# writes a metadata file listing the build platform and the system libraries
|
|
# it links against, so an installer can check compatibility before unpacking.
|
|
#
|
|
# <version> An installed version, as listed by `pyenv versions --bare'.
|
|
# <output-dir> Where to write the archive and metadata (default: `.').
|
|
#
|
|
set -e
|
|
[ -n "$PYENV_DEBUG" ] && set -x
|
|
|
|
# Provide pyenv completions
|
|
if [ "$1" = "--complete" ]; then
|
|
exec pyenv-versions --bare
|
|
fi
|
|
|
|
version="$1"
|
|
output_dir="${2:-$PWD}"
|
|
|
|
if [ -z "$version" ]; then
|
|
echo "Usage: pyenv binary save <version> [<output-dir>]" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# A version is a single directory name under versions/. With no slash allowed,
|
|
# the only remaining names that could point elsewhere are `.' and `..'.
|
|
case "$version" in
|
|
*/* | .. | . )
|
|
echo "pyenv-binary: invalid version name \`${version}'" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
prefix="${PYENV_ROOT}/versions/${version}"
|
|
if [ ! -d "${prefix}/bin" ]; then
|
|
echo "pyenv-binary: version \`${version}' is not installed" >&2
|
|
exit 1
|
|
fi
|
|
|
|
os="$(uname -s)"
|
|
arch="$(uname -m)"
|
|
platform="$(printf '%s' "$os" | tr '[:upper:]' '[:lower:]')-${arch}"
|
|
|
|
# Record the distro and libc version. Platform and arch alone are too coarse to
|
|
# judge compatibility: a build is only portable to a matching libc (e.g. a
|
|
# glibc 2.36 build will not load on an older glibc, nor on musl at all).
|
|
distro=""
|
|
libc=""
|
|
if [ -r /etc/os-release ]; then
|
|
distro="$( . /etc/os-release && printf '%s %s' "${ID:-}" "${VERSION_ID:-}" )"
|
|
elif [ "$os" = "Darwin" ]; then
|
|
distro="macos $(sw_vers -productVersion 2>/dev/null)"
|
|
fi
|
|
if [ "$os" = "Linux" ]; then
|
|
libc="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)"
|
|
fi
|
|
|
|
# List the external shared libraries the install links against: those that
|
|
# resolve outside its own prefix, so they must already exist on the target.
|
|
# The interpreter plus every bundled shared object are inspected.
|
|
system_deps() {
|
|
local f
|
|
{
|
|
for f in "${prefix}"/bin/python*; do
|
|
[ -e "$f" ] && printf '%s\n' "$f"
|
|
done
|
|
# CPython ships its extension modules as *.so (and *.dylib on macOS). A
|
|
# bare *.so.* is unusual for CPython itself, but the odd build carries a
|
|
# versioned copy alongside, so match it too rather than miss a dependency.
|
|
find "${prefix}" -type f \( -name '*.so' -o -name '*.so.*' -o -name '*.dylib' \)
|
|
} | sort -u | while IFS= read -r f; do
|
|
if [ "$os" = "Darwin" ]; then
|
|
otool -L "$f" 2>/dev/null | tail -n +2 | awk -v pfx="${prefix}/" \
|
|
'$1 !~ /^@/ && substr($1, 1, length(pfx)) != pfx { print $1 }'
|
|
else
|
|
ldd "$f" 2>/dev/null | awk -v pfx="${prefix}/" \
|
|
'$2 == "=>" && $3 ~ /^\// && substr($3, 1, length(pfx)) != pfx { print $1 }'
|
|
fi
|
|
done | sort -u
|
|
}
|
|
|
|
mkdir -p "$output_dir"
|
|
archive="${version}-${platform}.tar.gz"
|
|
metadata="${version}-${platform}.meta"
|
|
|
|
tar -C "$prefix" -czf "${output_dir}/${archive}" .
|
|
|
|
{
|
|
echo "# pyenv-binary metadata"
|
|
echo "version=${version}"
|
|
echo "os=${os}"
|
|
echo "arch=${arch}"
|
|
echo "platform=${platform}"
|
|
[ -n "$distro" ] && echo "distro=${distro}"
|
|
[ -n "$libc" ] && echo "libc=${libc}"
|
|
echo "archive=${archive}"
|
|
system_deps | while IFS= read -r dep; do
|
|
[ -n "$dep" ] && echo "dep=${dep}"
|
|
done
|
|
} > "${output_dir}/${metadata}"
|
|
|
|
echo "Saved ${archive} and ${metadata} to ${output_dir}"
|