Update eclasses for python3.11
BUG=b/389105832
TEST=presubmit
RELEASE_NOTE=None
Change-Id: I927cf4bc1127cddfebfe7c653deb391d29a124e4
Reviewed-on: https://cos-review.googlesource.com/c/third_party/overlays/eclass-overlay/+/94668
Reviewed-by: Kevin Berry <kpberry@google.com>
Tested-by: Cusky Presubmit Bot <presubmit@cos-infra-prod.iam.gserviceaccount.com>
diff --git a/eclass/python-utils-r1.eclass b/eclass/python-utils-r1.eclass
index 01836a6..6890a3c 100644
--- a/eclass/python-utils-r1.eclass
+++ b/eclass/python-utils-r1.eclass
@@ -1,4 +1,4 @@
-# Copyright 1999-2020 Gentoo Authors
+# Copyright 1999-2022 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
# @ECLASS: python-utils-r1.eclass
@@ -7,7 +7,7 @@
# @AUTHOR:
# Author: MichaĆ Górny <mgorny@gentoo.org>
# Based on work of: Krzysztof Pawlik <nelchael@gentoo.org>
-# @SUPPORTED_EAPIS: 5 6 7
+# @SUPPORTED_EAPIS: 6 7 8
# @BLURB: Utility functions for packages with Python parts.
# @DESCRIPTION:
# A utility eclass providing functions to query Python implementations,
@@ -17,11 +17,14 @@
# functions. It can be inherited safely.
#
# For more information, please see the Python Guide:
-# https://dev.gentoo.org/~mgorny/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
case "${EAPI:-0}" in
- [0-4]) die "Unsupported EAPI=${EAPI:-0} (too old) for ${ECLASS}" ;;
- [5-7]) ;;
+ [0-5]) die "Unsupported EAPI=${EAPI:-0} (too old) for ${ECLASS}" ;;
+ [6-8]) ;;
*) die "Unsupported EAPI=${EAPI} (unknown) for ${ECLASS}" ;;
esac
@@ -31,10 +34,11 @@
if [[ ! ${_PYTHON_UTILS_R1} ]]; then
-[[ ${EAPI} == 5 ]] && inherit eutils multilib
-inherit toolchain-funcs
+[[ ${EAPI} == [67] ]] && inherit eapi8-dosym
+[[ ${EAPI} == 6 ]] && inherit eqawarn
+inherit multiprocessing toolchain-funcs
-# @ECLASS-VARIABLE: _PYTHON_ALL_IMPLS
+# @ECLASS_VARIABLE: _PYTHON_ALL_IMPLS
# @INTERNAL
# @DESCRIPTION:
# All supported Python implementations, most preferred last.
@@ -44,7 +48,19 @@
)
readonly _PYTHON_ALL_IMPLS
-# @ECLASS-VARIABLE: PYTHON_COMPAT_NO_STRICT
+# @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
+ python2_{5..7}
+ python3_{1..5}
+)
+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)
@@ -56,33 +72,30 @@
# which can involve revisions of this eclass that support a different
# set of Python implementations.
-# @FUNCTION: _python_impl_supported
-# @USAGE: <impl>
+# @FUNCTION: _python_verify_patterns
+# @USAGE: <pattern>...
# @INTERNAL
# @DESCRIPTION:
-# Check whether the implementation <impl> (PYTHON_COMPAT-form)
-# is still supported.
-#
-# Returns 0 if the implementation is valid and supported. If it is
-# unsupported, returns 1 -- and the caller should ignore the entry.
-# If it is invalid, dies with an appopriate error messages.
-_python_impl_supported() {
+# 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} "${@}"
- [[ ${#} -eq 1 ]] || die "${FUNCNAME}: takes exactly 1 argument (impl)."
+ local impl pattern
+ for pattern; do
+ case ${pattern} in
+ -[23]|3.[6-9]|3.1[0-9])
+ continue
+ ;;
+ esac
- local impl=${1}
+ for impl in "${_PYTHON_ALL_IMPLS[@]}" "${_PYTHON_HISTORICAL_IMPLS[@]}"
+ do
+ [[ ${impl} == ${pattern/./_} ]] && continue 2
+ done
- # keep in sync with _PYTHON_ALL_IMPLS!
- # (not using that list because inline patterns shall be faster)
- case "${impl}" in
- python3_[6-9]|python3_1[0-9]|pypy3)
- return 0
- ;;
- *)
- [[ ${PYTHON_COMPAT_NO_STRICT} ]] && return 1
- die "Invalid implementation in PYTHON_COMPAT: ${impl}"
- esac
+ die "Invalid implementation pattern: ${pattern}"
+ done
}
# @FUNCTION: _python_set_impls
@@ -111,10 +124,42 @@
if [[ $(declare -p PYTHON_COMPAT) != "declare -a"* ]]; then
die 'PYTHON_COMPAT must be an array.'
fi
- for i in "${PYTHON_COMPAT[@]}"; do
- # trigger validity checks
- _python_impl_supported "${i}"
- done
+
+ 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|python2_7|python3_[6-9]|python3_1[0-9])
+ ;;
+ jython2_7|pypy|pypy1_[89]|pypy2_0|python2_[5-6]|python3_[1-5])
+ 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
+
+ if [[ -n ${obsolete[@]} && ${EBUILD_PHASE} == setup ]]; then
+ # complain if people don't clean up old impls while touching
+ # the ebuilds recently. use the copyright year to infer last
+ # modification
+ # NB: this check doesn't have to work reliably
+ if [[ $(head -n 1 "${EBUILD}" 2>/dev/null) == *2022* ]]; then
+ eqawarn "Please clean PYTHON_COMPAT of obsolete implementations:"
+ eqawarn " ${obsolete[*]}"
+ fi
+ fi
local supp=() unsupp=()
@@ -127,7 +172,13 @@
done
if [[ ! ${supp[@]} ]]; then
- die "No supported implementation in PYTHON_COMPAT."
+ # special-case python2_7 for python-any-r1
+ if [[ ${_PYTHON_ALLOW_PY27} ]] && has python2_7 "${PYTHON_COMPAT[@]}"
+ then
+ supp+=( python2_7 )
+ else
+ die "No supported implementation in PYTHON_COMPAT."
+ fi
fi
if [[ ${_PYTHON_SUPPORTED_IMPLS[@]} ]]; then
@@ -159,34 +210,56 @@
# 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 be
-# either:
-# a) fnmatch-style patterns, e.g. 'python2*', 'pypy'...
-# b) '-2' to indicate all Python 2 variants (= !python_is_python3)
-# c) '-3' to indicate all Python 3 variants (= python_is_python3)
+# <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
+ local impl=${1/./_} pattern
shift
for pattern; do
- if [[ ${pattern} == -2 ]]; then
- python_is_python3 "${impl}" || return 0
- elif [[ ${pattern} == -3 ]]; then
- python_is_python3 "${impl}" && return 0
- return
- # unify value style to allow lax matching
- elif [[ ${impl/./_} == ${pattern/./_} || ${impl} == python${pattern/./_} ]]; then
- return 0
- fi
+ case ${pattern} in
+ -2|python2*|pypy)
+ if [[ ${EAPI} != [67] ]]; 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} != [67] ]]; 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.9)
+ # the only unmasked pypy3 version is pypy3.9 atm
+ [[ ${impl} == python${pattern/./_} || ${impl} == pypy3 ]] &&
+ return 0
+ ;;
+ 3.8|3.1[01])
+ [[ ${impl} == python${pattern/./_} ]] && return 0
+ ;;
+ *)
+ # unify value style to allow lax matching
+ [[ ${impl} == ${pattern/./_} ]] && return 0
+ ;;
+ esac
done
return 1
}
-# @ECLASS-VARIABLE: PYTHON
+# @ECLASS_VARIABLE: PYTHON
# @DEFAULT_UNSET
# @DESCRIPTION:
# The absolute path to the current Python interpreter.
@@ -205,7 +278,7 @@
# /usr/bin/python2.7
# @CODE
-# @ECLASS-VARIABLE: EPYTHON
+# @ECLASS_VARIABLE: EPYTHON
# @DEFAULT_UNSET
# @DESCRIPTION:
# The executable name of the current Python interpreter.
@@ -224,21 +297,6 @@
# python2.7
# @CODE
-# @FUNCTION: python_export
-# @USAGE: [<impl>] <variables>...
-# @INTERNAL
-# @DESCRIPTION:
-# Backwards compatibility function. The relevant API is now considered
-# private, please use python_get* instead.
-python_export() {
- debug-print-function ${FUNCNAME} "${@}"
-
- eqawarn "python_export() is part of private eclass API."
- eqawarn "Please call python_get*() instead."
-
- _python_export "${@}"
-}
-
# @FUNCTION: _python_export
# @USAGE: [<impl>] <variables>...
# @INTERNAL
@@ -288,34 +346,24 @@
debug-print "${FUNCNAME}: PYTHON = ${PYTHON}"
;;
PYTHON_SITEDIR)
- [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it"
- # sysconfig can't be used because:
- # 1) pypy doesn't give site-packages but stdlib
- # 2) jython gives paths with wrong case
- PYTHON_SITEDIR=$("${PYTHON}" -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_lib())') || die
- export PYTHON_SITEDIR
+ export PYTHON_SITEDIR="/usr/lib/${impl}/site-packages"
debug-print "${FUNCNAME}: PYTHON_SITEDIR = ${PYTHON_SITEDIR}"
;;
PYTHON_INCLUDEDIR)
- [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it"
- PYTHON_INCLUDEDIR=$("${PYTHON}" -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_inc())') || die
- export PYTHON_INCLUDEDIR
+ export PYTHON_INCLUDEDIR="/usr/include/${impl}"
debug-print "${FUNCNAME}: PYTHON_INCLUDEDIR = ${PYTHON_INCLUDEDIR}"
-
- # Jython gives a non-existing directory
- if [[ ! -d ${PYTHON_INCLUDEDIR} ]]; then
- die "${impl} does not install any header files!"
- fi
;;
PYTHON_LIBPATH)
- [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it"
- PYTHON_LIBPATH=$("${PYTHON}" -c 'import os.path, sysconfig; print(os.path.join(sysconfig.get_config_var("LIBDIR"), sysconfig.get_config_var("LDLIBRARY")) if sysconfig.get_config_var("LDLIBRARY") else "")') || die
+ case "${ARCH}" in
+ *64 )
+ PYTHON_LIBPATH="/usr/lib64"
+ ;;
+ * )
+ PYTHON_LIBPATH="/usr/lib"
+ ;;
+ esac
export PYTHON_LIBPATH
debug-print "${FUNCNAME}: PYTHON_LIBPATH = ${PYTHON_LIBPATH}"
-
- if [[ ! ${PYTHON_LIBPATH} ]]; then
- die "${impl} lacks a (usable) dynamic library"
- fi
;;
PYTHON_CFLAGS)
local val
@@ -359,7 +407,13 @@
case "${impl}" in
python*)
[[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it"
- flags=$("${PYTHON}" -c 'import sysconfig; print(sysconfig.get_config_var("ABIFLAGS") or "")') || die
+ flags=$(
+ "${PYTHON}" - <<-EOF || die
+ import sysconfig
+ print(sysconfig.get_config_var("ABIFLAGS")
+ or "")
+ EOF
+ )
val=${PYTHON}${flags}-config
;;
*)
@@ -374,13 +428,21 @@
local d
case ${impl} in
python2.7)
- PYTHON_PKG_DEP='>=dev-lang/python-2.7.5-r2:2.7';;
+ PYTHON_PKG_DEP='>=dev-lang/python-2.7.10_p16:2.7';;
+ python3.8)
+ PYTHON_PKG_DEP=">=dev-lang/python-3.8.15_p3:3.8";;
+ python3.9)
+ PYTHON_PKG_DEP=">=dev-lang/python-3.9.15_p3:3.9";;
+ python3.10)
+ PYTHON_PKG_DEP=">=dev-lang/python-3.10.8_p3:3.10";;
+ python3.11)
+ PYTHON_PKG_DEP=">=dev-lang/python-3.11.0_p2:3.11";;
python*)
PYTHON_PKG_DEP="dev-lang/python:${impl#python}";;
pypy)
- PYTHON_PKG_DEP='>=dev-python/pypy-7.3.0:0=';;
+ PYTHON_PKG_DEP='>=dev-python/pypy-7.3.9-r2:0=';;
pypy3)
- PYTHON_PKG_DEP='>=dev-python/pypy3-7.3.0:0=';;
+ PYTHON_PKG_DEP='>=dev-python/pypy3-7.3.9_p9:0=';;
*)
die "Invalid implementation: ${impl}"
esac
@@ -508,46 +570,6 @@
echo "${PYTHON_SCRIPTDIR}"
}
-# @FUNCTION: _python_ln_rel
-# @USAGE: <from> <to>
-# @INTERNAL
-# @DESCRIPTION:
-# Create a relative symlink.
-_python_ln_rel() {
- debug-print-function ${FUNCNAME} "${@}"
-
- local target=${1}
- local symname=${2}
-
- local tgpath=${target%/*}/
- local sympath=${symname%/*}/
- local rel_target=
-
- while [[ ${sympath} ]]; do
- local tgseg= symseg=
-
- while [[ ! ${tgseg} && ${tgpath} ]]; do
- tgseg=${tgpath%%/*}
- tgpath=${tgpath#${tgseg}/}
- done
-
- while [[ ! ${symseg} && ${sympath} ]]; do
- symseg=${sympath%%/*}
- sympath=${sympath#${symseg}/}
- done
-
- if [[ ${tgseg} != ${symseg} ]]; then
- rel_target=../${rel_target}${tgseg:+${tgseg}/}
- fi
- done
- rel_target+=${tgpath}${target##*/}
-
- debug-print "${FUNCNAME}: ${symname} -> ${target}"
- debug-print "${FUNCNAME}: rel_target = ${rel_target}"
-
- ln -fs "${rel_target}" "${symname}"
-}
-
# @FUNCTION: python_optimize
# @USAGE: [<directory>...]
# @DESCRIPTION:
@@ -557,19 +579,11 @@
python_optimize() {
debug-print-function ${FUNCNAME} "${@}"
- if [[ ${EBUILD_PHASE} == pre* || ${EBUILD_PHASE} == post* ]]; then
- eerror "The new Python eclasses expect the compiled Python files to"
- eerror "be controlled by the Package Manager. For this reason,"
- eerror "the python_optimize function can be used only during src_* phases"
- eerror "(src_install most commonly) and not during pkg_* phases."
- echo
- die "python_optimize is not to be used in pre/post* phases"
- fi
-
[[ ${EPYTHON} ]] || die 'No Python implementation set (EPYTHON is null).'
local PYTHON=${PYTHON}
[[ ${PYTHON} ]] || _python_export PYTHON
+ [[ -x ${PYTHON} ]] || die "PYTHON (${PYTHON}) is not executable"
# default to sys.path
if [[ ${#} -eq 0 ]]; then
@@ -583,27 +597,37 @@
if [[ ${f} == /* && -d ${D%/}${f} ]]; then
set -- "${D%/}${f}" "${@}"
fi
- done < <("${PYTHON}" -c 'import sys; print("".join(x + "\0" for x in sys.path))' || die)
+ done < <(
+ "${PYTHON}" - <<-EOF || die
+ import sys
+ print("".join(x + "\0" for x in sys.path))
+ EOF
+ )
debug-print "${FUNCNAME}: using sys.path: ${*/%/;}"
fi
+ local jobs=$(makeopts_jobs)
local d
for d; do
# make sure to get a nice path without //
local instpath=${d#${D%/}}
instpath=/${instpath##/}
+ einfo "Optimize Python modules for ${instpath}"
case "${EPYTHON}" in
python2.7|python3.[34])
"${PYTHON}" -m compileall -q -f -d "${instpath}" "${d}"
"${PYTHON}" -OO -m compileall -q -f -d "${instpath}" "${d}"
;;
- python*|pypy3)
+ python3.[5678]|pypy3)
# both levels of optimization are separate since 3.5
- "${PYTHON}" -m compileall -q -f -d "${instpath}" "${d}"
- "${PYTHON}" -O -m compileall -q -f -d "${instpath}" "${d}"
- "${PYTHON}" -OO -m compileall -q -f -d "${instpath}" "${d}"
+ "${PYTHON}" -m compileall -j "${jobs}" -q -f -d "${instpath}" "${d}"
+ "${PYTHON}" -O -m compileall -j "${jobs}" -q -f -d "${instpath}" "${d}"
+ "${PYTHON}" -OO -m compileall -j "${jobs}" -q -f -d "${instpath}" "${d}"
+ ;;
+ python*)
+ "${PYTHON}" -m compileall -j "${jobs}" -o 0 -o 1 -o 2 --hardlink-dupes -q -f -d "${instpath}" "${d}"
;;
*)
"${PYTHON}" -m compileall -q -f -d "${instpath}" "${d}"
@@ -632,7 +656,7 @@
python_scriptinto() {
debug-print-function ${FUNCNAME} "${@}"
- python_scriptroot=${1}
+ _PYTHON_SCRIPTROOT=${1}
}
# @FUNCTION: python_doexe
@@ -646,6 +670,9 @@
python_doexe() {
debug-print-function ${FUNCNAME} "${@}"
+ [[ ${EBUILD_PHASE} != install ]] &&
+ die "${FUNCNAME} can only be used in src_install"
+
local f
for f; do
python_newexe "${f}" "${f##*/}"
@@ -664,10 +691,12 @@
python_newexe() {
debug-print-function ${FUNCNAME} "${@}"
+ [[ ${EBUILD_PHASE} != install ]] &&
+ die "${FUNCNAME} can only be used in src_install"
[[ ${EPYTHON} ]] || die 'No Python implementation set (EPYTHON is null).'
[[ ${#} -eq 2 ]] || die "Usage: ${FUNCNAME} <path> <new-name>"
- local wrapd=${python_scriptroot:-/usr/bin}
+ local wrapd=${_PYTHON_SCRIPTROOT:-/usr/bin}
local f=${1}
local newfn=${2}
@@ -683,8 +712,9 @@
)
# install the wrapper
- _python_ln_rel "${ED%/}"/usr/lib/python-exec/python-exec2 \
- "${ED%/}/${wrapd}/${newfn}" || die
+ local dosym=dosym
+ [[ ${EAPI} == [67] ]] && dosym=dosym8
+ "${dosym}" -r /usr/lib/python-exec/python-exec2 "${wrapd}/${newfn}"
# don't use this at home, just call python_doscript() instead
if [[ ${_PYTHON_REWRITE_SHEBANG} ]]; then
@@ -711,6 +741,9 @@
python_doscript() {
debug-print-function ${FUNCNAME} "${@}"
+ [[ ${EBUILD_PHASE} != install ]] &&
+ die "${FUNCNAME} can only be used in src_install"
+
local _PYTHON_REWRITE_SHEBANG=1
python_doexe "${@}"
}
@@ -735,6 +768,9 @@
python_newscript() {
debug-print-function ${FUNCNAME} "${@}"
+ [[ ${EBUILD_PHASE} != install ]] &&
+ die "${FUNCNAME} can only be used in src_install"
+
local _PYTHON_REWRITE_SHEBANG=1
python_newexe "${@}"
}
@@ -754,10 +790,10 @@
# site-packages directory.
#
# In the relative case, the exact path is determined directly
-# by each python_doscript/python_newscript function. Therefore,
-# python_moduleinto can be safely called before establishing the Python
-# interpreter and/or a single call can be used to set the path correctly
-# for multiple implementations, as can be seen in the following example.
+# by each python_domodule invocation. Therefore, python_moduleinto
+# can be safely called before establishing the Python interpreter and/or
+# a single call can be used to set the path correctly for multiple
+# implementations, as can be seen in the following example.
#
# Example:
# @CODE
@@ -770,7 +806,7 @@
python_moduleinto() {
debug-print-function ${FUNCNAME} "${@}"
- python_moduleroot=${1}
+ _PYTHON_MODULEROOT=${1}
}
# @FUNCTION: python_domodule
@@ -781,6 +817,10 @@
# and packages (directories). All listed files will be installed
# for all enabled implementations, and compiled afterwards.
#
+# The files are installed into ${D} when run in src_install() phase.
+# Otherwise, they are installed into ${BUILD_DIR}/install location
+# that is suitable for picking up by distutils-r1 in PEP517 mode.
+#
# Example:
# @CODE
# src_install() {
@@ -794,13 +834,13 @@
[[ ${EPYTHON} ]] || die 'No Python implementation set (EPYTHON is null).'
local d
- if [[ ${python_moduleroot} == /* ]]; then
+ if [[ ${_PYTHON_MODULEROOT} == /* ]]; then
# absolute path
- d=${python_moduleroot}
+ d=${_PYTHON_MODULEROOT}
else
# relative to site-packages
local sitedir=$(python_get_sitedir)
- d=${sitedir#${SYSROOT}}/${python_moduleroot//.//}
+ d=${sitedir#${SYSROOT}}/${_PYTHON_MODULEROOT//.//}
fi
if [[ ${EBUILD_PHASE} == install ]]; then
@@ -839,6 +879,8 @@
python_doheader() {
debug-print-function ${FUNCNAME} "${@}"
+ [[ ${EBUILD_PHASE} != install ]] &&
+ die "${FUNCNAME} can only be used in src_install"
[[ ${EPYTHON} ]] || die 'No Python implementation set (EPYTHON is null).'
local includedir=$(python_get_includedir)
@@ -851,20 +893,6 @@
)
}
-# @FUNCTION: python_wrapper_setup
-# @USAGE: [<path> [<impl>]]
-# @DESCRIPTION:
-# Backwards compatibility function. The relevant API is now considered
-# private, please use python_setup instead.
-python_wrapper_setup() {
- debug-print-function ${FUNCNAME} "${@}"
-
- eqawarn "python_wrapper_setup() is part of private eclass API."
- eqawarn "Please call python_setup() instead."
-
- _python_wrapper_setup "${@}"
-}
-
# @FUNCTION: _python_wrapper_setup
# @USAGE: [<path> [<impl>]]
# @INTERNAL
@@ -890,8 +918,6 @@
[[ ${impl} ]] || die "${FUNCNAME}: no impl nor EPYTHON specified."
if [[ ! -x ${workdir}/bin/python ]]; then
- _python_check_dead_variables
-
mkdir -p "${workdir}"/{bin,pkgconfig} || die
# Clean up, in case we were supposed to do a cheap update.
@@ -903,7 +929,7 @@
_python_export "${impl}" EPYTHON PYTHON
local pyver pyother
- if python_is_python3; then
+ if [[ ${EPYTHON} != python2* ]]; then
pyver=3
pyother=2
else
@@ -979,63 +1005,37 @@
# @DESCRIPTION:
# Check whether <impl> (or ${EPYTHON}) is a Python3k variant
# (i.e. uses syntax and stdlib of Python 3.*).
-#
-# Returns 0 (true) if it is, 1 (false) otherwise.
python_is_python3() {
- local impl=${1:-${EPYTHON}}
- [[ ${impl} ]] || die "python_is_python3: no impl nor EPYTHON"
-
- [[ ${impl} == python3* || ${impl} == pypy3 ]]
-}
-
-# @FUNCTION: python_is_installed
-# @USAGE: [<impl>]
-# @DESCRIPTION:
-# Check whether the interpreter for <impl> (or ${EPYTHON}) is installed.
-# Uses has_version with a proper dependency string.
-#
-# Returns 0 (true) if it is, 1 (false) otherwise.
-python_is_installed() {
- local impl=${1:-${EPYTHON}}
- [[ ${impl} ]] || die "${FUNCNAME}: no impl nor EPYTHON"
- local hasv_args=()
-
- case ${EAPI} in
- 5|6)
- hasv_args+=( --host-root )
- ;;
- *)
- hasv_args+=( -b )
- ;;
- esac
-
- local PYTHON_PKG_DEP
- _python_export "${impl}" PYTHON_PKG_DEP
- has_version "${hasv_args[@]}" "${PYTHON_PKG_DEP}"
+ :
}
# @FUNCTION: python_fix_shebang
# @USAGE: [-f|--force] [-q|--quiet] <path>...
# @DESCRIPTION:
-# Replace the shebang in Python scripts with the current Python
-# implementation (EPYTHON). If a directory is passed, works recursively
-# on all Python scripts.
+# Replace the shebang in Python scripts with the full path
+# to the current Python implementation (PYTHON, including EPREFIX).
+# If a directory is passed, works recursively on all Python scripts
+# found inside the directory tree.
#
-# Only files having a 'python*' shebang will be modified. Files with
-# other shebang will either be skipped when working recursively
-# on a directory or treated as error when specified explicitly.
+# Only files having a Python shebang (a path to any known Python
+# interpreter, optionally preceded by env(1) invocation) will
+# be processed. Files with any other shebang will either be skipped
+# silently when a directory was passed, or an error will be reported
+# for any files without Python shebangs specified explicitly.
#
-# Shebangs matching explicitly current Python version will be left
-# unmodified. Shebangs requesting another Python version will be treated
-# as fatal error, unless --force is given.
+# Shebangs that are compatible with the current Python version will be
+# mangled unconditionally. Incompatible shebangs will cause a fatal
+# error, unless --force is specified.
#
-# --force causes the function to replace even shebangs that require
-# incompatible Python version. --quiet causes the function not to list
-# modified files verbosely.
+# --force causes the function to replace shebangs with incompatible
+# Python version (but not non-Python shebangs). --quiet causes
+# the function not to list modified files verbosely.
python_fix_shebang() {
debug-print-function ${FUNCNAME} "${@}"
[[ ${EPYTHON} ]] || die "${FUNCNAME}: EPYTHON unset (pkg_setup not called?)"
+ local PYTHON
+ _python_export "${EPYTHON}" PYTHON
local force quiet
while [[ ${@} ]]; do
@@ -1051,13 +1051,13 @@
local path f
for path; do
- local any_correct any_fixed is_recursive
+ local any_fixed is_recursive
[[ -d ${path} ]] && is_recursive=1
while IFS= read -r -d '' f; do
local shebang i
- local error= from=
+ local error= match=
# note: we can't ||die here since read will fail if file
# has no newline characters
@@ -1066,65 +1066,36 @@
# First, check if it's shebang at all...
if [[ ${shebang} == '#!'* ]]; then
local split_shebang=()
- read -r -a split_shebang <<<${shebang} || die
+ read -r -a split_shebang <<<${shebang#"#!"} || die
- # Match left-to-right in a loop, to avoid matching random
- # repetitions like 'python2.7 python2'.
- for i in "${split_shebang[@]}"; do
- case "${i}" in
- *"${EPYTHON}")
- debug-print "${FUNCNAME}: in file ${f#${D%/}}"
- debug-print "${FUNCNAME}: shebang matches EPYTHON: ${shebang}"
+ local in_path=${split_shebang[0]}
+ local from='^#! *[^ ]*'
+ # if the first component is env(1), skip it
+ if [[ ${in_path} == */env ]]; then
+ in_path=${split_shebang[1]}
+ from+=' *[^ ]*'
+ fi
- # Nothing to do, move along.
- any_correct=1
- from=${EPYTHON}
- break
- ;;
- *python|*python[23])
- debug-print "${FUNCNAME}: in file ${f#${D%/}}"
- debug-print "${FUNCNAME}: rewriting shebang: ${shebang}"
-
- if [[ ${i} == *python2 ]]; then
- from=python2
- if [[ ! ${force} ]]; then
- python_is_python3 "${EPYTHON}" && error=1
- fi
- elif [[ ${i} == *python3 ]]; then
- from=python3
- if [[ ! ${force} ]]; then
- python_is_python3 "${EPYTHON}" || error=1
- fi
- else
- from=python
- fi
- break
- ;;
- *python[23].[0123456789]|*pypy|*pypy3|*jython[23].[0123456789])
- # Explicit mismatch.
- if [[ ! ${force} ]]; then
- error=1
- else
- case "${i}" in
- *python[23].[0123456789])
- from="python[23].[0123456789]";;
- *pypy)
- from="pypy";;
- *pypy3)
- from="pypy3";;
- *jython[23].[0123456789])
- from="jython[23].[0123456789]";;
- *)
- die "${FUNCNAME}: internal error in 2nd pattern match";;
- esac
- fi
- break
- ;;
- esac
- done
+ case ${in_path##*/} in
+ "${EPYTHON}")
+ match=1
+ ;;
+ python|python[23])
+ match=1
+ [[ ${in_path##*/} == python2 ]] && error=1
+ ;;
+ python[23].[0-9]|python3.[1-9][0-9]|pypy|pypy3|jython[23].[0-9])
+ # Explicit mismatch.
+ match=1
+ error=1
+ ;;
+ esac
fi
- if [[ ! ${error} && ! ${from} ]]; then
+ # disregard mismatches in force mode
+ [[ ${force} ]] && error=
+
+ if [[ ! ${match} ]]; then
# Non-Python shebang. Allowed in recursive mode,
# disallowed when specifying file explicitly.
[[ ${is_recursive} ]] && continue
@@ -1136,13 +1107,9 @@
fi
if [[ ! ${error} ]]; then
- # We either want to match ${from} followed by space
- # or at end-of-string.
- if [[ ${shebang} == *${from}" "* ]]; then
- sed -i -e "1s:${from} :${EPYTHON} :" "${f}" || die
- else
- sed -i -e "1s:${from}$:${EPYTHON}:" "${f}" || die
- fi
+ debug-print "${FUNCNAME}: in file ${f#${D%/}}"
+ debug-print "${FUNCNAME}: rewriting shebang: ${shebang}"
+ sed -i -e "1s@${from}@#!${PYTHON}@" "${f}" || die
any_fixed=1
else
eerror "The file has incompatible shebang:"
@@ -1154,17 +1121,9 @@
done < <(find -H "${path}" -type f -print0 || die)
if [[ ! ${any_fixed} ]]; then
- local cmd=eerror
- [[ ${EAPI} == 5 ]] && cmd=eqawarn
-
- "${cmd}" "QA warning: ${FUNCNAME}, ${path#${D%/}} did not match any fixable files."
- if [[ ${any_correct} ]]; then
- "${cmd}" "All files have ${EPYTHON} shebang already."
- else
- "${cmd}" "There are no Python files in specified directory."
- fi
-
- [[ ${cmd} == eerror ]] && die "${FUNCNAME} did not match any fixable files (QA warning fatal in EAPI ${EAPI})"
+ eerror "QA error: ${FUNCNAME}, ${path#${D%/}} did not match any fixable files."
+ eerror "There are no Python files in specified directory."
+ die "${FUNCNAME} did not match any fixable files"
fi
done
}
@@ -1198,7 +1157,7 @@
debug-print-function ${FUNCNAME} "${@}"
# If the locale program isn't available, just return.
- type locale >/dev/null || return 0
+ type locale &>/dev/null || return 0
if [[ $(locale charmap) != UTF-8 ]]; then
# Try English first, then everything else.
@@ -1254,179 +1213,188 @@
sed -i -e 's:^intersphinx_mapping:disabled_&:' \
"${dir}"/conf.py || die
- # not all packages include the Makefile in pypi tarball
- sphinx-build -b html -d "${dir}"/_build/doctrees "${dir}" \
- "${dir}"/_build/html || die
+ # 1. not all packages include the Makefile in pypi tarball,
+ # so we call sphinx-build directly
+ # 2. if autodoc is used, we need to call sphinx via EPYTHON,
+ # to ensure that PEP 517 venv is respected
+ # 3. if autodoc is not used, then sphinx might not be installed
+ # for the current impl, so we need a fallback to sphinx-build
+ local command=( "${EPYTHON}" -m sphinx.cmd.build )
+ if ! "${EPYTHON}" -c "import sphinx.cmd.build" 2>/dev/null; then
+ command=( sphinx-build )
+ fi
+ command+=(
+ -b html
+ -d "${dir}"/_build/doctrees
+ "${dir}"
+ "${dir}"/_build/html
+ )
+ echo "${command[@]}" >&2
+ "${command[@]}" || die
HTML_DOCS+=( "${dir}/_build/html/." )
}
-# -- python.eclass functions --
+# @FUNCTION: _python_check_EPYTHON
+# @INTERNAL
+# @DESCRIPTION:
+# Check if EPYTHON is set, die if not.
+_python_check_EPYTHON() {
+ if [[ -z ${EPYTHON} ]]; then
+ die "EPYTHON unset, invalid call context"
+ fi
+}
-_python_check_dead_variables() {
- local v
+# @VARIABLE: EPYTEST_DESELECT
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# Specifies an array of tests to be deselected via pytest's --deselect
+# parameter, when calling epytest. The list can include file paths,
+# specific test functions or parametrized test invocations.
+#
+# Note that the listed files will still be subject to collection,
+# i.e. modules imported in global scope will need to be available.
+# If this is undesirable, EPYTEST_IGNORE can be used instead.
- for v in PYTHON_DEPEND PYTHON_USE_WITH{,_OR,_OPT} {RESTRICT,SUPPORT}_PYTHON_ABIS
- do
- if [[ ${!v} ]]; then
- die "${v} is invalid for python-r1 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#Ebuild_head"
- fi
+# @VARIABLE: EPYTEST_IGNORE
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# Specifies an array of paths to be ignored via pytest's --ignore
+# parameter, when calling epytest. The listed files will be entirely
+# skipped from test collection.
+
+# @FUNCTION: epytest
+# @USAGE: [<args>...]
+# @DESCRIPTION:
+# Run pytest, passing the standard set of pytest options, then
+# --deselect and --ignore options based on EPYTEST_DESELECT
+# and EPYTEST_IGNORE, then user-specified options.
+#
+# This command dies on failure and respects nonfatal.
+epytest() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _python_check_EPYTHON
+
+ local color
+ case ${NOCOLOR} in
+ true|yes)
+ color=no
+ ;;
+ *)
+ color=yes
+ ;;
+ esac
+
+ local args=(
+ # verbose progress reporting and tracebacks
+ -vv
+ # list all non-passed tests in the summary for convenience
+ # (includes failures, skips, xfails...)
+ -ra
+ # print local variables in tracebacks, useful for debugging
+ -l
+ # override filterwarnings=error, we do not really want -Werror
+ # for end users, as it tends to fail on new warnings from deps
+ -Wdefault
+ # override color output
+ "--color=${color}"
+ # count is more precise when we're dealing with a large number
+ # of tests
+ -o console_output_style=count
+ # disable the undesirable-dependency plugins by default to
+ # trigger missing argument strips. strip options that require
+ # them from config files. enable them explicitly via "-p ..."
+ # if you *really* need them.
+ -p no:cov
+ -p no:flake8
+ -p no:flakes
+ -p no:pylint
+ # sterilize pytest-markdown as it runs code snippets from all
+ # *.md files found without any warning
+ -p no:markdown
+ # pytest-sugar undoes everything that's good about pytest output
+ # and makes it hard to read logs
+ -p no:sugar
+ # pytest-xvfb automatically spawns Xvfb for every test suite,
+ # effectively forcing it even when we'd prefer the tests
+ # not to have DISPLAY at all, causing crashes sometimes
+ # and causing us to miss missing virtualx usage
+ -p no:xvfb
+ # tavern is intrusive and breaks test suites of various packages
+ -p no:tavern
+ )
+ local x
+ for x in "${EPYTEST_DESELECT[@]}"; do
+ args+=( --deselect "${x}" )
done
-
- for v in PYTHON_{CPPFLAGS,CFLAGS,CXXFLAGS,LDFLAGS}
- do
- if [[ ${!v} ]]; then
- die "${v} is invalid for python-r1 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#PYTHON_CFLAGS"
- fi
+ for x in "${EPYTEST_IGNORE[@]}"; do
+ args+=( --ignore "${x}" )
done
+ set -- "${EPYTHON}" -m pytest "${args[@]}" "${@}"
- for v in PYTHON_TESTS_RESTRICTED_ABIS PYTHON_EXPORT_PHASE_FUNCTIONS \
- PYTHON_VERSIONED_{SCRIPTS,EXECUTABLES} PYTHON_NONVERSIONED_EXECUTABLES
- do
- if [[ ${!v} ]]; then
- die "${v} is invalid for python-r1 suite"
- fi
- done
+ echo "${@}" >&2
+ "${@}" || die -n "pytest failed with ${EPYTHON}"
+ local ret=${?}
- for v in DISTUTILS_USE_SEPARATE_SOURCE_DIRECTORIES DISTUTILS_SETUP_FILES \
- DISTUTILS_GLOBAL_OPTIONS DISTUTILS_SRC_TEST PYTHON_MODNAME
- do
- if [[ ${!v} ]]; then
- die "${v} is invalid for distutils-r1, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#${v}"
- fi
- done
-
- if [[ ${DISTUTILS_DISABLE_TEST_DEPENDENCY} ]]; then
- die "${v} is invalid for distutils-r1, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#DISTUTILS_SRC_TEST"
+ # remove common temporary directories left over by pytest plugins
+ rm -rf .hypothesis .pytest_cache || die
+ # pytest plugins create additional .pyc files while testing
+ # see e.g. https://bugs.gentoo.org/847235
+ if [[ -n ${BUILD_DIR} && -d ${BUILD_DIR} ]]; then
+ find "${BUILD_DIR}" -name '*-pytest-*.pyc' -delete || die
fi
- # python.eclass::progress
- for v in PYTHON_BDEPEND PYTHON_MULTIPLE_ABIS PYTHON_ABI_TYPE \
- PYTHON_RESTRICTED_ABIS PYTHON_TESTS_FAILURES_TOLERANT_ABIS \
- PYTHON_CFFI_MODULES_GENERATION_COMMANDS
- do
- if [[ ${!v} ]]; then
- die "${v} is invalid for python-r1 suite"
- fi
- done
+ return ${ret}
}
-python_pkg_setup() {
- die "${FUNCNAME}() is invalid for python-r1 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#pkg_setup"
+# @FUNCTION: eunittest
+# @USAGE: [<args>...]
+# @DESCRIPTION:
+# Run unit tests using dev-python/unittest-or-fail, passing the standard
+# set of options, followed by user-specified options.
+#
+# This command dies on failure and respects nonfatal.
+eunittest() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _python_check_EPYTHON
+
+ set -- "${EPYTHON}" -m unittest_or_fail discover -v "${@}"
+
+ echo "${@}" >&2
+ "${@}" || die -n "Tests failed with ${EPYTHON}"
+ return ${?}
}
-python_convert_shebangs() {
- die "${FUNCNAME}() is invalid for python-r1 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#python_convert_shebangs"
-}
+# @FUNCTION: _python_run_check_deps
+# @INTERNAL
+# @USAGE: <impl>
+# @DESCRIPTION:
+# Verify whether <impl> is an acceptable choice to run any-r1 style
+# code. Checks whether the interpreter is installed, runs
+# python_check_deps() if declared.
+_python_run_check_deps() {
+ debug-print-function ${FUNCNAME} "${@}"
-python_clean_py-compile_files() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
+ local impl=${1}
+ local hasv_args=( -b )
+ [[ ${EAPI} == 6 ]] && hasv_args=( --host-root )
-python_clean_installation_image() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
+ einfo "Checking whether ${impl} is suitable ..."
-python_execute_function() {
- die "${FUNCNAME}() is invalid for python-r1 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#python_execute_function"
-}
+ local PYTHON_PKG_DEP
+ _python_export "${impl}" PYTHON_PKG_DEP
+ ebegin " ${PYTHON_PKG_DEP}"
+ has_version "${hasv_args[@]}" "${PYTHON_PKG_DEP}"
+ eend ${?} || return 1
+ declare -f python_check_deps >/dev/null || return 0
-python_generate_wrapper_scripts() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_merge_intermediate_installation_images() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_set_active_version() {
- die "${FUNCNAME}() is invalid for python-r1 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#pkg_setup"
-}
-
-python_need_rebuild() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-PYTHON() {
- die "${FUNCNAME}() is invalid for python-r1 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#.24.28PYTHON.29.2C_.24.7BEPYTHON.7D"
-}
-
-python_get_implementation() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_get_implementational_package() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_get_libdir() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_get_library() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_get_version() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_get_implementation_and_version() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_execute_nosetests() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_execute_py.test() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_execute_trial() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_enable_pyc() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_disable_pyc() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_mod_optimize() {
- die "${FUNCNAME}() is invalid for python-r1 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#Python_byte-code_compilation"
-}
-
-python_mod_cleanup() {
- die "${FUNCNAME}() is invalid for python-r1 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#Python_byte-code_compilation"
-}
-
-# python.eclass::progress
-
-python_abi_depend() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_install_executables() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_get_extension_module_suffix() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_byte-compile_modules() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_clean_byte-compiled_modules() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
-}
-
-python_generate_cffi_modules() {
- die "${FUNCNAME}() is invalid for python-r1 suite"
+ local PYTHON_USEDEP="python_targets_${impl}(-)"
+ local PYTHON_SINGLE_USEDEP="python_single_target_${impl}(-)"
+ ebegin " python_check_deps"
+ python_check_deps
+ eend ${?}
}
# @FUNCTION: python_has_version
@@ -1435,8 +1403,10 @@
# A convenience wrapper for has_version() with verbose output and better
# defaults for use in python_check_deps().
#
-# The wrapper accepts -b/-d/-r options to indicate the root to perform
-# the lookup on. Unlike has_version, the default is -b.
+# The wrapper accepts EAPI 7+-style -b/-d/-r options to indicate
+# the root to perform the lookup on. Unlike has_version, the default
+# is -b. In EAPI 6, -b and -d are translated to --host-root
+# for compatibility.
#
# The wrapper accepts multiple package specifications. For the check
# to succeed, *all* specified atoms must match.
@@ -1451,6 +1421,14 @@
;;
esac
+ if [[ ${EAPI} == 6 ]]; then
+ if [[ ${root_arg} == -r ]]; then
+ root_arg=()
+ else
+ root_arg=( --host-root )
+ fi
+ fi
+
local pkg
for pkg; do
ebegin " ${pkg}"
@@ -1461,5 +1439,15 @@
return 0
}
+# @FUNCTION: gpep517
+# @USAGE: args passed directly to gpep517
+# @DESCRIPTION:
+# Wrapper to call gpep517. Run "gpep517 --help" for help.
+gpep517() {
+ # ZIP files (used by wheels) don't support dates before January 1, 1980.
+ # Set SOURCE_DATE_EPOCH to January 1, 2020 (arbitrary choice).
+ SOURCE_DATE_EPOCH="1577836800" "$(type -P gpep517)" "$@"
+}
+
_PYTHON_UTILS_R1=1
fi