python-utils-r1.eclass: python_moduleinto, allow package dot-notation
[gentoo.git] / eclass / python-utils-r1.eclass
index 5876d84055905b75d95f71ef4b4ed9868fdaaef2..863051337f0b1e12a779834229e36a7d7fbbe810 100644 (file)
@@ -1,6 +1,5 @@
-# Copyright 1999-2015 Gentoo Foundation
+# Copyright 1999-2017 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
-# $Id$
 
 # @ECLASS: python-utils-r1.eclass
 # @MAINTAINER:
@@ -20,7 +19,7 @@
 # https://wiki.gentoo.org/wiki/Project:Python/python-utils-r1
 
 case "${EAPI:-0}" in
-       0|1|2|3|4|5)
+       0|1|2|3|4|5|6)
                ;;
        *)
                die "Unsupported EAPI=${EAPI} (unknown) for ${ECLASS}"
@@ -33,18 +32,20 @@ fi
 
 if [[ ! ${_PYTHON_UTILS_R1} ]]; then
 
-inherit eutils multilib toolchain-funcs
+[[ ${EAPI:-0} == [012345] ]] && inherit eutils multilib
+inherit toolchain-funcs
 
 # @ECLASS-VARIABLE: _PYTHON_ALL_IMPLS
 # @INTERNAL
 # @DESCRIPTION:
 # All supported Python implementations, most preferred last.
 _PYTHON_ALL_IMPLS=(
-       jython2_5 jython2_7
+       jython2_7
        pypy pypy3
-       python3_3 python3_4 python3_5
        python2_7
+       python3_4 python3_5 python3_6
 )
+readonly _PYTHON_ALL_IMPLS
 
 # @FUNCTION: _python_impl_supported
 # @USAGE: <impl>
@@ -66,10 +67,10 @@ _python_impl_supported() {
        # keep in sync with _PYTHON_ALL_IMPLS!
        # (not using that list because inline patterns shall be faster)
        case "${impl}" in
-               python2_7|python3_[345]|jython2_[57])
+               python2_7|python3_[456]|jython2_7)
                        return 0
                        ;;
-               pypy1_[89]|pypy2_0|python2_[56]|python3_[12])
+               pypy1_[89]|pypy2_0|python2_[56]|python3_[123])
                        return 1
                        ;;
                pypy|pypy3)
@@ -82,6 +83,72 @@ _python_impl_supported() {
        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
+}
+
 # @ECLASS-VARIABLE: PYTHON
 # @DEFAULT_UNSET
 # @DESCRIPTION:
@@ -364,11 +431,9 @@ python_export() {
                                        python*)
                                                PYTHON_PKG_DEP="dev-lang/python:${impl#python}";;
                                        pypy)
-                                               PYTHON_PKG_DEP='virtual/pypy:0=';;
+                                               PYTHON_PKG_DEP='>=virtual/pypy-5:0=';;
                                        pypy3)
-                                               PYTHON_PKG_DEP='virtual/pypy3:0=';;
-                                       jython2.5)
-                                               PYTHON_PKG_DEP='>=dev-java/jython-2.5.3-r2:2.5';;
+                                               PYTHON_PKG_DEP='>=virtual/pypy3-5:0=';;
                                        jython2.7)
                                                PYTHON_PKG_DEP='dev-java/jython:2.7';;
                                        *)
@@ -567,9 +632,6 @@ python_optimize() {
        local PYTHON=${PYTHON}
        [[ ${PYTHON} ]] || python_export PYTHON
 
-       # Note: python2.6 can't handle passing files to compileall...
-       # TODO: we do not support 2.6 any longer
-
        # default to sys.path
        if [[ ${#} -eq 0 ]]; then
                local f
@@ -594,10 +656,16 @@ python_optimize() {
                instpath=/${instpath##/}
 
                case "${EPYTHON}" in
-                       python*)
+                       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}"
                                ;;
@@ -605,32 +673,23 @@ python_optimize() {
        done
 }
 
-# @ECLASS-VARIABLE: python_scriptroot
-# @DEFAULT_UNSET
+# @FUNCTION: python_scriptinto
+# @USAGE: <new-path>
 # @DESCRIPTION:
-# The current script destination for python_doscript(). The path
-# is relative to the installation root (${ED}).
-#
-# When unset, ${DESTTREE}/bin (/usr/bin by default) will be used.
+# 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}).
 #
-# Can be set indirectly through the python_scriptinto() function.
+# If not set explicitly, the directory defaults to /usr/bin.
 #
 # Example:
 # @CODE
 # src_install() {
-#   local python_scriptroot=${GAMES_BINDIR}
+#   python_scriptinto /usr/sbin
 #   python_foreach_impl python_doscript foo
 # }
 # @CODE
-
-# @FUNCTION: python_scriptinto
-# @USAGE: <new-path>
-# @DESCRIPTION:
-# Set the current scriptroot. The new value will be stored
-# in the 'python_scriptroot' environment variable. The new value need
-# be relative to the installation root (${ED}).
-#
-# Alternatively, you can set the variable directly.
 python_scriptinto() {
        debug-print-function ${FUNCNAME} "${@}"
 
@@ -640,7 +699,7 @@ python_scriptinto() {
 # @FUNCTION: python_doexe
 # @USAGE: <files>...
 # @DESCRIPTION:
-# Install the given executables into current python_scriptroot,
+# 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,
@@ -657,7 +716,7 @@ python_doexe() {
 # @FUNCTION: python_newexe
 # @USAGE: <path> <new-name>
 # @DESCRIPTION:
-# Install the given executable into current python_scriptroot,
+# 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,
@@ -668,8 +727,11 @@ python_newexe() {
 
        [[ ${EPYTHON} ]] || die 'No Python implementation set (EPYTHON is null).'
        [[ ${#} -eq 2 ]] || die "Usage: ${FUNCNAME} <path> <new-name>"
+       if [[ ${EAPI:-0} == [0123] ]]; then
+               die "python_do* and python_new* helpers are banned in EAPIs older than 4."
+       fi
 
-       local wrapd=${python_scriptroot:-${DESTTREE}/bin}
+       local wrapd=${python_scriptroot:-/usr/bin}
 
        local f=${1}
        local newfn=${2}
@@ -681,7 +743,7 @@ python_newexe() {
        (
                dodir "${wrapd}"
                exeinto "${d}"
-               newexe "${f}" "${newfn}" || die
+               newexe "${f}" "${newfn}" || return ${?}
        )
 
        # install the wrapper
@@ -697,7 +759,7 @@ python_newexe() {
 # @FUNCTION: python_doscript
 # @USAGE: <files>...
 # @DESCRIPTION:
-# Install the given scripts into current python_scriptroot,
+# 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
@@ -720,7 +782,7 @@ python_doscript() {
 # @FUNCTION: python_newscript
 # @USAGE: <path> <new-name>
 # @DESCRIPTION:
-# Install the given script into current python_scriptroot
+# Install the given script into the executable install directory
 # for the current Python implementation (${EPYTHON}), and name it
 # <new-name>.
 #
@@ -741,35 +803,34 @@ python_newscript() {
        python_newexe "${@}"
 }
 
-# @ECLASS-VARIABLE: python_moduleroot
-# @DEFAULT_UNSET
+# @FUNCTION: python_moduleinto
+# @USAGE: <new-path>
 # @DESCRIPTION:
-# The current module root for python_domodule(). The path can be either
-# an absolute system path (it must start with a slash, and ${ED} will be
-# prepended to it) or relative to the implementation's site-packages directory
-# (then it must start with a non-slash character).
+# 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 unset, the modules will be installed in the site-packages root.
+# When not set explicitly, the modules are installed to the top
+# site-packages directory.
 #
-# Can be set indirectly through the python_moduleinto() function.
+# 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() {
-#   local python_moduleroot=bar
+#   python_moduleinto bar
 #   # installs ${PYTHON_SITEDIR}/bar/baz.py
 #   python_foreach_impl python_domodule baz.py
 # }
 # @CODE
-
-# @FUNCTION: python_moduleinto
-# @USAGE: <new-path>
-# @DESCRIPTION:
-# Set the current module root. The new value will be stored
-# in the 'python_moduleroot' environment variable. The new value need
-# be relative to the site-packages root.
-#
-# Alternatively, you can set the variable directly.
 python_moduleinto() {
        debug-print-function ${FUNCNAME} "${@}"
 
@@ -779,8 +840,8 @@ python_moduleinto() {
 # @FUNCTION: python_domodule
 # @USAGE: <files>...
 # @DESCRIPTION:
-# Install the given modules (or packages) into the current
-# python_moduleroot. The list can mention both modules (files)
+# 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.
 #
@@ -795,6 +856,9 @@ python_domodule() {
        debug-print-function ${FUNCNAME} "${@}"
 
        [[ ${EPYTHON} ]] || die 'No Python implementation set (EPYTHON is null).'
+       if [[ ${EAPI:-0} == [0123] ]]; then
+               die "python_do* and python_new* helpers are banned in EAPIs older than 4."
+       fi
 
        local d
        if [[ ${python_moduleroot} == /* ]]; then
@@ -805,13 +869,13 @@ python_domodule() {
                local PYTHON_SITEDIR=${PYTHON_SITEDIR}
                [[ ${PYTHON_SITEDIR} ]] || python_export PYTHON_SITEDIR
 
-               d=${PYTHON_SITEDIR#${EPREFIX}}/${python_moduleroot}
+               d=${PYTHON_SITEDIR#${EPREFIX}}/${python_moduleroot//.//}
        fi
 
-       local INSDESTTREE
-
-       insinto "${d}"
-       doins -r "${@}" || die
+       (
+               insinto "${d}"
+               doins -r "${@}" || return ${?}
+       )
 
        python_optimize "${ED}/${d}"
 }
@@ -833,16 +897,19 @@ python_doheader() {
        debug-print-function ${FUNCNAME} "${@}"
 
        [[ ${EPYTHON} ]] || die 'No Python implementation set (EPYTHON is null).'
+       if [[ ${EAPI:-0} == [0123] ]]; then
+               die "python_do* and python_new* helpers are banned in EAPIs older than 4."
+       fi
 
        local d PYTHON_INCLUDEDIR=${PYTHON_INCLUDEDIR}
        [[ ${PYTHON_INCLUDEDIR} ]] || python_export PYTHON_INCLUDEDIR
 
        d=${PYTHON_INCLUDEDIR#${EPREFIX}}
 
-       local INSDESTTREE
-
-       insinto "${d}"
-       doins -r "${@}" || die
+       (
+               insinto "${d}"
+               doins -r "${@}" || return ${?}
+       )
 }
 
 # @FUNCTION: python_wrapper_setup
@@ -874,11 +941,11 @@ python_wrapper_setup() {
                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/python{,2,3}{,-config} || die
                rm -f "${workdir}"/bin/2to3 || die
                rm -f "${workdir}"/pkgconfig/python{,2,3}.pc || die
 
-               local EPYTHON PYTHON PYTHON_CONFIG
+               local EPYTHON PYTHON
                python_export "${impl}" EPYTHON PYTHON
 
                local pyver pyother
@@ -894,7 +961,7 @@ python_wrapper_setup() {
                # 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_
+               cat > "${workdir}/bin/python" <<-_EOF_ || die
                        #!/bin/sh
                        exec "${PYTHON}" "\${@}"
                _EOF_
@@ -905,11 +972,9 @@ python_wrapper_setup() {
 
                # CPython-specific
                if [[ ${EPYTHON} == python* ]]; then
-                       python_export "${impl}" PYTHON_CONFIG
-
-                       cat > "${workdir}/bin/python-config" <<-_EOF_
+                       cat > "${workdir}/bin/python-config" <<-_EOF_ || die
                                #!/bin/sh
-                               exec "${PYTHON_CONFIG}" "\${@}"
+                               exec "${PYTHON}-config" "\${@}"
                        _EOF_
                        cp "${workdir}/bin/python-config" \
                                "${workdir}/bin/python${pyver}-config" || die
@@ -929,25 +994,25 @@ python_wrapper_setup() {
 
                local x
                for x in "${nonsupp[@]}"; do
-                       cat >"${workdir}"/bin/${x} <<__EOF__
-#!/bin/sh
-echo "${x} is not supported by ${EPYTHON}" >&2
-exit 127
-__EOF__
+                       cat >"${workdir}"/bin/${x} <<-_EOF_ || die
+                               #!/bin/sh
+                               echo "${x} is not supported by ${EPYTHON}" >&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
+       # 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
@@ -1041,12 +1106,14 @@ python_fix_shebang() {
                        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}
+                               read -r -a split_shebang <<<${shebang} || die
 
                                # Match left-to-right in a loop, to avoid matching random
                                # repetitions like 'python2.7 python2'.
@@ -1131,15 +1198,20 @@ python_fix_shebang() {
                                eerror "  requested impl: ${EPYTHON}"
                                die "${FUNCNAME}: conversion of incompatible shebang requested"
                        fi
-               done < <(find "${path}" -type f -print0 || die)
+               done < <(find -H "${path}" -type f -print0 || die)
 
                if [[ ! ${any_fixed} ]]; then
-                       eqawarn "QA warning: ${FUNCNAME}, ${path#${D}} did not match any fixable files."
+                       local cmd=eerror
+                       [[ ${EAPI:-0} == [012345] ]] && cmd=eqawarn
+
+                       "${cmd}" "QA warning: ${FUNCNAME}, ${path#${D}} did not match any fixable files."
                        if [[ ${any_correct} ]]; then
-                               eqawarn "All files have ${EPYTHON} shebang already."
+                               "${cmd}" "All files have ${EPYTHON} shebang already."
                        else
-                               eqawarn "There are no Python files in specified directory."
+                               "${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
 }
@@ -1151,12 +1223,12 @@ python_fix_shebang() {
 # Check whether the specified locale sanely maps between lowercase
 # and uppercase ASCII characters.
 _python_check_locale_sanity() {
-       local -x LC_CTYPE=${1}
+       local -x LC_ALL=${1}
        local IFS=
 
        local lc=( {a..z} )
        local uc=( {A..Z} )
-       local input=${lc[*]}${uc[*]}
+       local input="${lc[*]}${uc[*]}"
 
        local output=$(tr '[:lower:][:upper:]' '[:upper:][:lower:]' <<<"${input}")
        [[ ${output} == "${uc[*]}${lc[*]}" ]]
@@ -1175,29 +1247,37 @@ python_export_utf8_locale() {
        type locale >/dev/null || return 0
 
        if [[ $(locale charmap) != UTF-8 ]]; then
-               if [[ -n ${LC_ALL} ]]; then
-                       ewarn "LC_ALL is set to a locale with a charmap other than UTF-8."
-                       ewarn "This may trigger build failures in some python packages."
-                       return 1
-               fi
-
                # Try English first, then everything else.
-               local lang locales="en_US.UTF-8 $(locale -a)"
+               local lang locales="C.UTF-8 en_US.UTF-8 en_GB.UTF-8 $(locale -a)"
 
                for lang in ${locales}; do
-                       if [[ $(LC_CTYPE=${lang} locale charmap 2>/dev/null) == UTF-8 ]]; then
+                       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  
+                       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  
+       fi
 
        return 0
 }