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 |