# Copyright 1999-2025 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
# @ECLASS: python-utils-r1.eclass
# @MAINTAINER:
# Python team <python@gentoo.org>
# @AUTHOR:
# Author: Michał Górny <mgorny@gentoo.org>
# Based on work of: Krzysztof Pawlik <nelchael@gentoo.org>
# @SUPPORTED_EAPIS: 7 8
# @BLURB: Utility functions for packages with Python parts.
# @DESCRIPTION:
# A utility eclass providing functions to query Python implementations,
# install Python modules and scripts.
#
# This eclass does not set any metadata variables nor export any phase
# functions. It can be inherited safely.
#
# For more information, please see the Python Guide:
# https://projects.gentoo.org/python/guide/
# NOTE: When dropping support for EAPIs here, we need to update
# metadata/install-qa-check.d/60python-pyc
# See bug #704286, bug #781878
if [[ -z ${_PYTHON_UTILS_R1_ECLASS} ]]; then
_PYTHON_UTILS_R1_ECLASS=1
case ${EAPI} in
7|8) ;;
*) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;;
esac
[[ ${EAPI} == 7 ]] && inherit eapi8-dosym
inherit multiprocessing toolchain-funcs
# @ECLASS_VARIABLE: _PYTHON_ALL_IMPLS
# @INTERNAL
# @DESCRIPTION:
# All supported Python implementations, most preferred last.
_PYTHON_ALL_IMPLS=(
pypy3_11
python3_{13..14}t
python3_{11..14}
)
readonly _PYTHON_ALL_IMPLS
# @ECLASS_VARIABLE: _PYTHON_HISTORICAL_IMPLS
# @INTERNAL
# @DESCRIPTION:
# All historical Python implementations that are no longer supported.
_PYTHON_HISTORICAL_IMPLS=(
jython2_7
pypy pypy1_{8,9} pypy2_0 pypy3
python2_{5..7}
python3_{1..10}
)
readonly _PYTHON_HISTORICAL_IMPLS
# @ECLASS_VARIABLE: PYTHON_COMPAT_NO_STRICT
# @INTERNAL
# @DESCRIPTION:
# Set to a non-empty value in order to make eclass tolerate (ignore)
# unknown implementations in PYTHON_COMPAT.
#
# This is intended to be set by the user when using ebuilds that may
# have unknown (newer) implementations in PYTHON_COMPAT. The assumption
# is that the ebuilds are intended to be used within multiple contexts
# which can involve revisions of this eclass that support a different
# set of Python implementations.
# @FUNCTION: _python_verify_patterns
# @USAGE: <pattern>...
# @INTERNAL
# @DESCRIPTION:
# Verify whether the patterns passed to the eclass function are correct
# (i.e. can match any valid implementation). Dies on wrong pattern.
_python_verify_patterns() {
debug-print-function ${FUNCNAME} "$@"
local impl pattern
for pattern; do
case ${pattern} in
-[23]|3.[89]|3.1[0-4])
continue
;;
esac
for impl in "${_PYTHON_ALL_IMPLS[@]}" "${_PYTHON_HISTORICAL_IMPLS[@]}"
do
[[ ${impl} == ${pattern/./_} ]] && continue 2
done
die "Invalid implementation pattern: ${pattern}"
done
}
# @FUNCTION: _python_set_impls
# @INTERNAL
# @DESCRIPTION:
# Check PYTHON_COMPAT for well-formedness and validity, then set
# two global variables:
#
# - _PYTHON_SUPPORTED_IMPLS containing valid implementations supported
# by the ebuild (PYTHON_COMPAT - dead implementations),
#
# - and _PYTHON_UNSUPPORTED_IMPLS containing valid implementations that
# are not supported by the ebuild.
#
# Implementations in both variables are ordered using the pre-defined
# eclass implementation ordering.
#
# This function must be called once in global scope by an eclass
# utilizing PYTHON_COMPAT.
_python_set_impls() {
local i
# TODO: drop BASH_VERSINFO check when we require EAPI 8
if [[ ${BASH_VERSINFO[0]} -ge 5 ]]; then
[[ ${PYTHON_COMPAT@a} == *a* ]]
else
[[ $(declare -p PYTHON_COMPAT) == "declare -a"* ]]
fi
if [[ ${?} -ne 0 ]]; then
if ! declare -p PYTHON_COMPAT &>/dev/null; then
die 'PYTHON_COMPAT not declared.'
else
die 'PYTHON_COMPAT must be an array.'
fi
fi
local obsolete=()
if [[ ! ${PYTHON_COMPAT_NO_STRICT} ]]; then
for i in "${PYTHON_COMPAT[@]}"; do
# check for incorrect implementations
# we're using pattern matching as an optimization
# please keep them in sync with _PYTHON_ALL_IMPLS
# and _PYTHON_HISTORICAL_IMPLS
case ${i} in
pypy3_11|python3_9|python3_1[1-4]|python3_1[3-4]t)
;;
jython2_7|pypy|pypy1_[89]|pypy2_0|pypy3|python2_[5-7]|python3_[1-9]|python3_10)
obsolete+=( "${i}" )
;;
*)
if has "${i}" "${_PYTHON_ALL_IMPLS[@]}" \
"${_PYTHON_HISTORICAL_IMPLS[@]}"
then
die "Mis-synced patterns in _python_set_impls: missing ${i}"
else
die "Invalid implementation in PYTHON_COMPAT: ${i}"
fi
esac
done
fi
local supp=() unsupp=()
for i in "${_PYTHON_ALL_IMPLS[@]}"; do
if has "${i}" "${PYTHON_COMPAT[@]}"; then
supp+=( "${i}" )
else
unsupp+=( "${i}" )
fi
done
if [[ ! ${supp[@]} ]]; then
die "No supported implementation in PYTHON_COMPAT."
fi
if [[ ${_PYTHON_SUPPORTED_IMPLS[@]} ]]; then
# set once already, verify integrity
if [[ ${_PYTHON_SUPPORTED_IMPLS[@]} != ${supp[@]} ]]; then
eerror "Supported impls (PYTHON_COMPAT) changed between inherits!"
eerror "Before: ${_PYTHON_SUPPORTED_IMPLS[*]}"
eerror "Now : ${supp[*]}"
die "_PYTHON_SUPPORTED_IMPLS integrity check failed"
fi
if [[ ${_PYTHON_UNSUPPORTED_IMPLS[@]} != ${unsupp[@]} ]]; then
eerror "Unsupported impls changed between inherits!"
eerror "Before: ${_PYTHON_UNSUPPORTED_IMPLS[*]}"
eerror "Now : ${unsupp[*]}"
die "_PYTHON_UNSUPPORTED_IMPLS integrity check failed"
fi
else
_PYTHON_SUPPORTED_IMPLS=( "${supp[@]}" )
_PYTHON_UNSUPPORTED_IMPLS=( "${unsupp[@]}" )
readonly _PYTHON_SUPPORTED_IMPLS _PYTHON_UNSUPPORTED_IMPLS
fi
}
# @FUNCTION: _python_impl_matches
# @USAGE: <impl> [<pattern>...]
# @INTERNAL
# @DESCRIPTION:
# Check whether the specified <impl> matches at least one
# of the patterns following it. Return 0 if it does, 1 otherwise.
# Matches if no patterns are provided.
#
# <impl> can be in PYTHON_COMPAT or EPYTHON form. The patterns
# can either be fnmatch-style or stdlib versions, e.g. "3.8", "3.9".
# In the latter case, pypy3 will match if there is at least one pypy3
# version matching the stdlib version.
_python_impl_matches() {
[[ ${#} -ge 1 ]] || die "${FUNCNAME}: takes at least 1 parameter"
[[ ${#} -eq 1 ]] && return 0
local impl=${1/./_} pattern
shift
# note: do not add "return 1" below, the function is supposed
# to iterate until it matches something
for pattern; do
case ${pattern} in
-2|python2*|pypy)
if [[ ${EAPI} != 7 ]]; then
eerror
eerror "Python 2 is no longer supported in Gentoo, please remove Python 2"
eerror "${FUNCNAME[1]} calls."
die "Passing ${pattern} to ${FUNCNAME[1]} is banned in EAPI ${EAPI}"
fi
;;
-3)
# NB: "python3*" is fine, as "not pypy3"
if [[ ${EAPI} != 7 ]]; then
eerror
eerror "Python 2 is no longer supported in Gentoo, please remove Python 2"
eerror "${FUNCNAME[1]} calls."
die "Passing ${pattern} to ${FUNCNAME[1]} is banned in EAPI ${EAPI}"
fi
return 0
;;
3.[89]|3.1[0-4])
[[ ${impl%t} == python${pattern/./_} || ${impl} == pypy${pattern/./_} ]] &&
return 0
;;
*)
# unify value style to allow lax matching
[[ ${impl} =
|