Skip to content

Instantly share code, notes, and snippets.

@liyang85
Last active July 31, 2020 08:34
Show Gist options
  • Save liyang85/85a0f2d7e7539ff570e1c7391630f9c9 to your computer and use it in GitHub Desktop.
Save liyang85/85a0f2d7e7539ff570e1c7391630f9c9 to your computer and use it in GitHub Desktop.
There is a more simple method than modifying build script, which is at the bottom of this gist. pyenv's built-in `python-build` script doesn't support Tk, so we have to modify it. (According to https://github.com/pyenv/pyenv/issues/1375#issuecomment-524280004)
#!/usr/bin/env bash
#
# Usage: python-build [-kpv] <definition> <prefix>
# python-build --definitions
# python-build --version
#
# -k/--keep Do not remove source tree after installation
# -p/--patch Apply a patch from stdin before building
# -v/--verbose Verbose mode: print compilation status to stdout
# -4/--ipv4 Resolve names to IPv4 addresses only
# -6/--ipv6 Resolve names to IPv6 addresses only
# --definitions List all built-in definitions
# --version Show version of python-build
# -g/--debug Build a debug version
#
PYTHON_BUILD_VERSION="20180424"
OLDIFS="$IFS"
set -E
exec 3<&2 # preserve original stderr at fd 3
lib() {
parse_options() {
OPTIONS=()
ARGUMENTS=()
local arg option index
for arg in "$@"; do
if [ "${arg:0:1}" = "-" ]; then
if [ "${arg:1:1}" = "-" ]; then
OPTIONS[${#OPTIONS[*]}]="${arg:2}"
else
index=1
while option="${arg:$index:1}"; do
[ -n "$option" ] || break
OPTIONS[${#OPTIONS[*]}]="$option"
index=$(($index+1))
done
fi
else
ARGUMENTS[${#ARGUMENTS[*]}]="$arg"
fi
done
}
if [ "$1" == "--$FUNCNAME" ]; then
declare -f "$FUNCNAME"
echo "$FUNCNAME \"\$1\";"
exit
fi
}
lib "$1"
resolve_link() {
$(type -p greadlink readlink | head -1) "$1"
}
abs_dirname() {
local path="$1"
# Use a subshell to avoid changing the current path
(
while [ -n "$path" ]; do
cd_path="${path%/*}"
if [[ "$cd_path" != "$path" ]]; then
cd "$cd_path"
fi
name="${path##*/}"
path="$(resolve_link "$name" || true)"
done
echo "$PWD"
)
}
capitalize() {
printf "%s" "$1" | tr a-z A-Z
}
sanitize() {
printf "%s" "$1" | sed "s/[^A-Za-z0-9.-]/_/g; s/__*/_/g"
}
colorize() {
if [ -t 1 ]; then printf "\e[%sm%s\e[m" "$1" "$2"
else echo -n "$2"
fi
}
os_information() {
if type -p lsb_release >/dev/null; then
lsb_release -sir | xargs echo
elif type -p sw_vers >/dev/null; then
echo "OS X $(sw_vers -productVersion)"
elif [ -r /etc/os-release ]; then
source /etc/os-release
echo "$NAME" $VERSION_ID
else
local os="$(cat /etc/{centos,redhat,fedora,system}-release /etc/debian_version 2>/dev/null | head -1)"
echo "${os:-$(uname -sr)}"
fi
}
is_mac() {
[ "$(uname -s)" = "Darwin" ] || return 1
[ $# -eq 0 ] || [ "$(osx_version)" "$@" ]
}
# 9.1 -> 901
# 10.9 -> 1009
# 10.10 -> 1010
osx_version() {
local -a ver
IFS=. ver=( `sw_vers -productVersion` )
IFS="$OLDIFS"
echo $(( ${ver[0]}*100 + ${ver[1]} ))
}
build_failed() {
{ echo
colorize 1 "BUILD FAILED"
echo " ($(os_information) using $(version))"
echo
if ! rmdir "${BUILD_PATH}" 2>/dev/null; then
echo "Inspect or clean up the working tree at ${BUILD_PATH}"
if file_is_not_empty "$LOG_PATH"; then
colorize 33 "Results logged to ${LOG_PATH}"
printf "\n\n"
echo "Last 10 log lines:"
tail -n 10 "$LOG_PATH"
fi
fi
} >&3
exit 1
}
file_is_not_empty() {
local filename="$1"
local line_count="$(wc -l "$filename" 2>/dev/null || true)"
if [ -n "$line_count" ]; then
words=( $line_count )
[ "${words[0]}" -gt 0 ]
else
return 1
fi
}
num_cpu_cores() {
local num
case "$(uname -s)" in
Darwin | *BSD )
num="$(sysctl -n hw.ncpu 2>/dev/null || true)"
;;
SunOS )
num="$(getconf NPROCESSORS_ONLN 2>/dev/null || true)"
;;
* )
num="$({ getconf _NPROCESSORS_ONLN ||
grep -c ^processor /proc/cpuinfo; } 2>/dev/null)"
num="${num#0}"
;;
esac
echo "${num:-2}"
}
install_package() {
install_package_using "tarball" 1 "$@"
}
install_nightly_package() {
install_package_using "nightly_tarball" 2 "$@"
}
install_git() {
install_package_using "git" 2 "$@"
}
install_hg() {
install_package_using "hg" 2 "$@"
}
install_svn() {
install_package_using "svn" 2 "$@"
}
install_jar() {
install_package_using "jar" 1 "$@"
}
install_zip() {
install_package_using "zip" 1 "$@"
}
install_script() {
install_package_using "script" 1 "$@"
}
install_package_using() {
local package_type="$1"
local package_type_nargs="$2"
local package_name="$3"
shift 3
local fetch_args=( "$package_name" "${@:1:$package_type_nargs}" )
local make_args=( "$package_name" )
local arg last_arg
for arg in "${@:$(( $package_type_nargs + 1 ))}"; do
if [ "$last_arg" = "--if" ]; then
"$arg" || return 0
elif [ "$arg" != "--if" ]; then
make_args["${#make_args[@]}"]="$arg"
fi
last_arg="$arg"
done
pushd "$BUILD_PATH" >&4
"fetch_${package_type}" "${fetch_args[@]}"
make_package "${make_args[@]}"
popd >&4
{ echo "Installed ${package_name} to ${PREFIX_PATH}"
echo
} >&2
}
make_package() {
local package_name="$1"
shift
pushd "$package_name" >&4
setup_builtin_patches "$package_name"
before_install_package "$package_name"
build_package "$package_name" $*
after_install_package "$package_name"
cleanup_builtin_patches "$package_name"
fix_directory_permissions
popd >&4
}
compute_sha2() {
local output
if type shasum &>/dev/null; then
output="$(shasum -a 256 -b)" || return 1
echo "${output% *}"
elif type openssl &>/dev/null; then
local openssl="$(command -v "$(brew --prefix openssl 2>/dev/null || true)"/bin/openssl openssl | head -1)"
output="$("$openssl" dgst -sha256 2>/dev/null)" || return 1
echo "${output##* }"
elif type sha256sum &>/dev/null; then
output="$(sha256sum -b)" || return 1
echo "${output%% *}"
else
return 1
fi
}
compute_md5() {
local output
if type md5 &>/dev/null; then
md5 -q
elif type openssl &>/dev/null; then
output="$(openssl md5)" || return 1
echo "${output##* }"
elif type md5sum &>/dev/null; then
output="$(md5sum -b)" || return 1
echo "${output%% *}"
else
return 1
fi
}
has_checksum_support() {
local checksum_command="$1"
local has_checksum_var="HAS_CHECKSUM_SUPPORT_${checksum_command}"
if [ -z "${!has_checksum_var+defined}" ]; then
printf -v "$has_checksum_var" "$(echo test | "$checksum_command" >/dev/null; echo $?)"
fi
return "${!has_checksum_var}"
}
verify_checksum() {
local checksum_command
local filename="$1"
local expected_checksum="$(echo "$2" | tr [A-Z] [a-z])"
# If the specified filename doesn't exist, return success
[ -e "$filename" ] || return 0
case "${#expected_checksum}" in
0) return 0 ;; # empty checksum; return success
32) checksum_command="compute_md5" ;;
64) checksum_command="compute_sha2" ;;
*)
{ echo
echo "unexpected checksum length: ${#expected_checksum} (${expected_checksum})"
echo "expected 0 (no checksum), 32 (MD5), or 64 (SHA2-256)"
echo
} >&4
return 1 ;;
esac
# If chosen provided checksum algorithm isn't supported, return success
has_checksum_support "$checksum_command" || return 0
# If the computed checksum is empty, return failure
local computed_checksum=`echo "$($checksum_command < "$filename")" | tr [A-Z] [a-z]`
[ -n "$computed_checksum" ] || return 1
if [ "$expected_checksum" != "$computed_checksum" ]; then
{ echo
echo "checksum mismatch: ${filename} (file is corrupt)"
echo "expected $expected_checksum, got $computed_checksum"
echo
} >&4
return 1
fi
}
http() {
local method="$1"
[ -n "$2" ] || return 1
shift 1
PYTHON_BUILD_HTTP_CLIENT="${PYTHON_BUILD_HTTP_CLIENT:-$(detect_http_client)}"
[ -n "$PYTHON_BUILD_HTTP_CLIENT" ] || return 1
"http_${method}_${PYTHON_BUILD_HTTP_CLIENT}" "$@"
}
detect_http_client() {
local client
for client in aria2c curl wget; do
if type "$client" &>/dev/null; then
echo "$client"
return
fi
done
echo "error: please install \`aria2c\`, \`curl\`, or \`wget\` and try again" >&2
return 1
}
http_head_aria2c() {
aria2c --dry-run --no-conf=true ${ARIA2_OPTS} "$1" >&4 2>&1
}
http_get_aria2c() {
local out="${2:-$(mktemp "out.XXXXXX")}"
if aria2c --allow-overwrite=true --no-conf=true -o "${out}" ${ARIA2_OPTS} "$1" >&4; then
[ -n "$2" ] || cat "${out}"
else
false
fi
}
http_head_curl() {
curl -qsILf ${CURL_OPTS} "$1" >&4 2>&1
}
http_get_curl() {
curl -q -o "${2:--}" -sSLf ${CURL_OPTS} "$1"
}
http_head_wget() {
wget -q --spider ${WGET_OPTS} "$1" >&4 2>&1
}
http_get_wget() {
wget -nv ${WGET_OPTS} -O "${2:--}" "$1"
}
fetch_tarball() {
local package_name="$1"
local package_url="$2"
local mirror_url
local checksum
local extracted_dir
if [ "$package_url" != "${package_url/\#}" ]; then
checksum="${package_url#*#}"
package_url="${package_url%%#*}"
if [ -n "$PYTHON_BUILD_MIRROR_URL" ]; then
if [[ -z "$PYTHON_BUILD_DEFAULT_MIRROR" || $package_url != */www.python.org/* ]]; then
mirror_url="${PYTHON_BUILD_MIRROR_URL}/$checksum"
fi
fi
fi
local tar_args="xzf"
local package_filename="${package_name}.tar.gz"
if [ "$package_url" != "${package_url%bz2}" ]; then
if ! type -p bzip2 >/dev/null; then
echo "warning: bzip2 not found; consider installing \`bzip2\` package" >&4
fi
package_filename="${package_filename%.gz}.bz2"
tar_args="${tar_args/z/j}"
fi
if [ "$package_url" != "${package_url%xz}" ]; then
if ! type -p xz >/dev/null; then
echo "warning: xz not found; consider installing \`xz\` package" >&4
fi
package_filename="${package_filename%.gz}.xz"
tar_args="${tar_args/z/J}"
fi
if ! reuse_existing_tarball "$package_filename" "$checksum"; then
local tarball_filename="$(basename "$package_url")"
echo "Downloading ${tarball_filename}..." >&2
http head "$mirror_url" &&
download_tarball "$mirror_url" "$package_filename" "$checksum" ||
download_tarball "$package_url" "$package_filename" "$checksum"
fi
{ if tar $tar_args "$package_filename"; then
if [ ! -d "$package_name" ]; then
extracted_dir="$(find_extracted_directory)"
mv "$extracted_dir" "$package_name"
fi
if [ -z "$KEEP_BUILD_PATH" ]; then
rm -f "$package_filename"
else
true
fi
fi
} >&4 2>&1
}
find_extracted_directory() {
for f in *; do
if [ -d "$f" ]; then
echo "$f"
return
fi
done
echo "Extracted directory not found" >&2
return 1
}
fetch_nightly_tarball() {
local package_name="$1"
local package_url="$2"
local package_pattern="$3"
fetch_tarball "$1" "$2"
if [ ! -e "${package_name}" ]; then
local nightly_package_name="$(echo ${package_pattern})"
if [ -e "${nightly_package_name}" ]; then
ln -fs "${nightly_package_name}" "${package_name}"
fi
fi
}
reuse_existing_tarball() {
local package_filename="$1"
local checksum="$2"
# Reuse existing file in build location
if [ -e "$package_filename" ] && verify_checksum "$package_filename" "$checksum"; then
return 0
fi
# Reuse previously downloaded file in cache location
[ -n "$PYTHON_BUILD_CACHE_PATH" ] || return 1
local cached_package_filename="${PYTHON_BUILD_CACHE_PATH}/$package_filename"
[ -e "$cached_package_filename" ] || return 1
verify_checksum "$cached_package_filename" "$checksum" >&4 2>&1 || return 1
ln -s "$cached_package_filename" "$package_filename" >&4 2>&1 || return 1
}
download_tarball() {
local package_url="$1"
[ -n "$package_url" ] || return 1
local package_filename="$2"
local checksum="$3"
echo "-> $package_url" >&2
if http get "$package_url" "$package_filename" >&4 2>&1; then
verify_checksum "$package_filename" "$checksum" >&4 2>&1 || return 1
else
echo "error: failed to download $package_filename" >&2
return 1
fi
if [ -n "$PYTHON_BUILD_CACHE_PATH" ]; then
local cached_package_filename="${PYTHON_BUILD_CACHE_PATH}/$package_filename"
{ mv "$package_filename" "$cached_package_filename"
ln -s "$cached_package_filename" "$package_filename"
} >&4 2>&1 || return 1
fi
}
has_tar_xz_support() {
tar Jc /dev/null 1>/dev/null 2>&1
}
fetch_git() {
local package_name="$1"
local git_url="$2"
local git_ref="$3"
echo "Cloning ${git_url}..." >&2
if type git &>/dev/null; then
if [ -n "$PYTHON_BUILD_CACHE_PATH" ]; then
pushd "$PYTHON_BUILD_CACHE_PATH" >&4
local clone_name="$(sanitize "$git_url")"
if [ -e "${clone_name}" ]; then
{ cd "${clone_name}"
git fetch --force "$git_url" "+${git_ref}:${git_ref}"
} >&4 2>&1
else
git clone --bare --branch "$git_ref" "$git_url" "${clone_name}" >&4 2>&1
fi
git_url="$PYTHON_BUILD_CACHE_PATH/${clone_name}"
popd >&4
fi
if [ -e "${package_name}" ]; then
( cd "${package_name}"
git fetch --depth 1 origin "+${git_ref}"
git checkout -q -B "$git_ref" "origin/${git_ref}"
) >&4 2>&1
else
git clone --depth 1 --branch "$git_ref" "$git_url" "${package_name}" >&4 2>&1
fi
else
echo "error: please install \`git\` and try again" >&2
exit 1
fi
}
fetch_hg() {
local package_name="$1"
local hg_url="$2"
local hg_ref="$3"
echo "Cloning ${hg_url}..." >&2
if type hg &>/dev/null; then
if [ -n "$PYTHON_BUILD_CACHE_PATH" ]; then
pushd "$PYTHON_BUILD_CACHE_PATH" >&4
local clone_name="$(sanitize "$hg_url")"
if [ -e "${clone_name}" ]; then
{ cd "${clone_name}"
hg pull --force "$hg_url"
} >&4 2>&1
else
{ hg clone --branch "$hg_ref" "$hg_url" "${clone_name}"
cd "${clone_name}"
hg update null
} >&4 2>&1
fi
hg_url="$PYTHON_BUILD_CACHE_PATH/${clone_name}"
popd >&4
fi
hg clone --branch "$hg_ref" "$hg_url" "${package_name}" >&4 2>&1
else
echo "error: please install \`mercurial\` and try again" >&2
exit 1
fi
}
fetch_svn() {
local package_name="$1"
local svn_url="$2"
local svn_rev="$3"
echo "Checking out ${svn_url}..." >&2
if type svn &>/dev/null; then
svn co -r "$svn_rev" "$svn_url" "${package_name}" >&4 2>&1
elif type svnlite &>/dev/null; then
svnlite co -r "$svn_rev" "$svn_url" "${package_name}" >&4 2>&1
else
echo "error: please install Subversion and try again" >&2
exit 1
fi
}
fetch_jar() {
local package_name="$1"
local package_url="$2"
local mirror_url
local checksum
if [ "$package_url" != "${package_url/\#}" ]; then
checksum="${package_url#*#}"
package_url="${package_url%%#*}"
if [ -n "$PYTHON_BUILD_MIRROR_URL" ]; then
mirror_url="${PYTHON_BUILD_MIRROR_URL}/$checksum"
fi
fi
local package_filename="${package_name}.jar"
if ! reuse_existing_tarball "$package_filename" "$checksum"; then
echo "Downloading ${package_filename}..." >&2
http head "$mirror_url" &&
download_tarball "$mirror_url" "$package_filename" "$checksum" ||
download_tarball "$package_url" "$package_filename" "$checksum"
fi
# Must use full path to jar and destination directory:
# http://bugs.jython.org/issue2350
{ if $JAVA -jar "$PWD/${package_name}.jar" -s -d "$PWD/${package_name}"; then
if [ -z "$KEEP_BUILD_PATH" ]; then
rm -f "$package_filename"
else
true
fi
fi
} >&4 2>&1
}
fetch_zip() {
local package_name="$1"
local package_url="$2"
local mirror_url
local checksum
if [ "$package_url" != "${package_url/\#}" ]; then
checksum="${package_url#*#}"
package_url="${package_url%%#*}"
if [ -n "$PYTHON_BUILD_MIRROR_URL" ]; then
mirror_url="${PYTHON_BUILD_MIRROR_URL}/$checksum"
fi
fi
local package_filename="${package_name}.zip"
if ! reuse_existing_tarball "$package_filename" "$checksum"; then
echo "Downloading ${package_filename}..." >&2
http head "$mirror_url" &&
download_tarball "$mirror_url" "$package_filename" "$checksum" ||
download_tarball "$package_url" "$package_filename" "$checksum"
fi
{ if unzip "$package_filename"; then
if [ -z "$KEEP_BUILD_PATH" ]; then
rm -f "$package_filename"
else
true
fi
fi
} >&4 2>&1
}
fetch_script() {
local package_name="$1"
local package_url="$2"
local mirror_url
local checksum
if [ "$package_url" != "${package_url/\#}" ]; then
checksum="${package_url#*#}"
package_url="${package_url%%#*}"
if [ -n "$PYTHON_BUILD_MIRROR_URL" ]; then
mirror_url="${PYTHON_BUILD_MIRROR_URL}/$checksum"
fi
fi
local package_filename="${package_name}.sh" # TODO: extract suffix from ${package_url}
if ! reuse_existing_tarball "$package_filename" "$checksum"; then
echo "Downloading ${package_filename}..." >&2
http head "$mirror_url" &&
download_tarball "$mirror_url" "$package_filename" "$checksum" ||
download_tarball "$package_url" "$package_filename" "$checksum"
fi
mkdir -p "$(dirname "${package_name}/${package_filename}")"
mv -f "${package_filename}" "${package_name}/${package_filename}"
}
build_package() {
local package_name="$1"
shift
if [ "$#" -eq 0 ]; then
local commands="standard"
else
local commands="$*"
fi
echo "Installing ${package_name}..." >&2
[ -n "$HAS_PATCH" ] && apply_python_patch "$package_name"
for command in $commands; do
"build_package_${command}" "$package_name"
done
}
package_option() {
local package_name="$1"
local command_name="$2"
local variable="$(capitalize "${package_name}_${command_name}")_OPTS_ARRAY"
local array="$variable[@]"
shift 2
local value=( "${!array}" "$@" )
eval "$variable=( \"\${value[@]}\" )"
}
build_package_warn_eol() {
local package_name="$1"
{ echo
echo "WARNING: $package_name is past its end of life and is now unsupported."
echo "It no longer receives bug fixes or critical security updates."
echo
} >&3
}
build_package_warn_unsupported() {
local package_name="$1"
{ echo
echo "WARNING: $package_name is nearing its end of life."
echo "It only receives critical security updates, no bug fixes."
echo
} >&3
}
build_package_standard_build() {
local package_name="$1"
if [ "${MAKEOPTS+defined}" ]; then
MAKE_OPTS="$MAKEOPTS"
elif [ -z "${MAKE_OPTS+defined}" ]; then
MAKE_OPTS="-j $(num_cpu_cores)"
fi
# Support YAML_CONFIGURE_OPTS, PYTHON_CONFIGURE_OPTS, etc.
local package_var_name="$(capitalize "${package_name%%-*}")"
local PACKAGE_CONFIGURE="${package_var_name}_CONFIGURE"
local PACKAGE_PREFIX_PATH="${package_var_name}_PREFIX_PATH"
local PACKAGE_CONFIGURE_OPTS="${package_var_name}_CONFIGURE_OPTS"
local PACKAGE_CONFIGURE_OPTS_ARRAY="${package_var_name}_CONFIGURE_OPTS_ARRAY[@]"
local PACKAGE_MAKE_OPTS="${package_var_name}_MAKE_OPTS"
local PACKAGE_MAKE_OPTS_ARRAY="${package_var_name}_MAKE_OPTS_ARRAY[@]"
local PACKAGE_CFLAGS="${package_var_name}_CFLAGS"
if [ "$package_var_name" = "PYTHON" ]; then
use_homebrew_readline || use_freebsd_pkg || true
if is_mac -ge 1014; then
use_xcode_sdk_zlib || use_homebrew_zlib || true
fi
fi
( if [ "${CFLAGS+defined}" ] || [ "${!PACKAGE_CFLAGS+defined}" ]; then
export CFLAGS="$CFLAGS ${!PACKAGE_CFLAGS}"
fi
if [ -z "$CC" ] && is_mac -ge 1010; then
export CC=clang
fi
# ${!PACKAGE_CONFIGURE:-./configure} --prefix="${!PACKAGE_PREFIX_PATH:-$PREFIX_PATH}" \
# $CONFIGURE_OPTS ${!PACKAGE_CONFIGURE_OPTS} "${!PACKAGE_CONFIGURE_OPTS_ARRAY}" || return 1
${!PACKAGE_CONFIGURE:-./configure} --prefix="${!PACKAGE_PREFIX_PATH:-$PREFIX_PATH}" \
$CONFIGURE_OPTS --with-tcltk-includes='-I/usr/local/opt/tcl-tk/include' \
--with-tcltk-libs='-L/usr/local/opt/tcl-tk/lib -ltcl8.6 -ltk8.6' \
${!PACKAGE_CONFIGURE_OPTS} "${!PACKAGE_CONFIGURE_OPTS_ARRAY}" || return 1
) >&4 2>&1
{ "$MAKE" $MAKE_OPTS ${!PACKAGE_MAKE_OPTS} "${!PACKAGE_MAKE_OPTS_ARRAY}"
} >&4 2>&1
}
build_package_standard_install() {
local package_name="$1"
local package_var_name="$(capitalize "${package_name%%-*}")"
local PACKAGE_MAKE_INSTALL_OPTS="${package_var_name}_MAKE_INSTALL_OPTS"
local PACKAGE_MAKE_INSTALL_OPTS_ARRAY="${package_var_name}_MAKE_INSTALL_OPTS_ARRAY[@]"
local PACKAGE_MAKE_INSTALL_TARGET="${package_var_name}_MAKE_INSTALL_TARGET"
{ "$MAKE" "${!PACKAGE_MAKE_INSTALL_TARGET:-install}" $MAKE_INSTALL_OPTS ${!PACKAGE_MAKE_INSTALL_OPTS} "${!PACKAGE_MAKE_INSTALL_OPTS_ARRAY}"
} >&4 2>&1
}
# Backward Compatibility for standard function
build_package_standard() {
build_package_standard_build "$@"
build_package_standard_install "$@"
}
build_package_autoconf() {
{ autoconf
} >&4 2>&1
}
build_package_python() {
local package_name="$1"
{ "$PYTHON_BIN" setup.py install
} >&4 2>&1
}
remove_windows_files() {
cd "$PREFIX_PATH"
rm -f bin/*.exe bin/*.dll bin/*.bat
}
build_package_jython() {
build_package_copy
{ if [ -x "${PREFIX_PATH}/bin/jython" ] && [ ! -x "${PREFIX_PATH}/bin/python" ]; then
( cd "${PREFIX_PATH}/bin" && ln -fs jython python )
fi
} >&4 2>&1
fix_jython_shebangs
}
fix_jython_shebangs() {
# Workaround for Jython 2.7+ (#458)
for file in "${PREFIX_PATH}/bin"/*; do
case "$(head -1 "${file}")" in
"#!"*"/bin/jython" )
sed -i.bak "1 s:.*:#\!${PREFIX_PATH}\/bin\/jython:" "${file}"
;;
"#!"*"/bin/python2.7"* )
sed -i.bak "1 s:.*:#\!\/usr\/bin\/env python:" "${file}"
;;
esac
rm -f "${file}.bak"
done
}
build_package_jython_builder() {
ant >&4 2>&1
( cd "dist" && build_package_jython )
}
build_package_pyston() {
mkdir -p "${PREFIX_PATH}/"
cp -fR . "${PREFIX_PATH}/"
chmod +x "${PREFIX_PATH}/"pyston
# FIXME ugly hack below adapted from
# https://github.com/dropbox/pyston/blob/master/docker/pyston/Dockerfile
( cd "${PREFIX_PATH}/" && ./pyston virtualenv/virtualenv.py . )
}
build_package_ironpython() {
mkdir -p "${PREFIX_PATH}/bin"
cp -fR . "${PREFIX_PATH}/bin"
chmod +x "${PREFIX_PATH}/bin/"*.exe
( cd "${PREFIX_PATH}/bin" && ln -fs ipy.exe python )
}
build_package_ironpython_builder() {
xbuild Build.proj /t:Stage "/p:Mono=true;BaseConfiguration=Release" >&4 2>&1
( cd "Stage/Release/IronPython-"* && build_package_ironpython )
}
build_package_micropython() {
if [ "${MAKEOPTS+defined}" ]; then
MAKE_OPTS="$MAKEOPTS"
elif [ -z "${MAKE_OPTS+defined}" ]; then
MAKE_OPTS="-j $(num_cpu_cores)"
fi
{ cd mpy-cross
"$MAKE" $MAKE_OPTS
cd ../ports/unix
"$MAKE" $MAKE_OPTS axtls
"$MAKE" $MAKE_OPTS CFLAGS_EXTRA="-DMICROPY_PY_SYS_PATH_DEFAULT='\"${PREFIX_PATH}/lib/micropython\"'"
"$MAKE" install $MAKE_INSTALL_OPTS PREFIX="${PREFIX_PATH}"
ln -fs micropython "${PREFIX_PATH}/bin/python"
mkdir -p "${PREFIX_PATH}/lib/micropython"
}>&4 2>&1
}
pypy_architecture() {
case "$(uname -s)" in
"Darwin" ) echo "osx64" ;;
"Linux" )
case "$(uname -m)" in
"armel" ) echo "linux-armel" ;;
"armhf" | "armv6l" | "armv7l" ) echo "linux-armhf" ;;
"i386" | "i486" | "i586" | "i686" | "i786" ) echo "linux" ;;
"ppc64" ) echo "linux-ppc64" ;;
"ppc64le" ) echo "linux-ppc64le" ;;
"x86_64" ) echo "linux64" ;;
"aarch64" ) echo "linux-aarch64" ;;
* ) return 1 ;;
esac
;;
"CYGWIN"* | "MINGW"* ) echo "win32" ;;
"FreeBSD" )
case "$(uname -m)" in
"x86_64" ) echo "freebsd64" ;;
* ) return 1 ;;
esac
;;
* ) return 1 ;;
esac
}
pyston_architecture() {
pypy_architecture
}
build_package_pypy() {
build_package_copy
mkdir -p "${PREFIX_PATH}/bin" "${PREFIX_PATH}/lib"
local bin
shopt -s nullglob
for bin in "bin/"*; do
if [ -f "${bin}" ] && [ -x "${bin}" ] && [ ! -L "${bin}" ]; then
case "${bin##*/}" in
"libpypy"* )
( cd "${PREFIX_PATH}/lib" && ln -fs "../bin/${bin##*/}" "${bin##*/}" )
;;
"pypy"* )
( cd "${PREFIX_PATH}/bin" && ln -fs "${bin##*/}" "python" )
;;
esac
fi
done
shopt -u nullglob
}
build_package_pypy_builder() {
if [ -f "rpython/bin/rpython" ]; then # pypy 2.x
if [ -z "${PYPY_OPTS}" ]; then
local PYPY_OPTS="--opt=jit --batch --make-jobs=$(num_cpu_cores)"
fi
python "rpython/bin/rpython" ${PYPY_OPTS} "pypy/goal/targetpypystandalone.py" >&4 2>&1
elif [ -f "pypy/translator/goal/translate.py" ]; then # pypy 1.x
if [ -z "${PYPY_OPTS}" ]; then
local PYPY_OPTS="--opt=jit"
fi
( cd "pypy/translator/goal" && python "translate.py" ${PYPY_OPTS} "targetpypystandalone.py" ) 1>&4 2>&1
else
echo "not a pypy source tree" 1>&3
return 1
fi
{ mkdir -p "bin" "lib"
local pypy
for pypy in "pypy"*; do
if [ -f "${pypy}" ] && [ -x "${pypy}" ] && [ ! -L "${pypy}" ]; then
mv -f "${pypy}" "bin/${pypy##*/}"
fi
done
local libpypy
for libpypy in "libpypy"*; do
if [ -f "${libpypy}" ] && [ -x "${libpypy}" ] && [ ! -L "${libpypy}" ]; then
mv -f "${libpypy}" "bin/${libpypy##*/}"
fi
done
} >&4 2>&1
build_package_pypy
}
activepython_architecture() {
case "$(uname -s)" in
"Darwin" ) echo "macosx10.9-i386-x86_64" ;;
"Linux" )
case "$(uname -m)" in
"i368" | "i486" | "i586" | "i686" | "i786" ) echo "linux-x86" ;;
"x86_64" ) echo "linux-x86_64" ;;
* ) return 1 ;;
esac
;;
* ) return 1 ;;
esac
}
build_package_activepython() {
local package_name="$1"
{ bash "install.sh" --install-dir "${PREFIX_PATH}"
} >&4 2>&1
}
anaconda_architecture() {
case "$(uname -s)" in
"Darwin" ) echo "MacOSX-x86_64" ;;
"Linux" )
case "$(uname -m)" in
"armv7l" ) echo "Linux-armv7l" ;;
"i386" | "i486" | "i586" | "i686" | "i786" ) echo "Linux-x86" ;;
"ppc64le" ) echo "Linux-ppc64le" ;;
"x86_64" ) echo "Linux-x86_64" ;;
* ) return 1 ;;
esac
;;
* ) return 1 ;;
esac
}
build_package_anaconda() {
local package_name="$1"
{ bash "${package_name}.sh" -f -b -p "${PREFIX_PATH}"
} >&4 2>&1
}
build_package_miniconda() {
build_package_anaconda "$@"
"${PREFIX_PATH}/bin/conda" install --yes "pip"
}
build_package_copy() {
mkdir -p "$PREFIX_PATH"
cp -fR . "$PREFIX_PATH"
}
before_install_package() {
local stub=1
}
after_install_package() {
local stub=1
}
setup_builtin_patches() {
local package_name="$1"
local package_patch_path="${DEFINITION_PATH%/*}/patches/${DEFINITION_PATH##*/}/${package_name}"
ORIG_HAS_PATCH="$HAS_PATCH"
# Apply built-in patches if patch was not given from stdin
if [ -z "$HAS_PATCH" ] && [ -d "${package_patch_path}" ]; then
{ find "${package_patch_path}" -maxdepth 1 -type f
} 2>/dev/null | sort | xargs cat 1>"${package_name}.patch"
exec <&-
exec <"${package_name}.patch"
HAS_PATCH=true
fi
}
cleanup_builtin_patches() {
local package_name="$1"
rm -f "${package_name}.patch"
HAS_PATCH="$ORIG_HAS_PATCH"
}
fix_directory_permissions() {
# Ensure installed directories are not world-writable
find "$PREFIX_PATH" -type d \( -perm -020 -o -perm -002 \) -exec chmod go-w {} \;
}
require_java7() {
local version="$(java -version 2>&1 | grep '\(java\|openjdk\) version' | head -1)"
if [[ $version != *[789]* ]]; then
colorize 1 "ERROR" >&3
echo ": Java 7 required. Please install a 1.7-compatible JRE." >&3
return 1
fi
}
require_gcc() {
local gcc="$(locate_gcc || true)"
if [ -z "$gcc" ]; then
{ echo
colorize 1 "ERROR"
echo ": This package must be compiled with GCC, but python-build couldn't"
echo "find a suitable \`gcc\` executable on your system. Please install GCC"
echo "and try again."
echo
if is_mac; then
colorize 1 "DETAILS"
echo ": Apple no longer includes the official GCC compiler with Xcode"
echo "as of version 4.2. Instead, the \`gcc\` executable is a symlink to"
echo "\`llvm-gcc\`, a modified version of GCC which outputs LLVM bytecode."
echo
echo "For most programs the \`llvm-gcc\` compiler works fine. However,"
echo "versions of CPython newer than 3.3.0 are incompatible with"
echo "\`llvm-gcc\`. To build newer versions of CPython you must have the official"
echo "GCC compiler installed on your system."
echo
colorize 1 "TO FIX THE PROBLEM"
if type brew &>/dev/null; then
echo ": Install Homebrew's GCC package with this"
echo -n "command: "
colorize 4 "brew install gcc@4.9"
else
echo ": Install the official GCC compiler using these"
echo -n "packages: "
colorize 4 "https://github.com/kennethreitz/osx-gcc-installer/downloads"
fi
echo
echo
echo "You will need to install the official GCC compiler to build newer"
echo "versions of CPython even if you have installed Apple's Command Line Tools"
echo "for Xcode package. The Command Line Tools for Xcode package only"
echo "includes \`llvm-gcc\`."
fi
} >&3
return 1
fi
export CC="$gcc"
if is_mac -ge 1010; then
export MACOSX_DEPLOYMENT_TARGET=10.9
fi
}
locate_gcc() {
local gcc gccs
IFS=: gccs=($(gccs_in_path))
IFS="$OLDIFS"
verify_gcc "$CC" ||
verify_gcc "$(command -v gcc || true)" || {
for gcc in "${gccs[@]}"; do
verify_gcc "$gcc" && break || true
done
}
return 1
}
gccs_in_path() {
local gcc path paths
local gccs=()
IFS=: paths=($PATH)
IFS="$OLDIFS"
shopt -s nullglob
for path in "${paths[@]}"; do
for gcc in "$path"/gcc-*; do
gccs["${#gccs[@]}"]="$gcc"
done
done
shopt -u nullglob
printf :%s "${gccs[@]}"
}
verify_gcc() {
local gcc="$1"
if [ -z "$gcc" ]; then
return 1
fi
local version="$("$gcc" --version 2>/dev/null || true)"
if [ -z "$version" ]; then
return 1
fi
if echo "$version" | grep LLVM >/dev/null; then
return 1
fi
echo "$gcc"
}
require_llvm() {
local llvm_version="$1"
if is_mac -ge 1010; then
if [[ "$PYTHON_CONFIGURE_OPTS" != *--llvm-* ]]; then
case "$llvm_version" in
3.2 )
package_option python configure --prebuilt-name="llvm-3.2-x86_64-apple-darwin13.tar.bz2"
;;
3.[56] )
local llvm_config="$(locate_llvm "$llvm_version")"
if [ -n "$llvm_config" ]; then
package_option python configure --llvm-config="$llvm_config"
else
local homebrew_package="llvm@$llvm_version"
{ echo
colorize 1 "ERROR"
echo ": Rubinius will not be able to compile using Apple's LLVM-based "
echo "build tools on OS X. You will need to install LLVM $llvm_version first."
echo
colorize 1 "TO FIX THE PROBLEM"
echo ": Install Homebrew's llvm package with this"
echo -n "command: "
colorize 4 "brew install $homebrew_package"
echo
} >&3
return 1
fi
;;
esac
fi
fi
}
locate_llvm() {
local llvm_version="$1"
local package llvm_config
shopt -s nullglob
for package in `brew list 2>/dev/null | grep "^llvm"`; do
llvm_config="$(echo "$(brew --prefix "$package")/bin/llvm-config"*)"
if [ -n "$llvm_config" ] && [[ "$("$llvm_config" --version)" = "$llvm_version"* ]]; then
echo "$llvm_config"
break
fi
done
shopt -u nullglob
}
require_java() {
local java="$(command -v java || true)"
if [ -z "$java" ]; then
{ echo
colorize 1 "ERROR"
echo ": This package must be installed with java, but python-build couldn't"
echo "find a suitable \`java\` executable on your system. Please install Java"
echo "and try again."
echo
} >&3
return 1
fi
export JAVA="$java"
}
# Let Jython installer to generate shell script instead of python script even if there's `python2.7` available in `$PATH` (#800)
# FIXME: better function naming
unrequire_python27() {
export PATH="${BUILD_PATH}/bin:${PATH}"
mkdir -p "${BUILD_PATH}/bin"
if command -v python2.7 1>/dev/null 2>&1; then
echo false > "${BUILD_PATH}/bin/python2.7"
chmod +x "${BUILD_PATH}/bin/python2.7"
fi
}
require_distro() {
for arg; do
if [[ "$(cat /etc/issue 2>/dev/null || true)" == "$arg"* ]]; then
return 0
fi
done
{ echo
colorize 1 "WARNING"
echo ": This binary distribution is built for the following distro(s): $@."
echo "installed binary may not run expectedly on other platforms."
echo
} >&2
return 1
}
require_osx_version() {
function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }
local required_version="$@"
local osx_version="$(sw_vers -productVersion)"
if [[ $(version $osx_version) -ge $(version $required_version) ]]; then
return 0
fi
return 1
}
configured_with_package_dir() {
local package_var_name="$(capitalize "$1")"
shift 1
local PACKAGE_CONFIGURE_OPTS="${package_var_name}_CONFIGURE_OPTS"
local PACKAGE_CONFIGURE_OPTS_ARRAY="${package_var_name}_MAKE_OPTS_ARRAY[@]"
local arg flag
for arg in ${CONFIGURE_OPTS} ${!PACKAGE_CONFIGURE_OPTS} "${!PACKAGE_CONFIGURE_OPTS_ARRAY}"; do
if [[ "$arg" == "CPPFLAGS="* ]]; then
for flag in ${CPPFLAGS} ${arg##CPPFLAGS=}; do
if [[ "$flag" == "-I"* ]]; then
local header
for header in "$@"; do
if [ -e "${flag##-I}/${header#/}" ]; then
return 0
fi
done
fi
done
fi
done
return 1
}
needs_yaml() {
! configured_with_package_dir "python" "yaml.h" &&
! use_homebrew_yaml
}
use_homebrew_yaml() {
local libdir="$(brew --prefix libyaml 2>/dev/null || true)"
if [ -d "$libdir" ]; then
echo "python-build: use libyaml from homebrew"
export CPPFLAGS="-I$libdir/include ${CPPFLAGS}"
export LDFLAGS="-L$libdir/lib ${LDFLAGS}"
else
return 1
fi
}
use_freebsd_pkg() {
# check if FreeBSD
if [ "FreeBSD" = "$(uname -s)" ]; then
# use openssl if installed from Ports Collection
if [ -f /usr/local/include/openssl/ssl.h ]; then
package_option ruby configure --with-openssl-dir="/usr/local"
fi
# check if 11-R or later
release="$(uname -r)"
if [ "${release%%.*}" -ge 11 ]; then
# prefers readline to compile most of ruby versions
if pkg info -e readline > /dev/null; then
# use readline from Ports Collection
package_option ruby configure --with-readline-dir="/usr/local"
elif pkg info -e libedit > /dev/null; then
# use libedit from Ports Collection
package_option ruby configure --enable-libedit
package_option ruby configure --with-libedit-dir="/usr/local"
fi
fi
fi
}
has_broken_mac_readline() {
# Mac OS X 10.4 has broken readline.
# https://github.com/pyenv/pyenv/issues/23
is_mac &&
! configured_with_package_dir "python" "readline/rlconf.h" &&
! use_homebrew_readline
}
use_homebrew_readline() {
if ! configured_with_package_dir "python" "readline/rlconf.h"; then
local libdir="$(brew --prefix readline 2>/dev/null || true)"
if [ -d "$libdir" ]; then
echo "python-build: use readline from homebrew"
export CPPFLAGS="-I$libdir/include ${CPPFLAGS}"
export LDFLAGS="-L$libdir/lib ${LDFLAGS}"
else
return 1
fi
fi
}
prefer_openssl11() {
# Allow overriding the preference of OpenSSL version per definition basis (#1302, #1325, #1326)
PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@1.1 openssl}"
export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA
}
build_package_mac_readline() {
# Install to a subdirectory since we don't want shims for bin/readline.
READLINE_PREFIX_PATH="${PREFIX_PATH}/readline"
# Tell Python to use this readline for its extension.
export CPPFLAGS="-I${READLINE_PREFIX_PATH}/include ${CPPFLAGS}"
export LDFLAGS="-L${READLINE_PREFIX_PATH}/lib ${LDFLAGS}"
# Make sure pkg-config finds our build first.
export PKG_CONFIG_PATH="${READLINE_PREFIX_PATH}/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
build_package_standard "$@"
}
has_broken_mac_openssl() {
is_mac || return 1
local openssl_version="$(/usr/bin/openssl version 2>/dev/null || true)"
[[ $openssl_version = "OpenSSL 0.9.8"?* || $openssl_version = "LibreSSL"* ]] &&
! use_homebrew_openssl
}
use_homebrew_openssl() {
command -v brew >/dev/null || return 1
for openssl in ${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl}; do
local ssldir="$(brew --prefix "${openssl}" || true)"
if [ -d "$ssldir" ]; then
echo "python-build: use ${openssl} from homebrew"
if [[ -n "${PYTHON_BUILD_CONFIGURE_WITH_OPENSSL:-}" ]]; then
# configure script of newer CPython versions support `--with-openssl`
# https://bugs.python.org/issue21541
package_option python configure --with-openssl="${ssldir}"
else
export CPPFLAGS="-I$ssldir/include ${CPPFLAGS}"
export LDFLAGS="-L$ssldir/lib ${LDFLAGS}"
fi
export PKG_CONFIG_PATH="$ssldir/lib/pkgconfig/:${PKG_CONFIG_PATH}"
return
fi
done
return 1
}
build_package_mac_openssl() {
# Install to a subdirectory since we don't want shims for bin/openssl.
OPENSSL_PREFIX_PATH="${PREFIX_PATH}/openssl"
# Put openssl.conf, certs, etc in ~/.pyenv/versions/*/openssl/ssl
OPENSSLDIR="${OPENSSLDIR:-$OPENSSL_PREFIX_PATH/ssl}"
# Tell Python to use this openssl for its extension.
if [[ -n "${PYTHON_BUILD_CONFIGURE_WITH_OPENSSL:-}" ]]; then
# configure script of newer CPython versions support `--with-openssl`
# https://bugs.python.org/issue21541
package_option python configure --with-openssl="${OPENSSL_PREFIX_PATH}"
else
export CPPFLAGS="-I${OPENSSL_PREFIX_PATH}/include ${CPPFLAGS}"
export LDFLAGS="-L${OPENSSL_PREFIX_PATH}/lib ${LDFLAGS}"
fi
# Make sure pkg-config finds our build first.
export PKG_CONFIG_PATH="${OPENSSL_PREFIX_PATH}/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
# Hint OpenSSL that we prefer a 64-bit build.
export KERNEL_BITS="64"
OPENSSL_CONFIGURE="${OPENSSL_CONFIGURE:-./config}"
local nokerberos
[[ "$1" != openssl-1.0.* ]] || nokerberos=1
# Compile a shared lib with zlib dynamically linked.
package_option openssl configure --openssldir="$OPENSSLDIR" zlib-dynamic no-ssl3 shared ${nokerberos:+no-ssl2 no-krb5}
# Default MAKE_OPTS are -j 2 which can confuse the build. Thankfully, make
# gives precedence to the last -j option, so we can override that.
package_option openssl make -j 1
build_package_standard "$@"
# Extract root certs from the system keychain in .pem format and rehash.
local pem_file="$OPENSSLDIR/cert.pem"
security find-certificate -a -p /Library/Keychains/System.keychain > "$pem_file"
security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain >> "$pem_file"
}
# Post-install check that the openssl extension was built.
build_package_verify_openssl() {
"$RUBY_BIN" -e '
manager = ARGV[0]
packages = {
"apt-get" => Hash.new {|h,k| "lib#{k}-dev" }.update(
"openssl" => "libssl-dev",
"zlib" => "zlib1g-dev"
),
"yum" => Hash.new {|h,k| "#{k}-devel" }.update(
"yaml" => "libyaml-devel"
)
}
failed = %w[openssl readline zlib yaml].reject do |lib|
begin
require lib
rescue LoadError
$stderr.puts "The Ruby #{lib} extension was not compiled."
end
end
if failed.size > 0
$stderr.puts "ERROR: Ruby install aborted due to missing extensions"
$stderr.print "Try running `%s install -y %s` to fetch missing dependencies.\n\n" % [
manager,
failed.map { |lib| packages.fetch(manager)[lib] }.join(" ")
] unless manager.empty?
$stderr.puts "Configure options used:"
require "rbconfig"; require "shellwords"
RbConfig::CONFIG.fetch("configure_args").shellsplit.each { |arg| $stderr.puts " #{arg}" }
exit 1
end
' "$(basename "$(type -p yum apt-get | head -1)")" >&4 2>&1
}
use_homebrew_zlib() {
local brew_zlib="$(brew --prefix zlib 2>/dev/null || true)"
if [ -d "$brew_zlib" ]; then
echo "python-build: use zlib from homebrew"
export CFLAGS="-I${brew_zlib} ${CFLAGS}"
fi
}
use_xcode_sdk_zlib() {
local xc_sdk_path="$(xcrun --show-sdk-path 2>/dev/null || true)"
if [ -d "$xc_sdk_path" ]; then
echo "python-build: use zlib from xcode sdk"
export CFLAGS="-I${xc_sdk_path}/usr/include ${CFLAGS}"
fi
}
# Ensure that directories listed in LDFLAGS exist
build_package_ldflags_dirs() {
local arg dir
set - $LDFLAGS
while [ $# -gt 0 ]; do
dir=""
case "$1" in
-L ) dir="$2" ;;
-L* ) dir="${1#-L}" ;;
esac
[ -z "$dir" ] || mkdir -p "$dir"
shift 1
done
}
build_package_enable_shared() {
package_option python configure --enable-shared
}
build_package_auto_tcltk() {
if is_mac && [ ! -d /usr/include/X11 ]; then
if [ -d /opt/X11/include ]; then
if [[ "$CPPFLAGS" != *-I/opt/X11/include* ]]; then
export CPPFLAGS="-I/opt/X11/include $CPPFLAGS"
fi
else
package_option python configure --without-tk
fi
fi
}
apply_python_patch() {
local patchfile
case "$1" in
Python-* | jython-* | pypy-* | stackless-* )
patchfile="$(mktemp "${TMP}/python-patch.XXXXXX")"
cat "${2:--}" >"$patchfile"
local striplevel=0
grep -q '^diff --git a/' "$patchfile" && striplevel=1
patch -p$striplevel --force -i "$patchfile"
;;
esac
}
build_package_symlink_version_suffix() {
if [[ "$PYTHON_CONFIGURE_OPTS" == *"--enable-framework"* ]]; then
if [ -e "${PREFIX_PATH}/bin" ]; then
# Always create `bin` as symlink to framework path if the version was built with `--enable-frameowrk` (#590)
rm -rf "${PREFIX_PATH}/bin.orig"
mv -f "${PREFIX_PATH}/bin" "${PREFIX_PATH}/bin.orig"
fi
# Only symlinks are installed in ${PREFIX_PATH}/bin
ln -fs "${PREFIX_PATH}/Python.framework/Versions/Current/bin" "${PREFIX_PATH}/bin"
fi
# Not create symlinks on `altinstall` (#255)
if [[ "$PYTHON_MAKE_INSTALL_TARGET" != *"altinstall"* ]]; then
shopt -s nullglob
local version_bin="$(ls -1 "${PREFIX_PATH}/bin/python"* | grep '[0-9]$' | sort | tail -1)"
suffix="$(basename "${version_bin}" | sed -e 's/^python//')"
if [ -n "${suffix}" ]; then
local file link
for file in "${PREFIX_PATH}/bin"/*; do
unset link
case "${file}" in
*/"python${suffix}-config" )
# Symlink `pythonX.Y-config` to `python-config` if `python-config` is missing (#296)
link="${file%/*}/python-config"
;;
*/*"-${suffix}" )
link="${file%%-${suffix}}"
;;
*/*"${suffix}" )
link="${file%%${suffix}}"
;;
esac
if [ -n "$link" ] && [ ! -e "$link" ]; then
( cd "${file%/*}" && ln -fs "${file##*/}" "${link##*/}" )
fi
done
fi
shopt -u nullglob
fi
}
verify_python() {
build_package_symlink_version_suffix
if [ ! -x "${PYTHON_BIN}" ]; then
{ colorize 1 "ERROR"
echo ": invalid Python executable: ${PYTHON_BIN}"
echo
echo "The python-build could not find proper executable of Python after successful build."
echo "Please open an issue for future improvements."
echo "https://github.com/pyenv/pyenv/issues"
return 1
} >&3
fi
}
try_python_module() {
if ! "$PYTHON_BIN" -c "import $1" 1>/dev/null 2>&1; then
{ colorize 1 "WARNING"
echo ": The Python $1 extension was not compiled. Missing the ${2:-$1}?"
return 0
} >&3
fi
}
verify_python_module() {
if ! "$PYTHON_BIN" -c "import $1" 1>/dev/null 2>&1; then
{ colorize 1 "ERROR"
echo ": The Python $1 extension was not compiled. Missing the ${2:-$1}?"
echo
echo "Please consult to the Wiki page to fix the problem."
echo "https://github.com/pyenv/pyenv/wiki/Common-build-problems"
echo
return 1
} >&3
fi
}
# Post-install check for Python 2.1.x
build_package_verify_py21() {
verify_python "${2:-python2.1}"
try_python_module "readline" "GNU readline lib"
verify_python_module "binascii" "binascii"
# fixme: zlib doesn't link correctly on 64-bit Linux, due to being in
# /usr/x86_64-linux-gnu instead of /usr/lib
try_python_module "zlib" "zlib"
try_python_module "bz2" "bzip2 lib"
}
# Post-install check for Python 2.2.x
build_package_verify_py22() {
verify_python "${2:-python2.2}"
try_python_module "readline" "GNU readline lib"
verify_python_module "binascii" "binascii"
# fixme: zlib doesn't link correctly on 64-bit Linux, due to being in
# /usr/x86_64-linux-gnu instead of /usr/lib
try_python_module "zlib" "zlib"
try_python_module "bz2" "bzip2 lib"
}
# Post-install check for Python 2.3.x
build_package_verify_py23() {
verify_python "${2:-python2.3}"
try_python_module "readline" "GNU readline lib"
verify_python_module "binascii" "binascii"
# fixme: zlib doesn't link correctly on 64-bit Linux, due to being in
# /usr/x86_64-linux-gnu instead of /usr/lib
try_python_module "zlib" "zlib"
try_python_module "bz2" "bzip2 lib"
}
# Post-install check for Python 2.4.x
build_package_verify_py24() {
verify_python "${2:-2.4}"
try_python_module "readline" "GNU readline lib"
verify_python_module "zlib" "zlib"
try_python_module "bz2" "bzip2 lib"
}
# Post-install check for Python 2.5.x
build_package_verify_py25() {
build_package_verify_py24 "$1" "${2:-2.5}"
try_python_module "sqlite3" "SQLite3 lib"
}
# Post-install check for Python 2.6.x
build_package_verify_py26() {
build_package_verify_py25 "$1" "${2:-2.6}"
verify_python_module "ssl" "OpenSSL lib"
}
# Post-install check for Python 2.7.x
build_package_verify_py27() {
build_package_verify_py26 "$1" "${2:-2.7}"
}
# Post-install check for Python 3.0.x
build_package_verify_py30() {
verify_python "${2:-3.0}"
try_python_module "bz2" "bzip2 lib"
try_python_module "readline" "GNU readline lib"
verify_python_module "ssl" "OpenSSL lib"
try_python_module "sqlite3" "SQLite3 lib"
verify_python_module "zlib" "zlib"
}
# Post-install check for Python 3.1.x
build_package_verify_py31() {
build_package_verify_py30 "$1" "${2:-3.1}"
}
# Post-install check for Python 3.2.x
build_package_verify_py32() {
build_package_verify_py31 "$1" "${2:-3.2}"
}
# Post-install check for Python 3.3.x
build_package_verify_py33() {
build_package_verify_py32 "$1" "${2:-3.3}"
}
# Post-install check for Python 3.4.x
build_package_verify_py34() {
build_package_verify_py33 "$1" "${2:-3.4}"
}
# Post-install check for Python 3.5.x
build_package_verify_py35() {
build_package_verify_py34 "$1" "${2:-3.5}"
}
# Post-install check for Python 3.6.x
build_package_verify_py36() {
build_package_verify_py35 "$1" "${2:-3.6}"
}
# Post-install check for Python 3.7.x
build_package_verify_py37() {
build_package_verify_py36 "$1" "${2:-3.7}"
}
# Post-install check for Python 3.8.x
build_package_verify_py38() {
build_package_verify_py37 "$1" "${2:-3.8}"
}
# Post-install check for Python 3.9.x
build_package_verify_py39() {
build_package_verify_py38 "$1" "${2:-3.9}"
}
# Post-install check for Python 3.10.x
build_package_verify_py310() {
build_package_verify_py39 "$1" "${2:-3.10}"
}
# Copy Tools/gdb/libpython.py to pythonX.Y-gdb.py (#1190)
build_package_copy_python_gdb() {
if [ -e "$BUILD_PATH/$1/Tools/gdb/libpython.py" ]; then
local version_re='-([0-9]\.[0-9]+)'
[[ "$1" =~ $version_re ]]
local python_bin="$PREFIX_PATH/bin/python${BASH_REMATCH[1]}"
cp "$BUILD_PATH/$1/Tools/gdb/libpython.py" "$python_bin-gdb.py"
fi
}
build_package_ez_setup() {
local ez_setup="ez_setup.py"
rm -f "${ez_setup}"
{ if [ "${EZ_SETUP+defined}" ] && [ -f "${EZ_SETUP}" ]; then
echo "Installing setuptools from ${EZ_SETUP}..." 1>&2
cat "${EZ_SETUP}"
else
[ -n "${EZ_SETUP_URL}" ]
echo "Installing setuptools from ${EZ_SETUP_URL}..." 1>&2
http get "${EZ_SETUP_URL}"
fi
} 1> "${ez_setup}"
"${PYTHON_BIN}" "${ez_setup}" ${EZ_SETUP_OPTS} 1>&4 2>&1 || {
echo "error: failed to install setuptools via ez_setup.py" >&2
return 1
}
build_package_symlink_version_suffix
}
build_package_get_pip() {
local get_pip="get-pip.py"
rm -f "${get_pip}"
{ if [ "${GET_PIP+defined}" ] && [ -f "${GET_PIP}" ]; then
echo "Installing pip from ${GET_PIP}..." 1>&2
cat "${GET_PIP}"
else
[ -n "${GET_PIP_URL}" ]
echo "Installing pip from ${GET_PIP_URL}..." 1>&2
http get "${GET_PIP_URL}"
fi
} 1> "${get_pip}"
"${PYTHON_BIN}" -s "${get_pip}" ${GET_PIP_OPTS} 1>&4 2>&1 || {
echo "error: failed to install pip via get-pip.py" >&2
return 1
}
build_package_symlink_version_suffix
}
build_package_ensurepip() {
local ensurepip_opts
# Install as `--altinstall` if the Python is installed as `altinstall` (#255)
if [[ "$PYTHON_MAKE_INSTALL_TARGET" == *"altinstall"* ]]; then
ensurepip_opts="--altinstall"
fi
# FIXME: `--altinstall` with `get-pip.py`
"$PYTHON_BIN" -s -m ensurepip ${ensurepip_opts} 1>/dev/null 2>&1 || build_package_get_pip "$@" || return 1
build_package_symlink_version_suffix
}
version() {
local git_revision
# Read the revision from git if the remote points to "python-build" repository
if GIT_DIR="$PYTHON_BUILD_INSTALL_PREFIX/../../.git" git remote -v 2>/dev/null | grep -q /pyenv; then
git_revision="$(GIT_DIR="$PYTHON_BUILD_INSTALL_PREFIX/../../.git" git describe --tags HEAD 2>/dev/null || true)"
git_revision="${git_revision#v}"
fi
echo "python-build ${git_revision:-$PYTHON_BUILD_VERSION}"
}
usage() {
sed -ne '/^#/!q;s/.\{1,2\}//;1,2d;p' < "$0"
[ -z "$1" ] || exit "$1"
}
list_definitions() {
{ for DEFINITION_DIR in "${PYTHON_BUILD_DEFINITIONS[@]}"; do
[ -d "$DEFINITION_DIR" ] && ls "$DEFINITION_DIR" | grep -xv patches
done
} | sort_versions | uniq
}
sort_versions() {
sed 'h; s/[+-]/./g; s/.p\([[:digit:]]\)/.z.\1/; s/$/.z/; G; s/\n/ /' | \
LC_ALL=C sort -t. -k 1,1 -k 2,2n -k 3,3n -k 4,4n -k 5,5n | awk '{print $2}'
}
unset VERBOSE
unset KEEP_BUILD_PATH
unset HAS_PATCH
unset DEBUG
unset IPV4
unset IPV6
PYTHON_BUILD_INSTALL_PREFIX="$(abs_dirname "$0")/.."
IFS=: PYTHON_BUILD_DEFINITIONS=($PYTHON_BUILD_DEFINITIONS ${PYTHON_BUILD_ROOT:-$PYTHON_BUILD_INSTALL_PREFIX}/share/python-build)
IFS="$OLDIFS"
parse_options "$@"
for option in "${OPTIONS[@]}"; do
case "$option" in
"h" | "help" )
version
echo
usage 0
;;
"definitions" )
list_definitions
exit 0
;;
"k" | "keep" )
KEEP_BUILD_PATH=true
;;
"v" | "verbose" )
VERBOSE=true
;;
"p" | "patch" )
HAS_PATCH=true
;;
"g" | "debug" )
DEBUG=true
# Disable optimization (#808)
PYTHON_CFLAGS="-O0 ${PYTHON_CFLAGS}"
;;
"4" | "ipv4")
IPV4=true
;;
"6" | "ipv6")
IPV6=true
;;
"version" )
version
exit 0
;;
esac
done
[ "${#ARGUMENTS[@]}" -eq 2 ] || usage 1 >&2
DEFINITION_PATH="${ARGUMENTS[0]}"
if [ -z "$DEFINITION_PATH" ]; then
usage 1 >&2
elif [ ! -f "$DEFINITION_PATH" ]; then
for DEFINITION_DIR in "${PYTHON_BUILD_DEFINITIONS[@]}"; do
if [ -f "${DEFINITION_DIR}/${DEFINITION_PATH}" ]; then
DEFINITION_PATH="${DEFINITION_DIR}/${DEFINITION_PATH}"
break
fi
done
if [ ! -f "$DEFINITION_PATH" ]; then
echo "python-build: definition not found: ${DEFINITION_PATH}" >&2
exit 2
fi
fi
PREFIX_PATH="${ARGUMENTS[1]}"
if [ -z "$PREFIX_PATH" ]; then
usage 1 >&2
elif [ "${PREFIX_PATH#/}" = "$PREFIX_PATH" ]; then
PREFIX_PATH="${PWD}/${PREFIX_PATH}"
fi
if [ -z "$TMPDIR" ]; then
TMP="/tmp"
else
TMP="${TMPDIR%/}"
fi
# Check if TMPDIR is accessible and can hold executables.
tmp_executable="${TMP}/python-build-test.$$"
noexec=""
if mkdir -p "$TMP" && touch "$tmp_executable" 2>/dev/null; then
cat > "$tmp_executable" <<-EOF
#!${BASH}
exit 0
EOF
chmod +x "$tmp_executable"
else
echo "python-build: TMPDIR=$TMP is set to a non-accessible location" >&2
exit 1
fi
"$tmp_executable" 2>/dev/null || noexec=1
rm -f "$tmp_executable"
if [ -n "$noexec" ]; then
echo "python-build: TMPDIR=$TMP cannot hold executables (partition possibly mounted with \`noexec\`)" >&2
exit 1
fi
if [ -z "$MAKE" ]; then
if [ "FreeBSD" = "$(uname -s)" ]; then
if [ "$(echo $1 | sed 's/-.*$//')" = "jruby" ]; then
export MAKE="gmake"
else
if [ "$(uname -r | sed 's/[^[:digit:]].*//')" -lt 10 ]; then
export MAKE="gmake"
else
export MAKE="make"
fi
fi
else
export MAKE="make"
fi
fi
if [ -n "$PYTHON_BUILD_CACHE_PATH" ] && [ -d "$PYTHON_BUILD_CACHE_PATH" ]; then
PYTHON_BUILD_CACHE_PATH="${PYTHON_BUILD_CACHE_PATH%/}"
else
unset PYTHON_BUILD_CACHE_PATH
fi
if [ -z "$PYTHON_BUILD_MIRROR_URL" ]; then
PYTHON_BUILD_MIRROR_URL="https://pyenv.github.io/pythons"
PYTHON_BUILD_DEFAULT_MIRROR=1
else
PYTHON_BUILD_MIRROR_URL="${PYTHON_BUILD_MIRROR_URL%/}"
PYTHON_BUILD_DEFAULT_MIRROR=
fi
if [ -n "$PYTHON_BUILD_SKIP_MIRROR" ] || ! has_checksum_support compute_sha2; then
unset PYTHON_BUILD_MIRROR_URL
fi
ARIA2_OPTS="${PYTHON_BUILD_ARIA2_OPTS} ${IPV4+--disable-ipv6=true} ${IPV6+--disable-ipv6=false}"
CURL_OPTS="${PYTHON_BUILD_CURL_OPTS} ${IPV4+--ipv4} ${IPV6+--ipv6}"
WGET_OPTS="${PYTHON_BUILD_WGET_OPTS} ${IPV4+--inet4-only} ${IPV6+--inet6-only}"
# Add an option to build a debug version of Python (#11)
if [ -n "$DEBUG" ]; then
package_option python configure --with-pydebug
fi
# python-build: Specify `--libdir` on configure to fix build on openSUSE (#36)
package_option python configure --libdir="${PREFIX_PATH}/lib"
# python-build: Set `RPATH` if `--enable-shared` was given (#65, #66, #82)
if [[ "$CONFIGURE_OPTS" == *"--enable-shared"* ]] || [[ "$PYTHON_CONFIGURE_OPTS" == *"--enable-shared"* ]]; then
# The ld on Darwin embeds the full paths to each dylib by default
if [[ "$LDFLAGS" != *"-rpath="* ]] && ! is_mac; then
export LDFLAGS="-Wl,-rpath=${PREFIX_PATH}/lib ${LDFLAGS}"
fi
fi
# python-build: Set `RPATH` if --shared` was given for PyPy (#244)
if [[ "$PYPY_OPTS" == *"--shared"* ]]; then
export LDFLAGS="-Wl,-rpath=${PREFIX_PATH}/lib ${LDFLAGS}"
fi
# Add support for framework installation (`--enable-framework`) of CPython (#55, #99)
if [[ "$PYTHON_CONFIGURE_OPTS" == *"--enable-framework"* ]]; then
if ! is_mac; then
echo "python-build: framework installation is not supported." >&2
exit 1
fi
create_framework_dirs() {
local version="$(echo "$1" | sed -E 's/^[^0-9]*([0-9]+\.[0-9]+).*$/\1/')"
mkdir -p "${PREFIX_PATH}/Python.framework/Versions/${version}"
( cd "${PREFIX_PATH}/Python.framework/Versions" && ln -fs "${version}" "Current")
local path
for path in include lib share; do
mkdir -p "${PREFIX_PATH}/Python.framework/Versions/Current/${path}"
ln -fs "${PREFIX_PATH}/Python.framework/Versions/Current/${path}" "${PREFIX_PATH}/${path}"
done
}
create_framework_dirs "${DEFINITION_PATH##*/}"
package_option python configure --enable-framework="${PREFIX_PATH}"
fi
# Build against universal SDK (#219, #220)
if [[ "$PYTHON_CONFIGURE_OPTS" == *"--enable-universalsdk"* ]]; then
if ! is_mac; then
echo "python-build: universal installation is not supported." >&2
exit 1
fi
package_option python configure --enable-universalsdk=/ --with-universal-archs=intel
fi
# Compile with `--enable-unicode=ucs4` by default (#257)
if [[ "$PYTHON_CONFIGURE_OPTS" != *"--enable-unicode="* ]]; then
if ! is_mac; then
# Skip specifying `--enable-unicode` for CPython 3.3+ (#912)
case "${DEFINITION_PATH##*/}" in
"2."* | \
"3.0" | "3.0."* | "3.0-"* | \
"3.1" | "3.1."* | "3.1-"* | \
"3.2" | "3.2."* | "3.2-"* )
package_option python configure --enable-unicode=ucs4
;;
esac
fi
fi
# regex_to_match="(--with-tcltk-libs='([^']+)')"
if [[ "$PYTHON_CONFIGURE_OPTS" =~ (--with-tcltk-libs=\'([^\']+)\') ]]; then
tcltk_match="${BASH_REMATCH[1]}"
tcltk_match_quoted="${tcltk_match//--with-tcltk-libs=/}"
# remove it from PYTHON_CONFIGURE_OPTS since it will mess up compile
PYTHON_CONFIGURE_OPTS="${PYTHON_CONFIGURE_OPTS//$tcltk_match/}"
# having issues passing the single quoted part, couldnt pass as single var and still work
package_option python configure "--with-tcltk-libs='${tcltk_match_quoted}'"
unset tcltk_match
unset tcltk_match_quoted
fi
# Unset `PIP_REQUIRE_VENV` during build (#216)
unset PIP_REQUIRE_VENV
unset PIP_REQUIRE_VIRTUALENV
# pydistutils.cfg may corrupt install location of Python libraries (#35, #111)
if [ -e "$HOME/.pydistutils.cfg" ]; then
{ colorize 1 "WARNING"
echo ": Please make sure you remove any previous custom paths from your $HOME/.pydistutils.cfg file."
} >&2
fi
# Download specified version of ez_setup.py/get-pip.py (#202)
if [ -z "${EZ_SETUP_URL}" ]; then
if [ -n "${SETUPTOOLS_VERSION}" ]; then
EZ_SETUP_URL="https://bitbucket.org/pypa/setuptools/raw/${SETUPTOOLS_VERSION}/ez_setup.py"
unset SETUPTOOLS_VERSION
else
EZ_SETUP_URL="https://bootstrap.pypa.io/ez_setup.py"
fi
fi
if [ -z "${GET_PIP_URL}" ]; then
if [ -n "${PIP_VERSION}" ]; then
{ colorize 1 "WARNING"
echo ": Setting PIP_VERSION=${PIP_VERSION} is no longer supported and may cause failures during the install process."
} 1>&2
GET_PIP_URL="https://raw.githubusercontent.com/pypa/pip/${PIP_VERSION}/contrib/get-pip.py"
# Unset `PIP_VERSION` from environment before invoking `get-pip.py` to deal with "ValueError: invalid truth value" (pypa/pip#4528)
unset PIP_VERSION
else
# Use custom get-pip URL based on the target version (#1127)
case "${DEFINITION_PATH##*/}" in
2.6 | 2.6.* )
GET_PIP_URL="https://bootstrap.pypa.io/2.6/get-pip.py"
;;
3.2 | 3.2.* )
GET_PIP_URL="https://bootstrap.pypa.io/3.2/get-pip.py"
;;
3.3 | 3.3.* )
GET_PIP_URL="https://bootstrap.pypa.io/3.3/get-pip.py"
;;
* )
GET_PIP_URL="https://bootstrap.pypa.io/get-pip.py"
;;
esac
fi
fi
# Set MACOSX_DEPLOYMENT_TARGET from the product version of OS X (#219, #220)
if is_mac; then
if [ -z "${MACOSX_DEPLOYMENT_TARGET}" ]; then
MACOS_VERSION="$(sw_vers -productVersion 2>/dev/null || true)"
MACOS_VERSION_ARRAY=(${MACOS_VERSION//\./ })
if [ "${#MACOS_VERSION_ARRAY[@]}" -ge 2 ]; then
export MACOSX_DEPLOYMENT_TARGET="${MACOS_VERSION_ARRAY[0]}.${MACOS_VERSION_ARRAY[1]}"
fi
fi
fi
python_bin_suffix() {
local version_name version_info
case "$1" in
2.* | 3.* )
version_name="$1"
version_name="${version_name%-dev}"
version_name="${version_name%-rc*}"
version_name="${version_name%rc*}"
version_info=(${version_name//./ })
echo "${version_info[0]}.${version_info[1]}"
;;
stackless-2.* | stackless-3.* )
version_name="${1#stackless-}"
version_name="${version_name%-dev}"
version_name="${version_name%-rc*}"
version_name="${version_name%rc*}"
version_info=(${version_name//./ })
echo "${version_info[0]}.${version_info[1]}"
;;
esac
}
SEED="$(date "+%Y%m%d%H%M%S").$$"
LOG_PATH="${TMP}/python-build.${SEED}.log"
PYTHON_BIN="${PREFIX_PATH}/bin/python$(python_bin_suffix "${DEFINITION_PATH##*/}")"
CWD="$(pwd)"
if [ -z "$PYTHON_BUILD_BUILD_PATH" ]; then
BUILD_PATH="${TMP}/python-build.${SEED}"
else
BUILD_PATH="$PYTHON_BUILD_BUILD_PATH"
fi
exec 4<> "$LOG_PATH" # open the log file at fd 4
if [ -n "$VERBOSE" ]; then
tail -f "$LOG_PATH" &
TAIL_PID=$!
trap "kill $TAIL_PID" SIGINT SIGTERM EXIT
fi
export LDFLAGS="-L${PREFIX_PATH}/lib ${LDFLAGS}"
export CPPFLAGS="-I${PREFIX_PATH}/include ${CPPFLAGS}"
unset PYTHONHOME
unset PYTHONPATH
trap build_failed ERR
mkdir -p "$BUILD_PATH"
source "$DEFINITION_PATH"
[ -z "${KEEP_BUILD_PATH}" ] && rm -fr "$BUILD_PATH"
trap - ERR
@liyang85
Copy link
Author

The full path of the built-in python-build script: /usr/local/Cellar/pyenv/1.2.19/plugins/python-build/bin/python-build.

@liyang85
Copy link
Author

When I install Python 3.7.8 via pyenv 1.2.20 with the modified build script, it failed and thrown an error /usr/local/bin/python-build: line 776: --with-tcltk-includes=-I/usr/local/opt/tcl-tk/include: No such file or directory, but the /usr/local/opt/tcl-tk/include directory exists!

After reading the pyenv issue again, I found a reply mentioned a Stack Overflow post "fixed everything". I followed the post step by step, wow, that's true! Fortunately, that's more simple than modifying the pyenv build script. Just do this:

  1. Install tcl-tk via Homebrew.
brew install tcl-tk
  1. Set ENV variables and install Python.
env \
  PATH="$(brew --prefix tcl-tk)/bin:$PATH" \
  LDFLAGS="-L$(brew --prefix tcl-tk)/lib" \
  CPPFLAGS="-I$(brew --prefix tcl-tk)/include" \
  PKG_CONFIG_PATH="$(brew --prefix tcl-tk)/lib/pkgconfig" \
  CFLAGS="-I$(brew --prefix tcl-tk)/include" \
  PYTHON_CONFIGURE_OPTS="--with-tcltk-includes='-I$(brew --prefix tcl-tk)/include' --with-tcltk-libs='-L$(brew --prefix tcl-tk)/lib -ltcl8.6 -ltk8.6'" \
  pyenv install 3.7.8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment