diff --git a/plugins/python-build/README.md b/plugins/python-build/README.md index e6b7b4f8..2b8852cf 100644 --- a/plugins/python-build/README.md +++ b/plugins/python-build/README.md @@ -138,6 +138,17 @@ would break all Pyenv-managed installations that depend on it. You can use a [community plugin `fix-version`](https://github.com/pyenv/pyenv/wiki/Plugins#community-plugins) to fix installations in such a case. +##### MacPorts + +MacPorts Homebrew is used to find dependency packages if `port` is found on `PATH` in MacOS. + +Set `PYTHON_BUILD_USE_MACPORTS` or `PYTHON_BUILD_SKIP_MACPORTS` to override this default. + +###### Interaction with Homebrew + +If both Homebrew and MacPorts are installed and allowed to be used, Homebrew takes preference. +There first ecosystem where any of the required dependency packages is found is used. + ##### Portage In FreeBSD, if `pkg` is on PATH, Ports are searched for some dependencies that Configure is known to not search for via `pkg-config`. @@ -164,6 +175,8 @@ You can set certain environment variables to control the build process. * `PYTHON_BUILD_SKIP_HOMEBREW`, if set, will not search for libraries installed by Homebrew when it would normally will. * `PYTHON_BUILD_USE_HOMEBREW`, if set, will search for libraries installed by Homebrew when it would normally not. * `PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA`, override the Homebrew OpenSSL formula to use. +* `PYTHON_BUILD_SKIP_MACPORTS`, if set, will not search for libraries installed by MacPorts when it would normally will. +* `PYTHON_BUILD_USE_MACPORTS`, if set, will search for libraries installed by MacPorts when it would normally not. * `PYTHON_BUILD_ROOT` overrides the default location from where build definitions in `share/python-build/` are looked up. * `PYTHON_BUILD_DEFINITIONS` can be a list of colon-separated paths that get diff --git a/plugins/python-build/bin/python-build b/plugins/python-build/bin/python-build index 32a7040c..a5dd3e6a 100755 --- a/plugins/python-build/bin/python-build +++ b/plugins/python-build/bin/python-build @@ -27,7 +27,6 @@ shopt -s extglob exec 3<&2 # preserve original stderr at fd 3 - lib() { parse_options() { OPTIONS=() @@ -81,7 +80,12 @@ abs_dirname() { cd "$cd_path" fi name="${path##*/}" - path="$(resolve_link "$name" || true)" + if [[ $name == ".." ]]; then + cd .. + path="$PWD" + else + path="$(resolve_link "$name" || true)" + fi done echo "$PWD" @@ -122,17 +126,56 @@ is_mac() { } can_use_homebrew() { + if locked_in; then + locked_in homebrew && rc=$? || rc=$?; return $rc + fi [[ -n "$PYTHON_BUILD_USE_HOMEBREW" && -n "$PYTHON_BUILD_SKIP_HOMEBREW" ]] && { echo "error: mutually exclusive environment variables PYTHON_BUILD_USE_HOMEBREW and PYTHON_BUILD_SKIP_HOMEBREW are set" >&3 exit 1 } [[ -n "$PYTHON_BUILD_USE_HOMEBREW" ]] && return 0 [[ -n "$PYTHON_BUILD_SKIP_HOMEBREW" ]] && return 1 - is_mac && return 0 + is_mac && command -v brew &>/dev/null && return 0 # In Linux, if Pyenv itself is installed with Homebrew, # we assume the user wants to take dependencies from there as well by default - command -v brew &>/dev/null && [[ $(abs_dirname "${BASH_SOURCE}") == "$(abs_dirname "$(brew --prefix 2>/dev/null ||true)")"/* ]] && return 0 - return 1 + local brew_prefix + command -v brew &>/dev/null && \ + # tests can have non-functional `brew' stub aliased to `false' + # in Bash 3.2, var="$(cmd)" errexits on failure even if part of a conditional chain + brew_prefix="$(brew --prefix || true)" && [[ -n "$brew_prefix" ]] && \ + [[ $(abs_dirname "${BASH_SOURCE}") == "$(abs_dirname "${brew_prefix}")"/* ]] && \ + { lock_in homebrew; return 0; } + + # do not check the same stuff multiple times + PYTHON_BUILD_SKIP_HOMEBREW=1; return 1 +} + +can_use_macports() { + if locked_in; then + locked_in macports && rc=$? || rc=$?; return $rc + fi + [[ -n "$PYTHON_BUILD_USE_MACPORTS" && -n "$PYTHON_BUILD_SKIP_MACPORTS" ]] && { + echo "error: mutually exclusive environment variables PYTHON_BUILD_USE_MACPORTS and PYTHON_BUILD_SKIP_MACPORTS are set" >&3 + exit 1 + } + [[ -n "$PYTHON_BUILD_USE_MACPORTS" ]] && return 0 + [[ -n "$PYTHON_BUILD_SKIP_MACPORTS" ]] && return 1 + is_mac && command -v port &>/dev/null && return 0 + + # do not check the same stuff multiple times + PYTHON_BUILD_SKIP_MACPORTS=1; return 1 +} + +locked_in() { + if [[ -z "$1" ]]; then + [[ -n $_PYTHON_BUILD_ECOSYSTEM_LOCKED_IN ]] + else + [[ $_PYTHON_BUILD_ECOSYSTEM_LOCKED_IN == "$1" ]] + fi +} + +lock_in() { + _PYTHON_BUILD_ECOSYSTEM_LOCKED_IN=${1:?} } # 9.1 -> 901 @@ -815,15 +858,35 @@ build_package_standard_build() { local PACKAGE_LDFLAGS="${package_var_name}_LDFLAGS" if [ "$package_var_name" = "PYTHON" ]; then - use_homebrew || true - use_custom_tcltk || use_homebrew_tcltk || true - use_homebrew_readline || use_freebsd_pkg || true - use_homebrew_ncurses || true - if is_mac -ge 1014; then - use_xcode_sdk_zlib || use_homebrew_zlib || true - else - use_homebrew_zlib || true + if can_use_homebrew; then + use_custom_tcltk || use_homebrew_tcltk || true + use_homebrew_readline || true + use_homebrew_ncurses || true + if is_mac -ge 1014; then + use_xcode_sdk_zlib || use_homebrew_zlib || true + else + use_homebrew_zlib || true + fi fi + if can_use_macports; then + use_custom_tcltk || true + use_macports_readline || true + use_macports_ncurses || true + if is_mac -ge 1014; then + use_xcode_sdk_zlib || use_macports_zlib || true + else + use_macports_zlib || true + fi + fi + if can_use_homebrew; then + use_homebrew || true + fi + if can_use_macports; then + use_macports || true + fi + + use_freebsd_pkg || true + use_dsymutil || true use_free_threading || true fi @@ -1434,13 +1497,30 @@ use_homebrew() { if [[ -n $brew_prefix && $brew_prefix != "/usr" && $brew_prefix != "/usr/local" ]]; then export CPPFLAGS="${CPPFLAGS:+$CPPFLAGS }-I${brew_prefix}/include" append_ldflags_libs "-L${brew_prefix}/lib -Wl,-rpath,${brew_prefix}/lib" + lock_in homebrew fi fi } +use_macports() { + can_use_macports || return 1 + local port_location="$(command -v port)" + if [ -n "$port_location" ]; then + local prefix="${port_location%/bin/port}" + export CPPFLAGS="${CPPFLAGS:+$CPPFLAGS }-I${prefix}/include" + append_ldflags_libs "-L${prefix}/lib -Wl,-rpath,${prefix}/lib" + lock_in macports + fi +} + needs_yaml() { - ! configured_with_package_dir "python" "yaml.h" && - ! use_homebrew_yaml + if ! configured_with_package_dir "python" "yaml.h"; then + if can_use_homebrew; then + use_homebrew_yaml && return 1 + elif can_use_macports; then + use_macports_yaml && return 1 + fi + fi } use_homebrew_yaml() { @@ -1450,6 +1530,23 @@ use_homebrew_yaml() { echo "python-build: use libyaml from homebrew" export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}" export LDFLAGS="-L$libdir/lib${LDFLAGS:+ ${LDFLAGS% }}" + lock_in homebrew + else + return 1 + fi +} + +use_macports_yaml() { + can_use_macports || return 1 + local prefix="$(port -q location libyaml 2>/dev/null || true)" + if [ -n "$prefix" ]; then + local libdir="$prefix/opt/local" + if [ -d "$libdir" ]; then + echo "python-build: use libyaml from MacPorts" + export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}" + export LDFLAGS="-L$libdir/lib${LDFLAGS:+ ${LDFLAGS% }}" + lock_in macports + fi else return 1 fi @@ -1488,9 +1585,14 @@ use_freebsd_pkg() { 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 + if is_mac && ! configured_with_package_dir "python" "readline/rlconf.h"; then + if can_use_homebrew; then + use_homebrew_readline && return 1 + fi + if can_use_macports; then + use_macports_readline && return 1 + fi + fi } use_homebrew_readline() { @@ -1501,6 +1603,25 @@ use_homebrew_readline() { echo "python-build: use readline from homebrew" export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}" export LDFLAGS="-L$libdir/lib${LDFLAGS:+ $LDFLAGS}" + lock_in homebrew + else + return 1 + fi + fi +} + +use_macports_readline() { + can_use_macports || return 1 + if ! configured_with_package_dir "python" "readline/rlconf.h"; then + local prefix="$(port -q location readline 2>/dev/null || true)" + if [ -n "$prefix" ]; then + local libdir="$prefix/opt/local" + if [ -d "$libdir" ]; then + echo "python-build: use readline from MacPorts" + export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}" + export LDFLAGS="-L$libdir/lib${LDFLAGS:+ $LDFLAGS}" + lock_in macports + fi else return 1 fi @@ -1514,6 +1635,23 @@ use_homebrew_ncurses() { echo "python-build: use ncurses from homebrew" export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}" export LDFLAGS="-L$libdir/lib${LDFLAGS:+ $LDFLAGS}" + lock_in homebrew + else + return 1 + fi +} + +use_macports_ncurses() { + can_use_macports || return 1 + local prefix="$(port -q location ncurses 2>/dev/null || true)" + if [[ -n "$prefix" ]]; then + local libdir="$prefix/opt/local" + if [ -d "$libdir" ]; then + echo "python-build: use ncurses from MacPorts" + export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}" + export LDFLAGS="-L$libdir/lib${LDFLAGS:+ $LDFLAGS}" + lock_in macports + fi else return 1 fi @@ -1548,8 +1686,14 @@ build_package_mac_readline() { 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 + if [[ $openssl_version = "OpenSSL 0.9.8"?* || $openssl_version = "LibreSSL"* ]]; then + if can_use_homebrew; then + use_homebrew_openssl && return 1 + fi + if can_use_macports; then + use_macports_openssl && return 1 + fi + fi } use_homebrew_openssl() { @@ -1568,7 +1712,34 @@ use_homebrew_openssl() { export LDFLAGS="-L$ssldir/lib${LDFLAGS:+ $LDFLAGS}" fi export PKG_CONFIG_PATH="$ssldir/lib/pkgconfig/:${PKG_CONFIG_PATH}" - return + lock_in homebrew + return 0 + fi + done + return 1 +} + +use_macports_openssl() { + can_use_macports || return 1 + command -v port >/dev/null || return 1 + for openssl in ${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl}; do + local ssldir="$(port -q location "${openssl}" 2>/dev/null || true)" + if [ -n "$ssldir" ]; then + ssldir="${ssldir}/opt/local" + if [ -d "$ssldir" ]; then + echo "python-build: use ${openssl} from MacPorts" + 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:+ $CPPFLAGS}" + export LDFLAGS="-L$ssldir/lib${LDFLAGS:+ $LDFLAGS}" + fi + fi + export PKG_CONFIG_PATH="$ssldir/lib/pkgconfig/:${PKG_CONFIG_PATH}" + lock_in macports + return 0 fi done return 1 @@ -1668,6 +1839,7 @@ use_homebrew_zlib() { if [ -d "$brew_zlib" ]; then echo "python-build: use zlib from homebrew" export CFLAGS="-I${brew_zlib} ${CFLAGS}" + lock_in homebrew fi } @@ -1687,6 +1859,22 @@ use_xcode_sdk_zlib() { fi } +use_macports_zlib() { + can_use_macports || return 1 + local prefix="$(port -q location zlib 2>/dev/null || true)" + if [[ -n "$prefix" ]]; then + local libdir="$prefix/opt/local" + if [[ -d "$libdir" ]]; then + echo "python-build: use zlib from MacPorts" + export CPPFLAGS="-I$prefix/include ${CPPFLAGS}" + export LDFLAGS="-L$prefix/lib ${LDFLAGS}" + lock_in macports + fi + else + return 1 + fi +} + use_homebrew_tcltk() { can_use_homebrew || return 1 # Since https://github.com/Homebrew/homebrew-core/commit/f10e88617b41555193c22fdcba6109fe82155ee2 (10.11.2024), @@ -1711,6 +1899,7 @@ use_homebrew_tcltk() { fi fi export PKG_CONFIG_PATH="${tcltk_libdir}/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" + lock_in homebrew return 0 fi done diff --git a/plugins/python-build/test/build.bats b/plugins/python-build/test/build.bats index b0b054cf..0f2aab0e 100644 --- a/plugins/python-build/test/build.bats +++ b/plugins/python-build/test/build.bats @@ -188,15 +188,49 @@ make install OUT } +@test "Homebrew and port are tried if both are present in PATH in MacOS" { + cached_tarball "Python-3.6.2" + + BREW_PREFIX="$TMP/homebrew-prefix" + + stub uname '-s : echo Darwin' + stub sw_vers '-productVersion : echo 1010' + for i in {1..5}; do stub brew false; done + stub brew "--prefix : echo '$BREW_PREFIX'" + for i in {1..3}; do stub port false; done + stub_make_install + + export PYENV_DEBUG=1 + run_inline_definition < "${TMP}/definitions/2.7.8-test" mkdir -p "${TMP}/other" echo false > "${TMP}/other/2.7.8-test" - run bin/python-build "2.7.8-test" "${TMP}/install" + run python-build "2.7.8-test" "${TMP}/install" assert_success "" }