diff --git a/tests/test_macosx_libfile.py b/tests/test_macosx_libfile.py new file mode 100644 index 00000000..79b5549f --- /dev/null +++ b/tests/test_macosx_libfile.py @@ -0,0 +1,114 @@ +import os +import sys +import distutils.util + +from wheel.macosx_libfile import extract_macosx_min_system_version +from wheel.pep425tags import get_platform + + +def test_read_from_dynlib(): + dirname = os.path.dirname(__file__) + dylib_dir = os.path.join(dirname, "testdata", + "macosx_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 + + +def return_factory(return_val): + def fun(*args, **kwargs): + return return_val + + return fun + + +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 + + 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 + + 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 + + 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 == "" + + 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): + monkeypatch.setattr(distutils.util, "get_platform", return_factory("linux_x86_64")) + monkeypatch.setattr(sys, "maxsize", 2147483647) + assert get_platform(None) == "linux_i686" diff --git a/tests/testdata/macosx_minimal_system_version/test_lib.c b/tests/testdata/macosx_minimal_system_version/test_lib.c new file mode 100644 index 00000000..ca24f6c0 --- /dev/null +++ b/tests/testdata/macosx_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/macosx_minimal_system_version/test_lib_10_10.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_10.dynlib new file mode 100644 index 00000000..eaf1a94e Binary files /dev/null and b/tests/testdata/macosx_minimal_system_version/test_lib_10_10.dynlib differ diff --git a/tests/testdata/macosx_minimal_system_version/test_lib_10_10_386.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_10_386.dynlib new file mode 100644 index 00000000..8f543875 Binary files /dev/null and b/tests/testdata/macosx_minimal_system_version/test_lib_10_10_386.dynlib differ diff --git a/tests/testdata/macosx_minimal_system_version/test_lib_10_10_fat.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_10_fat.dynlib new file mode 100644 index 00000000..6c095127 Binary files /dev/null and b/tests/testdata/macosx_minimal_system_version/test_lib_10_10_fat.dynlib differ diff --git a/tests/testdata/macosx_minimal_system_version/test_lib_10_14.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_14.dynlib new file mode 100644 index 00000000..c9024ccc Binary files /dev/null and b/tests/testdata/macosx_minimal_system_version/test_lib_10_14.dynlib differ diff --git a/tests/testdata/macosx_minimal_system_version/test_lib_10_14_386.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_14_386.dynlib new file mode 100644 index 00000000..c85b7169 Binary files /dev/null and b/tests/testdata/macosx_minimal_system_version/test_lib_10_14_386.dynlib differ diff --git a/tests/testdata/macosx_minimal_system_version/test_lib_10_14_fat.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_14_fat.dynlib new file mode 100644 index 00000000..4bb09407 Binary files /dev/null and b/tests/testdata/macosx_minimal_system_version/test_lib_10_14_fat.dynlib differ diff --git a/tests/testdata/macosx_minimal_system_version/test_lib_10_6.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_6.dynlib new file mode 100644 index 00000000..80401eed Binary files /dev/null and b/tests/testdata/macosx_minimal_system_version/test_lib_10_6.dynlib differ diff --git a/tests/testdata/macosx_minimal_system_version/test_lib_10_6_386.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_6_386.dynlib new file mode 100644 index 00000000..1e48cd85 Binary files /dev/null and b/tests/testdata/macosx_minimal_system_version/test_lib_10_6_386.dynlib differ diff --git a/tests/testdata/macosx_minimal_system_version/test_lib_10_6_fat.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_10_6_fat.dynlib new file mode 100644 index 00000000..f4ffaeec Binary files /dev/null and b/tests/testdata/macosx_minimal_system_version/test_lib_10_6_fat.dynlib differ diff --git a/tests/testdata/macosx_minimal_system_version/test_lib_multiple_fat.dynlib b/tests/testdata/macosx_minimal_system_version/test_lib_multiple_fat.dynlib new file mode 100644 index 00000000..5f7fd509 Binary files /dev/null and b/tests/testdata/macosx_minimal_system_version/test_lib_multiple_fat.dynlib differ diff --git a/wheel/bdist_wheel.py b/wheel/bdist_wheel.py index c79307b5..014983c1 100644 --- a/wheel/bdist_wheel.py +++ b/wheel/bdist_wheel.py @@ -48,7 +48,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,9 +150,15 @@ 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('.', '_') if self.root_is_pure: @@ -173,6 +179,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/macosx_libfile.py b/wheel/macosx_libfile.py new file mode 100644 index 00000000..5056f357 --- /dev/null +++ b/wheel/macosx_libfile.py @@ -0,0 +1,350 @@ +""" +This module contains function to analyse dynamic library +headers to extract system information + +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 +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 + + if len(versions_list) > 0: + return max(versions_list) + else: + return None + + 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 diff --git a/wheel/pep425tags.py b/wheel/pep425tags.py index b9242efa..6b1abd1a 100644 --- a/wheel/pep425tags.py +++ b/wheel/pep425tags.py @@ -3,9 +3,13 @@ import distutils.util import platform import sys +import os import sysconfig import warnings +from .macosx_libfile import extract_macosx_min_system_version + + try: from importlib.machinery import all_suffixes as get_all_suffixes except ImportError: @@ -105,17 +109,85 @@ def get_abi_tag(): return abi -def get_platform(): +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 + + 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.") + ) + + 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().replace('.', '_').replace('-', '_') + 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 result = "linux_i686" + 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`. @@ -153,7 +225,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: