From fb056581cf0aed1c35f694a56f8925f0a8d54c1d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 7 Sep 2023 14:37:36 +0800 Subject: [PATCH 01/16] Add mechanism to extract iOS version. --- crossenv/__init__.py | 17 ++++++++++++++++- crossenv/scripts/os-patch.py.tmpl | 2 +- crossenv/scripts/platform-patch.py.tmpl | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 76dcd5a..98628d1 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -533,6 +533,14 @@ def get_uname_info(self): else: raise ValueError("Unexpected major version %s for MACOSX_DEPLOYMENT_TARGET" % major) + elif self.host_sysname in {"ios", "tvos", "watchos"}: + # CFLAGS will include a `-mios-version-min=X.Y` identifier (or the + # equivalent for tvOS and watchOS). Use this as the version number. + self.host_release = [ + flag.split("=")[1] for flag in + self.host_sysconfigdata.build_time_vars["CFLAGS"].split(" ") + if flag.startswith(f"-m{self.host_sysname}-version-min=") + ][0] if self.host_sysname == "darwin": self.sysconfig_platform = "macosx-%s-%s" % (self.macosx_deployment_target, @@ -544,6 +552,13 @@ def get_uname_info(self): else: self.sysconfig_platform = self.host_platform + # Normalize case of the host sysname. + self.host_sysname = { + "ios": "iOS", + "tvos": "tvOS", + "watchos": "watchOS", + }.get(self.host_sysname, self.host_sysname.title()) + def expand_platform_tags(self): """ Convert legacy manylinux tags to PEP600, because pip only looks for one @@ -769,7 +784,7 @@ def make_cross_python(self, context): host_build_time_vars = self.host_sysconfigdata.build_time_vars sysconfig_name = self.host_sysconfigdata_name - + # Install patches to environment self.copy_and_patch_sysconfigdata(context) diff --git a/crossenv/scripts/os-patch.py.tmpl b/crossenv/scripts/os-patch.py.tmpl index d2e0bae..6503ca7 100644 --- a/crossenv/scripts/os-patch.py.tmpl +++ b/crossenv/scripts/os-patch.py.tmpl @@ -4,7 +4,7 @@ from collections import namedtuple uname_result_type = namedtuple('uname_result', 'sysname nodename release version machine') _uname_result = uname_result_type( - {{repr(self.host_sysname.title())}}, + {{repr(self.host_sysname)}}, 'build', {{repr(self.host_release)}}, '', diff --git a/crossenv/scripts/platform-patch.py.tmpl b/crossenv/scripts/platform-patch.py.tmpl index 8cbe8da..0c026d9 100644 --- a/crossenv/scripts/platform-patch.py.tmpl +++ b/crossenv/scripts/platform-patch.py.tmpl @@ -3,7 +3,7 @@ from collections import namedtuple platform_uname_result_type = namedtuple('uname_result', 'system node release version machine processor') _uname_result = platform_uname_result_type( - {{repr(self.host_sysname.title())}}, + {{repr(self.host_sysname)}}, 'build', {{repr(self.host_release)}}, '', From 7d4ef92877d1e82bac2cb8f14b01bd19dcba9c42 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 7 Sep 2023 14:42:17 +0800 Subject: [PATCH 02/16] Accomodate iOS compilers returning arm64 instead of aarch64 --- crossenv/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 98628d1..90864a3 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -416,7 +416,8 @@ def run_compiler(arg): # Sanity check that this is the right compiler. (See #24, #27.) res = run_compiler('-dumpmachine') - found_triple = res.stdout.strip() + # Apple's clang reports "aarch64" as "arm64" + found_triple = res.stdout.strip().replace("arm64-", "aarch64-") if res.returncode == 0 and found_triple: expected = self.host_sysconfigdata.build_time_vars['HOST_GNU_TYPE'] if not self._compare_triples(found_triple, expected): From d920ab3b7ed51396f07ab020c00b5d853c215108 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Sep 2023 10:58:22 +0800 Subject: [PATCH 03/16] Make sure sys.platform is overridden, and that setting doesn't disable fork/spawn. --- crossenv/__init__.py | 17 ++++++++++------- crossenv/scripts/subprocess-patch.py.tmpl | 2 ++ crossenv/scripts/sys-patch.py.tmpl | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 crossenv/scripts/subprocess-patch.py.tmpl diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 90864a3..a0b8c9f 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -502,9 +502,9 @@ def get_uname_info(self): # vary. host_info = self.host_platform.split('-') if not host_info: - self.host_sysname = sys.platform + self.host_sys_platform = sys.platform elif len(host_info) >= 1: - self.host_sysname = host_info[0] + self.host_sys_platform = host_info[0] if self.host_machine is None: platform2uname = { @@ -534,22 +534,24 @@ def get_uname_info(self): else: raise ValueError("Unexpected major version %s for MACOSX_DEPLOYMENT_TARGET" % major) - elif self.host_sysname in {"ios", "tvos", "watchos"}: + elif self.host_sys_platform in {"ios", "tvos", "watchos"}: # CFLAGS will include a `-mios-version-min=X.Y` identifier (or the # equivalent for tvOS and watchOS). Use this as the version number. self.host_release = [ flag.split("=")[1] for flag in self.host_sysconfigdata.build_time_vars["CFLAGS"].split(" ") - if flag.startswith(f"-m{self.host_sysname}-version-min=") + if flag.startswith(f"-m{self.host_sys_platform}-version-min=") ][0] - if self.host_sysname == "darwin": + if self.host_sys_platform == "darwin": self.sysconfig_platform = "macosx-%s-%s" % (self.macosx_deployment_target, self.host_machine) - elif self.host_sysname == "linux": + elif self.host_sys_platform == "linux": # Use self.host_machine here as powerpc64le gets converted # to ppc64le in self.host_machine self.sysconfig_platform = "linux-%s" % (self.host_machine) + elif self.host_sys_platform in {"ios", "tvos", "watchos"}: + self.sysconfig_platform = f'{self.host_sys_platform}-{self.host_release}-{self.host_machine}' else: self.sysconfig_platform = self.host_platform @@ -558,7 +560,7 @@ def get_uname_info(self): "ios": "iOS", "tvos": "tvOS", "watchos": "watchOS", - }.get(self.host_sysname, self.host_sysname.title()) + }.get(self.host_sys_platform, self.host_sys_platform.title()) def expand_platform_tags(self): """ @@ -803,6 +805,7 @@ def make_cross_python(self, context): 'importlib-metadata-patch.py', 'platform-patch.py', 'sysconfig-patch.py', + 'subprocess-patch.py', 'distutils-sysconfig-patch.py', 'pkg_resources-patch.py', 'packaging-tags-patch.py', diff --git a/crossenv/scripts/subprocess-patch.py.tmpl b/crossenv/scripts/subprocess-patch.py.tmpl new file mode 100644 index 0000000..942747f --- /dev/null +++ b/crossenv/scripts/subprocess-patch.py.tmpl @@ -0,0 +1,2 @@ +# We must be on a platform that supports fork and exec +_can_fork_exec = True diff --git a/crossenv/scripts/sys-patch.py.tmpl b/crossenv/scripts/sys-patch.py.tmpl index bfa6cd9..dbaab17 100644 --- a/crossenv/scripts/sys-patch.py.tmpl +++ b/crossenv/scripts/sys-patch.py.tmpl @@ -1,6 +1,6 @@ cross_compiling = True build_path = {{repr(context.build_sys_path)}} - +platform = {{repr(self.host_sys_platform)}} abiflags = {{repr(host_build_time_vars.get('ABIFLAGS'))}} if abiflags is None: del abiflags From 711419c0d35ea55bd434e3cd5e8f892570a2ac0a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Sep 2023 12:40:47 +0800 Subject: [PATCH 04/16] Correct the architecture and platform name in sysconfig_platform. --- crossenv/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index a0b8c9f..b9b18b6 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -457,7 +457,6 @@ def create(self, env_dir): :param env_dir: The target directory to create an environment in. """ - env_dir = os.path.abspath(env_dir) context = self.ensure_directories(env_dir) self.make_build_python(context) @@ -519,6 +518,13 @@ def get_uname_info(self): else: self.host_machine = self.host_gnu_type.split('-')[0] + # Apple uses arm64, not aarch64 + if ( + self.host_machine == "aarch64" + and self.host_sys_platform in {"ios", "tvos", "watchos"} + ): + self.host_machine = "arm64" + self.host_release = '' if self.macosx_deployment_target: try: @@ -551,7 +557,8 @@ def get_uname_info(self): # to ppc64le in self.host_machine self.sysconfig_platform = "linux-%s" % (self.host_machine) elif self.host_sys_platform in {"ios", "tvos", "watchos"}: - self.sysconfig_platform = f'{self.host_sys_platform}-{self.host_release}-{self.host_machine}' + multiarch = self.host_sysconfigdata.build_time_vars['MULTIARCH'] + self.sysconfig_platform = f"{multiarch}-{self.host_release}-{self.host_machine}" else: self.sysconfig_platform = self.host_platform From 3d7da3c1bf81a24e1673bbe29228c6873eba4504 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Sep 2023 12:43:08 +0800 Subject: [PATCH 05/16] Use the full environment name, not just 'cross' --- crossenv/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index b9b18b6..3896c97 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -701,8 +701,9 @@ def make_cross_python(self, context): if self.cross_prefix: context.cross_env_dir = self.cross_prefix else: - context.cross_env_dir = os.path.join(context.env_dir, 'cross') - clear_cross = self.clear in ('default', 'cross-only', 'both') + cross_env_name = os.path.split(context.env_dir)[-1] + context.cross_env_dir = os.path.join(context.env_dir, cross_env_name) + env = venv.EnvBuilder( system_site_packages=False, clear=self.clear_cross, From 9985b719800a8177ed2fefb232203b90d6722d68 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 21 Sep 2023 11:01:42 +0800 Subject: [PATCH 06/16] Add full handling for device simulators, plus the different results of os.uname/platform.uname --- crossenv/__init__.py | 129 +++++++++++------- .../scripts/importlib-machinery-patch.py.tmpl | 2 + crossenv/scripts/os-patch.py.tmpl | 2 +- crossenv/scripts/packaging-tags-patch.py.tmpl | 2 +- crossenv/scripts/platform-patch.py.tmpl | 4 +- crossenv/scripts/site.py.tmpl | 3 +- crossenv/scripts/subprocess-patch.py.tmpl | 10 ++ crossenv/scripts/sys-patch.py.tmpl | 4 + 8 files changed, 102 insertions(+), 54 deletions(-) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 3896c97..96ecea1 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -416,11 +416,16 @@ def run_compiler(arg): # Sanity check that this is the right compiler. (See #24, #27.) res = run_compiler('-dumpmachine') - # Apple's clang reports "aarch64" as "arm64" - found_triple = res.stdout.strip().replace("arm64-", "aarch64-") + found_triple = res.stdout.strip() if res.returncode == 0 and found_triple: expected = self.host_sysconfigdata.build_time_vars['HOST_GNU_TYPE'] - if not self._compare_triples(found_triple, expected): + clean_found = self._clean_triple(found_triple) + clean_expected = self._clean_triple(expected) + if ( + clean_found is not None + and clean_expected is not None + and clean_found != clean_expected + ): logger.warning("The cross-compiler (%r) does not appear to be " "for the correct architecture (got %s, expected " "%s). Use --cc to correct, if necessary.", @@ -428,28 +433,35 @@ def run_compiler(arg): found_triple, expected) - def _compare_triples(self, x, y): - # They are in the form cpu-vendor-kernel-system or cpu-kernel-system. - # So we'll get something like: x86_64-linux-gnu or x86_64-pc-linux-gnu. - # We won't overcomplicate this, since it's just to generate a warning. + def _clean_triple(self, triple): + # They are in the form cpu-vendor-kernel-system or cpu-kernel-system. So we'll + # get something like: x86_64-linux-gnu or x86_64-pc-linux-gnu. We won't + # overcomplicate this, since it's just to generate a warning. # - # We return True if we can't make sense of anything and wish to skip - # the warning. - - parts_x = x.split('-') - if len(parts_x) == 4: - del parts_x[1] - elif len(parts_x) != 3: - return True # Some other form? Bail out. - - parts_y = y.split('-') - if len(parts_y) == 4: - del parts_y[1] - elif len(parts_y) != 3: - return True # Some other form? Bail out. - - return parts_x == parts_y - + # Apple builds accept use "arm64-apple-ios12.0-simulator" for an iOS simulator. + # The host GNU type returned by clang won't include the version number; we also + # need to map "arm64" to "aarch64", and allow for the "-simulator" suffix. + + parts = triple.split('-') + if parts[1] == "apple": + # Normalize Apple's CPU architecture + if parts[0] == "arm64": + parts[0] = "aarch64" + + # Remove the iOS/tvOS/watchOS version number + if parts[2].startswith("ios"): + parts[2] = "ios" + elif parts[2].startswith("tvos"): + parts[2] = "tvos" + elif parts[2].startswith("watchos"): + parts[2] = "watchos" + + if len(parts) == 4 and parts[1] != "apple": + del parts[1] + elif len(parts) != 3: + return None # Some other form? Bail out. + + return parts def create(self, env_dir): """ @@ -496,9 +508,10 @@ def get_uname_info(self): """ What should uname() return? """ - - # host_platform is _probably_ something like linux-x86_64, but it can - # vary. + # host_platform is _probably_ something like linux-x86_64, but it can vary. + # On iOS/tvOS/watchOS, it will be of the form ios-12.0-iphonesimulator-arm64, + # telling you sys.platform, the minimum supported OS version, the device ABI, + # and the architecture. host_info = self.host_platform.split('-') if not host_info: self.host_sys_platform = sys.platform @@ -514,18 +527,33 @@ def get_uname_info(self): } if len(host_info) > 1 and host_info[-1] in platform2uname: # Test that this is still a special case when we can. - self.host_machine = platform2uname[host_info[-1]] + self.host_arch = platform2uname[host_info[-1]] else: - self.host_machine = self.host_gnu_type.split('-')[0] - - # Apple uses arm64, not aarch64 - if ( - self.host_machine == "aarch64" - and self.host_sys_platform in {"ios", "tvos", "watchos"} - ): - self.host_machine = "arm64" + self.host_arch = self.host_gnu_type.split('-')[0] + + # iOS/tvOS/watchOS return the device type as the machine, have a separate + # concept of being a simulator, and use arm64 rather than aarch64 as an + # architecture descriptor. + if self.host_sys_platform in {"ios", "tvos", "watchos"}: + if self.host_arch == "aarch64": + self.host_arch = "arm64" + + self.host_machine, self.host_is_simulator = { + "iphoneos": ("iPhone", False), + "iphonesimulator": ("iPhoneSimulator", True), + "appletvos": ("AppleTV", False), + "appletvsimulator": ("AppleTVSimulator", True), + "watchos": ("AppleWatch", False), + "watchsimulator": ("AppleWatchSimulator", True), + }[host_info[2]] + else: + # On all other platforms, machine == arch + self.host_machine = self.host_arch + self.host_is_simulator = None + else: + self.host_arch = self.host_machine + self.host_is_simulator = None - self.host_release = '' if self.macosx_deployment_target: try: major, minor = self.macosx_deployment_target.split(".") @@ -541,13 +569,9 @@ def get_uname_info(self): raise ValueError("Unexpected major version %s for MACOSX_DEPLOYMENT_TARGET" % major) elif self.host_sys_platform in {"ios", "tvos", "watchos"}: - # CFLAGS will include a `-mios-version-min=X.Y` identifier (or the - # equivalent for tvOS and watchOS). Use this as the version number. - self.host_release = [ - flag.split("=")[1] for flag in - self.host_sysconfigdata.build_time_vars["CFLAGS"].split(" ") - if flag.startswith(f"-m{self.host_sys_platform}-version-min=") - ][0] + self.host_release = host_info[1] + else: + self.host_release = '' if self.host_sys_platform == "darwin": self.sysconfig_platform = "macosx-%s-%s" % (self.macosx_deployment_target, @@ -556,19 +580,26 @@ def get_uname_info(self): # Use self.host_machine here as powerpc64le gets converted # to ppc64le in self.host_machine self.sysconfig_platform = "linux-%s" % (self.host_machine) - elif self.host_sys_platform in {"ios", "tvos", "watchos"}: - multiarch = self.host_sysconfigdata.build_time_vars['MULTIARCH'] - self.sysconfig_platform = f"{multiarch}-{self.host_release}-{self.host_machine}" else: self.sysconfig_platform = self.host_platform - # Normalize case of the host sysname. - self.host_sysname = { + self.sysconfig_ext_suffix = self.host_sysconfigdata.build_time_vars['EXT_SUFFIX'] + + # Normalize case of the host system/sysname. + self.host_system = { "ios": "iOS", "tvos": "tvOS", "watchos": "watchOS", }.get(self.host_sys_platform, self.host_sys_platform.title()) + # on iOS/tvOS/watchOS, platform.uname() reports the actual OS name; + # os.uname() reports the *kernel* name. + self.host_sysname = { + "ios": "Darwin", + "tvos": "Darwin", + "watchos": "Darwin", + }.get(self.host_sys_platform, self.host_sys_platform.title()) + def expand_platform_tags(self): """ Convert legacy manylinux tags to PEP600, because pip only looks for one diff --git a/crossenv/scripts/importlib-machinery-patch.py.tmpl b/crossenv/scripts/importlib-machinery-patch.py.tmpl index b375b3e..012199a 100644 --- a/crossenv/scripts/importlib-machinery-patch.py.tmpl +++ b/crossenv/scripts/importlib-machinery-patch.py.tmpl @@ -25,3 +25,5 @@ def _PathFinder_find_spec(cls, fullname, path=None, target=None): return _original_find_spec(fullname, path, target) PathFinder.find_spec = _PathFinder_find_spec + +EXTENSION_SUFFIXES = [{{repr(self.sysconfig_ext_suffix)}}, ".abi3.dylib", ".dylib"] diff --git a/crossenv/scripts/os-patch.py.tmpl b/crossenv/scripts/os-patch.py.tmpl index 6503ca7..d3bfb53 100644 --- a/crossenv/scripts/os-patch.py.tmpl +++ b/crossenv/scripts/os-patch.py.tmpl @@ -8,7 +8,7 @@ _uname_result = uname_result_type( 'build', {{repr(self.host_release)}}, '', - {{repr(self.host_machine)}}) + {{repr(self.host_arch)}}) def uname(): return _uname_result diff --git a/crossenv/scripts/packaging-tags-patch.py.tmpl b/crossenv/scripts/packaging-tags-patch.py.tmpl index 8a737a4..3ab1c0b 100644 --- a/crossenv/scripts/packaging-tags-patch.py.tmpl +++ b/crossenv/scripts/packaging-tags-patch.py.tmpl @@ -3,6 +3,6 @@ def _linux_platforms(): yield from {{repr(self.platform_tags)}} - arch = _normalize_string({{repr(self.host_machine)}}) + arch = _normalize_string({{repr(self.host_arch)}}) archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch]) yield from archs diff --git a/crossenv/scripts/platform-patch.py.tmpl b/crossenv/scripts/platform-patch.py.tmpl index 0c026d9..d85b90e 100644 --- a/crossenv/scripts/platform-patch.py.tmpl +++ b/crossenv/scripts/platform-patch.py.tmpl @@ -3,12 +3,12 @@ from collections import namedtuple platform_uname_result_type = namedtuple('uname_result', 'system node release version machine processor') _uname_result = platform_uname_result_type( - {{repr(self.host_sysname)}}, + {{repr(self.host_system)}}, 'build', {{repr(self.host_release)}}, '', {{repr(self.host_machine)}}, - {{repr(self.host_machine)}}) + {{repr(self.host_arch)}}) def uname(): return _uname_result diff --git a/crossenv/scripts/site.py.tmpl b/crossenv/scripts/site.py.tmpl index f97a30a..c619ad4 100644 --- a/crossenv/scripts/site.py.tmpl +++ b/crossenv/scripts/site.py.tmpl @@ -95,7 +95,7 @@ class CrossenvPatchLegacyLoader(importlib.abc.Loader): class CrossenvFinder(importlib.abc.MetaPathFinder): """Mucks with import machinery in two ways: - + 1) loads sysconfigdata from our hard-coded path, regardless of sys.path 2) intercepts and patches modules as they are loaded """ @@ -105,6 +105,7 @@ class CrossenvFinder(importlib.abc.MetaPathFinder): 'importlib.metadata': '{{context.lib_path}}/importlib-metadata-patch.py', 'sys': '{{context.lib_path}}/sys-patch.py', 'os': '{{context.lib_path}}/os-patch.py', + 'subprocess': '{{context.lib_path}}/subprocess-patch.py', 'sysconfig': '{{context.lib_path}}/sysconfig-patch.py', 'distutils.sysconfig': '{{context.lib_path}}/distutils-sysconfig-patch.py', 'distutils.sysconfig_pypy': '{{context.lib_path}}/distutils-sysconfig-patch.py', diff --git a/crossenv/scripts/subprocess-patch.py.tmpl b/crossenv/scripts/subprocess-patch.py.tmpl index 942747f..5600088 100644 --- a/crossenv/scripts/subprocess-patch.py.tmpl +++ b/crossenv/scripts/subprocess-patch.py.tmpl @@ -1,2 +1,12 @@ # We must be on a platform that supports fork and exec _can_fork_exec = True + +# Set the module-level properties that are dependent on _can_fork_exec +from _posixsubprocess import fork_exec as _fork_exec + +import os +_del_safe.waitpid = os.waitpid +_del_safe.waitstatus_to_exitcode = os.waitstatus_to_exitcode +_del_safe.WIFSTOPPED = os.WIFSTOPPED +_del_safe.WSTOPSIG = os.WSTOPSIG +_del_safe.WNOHANG = os.WNOHANG diff --git a/crossenv/scripts/sys-patch.py.tmpl b/crossenv/scripts/sys-patch.py.tmpl index dbaab17..e9044f4 100644 --- a/crossenv/scripts/sys-patch.py.tmpl +++ b/crossenv/scripts/sys-patch.py.tmpl @@ -9,6 +9,10 @@ implementation._multiarch = {{repr(host_build_time_vars.get('MULTIARCH'))}} if implementation._multiarch is None: del implementation._multiarch +implementation._simulator = {{repr(self.host_is_simulator)}} +if implementation._simulator is None: + del implementation._simulator + # Remove cross-python from sys.path. It's not needed after startup. path.remove({{repr(context.lib_path)}}) path.remove({{repr(stdlib)}}) From fb309f4904d2e8a3ca30c7ada4ac312f473e8c73 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 27 Sep 2023 12:28:17 +0800 Subject: [PATCH 07/16] Modify subprocess patch to allow for pre-Python 3.10 versions. --- crossenv/scripts/subprocess-patch.py.tmpl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crossenv/scripts/subprocess-patch.py.tmpl b/crossenv/scripts/subprocess-patch.py.tmpl index 5600088..bc373c8 100644 --- a/crossenv/scripts/subprocess-patch.py.tmpl +++ b/crossenv/scripts/subprocess-patch.py.tmpl @@ -5,8 +5,12 @@ _can_fork_exec = True from _posixsubprocess import fork_exec as _fork_exec import os -_del_safe.waitpid = os.waitpid -_del_safe.waitstatus_to_exitcode = os.waitstatus_to_exitcode -_del_safe.WIFSTOPPED = os.WIFSTOPPED -_del_safe.WSTOPSIG = os.WSTOPSIG -_del_safe.WNOHANG = os.WNOHANG +try: + _del_safe.waitpid = os.waitpid + _del_safe.waitstatus_to_exitcode = os.waitstatus_to_exitcode + _del_safe.WIFSTOPPED = os.WIFSTOPPED + _del_safe.WSTOPSIG = os.WSTOPSIG + _del_safe.WNOHANG = os.WNOHANG +except NameError: + # Pre Python 3.11 doesn't have the _del_safe helper. + pass From b8a3a72d59b2cf484cc6b31bcd122d7a975ef314 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 29 Sep 2023 08:34:17 +0800 Subject: [PATCH 08/16] Add backwards compatibility for Python 3.10 install schemes. --- crossenv/scripts/sysconfig-patch.py.tmpl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crossenv/scripts/sysconfig-patch.py.tmpl b/crossenv/scripts/sysconfig-patch.py.tmpl index b8d8a3f..189b917 100644 --- a/crossenv/scripts/sysconfig-patch.py.tmpl +++ b/crossenv/scripts/sysconfig-patch.py.tmpl @@ -1,3 +1,4 @@ +# Pre 3.10 configuration of prefixes # Patch the things that depend on os.environ _PROJECT_BASE = {{repr(self.host_project_base)}} @@ -8,6 +9,15 @@ _BASE_PREFIX = os.path.normpath(sys.base_prefix) _EXEC_PREFIX = os.path.normpath(sys.exec_prefix) _BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix) +# Python 3.10 introduced get_preferred_scheme; 3.11 made that method able to +# identify virtual environments. Hard-code the 3.11 behavior, since we will +# always be in a venv. +if "venv" not in _INSTALL_SCHEMES: + def get_preferred_scheme(key): + return "venv" + + _INSTALL_SCHEMES["venv"] = _INSTALL_SCHEMES["posix_prefix"] + def get_makefile_filename(): return {{repr(self.host_makefile)}} From 6d88cb8034d4aafb1ff0ce4f63f40f715cd83beb Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 27 Aug 2024 16:48:31 +0800 Subject: [PATCH 09/16] Modifications stemming from official 3.13 support. --- crossenv/__init__.py | 52 ++++++++++++------- .../scripts/importlib-machinery-patch.py.tmpl | 2 +- crossenv/scripts/platform-patch.py.tmpl | 16 ++++++ crossenv/scripts/sys-patch.py.tmpl | 8 +-- 4 files changed, 55 insertions(+), 23 deletions(-) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 96ecea1..7a23404 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -358,10 +358,20 @@ def find_host_python(self, host): # It was probably natively compiled, but not necessarily for this # architecture. Guess from HOST_GNU_TYPE. host = self.host_gnu_type.split('-') - if len(host) == 4: # i.e., aarch64-unknown-linux-gnu - self.host_platform = '-'.join([host[2], host[0]]) - elif len(host) == 3: # i.e., aarch64-linux-gnu, unlikely. - self.host_platform = '-'.join([host[1], host[0]]) + if len(host) == 4: + if host[1] == "apple": + machine = self.host_sysconfigdata.build_time_vars['MULTIARCH'] + os_name, release = self._split_apple_os_version(host[2]) + self.host_platform = '-'.join([os_name, release, machine]) + else: # i.e., aarch64-unknown-linux-gnu + self.host_platform = '-'.join([host[2], host[0]]) + elif len(host) == 3: + if host[1] == "apple": + machine = self.host_sysconfigdata.build_time_vars['MULTIARCH'] + os_name, release = self._split_apple_os_version(host[2]) + self.host_platform = '-'.join([os_name, release, machine]) + else: # i.e., aarch64-linux-gnu, unlikely. + self.host_platform = '-'.join([host[1], host[0]]) else: logger.warning("Cannot determine platform. Using build.") self.host_platform = sysconfig.get_platform() @@ -433,6 +443,18 @@ def run_compiler(arg): found_triple, expected) + def _split_apple_os_version(self, value): + # Split the Apple OS version from the OS name prefix. + if value.startswith("ios"): + offset = 3 + elif value.startswith("tvos"): + offset = 4 + elif value.startswith("watchos"): + offset = 7 + else: + raise Va + return value[:offset], value[offset:] + def _clean_triple(self, triple): # They are in the form cpu-vendor-kernel-system or cpu-kernel-system. So we'll # get something like: x86_64-linux-gnu or x86_64-pc-linux-gnu. We won't @@ -448,13 +470,7 @@ def _clean_triple(self, triple): if parts[0] == "arm64": parts[0] = "aarch64" - # Remove the iOS/tvOS/watchOS version number - if parts[2].startswith("ios"): - parts[2] = "ios" - elif parts[2].startswith("tvos"): - parts[2] = "tvos" - elif parts[2].startswith("watchos"): - parts[2] = "watchos" + parts[2], _ = self._split_apple_os_version(parts[2]) if len(parts) == 4 and parts[1] != "apple": del parts[1] @@ -539,13 +555,13 @@ def get_uname_info(self): self.host_arch = "arm64" self.host_machine, self.host_is_simulator = { - "iphoneos": ("iPhone", False), - "iphonesimulator": ("iPhoneSimulator", True), - "appletvos": ("AppleTV", False), - "appletvsimulator": ("AppleTVSimulator", True), - "watchos": ("AppleWatch", False), - "watchsimulator": ("AppleWatchSimulator", True), - }[host_info[2]] + "iphoneos": ("arm64", False), + "iphonesimulator": (self.host_arch, True), + "appletvos": ("arm64", False), + "appletvsimulator": (self.host_arch, True), + "watchos": ("arm64_32", False), + "watchsimulator": (self.host_arch, True), + }[host_info[3]] else: # On all other platforms, machine == arch self.host_machine = self.host_arch diff --git a/crossenv/scripts/importlib-machinery-patch.py.tmpl b/crossenv/scripts/importlib-machinery-patch.py.tmpl index 012199a..fa75bab 100644 --- a/crossenv/scripts/importlib-machinery-patch.py.tmpl +++ b/crossenv/scripts/importlib-machinery-patch.py.tmpl @@ -26,4 +26,4 @@ def _PathFinder_find_spec(cls, fullname, path=None, target=None): return _original_find_spec(fullname, path, target) PathFinder.find_spec = _PathFinder_find_spec -EXTENSION_SUFFIXES = [{{repr(self.sysconfig_ext_suffix)}}, ".abi3.dylib", ".dylib"] +EXTENSION_SUFFIXES = [{{repr(self.sysconfig_ext_suffix)}}, ".abi3.so", ".so"] diff --git a/crossenv/scripts/platform-patch.py.tmpl b/crossenv/scripts/platform-patch.py.tmpl index d85b90e..8c95722 100644 --- a/crossenv/scripts/platform-patch.py.tmpl +++ b/crossenv/scripts/platform-patch.py.tmpl @@ -22,6 +22,22 @@ def mac_ver(release='', versioninfo=('', '', ''), machine=''): machine = _uname_result.machine return release, versioninfo, machine +IOSVersionInfo = collections.namedtuple( + "IOSVersionInfo", + ["system", "release", "model", "is_simulator"] +) + +def ios_ver(system="", release="", model="", is_simulator=False): + if system == "": + system = {{repr(self.host_system)}} + if release == "": + release = {{repr(self.host_release)}} + if model == "": + model = {{repr("iPhone" if self.host_is_simulator else "iPhone13,2")}} + if release == "": + release = {{repr(self.host_is_simulator)}} + return IOSVersionInfo(system, release, model, is_simulator) + # Old, deprecated functions, but we support back to 3.5 if '_linux_distribution' in globals(): def _linux_distribution(distname, version, id, supported_dists, diff --git a/crossenv/scripts/sys-patch.py.tmpl b/crossenv/scripts/sys-patch.py.tmpl index e9044f4..d2dd769 100644 --- a/crossenv/scripts/sys-patch.py.tmpl +++ b/crossenv/scripts/sys-patch.py.tmpl @@ -9,10 +9,10 @@ implementation._multiarch = {{repr(host_build_time_vars.get('MULTIARCH'))}} if implementation._multiarch is None: del implementation._multiarch -implementation._simulator = {{repr(self.host_is_simulator)}} -if implementation._simulator is None: - del implementation._simulator - # Remove cross-python from sys.path. It's not needed after startup. path.remove({{repr(context.lib_path)}}) path.remove({{repr(stdlib)}}) + +# If a process started by cross-python tries to start a subprocess with sys.executable, +# make sure that it points at cross-python. +executable = {{repr(context.cross_env_exe)}} From aac5f9ea167c32968e86ee2e8ce146294cf77f61 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 5 Sep 2024 12:33:09 +0800 Subject: [PATCH 10/16] Ensure that the installed prefix is corrected to the installed location. --- crossenv/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 7a23404..04b1c65 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -913,12 +913,16 @@ def copy_and_patch_sysconfigdata(self, context): host_cc = self.real_host_cc[0] host_cxx = self.real_host_cxx[0] host_ar = self.real_host_ar[0] + host_prefix = self.host_sysconfigdata.build_time_vars['prefix'] + repl_cc = self.host_cc[0] repl_cxx = self.host_cxx[0] repl_ar = self.host_ar[0] + find_cc = re.compile(r'(?:^|(?<=\s))%s(?=\s|$)' % re.escape(host_cc)) find_cxx = re.compile(r'(?:^|(?<=\s))%s(?=\s|$)' % re.escape(host_cxx)) find_ar = re.compile(r'(?:^|(?<=\s))%s(?=\s|$)' % re.escape(host_ar)) + find_prefix = re.compile(r'(?:^|(?<=\s))%s' % re.escape(host_prefix)) cross_sysconfig_data = {} for key, value in self.host_sysconfigdata.__dict__.items(): @@ -932,6 +936,7 @@ def copy_and_patch_sysconfigdata(self, context): value = find_ar.sub(repl_ar, value) value = find_cxx.sub(repl_cxx, value) value = find_cc.sub(repl_cc, value) + value = find_prefix.sub(self.host_home, value) build_time_vars[key] = value From 5248f86c86b80e0488ba5717d7bc5543acba73bd Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Sep 2024 14:35:41 +0800 Subject: [PATCH 11/16] Cleanups now that everything is working. --- crossenv/__init__.py | 48 +++++++------------ crossenv/scripts/os-patch.py.tmpl | 2 +- crossenv/scripts/packaging-tags-patch.py.tmpl | 2 +- crossenv/scripts/platform-patch.py.tmpl | 7 ++- 4 files changed, 21 insertions(+), 38 deletions(-) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 04b1c65..3d9cb69 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -8,11 +8,8 @@ import subprocess import logging import importlib -import types -from configparser import ConfigParser import random import shlex -import platform import pprint import re @@ -452,11 +449,11 @@ def _split_apple_os_version(self, value): elif value.startswith("watchos"): offset = 7 else: - raise Va + raise ValueError("Unknown Apple compiler triple.") return value[:offset], value[offset:] def _clean_triple(self, triple): - # They are in the form cpu-vendor-kernel-system or cpu-kernel-system. So we'll + # Triples are in the form cpu-vendor-kernel-system or cpu-kernel-system. So we'll # get something like: x86_64-linux-gnu or x86_64-pc-linux-gnu. We won't # overcomplicate this, since it's just to generate a warning. # @@ -466,13 +463,16 @@ def _clean_triple(self, triple): parts = triple.split('-') if parts[1] == "apple": - # Normalize Apple's CPU architecture + # Normalize Apple's CPU architecture descriptor to the GNU form. if parts[0] == "arm64": parts[0] = "aarch64" + # Remove any version identifier from the OS (e.g., ios13.0 -> ios) parts[2], _ = self._split_apple_os_version(parts[2]) if len(parts) == 4 and parts[1] != "apple": + # 4-part "triples" are expected for Apple simulators; + # on any other platform, drop the 4th part. del parts[1] elif len(parts) != 3: return None # Some other form? Bail out. @@ -485,6 +485,7 @@ def create(self, env_dir): :param env_dir: The target directory to create an environment in. """ + env_dir = os.path.abspath(env_dir) context = self.ensure_directories(env_dir) self.make_build_python(context) @@ -525,7 +526,7 @@ def get_uname_info(self): What should uname() return? """ # host_platform is _probably_ something like linux-x86_64, but it can vary. - # On iOS/tvOS/watchOS, it will be of the form ios-12.0-iphonesimulator-arm64, + # On iOS/tvOS/watchOS, it will be of the form ios-13.0-arm64-iphonesimulator, # telling you sys.platform, the minimum supported OS version, the device ABI, # and the architecture. host_info = self.host_platform.split('-') @@ -534,6 +535,7 @@ def get_uname_info(self): elif len(host_info) >= 1: self.host_sys_platform = host_info[0] + self.host_is_simulator = None if self.host_machine is None: platform2uname = { # On uname.machine=ppc64, _PYTHON_HOST_PLATFORM is linux-powerpc64 @@ -543,32 +545,14 @@ def get_uname_info(self): } if len(host_info) > 1 and host_info[-1] in platform2uname: # Test that this is still a special case when we can. - self.host_arch = platform2uname[host_info[-1]] - else: - self.host_arch = self.host_gnu_type.split('-')[0] - - # iOS/tvOS/watchOS return the device type as the machine, have a separate - # concept of being a simulator, and use arm64 rather than aarch64 as an - # architecture descriptor. - if self.host_sys_platform in {"ios", "tvos", "watchos"}: - if self.host_arch == "aarch64": - self.host_arch = "arm64" - - self.host_machine, self.host_is_simulator = { - "iphoneos": ("arm64", False), - "iphonesimulator": (self.host_arch, True), - "appletvos": ("arm64", False), - "appletvsimulator": (self.host_arch, True), - "watchos": ("arm64_32", False), - "watchsimulator": (self.host_arch, True), - }[host_info[3]] + self.host_machine = platform2uname[host_info[-1]] + elif self.host_sys_platform in {"ios", "tvos", "watchos"}: + # iOS/tvOS/watchOS return the machine type as the last part + # of the host info. The device is a simulator + self.host_machine = host_info[-2] + self.host_is_simulator = host_info[-1].endswith("simulator") else: - # On all other platforms, machine == arch - self.host_machine = self.host_arch - self.host_is_simulator = None - else: - self.host_arch = self.host_machine - self.host_is_simulator = None + self.host_machine = self.host_gnu_type.split('-')[0] if self.macosx_deployment_target: try: diff --git a/crossenv/scripts/os-patch.py.tmpl b/crossenv/scripts/os-patch.py.tmpl index d3bfb53..6503ca7 100644 --- a/crossenv/scripts/os-patch.py.tmpl +++ b/crossenv/scripts/os-patch.py.tmpl @@ -8,7 +8,7 @@ _uname_result = uname_result_type( 'build', {{repr(self.host_release)}}, '', - {{repr(self.host_arch)}}) + {{repr(self.host_machine)}}) def uname(): return _uname_result diff --git a/crossenv/scripts/packaging-tags-patch.py.tmpl b/crossenv/scripts/packaging-tags-patch.py.tmpl index 3ab1c0b..8a737a4 100644 --- a/crossenv/scripts/packaging-tags-patch.py.tmpl +++ b/crossenv/scripts/packaging-tags-patch.py.tmpl @@ -3,6 +3,6 @@ def _linux_platforms(): yield from {{repr(self.platform_tags)}} - arch = _normalize_string({{repr(self.host_arch)}}) + arch = _normalize_string({{repr(self.host_machine)}}) archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch]) yield from archs diff --git a/crossenv/scripts/platform-patch.py.tmpl b/crossenv/scripts/platform-patch.py.tmpl index 8c95722..4b370ac 100644 --- a/crossenv/scripts/platform-patch.py.tmpl +++ b/crossenv/scripts/platform-patch.py.tmpl @@ -8,7 +8,7 @@ _uname_result = platform_uname_result_type( {{repr(self.host_release)}}, '', {{repr(self.host_machine)}}, - {{repr(self.host_arch)}}) + {{repr(self.host_machine)}}) def uname(): return _uname_result @@ -34,9 +34,8 @@ def ios_ver(system="", release="", model="", is_simulator=False): release = {{repr(self.host_release)}} if model == "": model = {{repr("iPhone" if self.host_is_simulator else "iPhone13,2")}} - if release == "": - release = {{repr(self.host_is_simulator)}} - return IOSVersionInfo(system, release, model, is_simulator) + + return IOSVersionInfo(system, release, model, {{repr(self.host_is_simulator)}}) # Old, deprecated functions, but we support back to 3.5 if '_linux_distribution' in globals(): From 0dc491a4c0ad91a4a3815fc48a6149bc3700373a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Sep 2024 14:45:31 +0800 Subject: [PATCH 12/16] Add PEP 621 configuration. --- pyproject.toml | 36 ++++++++++++++++++++++++++++++++++++ setup.py | 46 ---------------------------------------------- 2 files changed, 36 insertions(+), 46 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3ff2127 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[build-system] +requires = ["setuptools==74.1.2"] +build-backend = "setuptools.build_meta" + +[project] +dynamic = ["version"] +name = "crossenv" +description = "A cross-compiling tool for Python extension modules" +readme = { file = "README.rst", content-type = "text/x-rst"} +requires-python = ">=3.4" +license = { file = "LICENSE.txt" } +authors = [ + { name = "Benjamin Fogle", email = "benfogle@gmail.com" } +] +maintainers = [ + { name = "Benjamin Fogle", email = "benfogle@gmail.com" } +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", +] + +[project.urls] +Homepage = "https://github.com/benfogle/crossenv" + +[tool.setuptools] +packages = ["crossenv"] + +[tool.setuptools.dynamic] +version = { attr = "crossenv.__version__" } + +[tool.setuptools.package-data] +crossenv = ["scripts/*"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 6e70069..0000000 --- a/setup.py +++ /dev/null @@ -1,46 +0,0 @@ -from setuptools import setup -import os -import re - -here = os.path.abspath(os.path.dirname(__file__)) - -def read(*path, default=None): - try: - with open(os.path.join(here, *path), encoding='utf-8') as f: - return f.read() - except IOError: - return '' - -long_description = read('README.rst') - -def get_version(): - init_file = read('crossenv', '__init__.py') - version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - init_file, re.MULTILINE) - if version_match: - return version_match.group(1) - raise RuntimeError("Unable to find version string.") - -setup( - name="crossenv", - version=get_version(), - description="A cross-compiling tool for Python extension modules", - long_description=long_description, - url="https://github.com/benfogle/crossenv", - author="Benjamin Fogle", - author_email="benfogle@gmail.com", - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Build Tools', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', - ], - python_requires='>=3.4', - packages=['crossenv'], - package_data = { - 'crossenv' : ['scripts/*'], - }, -) - - From bc8bf73e4aa5a46df70259f08953c613a06df623 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Sep 2024 14:57:30 +0800 Subject: [PATCH 13/16] Completed a stray incomplete comment. --- crossenv/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 3d9cb69..261d275 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -547,8 +547,9 @@ def get_uname_info(self): # Test that this is still a special case when we can. self.host_machine = platform2uname[host_info[-1]] elif self.host_sys_platform in {"ios", "tvos", "watchos"}: - # iOS/tvOS/watchOS return the machine type as the last part - # of the host info. The device is a simulator + # iOS/tvOS/watchOS return the machine type as the last part of + # the host info. The device is a simulator if the last part ends + # with "simulator" (e.g., "iphoneos" vs "iphonesimulator") self.host_machine = host_info[-2] self.host_is_simulator = host_info[-1].endswith("simulator") else: From f0f07129eb06ea16d180650a26a02df2b948b888 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 10 Sep 2024 09:49:28 +0800 Subject: [PATCH 14/16] Extract Apple mobile platform list as a constant. --- crossenv/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 261d275..351faed 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -20,6 +20,9 @@ logger = logging.getLogger(__name__) +APPLE_MOBILE_PLATFORMS = {"ios", "tvos", "watchos"} + + class CrossEnvBuilder(venv.EnvBuilder): """ A class to build a cross-compiling virtual environment useful for @@ -546,7 +549,7 @@ def get_uname_info(self): if len(host_info) > 1 and host_info[-1] in platform2uname: # Test that this is still a special case when we can. self.host_machine = platform2uname[host_info[-1]] - elif self.host_sys_platform in {"ios", "tvos", "watchos"}: + elif self.host_sys_platform in APPLE_MOBILE_PLATFORMS: # iOS/tvOS/watchOS return the machine type as the last part of # the host info. The device is a simulator if the last part ends # with "simulator" (e.g., "iphoneos" vs "iphonesimulator") @@ -569,7 +572,7 @@ def get_uname_info(self): else: raise ValueError("Unexpected major version %s for MACOSX_DEPLOYMENT_TARGET" % major) - elif self.host_sys_platform in {"ios", "tvos", "watchos"}: + elif self.host_sys_platform in APPLE_MOBILE_PLATFORMS: self.host_release = host_info[1] else: self.host_release = '' From 8fbcf5f6fa86277b6010ca058eef6f5ccb3dab13 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 10 Sep 2024 12:07:52 +0800 Subject: [PATCH 15/16] Differentiate between directory name and cross environment name. --- crossenv/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 351faed..2a31c75 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -736,15 +736,16 @@ def make_cross_python(self, context): if self.cross_prefix: context.cross_env_dir = self.cross_prefix else: - cross_env_name = os.path.split(context.env_dir)[-1] - context.cross_env_dir = os.path.join(context.env_dir, cross_env_name) + context.cross_env_dir = os.path.join(context.env_dir, "cross") + cross_env_name = os.path.split(context.env_dir)[-1] env = venv.EnvBuilder( system_site_packages=False, clear=self.clear_cross, symlinks=True, upgrade=False, - with_pip=False) + with_pip=False, + prompt=cross_env_name) env.create(context.cross_env_dir) context.cross_bin_path = os.path.join(context.cross_env_dir, 'bin') context.cross_lib_path = os.path.join(context.cross_env_dir, 'lib') From 3ef761b02c45546edcb40ed038f459ce38d29e6b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 24 Sep 2024 11:58:35 -0700 Subject: [PATCH 16/16] Add patch to ensure entry scripts are always executable. --- crossenv/__init__.py | 1 + .../pip-_vendor-distlib-scripts-patch.py.tmpl | 17 +++++++++++++++++ crossenv/scripts/site.py.tmpl | 1 + 3 files changed, 19 insertions(+) create mode 100644 crossenv/scripts/pip-_vendor-distlib-scripts-patch.py.tmpl diff --git a/crossenv/__init__.py b/crossenv/__init__.py index 2a31c75..a38b8f8 100644 --- a/crossenv/__init__.py +++ b/crossenv/__init__.py @@ -851,6 +851,7 @@ def make_cross_python(self, context): 'sysconfig-patch.py', 'subprocess-patch.py', 'distutils-sysconfig-patch.py', + 'pip-_vendor-distlib-scripts-patch.py', 'pkg_resources-patch.py', 'packaging-tags-patch.py', ] diff --git a/crossenv/scripts/pip-_vendor-distlib-scripts-patch.py.tmpl b/crossenv/scripts/pip-_vendor-distlib-scripts-patch.py.tmpl new file mode 100644 index 0000000..f2cac12 --- /dev/null +++ b/crossenv/scripts/pip-_vendor-distlib-scripts-patch.py.tmpl @@ -0,0 +1,17 @@ +def _build_shebang(self, executable, post_interp): + """ + Build a shebang line. The default pip behavior will use a "simple" shim + if the path to the wrapped Python binary is < 127 chars long, and doesn't + contain a space. However, the host python binary isn't actually a binary - + it's a shell script that does additional environment modifications - and + os.execv() raises "OSError [Errno 8] Exec format error" if the shebang + of a script isn't a literal binary. + + So - patch the script writer so that it *always* uses a shim. + """ + result = b'#!/bin/sh\n' + result += b"'''exec' " + executable + post_interp + b' "$0" "$@"\n' + result += b"' '''" + return result + +ScriptMaker._build_shebang = _build_shebang diff --git a/crossenv/scripts/site.py.tmpl b/crossenv/scripts/site.py.tmpl index c619ad4..7b691cf 100644 --- a/crossenv/scripts/site.py.tmpl +++ b/crossenv/scripts/site.py.tmpl @@ -111,6 +111,7 @@ class CrossenvFinder(importlib.abc.MetaPathFinder): 'distutils.sysconfig_pypy': '{{context.lib_path}}/distutils-sysconfig-patch.py', 'platform': '{{context.lib_path}}/platform-patch.py', 'pkg_resources': '{{context.lib_path}}/pkg_resources-patch.py', + 'pip._vendor.distlib.scripts': '{{context.lib_path}}/pip-_vendor-distlib-scripts-patch.py', 'pip._vendor.pkg_resources': '{{context.lib_path}}/pkg_resources-patch.py', 'packaging.tags': '{{context.lib_path}}/packaging-tags-patch.py', }