From 99a8bbf993dd43f8064e99b28fd82fe118bb1fb4 Mon Sep 17 00:00:00 2001 From: czaki Date: Tue, 24 Sep 2019 15:20:11 +0200 Subject: [PATCH 1/9] read minimal macos version from from mach-O header --- setup.py | 3 +- tests/test_lib_file_analyse.py | 29 ++ .../macos_minimal_system_version/test_lib.c | 13 + .../test_lib_10_10.dynlib | Bin 0 -> 756 bytes .../test_lib_10_10_386.dynlib | Bin 0 -> 668 bytes .../test_lib_10_10_fat.dynlib | Bin 0 -> 8948 bytes .../test_lib_10_14.dynlib | Bin 0 -> 764 bytes .../test_lib_10_14_386.dynlib | Bin 0 -> 676 bytes .../test_lib_10_14_fat.dynlib | Bin 0 -> 8956 bytes .../test_lib_10_6.dynlib | Bin 0 -> 756 bytes .../test_lib_10_6_386.dynlib | Bin 0 -> 668 bytes .../test_lib_10_6_fat.dynlib | Bin 0 -> 8948 bytes .../test_lib_multiple_fat.dynlib | Bin 0 -> 8956 bytes wheel/lib_file_analyse.py | 309 ++++++++++++++++++ wheel/pep425tags.py | 15 +- 15 files changed, 367 insertions(+), 2 deletions(-) create mode 100644 tests/test_lib_file_analyse.py create mode 100644 tests/testdata/macos_minimal_system_version/test_lib.c create mode 100644 tests/testdata/macos_minimal_system_version/test_lib_10_10.dynlib create mode 100644 tests/testdata/macos_minimal_system_version/test_lib_10_10_386.dynlib create mode 100644 tests/testdata/macos_minimal_system_version/test_lib_10_10_fat.dynlib create mode 100644 tests/testdata/macos_minimal_system_version/test_lib_10_14.dynlib create mode 100644 tests/testdata/macos_minimal_system_version/test_lib_10_14_386.dynlib create mode 100644 tests/testdata/macos_minimal_system_version/test_lib_10_14_fat.dynlib create mode 100644 tests/testdata/macos_minimal_system_version/test_lib_10_6.dynlib create mode 100644 tests/testdata/macos_minimal_system_version/test_lib_10_6_386.dynlib create mode 100644 tests/testdata/macos_minimal_system_version/test_lib_10_6_fat.dynlib create mode 100644 tests/testdata/macos_minimal_system_version/test_lib_multiple_fat.dynlib create mode 100644 wheel/lib_file_analyse.py diff --git a/setup.py b/setup.py index 286e071e..643d1069 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,8 @@ def readall(*args): zip_safe=False, entry_points={ 'console_scripts': [ - 'wheel=wheel.cli:main' + 'wheel=wheel.cli:main', + 'lib_test=wheel.lib_file_analyse:test_extract' ], 'distutils.commands': [ 'bdist_wheel=wheel.bdist_wheel:bdist_wheel' diff --git a/tests/test_lib_file_analyse.py b/tests/test_lib_file_analyse.py new file mode 100644 index 00000000..bf8597d3 --- /dev/null +++ b/tests/test_lib_file_analyse.py @@ -0,0 +1,29 @@ +import os +from wheel.lib_file_analyse import extract_macosx_min_system_version + + +def test_read_from_dynlib(): + dirname = os.path.dirname(__file__) + dylib_dir = os.path.join(dirname, "testdata", + "macos_minimal_system_version") + versions = [ + ("test_lib_10_6_fat.dynlib", "10.6"), + ("test_lib_10_10_fat.dynlib", "10.10"), + ("test_lib_10_14_fat.dynlib", "10.14"), + ("test_lib_10_6.dynlib", "10.6"), + ("test_lib_10_10.dynlib", "10.10"), + ("test_lib_10_14.dynlib", "10.14"), + ("test_lib_10_6_386.dynlib", "10.6"), + ("test_lib_10_10_386.dynlib", "10.10"), + ("test_lib_10_14_386.dynlib", "10.14"), + ("test_lib_multiple_fat.dynlib", "10.14") + ] + for file_name, ver in versions: + extracted = extract_macosx_min_system_version( + os.path.join(dylib_dir, file_name) + ) + str_ver = str(extracted[0]) + "." + str(extracted[1]) + assert str_ver == ver + assert extract_macosx_min_system_version( + os.path.join(dylib_dir, "test_lib.c") + ) is None diff --git a/tests/testdata/macos_minimal_system_version/test_lib.c b/tests/testdata/macos_minimal_system_version/test_lib.c new file mode 100644 index 00000000..ca24f6c0 --- /dev/null +++ b/tests/testdata/macos_minimal_system_version/test_lib.c @@ -0,0 +1,13 @@ +int num_of_letters(char* text){ + int num = 0; + char * lett = text; + while (lett != 0){ + if (*lett >= 'a' && *lett <= 'z'){ + num += 1; + } else if (*lett >= 'A' && *lett <= 'Z'){ + num += 1; + } + lett += 1; + } + return num; +} \ No newline at end of file diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_10.dynlib b/tests/testdata/macos_minimal_system_version/test_lib_10_10.dynlib new file mode 100644 index 0000000000000000000000000000000000000000..eaf1a94e5f8a259da16f00db62453e0c7796c717 GIT binary patch literal 756 zcmX^A>+L^w1_nlE1|R{%EI_;g#83cYAdm!N3lJX%_yHBa096Cy1I=UrVUW2X5FcNX zT2TUFL---C5g`aZj5P(y0GS7J3mmX8G{D(FMtpp7er`cxa!Gt?UU_C-N_@PJ3$pwS zC`SQGXD~tBaso(0oeE?jKzw{^MtoXPVs0u#EIuC9eLJ8^9iVgv)I2Dcft!IL1I7l? zDnKj%VsLRWFz|6PfGm>$;tNpw9{_0)ARnlYApnSx0myw&HzErY#SZo8eCp9z`{TIl z7m#|7?%E$f?hB9R+7}G`ExBMJ*BAWzIGTSZ^7qIg^Q-v#`GEXx*DuWn7&~3RywpRM zb>!~>r3H^}sO~8KeohqCKn_^e1E|}h)Ahy6j{pDv0}YEk43dEXX#8S$4-^(4z*rT; zz*r%`D8R$cF#)Iw6pk`LtN}C(1fcpE7##To+L)Yq*+8100H~Y|WH3Ln>G64`x$*gF O@j0m_C8+gmEf_LjIn z%%uS61Dk&Fsw_hwT2-txOEpXB$vy5VyhhxJXGB#1a{yK4uac8xVVVFLGdydNWG6na zN0#@X9@rr!!TZEJCBy&n_uO~qBkf7!MF!m96%4oFyYkDq^g(nh9Fo3x0LL$h{&GUL z#UK;PzpCR!6 z+n0Sc_zTSYA`5NNq9kIU)(6N(#I}(mOZ#;XDwWc$uDG;{Map^m=DhY|`vive(|L(x z#Us6za$&J?zp~)U<>te?%{x+Fy`pnux1cAW(_k)$ym0ZOu1ansQdaA-wp3|UwLgB3 zX9!<}&Z-`9l@P~;sO$APEQk>ixttJ%Ty)k+xGi;F2bS)EJ>nWPEp#7sr*!zY);Ih; z?{mIun>+T9HgxAqe=hXxz-x!XTB}jW4y~T>!iu$iCgrYL8->Undan%M9uB5c$+ERh zx7S;5SsTa3YT-~a+8GWuN25{K9(r%`w_j5RLhlWdJQh7WiLrWJ7%kH%=8tAO$kHjN zp)+U^?I{<`wald{{k~C>r6!i{B%8|(xmc6;s;*mIdVmxQBftnS0*nA7zz8q`i~u9R z2rvSSz+Xh*Lp=W*_~SpG_hFyYOjd$IPC;k%j21=4e?sN%sv7P82`50Uwo?7{tQdt- z+yME9@dMkcPf6K2=5%_-w`y+36009_7+3H@mCh@azST3muiy>Nru~FC2%_;n7+gqa!5q-H;B?jQ zggVgGNJd!)yB*)|4>}Qh5G2$A73lZ-(-$?yBdip`BTSl`s3IJRM>vmWB>MXhG!^AF&B$2Q}+-Cm= zhLZ0|jqhnDFnKNm(K4!*r#+w6j?)wGt9ubVk9Qd(4A)~#0x?@OOPQ>NmBQ6|;S6zq zFL?eSCMmHL@^*QnD65j10MW=}uADIqFNZk2Usp^l30kwI#jhzxmDr@4P!_@Kq{WN4ec&QxJf|e5gx1tr(>aF PVljsohGiM$OD_EZ=Ie1K literal 0 HcmV?d00001 diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_14_386.dynlib b/tests/testdata/macos_minimal_system_version/test_lib_10_14_386.dynlib new file mode 100644 index 0000000000000000000000000000000000000000..c85b71691ee684af0f05edb64c424873d1081ef2 GIT binary patch literal 676 zcma)3y-LGS6h5gDqt;#r2L}Zoz)3o|R=nU~L8O8RatJl0f^DUyD5Zjs42Be1!3S{k zDMV0k^9fuX3c5K23C45M+>(NWANg{AzjM#!zJ7oHOaZ_IQ6uU^mz|X)X8#YoQSOdv zY+0{na4Vv1UD)gmfmo9JB^3?r4H?$+c_Zj>rF_J4qjqYRZEV%gkLyQ>+ncJ!_LjIn z%x3`TgAV;JFmxU@PlkilRIwnsyuFehh~D&AF9lqTmBFANMUgK<=Q(@$#1#U+A7OMYzB@x} z?}FAbwE66$eoa+AWr?^>BVtVB0w2DA zJ3&d!pdeeharSvPSKJA7g<(zwG&RE0s85XLi`z4YU zPt0D*`Ni5&dES>R^~VqD_ocjX-Nb0!hMt5@gSjB`VjUNKR5F!FSy_IR#a{^T&lCh0smN-_-YQW2N(;R@{W9g=+QL((d2d-wyYK zFS(X$@4Cdlh}Ntf&c>nJ3mUO-npJYSvC|PjTy{3krpz^GE3eFc@Wu+=e)nQ3SaLS$ z^rrN#vvq7F7X}4YW!!BK27{K{58mbOy`>0*-XA1+EP8elYweb>)@`ehJDS}mqBu__-{e7~~%2q6|mfDvE>7y(9r5nu!u0Y-ok zU<4R}zktBU;rwsm$G;lpXfE5KS*!?!oQBSr*-UvxFR?cFbdItAgcG1v+o^tLMpUj8 zH$e6{e&D(8Q`GAc=5u<+PkLU*7Q3G^jw{&FsaNz$^FH-6lI9G^H;y;h)8~k={uAE7 z3-`US5%0P^I*!+B#4sZ-Xhfh5YRctCPR9cC_psG=UKz0i%i8;U*XCDZ{WnD_k4mo>s8tQNo{OxkT^oktDv2p7+L^w1_nlE1|R{%EI_;g#83cYAdm!N3lJX%_yHBa096Cy1I=UrVUW2X5FcNX zT2TUFL---C5g`aZj5P(y0GS7J3mmX8G{D(FMtpp7er`cxa!Gt?UU_C-N_@PJ3$pwS zC`SQGXD~tBaso(0oeE?jKzw{^MtoXPVs0u#EIuC9eLJ8^9iVgv)I2Dcft!IL1I7l? zDnKj%Vz6;BFz|6PfGm>$;tNpw9{_0)ARnlYApnSx0myw&HzErY#SZo8eCp9z`{TIl z7m#|7?%E$f?hB9R+7}G`ExBMJ*BAWzIGTSZ^7qIg^Q-v#`GEXx*DuWn7&~3RywpRM zb>!~>r3H^}sO~8KeohqCKn_^e1E|}h)Ahy6j{pDv0}YEk43dEXX#8S$4-^(4z*rT; zz*r%`D8R$cF#)Iw6pk`LtN}C(1fcpE7##To+L)Yq*+8100H~Y|WH3Ln>G64`x$*gF O@j0m_C8+L^w1_lOZAZ7$&79g$xF%+PD5E~1)0~DJ9lmpQq{V+2?O5@{8QY%V8EC`5? z4{?nMf$<>pL?8vy53&yqSQr{$?D+WP{M>@XW zzyL%HB|rk|R1g;o;^R{@;?s%}b5mh_RClZZ3Y!421Jn$NDh38_28Ik6TLs7v0CCv3 z7#R4tz;;OhxiwJxI)F6D9Y8${0YHolK<w4fxjgUB;@*oe;-5hk3{|+StM>1e?K3X`=|K;W2ft%mpVw2j{H3!*Tb|% z@%MA0s0DIBl3<-bUQYP`|9`jZpXMVxv4=r2Kmdw2Ab@)dpCR!6 z+n0Sc_zTSYA`5NNq9kIU)(6N(#I}(mOZ#;XDwWc$uDG;{Map^m=DhY|`vive(|L(x z#Us6za$&J?zp~)U<>te?%{x+Fy`pnux1cAW(_k)$ym0ZOu1ansQdaA-wp3|UwLgB3 zX9!<}&Z-`9l@P~;sO$APEQk>ixk(`klhIix;kMLy9ay>t_K0iHw9tLjozmgoTHo;Z zywCZrZSL4Z+R&Xd{khP$1FszlYpq5hJG6Sj3oF+8nUuR~Z4@GR=)E$0dpMX*CCk=2 z-Cl3KWo;ZAtA#_!XlFRs9F0a@d+5E%-+oOQ2)#E*@>uljB*yA>VYE!6m_M5BAWNs5 zhR&cxw5MD!*D{x;^!r9hmYP_)lWZbwKi~u9R2rvSS03*N%FanGK zBftnS0)G*K5ApnO;E(@!-iLioGg%1=IR%~3Gg=fG{|S}5t7^3WC!7Ga+Di4)vtkrV zaRcNZ#t&?(J|$)AnA7PQ->SJCORRp#VO+rrRXVFI&HdC*OPVvF*spklU3LB^*nh$s zIR2jFx5FJfpldkYb_locc{(g40#I z6Y4-$BN=5K>~?&+Kj=j4L6A@fRG{DQPhZp+kFZh%k1%0wqKa@N9^pKik?8M3lncej Wnafi-YN`a~Icl763KTzpSNIMHtKS0v literal 0 HcmV?d00001 diff --git a/tests/testdata/macos_minimal_system_version/test_lib_multiple_fat.dynlib b/tests/testdata/macos_minimal_system_version/test_lib_multiple_fat.dynlib new file mode 100644 index 0000000000000000000000000000000000000000..5f7fd5091ea98cf4db80642362748f384c61200a GIT binary patch literal 8956 zcmeHM&ubGw6rPQ-U2B@n!9xX=96Wd^9>hbBjk>T03nCRnkRjBB3MMUei=`wWY>_M} zG(rzO_y_nW6c0gAD0onhJ$Nl7ptl@W360;I%&Z$MR74Mj_u%Et`!R23zun9G^5O7J zTL_VYjzdogkt6pNelk`Z%TugRF(?Zozz8q`i~u9R2rvSS03*N%FanGKBftopFa$pS z_;#oVe}Q>lq@guhLV#{-!eI}+t72+X)qT=UikRYS0z`8l%+*kUMW^e#vi@M zYlN>uXLXNZl@MozSTyU)SP&y3#wLWwPN=h{;MVkc4OqGd_J}*sq|klzos!`{THgx~ zg0GpTYwx+lyNKqT9nM9e+Y9QEa5l;mawDfLf~e?hUQD>_&Q?~r{ot(?y8Z5SB3W=Y z>Go#p183{ZNG%*ns-01{H5d$q+D`N{v!V(m zxB>Ey;|F$hpS-rqnA7PQKkK<2ORRp$aa_T!E}hl3rTduS#Cdvsv`3xD8+2VPi@_S_yF>ow~U+>RI2JD?9LD&%&YwjF#2e{d=3#{@!8(Ez>f z)npb70(}!+Q}2!`1XGQq%6i!Cg>I+YP%J_(rVyyWyyxG(tPwt8EeAef+-{+Yco^an huAmu-`A$S3n|qPEIXOm6wV-^A8pqrM#gE_@egaI<-)aB= literal 0 HcmV?d00001 diff --git a/wheel/lib_file_analyse.py b/wheel/lib_file_analyse.py new file mode 100644 index 00000000..8b435f99 --- /dev/null +++ b/wheel/lib_file_analyse.py @@ -0,0 +1,309 @@ +""" +This module contains function to analyse dynamic library +headers to extract system information + +Currently only for MacOs +""" + +import ctypes +import sys + +"""here the needed const and struct from mach-o header files""" + +FAT_MAGIC = 0xcafebabe +FAT_CIGAM = 0xbebafeca +FAT_MAGIC_64 = 0xcafebabf +FAT_CIGAM_64 = 0xbfbafeca +MH_MAGIC = 0xfeedface +MH_CIGAM = 0xcefaedfe +MH_MAGIC_64 = 0xfeedfacf +MH_CIGAM_64 = 0xcffaedfe + +LC_VERSION_MIN_MACOSX = 0x24 +LC_BUILD_VERSION = 0x32 + + +mach_header_fields = _fields_ = [ + ("magic", ctypes.c_uint32), ("cputype", ctypes.c_int), + ("cpusubtype", ctypes.c_int), ("filetype", ctypes.c_uint32), + ("ncmds", ctypes.c_uint32), ("sizeofcmds", ctypes.c_uint32), + ("flags", ctypes.c_uint32) + ] +""" +struct mach_header { + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ +}; +typedef integer_t cpu_type_t; +typedef integer_t cpu_subtype_t; +""" + +mach_header_fields_64 = mach_header_fields + [("reserved", ctypes.c_uint32)] +""" +struct mach_header_64 { + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ + uint32_t reserved; /* reserved */ +}; +""" + +fat_header_fields = [("magic", ctypes.c_uint32), ("nfat_arch", ctypes.c_uint32)] +""" +struct fat_header { + uint32_t magic; /* FAT_MAGIC or FAT_MAGIC_64 */ + uint32_t nfat_arch; /* number of structs that follow */ +}; +""" + +fat_arch_fields = [ + ("cputype", ctypes.c_int), ("cpusubtype", ctypes.c_int), + ("offset", ctypes.c_uint32), ("size", ctypes.c_uint32), + ("align", ctypes.c_uint32) +] +""" +struct fat_arch { + cpu_type_t cputype; /* cpu specifier (int) */ + cpu_subtype_t cpusubtype; /* machine specifier (int) */ + uint32_t offset; /* file offset to this object file */ + uint32_t size; /* size of this object file */ + uint32_t align; /* alignment as a power of 2 */ +}; +""" + +fat_arch_64_fields = [ + ("cputype", ctypes.c_int), ("cpusubtype", ctypes.c_int), + ("offset", ctypes.c_uint64), ("size", ctypes.c_uint64), + ("align", ctypes.c_uint32), ("reserved", ctypes.c_uint32) +] +""" +struct fat_arch_64 { + cpu_type_t cputype; /* cpu specifier (int) */ + cpu_subtype_t cpusubtype; /* machine specifier (int) */ + uint64_t offset; /* file offset to this object file */ + uint64_t size; /* size of this object file */ + uint32_t align; /* alignment as a power of 2 */ + uint32_t reserved; /* reserved */ +}; +""" + +segment_base_fields = [("cmd", ctypes.c_uint32), ("cmdsize", ctypes.c_uint32)] +"""base for reading segment info""" + +segment_command_fields = [ + ("cmd", ctypes.c_uint32), ("cmdsize", ctypes.c_uint32), + ("segname", ctypes.c_char * 16), ("vmaddr", ctypes.c_uint32), + ("vmsize", ctypes.c_uint32), ("fileoff", ctypes.c_uint32), + ("filesize", ctypes.c_uint32), ("maxprot", ctypes.c_int), + ("initprot", ctypes.c_int), ("nsects", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ] +""" +struct segment_command { /* for 32-bit architectures */ + uint32_t cmd; /* LC_SEGMENT */ + uint32_t cmdsize; /* includes sizeof section structs */ + char segname[16]; /* segment name */ + uint32_t vmaddr; /* memory address of this segment */ + uint32_t vmsize; /* memory size of this segment */ + uint32_t fileoff; /* file offset of this segment */ + uint32_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; +typedef int vm_prot_t; +""" + +segment_command_fields_64 = [ + ("cmd", ctypes.c_uint32), ("cmdsize", ctypes.c_uint32), + ("segname", ctypes.c_char * 16), ("vmaddr", ctypes.c_uint64), + ("vmsize", ctypes.c_uint64), ("fileoff", ctypes.c_uint64), + ("filesize", ctypes.c_uint64), ("maxprot", ctypes.c_int), + ("initprot", ctypes.c_int), ("nsects", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ] +""" +struct segment_command_64 { /* for 64-bit architectures */ + uint32_t cmd; /* LC_SEGMENT_64 */ + uint32_t cmdsize; /* includes sizeof section_64 structs */ + char segname[16]; /* segment name */ + uint64_t vmaddr; /* memory address of this segment */ + uint64_t vmsize; /* memory size of this segment */ + uint64_t fileoff; /* file offset of this segment */ + uint64_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; +""" + +version_min_command_fields = segment_base_fields + \ + [("version", ctypes.c_uint32), ("sdk", ctypes.c_uint32)] +""" +struct version_min_command { + uint32_t cmd; /* LC_VERSION_MIN_MACOSX or + LC_VERSION_MIN_IPHONEOS or + LC_VERSION_MIN_WATCHOS or + LC_VERSION_MIN_TVOS */ + uint32_t cmdsize; /* sizeof(struct min_version_command) */ + uint32_t version; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ +}; +""" + +build_version_command_fields = segment_base_fields + \ + [("platform", ctypes.c_uint32), ("minos", ctypes.c_uint32), + ("sdk", ctypes.c_uint32), ("ntools", ctypes.c_uint32)] +""" +struct build_version_command { + uint32_t cmd; /* LC_BUILD_VERSION */ + uint32_t cmdsize; /* sizeof(struct build_version_command) plus */ + /* ntools * sizeof(struct build_tool_version) */ + uint32_t platform; /* platform */ + uint32_t minos; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t ntools; /* number of tool entries following this */ +}; +""" + + +def swap32(x): + return (((x << 24) & 0xFF000000) | + ((x << 8) & 0x00FF0000) | + ((x >> 8) & 0x0000FF00) | + ((x >> 24) & 0x000000FF)) + + +def get_base_class_and_magic_number(lib_file, seek=None): + if seek is None: + seek = lib_file.tell() + else: + lib_file.seek(seek) + magic_number = ctypes.c_uint32.from_buffer_copy( + lib_file.read(ctypes.sizeof(ctypes.c_uint32))).value + # Handle wrong byte order + if magic_number in [FAT_CIGAM, FAT_CIGAM_64, MH_CIGAM, MH_CIGAM_64]: + if sys.byteorder == "little": + BaseClass = ctypes.BigEndianStructure + else: + BaseClass = ctypes.LittleEndianStructure + magic_number = swap32(magic_number) + else: + BaseClass = ctypes.Structure + lib_file.seek(seek) + return BaseClass, magic_number + + +def read_data(struct_class, lib_file): + return struct_class.from_buffer_copy(lib_file.read( + ctypes.sizeof(struct_class))) + + +def extract_macosx_min_system_version(path_to_lib): + with open(path_to_lib, "rb") as lib_file: + BaseClass, magic_number = get_base_class_and_magic_number(lib_file, 0) + if magic_number not in [FAT_MAGIC, FAT_MAGIC_64, MH_MAGIC, MH_MAGIC_64]: + return + if magic_number in [FAT_MAGIC, FAT_CIGAM_64]: + class FatHeader(BaseClass): + _fields_ = fat_header_fields + fat_header = read_data(FatHeader, lib_file) + if magic_number == FAT_MAGIC: + + class FatArch(BaseClass): + _fields_ = fat_arch_fields + else: + + class FatArch(BaseClass): + _fields_ = fat_arch_64_fields + fat_arch_list = [read_data(FatArch, lib_file) for _ in range(fat_header.nfat_arch)] + + class SegmentBase(BaseClass): + _fields_ = segment_base_fields + versions_list = [] + for el in fat_arch_list: + try: + version = read_mach_header(lib_file, el.offset) + if version is not None: + versions_list.append(version) + except ValueError: + pass + return max(versions_list) + else: + try: + return read_mach_header(lib_file, 0) + except ValueError: + """when some error during read library files""" + return None + + +def read_mach_header(lib_file, seek=None): + """ + This funcition parse mach-O header and extract + information about minimal system version + + :param lib_file: reference to opened library file with pointer + """ + if seek is not None: + lib_file.seek(seek) + base_class, magic_number = get_base_class_and_magic_number(lib_file) + arch = "32" if magic_number == MH_MAGIC else "64" + + class SegmentBase(base_class): + _fields_ = segment_base_fields + if arch == "32": + + class MachHeader(base_class): + _fields_ = mach_header_fields + + class SegmentCommand(base_class): + _fields_ = segment_command_fields + else: + + class MachHeader(base_class): + _fields_ = mach_header_fields_64 + + class SegmentCommand(base_class): + _fields_ = segment_command_fields_64 + mach_header = read_data(MachHeader, lib_file) + for _i in range(mach_header.ncmds): + pos = lib_file.tell() + segment_base = read_data(SegmentBase, lib_file) + lib_file.seek(pos) + if segment_base.cmd == LC_VERSION_MIN_MACOSX: + class VersionMinCommand(base_class): + _fields_ = version_min_command_fields + version_info = read_data(VersionMinCommand, lib_file) + return parse_version(version_info.version) + elif segment_base.cmd == LC_BUILD_VERSION: + class VersionBuild(base_class): + _fields_ = build_version_command_fields + version_info = read_data(VersionBuild, lib_file) + return parse_version(version_info.minos) + else: + lib_file.seek(pos + segment_base.cmdsize) + continue + + +def parse_version(version): + zz = version & 2**9-1 + version >>= 8 + yy = version & 2**9-1 + version >>= 8 + return version, yy, zz + + +def test_extract(): + print("path", sys.argv[1]) + print(extract_macosx_min_system_version(sys.argv[1])) diff --git a/wheel/pep425tags.py b/wheel/pep425tags.py index b9242efa..ed795baa 100644 --- a/wheel/pep425tags.py +++ b/wheel/pep425tags.py @@ -3,6 +3,7 @@ import distutils.util import platform import sys +import os import sysconfig import warnings @@ -108,7 +109,19 @@ def get_abi_tag(): def get_platform(): """Return our platform name 'win32', 'linux_x86_64'""" # XXX remove distutils dependency - result = distutils.util.get_platform().replace('.', '_').replace('-', '_') + result = distutils.util.get_platform() + # check if build is not created against newer api than python is + # compiled (on macosx) + if result.startswith("macosx-") and \ + "MACOSX_DEPLOYMENT_TARGET" in os.environ: + result.rfind() + tag = result[7:result.rfind("-")] + parsed_tag = tuple(map(int, tag.split("."))) + parsed_target = tuple(map(int, os.environ["MACOSX_DEPLOYMENT_TARGET"].split("."))) + if parsed_tag < parsed_target: + result = "macosx-" + os.environ["MACOSX_DEPLOYMENT_TARGET"] +\ + result[result.rfind('-'):] + result = result.replace('.', '_').replace('-', '_') if result == "linux_x86_64" and sys.maxsize == 2147483647: # pip pull request #3497 result = "linux_i686" From a3da68ddefae6ac109d96ea56b6c8c47b6c85b77 Mon Sep 17 00:00:00 2001 From: czaki Date: Tue, 24 Sep 2019 18:14:52 +0200 Subject: [PATCH 2/9] add check macos version to code --- setup.py | 1 - wheel/bdist_wheel.py | 10 ++++++++-- wheel/lib_file_analyse.py | 5 ----- wheel/pep425tags.py | 36 ++++++++++++++++++++++-------------- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/setup.py b/setup.py index 643d1069..e72241ae 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,6 @@ def readall(*args): entry_points={ 'console_scripts': [ 'wheel=wheel.cli:main', - 'lib_test=wheel.lib_file_analyse:test_extract' ], 'distutils.commands': [ 'bdist_wheel=wheel.bdist_wheel:bdist_wheel' diff --git a/wheel/bdist_wheel.py b/wheel/bdist_wheel.py index c79307b5..bc8a05ea 100644 --- a/wheel/bdist_wheel.py +++ b/wheel/bdist_wheel.py @@ -22,6 +22,7 @@ from .pkginfo import write_pkg_info from .metadata import pkginfo_to_metadata from .wheelfile import WheelFile + from . import pep425tags from . import __version__ as wheel_version @@ -48,7 +49,7 @@ class bdist_wheel(Command): "temporary directory for creating the distribution"), ('plat-name=', 'p', "platform name to embed in generated filenames " - "(default: %s)" % get_platform()), + "(default: %s)" % get_platform(None)), ('keep-temp', 'k', "keep the pseudo-installation tree around after " + "creating the distribution archive"), @@ -150,7 +151,11 @@ def get_tag(self): elif self.root_is_pure: plat_name = 'any' else: - plat_name = self.plat_name or get_platform() + # macosx contains system version in platform name so need special handle + if self.plat_name and not self.plat_name.startswith("macosx"): + plat_name = self.plat_name + else: + plat_name = get_platform(self.bdist_dir) if plat_name in ('linux-x86_64', 'linux_x86_64') and sys.maxsize == 2147483647: plat_name = 'linux_i686' plat_name = plat_name.replace('-', '_').replace('.', '_') @@ -173,6 +178,7 @@ def get_tag(self): abi_tag = str(get_abi_tag()).lower() tag = (impl, abi_tag, plat_name) supported_tags = pep425tags.get_supported( + self.bdist_dir, supplied_platform=plat_name if self.plat_name_supplied else None) # XXX switch to this alternate implementation for non-pure: if not self.py_limited_api: diff --git a/wheel/lib_file_analyse.py b/wheel/lib_file_analyse.py index 8b435f99..7db14527 100644 --- a/wheel/lib_file_analyse.py +++ b/wheel/lib_file_analyse.py @@ -302,8 +302,3 @@ def parse_version(version): yy = version & 2**9-1 version >>= 8 return version, yy, zz - - -def test_extract(): - print("path", sys.argv[1]) - print(extract_macosx_min_system_version(sys.argv[1])) diff --git a/wheel/pep425tags.py b/wheel/pep425tags.py index ed795baa..847349e2 100644 --- a/wheel/pep425tags.py +++ b/wheel/pep425tags.py @@ -6,6 +6,8 @@ import os import sysconfig import warnings +from .lib_file_analyse import extract_macosx_min_system_version + try: from importlib.machinery import all_suffixes as get_all_suffixes @@ -106,21 +108,27 @@ def get_abi_tag(): return abi -def get_platform(): +def get_platform(archive_root): """Return our platform name 'win32', 'linux_x86_64'""" # XXX remove distutils dependency result = distutils.util.get_platform() - # check if build is not created against newer api than python is - # compiled (on macosx) - if result.startswith("macosx-") and \ - "MACOSX_DEPLOYMENT_TARGET" in os.environ: - result.rfind() - tag = result[7:result.rfind("-")] - parsed_tag = tuple(map(int, tag.split("."))) - parsed_target = tuple(map(int, os.environ["MACOSX_DEPLOYMENT_TARGET"].split("."))) - if parsed_tag < parsed_target: - result = "macosx-" + os.environ["MACOSX_DEPLOYMENT_TARGET"] +\ - result[result.rfind('-'):] + if result.startswith("macosx") and archive_root is not None: + prefix, base_version, suffix = result.split('-') + base_version = tuple([int(x) for x in base_version.split(".")]) + if len(base_version) == 2: + base_version = base_version + (0,) + for (dirpath, dirnames, filenames) in os.walk(archive_root): + for filename in filenames: + if filename.endswith('.dynlib') or filename.endswith('.so'): + version = extract_macosx_min_system_version( + os.path.join(dirpath, filename) + ) + if version is not None: + base_version = max(base_version, version) + if base_version[-1] == 0: + base_version = base_version[:-1] + base_version = "_".join([str(x) for x in base_version]) + result = prefix + "_" + base_version + "_" + suffix result = result.replace('.', '_').replace('-', '_') if result == "linux_x86_64" and sys.maxsize == 2147483647: # pip pull request #3497 @@ -128,7 +136,7 @@ def get_platform(): return result -def get_supported(versions=None, supplied_platform=None): +def get_supported(archive_root, versions=None, supplied_platform=None): """Return a list of supported tags for each version specified in `versions`. @@ -166,7 +174,7 @@ def get_supported(versions=None, supplied_platform=None): platforms = [] if supplied_platform: platforms.append(supplied_platform) - platforms.append(get_platform()) + platforms.append(get_platform(archive_root)) # Current version, current API (built specifically for our Python): for abi in abis: From 93f1809a6b2ba6d5072f4efc925b6028129acb2f Mon Sep 17 00:00:00 2001 From: czaki Date: Wed, 25 Sep 2019 23:20:03 +0200 Subject: [PATCH 3/9] Add tests for get_platform, warning on update of macosx minimal version and description on begin of Mach-O parser --- setup.py | 2 +- tests/test_lib_file_analyse.py | 49 ++++++++++++++++++++++++++++++++++ wheel/lib_file_analyse.py | 32 +++++++++++++++++++++- wheel/pep425tags.py | 34 +++++++++++++++++++++-- 4 files changed, 113 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index e72241ae..8cd33b11 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ def readall(*args): packages=find_packages(), python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", extras_require={ - 'test': ['pytest >= 3.0.0', 'pytest-cov'] + 'test': ['pytest >= 3.0.0', 'pytest-cov', 'pytest-mock'] }, include_package_data=True, zip_safe=False, diff --git a/tests/test_lib_file_analyse.py b/tests/test_lib_file_analyse.py index bf8597d3..fd18c52a 100644 --- a/tests/test_lib_file_analyse.py +++ b/tests/test_lib_file_analyse.py @@ -1,5 +1,6 @@ import os from wheel.lib_file_analyse import extract_macosx_min_system_version +from wheel.pep425tags import get_platform def test_read_from_dynlib(): @@ -27,3 +28,51 @@ def test_read_from_dynlib(): assert extract_macosx_min_system_version( os.path.join(dylib_dir, "test_lib.c") ) is None + + +def test_get_platform_macos(mocker, capsys): + print(mocker, mocker.patch, mocker.patch.mock_module) + dirname = os.path.dirname(__file__) + dylib_dir = os.path.join(dirname, "testdata", + "macos_minimal_system_version") + with mocker.patch("distutils.util.get_platform", return_value="macosx-10.14-x86_64"): + assert get_platform(dylib_dir) == "macosx_10_14_x86_64" + with mocker.patch("distutils.util.get_platform", return_value="macosx-10.9-x86_64"): + assert get_platform(dylib_dir) == "macosx_10_14_x86_64" + captured = capsys.readouterr() + assert "[WARNING] This wheel needs higher macosx version than" in captured.err + with mocker.patch("distutils.util.get_platform", return_value="macosx-10.9-x86_64"): + with mocker.patch("os.walk", return_value=[ + (dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_10_fat.dynlib"])]): + assert get_platform(dylib_dir) == "macosx_10_10_x86_64" + captured = capsys.readouterr() + assert "[WARNING] This wheel needs higher macosx version than" in captured.err + + with mocker.patch("os.walk", return_value=[ + (dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])]): + assert get_platform(dylib_dir) == "macosx_10_9_x86_64" + mocker.patch.dict('os.environ', {"MACOSX_DEPLOYMENT_TARGET": "10.10"}) + assert get_platform(dylib_dir) == "macosx_10_10_x86_64" + + mocker.stopall() + with mocker.patch("distutils.util.get_platform", return_value="macosx-10.9-x86_64"): + mocker.patch.dict('os.environ', {"MACOSX_DEPLOYMENT_TARGET": "10.8"}) + with mocker.patch("os.walk", return_value=[ + (dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])]): + assert get_platform(dylib_dir) == "macosx_10_9_x86_64" + captured = capsys.readouterr() + print("aa", captured.err) + assert "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to lower value" in captured.err + with mocker.patch("os.walk", return_value=[ + (dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_10_fat.dynlib"])]): + assert get_platform(dylib_dir) == "macosx_10_10_x86_64" + captured = capsys.readouterr() + print("aa", captured.err) + assert "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to lower value" in captured.err + mocker.stopall() + + +def test_get_platform_linux(mocker): + with mocker.patch("distutils.util.get_platform", return_value="linux_x86_64"): + mocker.patch("sys.maxsize", new=2147483647) + assert get_platform(None) == "linux_i686" diff --git a/wheel/lib_file_analyse.py b/wheel/lib_file_analyse.py index 7db14527..42b92a47 100644 --- a/wheel/lib_file_analyse.py +++ b/wheel/lib_file_analyse.py @@ -2,7 +2,37 @@ This module contains function to analyse dynamic library headers to extract system information -Currently only for MacOs +Currently only for MacOSX + +Library file on macosx system starts with Mach-O or Fat field. +This can be distinguish by first 32 bites and it is called magic number. +Proper value of magic number is with suffix _MAGIC. Suffix _CIGAM means +reversed bytes order. +Both fields can occur in two types: 32 and 64 bytes. + +FAT field inform that this library contains few version of library +(typically for different types version). It contains +information where Mach-O headers starts. + +Each section started with Mach-O header contains one library +(So if file starts with this field it contains only one version). + +After filed Mach-O there are section fields. +Each of them starts with two fields: +cmd - magic number for this command +cmdsize - total size occupied by this section information. + +In this case only sections LC_VERSION_MIN_MACOSX (for macosx 10.13 and earlier) +and LC_BUILD_VERSION (for macosx 10.14 and newer) are interesting, +because them contains information about minimal system version. + +Important remarks: +- For fat files this implementation looks for maximum number version. + It not check if it is 32 or 64 and do not compare it with currently builded package. + So it is possible to false report higher version that needed. +- All structures signatures are taken form macosx header files. +- I think that binary format will be more stable than `otool` output. + and if apple introduce some changes both implementation will need to be updated. """ import ctypes diff --git a/wheel/pep425tags.py b/wheel/pep425tags.py index 847349e2..8e89c54b 100644 --- a/wheel/pep425tags.py +++ b/wheel/pep425tags.py @@ -117,6 +117,21 @@ def get_platform(archive_root): base_version = tuple([int(x) for x in base_version.split(".")]) if len(base_version) == 2: base_version = base_version + (0,) + assert len(base_version) == 3 + if "MACOSX_DEPLOYMENT_TARGET" in os.environ: + deploy_target = tuple([int(x) for x in os.environ[ + "MACOSX_DEPLOYMENT_TARGET"].split(".")]) + if len(deploy_target) == 2: + deploy_target = deploy_target + (0,) + if deploy_target < base_version: + sys.stderr.write( + "[WARNING] MACOSX_DEPLOYMENT_TARGET is set " + "to lower value than your python is compiled\n" + ) + else: + base_version = deploy_target + assert len(base_version) == 3 + start_version = base_version for (dirpath, dirnames, filenames) in os.walk(archive_root): for filename in filenames: if filename.endswith('.dynlib') or filename.endswith('.so'): @@ -126,8 +141,23 @@ def get_platform(archive_root): if version is not None: base_version = max(base_version, version) if base_version[-1] == 0: - base_version = base_version[:-1] - base_version = "_".join([str(x) for x in base_version]) + fin_base_version = base_version[:-1] + if start_version < base_version: + if "MACOSX_DEPLOYMENT_TARGET" in os.environ: + sys.stderr.write( + "[WARNING] This wheel needs higher macosx version than " + "is set in MACOSX_DEPLOYMENT_TARGET variable. " + "To silence this warning set MACOSX_DEPLOYMENT_TARGET to " + + ".".join([str(x) for x in fin_base_version]) + "\n" + ) + else: + sys.stderr.write( + "[WARNING] This wheel needs higher macosx version than " + "your python is compiled against " + "To silence this warning set MACOSX_DEPLOYMENT_TARGET to " + + ".".join([str(x) for x in fin_base_version]) + "\n" + ) + base_version = "_".join([str(x) for x in fin_base_version]) result = prefix + "_" + base_version + "_" + suffix result = result.replace('.', '_').replace('-', '_') if result == "linux_x86_64" and sys.maxsize == 2147483647: From 9144297e9920f53fefc37458c4f6ced8daa661aa Mon Sep 17 00:00:00 2001 From: czaki Date: Thu, 26 Sep 2019 07:41:48 +0200 Subject: [PATCH 4/9] fix error for undefined fin_base_version varaible --- wheel/pep425tags.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wheel/pep425tags.py b/wheel/pep425tags.py index 8e89c54b..f35664c6 100644 --- a/wheel/pep425tags.py +++ b/wheel/pep425tags.py @@ -142,6 +142,8 @@ def get_platform(archive_root): base_version = max(base_version, version) if base_version[-1] == 0: fin_base_version = base_version[:-1] + else: + fin_base_version = base_version if start_version < base_version: if "MACOSX_DEPLOYMENT_TARGET" in os.environ: sys.stderr.write( From d4ad3d917299ec549df6f951ca4eac1ca61f28df Mon Sep 17 00:00:00 2001 From: czaki Date: Thu, 26 Sep 2019 10:59:07 +0200 Subject: [PATCH 5/9] add more verbose output to inform which files forced to bump version --- tests/test_lib_file_analyse.py | 3 +++ wheel/pep425tags.py | 32 +++++++++++++++++--------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/tests/test_lib_file_analyse.py b/tests/test_lib_file_analyse.py index fd18c52a..37b734e1 100644 --- a/tests/test_lib_file_analyse.py +++ b/tests/test_lib_file_analyse.py @@ -47,12 +47,15 @@ def test_get_platform_macos(mocker, capsys): assert get_platform(dylib_dir) == "macosx_10_10_x86_64" captured = capsys.readouterr() assert "[WARNING] This wheel needs higher macosx version than" in captured.err + assert "test_lib_10_10_fat.dynlib" in captured.err with mocker.patch("os.walk", return_value=[ (dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])]): assert get_platform(dylib_dir) == "macosx_10_9_x86_64" mocker.patch.dict('os.environ', {"MACOSX_DEPLOYMENT_TARGET": "10.10"}) assert get_platform(dylib_dir) == "macosx_10_10_x86_64" + captured = capsys.readouterr() + assert captured.err == "" mocker.stopall() with mocker.patch("distutils.util.get_platform", return_value="macosx-10.9-x86_64"): diff --git a/wheel/pep425tags.py b/wheel/pep425tags.py index f35664c6..e28dc179 100644 --- a/wheel/pep425tags.py +++ b/wheel/pep425tags.py @@ -132,35 +132,37 @@ def get_platform(archive_root): base_version = deploy_target assert len(base_version) == 3 start_version = base_version + versions_dict = {} for (dirpath, dirnames, filenames) in os.walk(archive_root): for filename in filenames: if filename.endswith('.dynlib') or filename.endswith('.so'): - version = extract_macosx_min_system_version( - os.path.join(dirpath, filename) - ) - if version is not None: - base_version = max(base_version, version) + lib_path = os.path.join(dirpath, filename) + versions_dict[lib_path] = extract_macosx_min_system_version(lib_path) + if len(versions_dict) > 0: + base_version = max(base_version, max(versions_dict.values())) if base_version[-1] == 0: fin_base_version = base_version[:-1] else: fin_base_version = base_version + fin_base_version = "_".join([str(x) for x in fin_base_version]) if start_version < base_version: + problematic_files = [k for k, v in versions_dict.items() if v > start_version] + problematic_files = "\n".join(problematic_files) + error_message = \ + "[WARNING] This wheel needs higher macosx version than {} " \ + "is set in MACOSX_DEPLOYMENT_TARGET variable. " \ + "To silence this warning set MACOSX_DEPLOYMENT_TARGET to " +\ + fin_base_version + " or recreate this files with lower " \ + "MACOSX_DEPLOYMENT_TARGET \n" + problematic_files if "MACOSX_DEPLOYMENT_TARGET" in os.environ: sys.stderr.write( - "[WARNING] This wheel needs higher macosx version than " - "is set in MACOSX_DEPLOYMENT_TARGET variable. " - "To silence this warning set MACOSX_DEPLOYMENT_TARGET to " + - ".".join([str(x) for x in fin_base_version]) + "\n" + error_message.format("is set in MACOSX_DEPLOYMENT_TARGET variable.") ) else: sys.stderr.write( - "[WARNING] This wheel needs higher macosx version than " - "your python is compiled against " - "To silence this warning set MACOSX_DEPLOYMENT_TARGET to " + - ".".join([str(x) for x in fin_base_version]) + "\n" + error_message.format("your python is compiled against.") ) - base_version = "_".join([str(x) for x in fin_base_version]) - result = prefix + "_" + base_version + "_" + suffix + result = prefix + "_" + fin_base_version + "_" + suffix result = result.replace('.', '_').replace('-', '_') if result == "linux_x86_64" and sys.maxsize == 2147483647: # pip pull request #3497 From 306cde92e24e2e8e20d08ab6b9bef62495ef2a0b Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 26 Sep 2019 17:06:50 +0200 Subject: [PATCH 6/9] Fix errors pointed in review. --- setup.py | 2 +- ..._file_analyse.py => test_macosx_libfile.py} | 3 ++- wheel/bdist_wheel.py | 3 ++- .../{lib_file_analyse.py => macosx_libfile.py} | 18 +++++++++++++++++- wheel/pep425tags.py | 12 +++++++++++- 5 files changed, 33 insertions(+), 5 deletions(-) rename tests/{test_lib_file_analyse.py => test_macosx_libfile.py} (98%) rename wheel/{lib_file_analyse.py => macosx_libfile.py} (98%) diff --git a/setup.py b/setup.py index 8cd33b11..52a63958 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ def readall(*args): zip_safe=False, entry_points={ 'console_scripts': [ - 'wheel=wheel.cli:main', + 'wheel=wheel.cli:main' ], 'distutils.commands': [ 'bdist_wheel=wheel.bdist_wheel:bdist_wheel' diff --git a/tests/test_lib_file_analyse.py b/tests/test_macosx_libfile.py similarity index 98% rename from tests/test_lib_file_analyse.py rename to tests/test_macosx_libfile.py index 37b734e1..03848507 100644 --- a/tests/test_lib_file_analyse.py +++ b/tests/test_macosx_libfile.py @@ -1,5 +1,6 @@ import os -from wheel.lib_file_analyse import extract_macosx_min_system_version + +from wheel.macosx_libfile import extract_macosx_min_system_version from wheel.pep425tags import get_platform diff --git a/wheel/bdist_wheel.py b/wheel/bdist_wheel.py index bc8a05ea..014983c1 100644 --- a/wheel/bdist_wheel.py +++ b/wheel/bdist_wheel.py @@ -22,7 +22,6 @@ from .pkginfo import write_pkg_info from .metadata import pkginfo_to_metadata from .wheelfile import WheelFile - from . import pep425tags from . import __version__ as wheel_version @@ -156,8 +155,10 @@ def get_tag(self): plat_name = self.plat_name else: plat_name = get_platform(self.bdist_dir) + if plat_name in ('linux-x86_64', 'linux_x86_64') and sys.maxsize == 2147483647: plat_name = 'linux_i686' + plat_name = plat_name.replace('-', '_').replace('.', '_') if self.root_is_pure: diff --git a/wheel/lib_file_analyse.py b/wheel/macosx_libfile.py similarity index 98% rename from wheel/lib_file_analyse.py rename to wheel/macosx_libfile.py index 42b92a47..5056f357 100644 --- a/wheel/lib_file_analyse.py +++ b/wheel/macosx_libfile.py @@ -222,15 +222,18 @@ def get_base_class_and_magic_number(lib_file, seek=None): lib_file.seek(seek) magic_number = ctypes.c_uint32.from_buffer_copy( lib_file.read(ctypes.sizeof(ctypes.c_uint32))).value + # Handle wrong byte order if magic_number in [FAT_CIGAM, FAT_CIGAM_64, MH_CIGAM, MH_CIGAM_64]: if sys.byteorder == "little": BaseClass = ctypes.BigEndianStructure else: BaseClass = ctypes.LittleEndianStructure + magic_number = swap32(magic_number) else: BaseClass = ctypes.Structure + lib_file.seek(seek) return BaseClass, magic_number @@ -245,9 +248,11 @@ def extract_macosx_min_system_version(path_to_lib): BaseClass, magic_number = get_base_class_and_magic_number(lib_file, 0) if magic_number not in [FAT_MAGIC, FAT_MAGIC_64, MH_MAGIC, MH_MAGIC_64]: return + if magic_number in [FAT_MAGIC, FAT_CIGAM_64]: class FatHeader(BaseClass): _fields_ = fat_header_fields + fat_header = read_data(FatHeader, lib_file) if magic_number == FAT_MAGIC: @@ -257,10 +262,12 @@ class FatArch(BaseClass): class FatArch(BaseClass): _fields_ = fat_arch_64_fields + fat_arch_list = [read_data(FatArch, lib_file) for _ in range(fat_header.nfat_arch)] class SegmentBase(BaseClass): _fields_ = segment_base_fields + versions_list = [] for el in fat_arch_list: try: @@ -269,7 +276,12 @@ class SegmentBase(BaseClass): versions_list.append(version) except ValueError: pass - return max(versions_list) + + if len(versions_list) > 0: + return max(versions_list) + else: + return None + else: try: return read_mach_header(lib_file, 0) @@ -299,6 +311,7 @@ class MachHeader(base_class): class SegmentCommand(base_class): _fields_ = segment_command_fields + else: class MachHeader(base_class): @@ -306,6 +319,7 @@ class MachHeader(base_class): class SegmentCommand(base_class): _fields_ = segment_command_fields_64 + mach_header = read_data(MachHeader, lib_file) for _i in range(mach_header.ncmds): pos = lib_file.tell() @@ -314,11 +328,13 @@ class SegmentCommand(base_class): if segment_base.cmd == LC_VERSION_MIN_MACOSX: class VersionMinCommand(base_class): _fields_ = version_min_command_fields + version_info = read_data(VersionMinCommand, lib_file) return parse_version(version_info.version) elif segment_base.cmd == LC_BUILD_VERSION: class VersionBuild(base_class): _fields_ = build_version_command_fields + version_info = read_data(VersionBuild, lib_file) return parse_version(version_info.minos) else: diff --git a/wheel/pep425tags.py b/wheel/pep425tags.py index e28dc179..4776fb89 100644 --- a/wheel/pep425tags.py +++ b/wheel/pep425tags.py @@ -6,7 +6,8 @@ import os import sysconfig import warnings -from .lib_file_analyse import extract_macosx_min_system_version + +from .macosx_libfile import extract_macosx_min_system_version try: @@ -117,6 +118,7 @@ def get_platform(archive_root): base_version = tuple([int(x) for x in base_version.split(".")]) if len(base_version) == 2: base_version = base_version + (0,) + assert len(base_version) == 3 if "MACOSX_DEPLOYMENT_TARGET" in os.environ: deploy_target = tuple([int(x) for x in os.environ[ @@ -130,6 +132,7 @@ def get_platform(archive_root): ) else: base_version = deploy_target + assert len(base_version) == 3 start_version = base_version versions_dict = {} @@ -138,12 +141,15 @@ def get_platform(archive_root): if filename.endswith('.dynlib') or filename.endswith('.so'): lib_path = os.path.join(dirpath, filename) versions_dict[lib_path] = extract_macosx_min_system_version(lib_path) + if len(versions_dict) > 0: base_version = max(base_version, max(versions_dict.values())) + if base_version[-1] == 0: fin_base_version = base_version[:-1] else: fin_base_version = base_version + fin_base_version = "_".join([str(x) for x in fin_base_version]) if start_version < base_version: problematic_files = [k for k, v in versions_dict.items() if v > start_version] @@ -154,6 +160,7 @@ def get_platform(archive_root): "To silence this warning set MACOSX_DEPLOYMENT_TARGET to " +\ fin_base_version + " or recreate this files with lower " \ "MACOSX_DEPLOYMENT_TARGET \n" + problematic_files + if "MACOSX_DEPLOYMENT_TARGET" in os.environ: sys.stderr.write( error_message.format("is set in MACOSX_DEPLOYMENT_TARGET variable.") @@ -162,11 +169,14 @@ def get_platform(archive_root): sys.stderr.write( error_message.format("your python is compiled against.") ) + result = prefix + "_" + fin_base_version + "_" + suffix + result = result.replace('.', '_').replace('-', '_') if result == "linux_x86_64" and sys.maxsize == 2147483647: # pip pull request #3497 result = "linux_i686" + return result From be741f76ed4cb97a42b92a7426c78de2570dbdf9 Mon Sep 17 00:00:00 2001 From: czaki Date: Thu, 26 Sep 2019 23:06:24 +0200 Subject: [PATCH 7/9] change tests to use monkeypatching instead pytest-mock --- setup.py | 2 +- tests/test_macosx_libfile.py | 94 ++++++++++++++++++++---------------- 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/setup.py b/setup.py index 52a63958..286e071e 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ def readall(*args): packages=find_packages(), python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", extras_require={ - 'test': ['pytest >= 3.0.0', 'pytest-cov', 'pytest-mock'] + 'test': ['pytest >= 3.0.0', 'pytest-cov'] }, include_package_data=True, zip_safe=False, diff --git a/tests/test_macosx_libfile.py b/tests/test_macosx_libfile.py index 03848507..cdba34ba 100644 --- a/tests/test_macosx_libfile.py +++ b/tests/test_macosx_libfile.py @@ -1,4 +1,6 @@ import os +import sys +import distutils.util from wheel.macosx_libfile import extract_macosx_min_system_version from wheel.pep425tags import get_platform @@ -31,52 +33,60 @@ def test_read_from_dynlib(): ) is None -def test_get_platform_macos(mocker, capsys): - print(mocker, mocker.patch, mocker.patch.mock_module) +def return_factory(return_val): + def fun(*args, **kwargs): + return return_val + + return fun + + +def test_get_platform_macos(monkeypatch, capsys): dirname = os.path.dirname(__file__) dylib_dir = os.path.join(dirname, "testdata", "macos_minimal_system_version") - with mocker.patch("distutils.util.get_platform", return_value="macosx-10.14-x86_64"): - assert get_platform(dylib_dir) == "macosx_10_14_x86_64" - with mocker.patch("distutils.util.get_platform", return_value="macosx-10.9-x86_64"): - assert get_platform(dylib_dir) == "macosx_10_14_x86_64" - captured = capsys.readouterr() - assert "[WARNING] This wheel needs higher macosx version than" in captured.err - with mocker.patch("distutils.util.get_platform", return_value="macosx-10.9-x86_64"): - with mocker.patch("os.walk", return_value=[ - (dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_10_fat.dynlib"])]): - assert get_platform(dylib_dir) == "macosx_10_10_x86_64" - captured = capsys.readouterr() - assert "[WARNING] This wheel needs higher macosx version than" in captured.err - assert "test_lib_10_10_fat.dynlib" in captured.err + monkeypatch.setattr(distutils.util, "get_platform", return_factory("macosx-10.14-x86_64")) + assert get_platform(dylib_dir) == "macosx_10_14_x86_64" + monkeypatch.setattr(distutils.util, "get_platform", return_factory("macosx-10.9-x86_64")) + assert get_platform(dylib_dir) == "macosx_10_14_x86_64" + captured = capsys.readouterr() + assert "[WARNING] This wheel needs higher macosx version than" in captured.err + + monkeypatch.setattr(os, "walk", return_factory( + [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_10_fat.dynlib"])] + )) + assert get_platform(dylib_dir) == "macosx_10_10_x86_64" + captured = capsys.readouterr() + assert "[WARNING] This wheel needs higher macosx version than" in captured.err + assert "test_lib_10_10_fat.dynlib" in captured.err + + monkeypatch.setattr(os, "walk", return_factory( + [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])] + )) + assert get_platform(dylib_dir) == "macosx_10_9_x86_64" + monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "10.10") + assert get_platform(dylib_dir) == "macosx_10_10_x86_64" + captured = capsys.readouterr() + assert captured.err == "" - with mocker.patch("os.walk", return_value=[ - (dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])]): - assert get_platform(dylib_dir) == "macosx_10_9_x86_64" - mocker.patch.dict('os.environ', {"MACOSX_DEPLOYMENT_TARGET": "10.10"}) - assert get_platform(dylib_dir) == "macosx_10_10_x86_64" - captured = capsys.readouterr() - assert captured.err == "" + monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "10.8") + monkeypatch.setattr(os, "walk", return_factory( + [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])] + )) + assert get_platform(dylib_dir) == "macosx_10_9_x86_64" + captured = capsys.readouterr() + print("aa", captured.err) + assert "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to lower value" in captured.err - mocker.stopall() - with mocker.patch("distutils.util.get_platform", return_value="macosx-10.9-x86_64"): - mocker.patch.dict('os.environ', {"MACOSX_DEPLOYMENT_TARGET": "10.8"}) - with mocker.patch("os.walk", return_value=[ - (dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])]): - assert get_platform(dylib_dir) == "macosx_10_9_x86_64" - captured = capsys.readouterr() - print("aa", captured.err) - assert "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to lower value" in captured.err - with mocker.patch("os.walk", return_value=[ - (dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_10_fat.dynlib"])]): - assert get_platform(dylib_dir) == "macosx_10_10_x86_64" - captured = capsys.readouterr() - print("aa", captured.err) - assert "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to lower value" in captured.err - mocker.stopall() + monkeypatch.setattr(os, "walk", return_factory( + [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_10_fat.dynlib"])] + )) + assert get_platform(dylib_dir) == "macosx_10_10_x86_64" + captured = capsys.readouterr() + print("aa", captured.err) + assert "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to lower value" in captured.err -def test_get_platform_linux(mocker): - with mocker.patch("distutils.util.get_platform", return_value="linux_x86_64"): - mocker.patch("sys.maxsize", new=2147483647) - assert get_platform(None) == "linux_i686" +def test_get_platform_linux(monkeypatch): + monkeypatch.setattr(distutils.util, "get_platform", return_factory("linux_x86_64")) + monkeypatch.setattr(sys, "maxsize", 2147483647) + assert get_platform(None) == "linux_i686" From 1cde26cbb3093e1f11cc0f143958a70e4e7a350a Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 27 Sep 2019 10:40:08 +0200 Subject: [PATCH 8/9] fix typo in testdata sub folder. Refactor tests to be clear what they test --- tests/test_macosx_libfile.py | 104 +++++++++++------- .../test_lib.c | 0 .../test_lib_10_10.dynlib | Bin .../test_lib_10_10_386.dynlib | Bin .../test_lib_10_10_fat.dynlib | Bin .../test_lib_10_14.dynlib | Bin .../test_lib_10_14_386.dynlib | Bin .../test_lib_10_14_fat.dynlib | Bin .../test_lib_10_6.dynlib | Bin .../test_lib_10_6_386.dynlib | Bin .../test_lib_10_6_fat.dynlib | Bin .../test_lib_multiple_fat.dynlib | Bin 12 files changed, 63 insertions(+), 41 deletions(-) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib.c (100%) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib_10_10.dynlib (100%) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib_10_10_386.dynlib (100%) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib_10_10_fat.dynlib (100%) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib_10_14.dynlib (100%) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib_10_14_386.dynlib (100%) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib_10_14_fat.dynlib (100%) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib_10_6.dynlib (100%) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib_10_6_386.dynlib (100%) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib_10_6_fat.dynlib (100%) rename tests/testdata/{macos_minimal_system_version => macosx_minimal_system_version}/test_lib_multiple_fat.dynlib (100%) diff --git a/tests/test_macosx_libfile.py b/tests/test_macosx_libfile.py index cdba34ba..79b5549f 100644 --- a/tests/test_macosx_libfile.py +++ b/tests/test_macosx_libfile.py @@ -9,7 +9,7 @@ def test_read_from_dynlib(): dirname = os.path.dirname(__file__) dylib_dir = os.path.join(dirname, "testdata", - "macos_minimal_system_version") + "macosx_minimal_system_version") versions = [ ("test_lib_10_6_fat.dynlib", "10.6"), ("test_lib_10_10_fat.dynlib", "10.10"), @@ -40,50 +40,72 @@ def fun(*args, **kwargs): return fun -def test_get_platform_macos(monkeypatch, capsys): - dirname = os.path.dirname(__file__) - dylib_dir = os.path.join(dirname, "testdata", - "macos_minimal_system_version") - monkeypatch.setattr(distutils.util, "get_platform", return_factory("macosx-10.14-x86_64")) - assert get_platform(dylib_dir) == "macosx_10_14_x86_64" - monkeypatch.setattr(distutils.util, "get_platform", return_factory("macosx-10.9-x86_64")) - assert get_platform(dylib_dir) == "macosx_10_14_x86_64" - captured = capsys.readouterr() - assert "[WARNING] This wheel needs higher macosx version than" in captured.err +class TestGetPlatformMacosx: + def test_simple(self, monkeypatch): + dirname = os.path.dirname(__file__) + dylib_dir = os.path.join(dirname, "testdata", "macosx_minimal_system_version") + monkeypatch.setattr(distutils.util, "get_platform", return_factory("macosx-10.14-x86_64")) + assert get_platform(dylib_dir) == "macosx_10_14_x86_64" + + def test_version_bump(self, monkeypatch, capsys): + dirname = os.path.dirname(__file__) + dylib_dir = os.path.join(dirname, "testdata", "macosx_minimal_system_version") + monkeypatch.setattr(distutils.util, "get_platform", return_factory("macosx-10.9-x86_64")) + assert get_platform(dylib_dir) == "macosx_10_14_x86_64" + captured = capsys.readouterr() + assert "[WARNING] This wheel needs higher macosx version than" in captured.err - monkeypatch.setattr(os, "walk", return_factory( - [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_10_fat.dynlib"])] - )) - assert get_platform(dylib_dir) == "macosx_10_10_x86_64" - captured = capsys.readouterr() - assert "[WARNING] This wheel needs higher macosx version than" in captured.err - assert "test_lib_10_10_fat.dynlib" in captured.err + def test_information_about_problematic_files_python_version(self, monkeypatch, capsys): + dirname = os.path.dirname(__file__) + dylib_dir = os.path.join(dirname, "testdata", "macosx_minimal_system_version") + monkeypatch.setattr(distutils.util, "get_platform", return_factory("macosx-10.9-x86_64")) + monkeypatch.setattr(os, "walk", return_factory( + [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_10_fat.dynlib"])] + )) + assert get_platform(dylib_dir) == "macosx_10_10_x86_64" + captured = capsys.readouterr() + assert "[WARNING] This wheel needs higher macosx version than" in captured.err + assert "your python is compiled against." in captured.err + assert "test_lib_10_10_fat.dynlib" in captured.err - monkeypatch.setattr(os, "walk", return_factory( - [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])] - )) - assert get_platform(dylib_dir) == "macosx_10_9_x86_64" - monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "10.10") - assert get_platform(dylib_dir) == "macosx_10_10_x86_64" - captured = capsys.readouterr() - assert captured.err == "" + def test_information_about_problematic_files_env_variable(self, monkeypatch, capsys): + dirname = os.path.dirname(__file__) + dylib_dir = os.path.join(dirname, "testdata", "macosx_minimal_system_version") + monkeypatch.setattr(distutils.util, "get_platform", return_factory("macosx-10.9-x86_64")) + monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "10.8") + monkeypatch.setattr(os, "walk", return_factory( + [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_10_fat.dynlib"])] + )) + assert get_platform(dylib_dir) == "macosx_10_10_x86_64" + captured = capsys.readouterr() + assert "[WARNING] This wheel needs higher macosx version than" in captured.err + assert "is set in MACOSX_DEPLOYMENT_TARGET variable." in captured.err + assert "test_lib_10_10_fat.dynlib" in captured.err - monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "10.8") - monkeypatch.setattr(os, "walk", return_factory( - [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])] - )) - assert get_platform(dylib_dir) == "macosx_10_9_x86_64" - captured = capsys.readouterr() - print("aa", captured.err) - assert "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to lower value" in captured.err + def test_bump_platform_tag_by_env_variable(self, monkeypatch, capsys): + dirname = os.path.dirname(__file__) + dylib_dir = os.path.join(dirname, "testdata", "macosx_minimal_system_version") + monkeypatch.setattr(distutils.util, "get_platform", return_factory("macosx-10.9-x86_64")) + monkeypatch.setattr(os, "walk", return_factory( + [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])] + )) + assert get_platform(dylib_dir) == "macosx_10_9_x86_64" + monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "10.10") + assert get_platform(dylib_dir) == "macosx_10_10_x86_64" + captured = capsys.readouterr() + assert captured.err == "" - monkeypatch.setattr(os, "walk", return_factory( - [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_10_fat.dynlib"])] - )) - assert get_platform(dylib_dir) == "macosx_10_10_x86_64" - captured = capsys.readouterr() - print("aa", captured.err) - assert "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to lower value" in captured.err + def test_warning_on_to_low_env_variable(self, monkeypatch, capsys): + dirname = os.path.dirname(__file__) + dylib_dir = os.path.join(dirname, "testdata", "macosx_minimal_system_version") + monkeypatch.setattr(distutils.util, "get_platform", return_factory("macosx-10.9-x86_64")) + monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "10.8") + monkeypatch.setattr(os, "walk", return_factory( + [(dylib_dir, [], ["test_lib_10_6.dynlib", "test_lib_10_6_fat.dynlib"])] + )) + assert get_platform(dylib_dir) == "macosx_10_9_x86_64" + captured = capsys.readouterr() + assert "MACOSX_DEPLOYMENT_TARGET is set to lower value than your python" in captured.err def test_get_platform_linux(monkeypatch): diff --git a/tests/testdata/macos_minimal_system_version/test_lib.c b/tests/testdata/macosx_minimal_system_version/test_lib.c similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib.c rename to tests/testdata/macosx_minimal_system_version/test_lib.c diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_10.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_10.dynlib similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib_10_10.dynlib rename to tests/testdata/macosx_minimal_system_version/test_lib_10_10.dynlib diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_10_386.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_10_386.dynlib similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib_10_10_386.dynlib rename to tests/testdata/macosx_minimal_system_version/test_lib_10_10_386.dynlib diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_10_fat.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_10_fat.dynlib similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib_10_10_fat.dynlib rename to tests/testdata/macosx_minimal_system_version/test_lib_10_10_fat.dynlib diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_14.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_14.dynlib similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib_10_14.dynlib rename to tests/testdata/macosx_minimal_system_version/test_lib_10_14.dynlib diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_14_386.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_14_386.dynlib similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib_10_14_386.dynlib rename to tests/testdata/macosx_minimal_system_version/test_lib_10_14_386.dynlib diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_14_fat.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_14_fat.dynlib similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib_10_14_fat.dynlib rename to tests/testdata/macosx_minimal_system_version/test_lib_10_14_fat.dynlib diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_6.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_6.dynlib similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib_10_6.dynlib rename to tests/testdata/macosx_minimal_system_version/test_lib_10_6.dynlib diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_6_386.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_6_386.dynlib similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib_10_6_386.dynlib rename to tests/testdata/macosx_minimal_system_version/test_lib_10_6_386.dynlib diff --git a/tests/testdata/macos_minimal_system_version/test_lib_10_6_fat.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_6_fat.dynlib similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib_10_6_fat.dynlib rename to tests/testdata/macosx_minimal_system_version/test_lib_10_6_fat.dynlib diff --git a/tests/testdata/macos_minimal_system_version/test_lib_multiple_fat.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_multiple_fat.dynlib similarity index 100% rename from tests/testdata/macos_minimal_system_version/test_lib_multiple_fat.dynlib rename to tests/testdata/macosx_minimal_system_version/test_lib_multiple_fat.dynlib From b1dd2c461146342c60e11757d41eb73eb604b6ec Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 16 Oct 2019 10:19:45 +0200 Subject: [PATCH 9/9] extract macos platform tag calculation to separated function --- wheel/pep425tags.py | 123 +++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/wheel/pep425tags.py b/wheel/pep425tags.py index 4776fb89..6b1abd1a 100644 --- a/wheel/pep425tags.py +++ b/wheel/pep425tags.py @@ -109,69 +109,76 @@ def get_abi_tag(): return abi -def get_platform(archive_root): - """Return our platform name 'win32', 'linux_x86_64'""" - # XXX remove distutils dependency - result = distutils.util.get_platform() - if result.startswith("macosx") and archive_root is not None: - prefix, base_version, suffix = result.split('-') - base_version = tuple([int(x) for x in base_version.split(".")]) - if len(base_version) == 2: - base_version = base_version + (0,) +def calculate_macosx_platform_tag(archive_root, platform_tag): + """ + Calculate proper macosx platform tag basing on files which are included to wheel + """ + prefix, base_version, suffix = platform_tag.split('-') + base_version = tuple([int(x) for x in base_version.split(".")]) + if len(base_version) == 2: + base_version = base_version + (0,) + + assert len(base_version) == 3 + if "MACOSX_DEPLOYMENT_TARGET" in os.environ: + deploy_target = tuple([int(x) for x in os.environ[ + "MACOSX_DEPLOYMENT_TARGET"].split(".")]) + if len(deploy_target) == 2: + deploy_target = deploy_target + (0,) + if deploy_target < base_version: + sys.stderr.write( + "[WARNING] MACOSX_DEPLOYMENT_TARGET is set " + "to lower value than your python is compiled\n" + ) + else: + base_version = deploy_target + + assert len(base_version) == 3 + start_version = base_version + versions_dict = {} + for (dirpath, dirnames, filenames) in os.walk(archive_root): + for filename in filenames: + if filename.endswith('.dynlib') or filename.endswith('.so'): + lib_path = os.path.join(dirpath, filename) + versions_dict[lib_path] = extract_macosx_min_system_version(lib_path) + + if len(versions_dict) > 0: + base_version = max(base_version, max(versions_dict.values())) + + if base_version[-1] == 0: + fin_base_version = base_version[:-1] + else: + fin_base_version = base_version + + fin_base_version = "_".join([str(x) for x in fin_base_version]) + if start_version < base_version: + problematic_files = [k for k, v in versions_dict.items() if v > start_version] + problematic_files = "\n".join(problematic_files) + error_message = \ + "[WARNING] This wheel needs higher macosx version than {} " \ + "is set in MACOSX_DEPLOYMENT_TARGET variable. " \ + "To silence this warning set MACOSX_DEPLOYMENT_TARGET to " +\ + fin_base_version + " or recreate this files with lower " \ + "MACOSX_DEPLOYMENT_TARGET \n" + problematic_files - assert len(base_version) == 3 if "MACOSX_DEPLOYMENT_TARGET" in os.environ: - deploy_target = tuple([int(x) for x in os.environ[ - "MACOSX_DEPLOYMENT_TARGET"].split(".")]) - if len(deploy_target) == 2: - deploy_target = deploy_target + (0,) - if deploy_target < base_version: - sys.stderr.write( - "[WARNING] MACOSX_DEPLOYMENT_TARGET is set " - "to lower value than your python is compiled\n" - ) - else: - base_version = deploy_target - - assert len(base_version) == 3 - start_version = base_version - versions_dict = {} - for (dirpath, dirnames, filenames) in os.walk(archive_root): - for filename in filenames: - if filename.endswith('.dynlib') or filename.endswith('.so'): - lib_path = os.path.join(dirpath, filename) - versions_dict[lib_path] = extract_macosx_min_system_version(lib_path) - - if len(versions_dict) > 0: - base_version = max(base_version, max(versions_dict.values())) - - if base_version[-1] == 0: - fin_base_version = base_version[:-1] + sys.stderr.write( + error_message.format("is set in MACOSX_DEPLOYMENT_TARGET variable.") + ) else: - fin_base_version = base_version - - fin_base_version = "_".join([str(x) for x in fin_base_version]) - if start_version < base_version: - problematic_files = [k for k, v in versions_dict.items() if v > start_version] - problematic_files = "\n".join(problematic_files) - error_message = \ - "[WARNING] This wheel needs higher macosx version than {} " \ - "is set in MACOSX_DEPLOYMENT_TARGET variable. " \ - "To silence this warning set MACOSX_DEPLOYMENT_TARGET to " +\ - fin_base_version + " or recreate this files with lower " \ - "MACOSX_DEPLOYMENT_TARGET \n" + problematic_files - - if "MACOSX_DEPLOYMENT_TARGET" in os.environ: - sys.stderr.write( - error_message.format("is set in MACOSX_DEPLOYMENT_TARGET variable.") - ) - else: - sys.stderr.write( - error_message.format("your python is compiled against.") - ) + sys.stderr.write( + error_message.format("your python is compiled against.") + ) - result = prefix + "_" + fin_base_version + "_" + suffix + platform_tag = prefix + "_" + fin_base_version + "_" + suffix + return platform_tag + +def get_platform(archive_root): + """Return our platform name 'win32', 'linux_x86_64'""" + # XXX remove distutils dependency + result = distutils.util.get_platform() + if result.startswith("macosx") and archive_root is not None: + result = calculate_macosx_platform_tag(archive_root, result) result = result.replace('.', '_').replace('-', '_') if result == "linux_x86_64" and sys.maxsize == 2147483647: # pip pull request #3497