Gentoo Archives: gentoo-dev

From: "Michał Górny" <mgorny@g.o>
To: gentoo-dev@l.g.o
Cc: "Michał Górny" <mgorny@g.o>
Subject: [gentoo-dev] [PATCH] distutils-r1.eclass: Implement PEP517 mode
Date: Thu, 13 Jan 2022 17:42:34
Message-Id: 20220113174220.2732026-1-mgorny@gentoo.org
1 Add a PEP517 mode to the distutils-r1.eclass in order to facilitate
2 building packages via PEP517 backends. In order to use it, set
3 DISTUTILS_USE_PEP517 to the appropriate build system name. The eclass
4 will take care of setting BDEPEND, then invoke the backend to build
5 a wheel and then install its contents in python_compile(). The install
6 phase is limited to merging the staging area into the image directory.
7
8 In PEP517 mode, the test phase is automatically provided with venv-style
9 install tree that should suffice the vast majority of test suites.
10 As a result, distutils_install_for_testing should no longer be necessary
11 and is not available in this mode.
12
13 The new mode can also be used to install pre-PEP517 distutils
14 and setuptools packages. To do so, just specify setuptools backend.
15 If pyproject.toml is missing, the eclass assumes legacy setuptools
16 backend that invokes setup.py. It also enables setuptools-vendored
17 distutils, effectively carrying the migration from deprecated stdlib
18 version.
19
20 The PEP517 support effectively deprecates the legacy eclass mode.
21 This follows upstream deprecation of distutils and install commands
22 in setuptools.
23
24 Signed-off-by: Michał Górny <mgorny@g.o>
25 ---
26 eclass/distutils-r1.eclass | 370 ++++++++++++++++++++++++++++---------
27 1 file changed, 282 insertions(+), 88 deletions(-)
28
29
30 For a few examples of packages using the new mode, please see:
31 https://github.com/gentoo/gentoo/pull/23721
32
33 diff --git a/eclass/distutils-r1.eclass b/eclass/distutils-r1.eclass
34 index ba0226f8fed3..5c1a07d22d21 100644
35 --- a/eclass/distutils-r1.eclass
36 +++ b/eclass/distutils-r1.eclass
37 @@ -78,7 +78,33 @@ esac
38 # to be exported. It must be run in order for the eclass functions
39 # to function properly.
40
41 +# @ECLASS-VARIABLE: DISTUTILS_USE_PEP517
42 +# @PRE_INHERIT
43 +# @DEFAULT_UNSET
44 +# @DESCRIPTION:
45 +# Enable experimental PEP 517 mode for the specified build system.
46 +# In this mode, the complete build and install is done
47 +# in python_compile(), venv-style install tree is provided
48 +# to python_test() and python_install() just merges the temporary
49 +# install tree into real fs.
50 +#
51 +# The variable specifies the build system used. Currently,
52 +# the following values are supported:
53 +#
54 +# - flit - flit_core backend
55 +#
56 +# - pdm - pdm.pep517 backend
57 +#
58 +# - setuptools - distutils or setuptools (incl. legacy mode)
59 +#
60 +# - standalone - standalone build systems without external deps
61 +# (used for bootstrapping).
62 +#
63 +# The variable needs to be set before the inherit line. The eclass
64 +# adds appropriate build-time dependencies and verifies the value.
65 +
66 # @ECLASS-VARIABLE: DISTUTILS_USE_SETUPTOOLS
67 +# @DEFAULT_UNSET
68 # @PRE_INHERIT
69 # @DESCRIPTION:
70 # Controls adding dev-python/setuptools dependency. The allowed values
71 @@ -97,13 +123,13 @@ esac
72 # (assumes you will take care of doing it correctly)
73 #
74 # This variable is effective only if DISTUTILS_OPTIONAL is disabled.
75 -# It needs to be set before the inherit line.
76 -: ${DISTUTILS_USE_SETUPTOOLS:=bdepend}
77 +# It is available only in non-PEP517 mode. It needs to be set before
78 +# the inherit line.
79
80 if [[ ! ${_DISTUTILS_R1} ]]; then
81
82 [[ ${EAPI} == 6 ]] && inherit eutils xdg-utils
83 -inherit multiprocessing toolchain-funcs
84 +inherit multibuild multiprocessing toolchain-funcs
85
86 if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then
87 inherit python-r1
88 @@ -121,25 +147,58 @@ if [[ ! ${_DISTUTILS_R1} ]]; then
89
90 _distutils_set_globals() {
91 local rdep bdep
92 - local setuptools_dep='>=dev-python/setuptools-42.0.2[${PYTHON_USEDEP}]'
93 + if [[ ${DISTUTILS_USE_PEP517} ]]; then
94 + if [[ ${DISTUTILS_USE_SETUPTOOLS} ]]; then
95 + die "DISTUTILS_USE_SETUPTOOLS is not used in PEP517 mode"
96 + fi
97
98 - case ${DISTUTILS_USE_SETUPTOOLS} in
99 - no|manual)
100 - ;;
101 - bdepend)
102 - bdep+=" ${setuptools_dep}"
103 - ;;
104 - rdepend)
105 - bdep+=" ${setuptools_dep}"
106 - rdep+=" ${setuptools_dep}"
107 - ;;
108 - pyproject.toml)
109 - bdep+=' >=dev-python/pyproject2setuppy-22[${PYTHON_USEDEP}]'
110 - ;;
111 - *)
112 - die "Invalid DISTUTILS_USE_SETUPTOOLS=${DISTUTILS_USE_SETUPTOOLS}"
113 - ;;
114 - esac
115 + # pip is used to install the wheel (TODO: replace with installer
116 + # once it finally gets a CLI)
117 + # tomli is used to read build-backend from pyproject.toml
118 + bdep+='
119 + dev-python/pip[${PYTHON_USEDEP}]
120 + dev-python/tomli[${PYTHON_USEDEP}]'
121 + case ${DISTUTILS_USE_PEP517} in
122 + flit)
123 + bdep+='
124 + dev-python/flit_core[${PYTHON_USEDEP}]'
125 + ;;
126 + pdm)
127 + bdep+='
128 + dev-python/pdm-pep517[${PYTHON_USEDEP}]'
129 + ;;
130 + setuptools)
131 + bdep+='
132 + >=dev-python/setuptools-60.5.0[${PYTHON_USEDEP}]
133 + dev-python/wheel[${PYTHON_USEDEP}]'
134 + ;;
135 + standalone)
136 + ;;
137 + *)
138 + die "Unknown DISTUTILS_USE_PEP517=${DISTUTILS_USE_PEP517}"
139 + ;;
140 + esac
141 + else
142 + local setuptools_dep='>=dev-python/setuptools-42.0.2[${PYTHON_USEDEP}]'
143 +
144 + case ${DISTUTILS_USE_SETUPTOOLS:-bdepend} in
145 + no|manual)
146 + ;;
147 + bdepend)
148 + bdep+=" ${setuptools_dep}"
149 + ;;
150 + rdepend)
151 + bdep+=" ${setuptools_dep}"
152 + rdep+=" ${setuptools_dep}"
153 + ;;
154 + pyproject.toml)
155 + bdep+=' >=dev-python/pyproject2setuppy-22[${PYTHON_USEDEP}]'
156 + ;;
157 + *)
158 + die "Invalid DISTUTILS_USE_SETUPTOOLS=${DISTUTILS_USE_SETUPTOOLS}"
159 + ;;
160 + esac
161 + fi
162
163 if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then
164 bdep=${bdep//\$\{PYTHON_USEDEP\}/${PYTHON_USEDEP}}
165 @@ -525,9 +584,17 @@ esetup.py() {
166 #
167 # Please note that in order to test the solution properly you need
168 # to unmerge the package first.
169 +#
170 +# This function is not available in PEP517 mode. The eclass provides
171 +# a venv-style install unconditionally therefore, and therefore it
172 +# should no longer be necessary.
173 distutils_install_for_testing() {
174 debug-print-function ${FUNCNAME} "${@}"
175
176 + if [[ ${DISTUTILS_USE_PEP517} ]]; then
177 + die "${FUNCNAME} is not implemented in PEP517 mode"
178 + fi
179 +
180 # A few notes about --via-home mode:
181 # 1) 'install --home' is terribly broken on pypy, so we need
182 # to override --install-lib and --install-scripts,
183 @@ -676,8 +743,10 @@ distutils-r1_python_prepare_all() {
184 fi
185 fi
186
187 - _distutils-r1_disable_ez_setup
188 - _distutils-r1_handle_pyproject_toml
189 + if [[ ! ${DISTUTILS_USE_PEP517} ]]; then
190 + _distutils-r1_disable_ez_setup
191 + _distutils-r1_handle_pyproject_toml
192 + fi
193
194 if [[ ${DISTUTILS_IN_SOURCE_BUILD} && ! ${DISTUTILS_SINGLE_IMPL} ]]
195 then
196 @@ -716,13 +785,18 @@ _distutils-r1_create_setup_cfg() {
197 [bdist_egg]
198 dist_dir = ${BUILD_DIR}/dist
199
200 + # speed up temporary wheel builds
201 + [bdist_wheel]
202 + skip_build = True
203 + compression = stored
204 +
205 # avoid packing up eggs in a zip as it often breaks test suites
206 [options]
207 zip_safe = False
208 _EOF_
209
210 - # we can't refer to ${D} before src_install()
211 if [[ ${EBUILD_PHASE} == install ]]; then
212 + # we can't refer to ${D} before src_install()
213 cat >> "${HOME}"/.pydistutils.cfg <<-_EOF_ || die
214
215 # installation paths -- allow calling extra install targets
216 @@ -734,6 +808,7 @@ _distutils-r1_create_setup_cfg() {
217 _EOF_
218
219 if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then
220 + # this gets appended to [install]
221 cat >> "${HOME}"/.pydistutils.cfg <<-_EOF_ || die
222 install_scripts = $(python_get_scriptdir)
223 _EOF_
224 @@ -753,6 +828,32 @@ _distutils-r1_copy_egg_info() {
225 find -name '*.egg-info' -type d -exec cp -R -p {} "${BUILD_DIR}"/ ';' || die
226 }
227
228 +# @FUNCTION: _distutils-r1_backend_to_key
229 +# @USAGE: <backend>
230 +# @INTERNAL
231 +# @DESCRIPTION:
232 +# Print the DISTUTILS_USE_PEP517 value corresponding to the backend
233 +# passed as the only argument.
234 +_distutils-r1_backend_to_key() {
235 + debug-print-function ${FUNCNAME} "${@}"
236 +
237 + local backend=${1}
238 + case ${backend} in
239 + flit_core.buildapi)
240 + echo flit
241 + ;;
242 + pdm.pep517.api)
243 + echo pdm
244 + ;;
245 + setuptools.build_meta|setuptools.build_meta:__legacy__)
246 + echo setuptools
247 + ;;
248 + *)
249 + die "Unknown backend: ${backend}"
250 + ;;
251 + esac
252 +}
253 +
254 # @FUNCTION: distutils-r1_python_compile
255 # @USAGE: [additional-args...]
256 # @DESCRIPTION:
257 @@ -767,16 +868,87 @@ distutils-r1_python_compile() {
258
259 _python_check_EPYTHON
260
261 - _distutils-r1_copy_egg_info
262 + # call setup.py build when using setuptools (either via PEP517
263 + # or in legacy mode)
264 + if [[ ${DISTUTILS_USE_PEP517:-setuptools} == setuptools ]]; then
265 + _distutils-r1_copy_egg_info
266 +
267 + # distutils is parallel-capable since py3.5
268 + local jobs=$(makeopts_jobs "${MAKEOPTS}" INF)
269 + if [[ ${jobs} == INF ]]; then
270 + local nproc=$(get_nproc)
271 + jobs=$(( nproc + 1 ))
272 + fi
273
274 - # distutils is parallel-capable since py3.5
275 - local jobs=$(makeopts_jobs "${MAKEOPTS}" INF)
276 - if [[ ${jobs} == INF ]]; then
277 - local nproc=$(get_nproc)
278 - jobs=$(( nproc + 1 ))
279 + esetup.py build -j "${jobs}" "${@}"
280 fi
281
282 - esetup.py build -j "${jobs}" "${@}"
283 + if [[ ${DISTUTILS_USE_PEP517} ]]; then
284 + if [[ -n ${DISTUTILS_ARGS[@]} || -n ${mydistutilsargs[@]} ]]; then
285 + die "DISTUTILS_ARGS are not supported in PEP-517 mode"
286 + fi
287 +
288 + # python likes to compile any module it sees, which triggers sandbox
289 + # failures if some packages haven't compiled their modules yet.
290 + addpredict "${EPREFIX}/usr/lib/${EPYTHON}"
291 + addpredict /usr/lib/pypy3.8
292 + addpredict /usr/lib/portage/pym
293 + addpredict /usr/local # bug 498232
294 +
295 + local -x WHEEL_BUILD_DIR=${BUILD_DIR}/wheel
296 + mkdir -p "${WHEEL_BUILD_DIR}"
297 +
298 + local build_backend
299 + if [[ ! -f pyproject.toml && ${DISTUTILS_USE_PEP517} == setuptools &&
300 + -f setup.py ]]
301 + then
302 + # use the legacy setuptools backend
303 + build_backend=setuptools.build_meta:__legacy__
304 + else
305 + build_backend=$("${EPYTHON}" -c 'import tomli; \
306 + print(tomli.load(open("pyproject.toml", "rb")) \
307 + ["build-system"]["build-backend"])')
308 + [[ -z ${build_backend} ]] &&
309 + die "Unable to obtain build-backend from pyproject.toml"
310 + fi
311 +
312 + if [[ ${DISTUTILS_USE_PEP517} != standalone ]]; then
313 + local expected_value=$(_distutils-r1_backend_to_key "${build_backend}")
314 + if [[ ${DISTUTILS_USE_PEP517} != ${expected_value} ]]; then
315 + eerror "DISTUTILS_USE_PEP517 does not match pyproject.toml!"
316 + eerror " have: DISTUTILS_USE_PEP517=${DISTUTILS_USE_PEP517}"
317 + eerror "expected: DISTUTILS_USE_PEP517=${expected_value}"
318 + eerror "(backend: ${build_backend})"
319 + die "DISTUTILS_USE_PEP517 value incorrect"
320 + fi
321 + fi
322 +
323 + einfo "Building a wheel via ${build_backend}"
324 + "${EPYTHON}" -c "import ${build_backend%:*}; \
325 + import os; \
326 + ${build_backend/:/.}.build_wheel(os.environ['WHEEL_BUILD_DIR'])" ||
327 + die "Wheel build failed"
328 +
329 + local wheel=( "${WHEEL_BUILD_DIR}"/*.whl )
330 + if [[ ${#wheel[@]} -ne 1 ]]; then
331 + die "Incorrect number of wheels created (${#wheel[@]}): ${wheel[*]}"
332 + fi
333 +
334 + local root=${BUILD_DIR}/install
335 + pip install --root "${root}" --no-deps --disable-pip-version-check \
336 + --progress-bar off --verbose --ignore-installed \
337 + --no-warn-script-location "${wheel}" ||
338 + die "pip install failed"
339 +
340 + # enable venv magic inside the install tree
341 + mkdir -p "${root}"/usr/bin || die
342 + ln -s "${PYTHON}" "${root}/usr/bin/${EPYTHON}" || die
343 + ln -s "${EPYTHON}" "${root}/usr/bin/python3" || die
344 + ln -s "${EPYTHON}" "${root}/usr/bin/python" || die
345 + cat > "${root}"/usr/pyvenv.cfg <<-EOF || die
346 + include-system-site-packages = true
347 + EOF
348 + fi
349 }
350
351 # @FUNCTION: _distutils-r1_wrap_scripts
352 @@ -888,59 +1060,74 @@ distutils-r1_python_install() {
353
354 _python_check_EPYTHON
355
356 - local root=${D%/}/_${EPYTHON}
357 - [[ ${DISTUTILS_SINGLE_IMPL} ]] && root=${D%/}
358 -
359 - # inline DISTUTILS_ARGS logic from esetup.py in order to make
360 - # argv overwriting easier
361 - local args=(
362 - "${DISTUTILS_ARGS[@]}"
363 - "${mydistutilsargs[@]}"
364 - install --skip-build --root="${root}" "${args[@]}"
365 - "${@}"
366 - )
367 - local DISTUTILS_ARGS=()
368 - local mydistutilsargs=()
369 + local scriptdir=${EPREFIX}/usr/bin
370 + if [[ ${DISTUTILS_USE_PEP517} ]]; then
371 + local root=${BUILD_DIR}/install
372 + local rscriptdir=${root}$(python_get_scriptdir)
373 + [[ -d ${rscriptdir} ]] &&
374 + die "${rscriptdir} should not exist!"
375 + # remove venv magic
376 + rm "${root}"/usr/{pyvenv.cfg,bin/{python,python3,${EPYTHON}}} || die
377 + find "${root}"/usr/bin -empty -delete || die
378 + if [[ ! ${DISTUTILS_SINGLE_IMPL} && -d ${root}/usr/bin ]]; then
379 + mkdir -p "${rscriptdir%/*}" || die
380 + mv "${root}/usr/bin" "${rscriptdir}" || die
381 + fi
382 + python_optimize "${root}$(python_get_sitedir)"
383 + else
384 + local root=${D%/}/_${EPYTHON}
385 + [[ ${DISTUTILS_SINGLE_IMPL} ]] && root=${D%/}
386 +
387 + # inline DISTUTILS_ARGS logic from esetup.py in order to make
388 + # argv overwriting easier
389 + local args=(
390 + "${DISTUTILS_ARGS[@]}"
391 + "${mydistutilsargs[@]}"
392 + install --skip-build --root="${root}" "${args[@]}"
393 + "${@}"
394 + )
395 + local DISTUTILS_ARGS=()
396 + local mydistutilsargs=()
397
398 - # enable compilation for the install phase.
399 - local -x PYTHONDONTWRITEBYTECODE=
400 + # enable compilation for the install phase.
401 + local -x PYTHONDONTWRITEBYTECODE=
402
403 - # python likes to compile any module it sees, which triggers sandbox
404 - # failures if some packages haven't compiled their modules yet.
405 - addpredict "${EPREFIX}/usr/lib/${EPYTHON}"
406 - addpredict /usr/lib/pypy3.8
407 - addpredict /usr/lib/portage/pym
408 - addpredict /usr/local # bug 498232
409 + # python likes to compile any module it sees, which triggers sandbox
410 + # failures if some packages haven't compiled their modules yet.
411 + addpredict "${EPREFIX}/usr/lib/${EPYTHON}"
412 + addpredict /usr/lib/pypy3.8
413 + addpredict /usr/lib/portage/pym
414 + addpredict /usr/local # bug 498232
415
416 - if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then
417 - # user may override --install-scripts
418 - # note: this is poor but distutils argv parsing is dumb
419 - local scriptdir=${EPREFIX}/usr/bin
420 -
421 - # rewrite all the arguments
422 - set -- "${args[@]}"
423 - args=()
424 - while [[ ${@} ]]; do
425 - local a=${1}
426 - shift
427 + if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then
428 + # user may override --install-scripts
429 + # note: this is poor but distutils argv parsing is dumb
430 +
431 + # rewrite all the arguments
432 + set -- "${args[@]}"
433 + args=()
434 + while [[ ${@} ]]; do
435 + local a=${1}
436 + shift
437 +
438 + case ${a} in
439 + --install-scripts=*)
440 + scriptdir=${a#--install-scripts=}
441 + ;;
442 + --install-scripts)
443 + scriptdir=${1}
444 + shift
445 + ;;
446 + *)
447 + args+=( "${a}" )
448 + ;;
449 + esac
450 + done
451 + fi
452
453 - case ${a} in
454 - --install-scripts=*)
455 - scriptdir=${a#--install-scripts=}
456 - ;;
457 - --install-scripts)
458 - scriptdir=${1}
459 - shift
460 - ;;
461 - *)
462 - args+=( "${a}" )
463 - ;;
464 - esac
465 - done
466 + esetup.py "${args[@]}"
467 fi
468
469 - esetup.py "${args[@]}"
470 -
471 local forbidden_package_names=(
472 examples test tests
473 .pytest_cache .hypothesis
474 @@ -964,8 +1151,10 @@ distutils-r1_python_install() {
475 die "Package installs 'share' in PyPy prefix, see bug #465546."
476 fi
477
478 - if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then
479 + if [[ ! ${DISTUTILS_SINGLE_IMPL} || ${DISTUTILS_USE_PEP517} ]]; then
480 multibuild_merge_root "${root}" "${D%/}"
481 + fi
482 + if [[ ! ${DISTUTILS_SINGLE_IMPL} ]]; then
483 _distutils-r1_wrap_scripts "${scriptdir}"
484 fi
485 }
486 @@ -1006,12 +1195,21 @@ distutils-r1_run_phase() {
487 fi
488 local BUILD_DIR=${BUILD_DIR}/build
489 fi
490 - local -x PYTHONPATH="${BUILD_DIR}/lib:${PYTHONPATH}"
491
492 - # make PATH local for distutils_install_for_testing calls
493 - # it makes little sense to let user modify PATH in per-impl phases
494 - # and _all() already localizes it
495 - local -x PATH=${PATH}
496 + if [[ ${DISTUTILS_USE_PEP517} ]]; then
497 + local -x PATH=${BUILD_DIR}/install/usr/bin:${PATH}
498 + else
499 + local -x PYTHONPATH="${BUILD_DIR}/lib:${PYTHONPATH}"
500 +
501 + # make PATH local for distutils_install_for_testing calls
502 + # it makes little sense to let user modify PATH in per-impl phases
503 + # and _all() already localizes it
504 + local -x PATH=${PATH}
505 +
506 + # Undo the default switch in setuptools-60+ for the time being,
507 + # to avoid replacing .egg-info file with directory in-place.
508 + local -x SETUPTOOLS_USE_DISTUTILS="${SETUPTOOLS_USE_DISTUTILS:-stdlib}"
509 + fi
510
511 # Bug 559644
512 # using PYTHONPATH when the ${BUILD_DIR}/lib is not created yet might lead to
513 @@ -1037,10 +1235,6 @@ distutils-r1_run_phase() {
514
515 local -x LDSHARED="${CC} ${ldopts}" LDCXXSHARED="${CXX} ${ldopts}"
516
517 - # Undo the default switch in setuptools-60+ for the time being,
518 - # to avoid replacing .egg-info file with directory in-place.
519 - local -x SETUPTOOLS_USE_DISTUTILS="${SETUPTOOLS_USE_DISTUTILS:-stdlib}"
520 -
521 "${@}"
522
523 cd "${_DISTUTILS_INITIAL_CWD}" || die
524 --
525 2.34.1

Replies

Subject Author
Re: [gentoo-dev] [PATCH] distutils-r1.eclass: Implement PEP517 mode "Michał Górny" <mgorny@g.o>