crossenv
Advanced tools
| @property | ||
| def _Context_path(self): | ||
| path = vars(self).get('path') | ||
| if path is not None: | ||
| return path | ||
| import sysconfig | ||
| import sys | ||
| stdlib = sysconfig.get_path('stdlib') | ||
| path = list(sys.path) | ||
| try: | ||
| i = path.index(stdlib) | ||
| path[i:i] = sys.build_path | ||
| except ValueError: | ||
| pass | ||
| return path | ||
| # This will apply to all created instances of Context(), so we don't need to | ||
| # patch the default argument to any find_distribution() functions out there. | ||
| DistributionFinder.Context.path = _Context_path |
| # By default we disable manylinux/manymusl tags. However, we allow users to opt | ||
| # in. | ||
| def _linux_platforms(): | ||
| yield from {{repr(self.platform_tags)}} | ||
| arch = _normalize_string({{repr(self.host_machine)}}) | ||
| archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch]) | ||
| yield from archs |
| ########################## | ||
| # Basic download/compile | ||
| ######################### | ||
| from textwrap import dedent | ||
| import os | ||
| import copy | ||
| import zipfile | ||
| import pytest | ||
| from .testutils import make_crossenv | ||
| def test_build_simple(tmp_path, host_python, build_python, get_resource): | ||
| # It's a huge PITA to do out-of-source with setuptools, so we'll just | ||
| # make a copy. | ||
| source = get_resource('hello-module:source') | ||
| build = source.make_copy() | ||
| # Take care to prevent creation of a .pth file; we don't want to have to mess | ||
| # with sitecustomize.py stuff to make this work. | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python) | ||
| crossenv.check_call(['python', 'setup.py', 'install', | ||
| '--single-version-externally-managed', | ||
| '--root', build.path, | ||
| '--install-lib', '.'], cwd=build.path) | ||
| host_python.setenv('PYTHONPATH', str(build.path) + ':$PYTHONPATH') | ||
| host_python.check_call([host_python.binary, '-c', dedent('''\ | ||
| import hello | ||
| assert hello.hello() == 'Hello, world' | ||
| ''')]) | ||
| def test_wheel_simple(tmp_path, host_python, build_python, get_resource): | ||
| source = get_resource('hello-module:source') | ||
| build = source.make_copy() | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python) | ||
| crossenv.check_call(['pip', 'install', 'wheel']) | ||
| crossenv.check_call(['python', 'setup.py', 'bdist_wheel'], cwd=build.path) | ||
| mods = build.path / 'mods' | ||
| for whl in build.path.glob('dist/*.whl'): | ||
| with zipfile.ZipFile(whl) as zp: | ||
| zp.extractall(mods) | ||
| host_python.setenv('PYTHONPATH', str(mods) + ':$PYTHONPATH') | ||
| host_python.check_call([host_python.binary, '-c', dedent('''\ | ||
| import hello | ||
| assert hello.hello() == 'Hello, world' | ||
| ''')]) | ||
| def test_pip_install_numpy(tmp_path, host_python, build_python, python_version): | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python) | ||
| # Numpy is far too clever, and if it detects that any linear algebra | ||
| # libraries are available on the build system, then it will happily try to | ||
| # include them in the host build. Disable them so we have a consistent | ||
| # environment for our tests. | ||
| crossenv.setenv('BLAS', 'None') | ||
| crossenv.setenv('LAPACK', 'None') | ||
| crossenv.setenv('ATLAS', 'None') | ||
| if python_version == 'main': | ||
| # Not sure whose fault this sort of thing is. | ||
| pytest.xfail("Known broken against master branch") | ||
| crossenv.check_call(['cross-pip', '--no-cache-dir', 'install', | ||
| 'numpy==1.18.1', 'pytest==5.3.5']) | ||
| # Run some tests under emulation. We don't do the full numpy test suite | ||
| # because 1) it's very slow, and 2) there are some failing tests. | ||
| # The failing tests might be an issue with numpy on the given archtecture, | ||
| # or with qemu, or who knows, but in any case, it's really beyond the scope | ||
| # of this project to address. We'll choose a quick, but nontrivial set of | ||
| # tests to run. | ||
| host_python.setenv('PYTHONPATH', | ||
| str(crossenv.cross_site_packages) + ':$PYTHONPATH') | ||
| host_python.check_call([host_python.binary, '-c', dedent('''\ | ||
| import sys, numpy | ||
| ok = numpy.test(tests=['numpy.polynomial']) | ||
| sys.exit(ok != True) | ||
| ''')]) | ||
| def test_pip_install_bcrypt(tmp_path, host_python, build_python): | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python) | ||
| crossenv.check_call(['build-pip', '--no-cache-dir', 'install', 'cffi']) | ||
| crossenv.check_call(['cross-pip', '--no-cache-dir', 'install', 'bcrypt~=3.1.0']) | ||
| # From the bcrypt test suites | ||
| host_python.setenv('PYTHONPATH', | ||
| str(crossenv.cross_site_packages) + ':$PYTHONPATH') | ||
| output = host_python.check_output([host_python.binary, '-c', dedent(''' | ||
| import bcrypt, sys | ||
| pw = b"Kk4DQuMMfZL9o" | ||
| salt = b"$2b$04$cVWp4XaNU8a4v1uMRum2SO" | ||
| print(bcrypt.hashpw(pw, salt).decode('ascii')) | ||
| ''')]) | ||
| output = output.strip() | ||
| expected = b"$2b$04$cVWp4XaNU8a4v1uMRum2SO026BWLIoQMD/TXg5uZV.0P.uO8m3YEm" | ||
| assert output == expected | ||
| def test_build_pyproject_cffi(tmp_path, host_python, build_python): | ||
| # Adapted from https://github.com/benfogle/crossenv/issues/108 | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python) | ||
| proj = tmp_path / "myproj" | ||
| proj.mkdir() | ||
| with open(proj / "dummy.py", "w"): | ||
| pass | ||
| with open(proj / "pyproject.toml", "w") as fp: | ||
| fp.write(dedent(""" | ||
| [build-system] | ||
| requires = ["setuptools", "cffi"] | ||
| """)) | ||
| crossenv.check_call(['cross-pip', '--no-cache-dir', 'install', '-U', | ||
| 'pip==23.2.1', 'setuptools==68.0.0', 'wheel==0.41.1', 'build==0.10.0']) | ||
| crossenv.check_call(['build-pip', '--no-cache-dir', 'install', '-U', | ||
| 'pip==23.2.1', 'setuptools==68.0.0', 'wheel==0.41.1', 'build==0.10.0']) | ||
| crossenv.check_call(['build-pip', '--no-cache-dir', 'install', 'cffi==1.15.1']) | ||
| crossenv.check_call(['cross-expose', 'cffi']) | ||
| crossenv.check_call(['cross-python', '-m', 'build', '--no-isolation'], | ||
| cwd=proj) |
| ######################################## | ||
| # Test options that alter the compiler | ||
| ######################################## | ||
| from textwrap import dedent | ||
| import re | ||
| import platform | ||
| from .testutils import make_crossenv | ||
| def test_set_c_compiler(tmp_path, host_python, build_python): | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python, | ||
| '--cc=/bin/true') | ||
| compiler = crossenv.check_output(['python', '-c', dedent('''\ | ||
| import sysconfig | ||
| print(sysconfig.get_config_var("CC")) | ||
| ''')]) | ||
| compiler = compiler.split()[0] | ||
| assert compiler == b'/bin/true' | ||
| def test_set_cxx_compiler(tmp_path, host_python, build_python): | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python, | ||
| '--cxx=/bin/true') | ||
| compiler = crossenv.check_output(['python', '-c', dedent('''\ | ||
| import sysconfig | ||
| print(sysconfig.get_config_var("CXX")) | ||
| ''')]) | ||
| compiler = compiler.split()[0] | ||
| assert compiler == b'/bin/true' | ||
| def test_set_ar(tmp_path, host_python, build_python): | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python, | ||
| '--ar=/bin/true') | ||
| compiler = crossenv.check_output(['python', '-c', dedent('''\ | ||
| import sysconfig | ||
| print(sysconfig.get_config_var("AR")) | ||
| ''')]) | ||
| compiler = compiler.split()[0] | ||
| assert compiler == b'/bin/true' | ||
| def test_wrong_architecture(tmp_path, host_python, build_python, | ||
| architecture): | ||
| """Make sure we get a warning if the compiler doesn't seem right. Requires | ||
| gcc.""" | ||
| # N/A when the host and build systems share the same architecture. | ||
| if architecture.machine == platform.machine(): | ||
| return | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python, | ||
| '--cc=/usr/bin/gcc') | ||
| for line in crossenv.creation_log.splitlines(): | ||
| if re.match(r'WARNING:.*architecture', line): | ||
| return | ||
| assert False, "Crossenv did not detect wrong architecture" |
| import os | ||
| import re | ||
| from textwrap import dedent | ||
| from .testutils import make_crossenv | ||
| def test_uname(crossenv, architecture): | ||
| # We don't test all of uname. We currently have no values for release or | ||
| # version, and we don't really care about node. | ||
| out = crossenv.check_output(['python', '-c', dedent('''\ | ||
| import os | ||
| print(os.uname().sysname, os.uname().machine) | ||
| ''')], | ||
| universal_newlines=True) | ||
| out = out.strip() | ||
| expected = '{} {}'.format(architecture.system, architecture.machine) | ||
| assert out == expected | ||
| def test_platform(crossenv, architecture): | ||
| # Since uname isn't 100%, platform.platform() still looks a bit strange. | ||
| # Test platform.uname() components | ||
| # | ||
| # Also: don't run 'python -m platform'. The way this works, a __main__ | ||
| # module is created and populated with code from the system library | ||
| # platform module. This __main__ module is completely separate from what | ||
| # you get from 'import platform', so none of our patches apply to it! | ||
| # There's no good way around that that I can think of. | ||
| out = crossenv.check_output(['python', '-c', dedent('''\ | ||
| import platform | ||
| print(platform.uname().system, | ||
| platform.uname().machine, | ||
| platform.uname().processor) | ||
| ''')], | ||
| universal_newlines=True) | ||
| out = out.strip() | ||
| expected = '{0} {1} {1}'.format(architecture.system, architecture.machine) | ||
| assert out == expected | ||
| def test_sysconfig_platform(crossenv, architecture): | ||
| out = crossenv.check_output(['python', '-c', dedent('''\ | ||
| import sysconfig | ||
| print(sysconfig.get_platform()) | ||
| ''')], | ||
| universal_newlines=True) | ||
| out = out.strip() | ||
| expected = '{}-{}'.format(architecture.system, architecture.machine) | ||
| expected = expected.lower() | ||
| assert out == expected | ||
| def test_no_manylinux(crossenv, architecture): | ||
| crossenv.check_call(['pip', 'install', 'packaging']) | ||
| out = crossenv.check_output(['python', '-c', dedent('''\ | ||
| from packaging.tags import compatible_tags | ||
| platforms = set(tag.platform for tag in compatible_tags()) | ||
| print('\\n'.join(platforms)) | ||
| ''')], | ||
| universal_newlines=True) | ||
| out = out.strip() | ||
| assert 'manylinux' not in out | ||
| def test_explicit_platform_tags(tmp_path, host_python, build_python, architecture): | ||
| crossenv = make_crossenv( | ||
| tmp_path, | ||
| host_python, | ||
| build_python, | ||
| '--platform-tag=foobar1234', | ||
| '--platform-tag=mytag', | ||
| ) | ||
| crossenv.check_call(['pip', 'install', 'packaging']) | ||
| out = crossenv.check_output(['python', '-c', dedent('''\ | ||
| from packaging.tags import compatible_tags | ||
| platforms = set(tag.platform for tag in compatible_tags()) | ||
| print('\\n'.join(platforms)) | ||
| ''')], | ||
| universal_newlines=True) | ||
| out = out.strip() | ||
| assert 'foobar1234' in out | ||
| assert 'mytag' in out | ||
| def test_explicit_manylinux(tmp_path, host_python, build_python, architecture): | ||
| # not defined for all architectures, so pass them | ||
| if architecture.machine not in ('x86_64', 'aarch64'): | ||
| return | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python, | ||
| '--manylinux=manylinux2014') | ||
| crossenv.check_call(['pip', 'install', 'packaging']) | ||
| out = crossenv.check_output(['python', '-c', dedent('''\ | ||
| from packaging.tags import compatible_tags | ||
| platforms = set(tag.platform for tag in compatible_tags()) | ||
| print('\\n'.join(platforms)) | ||
| ''')], | ||
| universal_newlines=True) | ||
| out = out.strip() | ||
| assert 'manylinux2014' in out | ||
| assert 'manylinux_2_17' in out | ||
| def test_very_long_paths(tmp_path_factory, host_python, build_python): | ||
| tmp = tmp_path_factory.mktemp('A'*128) | ||
| dirname = tmp / ('B'*128) | ||
| os.mkdir(dirname) | ||
| assert len(str(dirname)) >= 256 | ||
| crossenv = make_crossenv(dirname, host_python, build_python) | ||
| crossenv.check_call(['python', '--version']) | ||
| def test_very_long_paths(tmp_path_factory, host_python, build_python): | ||
| tmp = tmp_path_factory.mktemp('A'*128) | ||
| dirname = tmp / ('B'*128) | ||
| os.mkdir(dirname) | ||
| assert len(str(dirname)) >= 256 | ||
| crossenv = make_crossenv(dirname, host_python, build_python) | ||
| crossenv.check_call(['python', '--version']) | ||
| def test_environment_leak(crossenv): | ||
| # a regression test that used to cause scary warnings during build | ||
| # processes. Triggered with subprocess.Popen with explicit environ | ||
| out = crossenv.check_output(['python', '-c', dedent('''\ | ||
| import subprocess | ||
| import sys | ||
| import os | ||
| env = os.environ.copy() | ||
| python = sys.executable | ||
| result = subprocess.run([python, '-c', 'print("ok")'], env=env) | ||
| sys.exit(result.returncode) | ||
| ''')], | ||
| universal_newlines=True) | ||
| assert 'Crossenv has leaked' not in out | ||
| def test_run_sysconfig_module(crossenv): | ||
| # a regression test that 'python -m sysconfig' works as well as 'import | ||
| # sysconfig' | ||
| out = crossenv.check_output(['python', '-m', 'sysconfig'], | ||
| universal_newlines=True) | ||
| m = re.search(r'^\s*DESTDIRS = "(.*)"$', out, re.M) | ||
| assert m is not None | ||
| destdirs_cmdline = m.group(1) | ||
| out = crossenv.check_output(['python', '-c', dedent('''\ | ||
| import sysconfig | ||
| print(sysconfig.get_config_var('DESTDIRS')) | ||
| ''')], | ||
| universal_newlines=True) | ||
| out = out.strip() | ||
| assert destdirs_cmdline == out | ||
| def test_cross_expose(crossenv): | ||
| out = crossenv.check_output(['pip', 'freeze']) | ||
| assert b'colorama' not in out | ||
| crossenv.check_call(['build-pip', 'install', 'colorama']) | ||
| out = crossenv.check_output(['pip', 'freeze']) | ||
| assert b'colorama' not in out | ||
| crossenv.check_call(['cross-expose', 'colorama']) | ||
| out = crossenv.check_output(['pip', 'freeze']) | ||
| assert b'colorama' in out | ||
| out = crossenv.check_output(['cross-expose', '--list']) | ||
| assert b'colorama' in out | ||
| crossenv.check_call(['cross-expose', '-u', 'colorama']) | ||
| out = crossenv.check_output(['pip', 'freeze']) | ||
| assert b'colorama' not in out | ||
| out = crossenv.check_output(['cross-expose', '--list']) | ||
| assert b'colorama' not in out | ||
| def test_machine_override(tmp_path, host_python, build_python): | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python, | ||
| '--machine=foobar') | ||
| out = crossenv.check_output(['python', '-c', dedent('''\ | ||
| import os, platform | ||
| print(os.uname().machine, platform.machine()) | ||
| ''')], | ||
| universal_newlines=True) | ||
| out = out.strip() | ||
| assert out == 'foobar foobar' |
| ####################################################################### | ||
| # These are tests to make sure that our prebuilt files are working | ||
| # correctly. | ||
| ####################################################################### | ||
| from textwrap import dedent | ||
| import os | ||
| import subprocess | ||
| import pytest | ||
| def test_build_python_runs(build_python): | ||
| build_python.check_call([build_python.binary, '--version']) | ||
| def test_host_python_emulates(host_python): | ||
| host_python.check_call([host_python.binary, '--version']) | ||
| def test_cross_compiler_runs(build_python, host_python): | ||
| cc = host_python.check_output([host_python.binary, '-c', dedent('''\ | ||
| import sysconfig | ||
| print(sysconfig.get_config_var("CC")) | ||
| ''')], | ||
| universal_newlines=True) | ||
| # Build-python should come with the correct path to the cross compiler | ||
| # could be a whole shell command in there, so run with shell=True | ||
| cmdline = cc.strip() + ' --version' | ||
| build_python.check_call(cmdline, shell=True) |
| ######################################## | ||
| # Test general usage | ||
| ######################################## | ||
| import subprocess | ||
| import pytest | ||
| from .testutils import make_crossenv | ||
| def test_no_pip(tmp_path, host_python, build_python): | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python, | ||
| '--without-pip') | ||
| # pip and pip3 could be in the user PATH, so call exactly the one we | ||
| # expect | ||
| pip_variants = [ | ||
| 'pip', | ||
| 'pip3', | ||
| 'cross-pip', | ||
| 'cross-pip3', | ||
| 'build-pip', | ||
| 'build-pip3', | ||
| ] | ||
| bin_variants = [ | ||
| crossenv.bindir, | ||
| crossenv.cross_bindir, | ||
| crossenv.build_bindir, | ||
| ] | ||
| for bindir in bin_variants: | ||
| for pip in pip_variants: | ||
| pip = bindir / pip | ||
| with pytest.raises(FileNotFoundError): | ||
| crossenv.check_call([pip, '--version']) | ||
| python_variants = [ | ||
| 'python', | ||
| 'python3', | ||
| 'cross-python', | ||
| 'cross-python3', | ||
| 'build-python', | ||
| 'build-python3', | ||
| ] | ||
| for python in python_variants: | ||
| result = crossenv.run( | ||
| [python, '-m', 'pip', '--version'], | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT, | ||
| universal_newlines=True) | ||
| assert result.returncode != 0, \ | ||
| "'%s -m pip' shouldn't succeed" % python | ||
| assert 'no module named pip' in result.stdout.lower() | ||
| def test_no_cross_pip(tmp_path, host_python, build_python): | ||
| crossenv = make_crossenv(tmp_path, host_python, build_python, | ||
| '--without-cross-pip') | ||
| # pip and pip3 could be in the user PATH, so call exactly the one we | ||
| # expect | ||
| pip_variants = [ | ||
| 'pip', | ||
| 'pip3', | ||
| 'cross-pip', | ||
| 'cross-pip3', | ||
| ] | ||
| bin_variants = [ | ||
| crossenv.bindir, | ||
| crossenv.cross_bindir, | ||
| ] | ||
| for bindir in bin_variants: | ||
| for pip in pip_variants: | ||
| pip = bindir / pip | ||
| with pytest.raises(FileNotFoundError): | ||
| crossenv.check_call([pip, '--version']) | ||
| # cross-python -m pip will still work, because cross-python | ||
| # can import build-python's modules. | ||
| def test_cross_prefix(tmp_path, host_python, build_python): | ||
| env = tmp_path / 'cross' | ||
| prefix = tmp_path / 'this_is_a_test' | ||
| crossenv = make_crossenv(env, host_python, build_python, | ||
| '--without-pip', '--cross-prefix={}'.format(prefix)) | ||
| cross_python = prefix / 'bin' / 'python' | ||
| assert cross_python.is_file() | ||
| activate = env / 'bin' / 'activate' | ||
| out = crossenv.check_output( | ||
| ['bash', '-c', '. {}; echo $PS1'.format(activate)], | ||
| universal_newlines=True) | ||
| assert '(this_is_a_test)' in out |
| import os | ||
| import hashlib | ||
| import subprocess | ||
| import string | ||
| import fcntl | ||
| from contextlib import contextmanager | ||
| def hash_file(path): | ||
| ctx = hashlib.sha256() | ||
| with open(path, 'rb') as fp: | ||
| data = fp.read(0x4000) | ||
| while data: | ||
| ctx.update(data) | ||
| data = fp.read(0x4000) | ||
| return ctx.hexdigest() | ||
| class ExecEnvironment: | ||
| '''Mostly some wrappers around popen''' | ||
| def __init__(self): | ||
| # prepopulate the environment | ||
| self.environ = { | ||
| 'PATH': os.environ.get('PATH', ''), | ||
| } | ||
| self.cwd = None | ||
| def expand_environ(self, value): | ||
| # os.path.expandvars exists, but doesn't operate on an arbitrary | ||
| # environment. We might clobber changes to $PATH, etc. if we use | ||
| # it. | ||
| class EnvDict: | ||
| '''trick Template.substitute into converting $NOTFOUND into ''' | ||
| def __init__(self, src): | ||
| self.src = src | ||
| def __getitem__(self, key): | ||
| return self.src.get(key, '') | ||
| if value is None: | ||
| return value | ||
| value = str(value) | ||
| return string.Template(value).substitute(EnvDict(self.environ)) | ||
| def setenv(self, name, value): | ||
| if value is None: | ||
| self.environ.pop(name, None) | ||
| else: | ||
| value = self.expand_environ(value) | ||
| self.environ[name] = value | ||
| def _alter_env(self, updated, original): | ||
| env = original.copy() | ||
| # Calculate all new values without updating | ||
| new_env = {} | ||
| for name, value in updated.items(): | ||
| if value is not None: | ||
| value = self.expand_environ(value) | ||
| new_env[name] = value | ||
| # Update new values, remove as necessary | ||
| for name, value in new_env.items(): | ||
| if value is None and name in env: | ||
| del env[name] | ||
| else: | ||
| env[name] = value | ||
| return env | ||
| def _popen(self, func, *args, **kwargs): | ||
| if self.environ: | ||
| env = kwargs.get('env', os.environ) | ||
| env = self._alter_env(self.environ, env) | ||
| kwargs['env'] = env | ||
| if self.cwd and 'cwd' not in kwargs: | ||
| kwargs['cwd'] = self.cwd | ||
| return func(*args, **kwargs) | ||
| def run(self, *args, **kwargs): | ||
| return self._popen(subprocess.run, *args, **kwargs) | ||
| def popen(self, *args, **kwargs): | ||
| return self._popen(subprocess.Popen, *args, **kwargs) | ||
| def check_call(self, *args, **kwargs): | ||
| return self._popen(subprocess.check_call, *args, **kwargs) | ||
| def check_output(self, *args, **kwargs): | ||
| return self._popen(subprocess.check_output, *args, **kwargs) | ||
| class CrossenvEnvironment(ExecEnvironment): | ||
| def __init__(self, build_python, crossenv_dir, creation_log=''): | ||
| super().__init__() | ||
| self.creation_log = creation_log | ||
| self.environ = build_python.environ.copy() | ||
| self.cwd = build_python.cwd | ||
| self.crossenv_dir = crossenv_dir | ||
| self.bindir = crossenv_dir / 'bin' | ||
| site = sorted(crossenv_dir.glob('build/lib/python*/site-packages'))[0] | ||
| self.build_bindir = crossenv_dir / 'build/bin' | ||
| self.build_site_packages = site | ||
| sites = crossenv_dir.glob('cross/lib/python*/site-packages') | ||
| sites = sorted(sites) | ||
| if sites: | ||
| self.cross_site_packages = sites[0] | ||
| self.cross_bindir = crossenv_dir / 'cross/bin' | ||
| else: | ||
| self.cross_site_packages = None | ||
| self.cross_bindir = None | ||
| # Mimic some of what crossenv_dir/bin/activate would do | ||
| self.setenv('PATH', '{}:{}:$PATH'.format(self.bindir, | ||
| self.cross_bindir)) | ||
| def make_crossenv(crossenv_dir, host_python, build_python, *args, **kwargs): | ||
| cmdline = [ build_python.binary, '-m', 'crossenv', host_python.binary, | ||
| crossenv_dir ] | ||
| cmdline.extend(args) | ||
| result = build_python.run(cmdline, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT, universal_newlines=True, | ||
| **kwargs) | ||
| if result.returncode: | ||
| print(result.stdout) # capture output on error | ||
| assert False, "Could not make crossenv!" | ||
| return CrossenvEnvironment(build_python, crossenv_dir, | ||
| creation_log=result.stdout) | ||
| @contextmanager | ||
| def open_lock_file(path): | ||
| fd = os.open(path, os.O_RDWR | os.O_CREAT, 0o660) | ||
| with open(fd, 'r+') as fp: | ||
| fcntl.flock(fp, fcntl.LOCK_EX) | ||
| try: | ||
| data = fp.read(64) | ||
| if data: | ||
| try: | ||
| yield int(data) | ||
| return | ||
| except ValueError: | ||
| fp.truncate(0) | ||
| yield None | ||
| fp.write(str(os.getpid())) | ||
| finally: | ||
| fp.flush() | ||
| fcntl.flock(fp, fcntl.LOCK_UN) |
| Metadata-Version: 2.1 | ||
| Name: crossenv | ||
| Version: 1.4.0 | ||
| Version: 1.5.0 | ||
| Summary: A cross-compiling tool for Python extension modules | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/benfogle/crossenv |
@@ -17,3 +17,5 @@ LICENSE.txt | ||
| crossenv/scripts/importlib-machinery-patch.py.tmpl | ||
| crossenv/scripts/importlib-metadata-patch.py.tmpl | ||
| crossenv/scripts/os-patch.py.tmpl | ||
| crossenv/scripts/packaging-tags-patch.py.tmpl | ||
| crossenv/scripts/pkg_resources-patch.py.tmpl | ||
@@ -24,2 +26,8 @@ crossenv/scripts/platform-patch.py.tmpl | ||
| crossenv/scripts/sys-patch.py.tmpl | ||
| crossenv/scripts/sysconfig-patch.py.tmpl | ||
| crossenv/scripts/sysconfig-patch.py.tmpl | ||
| tests/test_basic.py | ||
| tests/test_compiler.py | ||
| tests/test_environment.py | ||
| tests/test_resources.py | ||
| tests/test_usage.py | ||
| tests/testutils.py |
+45
-21
@@ -22,3 +22,3 @@ import venv | ||
| __version__ = '1.4.0' | ||
| __version__ = '1.5.0' | ||
@@ -106,2 +106,5 @@ logger = logging.getLogger(__name__) | ||
| :param manylinux_tags: Manylinux tags that are acceptable when downloading | ||
| from PyPI. Merged with platform_tags and retained for | ||
| compatibility only. | ||
| :param platform_tags: Platform tags that are acceptable when downloading | ||
| from PyPI. | ||
@@ -127,2 +130,3 @@ :param host_machine: Host machine override seen by cross-python at | ||
| manylinux_tags=(), | ||
| platform_tags=(), | ||
| host_machine=None): | ||
@@ -156,2 +160,3 @@ self.host_sysroot = host_sysroot | ||
| self.manylinux_tags = manylinux_tags | ||
| self.platform_tags = platform_tags | ||
| self.host_machine = host_machine | ||
@@ -162,3 +167,3 @@ | ||
| self.get_uname_info() | ||
| self.expand_manylinux_tags() | ||
| self.expand_platform_tags() | ||
@@ -279,3 +284,4 @@ super().__init__( | ||
| self.host_home = self.find_installed_host_home() | ||
| python_ver = 'python' + sysconfig.get_config_var('py_version_short') | ||
| py_thread = 't' if sysconfig.get_config_var('Py_GIL_DISABLED') else '' | ||
| python_ver = 'python' + sysconfig.get_config_var('py_version_short') + py_thread | ||
| libdir = os.path.join(self.host_home, 'lib', python_ver) | ||
@@ -510,6 +516,11 @@ sysconfig_paths = [ | ||
| if self.host_machine is None: | ||
| if len(host_info) > 1 and host_info[-1] == "powerpc64le": | ||
| platform2uname = { | ||
| # On uname.machine=ppc64, _PYTHON_HOST_PLATFORM is linux-powerpc64 | ||
| "powerpc64": "ppc64", | ||
| # On uname.machine=ppc64le, _PYTHON_HOST_PLATFORM is linux-powerpc64le | ||
| "powerpc64le": "ppc64le", | ||
| } | ||
| if len(host_info) > 1 and host_info[-1] in platform2uname: | ||
| # Test that this is still a special case when we can. | ||
| # On uname.machine=ppc64le, _PYTHON_HOST_PLATFORM is linux-powerpc64le | ||
| self.host_machine = "ppc64le" | ||
| self.host_machine = platform2uname[host_info[-1]] | ||
| else: | ||
@@ -544,3 +555,3 @@ self.host_machine = self.host_gnu_type.split('-')[0] | ||
| def expand_manylinux_tags(self): | ||
| def expand_platform_tags(self): | ||
| """ | ||
@@ -551,3 +562,4 @@ Convert legacy manylinux tags to PEP600, because pip only looks for one | ||
| manylinux_tags = set(self.manylinux_tags) | ||
| platform_tags = set(self.platform_tags) | ||
| platform_tags.update(self.manylinux_tags) | ||
| extra_tags = set() | ||
@@ -558,26 +570,31 @@ effective_glibc = None | ||
| # manylinux1 and so on. | ||
| if 'manylinux1' in manylinux_tags: | ||
| if 'manylinux1' in platform_tags: | ||
| extra_tags.add('manylinux_2_5') | ||
| effective_glibc = (2, 5) | ||
| if 'manylinux2010' in manylinux_tags: | ||
| if 'manylinux2010' in platform_tags: | ||
| extra_tags.add('manylinux_2_12') | ||
| effective_glibc = (2, 12) | ||
| if 'manylinux2014' in manylinux_tags: | ||
| if 'manylinux2014' in platform_tags: | ||
| extra_tags.add('manylinux_2_17') | ||
| effective_glibc = (2, 17) | ||
| if 'manylinux_2_5' in manylinux_tags: | ||
| if 'manylinux_2_5' in platform_tags: | ||
| extra_tags.add('manylinux1') | ||
| if 'manylinux_2_12' in manylinux_tags: | ||
| if 'manylinux_2_12' in platform_tags: | ||
| extra_tags.add('manylinux2010') | ||
| if 'manylinux_2_17' in manylinux_tags: | ||
| if 'manylinux_2_17' in platform_tags: | ||
| extra_tags.add('manylinux2014') | ||
| manylinux_tags.update(extra_tags) | ||
| self.manylinux_tags = manylinux_tags | ||
| platform_tags.update(extra_tags) | ||
| self.platform_tags = platform_tags | ||
| for tag in manylinux_tags: | ||
| # Retain the spell check from earlier versions just in case | ||
| for tag in self.manylinux_tags: | ||
| # I know *I* mistype it alot. | ||
| if not re.search(r'manylinux', tag): | ||
| logger.warning("Tag %r does not contain 'manylinux'") | ||
| if 'manylinux' not in tag: | ||
| logger.warning("Tag %r does not contain 'manylinux'" % tag) | ||
| # This is for patching confstr() in a few places where pip, setuptools, | ||
| # and packaging try to get the glibc version. Only glibc reports its | ||
| # version this way, so this is not necessary for, e.g., musl. | ||
| for tag in self.platform_tags: | ||
| m = re.match(r'manylinux_(\d+)_(\d+)', tag) | ||
@@ -590,3 +607,2 @@ if not m: | ||
| self.effective_glibc = effective_glibc | ||
@@ -783,2 +799,3 @@ | ||
| 'importlib-machinery-patch.py', | ||
| 'importlib-metadata-patch.py', | ||
| 'platform-patch.py', | ||
@@ -788,2 +805,3 @@ 'sysconfig-patch.py', | ||
| 'pkg_resources-patch.py', | ||
| 'packaging-tags-patch.py', | ||
| ] | ||
@@ -1050,3 +1068,8 @@ | ||
| enable pre-compiled wheels. This argument may be given multiple | ||
| times.""") | ||
| times. This is identical to --platform-tag and is retained for | ||
| compatibility only.""") | ||
| parser.add_argument('--platform-tag', action='append', default=[], | ||
| help="""Declare compatibility with the given platform tag to enable | ||
| pre-compiled wheels. This argument may be given multiple | ||
| times. Examples include manylinux_2_17 and musllinux_1_2.""") | ||
| parser.add_argument('--machine', action='store', | ||
@@ -1098,2 +1121,3 @@ help="""Override the value of os.uname().machine if cross-python is | ||
| manylinux_tags=args.manylinux, | ||
| platform_tags=args.platform_tag, | ||
| host_machine=args.machine, | ||
@@ -1100,0 +1124,0 @@ ) |
@@ -105,2 +105,3 @@ # Import only what we absolutely need before the path fixup | ||
| 'importlib.machinery': '{{context.lib_path}}/importlib-machinery-patch.py', | ||
| 'importlib.metadata': '{{context.lib_path}}/importlib-metadata-patch.py', | ||
| 'sys': '{{context.lib_path}}/sys-patch.py', | ||
@@ -114,2 +115,3 @@ 'os': '{{context.lib_path}}/os-patch.py', | ||
| 'pip._vendor.pkg_resources': '{{context.lib_path}}/pkg_resources-patch.py', | ||
| 'packaging.tags': '{{context.lib_path}}/packaging-tags-patch.py', | ||
| } | ||
@@ -116,0 +118,0 @@ |
+1
-1
| Metadata-Version: 2.1 | ||
| Name: crossenv | ||
| Version: 1.4.0 | ||
| Version: 1.5.0 | ||
| Summary: A cross-compiling tool for Python extension modules | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/benfogle/crossenv |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
123809
25.55%33
32%1667
50.72%