Python has little concept of cross-compiling but it turns out that making it work isn't so hard after all.
Platform-specific details are held within _sysconfigdata.py, sysconfig.py, and various distutils files. If we simply symlink these from SYSROOT into an empty directory and add that directory to PYTHONPATH then we can utilise the build host's Python with the target host's settings. The pkg-config files were already being symlinked in a similar manner but now we source them from within SYSROOT. In order for PYTHONPATH to be respected, Python must be executed via the wrapper, which was not the case before. We previously relied solely on the PATH but now PYTHON must point to the wrapper rather than the usual location under /usr/bin. However, we only do this when SYSROOT or EPREFIX are effectively set to avoid unnecessary complexity. This has required some rejigging in the way that PYTHON is set but it should remain compatible with existing packages. The python_wrapper_setup function that handles all this has had its arguments reordered because no one ever uses the path/workdir argument, which makes specifying other arguments tedious. Some packages rely on python-config but luckily this is just a shell script so it can be executed from within SYSROOT. This is bending the rules of PMS slightly but Python leaves us with little choice. I also wrote those rules so I'm allowed to bend them. ;) PYTHON_INCLUDEDIR, PYTHON_LIBPATH, and their associated functions are generally used during src_configure or src_compile and, as such, they now need to have SYSROOT prepended. python_doheader uses PYTHON_INCLUDEDIR to install new headers and therefore needs the value without SYSROOT. It was already stripping EPREFIX before so now it simply strips SYSROOT as well. Similar instances of this can do likewise or call the functions with SYSROOT unset to achieve the same effect. To make all this work, we are assuming that CPython is located at /usr/$(get_libdir)/${EPYTHON}, which is admittedly a little circular when we are querying Python for the right paths. I feel the reason that python_export was rewritten to query these dynamically was less because someone might install Python to some weird location and more to avoid special handling for each of the different implementations. And what of those other implementations? Being Java-based, Jython is installed under the platform-neutral /usr/share and presumably has few other platform-specific aspects. Enabling native extensions appears non-trivial and none of our module packages have enabled support for it anyway. I think PyPy could potentially support cross-compiling but it hardcodes the native extension filename suffix within its own binaries with no way to override it. Perhaps we could patch this in somehow but I'm afraid I don't care enough. Together with the following changes to distutils-r1.eclass, I have now been able to cross-compile a large number of packages with native Python extensions, most with no changes at all, and the rest with only minor fixes. Closes: https://bugs.gentoo.org/503874 Bug: https://bugs.gentoo.org/648652 Signed-off-by: James Le Cuirot <ch...@gentoo.org> --- eclass/python-utils-r1.eclass | 120 ++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 28 deletions(-) diff --git a/eclass/python-utils-r1.eclass b/eclass/python-utils-r1.eclass index 1a549f49f3f2..607af1b52f75 100644 --- a/eclass/python-utils-r1.eclass +++ b/eclass/python-utils-r1.eclass @@ -211,9 +211,15 @@ _python_impl_matches() { # # distutils-r1: Set within any of the python sub-phase functions. # -# Example value: +# If SYSROOT or EPREFIX are effectively set then this will point to an +# automatically generated wrapper rather than the usual path under +# /usr/bin in order to accommodate cross-compiling. We could do this all +# the time but it would add unnecessary complexity. +# +# Example values: # @CODE # /usr/bin/python2.7 +# /var/tmp/portage/dev-python/pyblake2-1.2.3/temp/python2.7/bin/python2.7 # @CODE # @ECLASS-VARIABLE: EPYTHON @@ -256,6 +262,10 @@ _python_impl_matches() { # Set and exported on request using python_export(). # Requires a proper build-time dependency on the Python implementation. # +# This is prepended with SYSROOT in order to accommodate +# cross-compiling. You may need to strip SYSROOT and EPREFIX if using it +# to install new headers. +# # Example value: # @CODE # /usr/include/python2.7 @@ -270,6 +280,9 @@ _python_impl_matches() { # Valid only for CPython. Requires a proper build-time dependency # on the Python implementation. # +# This is prepended with SYSROOT in order to accommodate +# cross-compiling. +# # Example value: # @CODE # /usr/lib64/libpython2.7.so @@ -314,6 +327,10 @@ _python_impl_matches() { # Valid only for CPython. Requires a proper build-time dependency # on the Python implementation and on pkg-config. # +# This is prepended with SYSROOT in order to accommodate +# cross-compiling. You generally should not execute files within SYSROOT +# but python-config is always a shell script. +# # Example value: # @CODE # /usr/bin/python2.7-config @@ -380,6 +397,10 @@ python_export() { esac debug-print "${FUNCNAME}: implementation: ${impl}" + # Many variables below need a PYTHON variable but we should not + # export it unless explicitly requested so use _PYTHON instead. + local _PYTHON + for var; do case "${var}" in EPYTHON) @@ -387,21 +408,21 @@ python_export() { debug-print "${FUNCNAME}: EPYTHON = ${EPYTHON}" ;; PYTHON) - export PYTHON=${EPREFIX}/usr/bin/${impl} + python_wrapper_setup ${impl} PYTHON debug-print "${FUNCNAME}: PYTHON = ${PYTHON}" ;; PYTHON_SITEDIR) - [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it" + python_wrapper_setup ${impl} _PYTHON # 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 + 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 + python_wrapper_setup ${impl} _PYTHON + PYTHON_INCLUDEDIR=${SYSROOT}$("${_PYTHON}" -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_inc())') || die export PYTHON_INCLUDEDIR debug-print "${FUNCNAME}: PYTHON_INCLUDEDIR = ${PYTHON_INCLUDEDIR}" @@ -411,8 +432,8 @@ python_export() { 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 + python_wrapper_setup ${impl} _PYTHON + PYTHON_LIBPATH=${SYSROOT}$("${_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}" @@ -457,9 +478,9 @@ python_export() { 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 + python_wrapper_setup ${impl} _PYTHON + flags=$("${_PYTHON}" -c 'import sysconfig; print(sysconfig.get_config_var("ABIFLAGS") or "")') || die + val=${ESYSROOT-${SYSROOT}${EPREFIX}}/usr/bin/${PYTHON##*/}${flags}-config ;; *) die "${impl}: obtaining ${var} not supported" @@ -954,7 +975,7 @@ python_doheader() { local d PYTHON_INCLUDEDIR=${PYTHON_INCLUDEDIR} [[ ${PYTHON_INCLUDEDIR} ]] || python_export PYTHON_INCLUDEDIR - d=${PYTHON_INCLUDEDIR#${EPREFIX}} + d=${PYTHON_INCLUDEDIR#${ESYSROOT-${SYSROOT}${EPREFIX}}} ( insopts -m 0644 @@ -964,7 +985,7 @@ python_doheader() { } # @FUNCTION: python_wrapper_setup -# @USAGE: [<path> [<impl>]] +# @USAGE: [<impl> [<pyvar> [<path>]]] # @DESCRIPTION: # Create proper 'python' executable and pkg-config wrappers # (if available) in the directory named by <path>. Set up PATH @@ -973,6 +994,9 @@ python_doheader() { # The wrappers will be created for implementation named by <impl>, # or for one named by ${EPYTHON} if no <impl> passed. # +# The path to the 'python' executable wrapper is exported to the +# variable named <pyvar> if given. +# # 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 @@ -980,25 +1004,41 @@ python_doheader() { python_wrapper_setup() { debug-print-function ${FUNCNAME} "${@}" - local impl=${2:-${EPYTHON}} - local workdir=${1:-${T}/${impl}} + local impl=${1:-${EPYTHON}} + local pyvar=${2} + local lpyvar=_${pyvar:-PYTHON} + + # Use separate directories for SYSROOT in case we need to execute + # Python in the context of the build host by unsetting SYSROOT. + local workdir=${3:-${T}/${impl}${SYSROOT:+-sysroot}} [[ ${workdir} ]] || die "${FUNCNAME}: no workdir specified." [[ ${impl} ]] || die "${FUNCNAME}: no impl nor EPYTHON specified." + local EPYTHON + python_export "${impl}" EPYTHON + + # We could use BROOT here but using the PATH accommodates + # cross-prefix where the PATH is sometimes manipulated to prefer + # build tools from the target prefix (i.e. EPREFIX). + # + # Also make sure we don't pick up an existing wrapper by replacing + # instances of ${T} with a bogus location. The workdir can be + # overridden but hopefully it will be somewhere under ${T}. + local ${lpyvar}=$(PATH=${PATH//${T}//dev/null} type -P "${EPYTHON}" || die "${FUNCNAME}: can't find ${EPYTHON} in PATH") + + local pysysroot=${ESYSROOT-${SYSROOT%/}${EPREFIX}} + if [[ ! -x ${workdir}/bin/python ]]; then _python_check_dead_variables - mkdir -p "${workdir}"/{bin,pkgconfig} || die + mkdir -p "${workdir}"/{bin,lib,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 @@ -1012,37 +1052,53 @@ 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_ || 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 + echo '#!/bin/sh' > "${workdir}/bin/python" || die local nonsupp=( "python${pyother}" "python${pyother}-config" ) # CPython-specific if [[ ${EPYTHON} == python* ]]; then + local pysysrootlib=${pysysroot}/usr/$(get_libdir) + cat > "${workdir}/bin/python-config" <<-_EOF_ || die #!/bin/sh - exec "${PYTHON}-config" "\${@}" + exec "${pysysroot}/usr/bin/${EPYTHON}-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 + if [[ -n ${pysysroot} ]]; then + # Use host-specific data from SYSROOT. Python 2 looks + # for _sysconfigdata while Python 3 uses + # _PYTHON_SYSCONFIGDATA_NAME. + ln -s "${pysysrootlib}/${EPYTHON}"/_sysconfigdata*.py "${workdir}"/lib/_sysconfigdata.py || die + + # Use distutils/sysconfig from SYSROOT as parts of it + # have GENTOO_LIBDIR baked in at Python build time. + ln -s "${pysysrootlib}/${EPYTHON}"/{distutils,sysconfig.py} "${workdir}"/lib/ || die + + # Add env vars to python wrapper accordingly. + echo "PYTHONPATH=\"${workdir}/lib\${PYTHONPATH:+:}\${PYTHONPATH}\" _PYTHON_SYSCONFIGDATA_NAME=_sysconfigdata \\" \ + >> "${workdir}/bin/python" || die + fi + # Python 2.6+. - ln -s "${PYTHON/python/2to3-}" "${workdir}"/bin/2to3 || die + ln -s "${EPYTHON/python/2to3-}" "${workdir}"/bin/2to3 || die # Python 2.7+. - ln -s "${EPREFIX}"/usr/$(get_libdir)/pkgconfig/${EPYTHON/n/n-}.pc \ + ln -s "${pysysrootlib}"/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 + echo "exec \"${!lpyvar}\" \"\${@}\"" >> "${workdir}"/bin/python || die + tee "${workdir}"/bin/{python${pyver},"${EPYTHON}"} < "${workdir}"/bin/python >/dev/null || die + chmod +x "${workdir}"/bin/{python,python${pyver},"${EPYTHON}"} || die + local x for x in "${nonsupp[@]}"; do cat >"${workdir}"/bin/${x} <<-_EOF_ || die @@ -1064,6 +1120,14 @@ python_wrapper_setup() { PKG_CONFIG_PATH=${workdir}/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}} fi export PATH PKG_CONFIG_PATH + + if [[ -n ${pyvar} ]]; then + if [[ -n ${pysysroot} ]]; then + export -- ${pyvar}=${workdir}/bin/${EPYTHON} + else + export -- ${pyvar}=${!lpyvar} + fi + fi } # @FUNCTION: python_is_python3 -- 2.19.2