diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 36e1f17d..81d8bf8b 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -744,6 +744,36 @@ def __init__( self._meson_cross_file.write_text(cross_file_data, encoding='utf-8') self._meson_args['setup'].extend(('--cross-file', os.fspath(self._meson_cross_file))) + # Android may be native-compiled (e.g. on Termux), or cross-compiled with + # cibuildwheel. In the latter case, synthesize the appropriate cross file. + elif ( + sysconfig.get_platform().startswith('android-') + and 'CIBW_HOST_TRIPLET' in os.environ + ): + cpu = platform.machine() + cpu_family = ( + 'arm' if cpu.startswith('arm') else 'x86' if cpu.endswith('86') else cpu + ) + + cross_file_data = textwrap.dedent(f''' + # Binaries are controlled by environment variables, so they don't need + # to be repeated here. + [host_machine] + system = 'android' + subsystem = 'android' + kernel = 'linux' + cpu_family = {cpu_family!r} + cpu = {cpu!r} + endian = {sys.byteorder!r} + + [properties] + # cibuildwheel's cross virtual environment will make Meson believe it's + # running on Android when it's actually running on Linux or macOS. + needs_exe_wrapper = true + ''') + self._meson_cross_file.write_text(cross_file_data, encoding='utf-8') + self._meson_args['setup'].extend(('--cross-file', os.fspath(self._meson_cross_file))) + # Support iOS targets. iOS does not have native build tools and always # requires cross compilation: synthesize the appropriate cross file. elif sysconfig.get_platform().startswith('ios-'): diff --git a/tests/test_project.py b/tests/test_project.py index b511b217..10f5a47c 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -378,6 +378,48 @@ def test_archflags_envvar_parsing_invalid(package_purelib_and_platlib, monkeypat os.environ.pop('_PYTHON_HOST_PLATFORM', None) +@pytest.mark.parametrize( + ('cpu', 'cpu_family'), + [ + ('aarch64', 'aarch64'), + ('armv7l', 'arm'), + ('armv8l', 'arm'), + ('i686', 'x86'), + ('x86_64', 'x86_64'), + ], +) +@pytest.mark.parametrize('cross', [True, False]) +def test_android_project(package_simple, monkeypatch, tmp_path, cpu, cpu_family, cross): + # Mock being on Android + monkeypatch.setattr(sys, 'platform', 'android') + monkeypatch.setattr(sys, 'byteorder', 'little') + monkeypatch.setattr(platform, 'machine', Mock(return_value=cpu)) + monkeypatch.setattr(sysconfig, 'get_platform', Mock(return_value='android-24')) + if cross: + monkeypatch.setenv('CIBW_HOST_TRIPLET', f'{cpu}-linux-android') + monkeypatch.setenv('STRIP', '/path/to/strip') + + # Create a project. + project = mesonpy.Project(source_dir=package_simple, build_dir=tmp_path) + + # When cross-compiling, a cross file should be generated and used. + setup_args = project._meson_args['setup'] + cross_path = tmp_path / 'meson-python-cross-file.ini' + if cross: + assert setup_args[-2:] == ['--cross-file', str(cross_path)] + cross_config = cross_path.read_text().splitlines() + assert "system = 'android'" in cross_config + assert "subsystem = 'android'" in cross_config + assert "kernel = 'linux'" in cross_config + assert f"cpu_family = '{cpu_family}'" in cross_config + assert f"cpu = '{cpu}'" in cross_config + assert "endian = 'little'" in cross_config + assert 'needs_exe_wrapper = true' in cross_config + else: + assert '--cross-file' not in setup_args + assert not cross_path.exists() + + @pytest.mark.skipif(sys.version_info < (3, 13), reason='requires Python 3.13 or higher') @pytest.mark.parametrize('multiarch', [ 'arm64-iphoneos',