eclass: Update distutils eclass

The updated app-portage/gentoolkit is using some function from
distutils-r1 that was not here before.
This commit is contained in:
Krzesimir Nowak 2020-10-28 21:40:45 +01:00
parent 5e957f27d9
commit a9e845b6ee

View File

@ -1,4 +1,4 @@
# Copyright 1999-2018 Gentoo Foundation # Copyright 1999-2020 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2 # Distributed under the terms of the GNU General Public License v2
# @ECLASS: distutils-r1.eclass # @ECLASS: distutils-r1.eclass
@ -40,8 +40,8 @@
# as well. Thus, all the variables defined and documented there are # as well. Thus, all the variables defined and documented there are
# relevant to the packages using distutils-r1. # relevant to the packages using distutils-r1.
# #
# For more information, please see the wiki: # For more information, please see the Python Guide:
# https://wiki.gentoo.org/wiki/Project:Python/distutils-r1 # https://dev.gentoo.org/~mgorny/python-guide/
case "${EAPI:-0}" in case "${EAPI:-0}" in
0|1|2|3|4) 0|1|2|3|4)
@ -77,9 +77,27 @@ esac
# to be exported. It must be run in order for the eclass functions # to be exported. It must be run in order for the eclass functions
# to function properly. # to function properly.
# @ECLASS-VARIABLE: DISTUTILS_USE_SETUPTOOLS
# @PRE_INHERIT
# @DESCRIPTION:
# Controls adding dev-python/setuptools dependency. The allowed values
# are:
#
# - no -- do not add the dependency (pure distutils package)
# - bdepend -- add it to BDEPEND (the default)
# - rdepend -- add it to BDEPEND+RDEPEND (when using entry_points)
# - pyproject.toml -- use pyproject2setuptools to install a project
# using pyproject.toml (flit, poetry...)
# - manual -- do not add the depedency and suppress the checks
# (assumes you will take care of doing it correctly)
#
# This variable is effective only if DISTUTILS_OPTIONAL is disabled.
# It needs to be set before the inherit line.
: ${DISTUTILS_USE_SETUPTOOLS:=bdepend}
if [[ ! ${_DISTUTILS_R1} ]]; then if [[ ! ${_DISTUTILS_R1} ]]; then
[[ ${EAPI} == [45] ]] && inherit eutils [[ ${EAPI} == [456] ]] && inherit eutils
[[ ${EAPI} == [56] ]] && inherit xdg-utils [[ ${EAPI} == [56] ]] && inherit xdg-utils
inherit multiprocessing toolchain-funcs inherit multiprocessing toolchain-funcs
@ -97,15 +115,46 @@ fi
if [[ ! ${_DISTUTILS_R1} ]]; then if [[ ! ${_DISTUTILS_R1} ]]; then
if [[ ! ${DISTUTILS_OPTIONAL} ]]; then _distutils_set_globals() {
RDEPEND=${PYTHON_DEPS} local rdep bdep
if [[ ${EAPI} != [56] ]]; then local setuptools_dep='>=dev-python/setuptools-42.0.2[${PYTHON_USEDEP}]'
BDEPEND=${PYTHON_DEPS}
case ${DISTUTILS_USE_SETUPTOOLS} in
no|manual)
;;
bdepend)
bdep+=" ${setuptools_dep}"
;;
rdepend)
bdep+=" ${setuptools_dep}"
rdep+=" ${setuptools_dep}"
;;
pyproject.toml)
bdep+=' dev-python/pyproject2setuppy[${PYTHON_USEDEP}]'
;;
*)
die "Invalid DISTUTILS_USE_SETUPTOOLS=${DISTUTILS_USE_SETUPTOOLS}"
;;
esac
if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then
bdep=${bdep//\$\{PYTHON_USEDEP\}/${PYTHON_USEDEP}}
rdep=${rdep//\$\{PYTHON_USEDEP\}/${PYTHON_USEDEP}}
else else
DEPEND=${PYTHON_DEPS} [[ -n ${bdep} ]] && bdep="$(python_gen_cond_dep "${bdep}")"
[[ -n ${rdep} ]] && rdep="$(python_gen_cond_dep "${rdep}")"
fi
RDEPEND="${PYTHON_DEPS} ${rdep}"
if [[ ${EAPI} != [56] ]]; then
BDEPEND="${PYTHON_DEPS} ${bdep}"
else
DEPEND="${PYTHON_DEPS} ${bdep}"
fi fi
REQUIRED_USE=${PYTHON_REQUIRED_USE} REQUIRED_USE=${PYTHON_REQUIRED_USE}
fi }
[[ ! ${DISTUTILS_OPTIONAL} ]] && _distutils_set_globals
unset -f _distutils_set_globals
# @ECLASS-VARIABLE: PATCHES # @ECLASS-VARIABLE: PATCHES
# @DEFAULT_UNSET # @DEFAULT_UNSET
@ -232,6 +281,222 @@ fi
# } # }
# @CODE # @CODE
# @FUNCTION: distutils_enable_sphinx
# @USAGE: <subdir> [--no-autodoc | <plugin-pkgs>...]
# @DESCRIPTION:
# Set up IUSE, BDEPEND, python_check_deps() and python_compile_all() for
# building HTML docs via dev-python/sphinx. python_compile_all() will
# append to HTML_DOCS if docs are enabled.
#
# This helper is meant for the most common case, that is a single Sphinx
# subdirectory with standard layout, building and installing HTML docs
# behind USE=doc. It assumes it's the only consumer of the three
# aforementioned functions. If you need to use a custom implemention,
# you can't use it.
#
# If your package uses additional Sphinx plugins, they should be passed
# (without PYTHON_USEDEP) as <plugin-pkgs>. The function will take care
# of setting appropriate any-of dep and python_check_deps().
#
# If no plugin packages are specified, the eclass will still utilize
# any-r1 API to support autodoc (documenting source code).
# If the package uses neither autodoc nor additional plugins, you should
# pass --no-autodoc to disable this API and simplify the resulting code.
#
# This function must be called in global scope. Take care not to
# overwrite the variables set by it. If you need to extend
# python_compile_all(), you can call the original implementation
# as sphinx_compile_all.
distutils_enable_sphinx() {
debug-print-function ${FUNCNAME} "${@}"
[[ ${#} -ge 1 ]] || die "${FUNCNAME} takes at least one arg: <subdir>"
_DISTUTILS_SPHINX_SUBDIR=${1}
shift
_DISTUTILS_SPHINX_PLUGINS=( "${@}" )
local deps autodoc=1 d
for d; do
if [[ ${d} == --no-autodoc ]]; then
autodoc=
else
deps+="
${d}[\${PYTHON_USEDEP}]"
fi
done
if [[ ! ${autodoc} && -n ${deps} ]]; then
die "${FUNCNAME}: do not pass --no-autodoc if external plugins are used"
fi
if [[ ${autodoc} ]]; then
deps="$(python_gen_any_dep "
dev-python/sphinx[\${PYTHON_USEDEP}]
${deps}")"
python_check_deps() {
use doc || return 0
local p
for p in dev-python/sphinx "${_DISTUTILS_SPHINX_PLUGINS[@]}"; do
has_version "${p}[${PYTHON_USEDEP}]" || return 1
done
}
else
deps="dev-python/sphinx"
fi
sphinx_compile_all() {
use doc || return
local confpy=${_DISTUTILS_SPHINX_SUBDIR}/conf.py
[[ -f ${confpy} ]] ||
die "${confpy} not found, distutils_enable_sphinx call wrong"
if [[ ${_DISTUTILS_SPHINX_PLUGINS[0]} == --no-autodoc ]]; then
if grep -F -q 'sphinx.ext.autodoc' "${confpy}"; then
die "distutils_enable_sphinx: --no-autodoc passed but sphinx.ext.autodoc found in ${confpy}"
fi
elif [[ -z ${_DISTUTILS_SPHINX_PLUGINS[@]} ]]; then
if ! grep -F -q 'sphinx.ext.autodoc' "${confpy}"; then
die "distutils_enable_sphinx: sphinx.ext.autodoc not found in ${confpy}, pass --no-autodoc"
fi
fi
build_sphinx "${_DISTUTILS_SPHINX_SUBDIR}"
}
python_compile_all() { sphinx_compile_all; }
IUSE+=" doc"
if [[ ${EAPI} == [56] ]]; then
DEPEND+=" doc? ( ${deps} )"
else
BDEPEND+=" doc? ( ${deps} )"
fi
# we need to ensure successful return in case we're called last,
# otherwise Portage may wrongly assume sourcing failed
return 0
}
# @FUNCTION: distutils_enable_tests
# @USAGE: [--install] <test-runner>
# @DESCRIPTION:
# Set up IUSE, RESTRICT, BDEPEND and python_test() for running tests
# with the specified test runner. Also copies the current value
# of RDEPEND to test?-BDEPEND. The test-runner argument must be one of:
#
# - nose: nosetests (dev-python/nose)
# - pytest: dev-python/pytest
# - setup.py: setup.py test (no deps included)
# - unittest: for built-in Python unittest module
#
# Additionally, if --install is passed as the first parameter,
# 'distutils_install_for_testing --via-root' is called before running
# the test suite.
#
# This function is meant as a helper for common use cases, and it only
# takes care of basic setup. You still need to list additional test
# dependencies manually. If you have uncommon use case, you should
# not use it and instead enable tests manually.
#
# This function must be called in global scope, after RDEPEND has been
# declared. Take care not to overwrite the variables set by it.
distutils_enable_tests() {
debug-print-function ${FUNCNAME} "${@}"
local do_install=
case ${1} in
--install)
do_install=1
shift
;;
esac
[[ ${#} -eq 1 ]] || die "${FUNCNAME} takes exactly one argument: test-runner"
local test_pkg
case ${1} in
nose)
test_pkg=">=dev-python/nose-1.3.7-r4"
if [[ ${do_install} ]]; then
python_test() {
distutils_install_for_testing --via-root
nosetests -v || die "Tests fail with ${EPYTHON}"
}
else
python_test() {
nosetests -v || die "Tests fail with ${EPYTHON}"
}
fi
;;
pytest)
test_pkg=">=dev-python/pytest-4.5.0"
if [[ ${do_install} ]]; then
python_test() {
distutils_install_for_testing --via-root
pytest -vv || die "Tests fail with ${EPYTHON}"
}
else
python_test() {
pytest -vv || die "Tests fail with ${EPYTHON}"
}
fi
;;
setup.py)
if [[ ${do_install} ]]; then
python_test() {
distutils_install_for_testing --via-root
nonfatal esetup.py test --verbose ||
die "Tests fail with ${EPYTHON}"
}
else
python_test() {
nonfatal esetup.py test --verbose ||
die "Tests fail with ${EPYTHON}"
}
fi
;;
unittest)
if [[ ${do_install} ]]; then
python_test() {
distutils_install_for_testing --via-root
"${EPYTHON}" -m unittest discover -v ||
die "Tests fail with ${EPYTHON}"
}
else
python_test() {
"${EPYTHON}" -m unittest discover -v ||
die "Tests fail with ${EPYTHON}"
}
fi
;;
*)
die "${FUNCNAME}: unsupported argument: ${1}"
esac
local test_deps=${RDEPEND}
if [[ -n ${test_pkg} ]]; then
if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then
test_deps+=" ${test_pkg}[${PYTHON_USEDEP}]"
else
test_deps+=" $(python_gen_cond_dep "
${test_pkg}[\${PYTHON_MULTI_USEDEP}]
")"
fi
fi
if [[ -n ${test_deps} ]]; then
IUSE+=" test"
RESTRICT+=" !test? ( test )"
if [[ ${EAPI} == [56] ]]; then
DEPEND+=" test? ( ${test_deps} )"
else
BDEPEND+=" test? ( ${test_deps} )"
fi
fi
# we need to ensure successful return in case we're called last,
# otherwise Portage may wrongly assume sourcing failed
return 0
}
# @FUNCTION: esetup.py # @FUNCTION: esetup.py
# @USAGE: [<args>...] # @USAGE: [<args>...]
# @DESCRIPTION: # @DESCRIPTION:
@ -269,7 +534,7 @@ esetup.py() {
} }
# @FUNCTION: distutils_install_for_testing # @FUNCTION: distutils_install_for_testing
# @USAGE: [<args>...] # @USAGE: [--via-root|--via-home] [<args>...]
# @DESCRIPTION: # @DESCRIPTION:
# Install the package into a temporary location for running tests. # Install the package into a temporary location for running tests.
# Update PYTHONPATH appropriately and set TEST_DIR to the test # Update PYTHONPATH appropriately and set TEST_DIR to the test
@ -280,11 +545,19 @@ esetup.py() {
# namespaces (and therefore proper install needs to be done to enforce # namespaces (and therefore proper install needs to be done to enforce
# PYTHONPATH) or tests rely on the results of install command. # PYTHONPATH) or tests rely on the results of install command.
# For most of the packages, tests built in BUILD_DIR are good enough. # For most of the packages, tests built in BUILD_DIR are good enough.
#
# The function supports two install modes. The current default is
# the legacy --via-home mode. However, it has problems with newer
# versions of setuptools (50.3.0+). The --via-root mode generally
# works for these packages, and it will probably become the default
# in the future, once we test all affected packages. Please note
# that proper testing sometimes requires unmerging the package first.
distutils_install_for_testing() { distutils_install_for_testing() {
debug-print-function ${FUNCNAME} "${@}" debug-print-function ${FUNCNAME} "${@}"
# A few notes: # A few notes:
# 1) because of namespaces, we can't use 'install --root', # 1) because of namespaces, we can't use 'install --root'
# (NB: this is probably no longer true with py3),
# 2) 'install --home' is terribly broken on pypy, so we need # 2) 'install --home' is terribly broken on pypy, so we need
# to override --install-lib and --install-scripts, # to override --install-lib and --install-scripts,
# 3) non-root 'install' complains about PYTHONPATH and missing dirs, # 3) non-root 'install' complains about PYTHONPATH and missing dirs,
@ -296,16 +569,42 @@ distutils_install_for_testing() {
TEST_DIR=${BUILD_DIR}/test TEST_DIR=${BUILD_DIR}/test
local bindir=${TEST_DIR}/scripts local bindir=${TEST_DIR}/scripts
local libdir=${TEST_DIR}/lib local libdir=${TEST_DIR}/lib
PATH=${bindir}:${PATH}
PYTHONPATH=${libdir}:${PYTHONPATH} PYTHONPATH=${libdir}:${PYTHONPATH}
local add_args=( local install_method=home
install case ${1} in
--home="${TEST_DIR}" --via-home)
--install-lib="${libdir}" install_method=home
--install-scripts="${bindir}" shift
) ;;
--via-root)
install_method=root
shift
;;
esac
local -a add_args
case ${install_method} in
home)
add_args=(
install
--home="${TEST_DIR}"
--install-lib="${libdir}"
--install-scripts="${bindir}"
)
mkdir -p "${libdir}" || die
;;
root)
add_args=(
install
--root="${TEST_DIR}"
--install-lib=lib
--install-scripts=scripts
)
;;
esac
mkdir -p "${libdir}" || die
esetup.py "${add_args[@]}" "${@}" esetup.py "${add_args[@]}" "${@}"
} }
@ -324,6 +623,28 @@ _distutils-r1_disable_ez_setup() {
fi fi
} }
# @FUNCTION: _distutils-r1_handle_pyproject_toml
# @INTERNAL
# @DESCRIPTION:
# Generate setup.py for pyproject.toml if requested.
_distutils-r1_handle_pyproject_toml() {
if [[ ! -f setup.py && -f pyproject.toml ]]; then
if [[ ${DISTUTILS_USE_SETUPTOOLS} == pyproject.toml ]]; then
cat > setup.py <<-EOF || die
#!/usr/bin/env python
from pyproject2setuppy.main import main
main()
EOF
chmod +x setup.py || die
else
eerror "No setup.py found but pyproject.toml is present. In order to enable"
eerror "pyproject.toml support in distutils-r1, set:"
eerror " DISTUTILS_USE_SETUPTOOLS=pyproject.toml"
die "No setup.py found and DISTUTILS_USE_SETUPTOOLS!=pyproject.toml"
fi
fi
}
# @FUNCTION: distutils-r1_python_prepare_all # @FUNCTION: distutils-r1_python_prepare_all
# @DESCRIPTION: # @DESCRIPTION:
# The default python_prepare_all(). It applies the patches from PATCHES # The default python_prepare_all(). It applies the patches from PATCHES
@ -352,6 +673,7 @@ distutils-r1_python_prepare_all() {
fi fi
_distutils-r1_disable_ez_setup _distutils-r1_disable_ez_setup
_distutils-r1_handle_pyproject_toml
if [[ ${DISTUTILS_IN_SOURCE_BUILD} && ! ${DISTUTILS_SINGLE_IMPL} ]] if [[ ${DISTUTILS_IN_SOURCE_BUILD} && ! ${DISTUTILS_SINGLE_IMPL} ]]
then then
@ -486,13 +808,11 @@ _distutils-r1_wrap_scripts() {
local path=${1} local path=${1}
local bindir=${2} local bindir=${2}
local PYTHON_SCRIPTDIR local scriptdir=$(python_get_scriptdir)
python_export PYTHON_SCRIPTDIR
local f python_files=() non_python_files=() local f python_files=() non_python_files=()
if [[ -d ${path}${PYTHON_SCRIPTDIR} ]]; then if [[ -d ${path}${scriptdir} ]]; then
for f in "${path}${PYTHON_SCRIPTDIR}"/*; do for f in "${path}${scriptdir}"/*; do
[[ -d ${f} ]] && die "Unexpected directory: ${f}" [[ -d ${f} ]] && die "Unexpected directory: ${f}"
debug-print "${FUNCNAME}: found executable at ${f#${path}/}" debug-print "${FUNCNAME}: found executable at ${f#${path}/}"
@ -547,6 +867,8 @@ distutils-r1_python_install() {
# failures if some packages haven't compiled their modules yet. # failures if some packages haven't compiled their modules yet.
addpredict "${EPREFIX}/usr/lib/${EPYTHON}" addpredict "${EPREFIX}/usr/lib/${EPYTHON}"
addpredict "${EPREFIX}/usr/$(get_libdir)/${EPYTHON}" addpredict "${EPREFIX}/usr/$(get_libdir)/${EPYTHON}"
addpredict /usr/lib/pypy2.7
addpredict /usr/lib/pypy3.6
addpredict /usr/lib/portage/pym addpredict /usr/lib/portage/pym
addpredict /usr/local # bug 498232 addpredict /usr/local # bug 498232
@ -588,9 +910,9 @@ distutils-r1_python_install() {
local root=${D%/}/_${EPYTHON} local root=${D%/}/_${EPYTHON}
[[ ${DISTUTILS_SINGLE_IMPL} ]] && root=${D%/} [[ ${DISTUTILS_SINGLE_IMPL} ]] && root=${D%/}
esetup.py install --root="${root}" "${args[@]}" esetup.py install --skip-build --root="${root}" "${args[@]}"
local forbidden_package_names=( examples test tests ) local forbidden_package_names=( examples test tests .pytest_cache )
local p local p
for p in "${forbidden_package_names[@]}"; do for p in "${forbidden_package_names[@]}"; do
if [[ -d ${root}$(python_get_sitedir)/${p} ]]; then if [[ -d ${root}$(python_get_sitedir)/${p} ]]; then
@ -607,9 +929,7 @@ distutils-r1_python_install() {
${shopt_save} ${shopt_save}
if [[ -n ${pypy_dirs} ]]; then if [[ -n ${pypy_dirs} ]]; then
local cmd=die die "Package installs 'share' in PyPy prefix, see bug #465546."
[[ ${EAPI} == [45] ]] && cmd=eqawarn
"${cmd}" "Package installs 'share' in PyPy prefix, see bug #465546."
fi fi
if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then
@ -635,8 +955,6 @@ distutils-r1_python_install_all() {
) )
docompress -x "/usr/share/doc/${PF}/examples" docompress -x "/usr/share/doc/${PF}/examples"
fi fi
_DISTUTILS_DEFAULT_CALLED=1
} }
# @FUNCTION: distutils-r1_run_phase # @FUNCTION: distutils-r1_run_phase
@ -656,13 +974,22 @@ distutils-r1_run_phase() {
debug-print-function ${FUNCNAME} "${@}" debug-print-function ${FUNCNAME} "${@}"
if [[ ${DISTUTILS_IN_SOURCE_BUILD} ]]; then if [[ ${DISTUTILS_IN_SOURCE_BUILD} ]]; then
if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then # only force BUILD_DIR if implementation is explicitly enabled
# for building; any-r1 API may select one that is not
# https://bugs.gentoo.org/701506
if [[ ! ${DISTUTILS_SINGLE_IMPL} ]] &&
has "${EPYTHON/./_}" ${PYTHON_TARGETS}; then
cd "${BUILD_DIR}" || die cd "${BUILD_DIR}" || die
fi fi
local BUILD_DIR=${BUILD_DIR}/build local BUILD_DIR=${BUILD_DIR}/build
fi fi
local -x PYTHONPATH="${BUILD_DIR}/lib:${PYTHONPATH}" local -x PYTHONPATH="${BUILD_DIR}/lib:${PYTHONPATH}"
# make PATH local for distutils_install_for_testing calls
# it makes little sense to let user modify PATH in per-impl phases
# and _all() already localizes it
local -x PATH=${PATH}
# Bug 559644 # Bug 559644
# using PYTHONPATH when the ${BUILD_DIR}/lib is not created yet might lead to # using PYTHONPATH when the ${BUILD_DIR}/lib is not created yet might lead to
# problems in setup.py scripts that try to import modules/packages from that path # problems in setup.py scripts that try to import modules/packages from that path
@ -855,21 +1182,12 @@ distutils-r1_src_install() {
_distutils-r1_run_foreach_impl distutils-r1_python_install _distutils-r1_run_foreach_impl distutils-r1_python_install
fi fi
local _DISTUTILS_DEFAULT_CALLED
if declare -f python_install_all >/dev/null; then if declare -f python_install_all >/dev/null; then
_distutils-r1_run_common_phase python_install_all _distutils-r1_run_common_phase python_install_all
else else
_distutils-r1_run_common_phase distutils-r1_python_install_all _distutils-r1_run_common_phase distutils-r1_python_install_all
fi fi
if [[ ! ${_DISTUTILS_DEFAULT_CALLED} ]]; then
local cmd=die
[[ ${EAPI} == [45] ]] && cmd=eqawarn
"${cmd}" "QA: python_install_all() didn't call distutils-r1_python_install_all"
fi
_distutils-r1_check_namespace_pth _distutils-r1_check_namespace_pth
} }