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