diff options
author | Fabian Groffen <grobian@gentoo.org> | 2024-08-07 19:07:28 +0200 |
---|---|---|
committer | Fabian Groffen <grobian@gentoo.org> | 2024-08-07 19:07:28 +0200 |
commit | 47763c3710263623f8f539da55e5bf744b5a05ff (patch) | |
tree | 43b08db261c2c41f6dfa6db63bb3cb86078812c6 | |
parent | eclass/python-utils-r1: sync with gx86 (diff) | |
download | prefix-47763c3710263623f8f539da55e5bf744b5a05ff.tar.gz prefix-47763c3710263623f8f539da55e5bf744b5a05ff.tar.bz2 prefix-47763c3710263623f8f539da55e5bf744b5a05ff.zip |
eclass/python-utils-r1: use gx86 version
stacked prefix support is no longer in use, if we need it we should see
about it then
Signed-off-by: Fabian Groffen <grobian@gentoo.org>
-rw-r--r-- | eclass/python-utils-r1.eclass | 1620 |
1 files changed, 0 insertions, 1620 deletions
diff --git a/eclass/python-utils-r1.eclass b/eclass/python-utils-r1.eclass deleted file mode 100644 index ec1a3d2006..0000000000 --- a/eclass/python-utils-r1.eclass +++ /dev/null @@ -1,1620 +0,0 @@ -# Copyright 1999-2024 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 - -case ${EAPI} in - 7|8) ;; - *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; -esac - -if [[ ! ${_PYTHON_UTILS_R1_ECLASS} ]]; then -_PYTHON_UTILS_R1_ECLASS=1 - -[[ ${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 - python3_{10..13} -) -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 - python2_{5..7} - python3_{1..9} -) -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-3]) - 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|python3_9|python3_1[0-3]) - ;; - jython2_7|pypy|pypy1_[89]|pypy2_0|python2_[5-7]|python3_[1-9]) - 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 - - 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.10) - [[ ${impl} == python${pattern/./_} || ${impl} == pypy3 ]] && - return 0 - ;; - 3.8|3.9|3.1[1-3]) - [[ ${impl} == python${pattern/./_} ]] && return 0 - ;; - *) - # unify value style to allow lax matching - [[ ${impl} == ${pattern/./_} ]] && return 0 - ;; - esac - done - - return 1 -} - -# @ECLASS_VARIABLE: PYTHON -# @DEFAULT_UNSET -# @DESCRIPTION: -# The absolute path to the current Python interpreter. -# -# This variable is set automatically in the following contexts: -# -# python-r1: Set in functions called by python_foreach_impl() or after -# calling python_setup(). -# -# python-single-r1: Set after calling python-single-r1_pkg_setup(). -# -# distutils-r1: Set within any of the python sub-phase functions. -# -# Example value: -# @CODE -# /usr/bin/python2.7 -# @CODE - -# @ECLASS_VARIABLE: EPYTHON -# @DEFAULT_UNSET -# @DESCRIPTION: -# The executable name of the current Python interpreter. -# -# This variable is set automatically in the following contexts: -# -# python-r1: Set in functions called by python_foreach_impl() or after -# calling python_setup(). -# -# python-single-r1: Set after calling python-single-r1_pkg_setup(). -# -# distutils-r1: Set within any of the python sub-phase functions. -# -# Example value: -# @CODE -# python2.7 -# @CODE - -# @FUNCTION: _python_export -# @USAGE: [<impl>] <variables>... -# @INTERNAL -# @DESCRIPTION: -# Set and export the Python implementation-relevant variables passed -# as parameters. -# -# The optional first parameter may specify the requested Python -# implementation (either as PYTHON_TARGETS value, e.g. python2_7, -# or an EPYTHON one, e.g. python2.7). If no implementation passed, -# the current one will be obtained from ${EPYTHON}. -# -# The variables which can be exported are: PYTHON, EPYTHON, -# PYTHON_SITEDIR. They are described more completely in the eclass -# variable documentation. -_python_export() { - debug-print-function ${FUNCNAME} "${@}" - - local impl var - - case "${1}" in - python*|jython*) - impl=${1/_/.} - shift - ;; - pypy|pypy3) - impl=${1} - shift - ;; - *) - impl=${EPYTHON} - if [[ -z ${impl} ]]; then - die "_python_export called without a python implementation and EPYTHON is unset" - fi - ;; - esac - debug-print "${FUNCNAME}: implementation: ${impl}" - - for var; do - case "${var}" in - EPYTHON) - export EPYTHON=${impl} - debug-print "${FUNCNAME}: EPYTHON = ${EPYTHON}" - ;; - PYTHON) - # 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 - # Need to look in build prefix - if [[ -x ${BROOT-${PORTAGE_OVERRIDE_EPREFIX}}/usr/bin/${impl} ]]; then - PYTHON=${BROOT-${PORTAGE_OVERRIDE_EPREFIX}}/usr/bin/${impl} - fi - fi - debug-print "${FUNCNAME}: PYTHON = ${PYTHON}" - ;; - PYTHON_STDLIB) - [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it" - PYTHON_STDLIB=$( - "${PYTHON}" - "${EPREFIX}/usr" <<-EOF || die - import sys, sysconfig - print(sysconfig.get_path("stdlib", vars={"installed_base": sys.argv[1]})) - EOF - ) - export PYTHON_STDLIB - debug-print "${FUNCNAME}: PYTHON_STDLIB = ${PYTHON_STDLIB}" - ;; - PYTHON_SITEDIR) - [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it" - PYTHON_SITEDIR=$( - "${PYTHON}" - "${EPREFIX}/usr" <<-EOF || die - import sys, sysconfig - print(sysconfig.get_path("purelib", vars={"base": sys.argv[1]})) - EOF - ) - export PYTHON_SITEDIR - 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}" - "${ESYSROOT}/usr" <<-EOF || die - import sys, sysconfig - print(sysconfig.get_path("platinclude", vars={"installed_platbase": sys.argv[1]})) - EOF - ) - export PYTHON_INCLUDEDIR - 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}" - <<-EOF || die - 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 "") - EOF - ) - 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 - - case "${impl}" in - python*) - # python-2.7, python-3.2, etc. - val=$($(tc-getPKG_CONFIG) --cflags ${impl/n/n-}) || die - ;; - *) - die "${impl}: obtaining ${var} not supported" - ;; - esac - - export PYTHON_CFLAGS=${val} - debug-print "${FUNCNAME}: PYTHON_CFLAGS = ${PYTHON_CFLAGS}" - ;; - PYTHON_LIBS) - local val - - case "${impl}" in - python*) - # python3.8+ - val=$($(tc-getPKG_CONFIG) --libs ${impl/n/n-}-embed) || die - ;; - *) - die "${impl}: obtaining ${var} not supported" - ;; - esac - - export PYTHON_LIBS=${val} - debug-print "${FUNCNAME}: PYTHON_LIBS = ${PYTHON_LIBS}" - ;; - PYTHON_CONFIG) - local flags val - - case "${impl}" in - python*) - [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it" - flags=$( - "${PYTHON}" - <<-EOF || die - import sysconfig - print(sysconfig.get_config_var("ABIFLAGS") - or "") - EOF - ) - val=${PYTHON}${flags}-config - ;; - *) - die "${impl}: obtaining ${var} not supported" - ;; - esac - - export PYTHON_CONFIG=${val} - debug-print "${FUNCNAME}: PYTHON_CONFIG = ${PYTHON_CONFIG}" - ;; - PYTHON_PKG_DEP) - local d - case ${impl} in - python*) - PYTHON_PKG_DEP="dev-lang/python:${impl#python}" - ;; - pypy3) - PYTHON_PKG_DEP="dev-python/${impl}:=" - ;; - *) - die "Invalid implementation: ${impl}" - esac - - # use-dep - if [[ ${PYTHON_REQ_USE} ]]; then - PYTHON_PKG_DEP+=[${PYTHON_REQ_USE}] - fi - - export PYTHON_PKG_DEP - debug-print "${FUNCNAME}: PYTHON_PKG_DEP = ${PYTHON_PKG_DEP}" - ;; - PYTHON_SCRIPTDIR) - local dir - export PYTHON_SCRIPTDIR=${EPREFIX}/usr/lib/python-exec/${impl} - if [[ " python jython pypy pypy3 " != *" ${PN} "* ]] \ - && [[ ! -x ${EPREFIX}/usr/bin/${impl} ]] \ - && { has prefix-stack ${USE} || has stacked-prefix ${FEATURES} ;} ; then - # Need to look in build prefix - if [[ -x ${BROOT-${PORTAGE_OVERRIDE_EPREFIX}}/usr/bin/${impl} ]]; then - PYTHON_SCRIPTDIR=${BROOT-${PORTAGE_OVERRIDE_EPREFIX}}/usr/lib/python-exec/${impl} - fi - fi - debug-print "${FUNCNAME}: PYTHON_SCRIPTDIR = ${PYTHON_SCRIPTDIR}" - ;; - *) - die "_python_export: unknown variable ${var}" - esac - done -} - -# @FUNCTION: python_get_stdlib -# @USAGE: [<impl>] -# @DESCRIPTION: -# Obtain and print the 'stdlib' path for the given implementation. If no -# implementation is provided, ${EPYTHON} will be used. -python_get_stdlib() { - debug-print-function ${FUNCNAME} "${@}" - - _python_export "${@}" PYTHON_STDLIB - echo "${PYTHON_STDLIB}" -} - -# @FUNCTION: python_get_sitedir -# @USAGE: [<impl>] -# @DESCRIPTION: -# Obtain and print the 'site-packages' path for the given -# implementation. If no implementation is provided, ${EPYTHON} will -# be used. -python_get_sitedir() { - debug-print-function ${FUNCNAME} "${@}" - - _python_export "${@}" PYTHON_SITEDIR - echo "${PYTHON_SITEDIR}" -} - -# @FUNCTION: python_get_includedir -# @USAGE: [<impl>] -# @DESCRIPTION: -# Obtain and print the include path for the given implementation. If no -# implementation is provided, ${EPYTHON} will be used. -python_get_includedir() { - debug-print-function ${FUNCNAME} "${@}" - - _python_export "${@}" PYTHON_INCLUDEDIR - echo "${PYTHON_INCLUDEDIR}" -} - -# @FUNCTION: python_get_library_path -# @USAGE: [<impl>] -# @DESCRIPTION: -# Obtain and print the Python library path for the given implementation. -# If no implementation is provided, ${EPYTHON} will be used. -# -# Please note that this function can be used with CPython only. Use -# in another implementation will result in a fatal failure. -python_get_library_path() { - debug-print-function ${FUNCNAME} "${@}" - - _python_export "${@}" PYTHON_LIBPATH - echo "${PYTHON_LIBPATH}" -} - -# @FUNCTION: python_get_CFLAGS -# @USAGE: [<impl>] -# @DESCRIPTION: -# Obtain and print the compiler flags for building against Python, -# for the given implementation. If no implementation is provided, -# ${EPYTHON} will be used. -# -# Please note that this function can be used with CPython only. -# It requires Python and pkg-config installed, and therefore proper -# build-time dependencies need be added to the ebuild. -python_get_CFLAGS() { - debug-print-function ${FUNCNAME} "${@}" - - _python_export "${@}" PYTHON_CFLAGS - echo "${PYTHON_CFLAGS}" -} - -# @FUNCTION: python_get_LIBS -# @USAGE: [<impl>] -# @DESCRIPTION: -# Obtain and print the compiler flags for linking against Python, -# for the given implementation. If no implementation is provided, -# ${EPYTHON} will be used. -# -# Please note that this function can be used with CPython only. -# It requires Python and pkg-config installed, and therefore proper -# build-time dependencies need be added to the ebuild. -python_get_LIBS() { - debug-print-function ${FUNCNAME} "${@}" - - _python_export "${@}" PYTHON_LIBS - echo "${PYTHON_LIBS}" -} - -# @FUNCTION: python_get_PYTHON_CONFIG -# @USAGE: [<impl>] -# @DESCRIPTION: -# Obtain and print the PYTHON_CONFIG location for the given -# implementation. If no implementation is provided, ${EPYTHON} will be -# used. -# -# Please note that this function can be used with CPython only. -# It requires Python installed, and therefore proper build-time -# dependencies need be added to the ebuild. -python_get_PYTHON_CONFIG() { - debug-print-function ${FUNCNAME} "${@}" - - _python_export "${@}" PYTHON_CONFIG - echo "${PYTHON_CONFIG}" -} - -# @FUNCTION: python_get_scriptdir -# @USAGE: [<impl>] -# @DESCRIPTION: -# Obtain and print the script install path for the given -# implementation. If no implementation is provided, ${EPYTHON} will -# be used. -python_get_scriptdir() { - debug-print-function ${FUNCNAME} "${@}" - - _python_export "${@}" PYTHON_SCRIPTDIR - echo "${PYTHON_SCRIPTDIR}" -} - -# @FUNCTION: python_optimize -# @USAGE: [<directory>...] -# @DESCRIPTION: -# Compile and optimize Python modules in specified directories (absolute -# paths). If no directories are provided, the default system paths -# are used (prepended with ${D}). -python_optimize() { - debug-print-function ${FUNCNAME} "${@}" - - [[ ${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 - local f - while IFS= read -r -d '' f; do - # 1) accept only absolute paths - # (i.e. skip '', '.' or anything like that) - # 2) skip paths which do not exist - # (python2.6 complains about them verbosely) - - if [[ ${f} == /* && -d ${D%/}${f} ]]; then - set -- "${D%/}${f}" "${@}" - fi - 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 - python3.8) - # both levels of optimization are separate since 3.5 - "${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*|pypy3) - # Python 3.9+ - "${PYTHON}" -m compileall -j "${jobs}" -o 0 -o 1 -o 2 --hardlink-dupes -q -f -d "${instpath}" "${d}" - ;; - pypy|jython2.7) - "${PYTHON}" -m compileall -q -f -d "${instpath}" "${d}" - ;; - *) - die "${FUNCNAME}: unexpected EPYTHON=${EPYTHON}" - ;; - esac - done -} - -# @FUNCTION: python_scriptinto -# @USAGE: <new-path> -# @DESCRIPTION: -# Set the directory to which files passed to python_doexe(), -# python_doscript(), python_newexe() and python_newscript() -# are going to be installed. The new value needs to be relative -# to the installation root (${ED}). -# -# If not set explicitly, the directory defaults to /usr/bin. -# -# Example: -# @CODE -# src_install() { -# python_scriptinto /usr/sbin -# python_foreach_impl python_doscript foo -# } -# @CODE -python_scriptinto() { - debug-print-function ${FUNCNAME} "${@}" - - _PYTHON_SCRIPTROOT=${1} -} - -# @FUNCTION: python_doexe -# @USAGE: <files>... -# @DESCRIPTION: -# Install the given executables into the executable install directory, -# for the current Python implementation (${EPYTHON}). -# -# The executable will be wrapped properly for the Python implementation, -# though no shebang mangling will be performed. -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##*/}" - done -} - -# @FUNCTION: python_newexe -# @USAGE: <path> <new-name> -# @DESCRIPTION: -# Install the given executable into the executable install directory, -# for the current Python implementation (${EPYTHON}). -# -# The executable will be wrapped properly for the Python implementation, -# though no shebang mangling will be performed. It will be renamed -# to <new-name>. -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 f=${1} - local newfn=${2} - - local scriptdir=$(python_get_scriptdir) - local d=${scriptdir#${EPREFIX}} - - ( - dodir "${wrapd}" - exeopts -m 0755 - exeinto "${d}" - newexe "${f}" "${newfn}" || return ${?} - ) - - # install the wrapper - local dosym=dosym - [[ ${EAPI} == 7 ]] && 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 - python_fix_shebang -q "${ED%/}/${d}/${newfn}" - fi -} - -# @FUNCTION: python_doscript -# @USAGE: <files>... -# @DESCRIPTION: -# Install the given scripts into the executable install directory, -# for the current Python implementation (${EPYTHON}). -# -# All specified files must start with a 'python' shebang. The shebang -# will be converted, and the files will be wrapped properly -# for the Python implementation. -# -# Example: -# @CODE -# src_install() { -# python_foreach_impl python_doscript ${PN} -# } -# @CODE -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 "${@}" -} - -# @FUNCTION: python_newscript -# @USAGE: <path> <new-name> -# @DESCRIPTION: -# Install the given script into the executable install directory -# for the current Python implementation (${EPYTHON}), and name it -# <new-name>. -# -# The file must start with a 'python' shebang. The shebang will be -# converted, and the file will be wrapped properly for the Python -# implementation. It will be renamed to <new-name>. -# -# Example: -# @CODE -# src_install() { -# python_foreach_impl python_newscript foo.py foo -# } -# @CODE -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 "${@}" -} - -# @FUNCTION: python_moduleinto -# @USAGE: <new-path> -# @DESCRIPTION: -# Set the Python module install directory for python_domodule(). -# The <new-path> can either be an absolute target system path (in which -# case it needs to start with a slash, and ${ED} will be prepended to -# it) or relative to the implementation's site-packages directory -# (then it must not start with a slash). The relative path can be -# specified either using the Python package notation (separated by dots) -# or the directory notation (using slashes). -# -# When not set explicitly, the modules are installed to the top -# site-packages directory. -# -# In the relative case, the exact path is determined directly -# 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 -# src_install() { -# python_moduleinto bar -# # installs ${PYTHON_SITEDIR}/bar/baz.py -# python_foreach_impl python_domodule baz.py -# } -# @CODE -python_moduleinto() { - debug-print-function ${FUNCNAME} "${@}" - - _PYTHON_MODULEROOT=${1} -} - -# @FUNCTION: python_domodule -# @USAGE: <files>... -# @DESCRIPTION: -# Install the given modules (or packages) into the current Python module -# installation directory. The list can mention both modules (files) -# 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() { -# # (${PN} being a directory) -# python_foreach_impl python_domodule ${PN} -# } -# @CODE -python_domodule() { - debug-print-function ${FUNCNAME} "${@}" - - [[ ${EPYTHON} ]] || die 'No Python implementation set (EPYTHON is null).' - - local d - if [[ ${_PYTHON_MODULEROOT} == /* ]]; then - # absolute path - d=${_PYTHON_MODULEROOT} - else - # relative to site-packages - local sitedir=$(python_get_sitedir) - d=${sitedir#${EPREFIX}}/${_PYTHON_MODULEROOT//.//} - fi - - if [[ ${EBUILD_PHASE} == install ]]; then - ( - insopts -m 0644 - insinto "${d}" - doins -r "${@}" || return ${?} - ) - python_optimize "${ED%/}/${d}" - elif [[ -n ${BUILD_DIR} ]]; then - local dest=${BUILD_DIR}/install${EPREFIX}/${d} - mkdir -p "${dest}" || die - cp -pR "${@}" "${dest}/" || die - ( - cd "${dest}" && - chmod -R a+rX "${@##*/}" - ) || die - else - die "${FUNCNAME} can only be used in src_install or with BUILD_DIR set" - fi -} - -# @FUNCTION: python_doheader -# @USAGE: <files>... -# @DESCRIPTION: -# Install the given headers into the implementation-specific include -# directory. This function is unconditionally recursive, i.e. you can -# pass directories instead of files. -# -# Example: -# @CODE -# src_install() { -# python_foreach_impl python_doheader foo.h bar.h -# } -# @CODE -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) - local d=${includedir#${ESYSROOT}} - - ( - insopts -m 0644 - insinto "${d}" - doins -r "${@}" || return ${?} - ) -} - -# @FUNCTION: _python_wrapper_setup -# @USAGE: [<path> [<impl>]] -# @INTERNAL -# @DESCRIPTION: -# Create proper 'python' executable and pkg-config wrappers -# (if available) in the directory named by <path>. Set up PATH -# and PKG_CONFIG_PATH appropriately. <path> defaults to ${T}/${EPYTHON}. -# -# The wrappers will be created for implementation named by <impl>, -# or for one named by ${EPYTHON} if no <impl> passed. -# -# If the named directory contains a python symlink already, it will -# be assumed to contain proper wrappers already and only environment -# setup will be done. If wrapper update is requested, the directory -# shall be removed first. -_python_wrapper_setup() { - debug-print-function ${FUNCNAME} "${@}" - - local workdir=${1:-${T}/${EPYTHON}} - local impl=${2:-${EPYTHON}} - - [[ ${workdir} ]] || die "${FUNCNAME}: no workdir specified." - [[ ${impl} ]] || die "${FUNCNAME}: no impl nor EPYTHON specified." - - if [[ ! -x ${workdir}/bin/python ]]; then - mkdir -p "${workdir}"/{bin,pkgconfig} || die - - # Clean up, in case we were supposed to do a cheap update. - rm -f "${workdir}"/bin/python{,2,3}{,-config} || die - rm -f "${workdir}"/bin/2to3 || die - rm -f "${workdir}"/pkgconfig/python{2,3}{,-embed}.pc || die - - local EPYTHON PYTHON - _python_export "${impl}" EPYTHON PYTHON - - # Python interpreter - # note: we don't use symlinks because python likes to do some - # symlink reading magic that breaks stuff - # https://bugs.gentoo.org/show_bug.cgi?id=555752 - cat > "${workdir}/bin/python" <<-_EOF_ || die - #!/bin/sh - exec "${PYTHON}" "\${@}" - _EOF_ - cp "${workdir}/bin/python" "${workdir}/bin/python3" || die - chmod +x "${workdir}/bin/python" "${workdir}/bin/python3" || die - - local nonsupp=( python2 python2-config ) - - # CPython-specific - if [[ ${EPYTHON} == python* ]]; then - cat > "${workdir}/bin/python-config" <<-_EOF_ || die - #!/bin/sh - exec "${PYTHON}-config" "\${@}" - _EOF_ - cp "${workdir}/bin/python-config" \ - "${workdir}/bin/python3-config" || die - chmod +x "${workdir}/bin/python-config" \ - "${workdir}/bin/python3-config" || die - - # Python 2.6+. - ln -s "${PYTHON/python/2to3-}" "${workdir}"/bin/2to3 || die - - # Python 2.7+. - ln -s "${EPREFIX}"/usr/$(get_libdir)/pkgconfig/${EPYTHON/n/n-}.pc \ - "${workdir}"/pkgconfig/python3.pc || die - - # Python 3.8+. - ln -s "${EPREFIX}"/usr/$(get_libdir)/pkgconfig/${EPYTHON/n/n-}-embed.pc \ - "${workdir}"/pkgconfig/python3-embed.pc || die - else - nonsupp+=( 2to3 python-config python3-config ) - fi - - local x - for x in "${nonsupp[@]}"; do - cat >"${workdir}"/bin/${x} <<-_EOF_ || die - #!/bin/sh - echo "${ECLASS}: ${FUNCNAME}: ${x} is not supported by ${EPYTHON} (PYTHON_COMPAT)" >&2 - exit 127 - _EOF_ - chmod +x "${workdir}"/bin/${x} || die - done - fi - - # Now, set the environment. - # But note that ${workdir} may be shared with something else, - # and thus already on top of PATH. - if [[ ${PATH##:*} != ${workdir}/bin ]]; then - PATH=${workdir}/bin${PATH:+:${PATH}} - fi - if [[ ${PKG_CONFIG_PATH##:*} != ${workdir}/pkgconfig ]]; then - PKG_CONFIG_PATH=${workdir}/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}} - fi - export PATH PKG_CONFIG_PATH -} - -# @FUNCTION: python_fix_shebang -# @USAGE: [-f|--force] [-q|--quiet] <path>... -# @DESCRIPTION: -# 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 (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 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 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 force quiet - while [[ ${@} ]]; do - case "${1}" in - -f|--force) force=1; shift;; - -q|--quiet) quiet=1; shift;; - --) shift; break;; - *) break;; - esac - done - - [[ ${1} ]] || die "${FUNCNAME}: no paths given" - - local path f - for path; do - local any_fixed is_recursive - - [[ -d ${path} ]] && is_recursive=1 - - while IFS= read -r -d '' f; do - local shebang i - local error= match= - - # note: we can't ||die here since read will fail if file - # has no newline characters - IFS= read -r shebang <"${f}" - - # First, check if it's shebang at all... - if [[ ${shebang} == '#!'* ]]; then - local split_shebang=() - read -r -a split_shebang <<<${shebang#"#!"} || die - - 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 - - case ${in_path##*/} in - "${EPYTHON}") - match=1 - ;; - python|python3) - match=1 - ;; - python2|python[23].[0-9]|python3.[1-9][0-9]|pypy|pypy3|jython[23].[0-9]) - # Explicit mismatch. - match=1 - error=1 - ;; - esac - fi - - # 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 - error=1 - fi - - if [[ ! ${quiet} ]]; then - einfo "Fixing shebang in ${f#${D%/}}." - fi - - if [[ ! ${error} ]]; then - debug-print "${FUNCNAME}: in file ${f#${D%/}}" - debug-print "${FUNCNAME}: rewriting shebang: ${shebang}" - sed -i -e "1s@${from}@#!${EPREFIX}/usr/bin/${EPYTHON}@" "${f}" || die - any_fixed=1 - else - eerror "The file has incompatible shebang:" - eerror " file: ${f#${D%/}}" - eerror " current shebang: ${shebang}" - eerror " requested impl: ${EPYTHON}" - die "${FUNCNAME}: conversion of incompatible shebang requested" - fi - done < <(find -H "${path}" -type f -print0 || die) - - if [[ ! ${any_fixed} ]]; then - 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 -} - -# @FUNCTION: _python_check_locale_sanity -# @USAGE: <locale> -# @RETURN: 0 if sane, 1 otherwise -# @INTERNAL -# @DESCRIPTION: -# Check whether the specified locale sanely maps between lowercase -# and uppercase ASCII characters. -_python_check_locale_sanity() { - local -x LC_ALL=${1} - local IFS= - - local lc=( {a..z} ) - local uc=( {A..Z} ) - local input="${lc[*]}${uc[*]}" - - local output=$(tr '[:lower:][:upper:]' '[:upper:][:lower:]' <<<"${input}") - [[ ${output} == "${uc[*]}${lc[*]}" ]] -} - -# @FUNCTION: python_export_utf8_locale -# @RETURN: 0 on success, 1 on failure. -# @DESCRIPTION: -# Attempts to export a usable UTF-8 locale in the LC_CTYPE variable. Does -# nothing if LC_ALL is defined, or if the current locale uses a UTF-8 charmap. -# This may be used to work around the quirky open() behavior of python3. -python_export_utf8_locale() { - debug-print-function ${FUNCNAME} "${@}" - - # If the locale program isn't available, just return. - type locale &>/dev/null || return 0 - - if [[ $(locale charmap) != UTF-8 ]]; then - # Try English first, then everything else. - local lang locales="C.UTF-8 en_US.UTF-8 en_GB.UTF-8 $(locale -a)" - - for lang in ${locales}; do - if [[ $(LC_ALL=${lang} locale charmap 2>/dev/null) == UTF-8 ]]; then - if _python_check_locale_sanity "${lang}"; then - export LC_CTYPE=${lang} - if [[ -n ${LC_ALL} ]]; then - export LC_NUMERIC=${LC_ALL} - export LC_TIME=${LC_ALL} - export LC_COLLATE=${LC_ALL} - export LC_MONETARY=${LC_ALL} - export LC_MESSAGES=${LC_ALL} - export LC_PAPER=${LC_ALL} - export LC_NAME=${LC_ALL} - export LC_ADDRESS=${LC_ALL} - export LC_TELEPHONE=${LC_ALL} - export LC_MEASUREMENT=${LC_ALL} - export LC_IDENTIFICATION=${LC_ALL} - export LC_ALL= - fi - return 0 - fi - fi - done - - ewarn "Could not find a UTF-8 locale. This may trigger build failures in" - ewarn "some python packages. Please ensure that a UTF-8 locale is listed in" - ewarn "/etc/locale.gen and run locale-gen." - return 1 - fi - - return 0 -} - -# @FUNCTION: build_sphinx -# @USAGE: <directory> -# @DESCRIPTION: -# Build HTML documentation using dev-python/sphinx in the specified -# <directory>. Takes care of disabling Intersphinx and appending -# to HTML_DOCS. -# -# If <directory> is relative to the current directory, care needs -# to be taken to run einstalldocs from the same directory -# (usually ${S}). -build_sphinx() { - debug-print-function ${FUNCNAME} "${@}" - [[ ${#} -eq 1 ]] || die "${FUNCNAME} takes 1 arg: <directory>" - - local dir=${1} - - sed -i -e 's:^intersphinx_mapping:disabled_&:' \ - "${dir}"/conf.py || 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/." ) -} - -# @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 -} - -# @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: -# 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. - -# @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. - -# @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. - -# @ECLASS_VARIABLE: EPYTEST_FLAGS -# @USER_VARIABLE -# @DEFAULT_UNSET -# @DESCRIPTION: -# Additional flags to pass to pytest. This is intended to be set -# in the environment when debugging packages (options such as -x or -s -# are useful here), rather than globally. It must not be set -# in ebuilds. - -# @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 - _python_check_occluded_packages - - local color=yes - [[ ${NO_COLOR} ]] && color=no - - 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 - # however, do error out if the package failed to load - # an appropriate async plugin - -Werror::pytest.PytestUnhandledCoroutineWarning - # override color output - "--color=${color}" - # count is more precise when we're dealing with a large number - # of tests - -o console_output_style=count - # 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}" ) - done - for x in "${EPYTEST_IGNORE[@]}"; do - args+=( --ignore "${x}" ) - done - set -- "${EPYTHON}" -m pytest "${args[@]}" "${@}" ${EPYTEST_FLAGS} - - echo "${@}" >&2 - "${@}" - local ret=${?} - - # 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 - - [[ ${ret} -ne 0 ]] && die -n "pytest failed with ${EPYTHON}" - return ${ret} -} - -# @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 - _python_check_occluded_packages - - # unittest fails with "no tests" correctly since Python 3.12 - local runner=unittest - if _python_impl_matches "${EPYTHON}" 3.{9..11}; then - runner=unittest_or_fail - fi - set -- "${EPYTHON}" -m "${runner}" discover -v "${@}" - - echo "${@}" >&2 - "${@}" || die -n "Tests failed with ${EPYTHON}" - return ${?} -} - -# @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} "${@}" - - local impl=${1} - - einfo "Checking whether ${impl} is suitable ..." - - local PYTHON_PKG_DEP - _python_export "${impl}" PYTHON_PKG_DEP - ebegin " ${PYTHON_PKG_DEP}" - has_version -b "${PYTHON_PKG_DEP}" - eend ${?} || return 1 - declare -f python_check_deps >/dev/null || return 0 - - 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 -# @USAGE: [-b|-d|-r] <atom>... -# @DESCRIPTION: -# 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 multiple package specifications. For the check -# to succeed, *all* specified atoms must match. -python_has_version() { - debug-print-function ${FUNCNAME} "${@}" - - local root_arg=( -b ) - case ${1} in - -b|-d|-r) - root_arg=( "${1}" ) - shift - ;; - esac - - local pkg - for pkg; do - ebegin " ${pkg}" - has_version "${root_arg[@]}" "${pkg}" - eend ${?} || return - done - - return 0 -} - -# @FUNCTION: _python_sanity_checks -# @INTERNAL -# @DESCRIPTION: -# Perform additional environment sanity checks. -_python_sanity_checks() { - debug-print-function ${FUNCNAME} "${@}" - - [[ ${_PYTHON_SANITY_CHECKED} ]] && return - - if [[ -v PYTHONPATH ]]; then - local x paths=() - mapfile -d ':' -t paths <<<${PYTHONPATH} - - for x in "${paths[@]}"; do - if [[ ${x} != /* ]]; then - eerror "Relative path found in PYTHONPATH:" - eerror - eerror " PYTHONPATH=${PYTHONPATH@Q}" - eerror - eerror "This is guaranteed to cause random breakage. Please make sure that" - eerror "your PYTHONPATH contains absolute paths only (and only if necessary)." - eerror "Note that empty values (including ':' at either end and an empty" - eerror "PYTHONPATH) count as the current directory. If no PYTHONPATH" - eerror "is intended, it needs to be unset instead." - die "Relative paths in PYTHONPATH are forbidden: ${x@Q}" - fi - done - - elog "PYTHONPATH=${PYTHONPATH@Q}" - fi - - _PYTHON_SANITY_CHECKED=1 -} - -fi |