commit: e94414618cb776a0d2e3808db5303667a7f007a9
Author: Fabian Groffen <grobian <AT> gentoo <DOT> org>
AuthorDate: Sun Jan 21 13:43:36 2024 +0000
Commit: Fabian Groffen <grobian <AT> gentoo <DOT> org>
CommitDate: Sun Jan 21 13:43:36 2024 +0000
URL: https://gitweb.gentoo.org/repo/proj/prefix.git/commit/?id=e9441461
eclass/python-utils-r1: sync with gx86
Signed-off-by: Fabian Groffen <grobian <AT> gentoo.org>
eclass/python-utils-r1.eclass | 245 +++++++++++++++++++++++++++++++-----------
1 file changed, 180 insertions(+), 65 deletions(-)
diff --git a/eclass/python-utils-r1.eclass b/eclass/python-utils-r1.eclass
index e565a8f752..5abe526b45 100644
--- a/eclass/python-utils-r1.eclass
+++ b/eclass/python-utils-r1.eclass
@@ -1,4 +1,4 @@
-# Copyright 1999-2023 Gentoo Authors
+# Copyright 1999-2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
# @ECLASS: python-utils-r1.eclass
@@ -114,11 +114,18 @@ _python_verify_patterns() {
_python_set_impls() {
local i
- if ! declare -p PYTHON_COMPAT &>/dev/null; then
- die 'PYTHON_COMPAT not declared.'
+ # 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 [[ $(declare -p PYTHON_COMPAT) != "declare -a"* ]]; then
- die 'PYTHON_COMPAT must be an array.'
+ 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=()
@@ -146,17 +153,6 @@ _python_set_impls() {
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=()
for i in "${_PYTHON_ALL_IMPLS[@]}"; do
@@ -231,12 +227,11 @@ _python_impl_matches() {
fi
return 0
;;
- 3.9)
- # the only unmasked pypy3 version is pypy3.9 atm
+ 3.10)
[[ ${impl} == python${pattern/./_} || ${impl}
== pypy3 ]] &&
return 0
;;
- 3.8|3.1[0-2])
+ 3.8|3.9|3.1[1-2])
[[ ${impl} == python${pattern/./_} ]] && return 0
;;
*)
@@ -332,7 +327,9 @@ _python_export() {
debug-print "${FUNCNAME}: EPYTHON = ${EPYTHON}"
;;
PYTHON)
- export PYTHON=${EPREFIX}/usr/bin/${impl}
+ # Under EAPI 7+, this should just use ${BROOT},
but Portage
+ # <3.0.50 was buggy, and prefix users need this
to update.
+ export
PYTHON=${BROOT-${EPREFIX}}/usr/bin/${impl}
if [[ " python jython pypy pypy3 " != *" ${PN}
"* ]] \
&& [[ ! -x ${EPREFIX}/usr/bin/${impl} ]] \
&& { has prefix-stack ${USE} || has
stacked-prefix ${FEATURES} ;} ; then
@@ -346,9 +343,9 @@ _python_export() {
PYTHON_SITEDIR)
[[ -n ${PYTHON} ]] || die "PYTHON needs to be
set for ${var} to be exported, or requested before it"
PYTHON_SITEDIR=$(
- "${PYTHON}" - <<-EOF || die
- import sysconfig
-
print(sysconfig.get_path("purelib"))
+ "${PYTHON}" - "${EPREFIX}/usr" <<-EOF
|| die
+ import sys, sysconfig
+
print(sysconfig.get_path("purelib", vars={"base": sys.argv[1]}))
EOF
)
export PYTHON_SITEDIR
@@ -357,9 +354,9 @@ _python_export() {
PYTHON_INCLUDEDIR)
[[ -n ${PYTHON} ]] || die "PYTHON needs to be
set for ${var} to be exported, or requested before it"
PYTHON_INCLUDEDIR=$(
- "${PYTHON}" - <<-EOF || die
- import sysconfig
-
print(sysconfig.get_path("platinclude"))
+ "${PYTHON}" - "${ESYSROOT}/usr" <<-EOF
|| die
+ import sys, sysconfig
+
print(sysconfig.get_path("platinclude", vars={"installed_platbase":
sys.argv[1]}))
EOF
)
export PYTHON_INCLUDEDIR
@@ -448,14 +445,12 @@ _python_export() {
PYTHON_PKG_DEP)
local d
case ${impl} in
- python3.10)
-
PYTHON_PKG_DEP=">=dev-lang/python-3.10.9-r1:3.10";;
- python3.11)
-
PYTHON_PKG_DEP=">=dev-lang/python-3.11.1-r1:3.11";;
- python3.12)
-
PYTHON_PKG_DEP=">=dev-lang/python-3.12.0_beta1:3.12";;
+ python*)
+
PYTHON_PKG_DEP="dev-lang/python:${impl#python}"
+ ;;
pypy3)
-
PYTHON_PKG_DEP='>=dev-python/pypy3-7.3.11-r1:0=';;
+
PYTHON_PKG_DEP="dev-python/${impl}:="
+ ;;
*)
die "Invalid implementation:
${impl}"
esac
@@ -1035,8 +1030,6 @@ 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
@@ -1245,6 +1238,62 @@ _python_check_EPYTHON() {
fi
}
+# @FUNCTION: _python_check_occluded_packages
+# @INTERNAL
+# @DESCRIPTION:
+# Check if the current directory does not contain any incomplete
+# package sources that would block installed packages from being used
+# (and effectively e.g. make it impossible to load compiled extensions).
+_python_check_occluded_packages() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ [[ -z ${BUILD_DIR} || ! -d ${BUILD_DIR}/install ]] && return
+
+ local sitedir="${BUILD_DIR}/install$(python_get_sitedir)"
+ # avoid unnecessarily checking if we are inside install dir
+ [[ ${sitedir} -ef . ]] && return
+
+ local f fn diff l
+ for f in "${sitedir}"/*/; do
+ f=${f%/}
+ fn=${f##*/}
+
+ # skip metadata directories
+ [[ ${fn} == *.dist-info || ${fn} == *.egg-info ]] && continue
+
+ if [[ -d ${fn} ]]; then
+ diff=$(
+ comm -1 -3 <(
+ find "${fn}" -type f -not -path
'*/__pycache__/*' |
+ sort
+ assert
+ ) <(
+ cd "${sitedir}" &&
+ find "${fn}" -type f -not -path
'*/__pycache__/*' |
+ sort
+ assert
+ )
+ )
+
+ if [[ -n ${diff} ]]; then
+ eqawarn "The directory ${fn} occludes package
installed for ${EPYTHON}."
+ eqawarn "The installed package includes
additional files:"
+ eqawarn
+ while IFS= read -r l; do
+ eqawarn " ${l}"
+ done <<<"${diff}"
+ eqawarn
+
+ if [[ ! ${_PYTHON_WARNED_OCCLUDED_PACKAGES} ]];
then
+ eqawarn "For more information on
occluded packages, please see:"
+ eqawarn
"https://projects.gentoo.org/python/guide/test.html#importerrors-for-c-extensions"
+ _PYTHON_WARNED_OCCLUDED_PACKAGES=1
+ fi
+ fi
+ fi
+ done
+}
+
# @VARIABLE: EPYTEST_DESELECT
# @DEFAULT_UNSET
# @DESCRIPTION:
@@ -1263,6 +1312,31 @@ _python_check_EPYTHON() {
# parameter, when calling epytest. The listed files will be entirely
# skipped from test collection.
+# @ECLASS_VARIABLE: EPYTEST_TIMEOUT
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# If set to a non-empty value, enables pytest-timeout plugin and sets
+# test timeout to the specified value. This variable can be either set
+# in ebuilds that are known to hang, or by user to prevent hangs
+# in automated test environments. If this variable is set prior
+# to calling distutils_enable_tests in distutils-r1, a test dependency
+# on dev-python/pytest-timeout is added automatically.
+
+# @ECLASS_VARIABLE: EPYTEST_XDIST
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# If set to a non-empty value, enables running tests in parallel
+# via pytest-xdist plugin. If this variable is set prior to calling
+# distutils_enable_tests in distutils-r1, a test dependency
+# on dev-python/pytest-xdist is added automatically.
+
+# @ECLASS_VARIABLE: EPYTEST_JOBS
+# @USER_VARIABLE
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# Specifies the number of jobs for parallel (pytest-xdist) test runs.
+# When unset, defaults to -j from MAKEOPTS, or the current nproc.
+
# @FUNCTION: epytest
# @USAGE: [<args>...]
# @DESCRIPTION:
@@ -1275,16 +1349,10 @@ epytest() {
debug-print-function ${FUNCNAME} "${@}"
_python_check_EPYTHON
+ _python_check_occluded_packages
- local color
- case ${NOCOLOR} in
- true|yes)
- color=no
- ;;
- *)
- color=yes
- ;;
- esac
+ local color=yes
+ [[ ${NO_COLOR} ]] && color=no
local args=(
# verbose progress reporting and tracebacks
@@ -1302,28 +1370,74 @@ epytest() {
# 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
+ # minimize the temporary directory retention, the test suites
+ # of some packages can grow them pretty large and normally
+ # we don't need to preserve them
+ -o tmp_path_retention_count=0
+ -o tmp_path_retention_policy=failed
)
+
+ if [[ ! ${PYTEST_DISABLE_PLUGIN_AUTOLOAD} ]]; then
+ args+=(
+ # 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
+ # intrusive packages that break random test suites
+ -p no:pytest-describe
+ -p no:plus
+ -p no:tavern
+ # does something to logging
+ -p no:salt-factories
+ )
+ fi
+
+ if [[ -n ${EPYTEST_TIMEOUT} ]]; then
+ if [[ ${PYTEST_PLUGINS} != *pytest_timeout* ]]; then
+ args+=(
+ -p timeout
+ )
+ fi
+
+ args+=(
+ "--timeout=${EPYTEST_TIMEOUT}"
+ )
+ fi
+
+ if [[ ${EPYTEST_XDIST} ]]; then
+ local jobs=${EPYTEST_JOBS:-$(makeopts_jobs)}
+ if [[ ${jobs} -gt 1 ]]; then
+ if [[ ${PYTEST_PLUGINS} != *xdist.plugin* ]]; then
+ args+=(
+ # explicitly enable the plugin, in case
the ebuild was
+ # using PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
+ -p xdist
+ )
+ fi
+ args+=(
+ -n "${jobs}"
+ # worksteal ensures that workers don't end up
idle when heavy
+ # jobs are unevenly distributed
+ --dist=worksteal
+ )
+ fi
+ fi
+
local x
for x in "${EPYTEST_DESELECT[@]}"; do
args+=( --deselect "${x}" )
@@ -1359,6 +1473,7 @@ eunittest() {
debug-print-function ${FUNCNAME} "${@}"
_python_check_EPYTHON
+ _python_check_occluded_packages
# unittest fails with "no tests" correctly since Python 3.12
local runner=unittest