Gentoo Archives: gentoo-dev

From: James Le Cuirot <chewi@g.o>
To: gentoo-dev@l.g.o
Subject: Re: [gentoo-dev] [PATCH 04/12] python-utils-r1.eclass: Use wrapper scripts to fix cross-compiling
Date: Sat, 05 Jan 2019 21:30:58
Message-Id: 20190105213037.08e89126@symphony.aura-online.co.uk
In Reply to: Re: [gentoo-dev] [PATCH 04/12] python-utils-r1.eclass: Use wrapper scripts to fix cross-compiling by "Michał Górny"
1 On Fri, 04 Jan 2019 16:53:46 +0100
2 Michał Górny <mgorny@g.o> wrote:
3
4 > On Thu, 2019-01-03 at 21:39 +0000, James Le Cuirot wrote:
5 > > Python has little concept of cross-compiling but it turns out that
6 > > making it work isn't so hard after all.
7 > >
8 > > Platform-specific details are held within _sysconfigdata.py,
9 > > sysconfig.py, and various distutils files. If we simply symlink these
10 > > from SYSROOT into an empty directory and add that directory to
11 > > PYTHONPATH then we can utilise the build host's Python with the target
12 > > host's settings.
13 > >
14 > > The pkg-config files were already being symlinked in a similar manner
15 > > but now we source them from within SYSROOT.
16 > >
17 > > In order for PYTHONPATH to be respected, Python must be executed via
18 > > the wrapper, which was not the case before. We previously relied
19 > > solely on the PATH but now PYTHON must point to the wrapper rather
20 > > than the usual location under /usr/bin. However, we only do this when
21 > > SYSROOT or EPREFIX are effectively set to avoid unnecessary
22 > > complexity. This has required some rejigging in the way that PYTHON is
23 > > set but it should remain compatible with existing packages.
24 > >
25 > > The python_wrapper_setup function that handles all this has had its
26 > > arguments reordered because no one ever uses the path/workdir
27 > > argument, which makes specifying other arguments tedious.
28 >
29 > This really belongs in a separate patch.
30
31 Fair enough.
32
33 > > Some packages rely on python-config but luckily this is just a shell
34 > > script so it can be executed from within SYSROOT. This is bending the
35 > > rules of PMS slightly but Python leaves us with little choice. I also
36 > > wrote those rules so I'm allowed to bend them. ;)
37 > >
38 > > PYTHON_INCLUDEDIR, PYTHON_LIBPATH, and their associated functions are
39 > > generally used during src_configure or src_compile and, as such, they
40 > > now need to have SYSROOT prepended.
41 > >
42 > > python_doheader uses PYTHON_INCLUDEDIR to install new headers and
43 > > therefore needs the value without SYSROOT. It was already stripping
44 > > EPREFIX before so now it simply strips SYSROOT as well. Similar
45 > > instances of this can do likewise or call the functions with SYSROOT
46 > > unset to achieve the same effect.
47 > >
48 > > To make all this work, we are assuming that CPython is located at
49 > > /usr/$(get_libdir)/${EPYTHON}, which is admittedly a little circular
50 > > when we are querying Python for the right paths. I feel the reason
51 > > that python_export was rewritten to query these dynamically was less
52 > > because someone might install Python to some weird location and more
53 > > to avoid special handling for each of the different
54 > > implementations. And what of those other implementations?
55 >
56 > This is a wrong assumption. CPython 3.7 is in /usr/lib/python3.7.
57
58 It was true at the time I wrote it. I addressed 3.7 later in the patch
59 series. I figured this diff was long enough already so I kept it
60 separate.
61
62 > > Being Java-based, Jython is installed under the platform-neutral
63 > > /usr/share and presumably has few other platform-specific
64 > > aspects. Enabling native extensions appears non-trivial and none of
65 > > our module packages have enabled support for it anyway.
66 > >
67 > > I think PyPy could potentially support cross-compiling but it
68 > > hardcodes the native extension filename suffix within its own binaries
69 > > with no way to override it. Perhaps we could patch this in somehow but
70 > > I'm afraid I don't care enough.
71 > >
72 > > Together with the following changes to distutils-r1.eclass, I have now
73 > > been able to cross-compile a large number of packages with native
74 > > Python extensions, most with no changes at all, and the rest with only
75 > > minor fixes.
76 > >
77 > > Closes: https://bugs.gentoo.org/503874
78 > > Bug: https://bugs.gentoo.org/648652
79 > > Signed-off-by: James Le Cuirot <chewi@g.o>
80 > > ---
81 > > eclass/python-utils-r1.eclass | 120 ++++++++++++++++++++++++++--------
82 > > 1 file changed, 92 insertions(+), 28 deletions(-)
83 > >
84 > > diff --git a/eclass/python-utils-r1.eclass b/eclass/python-utils-r1.eclass
85 > > index 1a549f49f3f2..607af1b52f75 100644
86 > > --- a/eclass/python-utils-r1.eclass
87 > > +++ b/eclass/python-utils-r1.eclass
88 > > @@ -211,9 +211,15 @@ _python_impl_matches() {
89 > > #
90 > > # distutils-r1: Set within any of the python sub-phase functions.
91 > > #
92 > > -# Example value:
93 > > +# If SYSROOT or EPREFIX are effectively set then this will point to an
94 > > +# automatically generated wrapper rather than the usual path under
95 > > +# /usr/bin in order to accommodate cross-compiling. We could do this all
96 > > +# the time but it would add unnecessary complexity.
97 > > +#
98 > > +# Example values:
99 > > # @CODE
100 > > # /usr/bin/python2.7
101 > > +# /var/tmp/portage/dev-python/pyblake2-1.2.3/temp/python2.7/bin/python2.7
102 > > # @CODE
103 > >
104 > > # @ECLASS-VARIABLE: EPYTHON
105 > > @@ -256,6 +262,10 @@ _python_impl_matches() {
106 > > # Set and exported on request using python_export().
107 > > # Requires a proper build-time dependency on the Python implementation.
108 > > #
109 > > +# This is prepended with SYSROOT in order to accommodate
110 > > +# cross-compiling. You may need to strip SYSROOT and EPREFIX if using it
111 > > +# to install new headers.
112 > > +#
113 > > # Example value:
114 > > # @CODE
115 > > # /usr/include/python2.7
116 > > @@ -270,6 +280,9 @@ _python_impl_matches() {
117 > > # Valid only for CPython. Requires a proper build-time dependency
118 > > # on the Python implementation.
119 > > #
120 > > +# This is prepended with SYSROOT in order to accommodate
121 > > +# cross-compiling.
122 > > +#
123 > > # Example value:
124 > > # @CODE
125 > > # /usr/lib64/libpython2.7.so
126 > > @@ -314,6 +327,10 @@ _python_impl_matches() {
127 > > # Valid only for CPython. Requires a proper build-time dependency
128 > > # on the Python implementation and on pkg-config.
129 > > #
130 > > +# This is prepended with SYSROOT in order to accommodate
131 > > +# cross-compiling. You generally should not execute files within SYSROOT
132 > > +# but python-config is always a shell script.
133 > > +#
134 > > # Example value:
135 > > # @CODE
136 > > # /usr/bin/python2.7-config
137 > > @@ -380,6 +397,10 @@ python_export() {
138 > > esac
139 > > debug-print "${FUNCNAME}: implementation: ${impl}"
140 > >
141 > > + # Many variables below need a PYTHON variable but we should not
142 > > + # export it unless explicitly requested so use _PYTHON instead.
143 > > + local _PYTHON
144 > > +
145 > > for var; do
146 > > case "${var}" in
147 > > EPYTHON)
148 > > @@ -387,21 +408,21 @@ python_export() {
149 > > debug-print "${FUNCNAME}: EPYTHON = ${EPYTHON}"
150 > > ;;
151 > > PYTHON)
152 > > - export PYTHON=${EPREFIX}/usr/bin/${impl}
153 > > + python_wrapper_setup ${impl} PYTHON
154 > > debug-print "${FUNCNAME}: PYTHON = ${PYTHON}"
155 > > ;;
156 > > PYTHON_SITEDIR)
157 > > - [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it"
158 > > + python_wrapper_setup ${impl} _PYTHON
159 > > # sysconfig can't be used because:
160 > > # 1) pypy doesn't give site-packages but stdlib
161 > > # 2) jython gives paths with wrong case
162 > > - PYTHON_SITEDIR=$("${PYTHON}" -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_lib())') || die
163 > > + PYTHON_SITEDIR=$("${_PYTHON}" -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_lib())') || die
164 > > export PYTHON_SITEDIR
165 > > debug-print "${FUNCNAME}: PYTHON_SITEDIR = ${PYTHON_SITEDIR}"
166 > > ;;
167 > > PYTHON_INCLUDEDIR)
168 > > - [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it"
169 > > - PYTHON_INCLUDEDIR=$("${PYTHON}" -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_inc())') || die
170 > > + python_wrapper_setup ${impl} _PYTHON
171 > > + PYTHON_INCLUDEDIR=${SYSROOT}$("${_PYTHON}" -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_inc())') || die
172 > > export PYTHON_INCLUDEDIR
173 > > debug-print "${FUNCNAME}: PYTHON_INCLUDEDIR = ${PYTHON_INCLUDEDIR}"
174 > >
175 > > @@ -411,8 +432,8 @@ python_export() {
176 > > fi
177 > > ;;
178 > > PYTHON_LIBPATH)
179 > > - [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it"
180 > > - 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
181 > > + python_wrapper_setup ${impl} _PYTHON
182 > > + 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
183 > > export PYTHON_LIBPATH
184 > > debug-print "${FUNCNAME}: PYTHON_LIBPATH = ${PYTHON_LIBPATH}"
185 > >
186 > > @@ -457,9 +478,9 @@ python_export() {
187 > >
188 > > case "${impl}" in
189 > > python*)
190 > > - [[ -n ${PYTHON} ]] || die "PYTHON needs to be set for ${var} to be exported, or requested before it"
191 > > - flags=$("${PYTHON}" -c 'import sysconfig; print(sysconfig.get_config_var("ABIFLAGS") or "")') || die
192 > > - val=${PYTHON}${flags}-config
193 > > + python_wrapper_setup ${impl} _PYTHON
194 > > + flags=$("${_PYTHON}" -c 'import sysconfig; print(sysconfig.get_config_var("ABIFLAGS") or "")') || die
195 > > + val=${ESYSROOT-${SYSROOT}${EPREFIX}}/usr/bin/${PYTHON##*/}${flags}-config
196 > > ;;
197 > > *)
198 > > die "${impl}: obtaining ${var} not supported"
199 > > @@ -954,7 +975,7 @@ python_doheader() {
200 > > local d PYTHON_INCLUDEDIR=${PYTHON_INCLUDEDIR}
201 > > [[ ${PYTHON_INCLUDEDIR} ]] || python_export PYTHON_INCLUDEDIR
202 > >
203 > > - d=${PYTHON_INCLUDEDIR#${EPREFIX}}
204 > > + d=${PYTHON_INCLUDEDIR#${ESYSROOT-${SYSROOT}${EPREFIX}}}
205 > >
206 > > (
207 > > insopts -m 0644
208 > > @@ -964,7 +985,7 @@ python_doheader() {
209 > > }
210 > >
211 > > # @FUNCTION: python_wrapper_setup
212 > > -# @USAGE: [<path> [<impl>]]
213 > > +# @USAGE: [<impl> [<pyvar> [<path>]]]
214 > > # @DESCRIPTION:
215 > > # Create proper 'python' executable and pkg-config wrappers
216 > > # (if available) in the directory named by <path>. Set up PATH
217 > > @@ -973,6 +994,9 @@ python_doheader() {
218 > > # The wrappers will be created for implementation named by <impl>,
219 > > # or for one named by ${EPYTHON} if no <impl> passed.
220 > > #
221 > > +# The path to the 'python' executable wrapper is exported to the
222 > > +# variable named <pyvar> if given.
223 >
224 > Why do you need that in the first place? The path should be rather
225 > predictable, shouldn't it?
226
227 I thought about keeping the logic of the PYTHON value back in
228 python_export but it's quite likely someone would export PYTHON without
229 calling python_wrapper_setup and so it would break. If we do that while
230 keeping the call to python_wrapper_setup, you'd need to handle infinite
231 loops for when python_wrapper_setup is called from elsewhere.
232
233 I initially had it export PYTHON unconditionally but later realised
234 this was impolite and actually caused a problem in dev-lang/python
235 itself. I guess we could export using another variable name but this
236 seemed tidier.
237
238 The only other way to pass the value back from python_wrapper_setup
239 would be to echo it but that seems ugly and would require amending all
240 the existing invocations.
241
242 > > +#
243 > > # If the named directory contains a python symlink already, it will
244 > > # be assumed to contain proper wrappers already and only environment
245 > > # setup will be done. If wrapper update is requested, the directory
246 > > @@ -980,25 +1004,41 @@ python_doheader() {
247 > > python_wrapper_setup() {
248 > > debug-print-function ${FUNCNAME} "${@}"
249 > >
250 > > - local impl=${2:-${EPYTHON}}
251 > > - local workdir=${1:-${T}/${impl}}
252 > > + local impl=${1:-${EPYTHON}}
253 > > + local pyvar=${2}
254 > > + local lpyvar=_${pyvar:-PYTHON}
255 > > +
256 > > + # Use separate directories for SYSROOT in case we need to execute
257 > > + # Python in the context of the build host by unsetting SYSROOT.
258 > > + local workdir=${3:-${T}/${impl}${SYSROOT:+-sysroot}}
259 > >
260 > > [[ ${workdir} ]] || die "${FUNCNAME}: no workdir specified."
261 > > [[ ${impl} ]] || die "${FUNCNAME}: no impl nor EPYTHON specified."
262 > >
263 > > + local EPYTHON
264 > > + python_export "${impl}" EPYTHON
265 >
266 > I'm getting dizzy because of the amount of circular dependencies between
267 > the two functions you've introduced. This is going to make it trivial
268 > to deadlock it when changing code later.
269
270 Look below, apart from my removing PYTHON, these lines were already
271 there! Indeed, this made my head hurt too but I tried my best to
272 respect how the eclass works now. If you want to simplify it somehow,
273 I'm totally up for that discussion.
274
275 > > +
276 > > + # We could use BROOT here but using the PATH accommodates
277 > > + # cross-prefix where the PATH is sometimes manipulated to prefer
278 > > + # build tools from the target prefix (i.e. EPREFIX).
279 > > + #
280 > > + # Also make sure we don't pick up an existing wrapper by replacing
281 > > + # instances of ${T} with a bogus location. The workdir can be
282 > > + # overridden but hopefully it will be somewhere under ${T}.
283 > > + local ${lpyvar}=$(PATH=${PATH//${T}//dev/null} type -P "${EPYTHON}" || die "${FUNCNAME}: can't find ${EPYTHON} in PATH")
284 >
285 > This PATH substitution hack is horrible.
286
287 It's not my favourite part of the changeset, I'll grant you, but the
288 PATH value only exists for that one line and I wanted to be sure that
289 type -P didn't pick up some other location. I could strip out ${T} more
290 accurately but that would be more code for little benefit. Your call.
291
292 > > +
293 > > + local pysysroot=${ESYSROOT-${SYSROOT%/}${EPREFIX}}
294 > > +
295 > > if [[ ! -x ${workdir}/bin/python ]]; then
296 > > _python_check_dead_variables
297 > >
298 > > - mkdir -p "${workdir}"/{bin,pkgconfig} || die
299 > > + mkdir -p "${workdir}"/{bin,lib,pkgconfig} || die
300 > >
301 > > # Clean up, in case we were supposed to do a cheap update.
302 > > rm -f "${workdir}"/bin/python{,2,3}{,-config} || die
303 > > rm -f "${workdir}"/bin/2to3 || die
304 > > rm -f "${workdir}"/pkgconfig/python{,2,3}.pc || die
305 > >
306 > > - local EPYTHON PYTHON
307 > > - python_export "${impl}" EPYTHON PYTHON
308 > > -
309 > > local pyver pyother
310 > > if python_is_python3; then
311 > > pyver=3
312 > > @@ -1012,37 +1052,53 @@ python_wrapper_setup() {
313 > > # note: we don't use symlinks because python likes to do some
314 > > # symlink reading magic that breaks stuff
315 > > # https://bugs.gentoo.org/show_bug.cgi?id=555752
316 > > - cat > "${workdir}/bin/python" <<-_EOF_ || die
317 > > - #!/bin/sh
318 > > - exec "${PYTHON}" "\${@}"
319 > > - _EOF_
320 > > - cp "${workdir}/bin/python" "${workdir}/bin/python${pyver}" || die
321 > > - chmod +x "${workdir}/bin/python" "${workdir}/bin/python${pyver}" || die
322 > > + echo '#!/bin/sh' > "${workdir}/bin/python" || die
323 > > local nonsupp=( "python${pyother}" "python${pyother}-config" )
324 > >
325 > > # CPython-specific
326 > > if [[ ${EPYTHON} == python* ]]; then
327 > > + local pysysrootlib=${pysysroot}/usr/$(get_libdir)
328 > > +
329 > > cat > "${workdir}/bin/python-config" <<-_EOF_ || die
330 > > #!/bin/sh
331 > > - exec "${PYTHON}-config" "\${@}"
332 > > + exec "${pysysroot}/usr/bin/${EPYTHON}-config" "\${@}"
333 > > _EOF_
334 > > cp "${workdir}/bin/python-config" \
335 > > "${workdir}/bin/python${pyver}-config" || die
336 > > chmod +x "${workdir}/bin/python-config" \
337 > > "${workdir}/bin/python${pyver}-config" || die
338 > >
339 > > + if [[ -n ${pysysroot} ]]; then
340 > > + # Use host-specific data from SYSROOT. Python 2 looks
341 > > + # for _sysconfigdata while Python 3 uses
342 > > + # _PYTHON_SYSCONFIGDATA_NAME.
343 > > + ln -s "${pysysrootlib}/${EPYTHON}"/_sysconfigdata*.py "${workdir}"/lib/_sysconfigdata.py || die
344 > > +
345 > > + # Use distutils/sysconfig from SYSROOT as parts of it
346 > > + # have GENTOO_LIBDIR baked in at Python build time.
347 > > + ln -s "${pysysrootlib}/${EPYTHON}"/{distutils,sysconfig.py} "${workdir}"/lib/ || die
348 > > +
349 > > + # Add env vars to python wrapper accordingly.
350 > > + echo "PYTHONPATH=\"${workdir}/lib\${PYTHONPATH:+:}\${PYTHONPATH}\" _PYTHON_SYSCONFIGDATA_NAME=_sysconfigdata \\" \
351 > > + >> "${workdir}/bin/python" || die
352 >
353 > Use explicit 'export ...' multi-line thing for readability.
354
355 Okay, I guess using a here document wouldn't be so bad.
356
357 > > + fi
358 > > +
359 > > # Python 2.6+.
360 > > - ln -s "${PYTHON/python/2to3-}" "${workdir}"/bin/2to3 || die
361 > > + ln -s "${EPYTHON/python/2to3-}" "${workdir}"/bin/2to3 || die
362 > >
363 > > # Python 2.7+.
364 > > - ln -s "${EPREFIX}"/usr/$(get_libdir)/pkgconfig/${EPYTHON/n/n-}.pc \
365 > > + ln -s "${pysysrootlib}"/pkgconfig/${EPYTHON/n/n-}.pc \
366 > > "${workdir}"/pkgconfig/python.pc || die
367 > > ln -s python.pc "${workdir}"/pkgconfig/python${pyver}.pc || die
368 > > else
369 > > nonsupp+=( 2to3 python-config "python${pyver}-config" )
370 > > fi
371 > >
372 > > + echo "exec \"${!lpyvar}\" \"\${@}\"" >> "${workdir}"/bin/python || die
373 > > + tee "${workdir}"/bin/{python${pyver},"${EPYTHON}"} < "${workdir}"/bin/python >/dev/null || die
374 >
375 > Whatever this is supposed to do, do it simpler.
376
377 I suppose the one line it saves isn't worth the extra brain time.
378
379 > > + chmod +x "${workdir}"/bin/{python,python${pyver},"${EPYTHON}"} || die
380 > > +
381 > > local x
382 > > for x in "${nonsupp[@]}"; do
383 > > cat >"${workdir}"/bin/${x} <<-_EOF_ || die
384 > > @@ -1064,6 +1120,14 @@ python_wrapper_setup() {
385 > > PKG_CONFIG_PATH=${workdir}/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}
386 > > fi
387 > > export PATH PKG_CONFIG_PATH
388 > > +
389 > > + if [[ -n ${pyvar} ]]; then
390 > > + if [[ -n ${pysysroot} ]]; then
391 > > + export -- ${pyvar}=${workdir}/bin/${EPYTHON}
392 > > + else
393 > > + export -- ${pyvar}=${!lpyvar}
394 > > + fi
395 > > + fi
396 >
397 > This is just plain ugly. No.
398
399 Could you be a bit more specific? Whether we make it conditional or
400 not, we need to export something. I have played it safe by only
401 pointing to the wrapper when SYSROOT or EPREFIX are effectively set. If
402 you'd prefer to just use the wrapper all the time then we can do that
403 but I imagine you'd rather minimise the risk to non-cross/prefix users.
404 If ${!lpyvar} is the issue, I did this to ensure that our local
405 variable name doesn't clash with the one being exported but we could
406 just use some fixed obscure name instead.
407
408 > > }
409 > >
410 > > # @FUNCTION: python_is_python3
411 >
412
413
414 --
415 James Le Cuirot (chewi)
416 Gentoo Linux Developer