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] python-utils-r1.eclass: Make python_fix_shebang force full path
Date: Thu, 31 Mar 2022 22:51:43
Message-Id: 20220331225125.59264-1-mgorny@gentoo.org
1 Change the behavior of python_fix_shebang to always output full path
2 to the Python interpreter (i.e. ${PYTHON}) instead of mangling
3 the original path. Most importantly, this ensures that:
4
5 1. EPREFIX is included in the final path
6
7 2. /usr/bin/env is replaced by the absolute path to avoid issues
8 when calling system executables from inside a venv
9
10 Note that this implies that a few unlikely corner cases may stop
11 working, notably:
12
13 a. "weird" shebangs such as "/usr/bin/foo python" will no longer work
14
15 b. the mangled scripts will escape temporary venv e.g. created
16 in distutils-r1 PEP517 mode (python_fix_shebang is not used in such
17 a way in ::gentoo)
18
19 Signed-off-by: Michał Górny <mgorny@g.o>
20 ---
21 eclass/python-utils-r1.eclass | 134 ++++++++++++--------------------
22 eclass/tests/python-utils-r1.sh | 86 ++++++++++++--------
23 2 files changed, 106 insertions(+), 114 deletions(-)
24
25 diff --git a/eclass/python-utils-r1.eclass b/eclass/python-utils-r1.eclass
26 index 7b0c9aa280d8..98cb49c95fd7 100644
27 --- a/eclass/python-utils-r1.eclass
28 +++ b/eclass/python-utils-r1.eclass
29 @@ -1001,25 +1001,30 @@ _python_wrapper_setup() {
30 # @FUNCTION: python_fix_shebang
31 # @USAGE: [-f|--force] [-q|--quiet] <path>...
32 # @DESCRIPTION:
33 -# Replace the shebang in Python scripts with the current Python
34 -# implementation (EPYTHON). If a directory is passed, works recursively
35 -# on all Python scripts.
36 +# Replace the shebang in Python scripts with the full path
37 +# to the current Python implementation (PYTHON, including EPREFIX).
38 +# If a directory is passed, works recursively on all Python scripts
39 +# found inside the directory tree.
40 #
41 -# Only files having a 'python*' shebang will be modified. Files with
42 -# other shebang will either be skipped when working recursively
43 -# on a directory or treated as error when specified explicitly.
44 +# Only files having a Python shebang (a path to any known Python
45 +# interpreter, optionally preceded by env(1) invocation) will
46 +# be processed. Files with any other shebang will either be skipped
47 +# silently when a directory was passed, or an error will be reported
48 +# for any files without Python shebangs specified explicitly.
49 #
50 -# Shebangs matching explicitly current Python version will be left
51 -# unmodified. Shebangs requesting another Python version will be treated
52 -# as fatal error, unless --force is given.
53 +# Shebangs that are compatible with the current Python version will be
54 +# mangled unconditionally. Incompatible shebangs will cause a fatal
55 +# error, unless --force is specified.
56 #
57 -# --force causes the function to replace even shebangs that require
58 -# incompatible Python version. --quiet causes the function not to list
59 -# modified files verbosely.
60 +# --force causes the function to replace shebangs with incompatible
61 +# Python version (but not non-Python shebangs). --quiet causes
62 +# the function not to list modified files verbosely.
63 python_fix_shebang() {
64 debug-print-function ${FUNCNAME} "${@}"
65
66 [[ ${EPYTHON} ]] || die "${FUNCNAME}: EPYTHON unset (pkg_setup not called?)"
67 + local PYTHON
68 + _python_export "${EPYTHON}" PYTHON
69
70 local force quiet
71 while [[ ${@} ]]; do
72 @@ -1035,13 +1040,13 @@ python_fix_shebang() {
73
74 local path f
75 for path; do
76 - local any_correct any_fixed is_recursive
77 + local any_fixed is_recursive
78
79 [[ -d ${path} ]] && is_recursive=1
80
81 while IFS= read -r -d '' f; do
82 local shebang i
83 - local error= from=
84 + local error= match=
85
86 # note: we can't ||die here since read will fail if file
87 # has no newline characters
88 @@ -1050,64 +1055,36 @@ python_fix_shebang() {
89 # First, check if it's shebang at all...
90 if [[ ${shebang} == '#!'* ]]; then
91 local split_shebang=()
92 - read -r -a split_shebang <<<${shebang} || die
93 -
94 - # Match left-to-right in a loop, to avoid matching random
95 - # repetitions like 'python2.7 python2'.
96 - for i in "${split_shebang[@]}"; do
97 - case "${i}" in
98 - *"${EPYTHON}")
99 - debug-print "${FUNCNAME}: in file ${f#${D%/}}"
100 - debug-print "${FUNCNAME}: shebang matches EPYTHON: ${shebang}"
101 -
102 - # Nothing to do, move along.
103 - any_correct=1
104 - from=${EPYTHON}
105 - break
106 - ;;
107 - *python|*python[23])
108 - debug-print "${FUNCNAME}: in file ${f#${D%/}}"
109 - debug-print "${FUNCNAME}: rewriting shebang: ${shebang}"
110 -
111 - if [[ ${i} == *python2 ]]; then
112 - from=python2
113 - if [[ ! ${force} ]]; then
114 - error=1
115 - fi
116 - elif [[ ${i} == *python3 ]]; then
117 - from=python3
118 - else
119 - from=python
120 - fi
121 - break
122 - ;;
123 - *python[23].[0-9]|*python3.[1-9][0-9]|*pypy|*pypy3|*jython[23].[0-9])
124 - # Explicit mismatch.
125 - if [[ ! ${force} ]]; then
126 - error=1
127 - else
128 - case "${i}" in
129 - *python[23].[0-9])
130 - from="python[23].[0-9]";;
131 - *python3.[1-9][0-9])
132 - from="python3.[1-9][0-9]";;
133 - *pypy)
134 - from="pypy";;
135 - *pypy3)
136 - from="pypy3";;
137 - *jython[23].[0-9])
138 - from="jython[23].[0-9]";;
139 - *)
140 - die "${FUNCNAME}: internal error in 2nd pattern match";;
141 - esac
142 - fi
143 - break
144 - ;;
145 - esac
146 - done
147 + read -r -a split_shebang <<<${shebang#"#!"} || die
148 +
149 + local in_path=${split_shebang[0]}
150 + local from='^#! *[^ ]*'
151 + # if the first component is env(1), skip it
152 + if [[ ${in_path} == */env ]]; then
153 + in_path=${split_shebang[1]}
154 + from+=' *[^ ]*'
155 + fi
156 +
157 + case ${in_path##*/} in
158 + "${EPYTHON}")
159 + match=1
160 + ;;
161 + python|python[23])
162 + match=1
163 + [[ ${in_path##*/} == python2 ]] && error=1
164 + ;;
165 + python[23].[0-9]|python3.[1-9][0-9]|pypy|pypy3|jython[23].[0-9])
166 + # Explicit mismatch.
167 + match=1
168 + error=1
169 + ;;
170 + esac
171 fi
172
173 - if [[ ! ${error} && ! ${from} ]]; then
174 + # disregard mismatches in force mode
175 + [[ ${force} ]] && error=
176 +
177 + if [[ ! ${match} ]]; then
178 # Non-Python shebang. Allowed in recursive mode,
179 # disallowed when specifying file explicitly.
180 [[ ${is_recursive} ]] && continue
181 @@ -1119,13 +1096,9 @@ python_fix_shebang() {
182 fi
183
184 if [[ ! ${error} ]]; then
185 - # We either want to match ${from} followed by space
186 - # or at end-of-string.
187 - if [[ ${shebang} == *${from}" "* ]]; then
188 - sed -i -e "1s:${from} :${EPYTHON} :" "${f}" || die
189 - else
190 - sed -i -e "1s:${from}$:${EPYTHON}:" "${f}" || die
191 - fi
192 + debug-print "${FUNCNAME}: in file ${f#${D%/}}"
193 + debug-print "${FUNCNAME}: rewriting shebang: ${shebang}"
194 + sed -i -e "1s@${from}@#!${PYTHON}@" "${f}" || die
195 any_fixed=1
196 else
197 eerror "The file has incompatible shebang:"
198 @@ -1138,12 +1111,7 @@ python_fix_shebang() {
199
200 if [[ ! ${any_fixed} ]]; then
201 eerror "QA error: ${FUNCNAME}, ${path#${D%/}} did not match any fixable files."
202 - if [[ ${any_correct} ]]; then
203 - eerror "All files have ${EPYTHON} shebang already."
204 - else
205 - eerror "There are no Python files in specified directory."
206 - fi
207 -
208 + eerror "There are no Python files in specified directory."
209 die "${FUNCNAME} did not match any fixable files"
210 fi
211 done
212 diff --git a/eclass/tests/python-utils-r1.sh b/eclass/tests/python-utils-r1.sh
213 index 8c733b22294e..ef7687b8a9cf 100755
214 --- a/eclass/tests/python-utils-r1.sh
215 +++ b/eclass/tests/python-utils-r1.sh
216 @@ -41,7 +41,7 @@ test_fix_shebang() {
217 local expect=${3}
218 local args=( "${@:4}" )
219
220 - tbegin "python_fix_shebang${args[@]+ ${args[*]}} from ${from} to ${to} (exp: ${expect})"
221 + tbegin "python_fix_shebang${args[@]+ ${args[*]}} from ${from@Q} to ${to@Q} (exp: ${expect@Q})"
222
223 echo "${from}" > "${tmpfile}"
224 output=$( EPYTHON=${to} python_fix_shebang "${args[@]}" -q "${tmpfile}" 2>&1 )
225 @@ -156,36 +156,60 @@ fi
226 test_var PYTHON_PKG_DEP pypy3 '*dev-python/pypy3*:0='
227 test_var PYTHON_SCRIPTDIR pypy3 /usr/lib/python-exec/pypy3
228
229 -# generic shebangs
230 -test_fix_shebang '#!/usr/bin/python' python3.6 '#!/usr/bin/python3.6'
231 -test_fix_shebang '#!/usr/bin/python' pypy3 '#!/usr/bin/pypy3'
232 -
233 -# python2/python3 matching
234 -test_fix_shebang '#!/usr/bin/python3' python3.6 '#!/usr/bin/python3.6'
235 -test_fix_shebang '#!/usr/bin/python2' python3.6 FAIL
236 -test_fix_shebang '#!/usr/bin/python2' python3.6 '#!/usr/bin/python3.6' --force
237 -
238 -# pythonX.Y matching (those mostly test the patterns)
239 -test_fix_shebang '#!/usr/bin/python2.7' python3.2 FAIL
240 -test_fix_shebang '#!/usr/bin/python2.7' python3.2 '#!/usr/bin/python3.2' --force
241 -test_fix_shebang '#!/usr/bin/python3.2' python3.2 '#!/usr/bin/python3.2'
242 -
243 -# fancy path handling
244 -test_fix_shebang '#!/mnt/python2/usr/bin/python' python3.6 \
245 - '#!/mnt/python2/usr/bin/python3.6'
246 -test_fix_shebang '#!/mnt/python2/usr/bin/python3' python3.8 \
247 - '#!/mnt/python2/usr/bin/python3.8'
248 -test_fix_shebang '#!/mnt/python2/usr/bin/env python' python3.8 \
249 - '#!/mnt/python2/usr/bin/env python3.8'
250 -test_fix_shebang '#!/mnt/python2/usr/bin/python3 python3' python3.8 \
251 - '#!/mnt/python2/usr/bin/python3.8 python3'
252 -test_fix_shebang '#!/mnt/python2/usr/bin/python2 python3' python3.8 FAIL
253 -test_fix_shebang '#!/mnt/python2/usr/bin/python2 python3' python3.8 \
254 - '#!/mnt/python2/usr/bin/python3.8 python3' --force
255 -test_fix_shebang '#!/usr/bin/foo' python3.8 FAIL
256 -
257 -# regression test for bug #522080
258 -test_fix_shebang '#!/usr/bin/python ' python3.8 '#!/usr/bin/python3.8 '
259 +for EPREFIX in '' /foo; do
260 + einfo "with EPREFIX=${EPREFIX@Q}"
261 + eindent
262 + # generic shebangs
263 + test_fix_shebang '#!/usr/bin/python' python3.6 \
264 + "#!${EPREFIX}/usr/bin/python3.6"
265 + test_fix_shebang '#!/usr/bin/python' pypy3 \
266 + "#!${EPREFIX}/usr/bin/pypy3"
267 +
268 + # python2/python3 matching
269 + test_fix_shebang '#!/usr/bin/python3' python3.6 \
270 + "#!${EPREFIX}/usr/bin/python3.6"
271 + test_fix_shebang '#!/usr/bin/python2' python3.6 FAIL
272 + test_fix_shebang '#!/usr/bin/python2' python3.6 \
273 + "#!${EPREFIX}/usr/bin/python3.6" --force
274 +
275 + # pythonX.Y matching (those mostly test the patterns)
276 + test_fix_shebang '#!/usr/bin/python2.7' python3.2 FAIL
277 + test_fix_shebang '#!/usr/bin/python2.7' python3.2 \
278 + "#!${EPREFIX}/usr/bin/python3.2" --force
279 + test_fix_shebang '#!/usr/bin/python3.2' python3.2 \
280 + "#!${EPREFIX}/usr/bin/python3.2"
281 +
282 + # fancy path handling
283 + test_fix_shebang '#!/mnt/python2/usr/bin/python' python3.6 \
284 + "#!${EPREFIX}/usr/bin/python3.6"
285 + test_fix_shebang '#!/mnt/python2/usr/bin/python3' python3.8 \
286 + "#!${EPREFIX}/usr/bin/python3.8"
287 + test_fix_shebang '#!/mnt/python2/usr/bin/env python' python3.8 \
288 + "#!${EPREFIX}/usr/bin/python3.8"
289 + test_fix_shebang '#!/mnt/python2/usr/bin/python3 python3' python3.8 \
290 + "#!${EPREFIX}/usr/bin/python3.8 python3"
291 + test_fix_shebang '#!/mnt/python2/usr/bin/python2 python3' python3.8 FAIL
292 + test_fix_shebang '#!/mnt/python2/usr/bin/python2 python3' python3.8 \
293 + "#!${EPREFIX}/usr/bin/python3.8 python3" --force
294 + test_fix_shebang '#!/usr/bin/foo' python3.8 FAIL
295 +
296 + # regression test for bug #522080
297 + test_fix_shebang '#!/usr/bin/python ' python3.8 \
298 + "#!${EPREFIX}/usr/bin/python3.8 "
299 +
300 + # test random whitespace in shebang
301 + test_fix_shebang '#! /usr/bin/python' python3.8 \
302 + "#!${EPREFIX}/usr/bin/python3.8"
303 + test_fix_shebang '#! /usr/bin/python' python3.8 \
304 + "#!${EPREFIX}/usr/bin/python3.8"
305 + test_fix_shebang '#! /usr/bin/env python' python3.8 \
306 + "#!${EPREFIX}/usr/bin/python3.8"
307 +
308 + # test preserving options
309 + test_fix_shebang '#! /usr/bin/python -b' python3.8 \
310 + "#!${EPREFIX}/usr/bin/python3.8 -b"
311 + eoutdent
312 +done
313
314 # check _python_impl_matches behavior
315 test_is "_python_impl_matches python3_6 -3" 0
316 --
317 2.35.1