From d7e58d6d06a9c9bcfc5bd6558872c5cfdc4d8123 Mon Sep 17 00:00:00 2001 From: Alexander Studnitskiy <25719+studnitskiy@users.noreply.github.com> Date: Sun, 9 Mar 2025 07:41:02 +0300 Subject: [PATCH] Add MacPorts support for python-build This commit allows building Python with the following libraries installed via MacPorts: - ncurses - openssl - readline - zlib (XCode SDK zlib is still tried first) If either Homebrew or MacPorts system is the only one installed, that system will be used automatically. When both systems are installed, then whatever is found first in PATH - brew or port - will be used for all dependencies. The following environment variables can be set to non-empty value to override the selection: PYTHON_BUILD_USE_HOMEBREW PYTHON_BUILD_USE_MACPORTS Tcl/Tk specific support is omitted due to CPython incompatibility with Tcl/Tk 9, according to the comments in use_homebrew_tcltk(). Those who need Tcl/Tk module, can use PYTHON_CONFIGURE_OPTS --with-tcltk-libs= pointing to the Tcl/Tk location which can be installed either from sources or via MacPorts. Tests for added *_macports_* functions are based off of the corresponding brew counterparts. Add MacPorts support for python-build This commit allows building Python with the following libraries installed via MacPorts: - ncurses - openssl - readline - zlib (XCode SDK zlib is still tried first) If either Homebrew or MacPorts system is the only one installed, that system will be used automatically. When both systems are installed, then whatever is found first in PATH - brew or port - will be used for all dependencies. The following environment variables can be set to non-empty value to override the selection: PYTHON_BUILD_USE_HOMEBREW PYTHON_BUILD_USE_MACPORTS Tcl/Tk specific support is omitted due to CPython incompatibility with Tcl/Tk 9, according to the comments in use_homebrew_tcltk(). Those who need Tcl/Tk module, can use PYTHON_CONFIGURE_OPTS --with-tcltk-libs= pointing to the Tcl/Tk location which can be installed either from sources or via MacPorts. Tests for added *_macports_* functions are based off of the corresponding brew counterparts. --- plugins/python-build/bin/python-build | 181 +++++++++++++-- plugins/python-build/test/build.bats | 245 +++++++++++++++++++++ plugins/python-build/test/definitions.bats | 2 +- 3 files changed, 412 insertions(+), 16 deletions(-) diff --git a/plugins/python-build/bin/python-build b/plugins/python-build/bin/python-build index b8b1b922..812a4581 100755 --- a/plugins/python-build/bin/python-build +++ b/plugins/python-build/bin/python-build @@ -128,13 +128,39 @@ can_use_homebrew() { } [[ -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 } +can_use_macports() { + [[ -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 + command -v port &>/dev/null && return 0 + return 1 +} + +is_homebrew_preferred() { + can_use_homebrew || return 1 + [ -n "$PYTHON_BUILD_USE_HOMEBREW" ] && return 0 + local brew_path port_path path paths + command -v port &>/dev/null && port_path=$(dirname "$(command -v port)") + [ -z "$port_path" ] && return 0 + [ -n "$PYTHON_BUILD_USE_MACPORTS" ] && return 1 + command -v brew &>/dev/null && brew_path=$(dirname "$(command -v brew)") + [ -z "$brew_path" ] && return 1 + # Homebrew and MacPorts found in PATH + # find out what to use based on the PATH order + [[ $PATH == *$brew_path*":"*$port_path* ]] && return 0 + return 1 +} + # 9.1 -> 901 # 10.9 -> 1009 # 10.10 -> 1010 @@ -815,14 +841,28 @@ 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 + if is_homebrew_preferred; then + use_homebrew || true + 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 + elif can_use_macports; then + use_macports || true + 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 else - use_homebrew_zlib || true + use_freebsd_pkg || true fi use_dsymutil || true use_free_threading || true @@ -1438,9 +1478,24 @@ use_homebrew() { 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" + fi +} + needs_yaml() { - ! configured_with_package_dir "python" "yaml.h" && - ! use_homebrew_yaml + if ! configured_with_package_dir "python" "yaml.h"; then + if is_homebrew_preferred; then + use_homebrew_yaml && return 1 + elif can_use_macports; then + use_macports_yaml && return 1 + fi + fi } use_homebrew_yaml() { @@ -1455,6 +1510,21 @@ use_homebrew_yaml() { 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% }}" + fi + else + return 1 + fi +} + use_freebsd_pkg() { # check if FreeBSD if [ "FreeBSD" = "${_PYTHON_BUILD_CACHE_UNAME_S:=$(uname -s)}" ]; then @@ -1488,9 +1558,13 @@ 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 is_homebrew_preferred; then + use_homebrew_readline && return 1 + elif can_use_macports; then + use_macports_readline && return 1 + fi + fi } use_homebrew_readline() { @@ -1507,6 +1581,23 @@ use_homebrew_readline() { 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}" + fi + else + return 1 + fi + fi +} + use_homebrew_ncurses() { can_use_homebrew || return 1 local libdir="$(brew --prefix ncurses 2>/dev/null || true)" @@ -1519,6 +1610,21 @@ use_homebrew_ncurses() { 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}" + fi + else + return 1 + 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}" @@ -1548,8 +1654,13 @@ 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 is_homebrew_preferred; then + use_homebrew_openssl && return 1 + elif can_use_macports; then + use_macports_openssl && return 1 + fi + fi } use_homebrew_openssl() { @@ -1574,6 +1685,31 @@ use_homebrew_openssl() { 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}" + 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" @@ -1687,6 +1823,21 @@ 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}" + 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), diff --git a/plugins/python-build/test/build.bats b/plugins/python-build/test/build.bats index b0b054cf..f1b29957 100644 --- a/plugins/python-build/test/build.bats +++ b/plugins/python-build/test/build.bats @@ -188,6 +188,135 @@ make install OUT } +@test "Homebrew is used when brew is before port in PATH" { + cached_tarball "Python-3.6.2" + + BREW_PREFIX="$TMP/homebrew-prefix" + mkdir -p "$BREW_PREFIX/bin" + PORT_PREFIX="$TMP/macports-prefix" + mkdir -p "$PORT_PREFIX/bin" + + for i in {1..9}; do stub uname '-s : echo Darwin'; done + for i in {1..2}; do stub sw_vers '-productVersion : echo 1010'; done + stub brew "--prefix : echo '$BREW_PREFIX'" + for i in {1..5}; do stub brew false; done + stub_make_install + export PATH="$BREW_PREFIX/bin:$PORT_PREFIX/bin:$PATH" + + 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 "" }