diff --git a/eclass/python-utils-r1.eclass b/eclass/python-utils-r1.eclass index 3482919cd9..ec1a3d2006 100644 --- a/eclass/python-utils-r1.eclass +++ b/eclass/python-utils-r1.eclass @@ -340,6 +340,17 @@ _python_export() { 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=$( @@ -482,6 +493,18 @@ _python_export() { done } +# @FUNCTION: python_get_stdlib +# @USAGE: [] +# @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: [] # @DESCRIPTION: @@ -1337,6 +1360,15 @@ _python_check_occluded_packages() { # 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: [...] # @DESCRIPTION: @@ -1448,10 +1480,10 @@ epytest() { for x in "${EPYTEST_IGNORE[@]}"; do args+=( --ignore "${x}" ) done - set -- "${EPYTHON}" -m pytest "${args[@]}" "${@}" + set -- "${EPYTHON}" -m pytest "${args[@]}" "${@}" ${EPYTEST_FLAGS} echo "${@}" >&2 - "${@}" || die -n "pytest failed with ${EPYTHON}" + "${@}" local ret=${?} # remove common temporary directories left over by pytest plugins @@ -1462,6 +1494,7 @@ epytest() { find "${BUILD_DIR}" -name '*-pytest-*.pyc' -delete || die fi + [[ ${ret} -ne 0 ]] && die -n "pytest failed with ${EPYTHON}" return ${ret} } @@ -1550,4 +1583,38 @@ python_has_version() { 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