diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e91e720d..bd3ae875 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -112,7 +112,7 @@ jobs: - { spec: cp314-manylinux_aarch64, arch: aarch64 } - { spec: cp314t-manylinux_aarch64, arch: aarch64 } - # aarch64 musllinux + # aarch64 musllinux - { spec: cp39-musllinux_aarch64, arch: aarch64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp310-musllinux_aarch64, arch: aarch64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp311-musllinux_aarch64, arch: aarch64, omit: ${{ env.skip_ci_redundant_jobs }} } @@ -379,8 +379,111 @@ jobs: if-no-files-found: error if: ${{ env.skip_artifact_upload != 'true' }} + make_ios_matrix: + runs-on: ubuntu-24.04 + outputs: + matrix_json: ${{ steps.make_matrix.outputs.matrix_json }} + steps: + - uses: actions/checkout@v4 + - name: make a matrix + id: make_matrix + uses: ./.github/actions/dynamatrix + with: + matrix_yaml: | + include: + # arm64 iOS device + - { spec: cp313-ios_arm64_iphoneos, platform: 'iphoneos' } + - { spec: cp314-ios_arm64_iphoneos, platform: 'iphoneos' } + + # arm64 iOS simulator + - { spec: cp313-ios_arm64_iphonesimulator, platform: 'iphonesimulator' } + - { spec: cp314-ios_arm64_iphonesimulator, platform: 'iphonesimulator' } + + ios: + needs: [python_sdist, make_ios_matrix] + runs-on: macos-14 + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.make_ios_matrix.outputs.matrix_json) }} + + steps: + - name: fetch sdist artifact + id: fetch_sdist + uses: actions/download-artifact@v4 + with: + name: ${{ needs.python_sdist.outputs.sdist_artifact_name }} + + - name: install python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: build wheel prereqs + run: | + set -eux + python3 -m pip install --user --upgrade cibuildwheel>=3.1.0 + brew uninstall --ignore-dependencies libffi 2>&1 || true + + - name: download libffi for iOS + env: + CFFI_IOS_LIBFFI_VERSION: '3.4.7-2' + run: | + set -eux + + # Download prebuilt libffi from beeware/cpython-apple-source-deps + platform="${{ matrix.platform }}" + version="${CFFI_IOS_LIBFFI_VERSION}" + url="https://github.com/beeware/cpython-apple-source-deps/releases/download/libFFI-${version}/libffi-${version}-${platform}.arm64.tar.gz" + + echo "Downloading libffi for iOS (${platform})..." + curl -L -o libffi-ios.tar.gz "${url}" + + # Extract libffi + mkdir -p libffi-ios + tar zxf libffi-ios.tar.gz -C libffi-ios + + # Set up paths for cibuildwheel + echo "LIBFFI_IOS_DIR=$(pwd)/libffi-ios" >> "$GITHUB_ENV" + + - name: build/test wheels + id: build + env: + CIBW_BUILD: ${{ matrix.spec }} + CIBW_ENABLE: cpython-prerelease + CIBW_PLATFORM: ios + SDKROOT: ${{ matrix.platform }} + CIBW_TEST_REQUIRES: pytest setuptools + CIBW_TEST_SOURCES: cffi + # Running tests from `testing/` will not work since they try to compile C code on device + CIBW_TEST_COMMAND: python -m pytest -sv cffi/src/c/ + # Environment variables for the build + CIBW_ENVIRONMENT: > + CFLAGS="-I${LIBFFI_IOS_DIR}/include" + LDFLAGS="-L${LIBFFI_IOS_DIR}/lib" + PYTHONUNBUFFERED=1 + # Pass through our custom env vars + CIBW_ENVIRONMENT_PASS_IOS: LIBFFI_IOS_DIR PYTHONUNBUFFERED + run: | + set -eux + + mkdir cffi + + tar zxf ${{ steps.fetch_sdist.outputs.download-path }}/cffi*.tar.gz --strip-components=1 -C cffi + + python3 -m cibuildwheel --output-dir dist cffi + + echo "artifact_name=$(ls ./dist/)" >> "$GITHUB_OUTPUT" + + - name: upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.build.outputs.artifact_name }} + path: dist/*.whl + if-no-files-found: error + if: ${{ env.skip_artifact_upload != 'true' }} + merge_artifacts: - needs: [python_sdist, linux, macos, windows] + needs: [python_sdist, linux, macos, windows, ios] runs-on: ubuntu-24.04 steps: - name: merge all artifacts @@ -456,7 +559,7 @@ jobs: check: if: always() - needs: [python_sdist, linux, macos, windows, clang_TSAN, pytest-run-parallel, merge_artifacts] + needs: [python_sdist, linux, macos, windows, ios, clang_TSAN, pytest-run-parallel, merge_artifacts] runs-on: ubuntu-24.04 steps: - name: Verify all previous jobs succeeded (provides a single check to sample for gating purposes) diff --git a/src/c/test_c.py b/src/c/test_c.py index 1ba62c21..74ced8b9 100644 --- a/src/c/test_c.py +++ b/src/c/test_c.py @@ -17,6 +17,9 @@ except ImportError: pass +is_ios = sys.platform == 'ios' + + def _setup_path(): import os, sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) @@ -1230,6 +1233,11 @@ def test_cannot_pass_struct_with_array_of_length_0(): BFunc2 = new_function_type((BInt,), BStruct, False) pytest.raises(NotImplementedError, cast(BFunc2, 123), 123) +@pytest.mark.xfail( + is_ios, + reason="For an unknown reason f(1, cast(BInt, 42)) returns 36792864", + raises=AssertionError, +) def test_call_function_9(): BInt = new_primitive_type("int") BFunc9 = new_function_type((BInt,), BInt, True) # vararg @@ -1362,6 +1370,7 @@ def test_write_variable(): pytest.raises(ValueError, ll.write_variable, BVoidP, "stderr", stderr) +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback(): BInt = new_primitive_type("int") def make_callback(): @@ -1378,6 +1387,7 @@ def cb(n): assert str(e.value) == "'int(*)(int)' expects 1 arguments, got 0" +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") @pytest.mark.thread_unsafe("mocks sys.unraiseablehook") def test_callback_exception(): def check_value(x): @@ -1435,6 +1445,7 @@ def oops(*args): assert ff(bigvalue) == -42 +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_return_type(): for rettype in ["signed char", "short", "int", "long", "long long", "unsigned char", "unsigned short", "unsigned int", @@ -1455,6 +1466,7 @@ def cb(n): assert f(max - 1) == max assert f(max) == 42 +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_a_lot_of_callbacks(): BIGNUM = 10000 if 'PY_DOT_PY' in globals(): BIGNUM = 100 # tests on py.py @@ -1470,6 +1482,7 @@ def cb(n): for i, f in enumerate(flist): assert f(-142) == -142 + i +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_receiving_tiny_struct(): BSChar = new_primitive_type("signed char") BInt = new_primitive_type("int") @@ -1485,6 +1498,7 @@ def cb(s): n = f(p[0]) assert n == -42 +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_returning_tiny_struct(): BSChar = new_primitive_type("signed char") BInt = new_primitive_type("int") @@ -1502,6 +1516,7 @@ def cb(n): assert s.a == -10 assert s.b == -30 +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_receiving_struct(): BSChar = new_primitive_type("signed char") BInt = new_primitive_type("int") @@ -1518,6 +1533,7 @@ def cb(s): n = f(p[0]) assert n == 42 +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_returning_struct(): BSChar = new_primitive_type("signed char") BInt = new_primitive_type("int") @@ -1537,6 +1553,7 @@ def cb(n): assert s.a == -10 assert s.b == 1E-42 +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_receiving_big_struct(): BInt = new_primitive_type("int") BStruct = new_struct_type("struct foo") @@ -1561,6 +1578,7 @@ def cb(s): n = f(p[0]) assert n == 42 +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_returning_big_struct(): BInt = new_primitive_type("int") BStruct = new_struct_type("struct foo") @@ -1586,6 +1604,7 @@ def cb(): for i, name in enumerate("abcdefghij"): assert getattr(s, name) == 13 - i +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_returning_void(): BVoid = new_void_type() BFunc = new_function_type((), BVoid, False) @@ -1694,6 +1713,7 @@ def test_enum_overflow(): pytest.raises(OverflowError, new_enum_type, "foo", ("AA",), (testcase,), BPrimitive) +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_returning_enum(): BInt = new_primitive_type("int") BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20), BInt) @@ -1710,6 +1730,7 @@ def cb(n): assert f(20) == 20 assert f(21) == 21 +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_returning_enum_unsigned(): BInt = new_primitive_type("int") BUInt = new_primitive_type("unsigned int") @@ -1727,6 +1748,7 @@ def cb(n): assert f(20) == 20 assert f(21) == 21 +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_returning_char(): BInt = new_primitive_type("int") BChar = new_primitive_type("char") @@ -1741,6 +1763,7 @@ def _hacked_pypy_uni4(): pyuni4 = {1: True, 2: False}[len(u+'\U00012345')] return 'PY_DOT_PY' in globals() and not pyuni4 +@pytest.mark.skipif(is_ios, reason="Cannot allocate executable memory on iOS") def test_callback_returning_wchar_t(): BInt = new_primitive_type("int") BWChar = new_primitive_type("wchar_t") @@ -2310,6 +2333,8 @@ def _test_wchar_variant(typename): assert str(q) == repr(q) pytest.raises(RuntimeError, string, q) # + if is_ios: + return # cannot allocate executable memory for the callback() below def cb(p): assert repr(p).startswith("