Signed-off-by: Michał Górny <[email protected]> --- eclass/distutils-r2.eclass | 1198 ++++++++++++++++++++++++ eclass/python-any-r2.eclass | 357 ++++++++ eclass/python-r2.eclass | 825 +++++++++++++++++ eclass/python-single-r2.eclass | 507 +++++++++++ eclass/python-utils-r2.eclass | 1518 +++++++++++++++++++++++++++++++ eclass/tests/distutils-r2.sh | 98 ++ eclass/tests/python-utils-r2.sh | 237 +++++ 7 files changed, 4740 insertions(+) create mode 100644 eclass/distutils-r2.eclass create mode 100644 eclass/python-any-r2.eclass create mode 100644 eclass/python-r2.eclass create mode 100644 eclass/python-single-r2.eclass create mode 100644 eclass/python-utils-r2.eclass create mode 100755 eclass/tests/distutils-r2.sh create mode 100755 eclass/tests/python-utils-r2.sh
diff --git a/eclass/distutils-r2.eclass b/eclass/distutils-r2.eclass new file mode 100644 index 000000000000..662bad3b9bcd --- /dev/null +++ b/eclass/distutils-r2.eclass @@ -0,0 +1,1198 @@ +# Copyright 1999-2020 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +# @ECLASS: distutils-r2.eclass +# @MAINTAINER: +# Python team <[email protected]> +# @AUTHOR: +# Author: Michał Górny <[email protected]> +# @SUPPORTED_EAPIS: 5 6 7 +# @BLURB: A simple eclass to build Python packages using distutils. +# @DESCRIPTION: +# A simple eclass providing functions to build Python packages using +# the distutils build system. It exports phase functions for all +# the src_* phases. Each of the phases runs two pseudo-phases: +# python_..._all() (e.g. python_prepare_all()) once in ${S}, then +# python_...() (e.g. python_prepare()) for each implementation +# (see: python_foreach_impl() in python-r2). +# +# In distutils-r2_src_prepare(), the 'all' function is run before +# per-implementation ones (because it creates the implementations), +# per-implementation functions are run in a random order. +# +# In remaining phase functions, the per-implementation functions are run +# before the 'all' one, and they are ordered from the least to the most +# preferred implementation (so that 'better' files overwrite 'worse' +# ones). +# +# If the ebuild doesn't specify a particular pseudo-phase function, +# the default one will be used (distutils-r2_...). Defaults are provided +# for all per-implementation pseudo-phases, python_prepare_all() +# and python_install_all(); whenever writing your own pseudo-phase +# functions, you should consider calling the defaults (and especially +# distutils-r2_python_prepare_all). +# +# Please note that distutils-r2 sets RDEPEND and DEPEND unconditionally +# for you. +# +# Also, please note that distutils-r2 will always inherit python-r2 +# as well. Thus, all the variables defined and documented there are +# relevant to the packages using distutils-r2. + +case "${EAPI:-0}" in + 0|1|2|3|4) + die "Unsupported EAPI=${EAPI:-0} (too old) for ${ECLASS}" + ;; + 5|6|7) + ;; + *) + die "Unsupported EAPI=${EAPI} (unknown) for ${ECLASS}" + ;; +esac + +# @ECLASS-VARIABLE: DISTUTILS_OPTIONAL +# @DEFAULT_UNSET +# @DESCRIPTION: +# If set to a non-null value, distutils part in the ebuild will +# be considered optional. No dependencies will be added and no phase +# functions will be exported. +# +# If you enable DISTUTILS_OPTIONAL, you have to set proper dependencies +# for your package (using ${PYTHON_DEPS}) and to either call +# distutils-r2 default phase functions or call the build system +# manually. + +# @ECLASS-VARIABLE: DISTUTILS_SINGLE_IMPL +# @DEFAULT_UNSET +# @DESCRIPTION: +# If set to a non-null value, the ebuild will support setting a single +# Python implementation only. It will effectively replace the python-r2 +# eclass inherit with python-single-r2. +# +# Note that inheriting python-single-r2 will cause pkg_setup() +# to be exported. It must be run in order for the eclass functions +# 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_R2} ]]; then + +[[ ${EAPI} == [456] ]] && inherit eutils +[[ ${EAPI} == [56] ]] && inherit xdg-utils +inherit multiprocessing toolchain-funcs + +if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then + inherit python-r2 +else + inherit python-single-r2 +fi + +fi + +if [[ ! ${DISTUTILS_OPTIONAL} ]]; then + EXPORT_FUNCTIONS src_prepare src_configure src_compile src_test src_install +fi + +if [[ ! ${_DISTUTILS_R2} ]]; then + +_distutils_set_globals() { + local rdep=${PYTHON_DEPS} + local bdep=${rdep} + + if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then + local sdep="dev-python/setuptools[${PYTHON_USEDEP}]" + else + local sdep="$(python_gen_cond_dep ' + dev-python/setuptools[${PYTHON_MULTI_USEDEP}] + ')" + fi + + case ${DISTUTILS_USE_SETUPTOOLS} in + no|manual) + ;; + bdepend) + bdep+=" ${sdep}" + ;; + rdepend) + bdep+=" ${sdep}" + rdep+=" ${sdep}" + ;; + pyproject.toml) + bdep+=" dev-python/pyproject2setuppy[${PYTHON_USEDEP}]" + ;; + *) + die "Invalid DISTUTILS_USE_SETUPTOOLS=${DISTUTILS_USE_SETUPTOOLS}" + ;; + esac + + RDEPEND=${rdep} + if [[ ${EAPI} != [56] ]]; then + BDEPEND=${bdep} + else + DEPEND=${bdep} + fi + REQUIRED_USE=${PYTHON_REQUIRED_USE} +} +[[ ! ${DISTUTILS_OPTIONAL} ]] && _distutils_set_globals +unset -f _distutils_set_globals + +# @ECLASS-VARIABLE: PATCHES +# @DEFAULT_UNSET +# @DESCRIPTION: +# An array containing patches to be applied to the sources before +# copying them. +# +# If unset, no custom patches will be applied. +# +# Please note, however, that at some point the eclass may apply +# additional distutils patches/quirks independently of this variable. +# +# Example: +# @CODE +# PATCHES=( "${FILESDIR}"/${P}-make-gentoo-happy.patch ) +# @CODE + +# @ECLASS-VARIABLE: DOCS +# @DEFAULT_UNSET +# @DESCRIPTION: +# An array containing documents installed using dodoc. The files listed +# there must exist in the directory from which +# distutils-r2_python_install_all() is run (${S} by default). +# +# If unset, the function will instead look up files matching default +# filename pattern list (from the Package Manager Specification), +# and install those found. +# +# Example: +# @CODE +# DOCS=( NEWS README ) +# @CODE + +# @ECLASS-VARIABLE: HTML_DOCS +# @DEFAULT_UNSET +# @DESCRIPTION: +# An array containing documents installed using dohtml. The files +# and directories listed there must exist in the directory from which +# distutils-r2_python_install_all() is run (${S} by default). +# +# If unset, no HTML docs will be installed. +# +# Example: +# @CODE +# HTML_DOCS=( doc/html/. ) +# @CODE + +# @ECLASS-VARIABLE: EXAMPLES +# @DEFAULT_UNSET +# @DESCRIPTION: +# OBSOLETE: this variable is deprecated and banned in EAPI 6 +# +# An array containing examples installed into 'examples' doc +# subdirectory. The files and directories listed there must exist +# in the directory from which distutils-r2_python_install_all() is run +# (${S} by default). +# +# The 'examples' subdirectory will be marked not to be compressed +# automatically. +# +# If unset, no examples will be installed. +# +# Example: +# @CODE +# EXAMPLES=( examples/. demos/. ) +# @CODE + +# @ECLASS-VARIABLE: DISTUTILS_IN_SOURCE_BUILD +# @DEFAULT_UNSET +# @DESCRIPTION: +# If set to a non-null value, in-source builds will be enabled. +# If unset, the default is to use in-source builds when python_prepare() +# is declared, and out-of-source builds otherwise. +# +# If in-source builds are used, the eclass will create a copy of package +# sources for each Python implementation in python_prepare_all(), +# and work on that copy afterwards. +# +# If out-of-source builds are used, the eclass will instead work +# on the sources directly, prepending setup.py arguments with +# 'build --build-base ${BUILD_DIR}' to enforce keeping & using built +# files in the specific root. + +# @ECLASS-VARIABLE: DISTUTILS_ALL_SUBPHASE_IMPLS +# @DEFAULT_UNSET +# @DESCRIPTION: +# An array of patterns specifying which implementations can be used +# for *_all() sub-phase functions. If undefined, defaults to '*' +# (allowing any implementation). If multiple values are specified, +# implementations matching any of the patterns will be accepted. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# If the restriction needs to apply conditionally to a USE flag, +# the variable should be set conditionally as well (e.g. in an early +# phase function or other convenient location). +# +# Please remember to add a matching || block to REQUIRED_USE, +# to ensure that at least one implementation matching the patterns will +# be enabled. +# +# Example: +# @CODE +# REQUIRED_USE="doc? ( || ( $(python_gen_useflags 'python2*') ) )" +# +# pkg_setup() { +# use doc && DISTUTILS_ALL_SUBPHASE_IMPLS=( 'python2*' ) +# } +# @CODE + +# @ECLASS-VARIABLE: mydistutilsargs +# @DEFAULT_UNSET +# @DESCRIPTION: +# An array containing options to be passed to setup.py. +# +# Example: +# @CODE +# python_configure_all() { +# mydistutilsargs=( --enable-my-hidden-option ) +# } +# @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-r2 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 + else + 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: <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 +# +# 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} "${@}" + [[ ${#} -eq 1 ]] || die "${FUNCNAME} takes exactly one argument: test-runner" + + local test_pkg + case ${1} in + nose) + test_pkg="dev-python/nose" + python_test() { + nosetests -v || die "Tests fail with ${EPYTHON}" + } + ;; + pytest) + test_pkg="dev-python/pytest" + python_test() { + pytest -vv || die "Tests fail with ${EPYTHON}" + } + ;; + setup.py) + python_test() { + esetup.py test --verbose + } + ;; + unittest) + python_test() { + "${EPYTHON}" -m unittest discover -v || + die "Tests fail with ${EPYTHON}" + } + ;; + *) + 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: _distutils-r2_verify_use_setuptools +# @INTERNAL +# @DESCRIPTION: +# Check setup.py for signs that DISTUTILS_USE_SETUPTOOLS have been set +# incorrectly. +_distutils_verify_use_setuptools() { + [[ ${DISTUTILS_OPTIONAL} ]] && return + [[ ${DISTUTILS_USE_SETUPTOOLS} == manual ]] && return + [[ ${DISTUTILS_USE_SETUPTOOLS} == pyproject.toml ]] && return + + # ok, those are cheap greps. we can try toimprove them if we hit + # false positives. + local expected=no + if [[ ${CATEGORY}/${PN} == dev-python/setuptools ]]; then + # as a special case, setuptools provides itself ;-) + : + elif grep -E -q -s '(from|import)\s+setuptools' setup.py; then + if grep -E -q -s 'entry_points\s*=' setup.py; then + expected=rdepend + elif grep -F -q -s '[options.entry_points]' setup.cfg; then + expected=rdepend + else + expected=bdepend + fi + fi + + if [[ ${DISTUTILS_USE_SETUPTOOLS} != ${expected} ]]; then + if [[ ! ${_DISTUTILS_SETUPTOOLS_WARNED} ]]; then + _DISTUTILS_SETUPTOOLS_WARNED=1 + local def= + [[ ${DISTUTILS_USE_SETUPTOOLS} == bdepend ]] && def=' (default?)' + + eqawarn "DISTUTILS_USE_SETUPTOOLS value is probably incorrect" + eqawarn " value: DISTUTILS_USE_SETUPTOOLS=${DISTUTILS_USE_SETUPTOOLS}${def}" + eqawarn " expected: DISTUTILS_USE_SETUPTOOLS=${expected}" + fi + fi +} + +# @FUNCTION: esetup.py +# @USAGE: [<args>...] +# @DESCRIPTION: +# Run setup.py using currently selected Python interpreter +# (if ${EPYTHON} is set; fallback 'python' otherwise). +# +# setup.py will be passed the following, in order: +# 1. ${mydistutilsargs[@]} +# 2. additional arguments passed to the esetup.py function. +# +# Please note that setup.py will respect defaults (unless overridden +# via command-line options) from setup.cfg that is created +# in distutils-r2_python_compile and in distutils-r2_python_install. +# +# This command dies on failure. +esetup.py() { + debug-print-function ${FUNCNAME} "${@}" + + local die_args=() + [[ ${EAPI} != [45] ]] && die_args+=( -n ) + + [[ ${BUILD_DIR} ]] && _distutils-r2_create_setup_cfg + _distutils_verify_use_setuptools + + set -- "${EPYTHON:-python}" setup.py "${mydistutilsargs[@]}" "${@}" + + echo "${@}" >&2 + "${@}" || die "${die_args[@]}" + local ret=${?} + + if [[ ${BUILD_DIR} ]]; then + rm "${HOME}"/.pydistutils.cfg || die "${die_args[@]}" + fi + + return ${ret} +} + +# @FUNCTION: distutils_install_for_testing +# @USAGE: [<args>...] +# @DESCRIPTION: +# Install the package into a temporary location for running tests. +# Update PYTHONPATH appropriately and set TEST_DIR to the test +# installation root. The Python packages will be installed in 'lib' +# subdir, and scripts in 'scripts' subdir (like in BUILD_DIR). +# +# Please note that this function should be only used if package uses +# namespaces (and therefore proper install needs to be done to enforce +# PYTHONPATH) or tests rely on the results of install command. +# For most of the packages, tests built in BUILD_DIR are good enough. +distutils_install_for_testing() { + debug-print-function ${FUNCNAME} "${@}" + + # A few notes: + # 1) because of namespaces, we can't use 'install --root', + # 2) 'install --home' is terribly broken on pypy, so we need + # to override --install-lib and --install-scripts, + # 3) non-root 'install' complains about PYTHONPATH and missing dirs, + # so we need to set it properly and mkdir them, + # 4) it runs a bunch of commands which write random files to cwd, + # in order to avoid that, we add the necessary path overrides + # in _distutils-r2_create_setup_cfg. + + TEST_DIR=${BUILD_DIR}/test + local bindir=${TEST_DIR}/scripts + local libdir=${TEST_DIR}/lib + PYTHONPATH=${libdir}:${PYTHONPATH} + + local add_args=( + install + --home="${TEST_DIR}" + --install-lib="${libdir}" + --install-scripts="${bindir}" + ) + + mkdir -p "${libdir}" || die + esetup.py "${add_args[@]}" "${@}" +} + +# @FUNCTION: _distutils-r2_disable_ez_setup +# @INTERNAL +# @DESCRIPTION: +# Stub out ez_setup.py and distribute_setup.py to prevent packages +# from trying to download a local copy of setuptools. +_distutils-r2_disable_ez_setup() { + local stub="def use_setuptools(*args, **kwargs): pass" + if [[ -f ez_setup.py ]]; then + echo "${stub}" > ez_setup.py || die + fi + if [[ -f distribute_setup.py ]]; then + echo "${stub}" > distribute_setup.py || die + fi +} + +# @FUNCTION: _distutils-r2_handle_pyproject_toml +# @INTERNAL +# @DESCRIPTION: +# Generate setup.py for pyproject.toml if requested. +_distutils-r2_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-r2, set:" + eerror " DISTUTILS_USE_SETUPTOOLS=pyproject.toml" + die "No setup.py found and DISTUTILS_USE_SETUPTOOLS!=pyproject.toml" + fi + fi +} + +# @FUNCTION: distutils-r2_python_prepare_all +# @DESCRIPTION: +# The default python_prepare_all(). It applies the patches from PATCHES +# array, then user patches and finally calls python_copy_sources to +# create copies of resulting sources for each Python implementation. +# +# At some point in the future, it may also apply eclass-specific +# distutils patches and/or quirks. +distutils-r2_python_prepare_all() { + debug-print-function ${FUNCNAME} "${@}" + + if [[ ! ${DISTUTILS_OPTIONAL} ]]; then + if [[ ${EAPI} != [45] ]]; then + default + else + [[ ${PATCHES} ]] && epatch "${PATCHES[@]}" + epatch_user + fi + fi + + # by default, use in-source build if python_prepare() is used + if [[ ! ${DISTUTILS_IN_SOURCE_BUILD+1} ]]; then + if declare -f python_prepare >/dev/null; then + DISTUTILS_IN_SOURCE_BUILD=1 + fi + fi + + _distutils-r2_disable_ez_setup + _distutils-r2_handle_pyproject_toml + + if [[ ${DISTUTILS_IN_SOURCE_BUILD} && ! ${DISTUTILS_SINGLE_IMPL} ]] + then + # create source copies for each implementation + python_copy_sources + fi + + _DISTUTILS_DEFAULT_CALLED=1 +} + +# @FUNCTION: distutils-r2_python_prepare +# @DESCRIPTION: +# The default python_prepare(). A no-op. +distutils-r2_python_prepare() { + debug-print-function ${FUNCNAME} "${@}" + + [[ ${EAPI} == [45] ]] || die "${FUNCNAME} is banned in EAPI 6 (it was a no-op)" +} + +# @FUNCTION: distutils-r2_python_configure +# @DESCRIPTION: +# The default python_configure(). A no-op. +distutils-r2_python_configure() { + debug-print-function ${FUNCNAME} "${@}" + + [[ ${EAPI} == [45] ]] || die "${FUNCNAME} is banned in EAPI 6 (it was a no-op)" +} + +# @FUNCTION: _distutils-r2_create_setup_cfg +# @INTERNAL +# @DESCRIPTION: +# Create implementation-specific configuration file for distutils, +# setting proper build-dir (and install-dir) paths. +_distutils-r2_create_setup_cfg() { + cat > "${HOME}"/.pydistutils.cfg <<-_EOF_ || die + [build] + build-base = ${BUILD_DIR} + + # using a single directory for them helps us export + # ${PYTHONPATH} and ebuilds find the sources independently + # of whether the package installs extensions or not + # + # note: due to some packages (wxpython) relying on separate + # platlib & purelib dirs, we do not set --build-lib (which + # can not be overridden with --build-*lib) + build-platlib = %(build-base)s/lib + build-purelib = %(build-base)s/lib + + # make the ebuild writer lives easier + build-scripts = %(build-base)s/scripts + + # this is needed by distutils_install_for_testing since + # setuptools like to create .egg files for install --home. + [bdist_egg] + dist-dir = ${BUILD_DIR}/dist + _EOF_ + + # we can't refer to ${D} before src_install() + if [[ ${EBUILD_PHASE} == install ]]; then + cat >> "${HOME}"/.pydistutils.cfg <<-_EOF_ || die + + # installation paths -- allow calling extra install targets + # without the default 'install' + [install] + compile = True + optimize = 2 + root = ${D%/} + _EOF_ + + if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then + cat >> "${HOME}"/.pydistutils.cfg <<-_EOF_ || die + install-scripts = $(python_get_scriptdir) + _EOF_ + fi + fi +} + +# @FUNCTION: _distutils-r2_copy_egg_info +# @INTERNAL +# @DESCRIPTION: +# Copy egg-info files to the ${BUILD_DIR} (that's going to become +# egg-base in esetup.py). This way, we respect whatever's in upstream +# egg-info. +_distutils-r2_copy_egg_info() { + mkdir -p "${BUILD_DIR}" || die + # stupid freebsd can't do 'cp -t ${BUILD_DIR} {} +' + find -name '*.egg-info' -type d -exec cp -R -p {} "${BUILD_DIR}"/ ';' || die +} + +# @FUNCTION: distutils-r2_python_compile +# @USAGE: [additional-args...] +# @DESCRIPTION: +# The default python_compile(). Runs 'esetup.py build'. Any parameters +# passed to this function will be appended to setup.py invocation, +# i.e. passed as options to the 'build' command. +# +# This phase also sets up initial setup.cfg with build directories +# and copies upstream egg-info files if supplied. +distutils-r2_python_compile() { + debug-print-function ${FUNCNAME} "${@}" + + _distutils-r2_copy_egg_info + + local build_args=() + # distutils is parallel-capable since py3.5 + # to avoid breaking stable ebuilds, enable it only if either: + # a. we're dealing with EAPI 7 + # b. we're dealing with Python 3.7 or PyPy3 + if python_is_python3 && [[ ${EPYTHON} != python3.4 ]]; then + if [[ ${EAPI} != [56] || ${EPYTHON} != python3.[56] ]]; then + local jobs=$(makeopts_jobs "${MAKEOPTS}" INF) + if [[ ${jobs} == INF ]]; then + local nproc=$(get_nproc) + jobs=$(( nproc + 1 )) + fi + build_args+=( -j "${jobs}" ) + fi + fi + + esetup.py build "${build_args[@]}" "${@}" +} + +# @FUNCTION: _distutils-r2_wrap_scripts +# @USAGE: <path> <bindir> +# @INTERNAL +# @DESCRIPTION: +# Moves and wraps all installed scripts/executables as necessary. +_distutils-r2_wrap_scripts() { + debug-print-function ${FUNCNAME} "${@}" + + [[ ${#} -eq 2 ]] || die "usage: ${FUNCNAME} <path> <bindir>" + local path=${1} + local bindir=${2} + + local PYTHON_SCRIPTDIR + python_export PYTHON_SCRIPTDIR + + local f python_files=() non_python_files=() + + if [[ -d ${path}${PYTHON_SCRIPTDIR} ]]; then + for f in "${path}${PYTHON_SCRIPTDIR}"/*; do + [[ -d ${f} ]] && die "Unexpected directory: ${f}" + debug-print "${FUNCNAME}: found executable at ${f#${path}/}" + + local shebang + read -r shebang < "${f}" + if [[ ${shebang} == '#!'*${EPYTHON}* ]]; then + debug-print "${FUNCNAME}: matching shebang: ${shebang}" + python_files+=( "${f}" ) + else + debug-print "${FUNCNAME}: non-matching shebang: ${shebang}" + non_python_files+=( "${f}" ) + fi + + mkdir -p "${path}${bindir}" || die + done + + for f in "${python_files[@]}"; do + local basename=${f##*/} + + debug-print "${FUNCNAME}: installing wrapper at ${bindir}/${basename}" + _python_ln_rel "${path}${EPREFIX}"/usr/lib/python-exec/python-exec2 \ + "${path}${bindir}/${basename}" || die + done + + for f in "${non_python_files[@]}"; do + local basename=${f##*/} + + debug-print "${FUNCNAME}: moving ${f#${path}/} to ${bindir}/${basename}" + mv "${f}" "${path}${bindir}/${basename}" || die + done + fi +} + +# @FUNCTION: distutils-r2_python_install +# @USAGE: [additional-args...] +# @DESCRIPTION: +# The default python_install(). Runs 'esetup.py install', doing +# intermediate root install and handling script wrapping afterwards. +# Any parameters passed to this function will be appended +# to the setup.py invocation (i.e. as options to the 'install' command). +# +# This phase updates the setup.cfg file with install directories. +distutils-r2_python_install() { + debug-print-function ${FUNCNAME} "${@}" + + local args=( "${@}" ) + + # enable compilation for the install phase. + local -x PYTHONDONTWRITEBYTECODE= + + # python likes to compile any module it sees, which triggers sandbox + # failures if some packages haven't compiled their modules yet. + addpredict "${EPREFIX}/usr/lib/${EPYTHON}" + addpredict "${EPREFIX}/usr/$(get_libdir)/${EPYTHON}" + addpredict /usr/lib/pypy2.7 + addpredict /usr/lib/pypy3.6 + addpredict /usr/lib/portage/pym + addpredict /usr/local # bug 498232 + + if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then + # user may override --install-scripts + # note: this is poor but distutils argv parsing is dumb + local mydistutilsargs=( "${mydistutilsargs[@]}" ) + local scriptdir=${EPREFIX}/usr/bin + + # construct a list of mydistutilsargs[0] args[0] args[1]... + local arg arg_vars + [[ ${mydistutilsargs[@]} ]] && eval arg_vars+=( + 'mydistutilsargs['{0..$(( ${#mydistutilsargs[@]} - 1 ))}']' + ) + [[ ${args[@]} ]] && eval arg_vars+=( + 'args['{0..$(( ${#args[@]} - 1 ))}']' + ) + + set -- "${arg_vars[@]}" + while [[ ${@} ]]; do + local arg_var=${1} + shift + local a=${!arg_var} + + case "${a}" in + --install-scripts=*) + scriptdir=${a#--install-scripts=} + unset "${arg_var}" + ;; + --install-scripts) + scriptdir=${!1} + unset "${arg_var}" "${1}" + shift + ;; + esac + done + fi + + local root=${D%/}/_${EPYTHON} + [[ ${DISTUTILS_SINGLE_IMPL} ]] && root=${D%/} + + esetup.py install --root="${root}" "${args[@]}" + + local forbidden_package_names=( examples test tests .pytest_cache ) + local p + for p in "${forbidden_package_names[@]}"; do + if [[ -d ${root}$(python_get_sitedir)/${p} ]]; then + die "Package installs '${p}' package which is forbidden and likely a bug in the build system." + fi + done + + local shopt_save=$(shopt -p nullglob) + shopt -s nullglob + local pypy_dirs=( + "${root}/usr/$(get_libdir)"/pypy*/share + "${root}/usr/lib"/pypy*/share + ) + ${shopt_save} + + if [[ -n ${pypy_dirs} ]]; then + local cmd=die + [[ ${EAPI} == [45] ]] && cmd=eqawarn + "${cmd}" "Package installs 'share' in PyPy prefix, see bug #465546." + fi + + if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then + _distutils-r2_wrap_scripts "${root}" "${scriptdir}" + multibuild_merge_root "${root}" "${D%/}" + fi +} + +# @FUNCTION: distutils-r2_python_install_all +# @DESCRIPTION: +# The default python_install_all(). It installs the documentation. +distutils-r2_python_install_all() { + debug-print-function ${FUNCNAME} "${@}" + + einstalldocs + + if declare -p EXAMPLES &>/dev/null; then + [[ ${EAPI} != [45] ]] && die "EXAMPLES are banned in EAPI ${EAPI}" + + ( + docinto examples + dodoc -r "${EXAMPLES[@]}" + ) + docompress -x "/usr/share/doc/${PF}/examples" + fi + + _DISTUTILS_DEFAULT_CALLED=1 +} + +# @FUNCTION: distutils-r2_run_phase +# @USAGE: [<argv>...] +# @INTERNAL +# @DESCRIPTION: +# Run the given command. +# +# If out-of-source builds are used, the phase function is run in source +# directory, with BUILD_DIR pointing at the build directory +# and PYTHONPATH having an entry for the module build directory. +# +# If in-source builds are used, the command is executed in the directory +# holding the per-implementation copy of sources. BUILD_DIR points +# to the 'build' subdirectory. +distutils-r2_run_phase() { + debug-print-function ${FUNCNAME} "${@}" + + if [[ ${DISTUTILS_IN_SOURCE_BUILD} ]]; then + # only force BUILD_DIR if implementation is explicitly enabled + # for building; any-r2 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 + fi + local BUILD_DIR=${BUILD_DIR}/build + fi + local -x PYTHONPATH="${BUILD_DIR}/lib:${PYTHONPATH}" + + # Bug 559644 + # 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 + # during the build process (Python at startup evaluates PYTHONPATH, if the dir is + # not valid then associates a NullImporter object to ${BUILD_DIR}/lib storing it + # in the sys.path_importer_cache) + mkdir -p "${BUILD_DIR}/lib" || die + + # Set up build environment, bug #513664. + local -x AR=${AR} CC=${CC} CPP=${CPP} CXX=${CXX} + tc-export AR CC CPP CXX + + # How to build Python modules in different worlds... + local ldopts + case "${CHOST}" in + # provided by haubi, 2014-07-08 + *-aix*) ldopts='-shared -Wl,-berok';; # good enough + # provided by grobian, 2014-06-22, bug #513664 c7 + *-darwin*) ldopts='-bundle -undefined dynamic_lookup';; + *) ldopts='-shared';; + esac + + local -x LDSHARED="${CC} ${ldopts}" LDCXXSHARED="${CXX} ${ldopts}" + + "${@}" + + cd "${_DISTUTILS_INITIAL_CWD}" || die +} + +# @FUNCTION: _distutils-r2_run_common_phase +# @USAGE: [<argv>...] +# @INTERNAL +# @DESCRIPTION: +# Run the given command, restoring the state for a most preferred Python +# implementation matching DISTUTILS_ALL_SUBPHASE_IMPLS. +# +# If in-source build is used, the command will be run in the copy +# of sources made for the selected Python interpreter. +_distutils-r2_run_common_phase() { + local DISTUTILS_ORIG_BUILD_DIR=${BUILD_DIR} + + if [[ ${DISTUTILS_SINGLE_IMPL} ]]; then + # reuse the dedicated code branch + _distutils-r2_run_foreach_impl "${@}" + else + local -x EPYTHON PYTHON + local -x PATH=${PATH} PKG_CONFIG_PATH=${PKG_CONFIG_PATH} + python_setup "${DISTUTILS_ALL_SUBPHASE_IMPLS[@]}" + + local MULTIBUILD_VARIANTS=( "${EPYTHON/./_}" ) + # store for restoring after distutils-r2_run_phase. + local _DISTUTILS_INITIAL_CWD=${PWD} + multibuild_foreach_variant \ + distutils-r2_run_phase "${@}" + fi +} + +# @FUNCTION: _distutils-r2_run_foreach_impl +# @INTERNAL +# @DESCRIPTION: +# Run the given phase for each implementation if multiple implementations +# are enabled, once otherwise. +_distutils-r2_run_foreach_impl() { + debug-print-function ${FUNCNAME} "${@}" + + # store for restoring after distutils-r2_run_phase. + local _DISTUTILS_INITIAL_CWD=${PWD} + set -- distutils-r2_run_phase "${@}" + + if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then + python_foreach_impl "${@}" + else + if [[ ! ${EPYTHON} ]]; then + die "EPYTHON unset, python-single-r2_pkg_setup not called?!" + fi + local BUILD_DIR=${BUILD_DIR:-${S}} + BUILD_DIR=${BUILD_DIR%%/}_${EPYTHON} + + "${@}" + fi +} + +distutils-r2_src_prepare() { + debug-print-function ${FUNCNAME} "${@}" + + local _DISTUTILS_DEFAULT_CALLED + + # common preparations + if declare -f python_prepare_all >/dev/null; then + python_prepare_all + else + distutils-r2_python_prepare_all + fi + + if [[ ! ${_DISTUTILS_DEFAULT_CALLED} ]]; then + local cmd=die + [[ ${EAPI} == [45] ]] && cmd=eqawarn + + "${cmd}" "QA: python_prepare_all() didn't call distutils-r2_python_prepare_all" + fi + + if declare -f python_prepare >/dev/null; then + _distutils-r2_run_foreach_impl python_prepare + fi +} + +distutils-r2_src_configure() { + python_export_utf8_locale + [[ ${EAPI} == [56] ]] && xdg_environment_reset # Bug 577704 + + if declare -f python_configure >/dev/null; then + _distutils-r2_run_foreach_impl python_configure + fi + + if declare -f python_configure_all >/dev/null; then + _distutils-r2_run_common_phase python_configure_all + fi +} + +distutils-r2_src_compile() { + debug-print-function ${FUNCNAME} "${@}" + + if declare -f python_compile >/dev/null; then + _distutils-r2_run_foreach_impl python_compile + else + _distutils-r2_run_foreach_impl distutils-r2_python_compile + fi + + if declare -f python_compile_all >/dev/null; then + _distutils-r2_run_common_phase python_compile_all + fi +} + +# @FUNCTION: _distutils-r2_clean_egg_info +# @INTERNAL +# @DESCRIPTION: +# Clean up potential stray egg-info files left by setuptools test phase. +# Those files ended up being unversioned, and caused issues: +# https://bugs.gentoo.org/534058 +_distutils-r2_clean_egg_info() { + rm -rf "${BUILD_DIR}"/lib/*.egg-info || die +} + +distutils-r2_src_test() { + debug-print-function ${FUNCNAME} "${@}" + + if declare -f python_test >/dev/null; then + _distutils-r2_run_foreach_impl python_test + _distutils-r2_run_foreach_impl _distutils-r2_clean_egg_info + fi + + if declare -f python_test_all >/dev/null; then + _distutils-r2_run_common_phase python_test_all + fi +} + +# @FUNCTION: _distutils-r2_check_namespace_pth +# @INTERNAL +# @DESCRIPTION: +# Check if any *-nspkg.pth files were installed (by setuptools) +# and warn about the policy non-conformance if they were. +_distutils-r2_check_namespace_pth() { + local f pth=() + + while IFS= read -r -d '' f; do + pth+=( "${f}" ) + done < <(find "${ED%/}" -name '*-nspkg.pth' -print0) + + if [[ ${pth[@]} ]]; then + ewarn "The following *-nspkg.pth files were found installed:" + ewarn + for f in "${pth[@]}"; do + ewarn " ${f#${ED%/}}" + done + ewarn + ewarn "The presence of those files may break namespaces in Python 3.5+. Please" + ewarn "read our documentation on reliable handling of namespaces and update" + ewarn "the ebuild accordingly:" + ewarn + ewarn " https://wiki.gentoo.org/wiki/Project:Python/Namespace_packages" + fi +} + +distutils-r2_src_install() { + debug-print-function ${FUNCNAME} "${@}" + + if declare -f python_install >/dev/null; then + _distutils-r2_run_foreach_impl python_install + else + _distutils-r2_run_foreach_impl distutils-r2_python_install + fi + + local _DISTUTILS_DEFAULT_CALLED + + if declare -f python_install_all >/dev/null; then + _distutils-r2_run_common_phase python_install_all + else + _distutils-r2_run_common_phase distutils-r2_python_install_all + fi + + if [[ ! ${_DISTUTILS_DEFAULT_CALLED} ]]; then + local cmd=die + [[ ${EAPI} == [45] ]] && cmd=eqawarn + + "${cmd}" "QA: python_install_all() didn't call distutils-r2_python_install_all" + fi + + _distutils-r2_check_namespace_pth +} + +# -- distutils.eclass functions -- + +distutils_get_intermediate_installation_image() { + die "${FUNCNAME}() is invalid for distutils-r2" +} + +distutils_src_unpack() { + die "${FUNCNAME}() is invalid for distutils-r2, and you don't want it in EAPI ${EAPI} anyway" +} + +distutils_src_prepare() { + die "${FUNCNAME}() is invalid for distutils-r2, you probably want: ${FUNCNAME/_/-r2_}" +} + +distutils_src_compile() { + die "${FUNCNAME}() is invalid for distutils-r2, you probably want: ${FUNCNAME/_/-r2_}" +} + +distutils_src_test() { + die "${FUNCNAME}() is invalid for distutils-r2, you probably want: ${FUNCNAME/_/-r2_}" +} + +distutils_src_install() { + die "${FUNCNAME}() is invalid for distutils-r2, you probably want: ${FUNCNAME/_/-r2_}" +} + +distutils_pkg_postinst() { + die "${FUNCNAME}() is invalid for distutils-r2, and pkg_postinst is unnecessary" +} + +distutils_pkg_postrm() { + die "${FUNCNAME}() is invalid for distutils-r2, and pkg_postrm is unnecessary" +} + +_DISTUTILS_R2=1 +fi diff --git a/eclass/python-any-r2.eclass b/eclass/python-any-r2.eclass new file mode 100644 index 000000000000..cf70f23f69d5 --- /dev/null +++ b/eclass/python-any-r2.eclass @@ -0,0 +1,357 @@ +# Copyright 1999-2020 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +# @ECLASS: python-any-r2.eclass +# @MAINTAINER: +# Python team <[email protected]> +# @AUTHOR: +# Author: Michał Górny <[email protected]> +# @SUPPORTED_EAPIS: 5 6 7 +# @BLURB: An eclass for packages having build-time dependency on Python. +# @DESCRIPTION: +# A minimal eclass for packages which need any Python interpreter +# installed without a need for explicit choice and invariability. +# This usually involves packages requiring Python at build-time +# but having no other relevance to it. +# +# This eclass provides a minimal PYTHON_DEPS variable with a dependency +# string on any of the supported Python implementations. It also exports +# pkg_setup() which finds the best supported implementation and sets it +# as the active one. +# +# Optionally, you can define a python_check_deps() function. It will +# be called by the eclass with EPYTHON set to each matching Python +# implementation and it is expected to check whether the implementation +# fulfills the package requirements. You can use the locally exported +# PYTHON_USEDEP to check USE-dependencies of relevant packages. It +# should return a true value (0) if the Python implementation fulfills +# the requirements, a false value (non-zero) otherwise. +# +# Please note that python-any-r2 will always inherit python-utils-r2 +# as well. Thus, all the functions defined there can be used in the +# packages using python-any-r2, and there is no need ever to inherit +# both. + +case "${EAPI:-0}" in + [0-4]) die "Unsupported EAPI=${EAPI:-0} (too old) for ${ECLASS}" ;; + [5-7]) ;; + *) die "Unsupported EAPI=${EAPI} (unknown) for ${ECLASS}" ;; +esac + +if [[ ! ${_PYTHON_ANY_R2} ]]; then + +if [[ ${_PYTHON_R2} ]]; then + die 'python-any-r2.eclass can not be used with python-r2.eclass.' +elif [[ ${_PYTHON_SINGLE_R2} ]]; then + die 'python-any-r2.eclass can not be used with python-single-r2.eclass.' +fi + +inherit python-utils-r2 + +fi + +EXPORT_FUNCTIONS pkg_setup + +# @ECLASS-VARIABLE: PYTHON_COMPAT +# @REQUIRED +# @DESCRIPTION: +# This variable contains a list of Python implementations the package +# supports. It must be set before the `inherit' call. It has to be +# an array. +# +# Example: +# @CODE +# PYTHON_COMPAT=( python{2_5,2_6,2_7} ) +# @CODE + +# @ECLASS-VARIABLE: PYTHON_COMPAT_OVERRIDE +# @INTERNAL +# @DESCRIPTION: +# This variable can be used when working with ebuilds to override +# the in-ebuild PYTHON_COMPAT. It is a string naming the implementation +# which will be used to build the package. It needs to be specified +# in the calling environment, and not in ebuilds. +# +# It should be noted that in order to preserve metadata immutability, +# PYTHON_COMPAT_OVERRIDE does not affect dependencies. The value of +# EPYTHON and eselect-python preferences are ignored. Dependencies need +# to be satisfied manually. +# +# Example: +# @CODE +# PYTHON_COMPAT_OVERRIDE='pypy' emerge -1v dev-python/bar +# @CODE + +# @ECLASS-VARIABLE: PYTHON_REQ_USE +# @DEFAULT_UNSET +# @DESCRIPTION: +# The list of USEflags required to be enabled on the Python +# implementations, formed as a USE-dependency string. It should be valid +# for all implementations in PYTHON_COMPAT, so it may be necessary to +# use USE defaults. +# +# Example: +# @CODE +# PYTHON_REQ_USE="gdbm,ncurses(-)?" +# @CODE +# +# It will cause the Python dependencies to look like: +# @CODE +# || ( dev-lang/python:X.Y[gdbm,ncurses(-)?] ... ) +# @CODE + +# @ECLASS-VARIABLE: PYTHON_DEPS +# @DESCRIPTION: +# This is an eclass-generated Python dependency string for all +# implementations listed in PYTHON_COMPAT. +# +# Any of the supported interpreters will satisfy the dependency. +# +# Example use: +# @CODE +# DEPEND="${RDEPEND} +# ${PYTHON_DEPS}" +# @CODE +# +# Example value: +# @CODE +# || ( dev-lang/python:2.7[gdbm] +# dev-lang/python:2.6[gdbm] ) +# @CODE + +# @ECLASS-VARIABLE: PYTHON_USEDEP +# @DESCRIPTION: +# An eclass-generated USE-dependency string for the currently tested +# implementation. It is set locally for python_check_deps() call. +# +# The generate USE-flag list is compatible with packages using python-r2, +# python-single-r2 and python-distutils-ng eclasses. It must not be used +# on packages using python.eclass. +# +# Example use: +# @CODE +# python_check_deps() { +# has_version "dev-python/foo[${PYTHON_USEDEP}]" +# } +# @CODE +# +# Example value: +# @CODE +# python_targets_python2_7(-)?,python_single_target_python2_7(+)? +# @CODE + +_python_any_set_globals() { + local usestr deps i PYTHON_PKG_DEP + [[ ${PYTHON_REQ_USE} ]] && usestr="[${PYTHON_REQ_USE}]" + + _python_set_impls + + for i in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + python_export "${i}" PYTHON_PKG_DEP + + # note: need to strip '=' slot operator for || deps + deps="${PYTHON_PKG_DEP/:0=/:0} ${deps}" + done + deps="|| ( ${deps})" + + if [[ ${PYTHON_DEPS+1} ]]; then + if [[ ${PYTHON_DEPS} != "${deps}" ]]; then + eerror "PYTHON_DEPS have changed between inherits (PYTHON_REQ_USE?)!" + eerror "Before: ${PYTHON_DEPS}" + eerror "Now : ${deps}" + die "PYTHON_DEPS integrity check failed" + fi + else + PYTHON_DEPS=${deps} + readonly PYTHON_DEPS + fi + + if [[ ! ${PYTHON_REQUIRED_USE+1} ]]; then + # fake var to catch mistaken usage + PYTHON_REQUIRED_USE='I-DO-NOT-EXIST-IN-PYTHON-ANY-R1' + readonly PYTHON_REQUIRED_USE + fi +} +_python_any_set_globals +unset -f _python_any_set_globals + +if [[ ! ${_PYTHON_ANY_R2} ]]; then + +# @FUNCTION: python_gen_any_dep +# @USAGE: <dependency-block> +# @DESCRIPTION: +# Generate an any-of dependency that enforces a version match between +# the Python interpreter and Python packages. <dependency-block> needs +# to list one or more dependencies with verbatim '${PYTHON_USEDEP}' +# references (quoted!) that will get expanded inside the function. +# +# This should be used along with an appropriate python_check_deps() +# that checks which of the any-of blocks were matched. +# +# Example use: +# @CODE +# DEPEND="$(python_gen_any_dep ' +# dev-python/foo[${PYTHON_USEDEP}] +# || ( dev-python/bar[${PYTHON_USEDEP}] +# dev-python/baz[${PYTHON_USEDEP}] )')" +# +# python_check_deps() { +# has_version "dev-python/foo[${PYTHON_USEDEP}]" \ +# && { has_version "dev-python/bar[${PYTHON_USEDEP}]" \ +# || has_version "dev-python/baz[${PYTHON_USEDEP}]"; } +# } +# @CODE +# +# Example value: +# @CODE +# || ( +# ( +# dev-lang/python:2.7 +# dev-python/foo[python_targets_python2_7(-)?,python_single_target_python2_7(+)?] +# || ( dev-python/bar[python_targets_python2_7(-)?,python_single_target_python2_7(+)?] +# dev-python/baz[python_targets_python2_7(-)?,python_single_target_python2_7(+)?] ) +# ) +# ( +# dev-lang/python:3.3 +# dev-python/foo[python_targets_python3_3(-)?,python_single_target_python3_3(+)?] +# || ( dev-python/bar[python_targets_python3_3(-)?,python_single_target_python3_3(+)?] +# dev-python/baz[python_targets_python3_3(-)?,python_single_target_python3_3(+)?] ) +# ) +# ) +# @CODE +python_gen_any_dep() { + debug-print-function ${FUNCNAME} "${@}" + + local depstr=${1} + [[ ${depstr} ]] || die "No dependency string provided" + + local i PYTHON_PKG_DEP out= + for i in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + local PYTHON_USEDEP="python_targets_${i}(-),python_single_target_${i}(+)" + python_export "${i}" PYTHON_PKG_DEP + + local i_depstr=${depstr//\$\{PYTHON_USEDEP\}/${PYTHON_USEDEP}} + # note: need to strip '=' slot operator for || deps + out="( ${PYTHON_PKG_DEP%=} ${i_depstr} ) ${out}" + done + echo "|| ( ${out})" +} + +# @FUNCTION: _python_EPYTHON_supported +# @USAGE: <epython> +# @INTERNAL +# @DESCRIPTION: +# Check whether the specified implementation is supported by package +# (specified in PYTHON_COMPAT). Calls python_check_deps() if declared. +_python_EPYTHON_supported() { + debug-print-function ${FUNCNAME} "${@}" + + local EPYTHON=${1} + local i=${EPYTHON/./_} + + case "${i}" in + python*|jython*|pypy*) + ;; + *) + ewarn "Invalid EPYTHON: ${EPYTHON}" + return 1 + ;; + esac + + if has "${i}" "${_PYTHON_SUPPORTED_IMPLS[@]}"; then + if python_is_installed "${i}"; then + if declare -f python_check_deps >/dev/null; then + local PYTHON_USEDEP="python_targets_${i}(-),python_single_target_${i}(+)" + python_check_deps + return ${?} + fi + + return 0 + fi + elif ! has "${i}" "${_PYTHON_ALL_IMPLS[@]}"; then + ewarn "Invalid EPYTHON: ${EPYTHON}" + fi + return 1 +} + +# @FUNCTION: python_setup +# @DESCRIPTION: +# Determine what the best installed (and supported) Python +# implementation is, and set the Python build environment up for it. +# +# This function will call python_check_deps() if defined. +python_setup() { + debug-print-function ${FUNCNAME} "${@}" + + # support developer override + if [[ ${PYTHON_COMPAT_OVERRIDE} ]]; then + local impls=( ${PYTHON_COMPAT_OVERRIDE} ) + [[ ${#impls[@]} -eq 1 ]] || die "PYTHON_COMPAT_OVERRIDE must name exactly one implementation for python-any-r2" + + ewarn "WARNING: PYTHON_COMPAT_OVERRIDE in effect. The following Python" + ewarn "implementation will be used:" + ewarn + ewarn " ${PYTHON_COMPAT_OVERRIDE}" + ewarn + ewarn "Dependencies won't be satisfied, and EPYTHON/eselect-python will be ignored." + + python_export "${impls[0]}" EPYTHON PYTHON + python_wrapper_setup + return + fi + + # first, try ${EPYTHON}... maybe it's good enough for us. + if [[ ${EPYTHON} ]]; then + if _python_EPYTHON_supported "${EPYTHON}"; then + python_export EPYTHON PYTHON + python_wrapper_setup + return + fi + fi + + # then, try eselect-python + local variant i + for variant in '' '--python2' '--python3'; do + i=$(eselect python --show ${variant} 2>/dev/null) + + if [[ ! ${i} ]]; then + # no eselect-python? + break + elif _python_EPYTHON_supported "${i}"; then + python_export "${i}" EPYTHON PYTHON + python_wrapper_setup + return + fi + done + + # fallback to best installed impl. + # (reverse iteration over _PYTHON_SUPPORTED_IMPLS) + for (( i = ${#_PYTHON_SUPPORTED_IMPLS[@]} - 1; i >= 0; i-- )); do + python_export "${_PYTHON_SUPPORTED_IMPLS[i]}" EPYTHON PYTHON + if _python_EPYTHON_supported "${EPYTHON}"; then + python_wrapper_setup + return + fi + done + + eerror "No Python implementation found for the build. This is usually" + eerror "a bug in the ebuild. Please report it to bugs.gentoo.org" + eerror "along with the build log." + echo + die "No supported Python implementation installed." +} + +# @FUNCTION: python-any-r2_pkg_setup +# @DESCRIPTION: +# Runs python_setup during from-source installs. +# +# In a binary package installs is a no-op. If you need Python in pkg_* +# phases of a binary package, call python_setup directly. +python-any-r2_pkg_setup() { + debug-print-function ${FUNCNAME} "${@}" + + [[ ${MERGE_TYPE} != binary ]] && python_setup +} + +_PYTHON_ANY_R2=1 +fi diff --git a/eclass/python-r2.eclass b/eclass/python-r2.eclass new file mode 100644 index 000000000000..8bf0b1a1ac29 --- /dev/null +++ b/eclass/python-r2.eclass @@ -0,0 +1,825 @@ +# Copyright 1999-2020 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +# @ECLASS: python-r2.eclass +# @MAINTAINER: +# Python team <[email protected]> +# @AUTHOR: +# Author: Michał Górny <[email protected]> +# @SUPPORTED_EAPIS: 5 6 7 +# @BLURB: A common, simple eclass for Python packages. +# @DESCRIPTION: +# A common eclass providing helper functions to build and install +# packages supporting being installed for multiple Python +# implementations. +# +# This eclass sets correct IUSE. Modification of REQUIRED_USE has to +# be done by the author of the ebuild (but PYTHON_REQUIRED_USE is +# provided for convenience, see below). python-r2 exports PYTHON_DEPS +# and PYTHON_USEDEP so you can create correct dependencies for your +# package easily. It also provides methods to easily run a command for +# each enabled Python implementation and duplicate the sources for them. +# +# Please note that python-r2 will always inherit python-utils-r2 as +# well. Thus, all the functions defined there can be used +# in the packages using python-r2, and there is no need ever to inherit +# both. + +case "${EAPI:-0}" in + 0|1|2|3|4) + die "Unsupported EAPI=${EAPI:-0} (too old) for ${ECLASS}" + ;; + 5|6|7) + # EAPI=5 is required for sane USE_EXPAND dependencies + ;; + *) + die "Unsupported EAPI=${EAPI} (unknown) for ${ECLASS}" + ;; +esac + +if [[ ! ${_PYTHON_R2} ]]; then + +if [[ ${_PYTHON_SINGLE_R2} ]]; then + die 'python-r2.eclass can not be used with python-single-r2.eclass.' +elif [[ ${_PYTHON_ANY_R2} ]]; then + die 'python-r2.eclass can not be used with python-any-r2.eclass.' +fi + +[[ ${EAPI} == [45] ]] && inherit eutils +inherit multibuild python-utils-r2 + +fi + +# @ECLASS-VARIABLE: PYTHON_COMPAT +# @REQUIRED +# @DESCRIPTION: +# This variable contains a list of Python implementations the package +# supports. It must be set before the `inherit' call. It has to be +# an array. +# +# Example: +# @CODE +# PYTHON_COMPAT=( python2_7 python3_3 python3_4 ) +# @CODE +# +# Please note that you can also use bash brace expansion if you like: +# @CODE +# PYTHON_COMPAT=( python2_7 python3_{3,4} ) +# @CODE + +# @ECLASS-VARIABLE: PYTHON_COMPAT_OVERRIDE +# @INTERNAL +# @DESCRIPTION: +# This variable can be used when working with ebuilds to override +# the in-ebuild PYTHON_COMPAT. It is a string listing all +# the implementations which package will be built for. It need be +# specified in the calling environment, and not in ebuilds. +# +# It should be noted that in order to preserve metadata immutability, +# PYTHON_COMPAT_OVERRIDE does not affect IUSE nor dependencies. +# The state of PYTHON_TARGETS is ignored, and all the implementations +# in PYTHON_COMPAT_OVERRIDE are built. Dependencies need to be satisfied +# manually. +# +# Example: +# @CODE +# PYTHON_COMPAT_OVERRIDE='pypy python3_3' emerge -1v dev-python/foo +# @CODE + +# @ECLASS-VARIABLE: PYTHON_REQ_USE +# @DEFAULT_UNSET +# @DESCRIPTION: +# The list of USEflags required to be enabled on the chosen Python +# implementations, formed as a USE-dependency string. It should be valid +# for all implementations in PYTHON_COMPAT, so it may be necessary to +# use USE defaults. +# +# This should be set before calling `inherit'. +# +# Example: +# @CODE +# PYTHON_REQ_USE="gdbm,ncurses(-)?" +# @CODE +# +# It will cause the Python dependencies to look like: +# @CODE +# python_targets_pythonX_Y? ( dev-lang/python:X.Y[gdbm,ncurses(-)?] ) +# @CODE + +# @ECLASS-VARIABLE: PYTHON_DEPS +# @DESCRIPTION: +# This is an eclass-generated Python dependency string for all +# implementations listed in PYTHON_COMPAT. +# +# Example use: +# @CODE +# RDEPEND="${PYTHON_DEPS} +# dev-foo/mydep" +# DEPEND="${RDEPEND}" +# @CODE +# +# Example value: +# @CODE +# dev-lang/python-exec:= +# python_targets_python2_7? ( dev-lang/python:2.7[gdbm] ) +# python_targets_pypy? ( dev-python/pypy[gdbm] ) +# @CODE + +# @ECLASS-VARIABLE: PYTHON_USEDEP +# @DESCRIPTION: +# This is an eclass-generated USE-dependency string which can be used to +# depend on another Python package being built for the same Python +# implementations. +# +# The generate USE-flag list is compatible with packages using python-r2 +# and python-distutils-ng eclasses. It must not be used on packages +# using python.eclass. +# +# Example use: +# @CODE +# RDEPEND="dev-python/foo[${PYTHON_USEDEP}]" +# @CODE +# +# Example value: +# @CODE +# python_targets_python2_7(-)?,python_targets_python3_4(-)? +# @CODE + +# @ECLASS-VARIABLE: PYTHON_REQUIRED_USE +# @DESCRIPTION: +# This is an eclass-generated required-use expression which ensures at +# least one Python implementation has been enabled. +# +# This expression should be utilized in an ebuild by including it in +# REQUIRED_USE, optionally behind a use flag. +# +# Example use: +# @CODE +# REQUIRED_USE="python? ( ${PYTHON_REQUIRED_USE} )" +# @CODE +# +# Example value: +# @CODE +# || ( python_targets_python2_7 python_targets_python3_4 ) +# @CODE + +_python_set_globals() { + local deps i PYTHON_PKG_DEP + + _python_set_impls + + for i in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + python_export "${i}" PYTHON_PKG_DEP + deps+="python_targets_${i}? ( ${PYTHON_PKG_DEP} ) " + done + + local flags=( "${_PYTHON_SUPPORTED_IMPLS[@]/#/python_targets_}" ) + local optflags=${flags[@]/%/(-)?} + + # A nice QA trick here. Since a python-single-r2 package has to have + # at least one PYTHON_SINGLE_TARGET enabled (REQUIRED_USE), + # the following check will always fail on those packages. Therefore, + # it should prevent developers from mistakenly depending on packages + # not supporting multiple Python implementations. + + local flags_st=( "${_PYTHON_SUPPORTED_IMPLS[@]/#/-python_single_target_}" ) + optflags+=,${flags_st[@]/%/(-)} + local requse="|| ( ${flags[*]} )" + local usedep=${optflags// /,} + + # 1) well, python-exec would suffice as an RDEP + # but no point in making this overcomplex, BDEP doesn't hurt anyone + # 2) python-exec should be built with all targets forced anyway + # but if new targets were added, we may need to force a rebuild + deps+=">=dev-lang/python-exec-2:=[${usedep}]" + + if [[ ${PYTHON_DEPS+1} ]]; then + # IUSE is magical, so we can't really check it + # (but we verify PYTHON_COMPAT already) + + if [[ ${PYTHON_DEPS} != "${deps}" ]]; then + eerror "PYTHON_DEPS have changed between inherits (PYTHON_REQ_USE?)!" + eerror "Before: ${PYTHON_DEPS}" + eerror "Now : ${deps}" + die "PYTHON_DEPS integrity check failed" + fi + + # these two are formality -- they depend on PYTHON_COMPAT only + if [[ ${PYTHON_REQUIRED_USE} != ${requse} ]]; then + eerror "PYTHON_REQUIRED_USE have changed between inherits!" + eerror "Before: ${PYTHON_REQUIRED_USE}" + eerror "Now : ${requse}" + die "PYTHON_REQUIRED_USE integrity check failed" + fi + + if [[ ${PYTHON_USEDEP} != "${usedep}" ]]; then + eerror "PYTHON_USEDEP have changed between inherits!" + eerror "Before: ${PYTHON_USEDEP}" + eerror "Now : ${usedep}" + die "PYTHON_USEDEP integrity check failed" + fi + else + IUSE=${flags[*]} + + PYTHON_DEPS=${deps} + PYTHON_REQUIRED_USE=${requse} + PYTHON_USEDEP=${usedep} + readonly PYTHON_DEPS PYTHON_REQUIRED_USE + fi +} +_python_set_globals +unset -f _python_set_globals + +if [[ ! ${_PYTHON_R2} ]]; then + +# @FUNCTION: _python_validate_useflags +# @INTERNAL +# @DESCRIPTION: +# Enforce the proper setting of PYTHON_TARGETS, if PYTHON_COMPAT_OVERRIDE +# is not in effect. If it is, just warn that the flags will be ignored. +_python_validate_useflags() { + debug-print-function ${FUNCNAME} "${@}" + + if [[ ${PYTHON_COMPAT_OVERRIDE} ]]; then + if [[ ! ${_PYTHON_COMPAT_OVERRIDE_WARNED} ]]; then + ewarn "WARNING: PYTHON_COMPAT_OVERRIDE in effect. The following Python" + ewarn "implementations will be enabled:" + ewarn + ewarn " ${PYTHON_COMPAT_OVERRIDE}" + ewarn + ewarn "Dependencies won't be satisfied, and PYTHON_TARGETS will be ignored." + _PYTHON_COMPAT_OVERRIDE_WARNED=1 + fi + # we do not use flags with PCO + return + fi + + local i + + for i in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + use "python_targets_${i}" && return 0 + done + + eerror "No Python implementation selected for the build. Please add one" + eerror "of the following values to your PYTHON_TARGETS (in make.conf):" + eerror + eerror "${PYTHON_COMPAT[@]}" + echo + die "No supported Python implementation in PYTHON_TARGETS." +} + +# @FUNCTION: _python_gen_usedep +# @INTERNAL +# @USAGE: [<pattern>...] +# @DESCRIPTION: +# Output a USE dependency string for Python implementations which +# are both in PYTHON_COMPAT and match any of the patterns passed +# as parameters to the function. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# This is an internal function used to implement python_gen_cond_dep +# and deprecated python_gen_usedep. +_python_gen_usedep() { + debug-print-function ${FUNCNAME} "${@}" + + local impl matches=() + + for impl in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + if _python_impl_matches "${impl}" "${@}"; then + matches+=( + "python_targets_${impl}(-)?" + "-python_single_target_${impl}(-)" + ) + fi + done + + [[ ${matches[@]} ]] || die "No supported implementations match python_gen_usedep patterns: ${@}" + + local out=${matches[@]} + echo "${out// /,}" +} + +# @FUNCTION: python_gen_usedep +# @USAGE: <pattern> [...] +# @DESCRIPTION: +# DEPRECATED. Please use python_gen_cond_dep instead. +# +# Output a USE dependency string for Python implementations which +# are both in PYTHON_COMPAT and match any of the patterns passed +# as parameters to the function. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# When all implementations are requested, please use ${PYTHON_USEDEP} +# instead. Please also remember to set an appropriate REQUIRED_USE +# to avoid ineffective USE flags. +# +# Example: +# @CODE +# PYTHON_COMPAT=( python{2_7,3_4} ) +# DEPEND="doc? ( dev-python/epydoc[$(python_gen_usedep 'python2*')] )" +# @CODE +# +# It will cause the dependency to look like: +# @CODE +# DEPEND="doc? ( dev-python/epydoc[python_targets_python2_7?] )" +# @CODE +python_gen_usedep() { + debug-print-function ${FUNCNAME} "${@}" + + # output only once, during some reasonable phase + # (avoid spamming cache regen runs) + if [[ ${EBUILD_PHASE} == setup ]]; then + eqawarn "python_gen_usedep() is deprecated. Please use python_gen_cond_dep instead." + fi + _python_gen_usedep "${@}" +} + +# @FUNCTION: python_gen_useflags +# @USAGE: [<pattern>...] +# @DESCRIPTION: +# Output a list of USE flags for Python implementations which +# are both in PYTHON_COMPAT and match any of the patterns passed +# as parameters to the function. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# Example: +# @CODE +# PYTHON_COMPAT=( python{2_7,3_4} ) +# REQUIRED_USE="doc? ( || ( $(python_gen_useflags python2*) ) )" +# @CODE +# +# It will cause the variable to look like: +# @CODE +# REQUIRED_USE="doc? ( || ( python_targets_python2_7 ) )" +# @CODE +python_gen_useflags() { + debug-print-function ${FUNCNAME} "${@}" + + local impl matches=() + + for impl in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + if _python_impl_matches "${impl}" "${@}"; then + matches+=( "python_targets_${impl}" ) + fi + done + + echo "${matches[@]}" +} + +# @FUNCTION: python_gen_cond_dep +# @USAGE: <dependency> [<pattern>...] +# @DESCRIPTION: +# Output a list of <dependency>-ies made conditional to USE flags +# of Python implementations which are both in PYTHON_COMPAT and match +# any of the patterns passed as the remaining parameters. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# In order to enforce USE constraints on the packages, verbatim +# '${PYTHON_USEDEP}' (quoted!) may be placed in the dependency +# specification. It will get expanded within the function into a proper +# USE dependency string. +# +# Example: +# @CODE +# PYTHON_COMPAT=( python{2_7,3_{3,4}} pypy ) +# RDEPEND="$(python_gen_cond_dep \ +# 'dev-python/unittest2[${PYTHON_USEDEP}]' python2_7 pypy )" +# @CODE +# +# It will cause the variable to look like: +# @CODE +# RDEPEND="python_targets_python2_7? ( +# dev-python/unittest2[python_targets_python2_7?] ) +# python_targets_pypy? ( +# dev-python/unittest2[python_targets_pypy?] )" +# @CODE +python_gen_cond_dep() { + debug-print-function ${FUNCNAME} "${@}" + + local impl matches=() + local dep=${1} + shift + + for impl in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + if _python_impl_matches "${impl}" "${@}"; then + # substitute ${PYTHON_USEDEP} if used + # (since python_gen_usedep() will not return ${PYTHON_USEDEP} + # the code is run at most once) + if [[ ${dep} == *'${PYTHON_USEDEP}'* ]]; then + local usedep=$(_python_gen_usedep "${@}") + dep=${dep//\$\{PYTHON_USEDEP\}/${usedep}} + fi + + matches+=( "python_targets_${impl}? ( ${dep} )" ) + fi + done + + echo "${matches[@]}" +} + +# @FUNCTION: python_gen_impl_dep +# @USAGE: [<requested-use-flags> [<impl-pattern>...]] +# @DESCRIPTION: +# Output a dependency on Python implementations with the specified USE +# dependency string appended, or no USE dependency string if called +# without the argument (or with empty argument). If any implementation +# patterns are passed, the output dependencies will be generated only +# for the implementations matching them. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# Use this function when you need to request different USE flags +# on the Python interpreter depending on package's USE flags. If you +# only need a single set of interpreter USE flags, just set +# PYTHON_REQ_USE and use ${PYTHON_DEPS} globally. +# +# Example: +# @CODE +# PYTHON_COMPAT=( python{2_7,3_{3,4}} pypy ) +# RDEPEND="foo? ( $(python_gen_impl_dep 'xml(+)') )" +# @CODE +# +# It will cause the variable to look like: +# @CODE +# RDEPEND="foo? ( +# python_targets_python2_7? ( +# dev-lang/python:2.7[xml(+)] ) +# python_targets_pypy? ( +# dev-python/pypy[xml(+)] ) )" +# @CODE +python_gen_impl_dep() { + debug-print-function ${FUNCNAME} "${@}" + + local impl matches=() + local PYTHON_REQ_USE=${1} + shift + + for impl in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + if _python_impl_matches "${impl}" "${@}"; then + local PYTHON_PKG_DEP + python_export "${impl}" PYTHON_PKG_DEP + matches+=( "python_targets_${impl}? ( ${PYTHON_PKG_DEP} )" ) + fi + done + + echo "${matches[@]}" +} + +# @FUNCTION: python_gen_any_dep +# @USAGE: <dependency-block> [<impl-pattern>...] +# @DESCRIPTION: +# Generate an any-of dependency that enforces a version match between +# the Python interpreter and Python packages. <dependency-block> needs +# to list one or more dependencies with verbatim '${PYTHON_USEDEP}' +# references (quoted!) that will get expanded inside the function. +# Optionally, patterns may be specified to restrict the dependency +# to a subset of Python implementations supported by the ebuild. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# This should be used along with an appropriate python_check_deps() +# that checks which of the any-of blocks were matched, and python_setup +# call that enables use of the matched implementation. +# +# Example use: +# @CODE +# DEPEND="$(python_gen_any_dep ' +# dev-python/foo[${PYTHON_USEDEP}] +# || ( dev-python/bar[${PYTHON_USEDEP}] +# dev-python/baz[${PYTHON_USEDEP}] )' -2)" +# +# python_check_deps() { +# has_version "dev-python/foo[${PYTHON_USEDEP}]" \ +# && { has_version "dev-python/bar[${PYTHON_USEDEP}]" \ +# || has_version "dev-python/baz[${PYTHON_USEDEP}]"; } +# } +# +# src_compile() { +# python_foreach_impl usual_code +# +# # some common post-build task that requires Python 2 +# python_setup -2 +# emake frobnicate +# } +# @CODE +# +# Example value: +# @CODE +# || ( +# ( +# dev-lang/python:2.7 +# dev-python/foo[python_targets_python2_7(-)?,python_single_target_python2_7(+)?] +# || ( dev-python/bar[python_targets_python2_7(-)?,python_single_target_python2_7(+)?] +# dev-python/baz[python_targets_python2_7(-)?,python_single_target_python2_7(+)?] ) +# ) +# ( +# dev-lang/python:3.3 +# dev-python/foo[python_targets_python3_3(-)?,python_single_target_python3_3(+)?] +# || ( dev-python/bar[python_targets_python3_3(-)?,python_single_target_python3_3(+)?] +# dev-python/baz[python_targets_python3_3(-)?,python_single_target_python3_3(+)?] ) +# ) +# ) +# @CODE +python_gen_any_dep() { + debug-print-function ${FUNCNAME} "${@}" + + local depstr=${1} + [[ ${depstr} ]] || die "No dependency string provided" + shift + + local i PYTHON_PKG_DEP out= + for i in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + if _python_impl_matches "${i}" "${@}"; then + local PYTHON_USEDEP="python_targets_${i}(-),python_single_target_${i}(+)" + python_export "${i}" PYTHON_PKG_DEP + + local i_depstr=${depstr//\$\{PYTHON_USEDEP\}/${PYTHON_USEDEP}} + # note: need to strip '=' slot operator for || deps + out="( ${PYTHON_PKG_DEP/:0=/:0} ${i_depstr} ) ${out}" + fi + done + echo "|| ( ${out})" +} + +# @ECLASS-VARIABLE: BUILD_DIR +# @DESCRIPTION: +# The current build directory. In global scope, it is supposed to +# contain an initial build directory; if unset, it defaults to ${S}. +# +# In functions run by python_foreach_impl(), the BUILD_DIR is locally +# set to an implementation-specific build directory. That path is +# created through appending a hyphen and the implementation name +# to the final component of the initial BUILD_DIR. +# +# Example value: +# @CODE +# ${WORKDIR}/foo-1.3-python2_7 +# @CODE + +# @FUNCTION: python_copy_sources +# @DESCRIPTION: +# Create a single copy of the package sources for each enabled Python +# implementation. +# +# The sources are always copied from initial BUILD_DIR (or S if unset) +# to implementation-specific build directory matching BUILD_DIR used by +# python_foreach_abi(). +python_copy_sources() { + debug-print-function ${FUNCNAME} "${@}" + + local MULTIBUILD_VARIANTS + _python_obtain_impls + + multibuild_copy_sources +} + +# @FUNCTION: _python_obtain_impls +# @INTERNAL +# @DESCRIPTION: +# Set up the enabled implementation list. +_python_obtain_impls() { + _python_validate_useflags + + if [[ ${PYTHON_COMPAT_OVERRIDE} ]]; then + MULTIBUILD_VARIANTS=( ${PYTHON_COMPAT_OVERRIDE} ) + return + fi + + MULTIBUILD_VARIANTS=() + + local impl + for impl in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + has "${impl}" "${PYTHON_COMPAT[@]}" && \ + use "python_targets_${impl}" && MULTIBUILD_VARIANTS+=( "${impl}" ) + done +} + +# @FUNCTION: _python_multibuild_wrapper +# @USAGE: <command> [<args>...] +# @INTERNAL +# @DESCRIPTION: +# Initialize the environment for Python implementation selected +# for multibuild. +_python_multibuild_wrapper() { + debug-print-function ${FUNCNAME} "${@}" + + local -x EPYTHON PYTHON + local -x PATH=${PATH} PKG_CONFIG_PATH=${PKG_CONFIG_PATH} + python_export "${MULTIBUILD_VARIANT}" EPYTHON PYTHON + python_wrapper_setup + + "${@}" +} + +# @FUNCTION: python_foreach_impl +# @USAGE: <command> [<args>...] +# @DESCRIPTION: +# Run the given command for each of the enabled Python implementations. +# If additional parameters are passed, they will be passed through +# to the command. +# +# The function will return 0 status if all invocations succeed. +# Otherwise, the return code from first failing invocation will +# be returned. +# +# For each command being run, EPYTHON, PYTHON and BUILD_DIR are set +# locally, and the former two are exported to the command environment. +python_foreach_impl() { + debug-print-function ${FUNCNAME} "${@}" + + local MULTIBUILD_VARIANTS + _python_obtain_impls + + multibuild_foreach_variant _python_multibuild_wrapper "${@}" +} + +# @FUNCTION: python_setup +# @USAGE: [<impl-pattern>...] +# @DESCRIPTION: +# Find the best (most preferred) Python implementation that is suitable +# for running common Python code. Set the Python build environment up +# for that implementation. This function has two modes of operation: +# pure and any-of dep. +# +# The pure mode is used if python_check_deps() function is not declared. +# In this case, an implementation is considered suitable if it is +# supported (in PYTHON_COMPAT), enabled (via USE flags) and matches +# at least one of the patterns passed (or '*' if no patterns passed). +# +# Implementation restrictions in the pure mode need to be accompanied +# by appropriate REQUIRED_USE constraints. Otherwise, the eclass may +# fail at build time due to unsatisfied dependencies. +# +# The any-of dep mode is used if python_check_deps() is declared. +# In this mode, an implementation is considered suitable if it is +# supported, matches at least one of the patterns and python_check_deps() +# has successful return code. USE flags are not considered. +# +# The python_check_deps() function in the any-of mode needs to be +# accompanied by appropriate any-of dependencies. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# This function needs to be used when Python is being called outside +# of python_foreach_impl calls (e.g. for shared processes like doc +# building). python_foreach_impl sets up the build environment itself. +# +# Pure mode example: +# @CODE +# DEPEND="doc? ( dev-python/epydoc[$(python_gen_usedep 'python2*')] )" +# REQUIRED_USE="doc? ( $(python_gen_useflags 'python2*') )" +# +# src_compile() { +# #... +# if use doc; then +# python_setup 'python2*' +# make doc +# fi +# } +# @CODE +# +# Any-of mode example: +# @CODE +# DEPEND="doc? ( +# $(python_gen_any_dep 'dev-python/epydoc[${PYTHON_USEDEP}]' 'python2*') )" +# +# python_check_deps() { +# has_version "dev-python/epydoc[${PYTHON_USEDEP}]" +# } +# +# src_compile() { +# #... +# if use doc; then +# python_setup 'python2*' +# make doc +# fi +# } +# @CODE +python_setup() { + debug-print-function ${FUNCNAME} "${@}" + + _python_validate_useflags + local pycompat=( "${PYTHON_COMPAT[@]}" ) + if [[ ${PYTHON_COMPAT_OVERRIDE} ]]; then + pycompat=( ${PYTHON_COMPAT_OVERRIDE} ) + fi + + local has_check_deps + declare -f python_check_deps >/dev/null && has_check_deps=1 + + # (reverse iteration -- newest impl first) + local found + for (( i = ${#_PYTHON_SUPPORTED_IMPLS[@]} - 1; i >= 0; i-- )); do + local impl=${_PYTHON_SUPPORTED_IMPLS[i]} + + # check PYTHON_COMPAT[_OVERRIDE] + has "${impl}" "${pycompat[@]}" || continue + + # match USE flags only if override is not in effect + # and python_check_deps() is not defined + if [[ ! ${PYTHON_COMPAT_OVERRIDE} && ! ${has_check_deps} ]]; then + use "python_targets_${impl}" || continue + fi + + # check patterns + _python_impl_matches "${impl}" "${@}" || continue + + python_export "${impl}" EPYTHON PYTHON + + # if python_check_deps() is declared, switch into any-of mode + if [[ ${has_check_deps} ]]; then + # first check if the interpreter is installed + python_is_installed "${impl}" || continue + # then run python_check_deps + local PYTHON_USEDEP="python_targets_${impl}(-),python_single_target_${impl}(+)" + python_check_deps || continue + fi + + found=1 + break + done + + if [[ ! ${found} ]]; then + eerror "${FUNCNAME}: none of the enabled implementation matched the patterns." + eerror " patterns: ${@-'(*)'}" + eerror "Likely a REQUIRED_USE constraint (possibly USE-conditional) is missing." + eerror " suggested: || ( \$(python_gen_useflags ${@}) )" + eerror "(remember to quote all the patterns with '')" + die "${FUNCNAME}: no enabled implementation satisfy requirements" + fi + + python_wrapper_setup +} + +# @FUNCTION: python_replicate_script +# @USAGE: <path>... +# @DESCRIPTION: +# Copy the given script to variants for all enabled Python +# implementations, then replace it with a symlink to the wrapper. +# +# All specified files must start with a 'python' shebang. A file not +# having a matching shebang will be refused. +python_replicate_script() { + debug-print-function ${FUNCNAME} "${@}" + + _python_replicate_script() { + local _PYTHON_FIX_SHEBANG_QUIET=1 + + local PYTHON_SCRIPTDIR + python_export PYTHON_SCRIPTDIR + + ( + exeopts -m 0755 + exeinto "${PYTHON_SCRIPTDIR#${EPREFIX}}" + doexe "${files[@]}" + ) + + python_fix_shebang -q \ + "${files[@]/*\//${D%/}/${PYTHON_SCRIPTDIR}/}" + } + + local files=( "${@}" ) + python_foreach_impl _python_replicate_script + unset -f _python_replicate_script + + # install the wrappers + local f + for f; do + _python_ln_rel "${ED%/}/usr/lib/python-exec/python-exec2" "${f}" || die + done +} + +_PYTHON_R2=1 +fi diff --git a/eclass/python-single-r2.eclass b/eclass/python-single-r2.eclass new file mode 100644 index 000000000000..5bf6ea7221bc --- /dev/null +++ b/eclass/python-single-r2.eclass @@ -0,0 +1,507 @@ +# Copyright 1999-2020 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +# @ECLASS: python-single-r2.eclass +# @MAINTAINER: +# Python team <[email protected]> +# @AUTHOR: +# Author: Michał Górny <[email protected]> +# @SUPPORTED_EAPIS: 5 6 7 +# @BLURB: An eclass for Python packages not installed for multiple implementations. +# @DESCRIPTION: +# An extension of the python-r2 eclass suite for packages which +# don't support being installed for multiple Python implementations. +# This mostly includes tools embedding Python and packages using foreign +# build systems. +# +# This eclass sets correct IUSE. It also provides PYTHON_DEPS +# and PYTHON_REQUIRED_USE that need to be added to appropriate ebuild +# metadata variables. +# +# The eclass exports PYTHON_SINGLE_USEDEP that is suitable for depending +# on other packages using the eclass. Dependencies on packages using +# python-r2 should be created via python_gen_cond_dep() function, +# using PYTHON_MULTI_USEDEP placeholder. +# +# Please note that packages support multiple Python implementations +# (using python-r2 eclass) can not depend on packages not supporting +# them (using this eclass). +# +# Please note that python-single-r2 will always inherit python-utils-r2 +# as well. Thus, all the functions defined there can be used +# in the packages using python-single-r2, and there is no need ever +# to inherit both. + +case "${EAPI:-0}" in + 0|1|2|3|4) + die "Unsupported EAPI=${EAPI:-0} (too old) for ${ECLASS}" + ;; + 5|6|7) + # EAPI=5 is required for sane USE_EXPAND dependencies + ;; + *) + die "Unsupported EAPI=${EAPI} (unknown) for ${ECLASS}" + ;; +esac + +if [[ ! ${_PYTHON_SINGLE_R2} ]]; then + +if [[ ${_PYTHON_R2} ]]; then + die 'python-single-r2.eclass can not be used with python-r2.eclass.' +elif [[ ${_PYTHON_ANY_R2} ]]; then + die 'python-single-r2.eclass can not be used with python-any-r2.eclass.' +fi + +inherit python-utils-r2 + +fi + +EXPORT_FUNCTIONS pkg_setup + +# @ECLASS-VARIABLE: PYTHON_COMPAT +# @REQUIRED +# @DESCRIPTION: +# This variable contains a list of Python implementations the package +# supports. It must be set before the `inherit' call. It has to be +# an array. +# +# Example: +# @CODE +# PYTHON_COMPAT=( python2_7 python3_3 python3_4 ) +# @CODE +# +# Please note that you can also use bash brace expansion if you like: +# @CODE +# PYTHON_COMPAT=( python2_7 python3_{3,4} ) +# @CODE + +# @ECLASS-VARIABLE: PYTHON_COMPAT_OVERRIDE +# @INTERNAL +# @DESCRIPTION: +# This variable can be used when working with ebuilds to override +# the in-ebuild PYTHON_COMPAT. It is a string naming the implementation +# which package will be built for. It needs to be specified +# in the calling environment, and not in ebuilds. +# +# It should be noted that in order to preserve metadata immutability, +# PYTHON_COMPAT_OVERRIDE does not affect IUSE nor dependencies. +# The state of PYTHON_SINGLE_TARGET is ignored, and the implementation +# in PYTHON_COMPAT_OVERRIDE is built instead. Dependencies need to be +# satisfied manually. +# +# Example: +# @CODE +# PYTHON_COMPAT_OVERRIDE='pypy' emerge -1v dev-python/bar +# @CODE + +# @ECLASS-VARIABLE: PYTHON_REQ_USE +# @DEFAULT_UNSET +# @DESCRIPTION: +# The list of USEflags required to be enabled on the chosen Python +# implementations, formed as a USE-dependency string. It should be valid +# for all implementations in PYTHON_COMPAT, so it may be necessary to +# use USE defaults. +# +# This should be set before calling `inherit'. +# +# Example: +# @CODE +# PYTHON_REQ_USE="gdbm,ncurses(-)?" +# @CODE +# +# It will cause the Python dependencies to look like: +# @CODE +# python_single_target_pythonX_Y? ( dev-lang/python:X.Y[gdbm,ncurses(-)?] ) +# @CODE + +# @ECLASS-VARIABLE: PYTHON_DEPS +# @DESCRIPTION: +# This is an eclass-generated Python dependency string for all +# implementations listed in PYTHON_COMPAT. +# +# The dependency string is conditional on PYTHON_SINGLE_TARGET. +# +# Example use: +# @CODE +# RDEPEND="${PYTHON_DEPS} +# dev-foo/mydep" +# DEPEND="${RDEPEND}" +# @CODE +# +# Example value: +# @CODE +# dev-lang/python-exec:= +# python_single_target_python2_7? ( dev-lang/python:2.7[gdbm] ) +# python_single_target_pypy? ( dev-python/pypy[gdbm] ) +# @CODE + +# @ECLASS-VARIABLE: PYTHON_SINGLE_USEDEP +# @DESCRIPTION: +# This is an eclass-generated USE-dependency string which can be used to +# depend on another python-single-r2 package being built for the same +# Python implementations. +# +# If you need to depend on a multi-impl (python-r2) package, use +# python_gen_cond_dep with PYTHON_MULTI_USEDEP placeholder instead. +# +# Example use: +# @CODE +# RDEPEND="dev-python/foo[${PYTHON_SINGLE_USEDEP}]" +# @CODE +# +# Example value: +# @CODE +# python_single_target_python3_4(-)? +# @CODE + +# @ECLASS-VARIABLE: PYTHON_MULTI_USEDEP +# @DESCRIPTION: +# This is a placeholder variable supported by python_gen_cond_dep, +# in order to depend on python-r2 packages built for the same Python +# implementations. +# +# Example use: +# @CODE +# RDEPEND="$(python_gen_cond_dep ' +# dev-python/foo[${PYTHON_MULTI_USEDEP}] +# ')" +# @CODE +# +# Example value: +# @CODE +# python_targets_python3_4(-) +# @CODE + +# @ECLASS-VARIABLE: PYTHON_REQUIRED_USE +# @DESCRIPTION: +# This is an eclass-generated required-use expression which ensures +# that exactly one PYTHON_SINGLE_TARGET value has been enabled. +# +# This expression should be utilized in an ebuild by including it in +# REQUIRED_USE, optionally behind a use flag. +# +# Example use: +# @CODE +# REQUIRED_USE="python? ( ${PYTHON_REQUIRED_USE} )" +# @CODE +# +# Example value: +# @CODE +# ^^ ( python_single_target_python2_7 python_single_target_python3_3 ) +# @CODE + +_python_single_set_globals() { + _python_set_impls + + local flags=( "${_PYTHON_SUPPORTED_IMPLS[@]/#/python_single_target_}" ) + + if [[ ${#_PYTHON_SUPPORTED_IMPLS[@]} -eq 1 ]]; then + # if only one implementation is supported, use IUSE defaults + # to avoid requesting the user to enable it + IUSE="+${flags[0]}" + else + IUSE="${flags[*]}" + fi + + local requse="^^ ( ${flags[*]} )" + local single_flags="${flags[@]/%/(-)?}" + local single_usedep=${single_flags// /,} + + local deps= i PYTHON_PKG_DEP + for i in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + python_export "${i}" PYTHON_PKG_DEP + # 1) well, python-exec would suffice as an RDEP + # but no point in making this overcomplex, BDEP doesn't hurt anyone + # 2) python-exec should be built with all targets forced anyway + # but if new targets were added, we may need to force a rebuild + deps+="python_single_target_${i}? ( + ${PYTHON_PKG_DEP} + >=dev-lang/python-exec-2:=[python_targets_${i}] + ) " + done + + if [[ ${PYTHON_DEPS+1} ]]; then + if [[ ${PYTHON_DEPS} != "${deps}" ]]; then + eerror "PYTHON_DEPS have changed between inherits (PYTHON_REQ_USE?)!" + eerror "Before: ${PYTHON_DEPS}" + eerror "Now : ${deps}" + die "PYTHON_DEPS integrity check failed" + fi + + # these two are formality -- they depend on PYTHON_COMPAT only + if [[ ${PYTHON_REQUIRED_USE} != ${requse} ]]; then + eerror "PYTHON_REQUIRED_USE have changed between inherits!" + eerror "Before: ${PYTHON_REQUIRED_USE}" + eerror "Now : ${requse}" + die "PYTHON_REQUIRED_USE integrity check failed" + fi + + if [[ ${PYTHON_SINGLE_USEDEP} != "${single_usedep}" ]]; then + eerror "PYTHON_SINGLE_USEDEP have changed between inherits!" + eerror "Before: ${PYTHON_SINGLE_USEDEP}" + eerror "Now : ${single_usedep}" + die "PYTHON_SINGLE_USEDEP integrity check failed" + fi + else + PYTHON_DEPS=${deps} + PYTHON_REQUIRED_USE=${requse} + PYTHON_USEDEP='%PYTHON_USEDEP-HAS-BEEN-REMOVED%' + PYTHON_SINGLE_USEDEP=${single_usedep} + readonly PYTHON_DEPS PYTHON_REQUIRED_USE PYTHON_SINGLE_USEDEP \ + PYTHON_USEDEP + fi +} +_python_single_set_globals +unset -f _python_single_set_globals + +if [[ ! ${_PYTHON_SINGLE_R2} ]]; then + +# @FUNCTION: _python_gen_usedep +# @INTERNAL +# @USAGE: [<pattern>...] +# @DESCRIPTION: +# Output a USE dependency string for Python implementations which +# are both in PYTHON_COMPAT and match any of the patterns passed +# as parameters to the function. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# This is an internal function used to implement python_gen_cond_dep. +_python_gen_usedep() { + debug-print-function ${FUNCNAME} "${@}" + + local impl matches=() + + for impl in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + if _python_impl_matches "${impl}" "${@}"; then + matches+=( + "python_single_target_${impl}(-)?" + ) + fi + done + + [[ ${matches[@]} ]] || die "No supported implementations match python_gen_usedep patterns: ${@}" + + local out=${matches[@]} + echo "${out// /,}" +} + +# @FUNCTION: python_gen_useflags +# @USAGE: [<pattern>...] +# @DESCRIPTION: +# Output a list of USE flags for Python implementations which +# are both in PYTHON_COMPAT and match any of the patterns passed +# as parameters to the function. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# Example: +# @CODE +# PYTHON_COMPAT=( python{2_7,3_4} ) +# REQUIRED_USE="doc? ( ^^ ( $(python_gen_useflags 'python2*') ) )" +# @CODE +# +# It will cause the variable to look like: +# @CODE +# REQUIRED_USE="doc? ( ^^ ( python_single_target_python2_7 ) )" +# @CODE +python_gen_useflags() { + debug-print-function ${FUNCNAME} "${@}" + + local impl matches=() + + for impl in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + if _python_impl_matches "${impl}" "${@}"; then + matches+=( "python_single_target_${impl}" ) + fi + done + + echo "${matches[@]}" +} + +# @FUNCTION: python_gen_cond_dep +# @USAGE: <dependency> [<pattern>...] +# @DESCRIPTION: +# Output a list of <dependency>-ies made conditional to USE flags +# of Python implementations which are both in PYTHON_COMPAT and match +# any of the patterns passed as the remaining parameters. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# In order to enforce USE constraints on the packages, verbatim +# '${PYTHON_SINGLE_USEDEP}' and '${PYTHON_MULTI_USEDEP}' (quoted!) may +# be placed in the dependency specification. It will get expanded within +# the function into a proper USE dependency string. +# +# Example: +# @CODE +# PYTHON_COMPAT=( python{2_7,3_{3,4}} pypy ) +# RDEPEND="$(python_gen_cond_dep \ +# 'dev-python/unittest2[${PYTHON_MULTI_USEDEP}]' python2_7 pypy )" +# @CODE +# +# It will cause the variable to look like: +# @CODE +# RDEPEND="python_single_target_python2_7? ( +# dev-python/unittest2[python_targets_python2_7(-)?,...] ) +# python_single_target_pypy? ( +# dev-python/unittest2[python_targets_pypy(-)?,...] )" +# @CODE +python_gen_cond_dep() { + debug-print-function ${FUNCNAME} "${@}" + + local impl matches=() + + local dep=${1} + shift + + for impl in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + if _python_impl_matches "${impl}" "${@}"; then + # substitute ${PYTHON_SINGLE_USEDEP} if used + # (since python_gen_usedep() will not return + # ${PYTHON_SINGLE_USEDEP}, the code is run at most once) + if [[ ${dep} == *'${PYTHON_SINGLE_USEDEP}'* ]]; then + local usedep=$(_python_gen_usedep "${@}") + dep=${dep//\$\{PYTHON_SINGLE_USEDEP\}/${usedep}} + fi + local multi_usedep="python_targets_${impl}(-)" + + matches+=( "python_single_target_${impl}? ( + ${dep//\$\{PYTHON_MULTI_USEDEP\}/${multi_usedep}} )" ) + fi + done + + echo "${matches[@]}" +} + +# @FUNCTION: python_gen_impl_dep +# @USAGE: [<requested-use-flags> [<impl-pattern>...]] +# @DESCRIPTION: +# Output a dependency on Python implementations with the specified USE +# dependency string appended, or no USE dependency string if called +# without the argument (or with empty argument). If any implementation +# patterns are passed, the output dependencies will be generated only +# for the implementations matching them. +# +# The patterns can be either fnmatch-style patterns (matched via bash +# == operator against PYTHON_COMPAT values) or '-2' / '-3' to indicate +# appropriately all enabled Python 2/3 implementations (alike +# python_is_python3). Remember to escape or quote the fnmatch patterns +# to prevent accidental shell filename expansion. +# +# Use this function when you need to request different USE flags +# on the Python interpreter depending on package's USE flags. If you +# only need a single set of interpreter USE flags, just set +# PYTHON_REQ_USE and use ${PYTHON_DEPS} globally. +# +# Example: +# @CODE +# PYTHON_COMPAT=( python{2_7,3_{3,4}} pypy ) +# RDEPEND="foo? ( $(python_gen_impl_dep 'xml(+)') )" +# @CODE +# +# It will cause the variable to look like: +# @CODE +# RDEPEND="foo? ( +# python_single_target_python2_7? ( +# dev-lang/python:2.7[xml(+)] ) +# python_single_target_pypy? ( +# dev-python/pypy[xml(+)] ) )" +# @CODE +python_gen_impl_dep() { + debug-print-function ${FUNCNAME} "${@}" + + local impl + local matches=() + + local PYTHON_REQ_USE=${1} + shift + + for impl in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + if _python_impl_matches "${impl}" "${@}"; then + local PYTHON_PKG_DEP + python_export "${impl}" PYTHON_PKG_DEP + matches+=( "python_single_target_${impl}? ( ${PYTHON_PKG_DEP} )" ) + fi + done + + echo "${matches[@]}" +} + +# @FUNCTION: python_setup +# @DESCRIPTION: +# Determine what the selected Python implementation is and set +# the Python build environment up for it. +python_setup() { + debug-print-function ${FUNCNAME} "${@}" + + unset EPYTHON + + # support developer override + if [[ ${PYTHON_COMPAT_OVERRIDE} ]]; then + local impls=( ${PYTHON_COMPAT_OVERRIDE} ) + [[ ${#impls[@]} -eq 1 ]] || die "PYTHON_COMPAT_OVERRIDE must name exactly one implementation for python-single-r2" + + ewarn "WARNING: PYTHON_COMPAT_OVERRIDE in effect. The following Python" + ewarn "implementation will be used:" + ewarn + ewarn " ${PYTHON_COMPAT_OVERRIDE}" + ewarn + ewarn "Dependencies won't be satisfied, and PYTHON_SINGLE_TARGET flags will be ignored." + + python_export "${impls[0]}" EPYTHON PYTHON + python_wrapper_setup + return + fi + + local impl + for impl in "${_PYTHON_SUPPORTED_IMPLS[@]}"; do + if use "python_single_target_${impl}"; then + if [[ ${EPYTHON} ]]; then + eerror "Your PYTHON_SINGLE_TARGET setting lists more than a single Python" + eerror "implementation. Please set it to just one value. If you need" + eerror "to override the value for a single package, please use package.env" + eerror "or an equivalent solution (man 5 portage)." + echo + die "More than one implementation in PYTHON_SINGLE_TARGET." + fi + + python_export "${impl}" EPYTHON PYTHON + python_wrapper_setup + fi + done + + if [[ ! ${EPYTHON} ]]; then + eerror "No Python implementation selected for the build. Please set" + eerror "the PYTHON_SINGLE_TARGET variable in your make.conf to one" + eerror "of the following values:" + eerror + eerror "${_PYTHON_SUPPORTED_IMPLS[@]}" + echo + die "No supported Python implementation in PYTHON_SINGLE_TARGET." + fi +} + +# @FUNCTION: python-single-r2_pkg_setup +# @DESCRIPTION: +# Runs python_setup. +python-single-r2_pkg_setup() { + debug-print-function ${FUNCNAME} "${@}" + + [[ ${MERGE_TYPE} != binary ]] && python_setup +} + +_PYTHON_SINGLE_R2=1 +fi diff --git a/eclass/python-utils-r2.eclass b/eclass/python-utils-r2.eclass new file mode 100644 index 000000000000..9821c043021a --- /dev/null +++ b/eclass/python-utils-r2.eclass @@ -0,0 +1,1518 @@ +# Copyright 1999-2020 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +# @ECLASS: python-utils-r2.eclass +# @MAINTAINER: +# Python team <[email protected]> +# @AUTHOR: +# Author: Michał Górny <[email protected]> +# @SUPPORTED_EAPIS: 5 6 7 +# @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. + +case "${EAPI:-0}" in + [0-4]) die "Unsupported EAPI=${EAPI:-0} (too old) for ${ECLASS}" ;; + [5-7]) ;; + *) die "Unsupported EAPI=${EAPI} (unknown) for ${ECLASS}" ;; +esac + +if [[ ${_PYTHON_UTILS_R1} ]]; then + die 'python-r2 suite eclasses can not be combined with python-r1 suite.' +fi + +if [[ ! ${_PYTHON_UTILS_R2} ]]; then + +[[ ${EAPI} == 5 ]] && inherit eutils multilib +inherit toolchain-funcs + +# @ECLASS-VARIABLE: _PYTHON_ALL_IMPLS +# @INTERNAL +# @DESCRIPTION: +# All supported Python implementations, most preferred last. +_PYTHON_ALL_IMPLS=( + pypy3 + python2_7 + python3_6 python3_7 python3_8 +) +readonly _PYTHON_ALL_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_impl_supported +# @USAGE: <impl> +# @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() { + debug-print-function ${FUNCNAME} "${@}" + + [[ ${#} -eq 1 ]] || die "${FUNCNAME}: takes exactly 1 argument (impl)." + + local impl=${1} + + # keep in sync with _PYTHON_ALL_IMPLS! + # (not using that list because inline patterns shall be faster) + case "${impl}" in + python2_7|python3_[678]|pypy3) + return 0 + ;; + jython2_7|pypy|pypy1_[89]|pypy2_0|python2_[56]|python3_[12345]) + return 1 + ;; + *) + [[ ${PYTHON_COMPAT_NO_STRICT} ]] && return 1 + die "Invalid implementation in PYTHON_COMPAT: ${impl}" + esac +} + +# @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 + + if ! declare -p PYTHON_COMPAT &>/dev/null; then + die 'PYTHON_COMPAT not declared.' + fi + 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 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 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) +_python_impl_matches() { + [[ ${#} -ge 1 ]] || die "${FUNCNAME}: takes at least 1 parameter" + [[ ${#} -eq 1 ]] && return 0 + + 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/./_} ]]; then + return 0 + fi + 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-r2: Set in functions called by python_foreach_impl() or after +# calling python_export_best(). +# +# python-single-r2: Set after calling python-single-r2_pkg_setup(). +# +# distutils-r2: 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-r2: Set in functions called by python_foreach_impl() or after +# calling python_export_best(). +# +# python-single-r2: Set after calling python-single-r2_pkg_setup(). +# +# distutils-r2: Set within any of the python sub-phase functions. +# +# Example value: +# @CODE +# python2.7 +# @CODE + +# @ECLASS-VARIABLE: PYTHON_SITEDIR +# @DEFAULT_UNSET +# @DESCRIPTION: +# The path to Python site-packages directory. +# +# Set and exported on request using python_export(). +# Requires a proper build-time dependency on the Python implementation. +# +# Example value: +# @CODE +# /usr/lib64/python2.7/site-packages +# @CODE + +# @ECLASS-VARIABLE: PYTHON_INCLUDEDIR +# @DEFAULT_UNSET +# @DESCRIPTION: +# The path to Python include directory. +# +# Set and exported on request using python_export(). +# Requires a proper build-time dependency on the Python implementation. +# +# Example value: +# @CODE +# /usr/include/python2.7 +# @CODE + +# @ECLASS-VARIABLE: PYTHON_LIBPATH +# @DEFAULT_UNSET +# @DESCRIPTION: +# The path to Python library. +# +# Set and exported on request using python_export(). +# Valid only for CPython. Requires a proper build-time dependency +# on the Python implementation. +# +# Example value: +# @CODE +# /usr/lib64/libpython2.7.so +# @CODE + +# @ECLASS-VARIABLE: PYTHON_CFLAGS +# @DEFAULT_UNSET +# @DESCRIPTION: +# Proper C compiler flags for building against Python. Obtained from +# pkg-config or python-config. +# +# Set and exported on request using python_export(). +# Valid only for CPython. Requires a proper build-time dependency +# on the Python implementation and on pkg-config. +# +# Example value: +# @CODE +# -I/usr/include/python2.7 +# @CODE + +# @ECLASS-VARIABLE: PYTHON_LIBS +# @DEFAULT_UNSET +# @DESCRIPTION: +# Proper C compiler flags for linking against Python. Obtained from +# pkg-config or python-config. +# +# Set and exported on request using python_export(). +# Valid only for CPython. Requires a proper build-time dependency +# on the Python implementation and on pkg-config. +# +# Example value: +# @CODE +# -lpython2.7 +# @CODE + +# @ECLASS-VARIABLE: PYTHON_CONFIG +# @DEFAULT_UNSET +# @DESCRIPTION: +# Path to the python-config executable. +# +# Set and exported on request using python_export(). +# Valid only for CPython. Requires a proper build-time dependency +# on the Python implementation and on pkg-config. +# +# Example value: +# @CODE +# /usr/bin/python2.7-config +# @CODE + +# @ECLASS-VARIABLE: PYTHON_PKG_DEP +# @DEFAULT_UNSET +# @DESCRIPTION: +# The complete dependency on a particular Python package as a string. +# +# Set and exported on request using python_export(). +# +# Example value: +# @CODE +# dev-lang/python:2.7[xml] +# @CODE + +# @ECLASS-VARIABLE: PYTHON_SCRIPTDIR +# @DEFAULT_UNSET +# @DESCRIPTION: +# The location where Python scripts must be installed for current impl. +# +# Set and exported on request using python_export(). +# +# Example value: +# @CODE +# /usr/lib/python-exec/python2.7 +# @CODE + +# @FUNCTION: python_export +# @USAGE: [<impl>] <variables>... +# @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) + export PYTHON=${EPREFIX}/usr/bin/${impl} + 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 + 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 + 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 + 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*) + # python-2.7, python-3.2, etc. + val=$($(tc-getPKG_CONFIG) --libs ${impl/n/n-}) || 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}" -c 'import sysconfig; print(sysconfig.get_config_var("ABIFLAGS") or "")') || die + 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 + python2.7) + PYTHON_PKG_DEP='>=dev-lang/python-2.7.5-r2:2.7';; + python3.3) + PYTHON_PKG_DEP='>=dev-lang/python-3.3.2-r2:3.3';; + python*) + PYTHON_PKG_DEP="dev-lang/python:${impl#python}";; + pypy) + PYTHON_PKG_DEP='>=dev-python/pypy-5:0=';; + pypy3) + PYTHON_PKG_DEP='>=dev-python/pypy3-5:0=';; + jython2.7) + PYTHON_PKG_DEP='dev-java/jython:2.7';; + *) + 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} + debug-print "${FUNCNAME}: PYTHON_SCRIPTDIR = ${PYTHON_SCRIPTDIR}" + ;; + *) + die "python_export: unknown variable ${var}" + esac + done +} + +# @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. +# +# If you just need to have PYTHON_SITEDIR set (and exported), then it is +# better to use python_export() directly instead. +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. +# +# If you just need to have PYTHON_INCLUDEDIR set (and exported), then it +# is better to use python_export() directly instead. +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_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: +# 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} "${@}" + + 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 + + # 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}" -c 'import sys; print("".join(x + "\0" for x in sys.path))' || die) + + debug-print "${FUNCNAME}: using sys.path: ${*/%/;}" + fi + + local d + for d; do + # make sure to get a nice path without // + local instpath=${d#${D%/}} + instpath=/${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) + # 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 -q -f -d "${instpath}" "${d}" + ;; + 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} "${@}" + + 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} "${@}" + + [[ ${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 PYTHON_SCRIPTDIR d + python_export PYTHON_SCRIPTDIR + d=${PYTHON_SCRIPTDIR#${EPREFIX}} + + ( + dodir "${wrapd}" + exeopts -m 0755 + exeinto "${d}" + newexe "${f}" "${newfn}" || return ${?} + ) + + # install the wrapper + _python_ln_rel "${ED%/}"/usr/lib/python-exec/python-exec2 \ + "${ED%/}/${wrapd}/${newfn}" || die + + # 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} "${@}" + + 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} "${@}" + + 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_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. +# +# 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. +# +# 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 PYTHON_SITEDIR=${PYTHON_SITEDIR} + [[ ${PYTHON_SITEDIR} ]] || python_export PYTHON_SITEDIR + + d=${PYTHON_SITEDIR#${EPREFIX}}/${python_moduleroot//.//} + fi + + ( + insopts -m 0644 + insinto "${d}" + doins -r "${@}" || return ${?} + ) + + python_optimize "${ED%/}/${d}" +} + +# @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} "${@}" + + [[ ${EPYTHON} ]] || die 'No Python implementation set (EPYTHON is null).' + + local d PYTHON_INCLUDEDIR=${PYTHON_INCLUDEDIR} + [[ ${PYTHON_INCLUDEDIR} ]] || python_export PYTHON_INCLUDEDIR + + d=${PYTHON_INCLUDEDIR#${EPREFIX}} + + ( + insopts -m 0644 + insinto "${d}" + doins -r "${@}" || return ${?} + ) +} + +# @FUNCTION: python_wrapper_setup +# @USAGE: [<path> [<impl>]] +# @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 + _python_check_dead_variables + + 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}.pc || die + + local EPYTHON PYTHON + python_export "${impl}" EPYTHON PYTHON + + local pyver pyother + if python_is_python3; then + pyver=3 + pyother=2 + else + pyver=2 + pyother=3 + fi + + # 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/python${pyver}" || die + chmod +x "${workdir}/bin/python" "${workdir}/bin/python${pyver}" || die + + local nonsupp=( "python${pyother}" "python${pyother}-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/python${pyver}-config" || die + chmod +x "${workdir}/bin/python-config" \ + "${workdir}/bin/python${pyver}-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/python.pc || die + ln -s python.pc "${workdir}"/pkgconfig/python${pyver}.pc || die + else + nonsupp+=( 2to3 python-config "python${pyver}-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_is_python3 +# @USAGE: [<impl>] +# @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 + + case "${impl}" in + pypy|pypy3) + local append= + if [[ ${PYTHON_REQ_USE} ]]; then + append=[${PYTHON_REQ_USE}] + fi + + # be happy with just the interpeter, no need for the virtual + has_version "${hasv_args[@]}" "dev-python/${impl}${append}" \ + || has_version "${hasv_args[@]}" "dev-python/${impl}-bin${append}" + ;; + *) + local PYTHON_PKG_DEP + python_export "${impl}" PYTHON_PKG_DEP + has_version "${hasv_args[@]}" "${PYTHON_PKG_DEP}" + ;; + esac +} + +# @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. +# +# 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. +# +# 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. +# +# --force causes the function to replace even shebangs that require +# incompatible Python version. --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_correct any_fixed is_recursive + + [[ -d ${path} ]] && is_recursive=1 + + while IFS= read -r -d '' f; do + local shebang i + local error= from= + + # 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 + + # 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}" + + # 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 + fi + + if [[ ! ${error} && ! ${from} ]]; 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 + # 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 + 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 + 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})" + fi + done +} + +# @FUNCTION: _python_check_locale_sanity +# @USAGE: <locale> +# @RETURN: 0 if sane, 1 otherwise +# @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 + # not all packages include the Makefile in pypi tarball + sphinx-build -b html -d "${dir}"/_build/doctrees "${dir}" \ + "${dir}"/_build/html || die + + HTML_DOCS+=( "${dir}/_build/html/." ) +} + +# -- python.eclass functions -- + +_python_check_dead_variables() { + local v + + for v in PYTHON_DEPEND PYTHON_USE_WITH{,_OR,_OPT} {RESTRICT,SUPPORT}_PYTHON_ABIS + do + if [[ ${!v} ]]; then + die "${v} is invalid for python-r2 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#Ebuild_head" + fi + done + + for v in PYTHON_{CPPFLAGS,CFLAGS,CXXFLAGS,LDFLAGS} + do + if [[ ${!v} ]]; then + die "${v} is invalid for python-r2 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#PYTHON_CFLAGS" + fi + done + + 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-r2 suite" + fi + done + + 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-r2, 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-r2, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#DISTUTILS_SRC_TEST" + 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-r2 suite" + fi + done +} + +python_pkg_setup() { + die "${FUNCNAME}() is invalid for python-r2 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#pkg_setup" +} + +python_convert_shebangs() { + die "${FUNCNAME}() is invalid for python-r2 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#python_convert_shebangs" +} + +python_clean_py-compile_files() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_clean_installation_image() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_execute_function() { + die "${FUNCNAME}() is invalid for python-r2 suite, please take a look @ https://wiki.gentoo.org/wiki/Project:Python/Python.eclass_conversion#python_execute_function" +} + +python_generate_wrapper_scripts() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_merge_intermediate_installation_images() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_set_active_version() { + die "${FUNCNAME}() is invalid for python-r2 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-r2 suite" +} + +PYTHON() { + die "${FUNCNAME}() is invalid for python-r2 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-r2 suite" +} + +python_get_implementational_package() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_get_libdir() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_get_library() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_get_version() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_get_implementation_and_version() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_execute_nosetests() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_execute_py.test() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_execute_trial() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_enable_pyc() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_disable_pyc() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_mod_optimize() { + die "${FUNCNAME}() is invalid for python-r2 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-r2 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-r2 suite" +} + +python_install_executables() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_get_extension_module_suffix() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_byte-compile_modules() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_clean_byte-compiled_modules() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +python_generate_cffi_modules() { + die "${FUNCNAME}() is invalid for python-r2 suite" +} + +_PYTHON_UTILS_R2=1 +fi diff --git a/eclass/tests/distutils-r2.sh b/eclass/tests/distutils-r2.sh new file mode 100755 index 000000000000..15f59bcd7d48 --- /dev/null +++ b/eclass/tests/distutils-r2.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# Copyright 1999-2015 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +EAPI=5 +PYTHON_COMPAT=( python2_7 ) +source tests-common.sh + +test-phase_name_free() { + local ph=${1} + + if declare -f "${ph}"; then + die "${ph} function declared while name reserved for phase!" + fi + if declare -f "${ph}_all"; then + die "${ph}_all function declared while name reserved for phase!" + fi +} + +test-distutils_enable_tests() { + local runner=${1} + local exp_IUSE=${2} + local exp_RESTRICT=${3} + local exp_DEPEND=${4} + + local IUSE=${IUSE} + local RESTRICT=${RESTRICT} + local DEPEND=${DEPEND} + + tbegin "${runner}" + + distutils_enable_tests "${runner}" + + local ret var + for var in IUSE RESTRICT DEPEND; do + local exp_var=exp_${var} + if [[ ${!var} != "${!exp_var}" ]]; then + eindent + eerror "${var} expected: ${!exp_var}" + eerror "${var} actual: ${!var}" + eoutdent + ret=1 + tret=1 + fi + done + + tend ${ret} +} + +DISTUTILS_USE_SETUPTOOLS=no +inherit distutils-r2 + +tbegin "sane function names" + +test-phase_name_free python_prepare +test-phase_name_free python_configure +test-phase_name_free python_compile +test-phase_name_free python_test +test-phase_name_free python_install + +tend + +einfo distutils_enable_tests +eindent +BASE_IUSE="python_targets_python2_7" +BASE_DEPS="python_targets_python2_7? ( >=dev-lang/python-2.7.5-r2:2.7 ) >=dev-lang/python-exec-2:=[python_targets_python2_7(-)?,-python_single_target_python2_7(-)]" +TEST_RESTRICT=" !test? ( test )" + +einfo "empty RDEPEND" +eindent +RDEPEND="" +test-distutils_enable_tests pytest \ + "${BASE_IUSE} test" "${TEST_RESTRICT}" "${BASE_DEPS} test? ( dev-python/pytest[${PYTHON_USEDEP}] )" +test-distutils_enable_tests nose \ + "${BASE_IUSE} test" "${TEST_RESTRICT}" "${BASE_DEPS} test? ( dev-python/nose[${PYTHON_USEDEP}] )" +test-distutils_enable_tests unittest \ + "${BASE_IUSE}" "" "${BASE_DEPS}" +test-distutils_enable_tests setup.py \ + "${BASE_IUSE}" "" "${BASE_DEPS}" +eoutdent + +einfo "non-empty RDEPEND" +eindent +BASE_RDEPEND="dev-python/foo[${PYTHON_USEDEP}]" +RDEPEND=${BASE_RDEPEND} +test-distutils_enable_tests pytest \ + "${BASE_IUSE} test" "${TEST_RESTRICT}" "${BASE_DEPS} test? ( ${BASE_RDEPEND} dev-python/pytest[${PYTHON_USEDEP}] )" +test-distutils_enable_tests nose \ + "${BASE_IUSE} test" "${TEST_RESTRICT}" "${BASE_DEPS} test? ( ${BASE_RDEPEND} dev-python/nose[${PYTHON_USEDEP}] )" +test-distutils_enable_tests unittest \ + "${BASE_IUSE} test" "${TEST_RESTRICT}" "${BASE_DEPS} test? ( ${BASE_RDEPEND} )" +test-distutils_enable_tests setup.py \ + "${BASE_IUSE} test" "${TEST_RESTRICT}" "${BASE_DEPS} test? ( ${BASE_RDEPEND} )" +eoutdent + +eoutdent + +texit diff --git a/eclass/tests/python-utils-r2.sh b/eclass/tests/python-utils-r2.sh new file mode 100755 index 000000000000..64490cb0d24a --- /dev/null +++ b/eclass/tests/python-utils-r2.sh @@ -0,0 +1,237 @@ +#!/bin/bash +# Copyright 1999-2019 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=7 +source tests-common.sh + +test_var() { + local var=${1} + local impl=${2} + local expect=${3} + + tbegin "${var} for ${impl}" + + local ${var} + python_export ${impl} PYTHON ${var} + [[ ${!var} == ${expect} ]] || eerror "(${impl}: ${var}: ${!var} != ${expect}" + + tend ${?} +} + +test_is() { + local func=${1} + local expect=${2} + + tbegin "${func} (expecting: ${expect})" + + ${func} + [[ ${?} == ${expect} ]] + + tend ${?} +} + +test_fix_shebang() { + local from=${1} + local to=${2} + local expect=${3} + local args=( "${@:4}" ) + + tbegin "python_fix_shebang${args[@]+ ${args[*]}} from ${from} to ${to} (exp: ${expect})" + + echo "${from}" > "${tmpfile}" + output=$( EPYTHON=${to} python_fix_shebang "${args[@]}" -q "${tmpfile}" 2>&1 ) + + if [[ ${?} != 0 ]]; then + if [[ ${expect} != FAIL ]]; then + echo "${output}" + tend 1 + else + tend 0 + fi + else + [[ $(<"${tmpfile}") == ${expect} ]] \ + || eerror "${from} -> ${to}: $(<"${tmpfile}") != ${expect}" + tend ${?} + fi +} + +tmpfile=$(mktemp) + +inherit python-utils-r2 + +test_var EPYTHON python2_7 python2.7 +test_var PYTHON python2_7 /usr/bin/python2.7 +if [[ -x /usr/bin/python2.7 ]]; then + test_var PYTHON_SITEDIR python2_7 "/usr/lib*/python2.7/site-packages" + test_var PYTHON_INCLUDEDIR python2_7 /usr/include/python2.7 + test_var PYTHON_LIBPATH python2_7 "/usr/lib*/libpython2.7$(get_libname)" + test_var PYTHON_CONFIG python2_7 /usr/bin/python2.7-config + test_var PYTHON_CFLAGS python2_7 "*-I/usr/include/python2.7*" + test_var PYTHON_LIBS python2_7 "*-lpython2.7*" +fi +test_var PYTHON_PKG_DEP python2_7 '*dev-lang/python*:2.7' +test_var PYTHON_SCRIPTDIR python2_7 /usr/lib/python-exec/python2.7 + +test_var EPYTHON python3_6 python3.6 +test_var PYTHON python3_6 /usr/bin/python3.6 +if [[ -x /usr/bin/python3.6 ]]; then + abiflags=$(/usr/bin/python3.6 -c 'import sysconfig; print(sysconfig.get_config_var("ABIFLAGS"))') + test_var PYTHON_SITEDIR python3_6 "/usr/lib*/python3.6/site-packages" + test_var PYTHON_INCLUDEDIR python3_6 "/usr/include/python3.6${abiflags}" + test_var PYTHON_LIBPATH python3_6 "/usr/lib*/libpython3.6${abiflags}$(get_libname)" + test_var PYTHON_CONFIG python3_6 "/usr/bin/python3.6${abiflags}-config" + test_var PYTHON_CFLAGS python3_6 "*-I/usr/include/python3.6*" + test_var PYTHON_LIBS python3_6 "*-lpython3.6*" +fi +test_var PYTHON_PKG_DEP python3_6 '*dev-lang/python*:3.6' +test_var PYTHON_SCRIPTDIR python3_6 /usr/lib/python-exec/python3.6 + +test_var EPYTHON python3_7 python3.7 +test_var PYTHON python3_7 /usr/bin/python3.7 +if [[ -x /usr/bin/python3.7 ]]; then + abiflags=$(/usr/bin/python3.7 -c 'import sysconfig; print(sysconfig.get_config_var("ABIFLAGS"))') + test_var PYTHON_SITEDIR python3_7 "/usr/lib/python3.7/site-packages" + test_var PYTHON_INCLUDEDIR python3_7 "/usr/include/python3.7${abiflags}" + test_var PYTHON_LIBPATH python3_7 "/usr/lib*/libpython3.7${abiflags}$(get_libname)" + test_var PYTHON_CONFIG python3_7 "/usr/bin/python3.7${abiflags}-config" + test_var PYTHON_CFLAGS python3_7 "*-I/usr/include/python3.7*" + test_var PYTHON_LIBS python3_7 "*-lpython3.7*" +fi +test_var PYTHON_PKG_DEP python3_7 '*dev-lang/python*:3.7' +test_var PYTHON_SCRIPTDIR python3_7 /usr/lib/python-exec/python3.7 + +test_var EPYTHON jython2_7 jython2.7 +test_var PYTHON jython2_7 /usr/bin/jython2.7 +if [[ -x /usr/bin/jython2.7 ]]; then + test_var PYTHON_SITEDIR jython2_7 /usr/share/jython-2.7/Lib/site-packages +fi +test_var PYTHON_PKG_DEP jython2_7 '*dev-java/jython*:2.7' +test_var PYTHON_SCRIPTDIR jython2_7 /usr/lib/python-exec/jython2.7 + +test_var EPYTHON pypy pypy +test_var PYTHON pypy /usr/bin/pypy +if [[ -x /usr/bin/pypy ]]; then + test_var PYTHON_SITEDIR pypy "/usr/lib*/pypy2.7/site-packages" + test_var PYTHON_INCLUDEDIR pypy "/usr/lib*/pypy2.7/include" +fi +test_var PYTHON_PKG_DEP pypy '*dev-python/pypy*:0=' +test_var PYTHON_SCRIPTDIR pypy /usr/lib/python-exec/pypy + +test_var EPYTHON pypy3 pypy3 +test_var PYTHON pypy3 /usr/bin/pypy3 +if [[ -x /usr/bin/pypy3 ]]; then + test_var PYTHON_SITEDIR pypy3 "/usr/lib*/pypy3.?/site-packages" + test_var PYTHON_INCLUDEDIR pypy3 "/usr/lib*/pypy3.?/include" +fi +test_var PYTHON_PKG_DEP pypy3 '*dev-python/pypy3*:0=' +test_var PYTHON_SCRIPTDIR pypy3 /usr/lib/python-exec/pypy3 + +test_is "python_is_python3 python2.7" 1 +test_is "python_is_python3 python3.2" 0 +test_is "python_is_python3 jython2.7" 1 +test_is "python_is_python3 pypy" 1 +test_is "python_is_python3 pypy3" 0 + +# generic shebangs +test_fix_shebang '#!/usr/bin/python' python2.7 '#!/usr/bin/python2.7' +test_fix_shebang '#!/usr/bin/python' python3.6 '#!/usr/bin/python3.6' +test_fix_shebang '#!/usr/bin/python' pypy '#!/usr/bin/pypy' +test_fix_shebang '#!/usr/bin/python' pypy3 '#!/usr/bin/pypy3' +test_fix_shebang '#!/usr/bin/python' jython2.7 '#!/usr/bin/jython2.7' + +# python2/python3 matching +test_fix_shebang '#!/usr/bin/python2' python2.7 '#!/usr/bin/python2.7' +test_fix_shebang '#!/usr/bin/python3' python2.7 FAIL +test_fix_shebang '#!/usr/bin/python3' python2.7 '#!/usr/bin/python2.7' --force +test_fix_shebang '#!/usr/bin/python3' python3.6 '#!/usr/bin/python3.6' +test_fix_shebang '#!/usr/bin/python2' python3.6 FAIL +test_fix_shebang '#!/usr/bin/python2' python3.6 '#!/usr/bin/python3.6' --force + +# pythonX.Y matching (those mostly test the patterns) +test_fix_shebang '#!/usr/bin/python2.7' python2.7 '#!/usr/bin/python2.7' +test_fix_shebang '#!/usr/bin/python2.7' python3.2 FAIL +test_fix_shebang '#!/usr/bin/python2.7' python3.2 '#!/usr/bin/python3.2' --force +test_fix_shebang '#!/usr/bin/python3.2' python3.2 '#!/usr/bin/python3.2' +test_fix_shebang '#!/usr/bin/python3.2' python2.7 FAIL +test_fix_shebang '#!/usr/bin/python3.2' python2.7 '#!/usr/bin/python2.7' --force +test_fix_shebang '#!/usr/bin/pypy' pypy '#!/usr/bin/pypy' +test_fix_shebang '#!/usr/bin/pypy' python2.7 FAIL +test_fix_shebang '#!/usr/bin/pypy' python2.7 '#!/usr/bin/python2.7' --force +test_fix_shebang '#!/usr/bin/jython2.7' jython2.7 '#!/usr/bin/jython2.7' +test_fix_shebang '#!/usr/bin/jython2.7' jython3.2 FAIL +test_fix_shebang '#!/usr/bin/jython2.7' jython3.2 '#!/usr/bin/jython3.2' --force + +# fancy path handling +test_fix_shebang '#!/mnt/python2/usr/bin/python' python3.6 \ + '#!/mnt/python2/usr/bin/python3.6' +test_fix_shebang '#!/mnt/python2/usr/bin/python2' python2.7 \ + '#!/mnt/python2/usr/bin/python2.7' +test_fix_shebang '#!/mnt/python2/usr/bin/env python' python2.7 \ + '#!/mnt/python2/usr/bin/env python2.7' +test_fix_shebang '#!/mnt/python2/usr/bin/python2 python2' python2.7 \ + '#!/mnt/python2/usr/bin/python2.7 python2' +test_fix_shebang '#!/mnt/python2/usr/bin/python3 python2' python2.7 FAIL +test_fix_shebang '#!/mnt/python2/usr/bin/python3 python2' python2.7 \ + '#!/mnt/python2/usr/bin/python2.7 python2' --force +test_fix_shebang '#!/usr/bin/foo' python2.7 FAIL + +# regression test for bug #522080 +test_fix_shebang '#!/usr/bin/python ' python2.7 '#!/usr/bin/python2.7 ' + +# make sure we don't break pattern matching +test_is "_python_impl_supported python2_5" 1 +test_is "_python_impl_supported python2_6" 1 +test_is "_python_impl_supported python2_7" 0 +test_is "_python_impl_supported python3_1" 1 +test_is "_python_impl_supported python3_2" 1 +test_is "_python_impl_supported python3_3" 1 +test_is "_python_impl_supported python3_4" 1 +test_is "_python_impl_supported python3_5" 1 +test_is "_python_impl_supported python3_6" 0 +test_is "_python_impl_supported python3_7" 0 +test_is "_python_impl_supported python3_8" 0 +test_is "_python_impl_supported pypy1_8" 1 +test_is "_python_impl_supported pypy1_9" 1 +test_is "_python_impl_supported pypy2_0" 1 +test_is "_python_impl_supported pypy" 1 +test_is "_python_impl_supported pypy3" 0 +test_is "_python_impl_supported jython2_7" 1 + +# check _python_impl_matches behavior +test_is "_python_impl_matches python2_7 -2" 0 +test_is "_python_impl_matches python3_6 -2" 1 +test_is "_python_impl_matches python3_7 -2" 1 +test_is "_python_impl_matches pypy -2" 0 +test_is "_python_impl_matches pypy3 -2" 1 +test_is "_python_impl_matches python2_7 -3" 1 +test_is "_python_impl_matches python3_6 -3" 0 +test_is "_python_impl_matches python3_7 -3" 0 +test_is "_python_impl_matches pypy -3" 1 +test_is "_python_impl_matches pypy3 -3" 0 +test_is "_python_impl_matches python2_7 -2 python3_6" 0 +test_is "_python_impl_matches python3_6 -2 python3_6" 0 +test_is "_python_impl_matches python3_7 -2 python3_6" 1 +test_is "_python_impl_matches pypy -2 python3_6" 0 +test_is "_python_impl_matches pypy3 -2 python3_6" 1 +test_is "_python_impl_matches python2_7 pypy3 -2 python3_6" 0 +test_is "_python_impl_matches python3_6 pypy3 -2 python3_6" 0 +test_is "_python_impl_matches python3_7 pypy3 -2 python3_6" 1 +test_is "_python_impl_matches pypy pypy3 -2 python3_6" 0 +test_is "_python_impl_matches pypy3 pypy3 -2 python3_6" 0 +set -f +test_is "_python_impl_matches python2_7 pypy*" 1 +test_is "_python_impl_matches python3_6 pypy*" 1 +test_is "_python_impl_matches python3_7 pypy*" 1 +test_is "_python_impl_matches pypy pypy*" 0 +test_is "_python_impl_matches pypy3 pypy*" 0 +test_is "_python_impl_matches python2_7 python*" 0 +test_is "_python_impl_matches python3_6 python*" 0 +test_is "_python_impl_matches python3_7 python*" 0 +test_is "_python_impl_matches pypy python*" 1 +test_is "_python_impl_matches pypy3 python*" 1 +set +f + +rm "${tmpfile}" + +texit -- 2.25.1
