From 2ce546a1591975a86aa6d0a09ab9d20ab83572f4 Mon Sep 17 00:00:00 2001 From: Aohan Dang Date: Tue, 29 Aug 2023 14:03:36 -0400 Subject: [PATCH] Place .py patch after header comment and shebang This avoids changing the result of help() or inspect.getcomments() on the file that is patched. --- delvewheel/_wheel_repair.py | 62 ++++++++++++++---- tests/run_tests.py | 11 +++- ...eext-0.0.1-0init-cp310-cp310-win_amd64.whl | Bin 46034 -> 81045 bytes 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/delvewheel/_wheel_repair.py b/delvewheel/_wheel_repair.py index 1668ef1..095d4d4 100644 --- a/delvewheel/_wheel_repair.py +++ b/delvewheel/_wheel_repair.py @@ -288,8 +288,8 @@ def _patch_py_contents(self, at_start: bool, libs_dir: str, load_order_filename: def _patch_py_file(self, py_path: str, libs_dir: str, load_order_filename: typing.Optional[str], depth: int) -> None: """Given the path to a .py file, create or patch the file so that vendored DLLs can be loaded at runtime. The patch is placed at the - topmost location after the docstring (if any) and any - "from __future__ import" statements. + topmost location after the shebang (if any), docstring or header + comments (if any), and any "from __future__ import" statements. py_path is the path to the .py file to create or patch libs_dir is the name of the directory where DLLs are stored. @@ -334,16 +334,7 @@ def _patch_py_file(self, py_path: str, libs_dir: str, load_order_filename: typin if remainder: file.write('\n') file.write(remainder) - elif docstring is None: - # prepend patch - patch_py_contents = self._patch_py_contents(True, libs_dir, load_order_filename, depth) - with open(py_path, 'w', newline=newline) as file: - file.write(patch_py_contents) - remainder = py_contents.lstrip() - if remainder: - file.write('\n') - file.write(remainder) - else: + elif docstring is not None: # place patch just after docstring patch_py_contents = self._patch_py_contents(False, libs_dir, load_order_filename, depth) if len(children) == 0 or not isinstance(children[0], ast.Expr) or ast.literal_eval(children[0].value) != docstring: @@ -391,6 +382,53 @@ def _patch_py_file(self, py_path: str, libs_dir: str, load_order_filename: typin file.write(patch_py_contents) file.write('\n') file.write(py_contents[docstring_end_index:].lstrip()) + else: + py_contents_lines = py_contents.splitlines() + start = 0 + if py_contents_lines and py_contents_lines[0].startswith('#!'): + start = 1 + while start < len(py_contents_lines) and py_contents_lines[start].strip() in ('', '#'): + start += 1 + if start < len(py_contents_lines) and py_contents_lines[start][:1] == '#': + # insert patch after header comments + end = start + 1 + while end < len(py_contents_lines) and py_contents_lines[end][:1] == '#': + end += 1 + patch_py_contents = self._patch_py_contents(False, libs_dir, load_order_filename, depth) + with open(py_path, 'w', newline=newline) as file: + file.write('\n'.join(py_contents_lines[:end]).rstrip()) + file.write('\n\n\n') + file.write(patch_py_contents) + remainder = '\n'.join(py_contents_lines[end:]).lstrip() + if remainder: + file.write('\n') + file.write(remainder) + if not remainder.endswith('\n'): + file.write('\n') + elif py_contents_lines and py_contents_lines[0].startswith('#!'): + # insert patch after shebang + patch_py_contents = self._patch_py_contents(False, libs_dir, load_order_filename, depth) + with open(py_path, 'w', newline=newline) as file: + file.write(py_contents_lines[0].rstrip()) + file.write('\n\n\n') + file.write(patch_py_contents) + remainder = '\n'.join(py_contents_lines[1:]).lstrip() + if remainder: + file.write('\n') + file.write(remainder) + if not remainder.endswith('\n'): + file.write('\n') + else: + # prepend patch + patch_py_contents = self._patch_py_contents(True, libs_dir, load_order_filename, depth) + with open(py_path, 'w', newline=newline) as file: + file.write(patch_py_contents) + remainder = py_contents.lstrip() + if remainder: + file.write('\n') + file.write(remainder) + if not remainder.endswith('\n'): + file.write('\n') # verify that the file can be parsed properly with open(py_path) as file: diff --git a/tests/run_tests.py b/tests/run_tests.py index 5832278..6e9ad7a 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -525,8 +525,15 @@ def test_init_patch(self): 5. 1 future import 6. multiple future imports 7. docstring and multiple future imports - 8. escaped quotes at docstring end""" - cases = 9 + 8. escaped quotes at docstring end + 9. comment after docstring + 10. comment without docstring + 11. blank line, multiline comment, no docstring + 12. comment, no docstring, code + 13. shebang + 14. shebang, comment, code + 15. shebang, split comments, code""" + cases = 16 check_call(['delvewheel', 'repair', '--add-path', 'simpleext/x64', '--no-mangle-all', 'simpleext/simpleext-0.0.1-0init-cp310-cp310-win_amd64.whl']) self.assertTrue(import_simpleext_successful('0init', [f'simpleext{x}.simpleext' for x in range(cases)])) diff --git a/tests/simpleext/simpleext-0.0.1-0init-cp310-cp310-win_amd64.whl b/tests/simpleext/simpleext-0.0.1-0init-cp310-cp310-win_amd64.whl index b70e4c885155d397cfd8d3a7d5d982856535853e..301e024eb9eb1782c21d004813b2f7170d4c3db4 100644 GIT binary patch delta 3319 zcmb7`3pkW%8^_;al#QlYISgT@hA@LMEc*A}pFGTCMq5m@qThlfi?+eZp)(~R8He=(7+1``U*kX$ zG?F~ZyPos};z!JdRx{Ui`)@?8)4ca);D=bF%QZ!9_csLRKi>YFuV1|<>ttrb1bIEB z<34s|UAy>OQ`=*_*3QaIEy{IJdUA)fdhE?pL4>q?6?J~waQ$X?D3zF0WL zOx`_aXz+Sww(=NJP5t?)Jok9ox6#{un#G7{yXzJwF~W1DC9ICg%huS6nZUi9;zSR; zudnV587{brII#|8*YO%Tk-gmgR9=Ds5-Ysr=9PomUf1%A-o+Gd@7f&tk)PLb8i(0E zhPgxR3gMlh&rp=}MoEpUcQ@x(3DZKUMXvRh4$E}T4P}H>*INHU&#XR(3kb1%&Hj$3 zncf;T+8rPDj8Ml~(c8`0!pVHw<37sHw~TWM$ZP65%N_!5;@lJbVn3;Cc1E#MDK#8d z%fC9Y%&+^XNkFrD5cV?LIDmPaI4y7p{l@i*UvkSa#E&&;?!UO74WR3er~5!F1C^jk-+d?BrBR73T%3<+uFq;S&CV)}TB`F{7uF z+t3(wJ4lB<&Z)-4e#0wURG%DvHZhfQjk6O&r+!|t=iTuG`26k>;rS5~%}v#RN_)ZJp6TPp|doE`_J9FZPfCU~~FH7=ZzU96!;U3w(at&=&#BpM&L$b9_j zL?8NPaHEjb#H$h*?r6IZVEeK<)#@kY&Smvam)_Fv)b)Mlvm7M8?ipJ_3H)z&Qcu4= zr`7)RI-3Ucwdd=LeMZVpHi`So^bGXVM7gEMm*MP-ZmxWCC%rC9BjVh+X^VN|+e_EU8z&79E(j4dBnxP^aUx$Fd z<7i7YKMTr#bC40Y5i^r?gV-HgJRuIL$xtVKd3xoZPP@9~wug2P1&@M#E^W|sG8lx` z{8}f{r>K@(7K6_HqE^qvG|);rw04Z#cKbhflgrPv_jlegdbo-@`>GME+{mCF(gz7Q zG8l#|kiMV^h5{3fFLmVKjvCB;>#?F>VQc~>aK?@oN%8sOn4loreFT+6r)Y4~`;}3>Lg+d(!@J@OJqMNrjnkBHBOjw;H}rLb<~BiivFX?;zRO zKA^SS6VY0UTP-hHH#gmX`VrLz$9Px?RkD3C@dCvaqI%EUzQY2jvJ-G;e0)hhRlRbK z$|u9XIjO;^ZJU>xC!jt-%oUO;KotEu zb7m9n8g0ejZxCv7gtM$}{_sU=%lu-AF;p{n=tD!+)6U9elXWVcPT$l_v$eHqxwm<7 zc|sLb7KEBEApe&DhHr6C~-R03`{Yg znJ{Qz=RBa#ij>2!lQDP-seztHwc{C~aElZ+0BaIKS{7(dEeiu4Fd%?vzP1DcudDWc zad_WzPesZE0gSwAW&@|IszP!nYoY>4B7X!)gsSq%Bl6cON0jj}|G?-`ihy-b%21U& zIm63WkwFIknUSRw0jr&qp(=E8#$>P}gR%g|wh+Y#dzfMU|1?a_sEt*O0J%IvNx+AX z0~MzfFvcTFYOAVOazf%!#Q=u|>|$6c0#+aCE>wj_&iJZOkpcGe4du77sSq?JNN%Il z_Ts9Mntd6l$upDtB$t6(cutbr+Tpp%43>F#DBo6bEx4A30jqi1@<2)*c$B8FIF4_m z#ukCDe1*lIiNHgACuvMIG*24@A1cmY83Ylr@^*>GJs*bU6}m1e3FuFQ`^ps-PM-#Q zGZf%|mK&*$X(2!@0bZ7&dW1FttShjByHRqor73yWmP1R!LwSFlrUovt%5Sf mD$K#UCOr{VNrTpuJQXP!WTgO}Ko^Av@W3TVj=G>J0Qeu7>A86T delta 1474 zcmbRGljYKLCcXe~W)=|!1_lm>S&^Ng8~K75`9O@9eDQL{ObiSP?342t)$1?s+_cDn zq4ii4Wg5$o6Z7n;U>n02*7FEZ}wR@IW1 zdi<+P5~W1f>2&3NQJtZ>`=_yY@2(H8e_MEF&1y-|@JkF?Dr3Cz{mbkI$!V{yEoa{; zaI;#ho+T%aXH}~7b>Ug2a~{V^9M0VHG@0RKaYOoX;V1uRa2$RVvL*3{P91w?WaL%V zOGipsoWj@F9FPjSw$|u*z{c*%+#k_X@2%f`vrWR@^lPR5*K^se20IcgwyYJ7?uc_f zdPT!X`({CL>Or1s*KewZFTJ`aoufWlNuFUu2t9a~(VWGpRJ!J4AjeXZ>ZFKd@$7Cp7P*ypV6#-78-OTO-fMCKNkg zIJDwet=yTt%=%(m8tTg!)6Oh={odgNf4^zC!SlEKSR4y(wO`2AG`f|2rpw{6|M7ac zGy9pd6*P60Uf17tRCSj~rNe2J$CtE!x@~x^7q&3((WYYSGKH3p+>6gQU$i#h%h|4b zaf{imxxqIo1fK3Tn*OP3$Kxb}l+MTypdK*)XM!>s%4Si`T(~zaDT03vYHf zm<}~y^<`&-O71H#m9ftv5W<(9!Gu+Q<{*UE{)7ro|If`>3KyT=WR2p+>5t_Yt@#+4 zL>Lemar$#ZMjy1CINi~R(Ln(e096sEs(65zmJ5j0ffxq1HL|lzUueW=3bt$dEh9!L zrUF>Pct&&jdt*iwBz;U;({GzY^|>ER?hys517TEsxi-@cEl~7rvza_&9kMIdTL9gr z=s*25s=6Hi>HL;Z^LfKc_6P&b7Q`_BdgAm{OBD0vk|te&7!UT6`PL474WKqq_@O$j zvt;^zRIN`-CLe!>?6g`?um!YFpJ|0+#ew$ecdejKlU%Q!;|er88N+<-S<`uW7$v6H wS|W!x^O9K%(<`iDaktSLXaoP+=^y17CBy=}S=oTb0E1DKAq~h1TMOa=09VE>)&Kwi