├── src └── forge │ ├── __init__.py │ ├── utils.py │ ├── logger.py │ ├── subprocess.py │ ├── pypi.py │ ├── schema │ └── meta-schema.yaml │ ├── package.py │ ├── __main__.py │ └── cross.py ├── recipes ├── fiona │ ├── test_fiona.py │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── gdal │ ├── test_gdal.py │ ├── meta.yaml │ └── patches │ │ └── config.patch ├── grpcio │ ├── test_grpcio.py │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── protobuf │ ├── test_protobuf.py │ └── meta.yaml ├── pyogrio │ ├── test_pyogrio.py │ └── meta.yaml ├── brotli │ ├── meta.yaml │ └── test_brotli.py ├── pyyaml │ └── meta.yaml ├── regex │ └── meta.yaml ├── aiohttp │ ├── meta.yaml │ └── test_aiohttp.py ├── bitarray │ ├── meta.yaml │ └── test_bitarray.py ├── lru-dict │ ├── meta.yaml │ └── test_lru_dict.py ├── msgpack │ └── meta.yaml ├── sqlalchemy │ └── meta.yaml ├── websockets │ └── meta.yaml ├── zstandard │ └── meta.yaml ├── markupsafe │ └── meta.yaml ├── time-machine │ └── meta.yaml ├── zope.interface │ └── meta.yaml ├── ruamel.yaml.clib │ └── meta.yaml ├── argon2-cffi-bindings │ ├── meta.yaml │ └── test_argon2_cffi.py ├── pillow │ ├── test │ │ ├── Vera.ttf │ │ ├── mandrill.jpg │ │ └── test_pillow.py │ ├── patches │ │ ├── setup-11.x.patch │ │ └── setup-10.x.patch │ └── meta.yaml ├── pyxirr │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── yarl │ ├── meta.yaml │ └── test_yarl.py ├── pycryptodome │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── pycryptodomex │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── msgspec │ └── meta.yaml ├── opaque │ └── meta.yaml ├── pysodium │ └── meta.yaml ├── bcrypt │ ├── meta.yaml │ └── test_bcrypt.py ├── cffi │ ├── meta.yaml │ ├── test_cffi.py │ └── patches │ │ └── mobile.patch ├── tiktoken │ └── meta.yaml ├── blis │ ├── meta.yaml │ ├── test_blis.py │ └── patches │ │ └── mobile.patch ├── pyobjus │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── tokenizers │ └── meta.yaml ├── pydantic-core │ └── meta.yaml ├── pyjnius │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── pynacl │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── greenlet │ └── meta.yaml ├── flet-libcpp-shared │ ├── meta.yaml │ └── build.sh ├── flet-libxml2 │ ├── meta.yaml │ ├── build.sh │ └── patches │ │ └── mobile.patch ├── jq │ └── meta.yaml ├── kiwisolver │ ├── meta.yaml │ └── test.py ├── shapely │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── flet-liboprf │ ├── build.sh │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── flet-libgeos │ ├── meta.yaml │ └── build.sh ├── flet-libpng │ ├── meta.yaml │ ├── build.sh │ └── patches │ │ └── config.patch ├── flet-libsodium │ ├── meta.yaml │ └── build.sh ├── flet-libcrc32c │ ├── meta.yaml │ └── build.sh ├── google-crc32c │ ├── meta.yaml │ └── test.py ├── flet-libxslt │ ├── meta.yaml │ ├── build.sh │ └── patches │ │ └── mobile.patch ├── cryptography │ ├── meta.yaml │ └── test_cryptography.py ├── flet-libopaque │ ├── build.sh │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── flet-libpyjni │ ├── meta.yaml │ └── build.sh ├── flet-libjpeg │ ├── meta.yaml │ └── build.sh ├── flet-libfreetype │ ├── meta.yaml │ ├── build.sh │ └── patches │ │ └── config.patch ├── flet-libpsl │ ├── meta.yaml │ ├── build.sh │ └── patches │ │ └── config.patch ├── flet-libtiff │ ├── build.sh │ ├── meta.yaml │ └── patches │ │ └── config.patch ├── flet-libjq │ ├── build.sh │ ├── meta.yaml │ └── patches │ │ └── config.patch ├── flet-libcurl │ ├── build.sh │ ├── patches │ │ └── config.patch │ └── meta.yaml ├── pandas │ ├── test_pandas.py │ ├── patches │ │ └── mobile.patch │ └── meta.yaml ├── pendulum │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── lxml │ ├── meta.yaml │ ├── test_lxml.py │ └── patches │ │ └── mobile.patch ├── pyproj │ ├── meta.yaml │ └── patches │ │ └── mobile.patch ├── flet-libgdal │ ├── meta.yaml │ └── build.sh ├── contourpy │ └── meta.yaml ├── flet-libproj │ ├── meta.yaml │ └── build.sh ├── matplotlib │ ├── meta.yaml │ └── test_matplotlib.py ├── numpy │ ├── test_numpy.py │ ├── patches │ │ ├── mobile-1.26.4.patch │ │ └── mobile-2.2.2.patch │ └── meta.yaml └── opencv-python │ ├── meta.yaml │ └── patches │ └── mobile.patch ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── package_request.yml │ ├── feature_request.yml │ └── bug_report.yml └── dependabot.yml ├── .gitignore ├── CONTRIBUTING.md ├── .ci ├── common.sh ├── install_ndk.sh ├── publish-wheels.py └── rebuild-simple-index.py ├── .flake8 ├── .pre-commit-config.yaml ├── LICENSE ├── pyproject.toml ├── make_dep_wheels.py ├── setup.sh ├── .appveyor.yml └── README.rst /src/forge/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /recipes/fiona/test_fiona.py: -------------------------------------------------------------------------------- 1 | # TBD -------------------------------------------------------------------------------- /recipes/gdal/test_gdal.py: -------------------------------------------------------------------------------- 1 | # TBD -------------------------------------------------------------------------------- /recipes/grpcio/test_grpcio.py: -------------------------------------------------------------------------------- 1 | # TBD -------------------------------------------------------------------------------- /recipes/protobuf/test_protobuf.py: -------------------------------------------------------------------------------- 1 | # TBD -------------------------------------------------------------------------------- /recipes/pyogrio/test_pyogrio.py: -------------------------------------------------------------------------------- 1 | # TBD -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /recipes/brotli/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: Brotli 3 | version: 1.1.0 4 | -------------------------------------------------------------------------------- /recipes/pyyaml/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: PyYAML 3 | version: 6.0.2 4 | -------------------------------------------------------------------------------- /recipes/regex/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: regex 3 | version: 2024.11.6 -------------------------------------------------------------------------------- /recipes/aiohttp/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: aiohttp 3 | version: 3.9.5 4 | -------------------------------------------------------------------------------- /recipes/bitarray/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: bitarray 3 | version: 2.9.2 4 | -------------------------------------------------------------------------------- /recipes/lru-dict/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: lru-dict 3 | version: 1.3.0 4 | -------------------------------------------------------------------------------- /recipes/msgpack/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: msgpack 3 | version: 1.1.0 4 | -------------------------------------------------------------------------------- /recipes/protobuf/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: protobuf 3 | version: 5.28.3 4 | -------------------------------------------------------------------------------- /recipes/sqlalchemy/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: sqlalchemy 3 | version: 2.0.36 -------------------------------------------------------------------------------- /recipes/websockets/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: websockets 3 | version: 13.0.1 -------------------------------------------------------------------------------- /recipes/zstandard/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: zstandard 3 | version: 0.23.0 -------------------------------------------------------------------------------- /recipes/markupsafe/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: MarkupSafe 3 | version: 2.1.5 4 | -------------------------------------------------------------------------------- /recipes/time-machine/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: time-machine 3 | version: 2.16.0 -------------------------------------------------------------------------------- /recipes/zope.interface/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: zope.interface 3 | version: '7.2' -------------------------------------------------------------------------------- /recipes/ruamel.yaml.clib/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: ruamel.yaml.clib 3 | version: 0.2.12 -------------------------------------------------------------------------------- /recipes/argon2-cffi-bindings/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: argon2-cffi-bindings 3 | version: 21.2.0 4 | -------------------------------------------------------------------------------- /recipes/pillow/test/Vera.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flet-dev/mobile-forge/HEAD/recipes/pillow/test/Vera.ttf -------------------------------------------------------------------------------- /recipes/pyxirr/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pyxirr 3 | version: 0.10.6 4 | 5 | patches: 6 | - mobile.patch -------------------------------------------------------------------------------- /recipes/pillow/test/mandrill.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flet-dev/mobile-forge/HEAD/recipes/pillow/test/mandrill.jpg -------------------------------------------------------------------------------- /recipes/yarl/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: yarl 3 | version: 1.11.1 4 | 5 | requirements: 6 | build: 7 | - cython -------------------------------------------------------------------------------- /recipes/pycryptodome/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pycryptodome 3 | version: 3.21.0 4 | 5 | patches: 6 | - mobile.patch -------------------------------------------------------------------------------- /recipes/pycryptodomex/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pycryptodomex 3 | version: 3.21.0 4 | 5 | patches: 6 | - mobile.patch -------------------------------------------------------------------------------- /recipes/msgspec/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: msgspec 3 | version: 0.18.6 4 | 5 | requirements: 6 | build: 7 | - setuptools ^69.5.1 -------------------------------------------------------------------------------- /recipes/opaque/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: opaque 3 | version: 0.2.0 4 | 5 | requirements: 6 | host: 7 | - flet-libopaque 0.99.8 -------------------------------------------------------------------------------- /recipes/pysodium/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pysodium 3 | version: 0.7.18 4 | 5 | requirements: 6 | host: 7 | - flet-libsodium 1.0.20 -------------------------------------------------------------------------------- /recipes/bcrypt/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: bcrypt 3 | version: 4.2.0 4 | 5 | build: 6 | script_env: 7 | _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' -------------------------------------------------------------------------------- /recipes/cffi/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: cffi 3 | version: 1.17.1 4 | 5 | requirements: 6 | host: 7 | - libffi 8 | 9 | patches: 10 | - mobile.patch -------------------------------------------------------------------------------- /recipes/tiktoken/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: tiktoken 3 | version: 0.9.0 4 | 5 | build: 6 | script_env: 7 | _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' -------------------------------------------------------------------------------- /recipes/blis/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: blis 3 | version: 1.0.0 4 | 5 | requirements: 6 | host: 7 | - numpy ^2.0.0 8 | 9 | patches: 10 | - mobile.patch -------------------------------------------------------------------------------- /recipes/pyobjus/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pyobjus 3 | version: 1.2.3 4 | 5 | patches: 6 | - mobile.patch 7 | 8 | requirements: 9 | host: 10 | - libffi -------------------------------------------------------------------------------- /recipes/tokenizers/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: tokenizers 3 | version: 0.21.0 4 | 5 | build: 6 | script_env: 7 | _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' -------------------------------------------------------------------------------- /recipes/pydantic-core/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pydantic-core 3 | version: 2.29.0 4 | 5 | build: 6 | script_env: 7 | _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' -------------------------------------------------------------------------------- /recipes/pyjnius/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pyjnius 3 | version: 1.6.1 4 | 5 | patches: 6 | - mobile.patch 7 | 8 | requirements: 9 | host: 10 | - flet-libpyjni 1.0.1 -------------------------------------------------------------------------------- /recipes/pynacl/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: PyNaCl 3 | version: 1.5.0 4 | 5 | requirements: 6 | host: 7 | - flet-libsodium 1.0.20 8 | 9 | patches: 10 | - mobile.patch -------------------------------------------------------------------------------- /recipes/greenlet/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: greenlet 3 | version: 3.1.1 4 | 5 | # {% if sdk != 'android' %} 6 | build: 7 | script_env: 8 | CXXFLAGS: -std=c++14 9 | # {% endif %} -------------------------------------------------------------------------------- /recipes/flet-libcpp-shared/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: flet-libcpp-shared 3 | version: 27.2.12479018 4 | 5 | source: 6 | url: https://github.com/flet-dev/awesome-flet/archive/refs/heads/main.zip -------------------------------------------------------------------------------- /recipes/flet-libxml2/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: flet-libxml2 3 | version: 2.9.8 4 | 5 | source: 6 | url: http://xmlsoft.org/download/libxml2-2.9.8.tar.gz 7 | 8 | patches: 9 | - mobile.patch -------------------------------------------------------------------------------- /recipes/jq/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: jq 3 | version: 1.8.0 4 | 5 | requirements: 6 | host: 7 | - flet-libjq 1.7.1 8 | 9 | build: 10 | script_env: 11 | JQPY_USE_SYSTEM_LIBS: 1 -------------------------------------------------------------------------------- /recipes/kiwisolver/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: kiwisolver 3 | version: 1.4.7 4 | 5 | # {% if sdk == 'android' %} 6 | requirements: 7 | host: 8 | - flet-libcpp-shared 27.2.12479018 9 | # {% endif %} -------------------------------------------------------------------------------- /recipes/shapely/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: shapely 3 | version: 2.0.6 4 | 5 | requirements: 6 | host: 7 | - flet-libgeos 3.13.0 8 | - numpy ^2.0.0 9 | 10 | patches: 11 | - mobile.patch -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swo 2 | *.swp 3 | .envrc 4 | .vscode/ 5 | *.dist-info 6 | *.egg-info 7 | __pycache__ 8 | *.log 9 | *.DS_Store 10 | downloads/ 11 | dist/ 12 | venv*/ 13 | build/ 14 | local/ 15 | published/ 16 | tools/ 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | BeeWare <3's contributions! 4 | 5 | Please be aware, BeeWare operates under a Code of Conduct. 6 | 7 | See [CONTRIBUTING to BeeWare](https://beeware.org/contributing) for details. 8 | -------------------------------------------------------------------------------- /recipes/cffi/test_cffi.py: -------------------------------------------------------------------------------- 1 | def test_basic(): 2 | from cffi import FFI 3 | 4 | ffi = FFI() 5 | ffi.cdef("size_t strlen(char *str);") 6 | lib = ffi.dlopen(None) 7 | assert lib.strlen(ffi.new("char[]", b"hello world")) == 11 8 | -------------------------------------------------------------------------------- /recipes/flet-liboprf/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | cd src 5 | make -j $CPU_COUNT 6 | make install 7 | 8 | if [ $CROSS_VENV_SDK == "android" ]; then 9 | rm -r $PREFIX/lib/*.a 10 | else 11 | rm -r $PREFIX/lib/*.so 12 | fi -------------------------------------------------------------------------------- /.ci/common.sh: -------------------------------------------------------------------------------- 1 | function publish_to_pypi() { 2 | if [[ "$APPVEYOR_PULL_REQUEST_NUMBER" == "" ]]; then 3 | for wheel in "$@"; do 4 | curl -F package=@$wheel https://$GEMFURY_TOKEN@push.fury.io/flet/ 5 | done 6 | fi 7 | } -------------------------------------------------------------------------------- /recipes/yarl/test_yarl.py: -------------------------------------------------------------------------------- 1 | def test_basic(): 2 | from yarl import URL 3 | 4 | assert ( 5 | str(URL("http://εμπορικόσήμα.eu/путь/這裡")) 6 | == "http://xn--jxagkqfkduily1i.eu/%D0%BF%D1%83%D1%82%D1%8C/%E9%80%99%E8%A3%A1" 7 | ) 8 | -------------------------------------------------------------------------------- /recipes/flet-libgeos/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: flet-libgeos 3 | version: 3.13.0 4 | 5 | build: 6 | number: 1 7 | 8 | source: 9 | url: http://download.osgeo.org/geos/geos-3.13.0.tar.bz2 10 | 11 | requirements: 12 | build: 13 | - cmake -------------------------------------------------------------------------------- /recipes/flet-libpng/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: flet-libpng 3 | version: 1.6.43 4 | 5 | build: 6 | number: 1 7 | 8 | source: 9 | url: https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.43.tar.gz 10 | 11 | patches: 12 | - config.patch -------------------------------------------------------------------------------- /recipes/lru-dict/test_lru_dict.py: -------------------------------------------------------------------------------- 1 | def test_basic(): 2 | from lru import LRU 3 | 4 | data = LRU(3) 5 | data[1] = None 6 | data[2] = None 7 | data[3] = None 8 | data[1] 9 | data[4] = None 10 | assert data.keys == [4, 1, 3] 11 | -------------------------------------------------------------------------------- /recipes/bcrypt/test_bcrypt.py: -------------------------------------------------------------------------------- 1 | def test_basic(self): 2 | import bcrypt 3 | 4 | hashed = b"$2b$12$9cwzD/MRnVT7uvkxAQvkIejrif4bwRTGvIRqO7xf4OYtDQ3sl8CWW" 5 | assert bcrypt.checkpw(b"password", hashed) 6 | assert not bcrypt.checkpw(b"passwerd", hashed) 7 | -------------------------------------------------------------------------------- /recipes/flet-libsodium/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set version = "1.0.20" %} 2 | 3 | package: 4 | name: flet-libsodium 5 | version: '{{ version }}' 6 | 7 | source: 8 | url: https://github.com/jedisct1/libsodium/releases/download/{{ version }}-RELEASE/libsodium-{{ version }}.tar.gz -------------------------------------------------------------------------------- /recipes/flet-libcrc32c/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: flet-libcrc32c 3 | version: 1.1.2 4 | 5 | build: 6 | number: 1 7 | 8 | source: 9 | url: https://github.com/google/crc32c/archive/refs/tags/1.1.2.tar.gz 10 | 11 | requirements: 12 | build: 13 | - cmake -------------------------------------------------------------------------------- /recipes/google-crc32c/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: google-crc32c 3 | version: 1.6.0 4 | 5 | requirements: 6 | host: 7 | - flet-libcrc32c 1.1.2 8 | 9 | # {% if sdk != 'android' %} 10 | build: 11 | script_env: 12 | LDFLAGS: '-lc++' 13 | # {% endif %} -------------------------------------------------------------------------------- /recipes/flet-libxslt/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: flet-libxslt 3 | version: 1.1.32 4 | 5 | source: 6 | url: http://xmlsoft.org/download/libxslt-1.1.32.tar.gz 7 | 8 | requirements: 9 | host: 10 | - flet-libxml2 2.9.8 11 | 12 | patches: 13 | - mobile.patch -------------------------------------------------------------------------------- /recipes/cryptography/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: cryptography 3 | version: 43.0.1 4 | 5 | requirements: 6 | host: 7 | - openssl ^3.0.12 8 | 9 | build: 10 | script_env: 11 | OPENSSL_DIR: '{platlib}/opt' 12 | _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' -------------------------------------------------------------------------------- /recipes/flet-libopaque/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | cd src 5 | make -j $CPU_COUNT 6 | make install 7 | 8 | rm -r $PREFIX/bin 9 | rm -r $PREFIX/lib/*.a 10 | 11 | if [ $CROSS_VENV_SDK != "android" ]; then 12 | mv $PREFIX/lib/libopaque.so $PREFIX/../libopaque.so 13 | fi -------------------------------------------------------------------------------- /recipes/flet-libpyjni/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: flet-libpyjni 3 | version: 1.0.1 4 | 5 | build: 6 | number: 1 7 | 8 | source: 9 | url: https://github.com/flet-dev/libpyjni/releases/download/v1.0.1/pyjni-1.0.1.tar.gz 10 | 11 | requirements: 12 | build: 13 | - cmake -------------------------------------------------------------------------------- /recipes/flet-libjpeg/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: flet-libjpeg 3 | version: 3.0.90 4 | 5 | source: 6 | url: https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/3.0.90/libjpeg-turbo-3.0.90.tar.gz 7 | 8 | build: 9 | number: 1 10 | 11 | requirements: 12 | build: 13 | - cmake -------------------------------------------------------------------------------- /recipes/flet-libfreetype/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: flet-libfreetype 3 | version: 2.13.3 4 | 5 | build: 6 | number: 1 7 | 8 | source: 9 | url: https://download.savannah.gnu.org/releases/freetype/freetype-2.13.3.tar.gz 10 | 11 | patches: 12 | - config.patch 13 | 14 | about: 15 | license_file: docs/FTL.TXT 16 | -------------------------------------------------------------------------------- /recipes/blis/test_blis.py: -------------------------------------------------------------------------------- 1 | def test_einsum(): 2 | import numpy as np 3 | from blis.py import einsum 4 | 5 | a = np.array([[1.0, 2.0], [3.0, 4.0]]) 6 | b = np.array([[2.0, 3.0], [5.0, 7.0]]) 7 | np.testing.assert_equal( 8 | np.array([[12.0, 17.0], [26.0, 37.0]]), einsum("ab,bc->ac", a, b) 9 | ) 10 | -------------------------------------------------------------------------------- /recipes/flet-libpsl/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set version = "0.21.5" %} 2 | 3 | package: 4 | name: flet-libpsl 5 | version: '{{ version }}' 6 | 7 | source: 8 | url: https://github.com/rockdaboot/libpsl/releases/download/{{ version }}/libpsl-{{ version }}.tar.gz 9 | 10 | build: 11 | number: 1 12 | 13 | patches: 14 | - config.patch -------------------------------------------------------------------------------- /recipes/flet-libtiff/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | ./configure --host=$HOST_TRIPLET --prefix=$PREFIX --disable-docs 5 | make -j $CPU_COUNT 6 | make install 7 | 8 | rm -r $PREFIX/bin 9 | rm -rf $PREFIX/lib/{*.la,*xx.*,pkgconfig} 10 | 11 | if [ $CROSS_VENV_SDK == "android" ]; then 12 | rm -r $PREFIX/lib/*.a 13 | fi -------------------------------------------------------------------------------- /recipes/brotli/test_brotli.py: -------------------------------------------------------------------------------- 1 | def test_basic(): 2 | import brotli 3 | 4 | plain = b"it was the best of times, it was the worst of times" 5 | compressed = brotli.compress(plain) 6 | 7 | compressed = compressed.hex() 8 | assert len(compressed) < len(plain) 9 | assert plain == brotli.decompress(compressed) 10 | -------------------------------------------------------------------------------- /recipes/flet-libjq/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | ./configure --host=$HOST_TRIPLET --prefix=$PREFIX --with-oniguruma=builtin 5 | make -j $CPU_COUNT 6 | make install 7 | 8 | rm -r $PREFIX/{bin,share} 9 | rm -r $PREFIX/lib/{*.la,pkgconfig} 10 | 11 | if [ $CROSS_VENV_SDK == "android" ]; then 12 | rm -r $PREFIX/lib/*.a 13 | fi -------------------------------------------------------------------------------- /recipes/flet-libcurl/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | ./configure --host=$HOST_TRIPLET --prefix=$PREFIX --with-openssl=$PLATLIB/opt 5 | make -j $CPU_COUNT 6 | make install 7 | 8 | rm -r $PREFIX/{bin,share} 9 | rm -r $PREFIX/lib/{*.la,pkgconfig} 10 | 11 | if [ $CROSS_VENV_SDK == "android" ]; then 12 | rm -r $PREFIX/lib/*.a 13 | fi -------------------------------------------------------------------------------- /recipes/flet-libxml2/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | ./configure --host=$HOST_TRIPLET --prefix=$PREFIX --without-python 5 | make -j $CPU_COUNT 6 | make install 7 | 8 | mv $PREFIX/include/libxml2/libxml $PREFIX/include 9 | rm -r $PREFIX/include/libxml2 10 | 11 | rm -r $PREFIX/share 12 | rm -r $PREFIX/lib/{cmake,pkgconfig,*.a,*.la,*.sh} -------------------------------------------------------------------------------- /recipes/pandas/test_pandas.py: -------------------------------------------------------------------------------- 1 | def test_basic(): 2 | from pandas import DataFrame 3 | 4 | df = DataFrame( 5 | [("alpha", 1), ("bravo", 2), ("charlie", 3)], 6 | columns=["Letter", "Number"], 7 | ) 8 | assert df.to_csv() == ( 9 | ",Letter,Number\n" "0,alpha,1\n" "1,bravo,2\n" "2,charlie,3\n" 10 | ) 11 | -------------------------------------------------------------------------------- /recipes/pendulum/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pendulum 3 | version: 3.0.0 4 | 5 | build: 6 | script_env: 7 | _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' 8 | # {% if sdk == 'iphonesimulator' %} 9 | CFLAGS_aarch64-apple-ios-sim: "--target=arm64-apple-ios13.0-simulator" 10 | # {% endif %} 11 | 12 | patches: 13 | - mobile.patch -------------------------------------------------------------------------------- /recipes/flet-libcurl/patches/config.patch: -------------------------------------------------------------------------------- 1 | diff --git a/config.sub b/config.sub 2 | index dba16e8..215b8e2 100755 3 | --- a/config.sub 4 | +++ b/config.sub 5 | @@ -1792,6 +1792,8 @@ case $kernel-$os in 6 | ;; 7 | *-eabi* | *-gnueabi*) 8 | ;; 9 | + ios-simulator) 10 | + ;; 11 | -*) 12 | # Blank kernel with real OS is always fine. 13 | ;; 14 | -------------------------------------------------------------------------------- /recipes/flet-libpsl/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | ./configure --host=$HOST_TRIPLET --build=$BUILD_TRIPLET --prefix=$PREFIX --disable-runtime 5 | make -j $CPU_COUNT 6 | make install 7 | 8 | rm -r $PREFIX/{bin,share} 9 | rm -r $PREFIX/lib/{*.la,pkgconfig} 10 | 11 | if [ $CROSS_VENV_SDK == "android" ]; then 12 | rm -r $PREFIX/lib/*.a 13 | fi -------------------------------------------------------------------------------- /recipes/lxml/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: lxml 3 | version: 5.3.0 4 | 5 | build: 6 | script_env: 7 | WITH_XML2_CONFIG: '{platlib}/opt/bin/xml2-config' 8 | WITH_XSLT_CONFIG: '{platlib}/opt/bin/xslt-config' 9 | 10 | patches: 11 | - mobile.patch 12 | 13 | requirements: 14 | host: 15 | - flet-libxslt 1.1.32 16 | - flet-libxml2 2.9.8 -------------------------------------------------------------------------------- /recipes/pyxirr/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/pyproject.toml b/pyproject.toml 2 | index be1a7b7..7bda5b8 100644 3 | --- a/pyproject.toml 4 | +++ b/pyproject.toml 5 | @@ -1,4 +1,5 @@ 6 | [project] 7 | +version = "0.10.6" 8 | name = "pyxirr" 9 | description-content-type = "text/markdown; charset=UTF-8; variant=GFM" 10 | requires-python = ">=3.7,<3.14" 11 | -------------------------------------------------------------------------------- /recipes/flet-libtiff/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set version = "4.7.0" %} 2 | 3 | package: 4 | name: flet-libtiff 5 | version: '{{ version }}' 6 | 7 | source: 8 | url: https://download.osgeo.org/libtiff/tiff-{{ version }}.tar.gz 9 | 10 | build: 11 | number: 1 12 | 13 | requirements: 14 | host: 15 | - flet-libjpeg 3.0.90 16 | 17 | patches: 18 | - config.patch -------------------------------------------------------------------------------- /recipes/flet-libjq/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set version = "1.7.1" %} 2 | 3 | package: 4 | name: flet-libjq 5 | version: '{{ version }}' 6 | 7 | source: 8 | url: https://github.com/jqlang/jq/releases/download/jq-{{ version }}/jq-{{ version }}.tar.gz 9 | 10 | build: 11 | number: 1 12 | 13 | requirements: 14 | build: 15 | - cmake 16 | 17 | patches: 18 | - config.patch -------------------------------------------------------------------------------- /recipes/pyproj/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pyproj 3 | version: 3.7.0 4 | 5 | build: 6 | script_env: 7 | PROJ_VERSION: 9.5.0 8 | PROJ_DIR: '{platlib}/opt' 9 | # {% if sdk != 'android' %} 10 | LDFLAGS: '-undefined dynamic_lookup' 11 | # {% endif %} 12 | 13 | requirements: 14 | host: 15 | - flet-libproj 9.5.0 16 | 17 | patches: 18 | - mobile.patch -------------------------------------------------------------------------------- /recipes/flet-libcurl/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set version = "8.11.0" %} 2 | 3 | package: 4 | name: flet-libcurl 5 | version: '{{ version }}' 6 | 7 | source: 8 | url: https://curl.se/download/curl-{{ version }}.tar.gz 9 | 10 | build: 11 | number: 1 12 | 13 | requirements: 14 | host: 15 | - openssl 3.0.15 16 | - flet-libpsl 0.21.5 17 | 18 | patches: 19 | - config.patch -------------------------------------------------------------------------------- /recipes/flet-libgdal/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set version = "3.10.0" %} 2 | 3 | package: 4 | name: flet-libgdal 5 | version: '{{ version }}' 6 | 7 | source: 8 | url: https://github.com/OSGeo/gdal/releases/download/v{{ version }}/gdal-{{ version }}.tar.gz 9 | 10 | build: 11 | number: 1 12 | 13 | requirements: 14 | build: 15 | - cmake 16 | host: 17 | - flet-libproj 9.5.0 -------------------------------------------------------------------------------- /recipes/flet-libopaque/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set version = "0.99.8" %} 2 | 3 | package: 4 | name: flet-libopaque 5 | version: '{{ version }}' 6 | 7 | source: 8 | url: https://github.com/stef/libopaque/archive/refs/tags/v{{ version }}.tar.gz 9 | 10 | requirements: 11 | host: 12 | - flet-libsodium 1.0.20 13 | - flet-liboprf 0.5.0 14 | 15 | patches: 16 | - mobile.patch -------------------------------------------------------------------------------- /recipes/flet-libpsl/patches/config.patch: -------------------------------------------------------------------------------- 1 | diff --git a/build-aux/config.sub b/build-aux/config.sub 2 | index dba16e8..215b8e2 100755 3 | --- a/build-aux/config.sub 4 | +++ b/build-aux/config.sub 5 | @@ -1792,6 +1792,8 @@ case $kernel-$os in 6 | ;; 7 | *-eabi* | *-gnueabi*) 8 | ;; 9 | + ios-simulator) 10 | + ;; 11 | -*) 12 | # Blank kernel with real OS is always fine. 13 | ;; 14 | -------------------------------------------------------------------------------- /recipes/gdal/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: gdal 3 | version: 3.10.0 4 | 5 | requirements: 6 | host: 7 | - flet-libgdal 3.10.0 8 | 9 | build: 10 | script_env: 11 | GDAL_VERSION: 3.10.0 12 | GDAL_PREFIX: '{platlib}/opt' 13 | GDAL_CFLAGS: '' 14 | # {% if sdk != 'android' %} 15 | LDFLAGS: '-undefined dynamic_lookup' 16 | # {% endif %} 17 | 18 | patches: 19 | - config.patch -------------------------------------------------------------------------------- /recipes/pyogrio/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pyogrio 3 | version: 0.10.0 4 | 5 | requirements: 6 | host: 7 | - flet-libgdal 3.10.0 8 | 9 | build: 10 | script_env: 11 | GDAL_VERSION: 3.10.0 12 | GDAL_LIBRARY_PATH: '{platlib}/opt/lib' 13 | GDAL_INCLUDE_PATH: '{platlib}/opt/include' 14 | # {% if sdk != 'android' %} 15 | LDFLAGS: '-undefined dynamic_lookup' 16 | # {% endif %} -------------------------------------------------------------------------------- /recipes/flet-libcpp-shared/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | if [[ "$CROSS_VENV_SDK" != "android" ]]; then 5 | echo "This package can be built for Android only." 6 | exit 1 7 | fi 8 | 9 | toolchain=$(echo $NDK_ROOT/toolchains/llvm/prebuilt/*) 10 | export LIBC_SHARED_SO="$toolchain/sysroot/usr/lib/${HOST_TRIPLET}/libc++_shared.so" 11 | 12 | mkdir -p $PREFIX/lib 13 | cp $LIBC_SHARED_SO $PREFIX/lib 14 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | # flake8 doesn't allow for pyproject.toml configuration. 2 | [flake8] 3 | # https://flake8.readthedocs.org/en/latest/ 4 | exclude=\ 5 | venv*/*,\ 6 | local/*,\ 7 | docs/*,\ 8 | build/*,\ 9 | tests/apps/*,\ 10 | .eggs/*,\ 11 | .tox/* 12 | max-line-length = 119 13 | extend-ignore = 14 | # whitespace before : 15 | # See https://github.com/PyCQA/pycodestyle/issues/373 16 | E203, 17 | -------------------------------------------------------------------------------- /recipes/flet-liboprf/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set version = "0.5.0" %} 2 | 3 | package: 4 | name: flet-liboprf 5 | version: '{{ version }}' 6 | 7 | source: 8 | url: https://github.com/stef/liboprf/archive/refs/tags/v{{ version }}.tar.gz 9 | 10 | requirements: 11 | host: 12 | - flet-libsodium 1.0.20 13 | 14 | build: 15 | script_env: 16 | CFLAGS: '-Qunused-arguments -Wno-unreachable-code' 17 | 18 | patches: 19 | - mobile.patch -------------------------------------------------------------------------------- /recipes/contourpy/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: contourpy 3 | version: 1.3.1 4 | 5 | requirements: 6 | build: 7 | - ninja 8 | - cmake 9 | - git+https://github.com/flet-dev/meson@ios-dynamiclib 10 | host: 11 | - pybind11 12 | # {% if sdk == 'android' %} 13 | - flet-libcpp-shared 27.2.12479018 14 | # {% endif %} 15 | 16 | build: 17 | backend-args: 18 | - -Csetup-args=--cross-file 19 | - -Csetup-args={MESON_CROSS_FILE} -------------------------------------------------------------------------------- /recipes/fiona/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: fiona 3 | version: 1.10.1 4 | 5 | requirements: 6 | host: 7 | - flet-libgdal 3.10.0 8 | 9 | build: 10 | script_env: 11 | GDAL_VERSION: 3.10.0 12 | GDAL_LIB_PATH: '{platlib}/opt/lib' 13 | GDAL_INCLUDE_PATH: '{platlib}/opt/include' 14 | GDAL_LIBS: gdal 15 | # {% if sdk != 'android' %} 16 | LDFLAGS: '-undefined dynamic_lookup' 17 | # {% endif %} 18 | 19 | patches: 20 | - mobile.patch -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | # Check for updates on Sunday, 8PM UTC 8 | interval: "weekly" 9 | day: "sunday" 10 | time: "20:00" 11 | 12 | - package-ecosystem: "pip" 13 | directory: "/" 14 | schedule: 15 | # Check for updates on Sunday, 8PM UTC 16 | interval: "weekly" 17 | day: "sunday" 18 | time: "20:00" 19 | -------------------------------------------------------------------------------- /recipes/argon2-cffi-bindings/test_argon2_cffi.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | # See https://argon2-cffi.readthedocs.io/en/stable/ 5 | def test_basic(self): 6 | import argon2 7 | 8 | ph = argon2.PasswordHasher() 9 | hashed = ph.hash("s3kr3tp4ssw0rd") 10 | assert hashed.startswith("$argon2") 11 | assert ph.verify(hashed, "s3kr3tp4ssw0rd") 12 | with pytest.raises(argon2.exceptions.VerifyMismatchError): 13 | ph.verify(hashed, "s3kr3tp4sswOrd") 14 | -------------------------------------------------------------------------------- /recipes/flet-libxslt/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | export CFLAGS="-Wno-error=incompatible-function-pointer-types" 5 | export LIBS="-lxml2" 6 | 7 | ./configure --host=$HOST_TRIPLET --prefix=$PREFIX --without-crypto --without-python \ 8 | --with-libxml-include-prefix=$PLATLIB/opt/include \ 9 | --with-libxml-libs-prefix=$PLATLIB/opt/lib 10 | make -j $CPU_COUNT V=1 11 | make install 12 | 13 | rm -r $PREFIX/share 14 | rm -r $PREFIX/lib/{libxslt-*,pkgconfig,*.a,*.la,*.sh} -------------------------------------------------------------------------------- /recipes/flet-libtiff/patches/config.patch: -------------------------------------------------------------------------------- 1 | diff --git a/config/config.sub b/config/config.sub 2 | index 4aaae46..1692095 100755 3 | --- a/config/config.sub 4 | +++ b/config/config.sub 5 | @@ -2259,6 +2259,8 @@ case $kernel-$os-$obj in 6 | --*) 7 | # Blank kernel and OS with real machine code file format is always fine. 8 | ;; 9 | + ios-simulator*-) 10 | + ;; 11 | *-*-*) 12 | echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2 13 | exit 1 14 | -------------------------------------------------------------------------------- /recipes/flet-libproj/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set version = "9.5.0" %} 2 | 3 | package: 4 | name: flet-libproj 5 | version: '{{ version }}' 6 | 7 | source: 8 | url: https://download.osgeo.org/proj/proj-{{ version }}.tar.gz 9 | 10 | build: 11 | number: 1 12 | 13 | requirements: 14 | build: 15 | - cmake 16 | host: 17 | - flet-libtiff 4.7.0 18 | - flet-libcurl 8.11.0 19 | 20 | # {% if sdk != 'android' %} 21 | build: 22 | script_env: 23 | LDFLAGS: '-undefined dynamic_lookup' 24 | # {% endif %} -------------------------------------------------------------------------------- /recipes/lxml/test_lxml.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestLxml(unittest.TestCase): 5 | 6 | def test_basic(self): 7 | from lxml import etree 8 | 9 | parent = etree.fromstring( 10 | "" 11 | ) 12 | self.assertEqual("parent", parent.tag) 13 | self.assertEqual(2, len(parent)) 14 | self.assertEqual("one", parent[0].get("name")) 15 | self.assertEqual("two", parent[1].get("name")) 16 | -------------------------------------------------------------------------------- /recipes/bitarray/test_bitarray.py: -------------------------------------------------------------------------------- 1 | # See https://github.com/ilanschnell/bitarray/blob/master/README.md 2 | def test_basic(): 3 | from bitarray import bitarray 4 | 5 | a = bitarray() 6 | a.append(True) 7 | a.extend([False, True, True]) 8 | assert a == bitarray("1011") 9 | assert a != bitarray("1111") 10 | 11 | b = bitarray("1100") 12 | assert a & b == bitarray("1000") 13 | assert a | b == bitarray("1111") 14 | assert a ^ b == bitarray("0111") 15 | 16 | a[:] = True 17 | assert a == bitarray("1111") 18 | -------------------------------------------------------------------------------- /recipes/pendulum/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/rust/src/helpers.rs b/rust/src/helpers.rs 2 | index 364075a..8c5fbe8 100644 3 | --- a/rust/src/helpers.rs 4 | +++ b/rust/src/helpers.rs 5 | @@ -56,7 +56,7 @@ pub fn local_time( 6 | seconds -= (10957 * SECS_PER_DAY as usize) as isize; 7 | year += 30; // == 2000 8 | } else { 9 | - seconds += ((146_097 - 10957) * SECS_PER_DAY as usize) as isize; 10 | + seconds += ((146_097 - 10957) as u64 * SECS_PER_DAY as u64) as isize; 11 | year -= 370; // == 1600 12 | } 13 | -------------------------------------------------------------------------------- /recipes/flet-libxslt/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/config.sub b/config.sub 2 | index 7b334f9..a32642c 100755 3 | --- a/config.sub 4 | +++ b/config.sub 5 | @@ -316,7 +316,8 @@ case $basic_machine in 6 | | visium \ 7 | | we32k \ 8 | | x86 | xc16x | xstormy16 | xtensa \ 9 | - | z8k | z80) 10 | + | z8k | z80 \ 11 | + | arm64-apple | *-ios) 12 | basic_machine=$basic_machine-unknown 13 | ;; 14 | c54x) 15 | @@ -1539,7 +1540,7 @@ case $os in 16 | ;; 17 | -nacl*) 18 | ;; 19 | - -ios) 20 | + -ios | -simulator) 21 | ;; 22 | -none) 23 | ;; 24 | -------------------------------------------------------------------------------- /recipes/matplotlib/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: matplotlib 3 | version: 3.10.0 4 | 5 | requirements: 6 | build: 7 | - ninja 8 | - git+https://github.com/flet-dev/meson@ios-dynamiclib 9 | host: 10 | - numpy ^2.0.0 11 | - pybind11 12 | - flet-libjpeg 3.0.90 13 | 14 | build: 15 | # {% if sdk == 'android' and arch in ['armeabi-v7a', 'x86'] %} 16 | script_env: 17 | CPPFLAGS: -Wno-c++11-narrowing 18 | # {% endif %} 19 | backend-args: 20 | - -Csetup-args=--cross-file 21 | - -Csetup-args={MESON_CROSS_FILE} 22 | 23 | # potential issues: 24 | # https://github.com/godotengine/godot/pull/101036/files -------------------------------------------------------------------------------- /recipes/flet-libpyjni/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | mkdir build 5 | cd build 6 | 7 | if [ $CROSS_VENV_SDK == "android" ]; then 8 | cmake .. \ 9 | -DCMAKE_SYSTEM_NAME=Android \ 10 | -DANDROID_PLATFORM=$SDK_VERSION \ 11 | -DANDROID_ABI=$ANDROID_ABI \ 12 | -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ 13 | -DCMAKE_BUILD_TYPE=Release \ 14 | -DBUILD_SHARED_LIBS=1 \ 15 | -DCMAKE_INSTALL_PREFIX="$PREFIX" 16 | else 17 | echo "flet-libpyjni library can be built for Android only." 18 | exit 1 19 | fi 20 | 21 | make -j $CPU_COUNT 22 | make install -------------------------------------------------------------------------------- /recipes/pandas/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/pyproject.toml b/pyproject.toml 2 | index 238abd8..37de5c7 100644 3 | --- a/pyproject.toml 4 | +++ b/pyproject.toml 5 | @@ -2,8 +2,8 @@ 6 | # Minimum requirements for the build system to execute. 7 | # See https://github.com/scipy/scipy/pull/12940 for the AIX issue. 8 | requires = [ 9 | - "meson-python==0.13.1", 10 | - "meson==1.2.1", 11 | + "meson-python==0.15.0", 12 | + #"meson==1.2.1", 13 | "wheel", 14 | "Cython~=3.0.5", # Note: sync with setup.py, environment.yml and asv.conf.json 15 | # Force numpy higher than 2.0, so that built wheels are compatible 16 | -------------------------------------------------------------------------------- /recipes/pyproj/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/setup.py b/setup.py 2 | index 9987cff..b56a0fc 100644 3 | --- a/setup.py 4 | +++ b/setup.py 5 | @@ -194,7 +194,9 @@ def get_extension_modules(): 6 | "include_dirs": include_dirs, 7 | "library_dirs": library_dirs, 8 | "runtime_library_dirs": ( 9 | - library_dirs if os.name != "nt" and sys.platform != "cygwin" else None 10 | + library_dirs 11 | + if os.name != "nt" and sys.platform != "cygwin" and sys.platform != "ios" 12 | + else None 13 | ), 14 | "libraries": get_libraries(library_dirs), 15 | } 16 | -------------------------------------------------------------------------------- /recipes/google-crc32c/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestGoogleCrc32c(unittest.TestCase): 5 | 6 | # Based on https://github.com/googleapis/python-crc32c/blob/main/tests/test___init__.py 7 | def test_basic(self): 8 | import google_crc32c 9 | 10 | self.assertEqual("c", google_crc32c.implementation) 11 | for data, expected in [(b"", 0x00000000), 12 | (b"\x00" * 32, 0x8A9136AA), 13 | (bytes(range(32)), 0x46DD794E)]: 14 | with self.subTest(data=data): 15 | self.assertEqual(expected, google_crc32c.value(data)) 16 | -------------------------------------------------------------------------------- /recipes/numpy/test_numpy.py: -------------------------------------------------------------------------------- 1 | def test_basic(): 2 | from numpy import array 3 | 4 | assert (array([1, 2]) + array([3, 5])).tolist() == [4, 7] 5 | 6 | 7 | def test_performance(): 8 | from time import time 9 | 10 | import numpy as np 11 | 12 | start_time = time() 13 | SIZE = 500 14 | a = np.random.rand(SIZE, SIZE) 15 | b = np.random.rand(SIZE, SIZE) 16 | np.dot(a, b) 17 | 18 | # With OpenBLAS, the test devices take at most 0.4 seconds. Without OpenBLAS, they take 19 | # at least 1.0 seconds. 20 | duration = time() - start_time 21 | print(f"{duration:.3f}") 22 | assert duration < 0.7 23 | -------------------------------------------------------------------------------- /recipes/pillow/patches/setup-11.x.patch: -------------------------------------------------------------------------------- 1 | diff --git a/setup.py b/setup.py 2 | index a85731d..fbbb5b6 100644 3 | --- a/setup.py 4 | +++ b/setup.py 5 | @@ -355,9 +355,7 @@ class pil_build_ext(build_ext): 6 | return True if value in configuration.get(option, []) else None 7 | 8 | def initialize_options(self) -> None: 9 | - self.disable_platform_guessing = self.check_configuration( 10 | - "platform-guessing", "disable" 11 | - ) 12 | + self.disable_platform_guessing = True 13 | self.add_imaging_libs = "" 14 | build_ext.initialize_options(self) 15 | for x in self.feature: 16 | -------------------------------------------------------------------------------- /recipes/gdal/patches/config.patch: -------------------------------------------------------------------------------- 1 | diff --git a/setup.py b/setup.py 2 | index 5c6ac95..26bb5fa 100644 3 | --- a/setup.py 4 | +++ b/setup.py 5 | @@ -228,6 +228,9 @@ class gdal_ext(build_ext): 6 | 7 | def get_gdal_config(self, option): 8 | try: 9 | + var_name = f"GDAL_{option.upper()}" 10 | + if var_name in os.environ: 11 | + return os.environ[var_name] 12 | return fetch_config(option, gdal_config=self.gdal_config) 13 | except gdal_config_error: 14 | msg = 'Could not find gdal-config. Make sure you have installed the GDAL native library and development headers.' 15 | -------------------------------------------------------------------------------- /recipes/matplotlib/test_matplotlib.py: -------------------------------------------------------------------------------- 1 | def test_png(self): 2 | import io 3 | 4 | import matplotlib.pyplot as plt 5 | 6 | plt.figure() 7 | plt.plot([1, 2]) 8 | bio = io.BytesIO() 9 | plt.savefig(bio, format="png") 10 | b = bio.getvalue() 11 | 12 | EXPECTED_LEN = 16782 13 | assert len(b) > int(EXPECTED_LEN * 0.8) 14 | assert len(b) < int(EXPECTED_LEN * 1.2) 15 | 16 | assert b[:24] == ( 17 | b"\x89PNG\r\n\x1a\n" 18 | + b"\x00\x00\x00\rIHDR" # File header 19 | + b"\x00\x00\x02\x80" # Header chunk header 20 | + b"\x00\x00\x01\xe0" # Width 640 # Height 480 21 | ) 22 | -------------------------------------------------------------------------------- /src/forge/utils.py: -------------------------------------------------------------------------------- 1 | def merge_dicts(dict1, dict2): 2 | """ 3 | Merges two multi-level dictionaries recursively. 4 | 5 | Args: 6 | dict1: The first dictionary. 7 | dict2: The second dictionary. 8 | 9 | Returns: 10 | A new dictionary with the merged values from dict1 and dict2. 11 | """ 12 | 13 | merged = dict1.copy() 14 | for key, value in dict2.items(): 15 | if key in merged: 16 | if isinstance(value, dict) and isinstance(merged[key], dict): 17 | merged[key] = merge_dicts(merged[key], value) 18 | else: 19 | merged[key] = value 20 | else: 21 | merged[key] = value 22 | return merged -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/package_request.yml: -------------------------------------------------------------------------------- 1 | name: 📦 Package request 2 | description: Have a specific package that you'd like to see added? Request it here! 3 | labels: ["enhancement"] 4 | 5 | body: 6 | 7 | - type: input 8 | id: package 9 | attributes: 10 | label: What is the PyPI name of the package you would like to see added? 11 | placeholder: e.g., cryptography 12 | validations: 13 | required: true 14 | 15 | - type: textarea 16 | id: details 17 | attributes: 18 | label: Additional details 19 | description: Are there any other details you can provide? 20 | validations: 21 | required: false 22 | -------------------------------------------------------------------------------- /recipes/flet-libfreetype/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | ./configure --host=$HOST_TRIPLET --build=$BUILD_TRIPLET --without-harfbuzz --without-png 5 | make -j $CPU_COUNT 6 | make install prefix=$PREFIX 7 | 8 | mv $PREFIX/include/freetype2/* $PREFIX/include 9 | rmdir $PREFIX/include/freetype2 10 | 11 | # Some versions of Android (e.g. API level 26) have a libft2.so in /system/lib, but our copy 12 | # has an SONAME of libfreetype.so, so there's no conflict. 13 | # rm -r $PREFIX/lib/{*.a,*.la,pkgconfig} 14 | rm -r $PREFIX/lib/{*.la,pkgconfig} 15 | rm -r $PREFIX/share 16 | 17 | if [ $CROSS_VENV_SDK == "android" ]; then 18 | rm -r $PREFIX/lib/*.a 19 | fi -------------------------------------------------------------------------------- /recipes/pandas/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: pandas 3 | version: 2.2.3 4 | 5 | # {% if not version or version >= (2,1) %} 6 | # pandas >= 2.1.0 7 | 8 | requirements: 9 | build: 10 | - ninja 11 | - git+https://github.com/flet-dev/meson@ios-dynamiclib 12 | host: 13 | - numpy ^2.0.0 14 | # {% if sdk == 'android' %} 15 | - flet-libcpp-shared 27.2.12479018 16 | # {% endif %} 17 | 18 | patches: 19 | - mobile.patch 20 | 21 | build: 22 | backend-args: 23 | - -Csetup-args=--cross-file 24 | - -Csetup-args={MESON_CROSS_FILE} 25 | 26 | # {% else %} 27 | # pandas < 2.1.0 28 | 29 | requirements: 30 | host: 31 | - numpy 1.26.4 32 | 33 | # {% endif %} -------------------------------------------------------------------------------- /recipes/pillow/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: Pillow 3 | version: 11.1.0 4 | 5 | requirements: 6 | host: 7 | # PNG support is internal: libpng is not used. 8 | - flet-libjpeg 3.0.90 9 | - flet-libfreetype 2.13.3 10 | 11 | # {% if version and version >= (11,0,0) %} 12 | # pillow >= 11.x 13 | 14 | patches: 15 | - setup-11.x.patch 16 | 17 | # {% else %} 18 | # pillow <= 10.x 19 | 20 | patches: 21 | - setup-10.x.patch 22 | 23 | # {% endif %} 24 | 25 | # {% if sdk != 'android' %} 26 | build: 27 | script_env: 28 | # libfreetype references both libz and libbz2 29 | # but doesn't link them into the static library 30 | LDFLAGS: -lz -lbz2 31 | # {% endif %} -------------------------------------------------------------------------------- /recipes/flet-libpng/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | ./configure --host=$HOST_TRIPLET --build=$BUILD_TRIPLET 5 | make -j $CPU_COUNT 6 | make install prefix=$PREFIX 7 | 8 | find $PREFIX -type l | xargs rm 9 | 10 | rm -r $PREFIX/bin 11 | 12 | mv $PREFIX/include/libpng16/* $PREFIX/include 13 | rmdir $PREFIX/include/libpng16 14 | 15 | # Some versions of Android (e.g. API level 26) have a libpng.so in /system/lib, but our copy 16 | # has an SONAME of libpng16.so, so there's no conflict. 17 | rm -r $PREFIX/lib/{*.la,pkgconfig} 18 | 19 | # Downstream recipes expect the name libpng.a, not libpng16.a 20 | mv $PREFIX/lib/libpng16.a $PREFIX/lib/libpng.a 21 | 22 | rm -r $PREFIX/share 23 | -------------------------------------------------------------------------------- /recipes/grpcio/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: grpcio 3 | version: 1.67.1 4 | 5 | build: 6 | script_env: 7 | # {% if sdk == 'android' %} 8 | GRPC_PYTHON_BUILD_SYSTEM_OPENSSL: '1' 9 | GRPC_PYTHON_BUILD_SYSTEM_ZLIB: '1' 10 | PLATFORM: android 11 | CFLAGS: '-U__ANDROID_API__ -D__ANDROID_API__={{ sdk_version }} -Wno-reserved-user-defined-literal' 12 | LDFLAGS: '-llog' 13 | # {% else %} 14 | CXXFLAGS: -std=c++14 -Wno-c++11-narrowing 15 | LDFLAGS: '-framework CoreFoundation' 16 | # {% endif %} 17 | 18 | patches: 19 | - mobile.patch 20 | 21 | # {% if sdk == 'android' %} 22 | requirements: 23 | host: 24 | - openssl 3.0.15 25 | - flet-libcpp-shared 27.2.12479018 26 | # {% endif %} -------------------------------------------------------------------------------- /recipes/kiwisolver/test.py: -------------------------------------------------------------------------------- 1 | # Based on https://kiwisolver.readthedocs.io/en/latest/basis/basic_systems.html 2 | def test_basic(): 3 | from kiwisolver import Solver, Variable 4 | 5 | x1 = Variable("x1") 6 | x2 = Variable("x2") 7 | xm = Variable("xm") 8 | 9 | constraints = [x1 >= 0, x2 <= 100, x2 >= x1 + 10, xm == (x1 + x2) / 2] 10 | solver = Solver() 11 | for cn in constraints: 12 | solver.addConstraint(cn) 13 | 14 | solver.addConstraint((x1 == 40) | "weak") 15 | solver.addEditVariable(xm, "strong") 16 | solver.suggestValue(xm, 60) 17 | solver.updateVariables() 18 | 19 | assert xm.value() == 60 20 | assert x1.value() == 40 21 | assert x2.value() == 80 22 | -------------------------------------------------------------------------------- /recipes/flet-libjq/patches/config.patch: -------------------------------------------------------------------------------- 1 | diff --git a/config/config.sub b/config/config.sub 2 | index dba16e8..215b8e2 100755 3 | --- a/config/config.sub 4 | +++ b/config/config.sub 5 | @@ -1792,6 +1792,8 @@ case $kernel-$os in 6 | ;; 7 | *-eabi* | *-gnueabi*) 8 | ;; 9 | + ios-simulator) 10 | + ;; 11 | -*) 12 | # Blank kernel with real OS is always fine. 13 | ;; 14 | diff --git a/modules/oniguruma/config.sub b/modules/oniguruma/config.sub 15 | index 0753e30..fe182ac 100755 16 | --- a/modules/oniguruma/config.sub 17 | +++ b/modules/oniguruma/config.sub 18 | @@ -1753,6 +1753,8 @@ case $kernel-$os in 19 | ;; 20 | *-eabi* | *-gnueabi*) 21 | ;; 22 | + ios-simulator) 23 | + ;; 24 | -*) 25 | # Blank kernel with real OS is always fine. 26 | ;; 27 | -------------------------------------------------------------------------------- /recipes/aiohttp/test_aiohttp.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def test_basic(): 5 | import asyncio 6 | 7 | import aiohttp 8 | 9 | async def main(): 10 | async with aiohttp.ClientSession() as session: 11 | async with session.get("http://python.org") as response: 12 | assert response.status == 200 13 | assert re.match(r"^text/html", response.headers["content-type"]) 14 | 15 | asyncio.run(main()) 16 | 17 | 18 | # Check that the native module is being used. 19 | def test_extension(): 20 | from aiohttp import _http_parser, http_parser 21 | 22 | assert http_parser.HttpResponseParser is _http_parser.HttpResponseParser 23 | assert http_parser.HttpResponseParser is not http_parser.HttpResponseParserPy 24 | -------------------------------------------------------------------------------- /recipes/numpy/patches/mobile-1.26.4.patch: -------------------------------------------------------------------------------- 1 | diff --git a/vendored-meson/meson/mesonbuild/linkers/linkers.py b/vendored-meson/meson/mesonbuild/linkers/linkers.py 2 | index 7b3202c..2bf5e19 100644 3 | --- a/vendored-meson/meson/mesonbuild/linkers/linkers.py 4 | +++ b/vendored-meson/meson/mesonbuild/linkers/linkers.py 5 | @@ -767,7 +767,7 @@ def get_allow_undefined_args(self) -> T.List[str]: 6 | return self._apply_prefix('-undefined,dynamic_lookup') 7 | 8 | def get_std_shared_module_args(self, options: 'KeyedOptionDictType') -> T.List[str]: 9 | - return ['-bundle'] + self._apply_prefix('-undefined,dynamic_lookup') 10 | + return ['-dynamiclib'] + self._apply_prefix('-undefined,dynamic_lookup') 11 | 12 | def get_pie_args(self) -> T.List[str]: 13 | return [] 14 | -------------------------------------------------------------------------------- /recipes/numpy/patches/mobile-2.2.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/vendored-meson/meson/mesonbuild/linkers/linkers.py b/vendored-meson/meson/mesonbuild/linkers/linkers.py 2 | index 4eec82e..ca4586f 100644 3 | --- a/vendored-meson/meson/mesonbuild/linkers/linkers.py 4 | +++ b/vendored-meson/meson/mesonbuild/linkers/linkers.py 5 | @@ -761,7 +761,7 @@ def get_allow_undefined_args(self) -> T.List[str]: 6 | return self._apply_prefix('-undefined,dynamic_lookup') 7 | 8 | def get_std_shared_module_args(self, options: 'KeyedOptionDictType') -> T.List[str]: 9 | - return ['-bundle'] + self._apply_prefix('-undefined,dynamic_lookup') 10 | + return ['-dynamiclib'] + self._apply_prefix('-undefined,dynamic_lookup') 11 | 12 | def get_pie_args(self) -> T.List[str]: 13 | return [] 14 | -------------------------------------------------------------------------------- /recipes/numpy/meta.yaml: -------------------------------------------------------------------------------- 1 | # Building for Android requires API Level 24 or above. 2 | # See https://apilevels.com 3 | 4 | package: 5 | name: numpy 6 | version: 2.2.2 7 | 8 | requirements: 9 | build: 10 | - ninja 11 | 12 | patches: 13 | {% if version and version < (2, 0) %} 14 | - mobile-1.26.4.patch 15 | {% else %} 16 | - mobile-2.2.2.patch 17 | {% endif %} 18 | 19 | build: 20 | script_env: 21 | NPY_DISABLE_SVML: 1 22 | 23 | backend-args: 24 | - -Csetup-args=-Dblas=none 25 | - -Csetup-args=-Dlapack=none 26 | - -Csetup-args=--cross-file 27 | - -Csetup-args={MESON_CROSS_FILE} 28 | 29 | meson: 30 | properties: 31 | # {% if sdk == 'iOS' or (sdk == 'android' and arch in ['arm64-v8a', 'x86_64']) %} 32 | longdouble_format: IEEE_QUAD_LE 33 | # {% else %} 34 | longdouble_format: IEEE_DOUBLE_LE 35 | # {% endif %} -------------------------------------------------------------------------------- /recipes/flet-libgeos/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | if [ $CROSS_VENV_SDK == "android" ]; then 5 | cmake \ 6 | -DCMAKE_SYSTEM_NAME=Android \ 7 | -DANDROID_PLATFORM=$SDK_VERSION \ 8 | -DANDROID_ABI=$ANDROID_ABI \ 9 | -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ 10 | -DCMAKE_BUILD_TYPE=Release \ 11 | -DCMAKE_INSTALL_PREFIX="$PREFIX" \ 12 | -DBUILD_TESTING=0 13 | else 14 | cmake \ 15 | -DCMAKE_SYSTEM_NAME=iOS \ 16 | -DCMAKE_OSX_SYSROOT=$SDK \ 17 | -DCMAKE_OSX_ARCHITECTURES=$HOST_ARCH \ 18 | -DCMAKE_BUILD_TYPE=Release \ 19 | -DCMAKE_INSTALL_PREFIX=$PREFIX \ 20 | -DBUILD_SHARED_LIBS=OFF \ 21 | -DBUILD_TESTING=0 22 | fi 23 | 24 | make -j $CPU_COUNT 25 | make install 26 | 27 | rm -rf $PREFIX/bin 28 | rm -rf $PREFIX/lib/{cmake,pkgconfig} -------------------------------------------------------------------------------- /recipes/flet-libjpeg/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # SIMD is only available for x86, so disable for consistency between ABIs. 5 | if [ $CROSS_VENV_SDK == "android" ]; then 6 | cmake -G"Unix Makefiles" \ 7 | -DCMAKE_SYSTEM_NAME=Android \ 8 | -DANDROID_PLATFORM=$SDK_VERSION \ 9 | -DANDROID_ABI=$ANDROID_ABI \ 10 | -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ 11 | -DCMAKE_INSTALL_PREFIX=$PREFIX . 12 | else 13 | cmake -G"Unix Makefiles" \ 14 | -DCMAKE_SYSTEM_NAME=iOS \ 15 | -DCMAKE_SYSTEM_PROCESSOR=$HOST_ARCH \ 16 | -DCMAKE_INSTALL_PREFIX=$PREFIX . 17 | fi 18 | 19 | make -j $CPU_COUNT > /dev/null 2>&1 || { echo "Error building libjpeg"; exit 1; } 20 | make install 21 | 22 | rm -r $PREFIX/{bin,share} 23 | rm -r $PREFIX/lib/{pkgconfig,cmake} 24 | find "$PREFIX/lib/" -name "*.dylib" -exec rm -rf {} \; -------------------------------------------------------------------------------- /recipes/flet-libfreetype/patches/config.patch: -------------------------------------------------------------------------------- 1 | diff --git a/builds/unix/config.sub b/builds/unix/config.sub 2 | index 4aaae46..526f2d4 100755 3 | --- a/builds/unix/config.sub 4 | +++ b/builds/unix/config.sub 5 | @@ -155,6 +155,7 @@ case $1 in 6 | | storm-chaos* \ 7 | | uclinux-gnu* \ 8 | | uclinux-uclibc* \ 9 | + | ios*-simulator \ 10 | | windows-* ) 11 | basic_machine=$field1 12 | basic_os=$maybe_os 13 | @@ -1727,6 +1728,8 @@ case $os in 14 | obj=$os 15 | os= 16 | ;; 17 | + ios | ios-simulator) 18 | + ;; 19 | *) 20 | # No normalization, but not necessarily accepted, that comes below. 21 | ;; 22 | @@ -2253,6 +2256,8 @@ case $kernel-$os-$obj in 23 | # None (no kernel, i.e. freestanding / bare metal), 24 | # can be paired with an machine code file format 25 | ;; 26 | + ios-simulator-) 27 | + ;; 28 | -*-) 29 | # Blank kernel with real OS is always fine. 30 | ;; 31 | -------------------------------------------------------------------------------- /recipes/pynacl/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/setup.py b/setup.py 2 | index 96d4b32..045314f 100644 3 | --- a/setup.py 4 | +++ b/setup.py 5 | @@ -29,7 +29,6 @@ from setuptools import Distribution, setup 6 | from setuptools.command.build_clib import build_clib as _build_clib 7 | from setuptools.command.build_ext import build_ext as _build_ext 8 | 9 | - 10 | requirements = [] 11 | setup_requirements = ["setuptools"] 12 | test_requirements = [ 13 | @@ -218,8 +217,8 @@ setup( 14 | package_data={"nacl": ["py.typed"]}, 15 | ext_package="nacl", 16 | cffi_modules=["src/bindings/build.py:ffi"], 17 | - cmdclass={"build_clib": build_clib, "build_ext": build_ext}, 18 | - distclass=Distribution, 19 | + # cmdclass={"build_ext": build_ext}, 20 | + # distclass=Distribution, 21 | zip_safe=False, 22 | classifiers=[ 23 | "Programming Language :: Python :: Implementation :: CPython", 24 | -------------------------------------------------------------------------------- /src/forge/logger.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | 4 | def log(log_file, *args, debug=False, **kwargs): 5 | """Log output to the screen, and to the log file. 6 | 7 | :param log_file: An open file handle to write log content to 8 | :param args: The arguments to pass to print 9 | :param debug: Is the output debug-specific? Debug output is only output to the log 10 | file. 11 | :param kwargs: Additional keyword arguments to pass to print. 12 | """ 13 | if not debug: 14 | print(*args, **kwargs) 15 | if log_file: 16 | print(*args, **kwargs, file=log_file) 17 | 18 | 19 | def log_exception(log_file): 20 | """Log the current exception stack tracce to the screen, and to the log file. 21 | 22 | :param log_file: An open file handle to write log content to 23 | """ 24 | traceback.print_exc() 25 | if log_file: 26 | traceback.print_exc(file=log_file) 27 | -------------------------------------------------------------------------------- /recipes/flet-libsodium/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | if [ $CROSS_VENV_SDK != "android" ]; then 5 | case $HOST_TRIPLET in 6 | arm64-apple-ios) 7 | HOST_TRIPLET=arm-apple-darwin23 8 | ;; 9 | arm64-apple-ios-simulator) 10 | HOST_TRIPLET=aarch64-apple-darwin23 11 | ;; 12 | x86_64-apple-ios-simulator) 13 | HOST_TRIPLET=x86_64-apple-darwin23 14 | ;; 15 | *) 16 | echo "Unknown host triplet: '$HOST_TRIPLET'" 17 | exit 1 18 | ;; 19 | esac 20 | fi 21 | 22 | ./configure --host=$HOST_TRIPLET --prefix=$PREFIX --disable-soname-versions 23 | make -j $CPU_COUNT 24 | make install 25 | 26 | rm -r $PREFIX/lib/{*.la,pkgconfig} 27 | 28 | if [ $CROSS_VENV_SDK == "android" ]; then 29 | rm -r $PREFIX/lib/*.a 30 | else 31 | mv $PREFIX/lib/libsodium.dylib $PREFIX/../libsodium.so 32 | fi -------------------------------------------------------------------------------- /recipes/flet-libcrc32c/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | if [ $CROSS_VENV_SDK == "android" ]; then 5 | cmake \ 6 | -DCMAKE_SYSTEM_NAME=Android \ 7 | -DANDROID_PLATFORM=$SDK_VERSION \ 8 | -DANDROID_ABI=$ANDROID_ABI \ 9 | -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ 10 | -DCRC32C_BUILD_TESTS=0 \ 11 | -DCRC32C_BUILD_BENCHMARKS=0 \ 12 | -DCRC32C_USE_GLOG=0 \ 13 | -DCMAKE_BUILD_TYPE=Release \ 14 | -DBUILD_SHARED_LIBS=1 \ 15 | -DCMAKE_INSTALL_PREFIX="$PREFIX" 16 | else 17 | cmake \ 18 | -DCMAKE_SYSTEM_NAME=iOS \ 19 | -DCMAKE_OSX_SYSROOT=$SDK \ 20 | -DCMAKE_OSX_ARCHITECTURES=$HOST_ARCH \ 21 | -DCRC32C_BUILD_TESTS=0 \ 22 | -DCRC32C_BUILD_BENCHMARKS=0 \ 23 | -DCRC32C_USE_GLOG=0 \ 24 | -DCMAKE_BUILD_TYPE=Release \ 25 | -DCMAKE_INSTALL_PREFIX=$PREFIX 26 | fi 27 | 28 | make -j $CPU_COUNT 29 | make install 30 | 31 | # cleanup 32 | rm -r $PREFIX/lib/cmake -------------------------------------------------------------------------------- /recipes/pillow/test/test_pillow.py: -------------------------------------------------------------------------------- 1 | import io 2 | from os.path import dirname, join 3 | 4 | 5 | def test_basic(): 6 | from PIL import Image 7 | 8 | img = Image.open(join(dirname(__file__), "mandrill.jpg")) 9 | assert img.width == 512 10 | assert img.height == 512 11 | 12 | out_file = io.BytesIO() 13 | img.save(out_file, "png") 14 | out_bytes = out_file.getvalue() 15 | 16 | EXPECTED_LEN = 313772 17 | assert len(out_bytes) > int(EXPECTED_LEN * 0.8) 18 | assert len(out_bytes) < int(EXPECTED_LEN * 1.2) 19 | 20 | assert out_bytes[:24] == ( 21 | b"\x89PNG\r\n\x1a\n" 22 | + b"\x00\x00\x00\rIHDR" # File header 23 | + b"\x00\x00\x02\x00" # Header chunk header 24 | + b"\x00\x00\x02\x00" # Width 512 # Height 512 25 | ) 26 | 27 | 28 | def test_font(): 29 | from PIL import ImageFont 30 | 31 | font = ImageFont.truetype(join(dirname(__file__), "Vera.ttf"), size=20) 32 | font.getsize("Hello") == (51, 19) 33 | font.getsize("Hello world") == (112, 19) 34 | -------------------------------------------------------------------------------- /recipes/lxml/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/setupinfo.py b/setupinfo.py 2 | index 97e3399..634183a 100644 3 | --- a/setupinfo.py 4 | +++ b/setupinfo.py 5 | @@ -1,12 +1,13 @@ 6 | -import sys 7 | import io 8 | import os 9 | import os.path 10 | import subprocess 11 | - 12 | -from setuptools.command.build_ext import build_ext as _build_ext 13 | +import sys 14 | from distutils.core import Extension 15 | from distutils.errors import CompileError, DistutilsOptionError 16 | + 17 | +from setuptools.command.build_ext import build_ext as _build_ext 18 | + 19 | from versioninfo import get_base_dir 20 | 21 | try: 22 | @@ -330,7 +331,7 @@ def include_dirs(static_include_dirs): 23 | result = [] 24 | possible_include_dirs = flags('cflags') 25 | for possible_include_dir in possible_include_dirs: 26 | - if possible_include_dir.startswith('-I'): 27 | + if possible_include_dir.startswith('-I') and not possible_include_dir.endswith('/MacOSX.sdk/usr/include'): 28 | result.append(possible_include_dir[2:]) 29 | return result 30 | 31 | -------------------------------------------------------------------------------- /recipes/fiona/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/setup.py b/setup.py 2 | index e1f48a6..6c04d39 100644 3 | --- a/setup.py 4 | +++ b/setup.py 5 | @@ -9,7 +9,6 @@ import sys 6 | from setuptools import setup 7 | from setuptools.extension import Extension 8 | 9 | - 10 | # Ensure minimum version of Python is running 11 | if sys.version_info[0:2] < (3, 6): 12 | raise RuntimeError('Fiona requires Python>=3.6') 13 | @@ -85,6 +84,13 @@ if 'clean' not in sys.argv: 14 | else: 15 | logging.warning("Failed to get options via gdal-config: %s", str(e)) 16 | 17 | + if 'GDAL_LIB_PATH' in os.environ: 18 | + library_dirs.extend(os.environ['GDAL_LIB_PATH'].split(":")) 19 | + if 'GDAL_INCLUDE_PATH' in os.environ: 20 | + include_dirs.extend(os.environ['GDAL_INCLUDE_PATH'].split(":")) 21 | + if 'GDAL_LIBS' in os.environ: 22 | + libraries.extend(os.environ['GDAL_LIBS'].split(",")) 23 | + 24 | # Get GDAL API version from environment variable. 25 | if 'GDAL_VERSION' in os.environ: 26 | gdalversion = os.environ['GDAL_VERSION'] 27 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: check-toml 6 | # Recipes are templates, not pure YAML 7 | # - id: check-yaml 8 | - id: check-case-conflict 9 | - id: check-docstring-first 10 | - id: end-of-file-fixer 11 | - id: trailing-whitespace 12 | - repo: https://github.com/PyCQA/isort 13 | rev: 5.12.0 14 | hooks: 15 | - id: isort 16 | additional_dependencies: [toml] 17 | - repo: https://github.com/asottile/pyupgrade 18 | rev: v3.11.0 19 | hooks: 20 | - id: pyupgrade 21 | args: [--py38-plus] 22 | - repo: https://github.com/PyCQA/docformatter 23 | rev: v1.7.5 24 | hooks: 25 | - id: docformatter 26 | args: [--in-place, --black] 27 | - repo: https://github.com/psf/black 28 | rev: 23.9.1 29 | hooks: 30 | - id: black 31 | language_version: python3 32 | - repo: https://github.com/PyCQA/flake8 33 | rev: 6.1.0 34 | hooks: 35 | - id: flake8 36 | - repo: https://github.com/codespell-project/codespell 37 | rev: v2.2.5 38 | hooks: 39 | - id: codespell 40 | additional_dependencies: 41 | - tomli 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 💡 Feature request 2 | description: Have an idea for a new feature for Mobile Forge itself? Let us know about it! 3 | labels: ["enhancement"] 4 | 5 | body: 6 | 7 | - type: textarea 8 | id: description 9 | attributes: 10 | label: What is the problem or limitation you are having? 11 | description: A clear and concise description of the problem; e.g., I get frustrated when... 12 | validations: 13 | required: true 14 | 15 | - type: textarea 16 | id: solution 17 | attributes: 18 | label: Describe the solution you'd like 19 | description: A clear and concise description of what you want to happen. 20 | validations: 21 | required: true 22 | 23 | - type: textarea 24 | id: alternatives 25 | attributes: 26 | label: Describe alternatives you've considered 27 | description: A clear and concise description of any alternative solutions or features you've considered. 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | id: extrainfo 33 | attributes: 34 | label: Additional context 35 | description: Add any other context or screenshots about the feature request here. 36 | validations: 37 | required: false 38 | -------------------------------------------------------------------------------- /recipes/flet-libpng/patches/config.patch: -------------------------------------------------------------------------------- 1 | index defe52c..829037b 100755 2 | --- a/config.sub 3 | +++ b/config.sub 4 | @@ -146,6 +146,7 @@ case $1 in 5 | | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ 6 | | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ 7 | | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \ 8 | + | ios*-simulator | tvos*-simulator | watchos*-simulator \ 9 | | windows-* ) 10 | basic_machine=$field1 11 | basic_os=$maybe_os 12 | @@ -1500,6 +1501,8 @@ case $os in 13 | obj=$os 14 | os= 15 | ;; 16 | + ios | ios-simulator) 17 | + ;; 18 | *) 19 | # No normalization, but not necessarily accepted, that comes below. 20 | ;; 21 | @@ -1855,6 +1858,8 @@ case $kernel-$os-$obj in 22 | ;; 23 | *-eabi*- | *-gnueabi*-) 24 | ;; 25 | + ios-simulator-) 26 | + ;; 27 | none--*) 28 | # None (no kernel, i.e. freestanding / bare metal), 29 | # can be paired with an machine code file format -------------------------------------------------------------------------------- /recipes/pycryptodome/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/lib/Crypto/Util/_raw_api.py b/lib/Crypto/Util/_raw_api.py 2 | index e0065c3..3b14e00 100644 3 | --- a/lib/Crypto/Util/_raw_api.py 4 | +++ b/lib/Crypto/Util/_raw_api.py 5 | @@ -30,6 +30,7 @@ 6 | 7 | import os 8 | import abc 9 | +import pathlib 10 | import sys 11 | from Crypto.Util.py3compat import byte_string 12 | from Crypto.Util._file_system import pycryptodome_filename 13 | @@ -302,13 +303,17 @@ def load_pycryptodome_raw_lib(name, cdecl): 14 | split = name.split(".") 15 | dir_comps, basename = split[:-1], split[-1] 16 | attempts = [] 17 | - for ext in extension_suffixes: 18 | + for ext in extension_suffixes + [".fwork"]: 19 | try: 20 | filename = basename + ext 21 | full_name = pycryptodome_filename(dir_comps, filename) 22 | if not os.path.isfile(full_name): 23 | attempts.append("Not found '%s'" % filename) 24 | continue 25 | + if full_name.endswith(".fwork"): 26 | + with open(full_name, 'r') as f: 27 | + framework_binary = f.read().strip() 28 | + full_name = str(pathlib.Path(sys.executable).parent.joinpath(framework_binary)) 29 | return load_lib(full_name, cdecl) 30 | except OSError as exp: 31 | attempts.append("Cannot load '%s': %s" % (filename, str(exp))) 32 | -------------------------------------------------------------------------------- /recipes/pycryptodomex/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/lib/Cryptodome/Util/_raw_api.py b/lib/Cryptodome/Util/_raw_api.py 2 | index e0065c3..3b14e00 100644 3 | --- a/lib/Cryptodome/Util/_raw_api.py 4 | +++ b/lib/Cryptodome/Util/_raw_api.py 5 | @@ -30,6 +30,7 @@ 6 | 7 | import os 8 | import abc 9 | +import pathlib 10 | import sys 11 | from Cryptodome.Util.py3compat import byte_string 12 | from Cryptodome.Util._file_system import pycryptodome_filename 13 | @@ -302,13 +303,17 @@ def load_pycryptodome_raw_lib(name, cdecl): 14 | split = name.split(".") 15 | dir_comps, basename = split[:-1], split[-1] 16 | attempts = [] 17 | - for ext in extension_suffixes: 18 | + for ext in extension_suffixes + [".fwork"]: 19 | try: 20 | filename = basename + ext 21 | full_name = pycryptodome_filename(dir_comps, filename) 22 | if not os.path.isfile(full_name): 23 | attempts.append("Not found '%s'" % filename) 24 | continue 25 | + if full_name.endswith(".fwork"): 26 | + with open(full_name, 'r') as f: 27 | + framework_binary = f.read().strip() 28 | + full_name = str(pathlib.Path(sys.executable).parent.joinpath(framework_binary)) 29 | return load_lib(full_name, cdecl) 30 | except OSError as exp: 31 | attempts.append("Cannot load '%s': %s" % (filename, str(exp))) 32 | -------------------------------------------------------------------------------- /recipes/flet-libxml2/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/config.sub b/config.sub 2 | index 7b334f9..cdb35e9 100755 3 | --- a/config.sub 4 | +++ b/config.sub 5 | @@ -355,6 +355,10 @@ case $basic_machine in 6 | xscaleel) 7 | basic_machine=armel-unknown 8 | ;; 9 | + arm64-apple | *-ios) 10 | + basic_machine=$basic_machine-unknown 11 | + os=-none 12 | + ;; 13 | 14 | # We use `pc' rather than `unknown' 15 | # because (1) that's what they normally are, and 16 | diff --git a/libxml2.syms b/libxml2.syms 17 | index 370dcf1..3c4a3dc 100644 18 | --- a/libxml2.syms 19 | +++ b/libxml2.syms 20 | @@ -453,16 +453,16 @@ LIBXML2_2.4.30 { 21 | xmlNanoFTPUpdateURL; 22 | 23 | # DOCBparser 24 | - docbCreateFileParserCtxt; 25 | - docbCreatePushParserCtxt; 26 | - docbEncodeEntities; 27 | - docbFreeParserCtxt; 28 | - docbParseChunk; 29 | - docbParseDoc; 30 | - docbParseDocument; 31 | - docbParseFile; 32 | - docbSAXParseDoc; 33 | - docbSAXParseFile; 34 | +# docbCreateFileParserCtxt; 35 | +# docbCreatePushParserCtxt; 36 | +# docbEncodeEntities; 37 | +# docbFreeParserCtxt; 38 | +# docbParseChunk; 39 | +# docbParseDoc; 40 | +# docbParseDocument; 41 | +# docbParseFile; 42 | +# docbSAXParseDoc; 43 | +# docbSAXParseFile; 44 | 45 | # xpath 46 | xmlXPathCastBooleanToNumber; 47 | @@ -2187,7 +2187,7 @@ LIBXML2_2.6.29 { 48 | global: 49 | 50 | # threads 51 | - xmlDllMain; 52 | + # xmlDllMain; 53 | } LIBXML2_2.6.28; 54 | 55 | LIBXML2_2.6.32 { 56 | -------------------------------------------------------------------------------- /recipes/grpcio/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/setup.py b/setup.py 2 | index 48bfefe..ff21bc5 100644 3 | --- a/setup.py 4 | +++ b/setup.py 5 | @@ -58,12 +58,14 @@ CARES_INCLUDE = ( 6 | os.path.join("third_party", "cares"), 7 | os.path.join("third_party", "cares", "cares"), 8 | ) 9 | -if "darwin" in sys.platform: 10 | +if "darwin" in sys.platform or "ios" in sys.platform: 11 | CARES_INCLUDE += (os.path.join("third_party", "cares", "config_darwin"),) 12 | if "freebsd" in sys.platform: 13 | CARES_INCLUDE += (os.path.join("third_party", "cares", "config_freebsd"),) 14 | if "linux" in sys.platform: 15 | CARES_INCLUDE += (os.path.join("third_party", "cares", "config_linux"),) 16 | +if "android" in sys.platform: 17 | + CARES_INCLUDE += (os.path.join("third_party", "cares", "config_android"),) 18 | if "openbsd" in sys.platform: 19 | CARES_INCLUDE += (os.path.join("third_party", "cares", "config_openbsd"),) 20 | RE2_INCLUDE = (os.path.join("third_party", "re2"),) 21 | @@ -329,15 +331,16 @@ EXTENSION_INCLUDE_DIRECTORIES = ( 22 | + ADDRESS_SORTING_INCLUDE 23 | + CARES_INCLUDE 24 | + RE2_INCLUDE 25 | - + SSL_INCLUDE 26 | + UPB_INCLUDE 27 | + UPB_GRPC_GENERATED_INCLUDE 28 | + UPBDEFS_GRPC_GENERATED_INCLUDE 29 | + UTF8_RANGE_INCLUDE 30 | + XXHASH_INCLUDE 31 | - + ZLIB_INCLUDE 32 | ) 33 | 34 | +if "android" not in sys.platform: 35 | + EXTENSION_INCLUDE_DIRECTORIES += SSL_INCLUDE + ZLIB_INCLUDE 36 | + 37 | EXTENSION_LIBRARIES = () 38 | if "linux" in sys.platform: 39 | EXTENSION_LIBRARIES += ("rt",) 40 | -------------------------------------------------------------------------------- /recipes/cffi/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/setup.py b/setup.py 2 | index f9cabaa..c592203 100644 3 | --- a/setup.py 4 | +++ b/setup.py 5 | @@ -9,8 +9,7 @@ import setuptools 6 | 7 | sources = ['src/c/_cffi_backend.c'] 8 | libraries = ['ffi'] 9 | -include_dirs = ['/usr/include/ffi', 10 | - '/usr/include/libffi'] # may be changed by pkg-config 11 | +include_dirs = [] # Don't use system include dirs when cross compiling 12 | define_macros = [('FFI_BUILDING', '1')] # for linking with libffi static library 13 | library_dirs = [] 14 | extra_compile_args = [] 15 | @@ -144,7 +143,7 @@ if sys.platform == "win32" and uses_msvc(): 16 | sources.extend(os.path.join(COMPILE_LIBFFI, filename) 17 | for filename in _filenames) 18 | else: 19 | - use_pkg_config() 20 | + #use_pkg_config() 21 | ask_supports_thread() 22 | ask_supports_sync_synchronize() 23 | 24 | diff --git a/src/c/malloc_closure.h b/src/c/malloc_closure.h 25 | index bebb93d..f82dbe4 100644 26 | --- a/src/c/malloc_closure.h 27 | +++ b/src/c/malloc_closure.h 28 | @@ -42,6 +42,8 @@ emutramp_enabled_check (void) 29 | return 0; 30 | ret = 0; 31 | 32 | + /* Chaquopy: getline requires API level 18, but PaX isn't used by Android anyway. */ 33 | + #if __ANDROID_API__ >= 18 34 | while (getline (&buf, &len, f) != -1) 35 | if (!strncmp (buf, "PaX:", 4)) 36 | { 37 | @@ -51,6 +53,8 @@ emutramp_enabled_check (void) 38 | break; 39 | } 40 | free (buf); 41 | + #endif 42 | + 43 | fclose (f); 44 | return ret; 45 | } -------------------------------------------------------------------------------- /recipes/flet-libproj/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | if [ $CROSS_VENV_SDK == "android" ]; then 5 | cmake \ 6 | -DCMAKE_SYSTEM_NAME=Android \ 7 | -DANDROID_PLATFORM=$SDK_VERSION \ 8 | -DANDROID_ABI=$ANDROID_ABI \ 9 | -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ 10 | -DCMAKE_BUILD_TYPE=Release \ 11 | -DCMAKE_INSTALL_PREFIX="$PREFIX" \ 12 | -DBUILD_TESTING=0 \ 13 | -DTIFF_LIBRARY="$PLATLIB/opt/lib/libtiff.so" \ 14 | -DTIFF_INCLUDE_DIR="$PLATLIB/opt/include" \ 15 | -DCURL_LIBRARY="$PLATLIB/opt/lib/libcurl.so" \ 16 | -DCURL_INCLUDE_DIR="$PLATLIB/opt/include" \ 17 | -DSQLite3_LIBRARY=$PYTHON_PREFIX/lib/libsqlite3_python.so \ 18 | -DSQLite3_INCLUDE_DIR=$PYTHON_PREFIX/include 19 | else 20 | cmake \ 21 | -DCMAKE_SYSTEM_NAME=iOS \ 22 | -DCMAKE_OSX_SYSROOT=$SDK \ 23 | -DCMAKE_OSX_ARCHITECTURES=$HOST_ARCH \ 24 | -DCMAKE_BUILD_TYPE=Release \ 25 | -DCMAKE_INSTALL_PREFIX=$PREFIX \ 26 | -DBUILD_SHARED_LIBS=OFF \ 27 | -DBUILD_TESTING=0 \ 28 | -DTIFF_LIBRARY="$PLATLIB/opt/lib/libtiff.a" \ 29 | -DTIFF_INCLUDE_DIR="$PLATLIB/opt/include" \ 30 | -DCURL_LIBRARY="$PLATLIB/opt/lib/libcurl.a" \ 31 | -DCURL_INCLUDE_DIR="$PLATLIB/opt/include" \ 32 | -DSQLite3_LIBRARY=$SDK_ROOT/usr/lib/libsqlite3.tbd \ 33 | -DSQLite3_INCLUDE_DIR=$SDK_ROOT/usr/include 34 | fi 35 | 36 | cmake --build . -j $CPU_COUNT 37 | cmake --build . --target install 38 | 39 | rm -rf $PREFIX/{bin,share} 40 | rm -rf $PREFIX/lib/{cmake,pkgconfig} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Russell Keith-Magee. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Mobile Forge nor the names of its contributors may 15 | be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /recipes/opencv-python/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: opencv-python 3 | version: 4.10.0.84 4 | 5 | requirements: 6 | host: 7 | - numpy ^2.0.0 8 | 9 | patches: 10 | - mobile.patch 11 | 12 | # {% if sdk == 'android' %} 13 | build: 14 | script_env: 15 | CMAKE_ARGS: >- 16 | -DANDROID=ON 17 | -DWITH_IPP=OFF 18 | -DWITH_ITT=OFF 19 | -DBUILD_ANDROID_PROJECTS=OFF 20 | -DBUILD_ANDROID_EXAMPLES=OFF 21 | -DBUILD_TESTS=OFF 22 | -DBUILD_PERF_TESTS=OFF 23 | -DENABLE_TESTING=OFF 24 | -DBUILD_EXAMPLES=OFF 25 | -DANDROID_ABI={ANDROID_ABI} 26 | -DANDROID_STANDALONE_TOOLCHAIN={NDK_ROOT} 27 | -DANDROID_NATIVE_API_LEVEL={ANDROID_API_LEVEL} 28 | -DANDROID_ALLOW_UNDEFINED_VERSION_SCRIPT_SYMBOLS=1 29 | -DCMAKE_TOOLCHAIN_FILE={NDK_ROOT}/build/cmake/android.toolchain.cmake 30 | -DOPENCV_FORCE_PYTHON_LIBS=ON 31 | -DPYTHON3_INCLUDE_PATH={prefix}/include/python{py_version_short} 32 | -DPYTHON3_LIBRARIES={prefix}/lib/libpython{py_version_short}.so 33 | -DPYTHON3_NUMPY_INCLUDE_DIRS={platlib}/numpy/_core/include 34 | # {% else %} 35 | build: 36 | script_env: 37 | CMAKE_ARGS: >- 38 | -DAPPLE_FRAMEWORK=ON 39 | -DCMAKE_SYSTEM_NAME=iOS 40 | -DCMAKE_SYSTEM_PROCESSOR=aarch64 41 | -DCMAKE_OSX_SYSROOT={{ sdk }} 42 | -DCMAKE_OSX_DEPLOYMENT_TARGET={{ sdk_version }} 43 | -DCMAKE_OSX_ARCHITECTURES={{ arch }} 44 | -DWITH_IPP=OFF 45 | -DWITH_ITT=OFF 46 | -DBUILD_TESTS=OFF 47 | -DBUILD_PERF_TESTS=OFF 48 | -DENABLE_TESTING=OFF 49 | -DBUILD_EXAMPLES=OFF 50 | -DWITH_OPENCL=OFF 51 | -DOPENCV_FORCE_PYTHON_LIBS=ON 52 | -DPYTHON3_INCLUDE_PATH={prefix}/include/python{py_version_short} 53 | -DPYTHON3_LIBRARIES={prefix}/lib/libpython{py_version_short}.so 54 | -DPYTHON3_NUMPY_INCLUDE_DIRS={platlib}/numpy/_core/include 55 | # {% endif %} -------------------------------------------------------------------------------- /.ci/install_ndk.sh: -------------------------------------------------------------------------------- 1 | if [[ -z "${NDK_HOME-}" ]]; then 2 | NDK_HOME=$HOME/ndk/$NDK_VERSION 3 | echo "NDK_HOME environment variable is not set." 4 | if [ ! -d $NDK_HOME ]; then 5 | echo "Installing NDK $NDK_VERSION to $NDK_HOME" 6 | mkdir -p downloads 7 | 8 | if [ $(uname) = "Darwin" ]; then 9 | seven_zip=downloads/7zip/7zz 10 | if ! test -f $seven_zip; then 11 | echo "Installing 7-zip" 12 | mkdir -p $(dirname $seven_zip) 13 | cd $(dirname $seven_zip) 14 | curl -#OL https://www.7-zip.org/a/7z2301-mac.tar.xz 15 | tar -xf 7z2301-mac.tar.xz 16 | cd - 17 | fi 18 | 19 | ndk_dmg=android-ndk-$NDK_VERSION-darwin.dmg 20 | if ! test -f downloads/$ndk_dmg; then 21 | echo ">>> Downloading $ndk_dmg" 22 | curl -#L -o downloads/$ndk_dmg https://dl.google.com/android/repository/$ndk_dmg 23 | fi 24 | 25 | cd downloads 26 | $seven_zip x $ndk_dmg 27 | mkdir -p $(dirname $NDK_HOME) 28 | mv Android\ NDK\ */AndroidNDK*.app/Contents/NDK $NDK_HOME 29 | rm -rf Android\ NDK\ * 30 | cd - 31 | else 32 | ndk_zip=android-ndk-$NDK_VERSION-linux.zip 33 | if ! test -f downloads/$ndk_zip; then 34 | echo ">>> Downloading $ndk_zip" 35 | curl -#L -o downloads/$ndk_zip https://dl.google.com/android/repository/$ndk_zip 36 | fi 37 | cd downloads 38 | unzip -oq $ndk_zip 39 | mkdir -p $(dirname $NDK_HOME) 40 | mv android-ndk-$NDK_VERSION $NDK_HOME 41 | cd - 42 | echo "NDK installed to $NDK_HOME" 43 | fi 44 | else 45 | echo "NDK $NDK_VERSION is already installed in $NDK_HOME" 46 | fi 47 | else 48 | echo "NDK home: $NDK_HOME" 49 | fi -------------------------------------------------------------------------------- /src/forge/subprocess.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import shlex 4 | import subprocess as stdlib_subprocess 5 | 6 | from forge.logger import log 7 | 8 | # Pass through check_output without logging 9 | check_output = stdlib_subprocess.check_output 10 | CalledProcessError = stdlib_subprocess.CalledProcessError 11 | 12 | 13 | def run(logfile, *args, **kwargs): 14 | """A wrapper around subprocess.run() that logs all output. 15 | 16 | Subprocesses will always be run in check mode, with UTF-8 text output, and stderr 17 | redirected to stdout, and stdout being piped so it can be logged. 18 | 19 | :param logfile: An open file handle to which all output will be logged. 20 | :param args: The args to pass to subprocess.run 21 | :param kwargs: The keyword arguments to pass to subprocess.run. 22 | """ 23 | # stdout/err must be piped so the output streamer can print it. 24 | kwargs["stdout"] = stdlib_subprocess.PIPE 25 | kwargs["stderr"] = stdlib_subprocess.STDOUT 26 | # use line-buffered output by default 27 | kwargs["bufsize"] = 1 28 | # use text mode 29 | kwargs["encoding"] = "UTF-8" 30 | kwargs["text"] = True 31 | kwargs["errors"] = "ignore" 32 | 33 | log(logfile) 34 | log(logfile, f">>> {shlex.join(str(arg) for arg in args[0])}", debug=True) 35 | for key, value in kwargs.get("env", {}).items(): 36 | log(logfile, f" {key}={shlex.quote(value)}", debug=True) 37 | log(logfile, "-" * 80, debug=True) 38 | 39 | with stdlib_subprocess.Popen(*args, **kwargs) as process: 40 | while (return_code := process.poll()) is None: 41 | output = process.stdout.readline() 42 | if output: 43 | log(logfile, output.strip()) 44 | 45 | log(logfile, "-" * 80, debug=True) 46 | log(logfile, f"<<< Return code: {return_code}", debug=True) 47 | 48 | if return_code: 49 | raise stdlib_subprocess.CalledProcessError(return_code, args) 50 | 51 | return stdlib_subprocess.CompletedProcess(args, return_code) 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug report 2 | description: Create a bug report to help us improve 3 | labels: ["bug"] 4 | 5 | body: 6 | 7 | - type: textarea 8 | id: description 9 | attributes: 10 | label: Describe the bug 11 | description: A clear and concise description of the problem behavior 12 | validations: 13 | required: true 14 | 15 | - type: textarea 16 | id: reproduce 17 | attributes: 18 | label: Steps to reproduce 19 | description: Steps to reproduce the behavior 20 | value: | 21 | 1. Go to '...' 22 | 2. Click on '....' 23 | 3. Scroll down to '....' 24 | 4. See error 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | id: expected 30 | attributes: 31 | label: Expected behavior 32 | description: A clear and concise description of what you expected to happen 33 | validations: 34 | required: true 35 | 36 | - type: textarea 37 | id: screenshots 38 | attributes: 39 | label: Screenshots 40 | description: Please add screenshots, if applicable. 41 | validations: 42 | required: false 43 | 44 | - type: textarea 45 | attributes: 46 | label: Environment 47 | description: Include versions for all relevant dependencies. 48 | value: | 49 | - Operating System: 50 | - Python version: 51 | - Software versions: 52 | - Briefcase: 53 | - Toga: 54 | - ... 55 | validations: 56 | required: true 57 | 58 | - type: textarea 59 | attributes: 60 | label: Logs 61 | description: Paste or upload relevant log files. Briefcase will create a logfile when invoked with the ``--log`` flag. 62 | value: | 63 | ``` 64 | 65 | ``` 66 | validations: 67 | required: false 68 | 69 | - type: textarea 70 | attributes: 71 | label: Additional context 72 | description: Add any other context about the problem here. 73 | validations: 74 | required: false 75 | -------------------------------------------------------------------------------- /recipes/flet-libgdal/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | mkdir build 5 | cd build 6 | 7 | if [ $CROSS_VENV_SDK == "android" ]; then 8 | cmake .. \ 9 | -DCMAKE_SYSTEM_NAME=Android \ 10 | -DANDROID_PLATFORM=$SDK_VERSION \ 11 | -DANDROID_ABI=$ANDROID_ABI \ 12 | -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ 13 | -DCMAKE_BUILD_TYPE=Release \ 14 | -DCMAKE_INSTALL_PREFIX="$PREFIX" \ 15 | -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \ 16 | -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER \ 17 | -DCMAKE_FIND_USE_CMAKE_SYSTEM_PATH=NO \ 18 | -DPROJ_LIBRARY=$PLATLIB/opt/lib/libproj.so \ 19 | -DPROJ_INCLUDE_DIR=$PLATLIB/opt/include \ 20 | -DSQLite3_LIBRARY=$PYTHON_PREFIX/lib/libsqlite3_python.so \ 21 | -DSQLite3_INCLUDE_DIR=$PYTHON_PREFIX/include \ 22 | -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF \ 23 | -DOGR_BUILD_OPTIONAL_DRIVERS=OFF \ 24 | -DGDAL_USE_EXPAT=OFF \ 25 | -DGDAL_USE_OPENSSL=OFF \ 26 | -DGDAL_USE_CURL=OFF \ 27 | -DGDAL_USE_LIBXML2=OFF \ 28 | -DBUILD_APPS=OFF \ 29 | -DBUILD_TESTING=OFF 30 | else 31 | cmake .. \ 32 | -DCMAKE_SYSTEM_NAME=iOS \ 33 | -DCMAKE_OSX_SYSROOT=$SDK \ 34 | -DCMAKE_OSX_ARCHITECTURES=$HOST_ARCH \ 35 | -DCMAKE_BUILD_TYPE=Release \ 36 | -DCMAKE_INSTALL_PREFIX=$PREFIX \ 37 | -DBUILD_SHARED_LIBS=OFF \ 38 | -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \ 39 | -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER \ 40 | -DCMAKE_FIND_USE_CMAKE_SYSTEM_PATH=NO \ 41 | -DCMAKE_CXX_FLAGS="$CFLAGS" \ 42 | -DGDAL_USE_EXTERNAL_LIBS=OFF \ 43 | -DPROJ_LIBRARY=$PLATLIB/opt/lib/libproj.a \ 44 | -DPROJ_INCLUDE_DIR=$PLATLIB/opt/include \ 45 | -DSQLite3_LIBRARY=$SDK_ROOT/usr/lib/libsqlite3.tbd \ 46 | -DSQLite3_INCLUDE_DIR=$SDK_ROOT/usr/include \ 47 | -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF \ 48 | -DOGR_BUILD_OPTIONAL_DRIVERS=OFF \ 49 | -DBUILD_APPS=OFF \ 50 | -DBUILD_TESTING=OFF 51 | fi 52 | 53 | cmake --build . -j $CPU_COUNT 54 | cmake --build . --target install 55 | 56 | rm -rf $PREFIX/{bin,share} 57 | rm -rf $PREFIX/lib/{cmake,pkgconfig} -------------------------------------------------------------------------------- /src/forge/pypi.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import ssl 4 | import sys 5 | from functools import lru_cache 6 | from urllib.request import urlopen 7 | 8 | import certifi 9 | 10 | START_YEAR = datetime.datetime.now().year - 3 11 | 12 | 13 | @lru_cache 14 | def get_pypi_releases(package_name): 15 | url = f"https://pypi.org/pypi/{package_name}/json" 16 | 17 | # ensure we're using a root certificate that works with PyPI 18 | context = ssl.create_default_context(cafile=certifi.where()) 19 | releases = json.load(urlopen(url, context=context))["releases"] 20 | 21 | return releases 22 | 23 | 24 | def get_pypi_versions(package_name, year=START_YEAR): 25 | """Return 'all versions' for the package. 26 | 27 | This isn't really "all" versions - it's all versions: 28 | * Published since `year` (last 3 years by default) 29 | * for which there is a macOS wheel published 30 | * for the current version of python 31 | 32 | :param name: The PyPI name of the package to query. 33 | """ 34 | releases = get_pypi_releases(package_name) 35 | 36 | versions = set() 37 | for version, release in releases.items(): 38 | for package in release: 39 | if ( 40 | package["packagetype"] == "bdist_wheel" 41 | and "-macosx_" in package["filename"] 42 | and int(package["upload_time"].split("-")[0]) >= year 43 | and not any(c.isalpha() for c in version) 44 | and package["python_version"] == f"cp3{sys.version_info.minor}" 45 | ): 46 | versions.add(version) 47 | 48 | return sorted(versions) 49 | 50 | 51 | @lru_cache 52 | def get_pypi_source_urls(package_name): 53 | """Get the download source URLs for a PyPI package. 54 | 55 | :param name: The PyPI name of the package to query. 56 | :returns: a dictionary URLs for of all non-yanked source distributions for the 57 | project, keyed by version number. 58 | """ 59 | releases = get_pypi_releases(package_name) 60 | 61 | urls = {} 62 | for version, release in releases.items(): 63 | for package in release: 64 | if package["packagetype"] == "sdist" and not package["yanked"]: 65 | urls[version] = package["url"] 66 | 67 | return urls 68 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=60"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "mobile-forge" 7 | version = "2023.0.0" 8 | description = "A tool to manage building cross-platform binary wheels for mobile devices" 9 | readme = { file = "README.md", content-type = "text/x-rst" } 10 | requires-python = ">=3.8" 11 | license = { file = "LICENSE" } 12 | authors = [{ name = "Russell Keith-Magee", email = "russell@keith-magee.com" }] 13 | maintainers = [ 14 | { name = "Russell Keith-Magee", email = "russell@keith-magee.com" }, 15 | ] 16 | classifiers = [ 17 | "Development Status :: 3 - Alpha", 18 | "Intended Audience :: Developers", 19 | "License :: OSI Approved :: BSD License", 20 | "Programming Language :: Python :: 3", 21 | "Programming Language :: Python :: 3.8", 22 | "Programming Language :: Python :: 3.9", 23 | "Programming Language :: Python :: 3.10", 24 | "Programming Language :: Python :: 3.11", 25 | "Programming Language :: Python :: 3.12", 26 | "Programming Language :: Python :: 3 :: Only", 27 | "Topic :: Software Development", 28 | ] 29 | dependencies = [ 30 | # Currently using a fork of crossenv to get iOS fixes. 31 | # Replace when/if these are merged and released. 32 | #"crossenv @ git+https://github.com/flet-dev/crossenv@ios-support", 33 | #"crossenv @ git+https://github.com/freakboy3742/crossenv@f0f07129eb06ea16d180650a26a02df2b948b888", 34 | "crossenv @ git+https://github.com/benfogle/crossenv@c801a526403a06f653939a0c45534d7703f9066f", 35 | "httpx == 0.27.0", 36 | "Jinja2 == 3.1.3", 37 | "jsonschema == 4.21.1", 38 | "packaging == 24.0", 39 | "PyYAML == 6.0.1", 40 | "tomli >= 2.0,< 3.0; python_version <= '3.10'", 41 | "wheel==0.43.0", 42 | ] 43 | 44 | [project.optional-dependencies] 45 | dev = ["pre-commit==3.7.0"] 46 | 47 | [project.urls] 48 | Homepage = "https://beeware.org" 49 | Funding = "https://beeware.org/contributing/membership/" 50 | Repository = "https://github.com/beeware/mobile-forge.git" 51 | Tracker = "https://github.com/beeware/briefcase/issues" 52 | 53 | [project.scripts] 54 | forge = "forge.__main__:main" 55 | forge-env = "forge.cross:main" 56 | 57 | [tool.isort] 58 | profile = "black" 59 | skip_glob = ["docs/conf.py", "venv*", "local"] 60 | multi_line_output = 3 61 | 62 | [tool.codespell] 63 | skip = '.git,*.pdf,*.svg' 64 | # the way to make case sensitive skips of words etc 65 | ignore-regex = '\bNd\b' 66 | # case insensitive 67 | # ignore-words-list = '' 68 | -------------------------------------------------------------------------------- /recipes/cryptography/test_cryptography.py: -------------------------------------------------------------------------------- 1 | def test_fernet(): 2 | from cryptography.fernet import Fernet 3 | 4 | key = Fernet.generate_key() 5 | f = Fernet(key) 6 | msg = b"my deep dark secret" 7 | token = f.encrypt(msg) 8 | assert f.decrypt(token) == msg 9 | 10 | 11 | def test_x509(): 12 | from textwrap import dedent 13 | 14 | from cryptography import x509 15 | from cryptography.hazmat.backends import default_backend 16 | from cryptography.x509.oid import NameOID 17 | 18 | cert_pem = dedent( 19 | """ 20 | -----BEGIN CERTIFICATE----- 21 | MIIEhDCCA2ygAwIBAgIIF2d9E030vlcwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UE 22 | BhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczElMCMGA1UEAxMc 23 | R29vZ2xlIEludGVybmV0IEF1dGhvcml0eSBHMzAeFw0xODA0MTcxMzI0MzhaFw0x 24 | ODA3MTAxMjM5MDBaMGkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh 25 | MRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKDApHb29nbGUgSW5jMRgw 26 | FgYDVQQDDA93d3cuYW5kcm9pZC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 27 | ggEKAoIBAQC3t8zd3s9oSLFUkogYhD//BoFwvtHnpUHW2n9g3KiAXCHHG5+8QD4Q 28 | abgAzrpeQqewWngE9B3Feq4rUo9vsk0UpB7Pj97TAgkkmpRMcW0lU4p4rKNhDfri 29 | c+SvnuZuy048v8Ta7DtMymuCIyejekjTg7Gf/U46PqK87ZbV5RTadSgfvlymnkQb 30 | SwJLUA8qe/H98bEARpQLyJvWi8dUSurpfKHdbXfd1Dk9GACHNAX9A4bV0BdQBmPu 31 | 6BMGeY5O4CYwwM51U/W+ptyc5eFRMi10up1cck3Udwl/jw5OAx5NP7geuxuIc4uu 32 | l41Zwbnr5v6sdJJsWMvMg7ot/97+EHvXAgMBAAGjggFDMIIBPzATBgNVHSUEDDAK 33 | BggrBgEFBQcDATAaBgNVHREEEzARgg93d3cuYW5kcm9pZC5jb20waAYIKwYBBQUH 34 | AQEEXDBaMC0GCCsGAQUFBzAChiFodHRwOi8vcGtpLmdvb2cvZ3NyMi9HVFNHSUFH 35 | My5jcnQwKQYIKwYBBQUHMAGGHWh0dHA6Ly9vY3NwLnBraS5nb29nL0dUU0dJQUcz 36 | MB0GA1UdDgQWBBSYOxV7LRH/9yKSFL5jLJfhwZxCUDAMBgNVHRMBAf8EAjAAMB8G 37 | A1UdIwQYMBaAFHfCuFCaZ3Z2sS3ChtCDoH6mfrpLMCEGA1UdIAQaMBgwDAYKKwYB 38 | BAHWeQIFAzAIBgZngQwBAgIwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovL2NybC5w 39 | a2kuZ29vZy9HVFNHSUFHMy5jcmwwDQYJKoZIhvcNAQELBQADggEBAI4fv5P+VLSE 40 | /f+hOoPuxWx2TEDdc/Gt2u3XUiGkMrOSW2k1ob0kUjBDILhear3tpp+V5N5H0NzZ 41 | Ymvpbbl3ZD5Bk5Co9FIJwFNMfGAlzSAduuYdAblOXTkLzlyLwn5qbzDjbkBIS+0O 42 | l+1zga+3gZGYbDQiByFyq8P/uAKzc0BAX82bgXDkIC3E26YvvTnUpkKh6l6bOOTB 43 | xaTg8Uh6KsKGch837BDbNegs3wHw3T3s7PC+H7dvqjELqN7y2GNNA361/aPPCWgs 44 | jUsy3XnYSd8og34IzY3+W2b3TrU8P+p+pBwOjgXuNHZwobU+3/e2s4/0AfDilpI0 45 | KX/1hroho1I= 46 | -----END CERTIFICATE----- 47 | """ 48 | ).encode("ASCII") 49 | cert = x509.load_pem_x509_certificate(cert_pem, default_backend()) 50 | domain = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value 51 | assert domain == "www.android.com" 52 | -------------------------------------------------------------------------------- /recipes/blis/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/blis/_src/frame/thread/bli_pthread.c b/blis/_src/frame/thread/bli_pthread.c 2 | index a099356..6d5fe03 100644 3 | --- a/blis/_src/frame/thread/bli_pthread.c 4 | +++ b/blis/_src/frame/thread/bli_pthread.c 5 | @@ -594,7 +594,7 @@ int bli_pthread_barrier_wait 6 | return 0; 7 | } 8 | 9 | -#elif defined(__APPLE__) || defined(_MSC_VER) // !defined(BLIS_DISABLE_SYSTEM) 10 | +#elif defined(__APPLE__) || defined(_MSC_VER) || defined(__ANDROID__) // !defined(BLIS_DISABLE_SYSTEM) 11 | 12 | #include 13 | 14 | diff --git a/blis/_src/include/linux-generic/blis.h b/blis/_src/include/linux-generic/blis.h 15 | index d5158ff..bf3fbe5 100644 16 | --- a/blis/_src/include/linux-generic/blis.h 17 | +++ b/blis/_src/include/linux-generic/blis.h 18 | @@ -1581,7 +1581,7 @@ typedef pthread_cond_t bli_pthread_cond_t; 19 | typedef pthread_condattr_t bli_pthread_condattr_t; 20 | typedef pthread_once_t bli_pthread_once_t; 21 | 22 | -#if defined(__APPLE__) 23 | +#if defined(__APPLE__) || defined(__ANDROID__) 24 | 25 | // For OS X, we must define the barrier types ourselves since Apple does 26 | // not implement barriers in their variant of pthreads. 27 | diff --git a/setup.py b/setup.py 28 | index d0944c9..6b3c19e 100644 29 | --- a/setup.py 30 | +++ b/setup.py 31 | @@ -21,6 +21,7 @@ import subprocess 32 | import sys 33 | import platform 34 | import numpy 35 | +import sysconfig 36 | 37 | 38 | PLATFORM_TO_ARCH = { 39 | @@ -36,6 +37,10 @@ PLATFORM_TO_ARCH = { 40 | 41 | MOD_NAMES = ["blis.cy", "blis.py"] 42 | 43 | +# Redirect the compiler to CC 44 | +os.environ["BLIS_ARCH"] = "generic" 45 | +os.environ["BLIS_COMPILER"] = os.environ["CC"] 46 | + 47 | print("BLIS_COMPILER?", os.environ.get("BLIS_COMPILER", "None")) 48 | 49 | 50 | @@ -220,6 +225,9 @@ class ExtensionBuilder(build_ext, build_ext_options): 51 | objects = [] 52 | platform_arch = platform + "-" + py_arch 53 | compiler = self.get_compiler_name() 54 | + host_triplet = sysconfig.get_platform().split("-") 55 | + print("Host triplet:", host_triplet) 56 | + 57 | with open(os.path.join(BLIS_DIR, "make", "%s.jsonl" % platform_arch)) as file_: 58 | env = {} 59 | for line in file_: 60 | @@ -255,6 +263,8 @@ class ExtensionBuilder(build_ext, build_ext_options): 61 | spec["flags"] = [ 62 | f for f in spec["flags"] if "visibility=hidden" not in f 63 | ] 64 | + if len(host_triplet) == 4 and host_triplet[0] == "ios": 65 | + spec["flags"].append(f"-mios-version-min={host_triplet[1]}") 66 | objects.append(self.build_object(env=env, **spec)) 67 | return objects 68 | 69 | -------------------------------------------------------------------------------- /recipes/flet-libopaque/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/makefile b/src/makefile 2 | index 42e70bb..d2759e5 100644 3 | --- a/src/makefile 4 | +++ b/src/makefile 5 | @@ -6,34 +6,35 @@ CFLAGS?=-march=native -Wall -O2 -g -fstack-protector-strong -D_FORTIFY_SOURCE=2 6 | -Warray-bounds -fsanitize=bounds -fsanitize-undefined-trap-on-error -ftrapv $(DEFINES) 7 | #-fstrict-flex-arrays 8 | CFLAGS+= -std=c99 -fpic 9 | -LDFLAGS=-g $(LIBS) 10 | +LDFLAGS+=-g $(LIBS) 11 | CC?=gcc 12 | AEXT=a 13 | SOVER=0 14 | +SOEXT=so 15 | 16 | AR?=ar 17 | 18 | -UNAME := $(shell uname -s) 19 | -ARCH := $(shell uname -m) 20 | -ifeq ($(UNAME),Darwin) 21 | - SOEXT=dylib 22 | - SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/libopaque.$(SOEXT) 23 | -else 24 | - CFLAGS+=-Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now \ 25 | - -fsanitize=signed-integer-overflow -fsanitize-undefined-trap-on-error 26 | - # -mbranch-protection=standard -fstrict-flex-arrays=3 27 | - SOEXT=so 28 | - SOFLAGS=-Wl,-soname,libopaque.$(SOEXT).$(SOVER) 29 | - ifeq ($(ARCH),x86_64) 30 | - CFLAGS+=-fcf-protection=full 31 | - endif 32 | - 33 | - ifeq ($(ARCH),parisc64) 34 | - else ifeq ($(ARCH),parisc64) 35 | - else 36 | - CFLAGS+=-fstack-clash-protection 37 | - endif 38 | -endif 39 | +# UNAME := $(shell uname -s) 40 | +# ARCH := $(shell uname -m) 41 | +# ifeq ($(UNAME),Darwin) 42 | +# SOEXT=dylib 43 | +# SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/libopaque.$(SOEXT) 44 | +# else 45 | +# CFLAGS+=-Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now \ 46 | +# -fsanitize=signed-integer-overflow -fsanitize-undefined-trap-on-error 47 | +# # -mbranch-protection=standard -fstrict-flex-arrays=3 48 | +# SOEXT=so 49 | +# SOFLAGS=-Wl,-soname,libopaque.$(SOEXT).$(SOVER) 50 | +# ifeq ($(ARCH),x86_64) 51 | +# CFLAGS+=-fcf-protection=full 52 | +# endif 53 | + 54 | +# ifeq ($(ARCH),parisc64) 55 | +# else ifeq ($(ARCH),parisc64) 56 | +# else 57 | +# CFLAGS+=-fstack-clash-protection 58 | +# endif 59 | +# endif 60 | 61 | SODIUM_NEWER_THAN_1_0_18 := $(shell pkgconf --atleast-version=1.0.19 libsodium; echo $$?) 62 | ifeq ($(SODIUM_NEWER_THAN_1_0_18),1) 63 | @@ -58,7 +59,7 @@ ifneq (, $(shell which pandoc)) 64 | endif 65 | 66 | 67 | -all: libopaque.$(SOEXT) libopaque.$(AEXT) tests utils/opaque $(MANPAGES) 68 | +all: libopaque.$(SOEXT) libopaque.$(AEXT) 69 | 70 | debug: DEFINES=-DTRACE -DNORANDOM 71 | debug: all 72 | @@ -154,8 +155,7 @@ man-uninstall: 73 | 74 | $(DESTDIR)$(PREFIX)/lib/libopaque.$(SOEXT): libopaque.$(SOEXT) 75 | mkdir -p $(DESTDIR)$(PREFIX)/lib 76 | - cp $< $@.$(SOVER) 77 | - ln -fs $@.$(SOVER) $@ 78 | + cp $< $@ 79 | 80 | $(DESTDIR)$(PREFIX)/lib/libopaque.$(AEXT): libopaque.$(AEXT) 81 | mkdir -p $(DESTDIR)$(PREFIX)/lib 82 | -------------------------------------------------------------------------------- /recipes/pillow/patches/setup-10.x.patch: -------------------------------------------------------------------------------- 1 | diff --git a/setup.py b/setup.py 2 | index 0abfaad..7b077d4 100644 3 | --- a/setup.py 4 | +++ b/setup.py 5 | @@ -342,9 +342,7 @@ class pil_build_ext(build_ext): 6 | return True if value in configuration.get(option, []) else None 7 | 8 | def initialize_options(self): 9 | - self.disable_platform_guessing = self.check_configuration( 10 | - "platform-guessing", "disable" 11 | - ) 12 | + self.disable_platform_guessing = True 13 | self.add_imaging_libs = "" 14 | build_ext.initialize_options(self) 15 | for x in self.feature: 16 | @@ -422,10 +420,22 @@ class pil_build_ext(build_ext): 17 | self.extensions.remove(extension) 18 | break 19 | 20 | - def get_macos_sdk_path(self): 21 | + def get_apple_sdk_path(self): 22 | try: 23 | + sdk = { 24 | + ("ios", False): ["--sdk", "iphoneos"], 25 | + ("ios", True): ["--sdk", "iphonesimulator"], 26 | + ("tvos", False): ["--sdk", "appletvos"], 27 | + ("tvos", True): ["--sdk", "appletvsimulator"], 28 | + ("watchos", False): ["--sdk", "watchos"], 29 | + ("watchos", True): ["--sdk", "watchsimulator"], 30 | + ("darwin", False): [], 31 | + }[ 32 | + sys.platform, 33 | + getattr(sys.implementation, "_multiarch", "").endswith("simulator"), 34 | + ] 35 | sdk_path = ( 36 | - subprocess.check_output(["xcrun", "--show-sdk-path"]) 37 | + subprocess.check_output(["xcrun", "--show-sdk-path"] + sdk) 38 | .strip() 39 | .decode("latin1") 40 | ) 41 | @@ -580,13 +590,18 @@ class pil_build_ext(build_ext): 42 | _add_directory(library_dirs, "/usr/X11/lib") 43 | _add_directory(include_dirs, "/usr/X11/include") 44 | 45 | - sdk_path = self.get_macos_sdk_path() 46 | + sdk_path = self.get_apple_sdk_path() 47 | if sdk_path: 48 | _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) 49 | _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) 50 | 51 | for extension in self.extensions: 52 | extension.extra_compile_args = ["-Wno-nullability-completeness"] 53 | + elif sys.platform in {"ios", "tvos", "watchos"}: 54 | + sdk_path = self.get_apple_sdk_path() 55 | + if sdk_path: 56 | + _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) 57 | + _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) 58 | elif ( 59 | sys.platform.startswith("linux") 60 | or sys.platform.startswith("gnu") 61 | -------------------------------------------------------------------------------- /recipes/shapely/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/setup.py b/setup.py 2 | index d49d722..ca3c433 100644 3 | --- a/setup.py 4 | +++ b/setup.py 5 | @@ -73,39 +73,39 @@ def get_geos_paths(): 6 | "libraries": ["geos_c"], 7 | } 8 | 9 | - geos_version = get_geos_config("--version") 10 | - if not geos_version: 11 | - log.warning( 12 | - "Could not find geos-config executable. Either append the path to geos-config" 13 | - " to PATH or manually provide the include_dirs, library_dirs, libraries and " 14 | - "other link args for compiling against a GEOS version >=%s.", 15 | - MIN_GEOS_VERSION, 16 | - ) 17 | - return {} 18 | - 19 | - def version_tuple(ver): 20 | - return tuple(int(itm) if itm.isnumeric() else itm for itm in ver.split(".")) 21 | - 22 | - if version_tuple(geos_version) < version_tuple(MIN_GEOS_VERSION): 23 | - raise ImportError( 24 | - f"GEOS version should be >={MIN_GEOS_VERSION}, found {geos_version}" 25 | - ) 26 | - 27 | - libraries = [] 28 | + # geos_version = get_geos_config("--version") 29 | + # if not geos_version: 30 | + # log.warning( 31 | + # "Could not find geos-config executable. Either append the path to geos-config" 32 | + # " to PATH or manually provide the include_dirs, library_dirs, libraries and " 33 | + # "other link args for compiling against a GEOS version >=%s.", 34 | + # MIN_GEOS_VERSION, 35 | + # ) 36 | + # return {} 37 | + 38 | + # def version_tuple(ver): 39 | + # return tuple(int(itm) if itm.isnumeric() else itm for itm in ver.split(".")) 40 | + 41 | + # if version_tuple(geos_version) < version_tuple(MIN_GEOS_VERSION): 42 | + # raise ImportError( 43 | + # f"GEOS version should be >={MIN_GEOS_VERSION}, found {geos_version}" 44 | + # ) 45 | + 46 | + libraries = ["geos", "geos_c"] 47 | library_dirs = [] 48 | include_dirs = ["./src"] 49 | - extra_link_args = [] 50 | - for item in get_geos_config("--cflags").split(): 51 | - if item.startswith("-I"): 52 | - include_dirs.extend(item[2:].split(":")) 53 | - 54 | - for item in get_geos_config("--clibs").split(): 55 | - if item.startswith("-L"): 56 | - library_dirs.extend(item[2:].split(":")) 57 | - elif item.startswith("-l"): 58 | - libraries.append(item[2:]) 59 | - else: 60 | - extra_link_args.append(item) 61 | + extra_link_args = ["-undefined", "dynamic_lookup"] if sys.platform == "ios" else [] 62 | + # for item in get_geos_config("--cflags").split(): 63 | + # if item.startswith("-I"): 64 | + # include_dirs.extend(item[2:].split(":")) 65 | + 66 | + # for item in get_geos_config("--clibs").split(): 67 | + # if item.startswith("-L"): 68 | + # library_dirs.extend(item[2:].split(":")) 69 | + # elif item.startswith("-l"): 70 | + # libraries.append(item[2:]) 71 | + # else: 72 | + # extra_link_args.append(item) 73 | 74 | return { 75 | "include_dirs": include_dirs, 76 | -------------------------------------------------------------------------------- /recipes/pyobjus/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/pyobjus/_runtime.h b/pyobjus/_runtime.h 2 | index c31b7ba..6d0cf85 100644 3 | --- a/pyobjus/_runtime.h 4 | +++ b/pyobjus/_runtime.h 5 | @@ -1,6 +1,6 @@ 6 | #include 7 | #include 8 | -#include 9 | +#include 10 | #include 11 | #include 12 | #include 13 | diff --git a/pyobjus/common.pxi b/pyobjus/common.pxi 14 | index 3a17bbb..4f43c6a 100644 15 | --- a/pyobjus/common.pxi 16 | +++ b/pyobjus/common.pxi 17 | @@ -109,7 +109,7 @@ cdef extern from "objc/runtime.h": 18 | objc_method_description* protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount) 19 | 20 | 21 | -cdef extern from "ffi/ffi.h": 22 | +cdef extern from "ffi.h": 23 | ctypedef unsigned long ffi_arg 24 | ctypedef signed long ffi_sarg 25 | ctypedef enum: FFI_TYPE_STRUCT 26 | diff --git a/setup.py b/setup.py 27 | index 0de7708..c8deb36 100644 28 | --- a/setup.py 29 | +++ b/setup.py 30 | @@ -20,13 +20,7 @@ if kivy_ios_root is not None: 31 | print("Pyobjus platform is {}".format(dev_platform)) 32 | 33 | # OSX 34 | -files = [] 35 | -if dev_platform == 'darwin': 36 | - files = ['pyobjus.pyx'] 37 | -# iOS 38 | -elif dev_platform == 'ios': 39 | - files = ['pyobjus.c'] 40 | - 41 | +files = ['pyobjus.pyx'] 42 | 43 | class PyObjusBuildExt(build_ext, object): 44 | 45 | @@ -43,13 +37,10 @@ class PyObjusBuildExt(build_ext, object): 46 | # The following essentially supply a dynamically generated subclass 47 | # that mix in the cython version of build_ext so that the 48 | # functionality provided will also be executed. 49 | - if dev_platform != 'ios': 50 | - from Cython.Distutils import build_ext as cython_build_ext 51 | - build_ext_cls = type( 52 | - 'PyObjusBuildExt', (PyObjusBuildExt, cython_build_ext), {}) 53 | - return super(PyObjusBuildExt, cls).__new__(build_ext_cls) 54 | - else: 55 | - return super(PyObjusBuildExt, cls).__new__(cls) 56 | + from Cython.Distutils import build_ext as cython_build_ext 57 | + build_ext_cls = type( 58 | + 'PyObjusBuildExt', (PyObjusBuildExt, cython_build_ext), {}) 59 | + return super(PyObjusBuildExt, cls).__new__(build_ext_cls) 60 | 61 | def build_extensions(self): 62 | # create a configuration file for pyobjus (export the platform) 63 | @@ -57,11 +48,9 @@ class PyObjusBuildExt(build_ext, object): 64 | config_pxi_need_update = True 65 | config_pxi = 'DEF PLATFORM = "{}"\n'.format(dev_platform) 66 | config_pxi += 'DEF ARCH = "{}"\n'.format(arch) 67 | - if dev_platform == 'ios': 68 | - cython3 = False # Assume Cython 0.29, which is what we use for kivy-ios (ATM) 69 | - else: 70 | - import Cython 71 | - cython3 = Cython.__version__.startswith('3.') 72 | + 73 | + import Cython 74 | + cython3 = Cython.__version__.startswith('3.') 75 | config_pxi += f"DEF PYOBJUS_CYTHON_3 = {cython3}" 76 | if exists(config_pxi_fn): 77 | with open(config_pxi_fn) as fd: 78 | @@ -73,7 +62,7 @@ class PyObjusBuildExt(build_ext, object): 79 | super().build_extensions() 80 | 81 | 82 | -libraries = ['ffi'] 83 | +libraries = ['ffi', 'objc'] 84 | library_dirs = [] 85 | extra_compile_args = [] 86 | extra_link_args = [] 87 | -------------------------------------------------------------------------------- /.ci/publish-wheels.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import hashlib 3 | import os 4 | import zipfile 5 | from argparse import ArgumentParser 6 | 7 | import boto3 8 | 9 | 10 | def get_content_sha256(content): 11 | hasher = hashlib.sha256() 12 | hasher.update(content) 13 | return hasher.hexdigest() 14 | 15 | 16 | def get_file_sha256(filepath): 17 | with open(filepath, "rb") as f: 18 | return get_content_sha256(f.read()) 19 | 20 | 21 | def upload_file( 22 | s3_client, bucket_name, local_file_path, remote_file_path, wheel_hash, metadata_hash 23 | ): 24 | """Uploads a file to Cloudflare S2 storage with SHA256 hash in metadata.""" 25 | try: 26 | with open(local_file_path, "rb") as f: 27 | metadata = {"wheel_hash": wheel_hash, "metadata_hash": metadata_hash} 28 | s3_client.upload_fileobj( 29 | f, 30 | bucket_name, 31 | remote_file_path, 32 | ExtraArgs=dict(Metadata=metadata), 33 | ) 34 | print(f"Upload successful for {local_file_path}") 35 | except Exception as e: 36 | print(f"Error uploading {local_file_path}: {e}") 37 | 38 | 39 | def main(): 40 | parser = ArgumentParser( 41 | description="Upload files to Cloudflare S2 storage with SHA256 hash in metadata" 42 | ) 43 | parser.add_argument( 44 | "dist_dir", 45 | help="Path to the directory containing files to upload (absolute or relative)", 46 | ) 47 | args = parser.parse_args() 48 | 49 | # Get credentials from environment variables 50 | cf_access_key_id = os.environ.get("CF_ACCESS_KEY_ID") 51 | cf_secret_access_key = os.environ.get("CF_SECRET_ACCESS_KEY") 52 | cf_endpoint_url = os.environ.get("CF_ENDPOINT_URL") 53 | cf_bucket_name = os.environ.get("CF_BUCKET_NAME") 54 | 55 | # Check if required environment variables are set 56 | if not all( 57 | [cf_access_key_id, cf_secret_access_key, cf_endpoint_url, cf_bucket_name] 58 | ): 59 | print( 60 | "Error: Missing required environment variables. Please set CF_ACCESS_KEY_ID, CF_SECRET_ACCESS_KEY, CF_ENDPOINT_URL, CF_BUCKET_NAME" 61 | ) 62 | exit(1) 63 | 64 | # Resolve the provided path to absolute relative to the current directory 65 | dist_dir = os.path.abspath(args.dist_dir) 66 | 67 | # Check if the directory exists 68 | if not os.path.isdir(dist_dir): 69 | print(f"Error: Directory not found: {dist_dir}") 70 | exit(1) 71 | 72 | # Create S3 client with Cloudflare R2 endpoint 73 | s3_client = boto3.client( 74 | "s3", 75 | endpoint_url=cf_endpoint_url, 76 | aws_access_key_id=cf_access_key_id, 77 | aws_secret_access_key=cf_secret_access_key, 78 | ) 79 | 80 | # Loop through files in the directory 81 | for wheel in glob.glob(f"{dist_dir}/*.whl"): 82 | remote_wheel_path = os.path.basename(wheel) 83 | 84 | # extract and upload metadata 85 | with zipfile.ZipFile(wheel) as z: 86 | metadata_filename = next( 87 | filter(lambda f: f.endswith(".dist-info/METADATA"), z.namelist()) 88 | ) 89 | with z.open(metadata_filename) as f: 90 | metadata = f.read() 91 | s3_client.put_object( 92 | Key=f"{remote_wheel_path}.metadata", 93 | Body=metadata, 94 | Bucket=cf_bucket_name, 95 | ContentType="application/octet-stream", 96 | ) 97 | 98 | # upload wheel 99 | upload_file( 100 | s3_client, 101 | cf_bucket_name, 102 | wheel, 103 | remote_wheel_path, 104 | wheel_hash=get_file_sha256(wheel), 105 | metadata_hash=get_content_sha256(metadata), 106 | ) 107 | 108 | print("All files uploaded!") 109 | 110 | 111 | if __name__ == "__main__": 112 | main() 113 | -------------------------------------------------------------------------------- /src/forge/schema/meta-schema.yaml: -------------------------------------------------------------------------------- 1 | # A subset of conda syntax 2 | # (https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html). 3 | 4 | type: object 5 | required: [package] 6 | properties: 7 | 8 | package: 9 | type: object 10 | required: [name, version] 11 | properties: 12 | name: # Must be in its original form, as used in sdist filenames. 13 | type: string 14 | version: 15 | type: [string, number] 16 | additionalProperties: false 17 | 18 | source: 19 | default: pypi 20 | oneOf: 21 | - type: "null" # The build script will get its own source. 22 | - type: string # Download an sdist from PyPI. 23 | const: pypi 24 | - type: object # Download an archive from a URL. 25 | required: [url] 26 | properties: 27 | url: 28 | type: string 29 | additionalProperties: false 30 | - type: object # Clone a Git repository. 31 | required: [git_url, git_rev] 32 | properties: 33 | git_url: 34 | type: string 35 | git_rev: 36 | type: [string, number] 37 | additionalProperties: false 38 | - type: object # Copy a local directory. 39 | required: [path] 40 | properties: 41 | path: 42 | type: string 43 | additionalProperties: false 44 | 45 | # Patches to apply to the code. Each entry is a filename in the `patches` folder 46 | # of the recipe. 47 | patches: 48 | type: array 49 | default: [] 50 | items: 51 | type: string 52 | 53 | build: 54 | type: object 55 | default: {} 56 | properties: 57 | number: # Used as the wheel build tag. 58 | type: integer 59 | default: 0 60 | script_env: # Environment variables in the form KEY=value (no spaces around =). 61 | type: object 62 | default: {} 63 | additionalProperties: true 64 | 65 | requirements: 66 | type: object 67 | default: {} 68 | properties: 69 | 70 | # Requirements which must be installed in the build environment. One of the following: 71 | # 72 | # * ` `: A Python package. 73 | # * `cmake`: indicates that CMake is used in the build. A `chaquopy.toolchain.cmake` file 74 | # will be generated in the build directory for use with `-DCMAKE_TOOLCHAIN_FILE`. 75 | build: 76 | type: array 77 | default: [] 78 | items: 79 | type: string 80 | 81 | # Requirements which must be available at runtime. One of the following: 82 | # 83 | # * ` `: a native Python package. A compatible wheel file must exist in 84 | # pypi/dist, and will be extracted into $SRC_DIR/../requirements before the build is 85 | # run. A requirement specification for >= this version will also be added to the final 86 | # wheel. 87 | # 88 | # * `python`: indicates that this is a Python package. This is implied if `source` is 89 | # `pypi` or unspecified. Python includes and libraries will be added to the CFLAGS and 90 | # LDFLAGS, and the wheel build tag will be set accordingly. 91 | # 92 | # * `openssl` / `sqlite`: the corresponding library will be added to CFLAGS and LDFLAGS. 93 | host: 94 | type: array 95 | default: [] 96 | items: 97 | type: string 98 | 99 | additionalProperties: false 100 | 101 | about: 102 | type: object 103 | default: {} 104 | properties: 105 | 106 | # Filename, relative to the source directory, to add to the wheel's .dist-info 107 | # directory. build-wheel will automatically include any file in the source or recipe 108 | # directory whose name starts with "LICEN[CS]E" or "COPYING", case-insensitive. If there 109 | # is no such file, then this setting is required. 110 | license_file: 111 | type: string 112 | default: "" 113 | 114 | additionalProperties: false 115 | 116 | additionalProperties: false 117 | -------------------------------------------------------------------------------- /.ci/rebuild-simple-index.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | import boto3 5 | 6 | html_header = "\n" 7 | html_root_anchor = '{0}
\n' 8 | html_package_anchor = '{key}
\n' 9 | html_footer = "\n" 10 | 11 | 12 | def upload_file(s3_client, bucket_name, local_file_path, remote_file_path): 13 | """Uploads a file to Cloudflare S2 storage with SHA256 hash in metadata.""" 14 | try: 15 | with open(local_file_path, "rb") as f: 16 | s3_client.upload_fileobj(f, bucket_name, remote_file_path) 17 | print(f"Upload successful for {local_file_path}") 18 | except Exception as e: 19 | print(f"Error uploading {local_file_path}: {e}") 20 | 21 | 22 | def main(): 23 | # Get credentials from environment variables 24 | cf_access_key_id = os.environ.get("CF_ACCESS_KEY_ID") 25 | cf_secret_access_key = os.environ.get("CF_SECRET_ACCESS_KEY") 26 | cf_endpoint_url = os.environ.get("CF_ENDPOINT_URL") 27 | cf_bucket_name = os.environ.get("CF_BUCKET_NAME") 28 | 29 | # Check if required environment variables are set 30 | if not all( 31 | [cf_access_key_id, cf_secret_access_key, cf_endpoint_url, cf_bucket_name] 32 | ): 33 | print( 34 | "Error: Missing required environment variables. Please set CF_ACCESS_KEY_ID, CF_SECRET_ACCESS_KEY, CF_ENDPOINT_URL, CF_BUCKET_NAME" 35 | ) 36 | exit(1) 37 | 38 | # Create S3 client with Cloudflare R2 endpoint 39 | s3_client = boto3.client( 40 | "s3", 41 | endpoint_url=cf_endpoint_url, 42 | aws_access_key_id=cf_access_key_id, 43 | aws_secret_access_key=cf_secret_access_key, 44 | ) 45 | 46 | def normalize(name): 47 | return re.sub(r"[-_.]+", "-", name).lower() 48 | 49 | index = {} 50 | 51 | for obj in s3_client.list_objects(Bucket=cf_bucket_name)["Contents"]: 52 | key = obj["Key"] 53 | if key.endswith("/index.html"): 54 | parts = key.split("/") 55 | if len(parts) > 2: 56 | package_name = parts[1] 57 | if not package_name in index: 58 | index[package_name] = [] 59 | elif key.endswith(".whl"): 60 | print(key) 61 | package_name = normalize(key.split("-")[0]) 62 | wheels = index.get(package_name, None) 63 | if wheels is None: 64 | wheels = [] 65 | index[package_name] = wheels 66 | metadata = s3_client.head_object(Bucket=cf_bucket_name, Key=obj["Key"]) 67 | wheels.append( 68 | { 69 | "key": key, 70 | "wheel_hash": metadata["Metadata"].get("wheel_hash", ""), 71 | "metadata_hash": metadata["Metadata"].get("metadata_hash", ""), 72 | } 73 | ) 74 | 75 | print("Writing root index") 76 | packages = [ 77 | html_root_anchor.format(p) 78 | for p in sorted([p[0] for p in index.items() if len(p[1]) > 0]) 79 | ] 80 | lines = [html_header] + packages + [html_footer] 81 | s3_client.put_object( 82 | Key="simple/index.html", 83 | Body="\n".join(lines).encode("utf8"), 84 | Bucket=cf_bucket_name, 85 | ContentType="text/html", 86 | ) 87 | 88 | print("Updating package indexes") 89 | for package_name, files in index.items(): 90 | files.sort(key=lambda f: f["key"]) 91 | versions = [ 92 | html_package_anchor.format( 93 | key=f["key"], 94 | wheel_hash=f["wheel_hash"], 95 | metadata_hash=f["metadata_hash"], 96 | ) 97 | for f in files 98 | ] 99 | key = f"simple/{package_name}/index.html" 100 | if len(versions) == 0: 101 | print("Deleting index", key) 102 | s3_client.delete_object(Key=key, Bucket=cf_bucket_name) 103 | else: 104 | print("Updating index", key) 105 | lines = [html_header] + versions + [html_footer] 106 | s3_client.put_object( 107 | Key=key, 108 | Body="\n".join(lines).encode("utf8"), 109 | Bucket=cf_bucket_name, 110 | ContentType="text/html", 111 | ) 112 | 113 | 114 | if __name__ == "__main__": 115 | main() 116 | -------------------------------------------------------------------------------- /src/forge/package.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import sys 4 | from copy import deepcopy 5 | from pathlib import Path 6 | 7 | import jinja2 8 | import jsonschema 9 | import yaml 10 | 11 | from forge.build import Builder, PythonPackageBuilder, SimplePackageBuilder 12 | from forge.cross import CrossVEnv 13 | 14 | 15 | class Package: 16 | def __init__( 17 | self, 18 | package_name_or_recipe: str, 19 | version: str | None, 20 | build_number: int | None, 21 | sdk: str, 22 | sdk_version: str, 23 | arch: str, 24 | ): 25 | self.sdk = sdk 26 | self.sdk_version = sdk_version 27 | self.arch = arch 28 | 29 | if "/" in package_name_or_recipe: 30 | self.recipe_path = Path(package_name_or_recipe) 31 | else: 32 | self.recipe_path = Path.cwd() / "recipes" / package_name_or_recipe 33 | 34 | if not (self.recipe_path / "meta.yaml").exists(): 35 | raise ValueError( 36 | f"{package_name_or_recipe} does not appear to be a valid recipe." 37 | ) 38 | 39 | self.meta = self.load_meta( 40 | override_version=version, override_build=build_number 41 | ) 42 | 43 | # Extract some useful properties from the metadata 44 | self.name = self.meta["package"]["name"] 45 | self.version = self.meta["package"]["version"] 46 | 47 | def __str__(self): 48 | return f"{self.name} {self.version}" 49 | 50 | def load_meta(self, override_version, override_build): 51 | # http://python-jsonschema.readthedocs.io/en/latest/faq/ 52 | def with_defaults(validator_cls): 53 | def set_defaults(validator, properties, instance, schema): 54 | for name, subschema in properties.items(): 55 | if "default" in subschema: 56 | instance.setdefault(name, deepcopy(subschema["default"])) 57 | yield from validator_cls.VALIDATORS["properties"]( 58 | validator, properties, instance, schema 59 | ) 60 | 61 | return jsonschema.validators.extend( 62 | validator_cls, {"properties": set_defaults} 63 | ) 64 | 65 | # Validate the meta-schema 66 | Validator = jsonschema.Draft4Validator 67 | with (Path(__file__).parent / "schema" / "meta-schema.yaml").open( 68 | encoding="utf-8" 69 | ) as f: 70 | schema = yaml.safe_load(f) 71 | Validator.check_schema(schema) 72 | 73 | with (self.recipe_path / "meta.yaml").open(encoding="utf-8") as f: 74 | meta_template = f.read() 75 | 76 | # Render the meta template. 77 | meta_str = jinja2.Template(meta_template).render( 78 | sdk=self.sdk, 79 | sdk_version=self.sdk_version, 80 | arch=self.arch, 81 | version=( 82 | tuple(int(v) for v in override_version.split(".")) 83 | if override_version 84 | else None 85 | ), 86 | py_version=sys.version_info, 87 | ) 88 | 89 | # Parse the rendered meta template 90 | meta = yaml.safe_load(meta_str) 91 | 92 | # If there's a version override, set it in the package metadata. 93 | # If there's a build number override, set it; otherwise purge 94 | # the build number (since it won't match the override version) 95 | if override_version: 96 | try: 97 | meta["package"]["version"] = override_version 98 | if override_build: 99 | meta.setdefault("build", {})["number"] = override_build 100 | else: 101 | del meta["build"]["number"] 102 | except KeyError: 103 | pass 104 | 105 | # Validate the metadata against the schema. 106 | with_defaults(Validator)(schema).validate(meta) 107 | 108 | return meta 109 | 110 | def builder(self, cross_venv: CrossVEnv) -> Builder: 111 | """Return a builder for this package in the given cross-platform environment. 112 | 113 | :param cross_venv: The cross-platform environment to use for the build 114 | :returns: A builder for the package. 115 | """ 116 | if (self.recipe_path / "build.sh").exists(): 117 | return SimplePackageBuilder(cross_venv=cross_venv, package=self) 118 | else: 119 | return PythonPackageBuilder(cross_venv=cross_venv, package=self) 120 | -------------------------------------------------------------------------------- /make_dep_wheels.py: -------------------------------------------------------------------------------- 1 | """make_dep_wheels.py. 2 | 3 | A utility script for converting the "installed" versions of dependencies into wheels 4 | that can be referenced during forge builds. 5 | 6 | You should not need to invoke this script directly; it should be called by `./setup- 7 | iOS.sh` when creating a new forge environment. 8 | """ 9 | 10 | import os 11 | import re 12 | import shutil 13 | import subprocess 14 | import sys 15 | import tempfile 16 | from pathlib import Path 17 | 18 | 19 | def make_wheel(package, os_name, target): 20 | """Create a target-specific wheel for a given package. 21 | 22 | Requires that PYTHON_APPLE_SUPPORT is set in the environment, and that variable 23 | points to a completed support build. 24 | 25 | :param package: The name of the package to build (e.g., "BZip2") 26 | :param os_name: The OS name to target (e.g., "iOS") 27 | :param target: The target specifier (e.g., "iphoneos.arm64") 28 | """ 29 | support = Path( 30 | os.environ[ 31 | ( 32 | "MOBILE_FORGE_ANDROID_SUPPORT_PATH" 33 | if os_name == "android" 34 | else "MOBILE_FORGE_IOS_SUPPORT_PATH" 35 | ) 36 | ] 37 | ) 38 | 39 | versions_file = ( 40 | support 41 | / "support" 42 | / ".".join(sys.version.split(".")[:2]) 43 | / os_name 44 | / "VERSIONS" 45 | ) 46 | with versions_file.open(encoding="utf-8") as f: 47 | versions = f.read() 48 | 49 | package_version_build = re.search( 50 | rf"^{package}: (.*)", versions, re.MULTILINE | re.IGNORECASE 51 | )[1] 52 | min_version = re.search(rf"^Min {os_name} version: (.*)", versions, re.MULTILINE)[1] 53 | 54 | package_version, package_build = package_version_build.split("-") 55 | 56 | target_parts = target.split(".") 57 | target_parts.reverse() 58 | wheel_target = "_".join(target_parts) 59 | wheel_tag = f"py3-none-{os_name}_{min_version}_{wheel_target.replace('-', '_')}".lower().replace( 60 | ".", "_" 61 | ) 62 | 63 | wheel_file = ( 64 | Path("dist") / f"{package.lower()}-{package_version_build}-{wheel_tag}.whl" 65 | ) 66 | if wheel_file.exists(): 67 | print(f"{wheel_file} already exists") 68 | return 69 | 70 | install_path = ( 71 | support 72 | / "install" 73 | / os_name 74 | / target 75 | / f"{package.lower()}-{package_version_build}" 76 | ) 77 | if not install_path.exists(): 78 | print( 79 | f"Cannot build {target} wheel for {package}; can't find installed version in {install_path}" 80 | ) 81 | sys.exit(1) 82 | 83 | with tempfile.TemporaryDirectory(dir=".") as tmp: 84 | wheel_path = Path(tmp) 85 | distinfo_path = wheel_path / f"{package.lower()}-{package_version}.dist-info" 86 | distinfo_path.mkdir() 87 | 88 | # Copy the installed content. 89 | # TODO: Enable ignore_dangling_symlinks because of https://github.com/beeware/cpython-android-source-deps/issues/2 90 | shutil.copytree(install_path, wheel_path / "opt", ignore_dangling_symlinks=True) 91 | 92 | # Write package metadata 93 | with (distinfo_path / "METADATA").open("w", encoding="utf-8") as f: 94 | f.write( 95 | "\n".join( 96 | [ 97 | "Metadata-Version: 1.2", 98 | f"Name: {package.lower()}", 99 | f"Version: {package_version}", 100 | "Summary: ", 101 | "Download-URL: ", 102 | ] 103 | ) 104 | ) 105 | 106 | # Write wheel metadata 107 | with (distinfo_path / "WHEEL").open("w", encoding="utf-8") as f: 108 | f.write( 109 | "\n".join( 110 | [ 111 | "Wheel-Version: 1.0", 112 | "Root-Is-Purelib: false", 113 | "Generator: Mobile-Forge.BeeWare", 114 | f"Build: {package_build}", 115 | f"Tag: {wheel_tag}", 116 | ] 117 | ) 118 | ) 119 | 120 | # Ensure the dist folder exists 121 | Path("dist").mkdir(exist_ok=True) 122 | 123 | # Pack the wheel 124 | subprocess.run( 125 | [ 126 | sys.executable, 127 | "-m", 128 | "wheel", 129 | "pack", 130 | "--dest-dir", 131 | "dist", 132 | wheel_path, 133 | ] 134 | ) 135 | 136 | 137 | if __name__ == "__main__": 138 | os_name = sys.argv[1] 139 | for target in { 140 | "android": ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"], 141 | "iOS": [ 142 | "iphoneos.arm64", 143 | "iphonesimulator.arm64", 144 | "iphonesimulator.x86_64", 145 | ], 146 | }[os_name]: 147 | for dep in ["BZip2", "XZ", "libFFI", "OpenSSL"]: 148 | make_wheel(dep, os_name, target) 149 | -------------------------------------------------------------------------------- /recipes/flet-liboprf/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/makefile b/src/makefile 2 | index f7819af..b76083f 100644 3 | --- a/src/makefile 4 | +++ b/src/makefile 5 | @@ -7,33 +7,33 @@ CFLAGS?=-march=native -Wall -O2 -g \ 6 | -fstack-protector-strong -fasynchronous-unwind-tables -fpic \ 7 | -ftrapv -D_GLIBCXX_ASSERTIONS $(DEFINES) 8 | 9 | -LDFLAGS?=-lsodium -loprf-noiseXK -Lnoise_xk 10 | +LDFLAGS_PRIVATE=$(LDFLAGS) -lsodium -loprf-noiseXK -Lnoise_xk 11 | CC?=gcc 12 | SOEXT?=so 13 | STATICEXT?=a 14 | SOVER=0 15 | 16 | -UNAME := $(shell uname -s) 17 | -ARCH := $(shell uname -m) 18 | -ifeq ($(UNAME),Darwin) 19 | - SOEXT=dylib 20 | - SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/liboprf.$(SOEXT) 21 | -else 22 | - CFLAGS+=-Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now -Wtrampolines \ 23 | - -fsanitize=signed-integer-overflow -fsanitize-undefined-trap-on-error 24 | - #-fstrict-flex-arrays=3 -mbranch-protection=standard 25 | - SOEXT=so 26 | - SOFLAGS=-Wl,-soname,liboprf.$(SOEXT).$(SOVER) 27 | - ifeq ($(ARCH),x86_64) 28 | - CFLAGS+=-fcf-protection=full 29 | - endif 30 | - 31 | - ifeq ($(ARCH),parisc64) 32 | - else ifeq ($(ARCH),parisc64) 33 | - else 34 | - CFLAGS+=-fstack-clash-protection 35 | - endif 36 | -endif 37 | +# UNAME := $(shell uname -s) 38 | +# ARCH := $(shell uname -m) 39 | +# ifeq ($(UNAME),Darwin) 40 | +# SOEXT=dylib 41 | +# SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/liboprf.$(SOEXT) 42 | +# else 43 | +# CFLAGS+=-Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now -Wtrampolines \ 44 | +# -fsanitize=signed-integer-overflow -fsanitize-undefined-trap-on-error 45 | +# #-fstrict-flex-arrays=3 -mbranch-protection=standard 46 | +# SOEXT=so 47 | +# SOFLAGS=-Wl,-soname,liboprf.$(SOEXT).$(SOVER) 48 | +# ifeq ($(ARCH),x86_64) 49 | +# CFLAGS+=-fcf-protection=full 50 | +# endif 51 | + 52 | +# ifeq ($(ARCH),parisc64) 53 | +# else ifeq ($(ARCH),parisc64) 54 | +# else 55 | +# CFLAGS+=-fstack-clash-protection 56 | +# endif 57 | +# endif 58 | 59 | CFLAGS+=$(INCLUDES) 60 | 61 | @@ -55,16 +55,16 @@ asan: 62 | else 63 | CFLAGS+=-fstack-clash-protection 64 | endif 65 | -asan: LDFLAGS+= -fsanitize=address -static-libasan 66 | +asan: LDFLAGS_PRIVATE+= -fsanitize=address -static-libasan 67 | asan: all 68 | 69 | AR ?= ar 70 | 71 | liboprf.$(SOEXT): $(SOURCES) noise_xk/liboprf-noiseXK.$(SOEXT) 72 | - $(CC) $(CFLAGS) -fPIC -shared $(SOFLAGS) -o $@ $^ $(LDFLAGS) 73 | + $(CC) $(CFLAGS) -fPIC -shared $(SOFLAGS) -o $@ $(SOURCES) $(LDFLAGS_PRIVATE) 74 | 75 | liboprf-corrupt-dkg.$(SOEXT): $(SOURCES) noise_xk/liboprf-noiseXK.$(SOEXT) 76 | - $(CC) $(CFLAGS) -DUNITTEST -DUNITTEST_CORRUPT -fPIC -shared $(SOFLAGS) -o $@ $^ $(LDFLAGS) 77 | + $(CC) $(CFLAGS) -DUNITTEST -DUNITTEST_CORRUPT -fPIC -shared $(SOFLAGS) -o $@ $(SOURCES) $(LDFLAGS_PRIVATE) 78 | 79 | liboprf.$(STATICEXT): $(OBJECTS) 80 | $(AR) rcs $@ $^ 81 | @@ -98,8 +98,7 @@ uninstall-noiseXK: 82 | 83 | $(DESTDIR)$(PREFIX)/lib/liboprf.$(SOEXT): liboprf.$(SOEXT) 84 | mkdir -p $(DESTDIR)$(PREFIX)/lib 85 | - cp $< $@.$(SOVER) 86 | - ln -sf $@.$(SOVER) $@ 87 | + cp $< $@ 88 | 89 | $(DESTDIR)$(PREFIX)/lib/liboprf.$(STATICEXT): liboprf.$(STATICEXT) 90 | mkdir -p $(DESTDIR)$(PREFIX)/lib 91 | diff --git a/src/noise_xk/makefile b/src/noise_xk/makefile 92 | index 8d69ae9..b8fc7b9 100644 93 | --- a/src/noise_xk/makefile 94 | +++ b/src/noise_xk/makefile 95 | @@ -1,5 +1,5 @@ 96 | PREFIX?=/usr/local 97 | -LDFLAGS=-lsodium 98 | +LDFLAGS_PRIVATE=$(LDFLAGS) -lsodium 99 | SOURCES=src/Noise_XK.c src/XK.c 100 | 101 | CFLAGS += -Iinclude -I include/karmel -I include/karmel/minimal \ 102 | @@ -17,26 +17,26 @@ SOEXT?=so 103 | STATICEXT?=a 104 | SOVER=0 105 | 106 | -UNAME := $(shell uname -s) 107 | -ARCH := $(shell uname -m) 108 | -ifeq ($(UNAME),Darwin) 109 | - SOEXT=dylib 110 | - SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/liboprf-noiseXK.$(SOEXT) 111 | -else 112 | - ifeq ($(shell uname),Linux) 113 | - CFLAGS += -Wl,--error-unresolved-symbols -Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack 114 | - SOEXT=so 115 | - SOFLAGS=-Wl,-soname,liboprf-noiseXK.$(SOEXT).$(SOVER) 116 | - endif 117 | - ifeq ($(ARCH),x86_64) 118 | - CFLAGS+=-fcf-protection=full 119 | - endif 120 | - ifeq ($(ARCH),parisc64) 121 | - else ifeq ($(ARCH),parisc64) 122 | - else 123 | - CFLAGS+=-fstack-clash-protection 124 | - endif 125 | -endif 126 | +# UNAME := $(shell uname -s) 127 | +# ARCH := $(shell uname -m) 128 | +# ifeq ($(UNAME),Darwin) 129 | +# SOEXT=dylib 130 | +# SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/liboprf-noiseXK.$(SOEXT) 131 | +# else 132 | +# ifeq ($(shell uname),Linux) 133 | +# CFLAGS += -Wl,--error-unresolved-symbols -Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack 134 | +# SOEXT=so 135 | +# SOFLAGS=-Wl,-soname,liboprf-noiseXK.$(SOEXT).$(SOVER) 136 | +# endif 137 | +# ifeq ($(ARCH),x86_64) 138 | +# CFLAGS+=-fcf-protection=full 139 | +# endif 140 | +# ifeq ($(ARCH),parisc64) 141 | +# else ifeq ($(ARCH),parisc64) 142 | +# else 143 | +# CFLAGS+=-fstack-clash-protection 144 | +# endif 145 | +# endif 146 | 147 | OBJS += $(patsubst %.c,%.o,$(SOURCES)) 148 | 149 | @@ -48,7 +48,7 @@ AR ?= ar 150 | $(AR) rcs $@ $^ 151 | 152 | %.$(SOEXT): $(OBJS) 153 | - $(CC) $(CFLAGS) -fPIC -shared $(SOFLAGS) -o $@ $^ $(LDFLAGS) 154 | + $(CC) $(CFLAGS) -fPIC -shared $(SOFLAGS) -o $@ $^ $(LDFLAGS_PRIVATE) 155 | 156 | clean: 157 | rm -rf *.so *.a src/*.o 158 | @@ -64,8 +64,7 @@ uninstall: $(DESTDIR)$(PREFIX)/lib/liboprf-noiseXK.$(SOEXT) $(DESTDIR)$(PREFIX)/ 159 | 160 | $(DESTDIR)$(PREFIX)/lib/liboprf-noiseXK.$(SOEXT): liboprf-noiseXK.$(SOEXT) 161 | mkdir -p $(DESTDIR)$(PREFIX)/lib/ 162 | - cp $< $@.$(SOVER) 163 | - ln -sf $@.$(SOVER) $@ 164 | + cp $< $@ 165 | 166 | $(DESTDIR)$(PREFIX)/lib/liboprf-noiseXK.$(STATICEXT): liboprf-noiseXK.$(STATICEXT) 167 | mkdir -p $(DESTDIR)$(PREFIX)/lib/ 168 | -------------------------------------------------------------------------------- /recipes/pyjnius/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/jnius/env.py b/jnius/env.py 2 | index ea12d82..4111845 100644 3 | --- a/jnius/env.py 4 | +++ b/jnius/env.py 5 | @@ -288,7 +288,7 @@ class MacOsXJavaLocation(UnixJavaLocation): 6 | 7 | class AndroidJavaLocation(UnixJavaLocation): 8 | def get_libraries(self): 9 | - return ['SDL2', 'log'] 10 | + return ['log', 'pyjni'] 11 | 12 | def get_include_dirs(self): 13 | # When cross-compiling for Android, we should not use the include dirs 14 | diff --git a/jnius/jnius_conversion.pxi b/jnius/jnius_conversion.pxi 15 | index 2e0b48d..e474445 100644 16 | --- a/jnius/jnius_conversion.pxi 17 | +++ b/jnius/jnius_conversion.pxi 18 | @@ -719,7 +719,7 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except 19 | 20 | elif definition[0] == 'L': 21 | defstr = str_for_c(definition[1:-1]) 22 | - j_class = j_env[0].FindClass(j_env, defstr) 23 | + j_class = PyJni_FindClass(defstr) 24 | 25 | if j_class == NULL: 26 | raise JavaException( 27 | diff --git a/jnius/jnius_export_class.pxi b/jnius/jnius_export_class.pxi 28 | index a688e2b..7018adf 100644 29 | --- a/jnius/jnius_export_class.pxi 30 | +++ b/jnius/jnius_export_class.pxi 31 | @@ -146,7 +146,7 @@ class MetaJavaClass(MetaJavaBase): 32 | 33 | if NULL == obj: 34 | for interface in getattr(value, '__javainterfaces__', []): 35 | - obj = j_env[0].FindClass(j_env, str_for_c(interface)) 36 | + obj = PyJni_FindClass(str_for_c(interface)) 37 | if obj == NULL: 38 | j_env[0].ExceptionClear(j_env) 39 | elif 0 != j_env[0].IsAssignableFrom(j_env, obj, me.j_cls): 40 | @@ -177,11 +177,11 @@ class MetaJavaClass(MetaJavaBase): 41 | cdef JNIEnv *j_env = get_jnienv() 42 | 43 | if __javainterfaces__ and __javabaseclass__: 44 | - baseclass = j_env[0].FindClass(j_env, __javabaseclass__) 45 | + baseclass = PyJni_FindClass(__javabaseclass__) 46 | interfaces = malloc(sizeof(jclass) * len(__javainterfaces__)) 47 | 48 | for n, i in enumerate(__javainterfaces__): 49 | - interfaces[n] = j_env[0].FindClass(j_env, i) 50 | + interfaces[n] = PyJni_FindClass(i) 51 | 52 | getProxyClass = j_env[0].GetStaticMethodID( 53 | j_env, baseclass, "getProxyClass", 54 | @@ -206,8 +206,7 @@ class MetaJavaClass(MetaJavaBase): 55 | ' {0}'.format(__javaclass__)) 56 | else: 57 | class_name = str_for_c(__javaclass__) 58 | - jcs.j_cls = j_env[0].FindClass(j_env, 59 | - class_name) 60 | + jcs.j_cls = PyJni_FindClass(class_name) 61 | if jcs.j_cls == NULL: 62 | raise JavaException('Unable to find the class' 63 | ' {0}'.format(__javaclass__)) 64 | diff --git a/jnius/jnius_export_func.pxi b/jnius/jnius_export_func.pxi 65 | index 3a76dd5..c3e1375 100644 66 | --- a/jnius/jnius_export_func.pxi 67 | +++ b/jnius/jnius_export_func.pxi 68 | @@ -18,7 +18,7 @@ def find_javaclass(namestr): 69 | cdef jclass jc 70 | cdef JNIEnv *j_env = get_jnienv() 71 | 72 | - jc = j_env[0].FindClass(j_env, name) 73 | + jc = PyJni_FindClass(name) 74 | check_exception(j_env) 75 | 76 | cls = Class(noinstance=True) 77 | diff --git a/jnius/jnius_jvm_android.pxi b/jnius/jnius_jvm_android.pxi 78 | index dae6b31..17fe0e2 100644 79 | --- a/jnius/jnius_jvm_android.pxi 80 | +++ b/jnius/jnius_jvm_android.pxi 81 | @@ -1,6 +1,7 @@ 82 | # on android, rely on SDL to get the JNI env 83 | -cdef extern JNIEnv *SDL_AndroidGetJNIEnv() 84 | +cdef extern JNIEnv *PyJni_AndroidGetJNIEnv() 85 | 86 | +cdef extern jclass *PyJni_FindClass(const char* className) 87 | 88 | cdef JNIEnv *get_platform_jnienv() except NULL: 89 | - return SDL_AndroidGetJNIEnv() 90 | + return PyJni_AndroidGetJNIEnv() 91 | diff --git a/jnius/jnius_utils.pxi b/jnius/jnius_utils.pxi 92 | index ef5d6ab..a6edb0c 100644 93 | --- a/jnius/jnius_utils.pxi 94 | +++ b/jnius/jnius_utils.pxi 95 | @@ -164,14 +164,14 @@ cdef void check_assignable_from_str(JNIEnv *env, source, target) except *: 96 | return 97 | 98 | s_source = str_for_c(source) 99 | - cls_source = env[0].FindClass(env, s_source) 100 | + cls_source = PyJni_FindClass(s_source) 101 | 102 | if cls_source == NULL: 103 | raise JavaException('Unable to find the class for {0!r}'.format( 104 | source)) 105 | 106 | s_target = str_for_c(target) 107 | - cls_target = env[0].FindClass(env, s_target) 108 | + cls_target = PyJni_FindClass(s_target) 109 | 110 | if cls_target == NULL: 111 | raise JavaException('Unable to find the class for {0!r}'.format( 112 | @@ -238,7 +238,7 @@ cdef void check_assignable_from(JNIEnv *env, JavaClass jc, signature) except *: 113 | # we got an object that doesn't match with the signature 114 | # check if we can use it. 115 | s = str_for_c(signature) 116 | - cls = env[0].FindClass(env, s) 117 | + cls = PyJni_FindClass(s) 118 | if cls == NULL: 119 | raise JavaException('Unable to found the class for {0!r}'.format( 120 | signature)) 121 | diff --git a/setup.py b/setup.py 122 | index 3cca8c1..a7f40f0 100644 123 | --- a/setup.py 124 | +++ b/setup.py 125 | @@ -54,14 +54,7 @@ PXI_FILES = [ 126 | EXTRA_LINK_ARGS = [] 127 | 128 | # detect Python for android 129 | -PLATFORM = sys.platform 130 | -NDKPLATFORM = getenv('NDKPLATFORM') 131 | -if NDKPLATFORM is not None and getenv('LIBLINK'): 132 | - PLATFORM = 'android' 133 | - 134 | -# detect platform 135 | -if PLATFORM == 'android': 136 | - PYX_FILES = [fn[:-3] + 'c' for fn in PYX_FILES] 137 | +PLATFORM = 'android' 138 | 139 | JAVA=get_java_setup(PLATFORM) 140 | 141 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | usage() { 2 | echo "Usage:" 3 | echo 4 | echo " source $1 " 5 | echo 6 | echo "for example:" 7 | echo 8 | echo " source $1 3.13" 9 | echo 10 | } 11 | 12 | # make sure the script is sourced 13 | if [ "${BASH_SOURCE[0]}" = "$0" ]; then 14 | echo "This script must be sourced." 15 | echo 16 | usage $0 17 | exit 1 18 | fi 19 | 20 | if [ -z "$1" ]; then 21 | echo "Python version is not provided." 22 | echo 23 | usage $0 24 | return 25 | fi 26 | 27 | PYTHON_VERSION=$1 28 | read python_version_major python_version_minor < <(echo $PYTHON_VERSION | sed -E 's/^([0-9]+)\.([0-9]+).*/\1 \2/') 29 | PYTHON_VER=$python_version_major.$python_version_minor 30 | 31 | PYTHON_URL_PREFIX=https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-$PYTHON_VERSION+20241016 32 | 33 | echo "Python version: $PYTHON_VERSION" 34 | echo "Python short version: $PYTHON_VER" 35 | 36 | if [[ -z "$MOBILE_FORGE_IOS_SUPPORT_PATH" && -z "$MOBILE_FORGE_ANDROID_SUPPORT_PATH" ]]; then 37 | echo "Neither MOBILE_FORGE_IOS_SUPPORT_PATH nor MOBILE_FORGE_ANDROID_SUPPORT_PATH are defined." 38 | return 39 | fi 40 | 41 | venv_dir="$(pwd)/venv$PYTHON_VER" 42 | 43 | if [ ! -d $venv_dir ]; then 44 | echo "Creating Python $PYTHON_VER virtual environment for build in $venv_dir..." 45 | 46 | if ! [ -d "tools/python" ]; then 47 | if [ $(uname) = "Darwin" ]; then 48 | # macOS 49 | if [ $(uname -m) = "arm64" ]; then 50 | PYTHON_SUFFIX="aarch64-apple-darwin-install_only.tar.gz" 51 | else 52 | PYTHON_SUFFIX="x86_64-apple-darwin-install_only.tar.gz" 53 | fi 54 | else 55 | # Linux 56 | if [ $(uname -m) = "arm64" ]; then 57 | PYTHON_SUFFIX="aarch64-unknown-linux-gnu-install_only.tar.gz" 58 | else 59 | PYTHON_SUFFIX="x86_64_v3-unknown-linux-gnu-install_only.tar.gz" 60 | fi 61 | fi 62 | 63 | if ! [ -f "downloads/python-${PYTHON_VERSION}-${PYTHON_SUFFIX}" ]; then 64 | echo "Downloading Python ${PYTHON_VERSION}" 65 | python_dist_filename="downloads/python-${PYTHON_VERSION}-${PYTHON_SUFFIX}" 66 | mkdir -p downloads 67 | rm -rf $python_dist_filename 68 | curl --location --progress-bar --fail "${PYTHON_URL_PREFIX}-${PYTHON_SUFFIX}" --output $python_dist_filename 69 | if [ $? -ne 0 ]; then 70 | echo "Can't download a Python from ${PYTHON_URL_PREFIX}-${PYTHON_SUFFIX}" 71 | return 72 | fi 73 | fi 74 | 75 | mkdir -p tools 76 | tar -xzf "downloads/python-${PYTHON_VERSION}-${PYTHON_SUFFIX}" -C tools 77 | fi 78 | 79 | # BUILD_PYTHON=$(which python$PYTHON_VER) 80 | # if [ $? -ne 0 ]; then 81 | # echo "Can't find a Python $PYTHON_VER binary on the path." 82 | # return 83 | # fi 84 | 85 | BUILD_PYTHON=tools/python/bin/python 86 | 87 | if ! [ -f $BUILD_PYTHON ]; then 88 | echo "Can't find a Python $BUILD_PYTHON binary on the path." 89 | return 90 | fi 91 | 92 | echo "Using $BUILD_PYTHON as the build python" 93 | $BUILD_PYTHON -m venv $venv_dir 94 | source $venv_dir/bin/activate 95 | 96 | pip install -U pip 97 | pip install -e . wheel 98 | 99 | echo "Building platform dependency wheels..." 100 | if [ ! -z "$MOBILE_FORGE_IOS_SUPPORT_PATH" ]; then 101 | python -m make_dep_wheels iOS 102 | if [ $? -ne 0 ]; then 103 | return 104 | fi 105 | fi 106 | 107 | if [ ! -z "$MOBILE_FORGE_ANDROID_SUPPORT_PATH" ]; then 108 | python -m make_dep_wheels android 109 | if [ $? -ne 0 ]; then 110 | return 111 | fi 112 | fi 113 | 114 | echo "Python $PYTHON_VERSION environment has been created." 115 | echo 116 | else 117 | echo "Using existing Python $PYTHON_VERSION environment." 118 | source $venv_dir/bin/activate 119 | fi 120 | 121 | # configure iOS paths 122 | if [ ! -z "$MOBILE_FORGE_IOS_SUPPORT_PATH" ]; then 123 | 124 | if [ ! -d $MOBILE_FORGE_IOS_SUPPORT_PATH/support/$PYTHON_VER/iOS/Python.xcframework ]; then 125 | echo "MOBILE_FORGE_IOS_SUPPORT_PATH does not point at a valid location." 126 | return 127 | fi 128 | 129 | if [ ! -e $MOBILE_FORGE_IOS_SUPPORT_PATH/support/$PYTHON_VER/iOS/Python.xcframework/ios-arm64/bin/python$PYTHON_VER ]; then 130 | echo "MOBILE_FORGE_IOS_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION iOS ARM64 device binary." 131 | return 132 | fi 133 | 134 | if [ ! -e $MOBILE_FORGE_IOS_SUPPORT_PATH/support/$PYTHON_VER/iOS/Python.xcframework/ios-arm64_x86_64-simulator/bin/python$PYTHON_VER ]; then 135 | echo "MOBILE_FORGE_IOS_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION iOS ARM64/x86_64 simulator binaries." 136 | return 137 | fi 138 | 139 | echo "MOBILE_FORGE_IOS_SUPPORT_PATH: $MOBILE_FORGE_IOS_SUPPORT_PATH" 140 | fi 141 | 142 | # configure Android paths 143 | if [ ! -z "$MOBILE_FORGE_ANDROID_SUPPORT_PATH" ]; then 144 | if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/arm64-v8a/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then 145 | echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android arm64-v8a device binary." 146 | return 147 | fi 148 | 149 | if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/armeabi-v7a/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then 150 | echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android armeabi-v7a device binary." 151 | return 152 | fi 153 | 154 | if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/x86_64/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then 155 | echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android x86_64 device binary." 156 | return 157 | fi 158 | 159 | if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/x86/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then 160 | echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android x86 device binary." 161 | return 162 | fi 163 | 164 | echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH: $MOBILE_FORGE_ANDROID_SUPPORT_PATH" 165 | fi 166 | 167 | echo 168 | echo "You can now build packages with forge; e.g.:" 169 | echo 170 | echo "Build all packages for all iOS targets:" 171 | echo " forge iOS" 172 | echo 173 | echo "Build only the non-python packages, for all iOS targets:" 174 | echo " forge iOS -s non-py" 175 | echo 176 | echo "Build all packages needed for a smoke test, for all iOS targets:" 177 | echo " forge iOS -s smoke" 178 | echo 179 | echo "Build lru-dict for all iOS targets:" 180 | echo " forge iOS lru-dict" 181 | echo 182 | echo "Build lru-dict for the ARM64 device target:" 183 | echo " forge iphoneos:arm64 lru-dict" 184 | echo 185 | echo "Build all applicable versions of lru-dict for all iOS targets:" 186 | echo " forge iOS --all-versions lru-dict" 187 | echo -------------------------------------------------------------------------------- /src/forge/__main__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import sys 5 | from pathlib import Path 6 | 7 | from forge.cross import CrossVEnv 8 | from forge.package import Package 9 | from forge.pypi import get_pypi_versions 10 | 11 | 12 | def main(): 13 | parser = argparse.ArgumentParser( 14 | description="Build binary wheels for mobile platforms" 15 | ) 16 | parser.add_argument("-v", "--verbose", action="store_true", help="Log more detail") 17 | parser.add_argument( 18 | "--clean", 19 | action="store_true", 20 | help="Clean the build folder prior to building.", 21 | ) 22 | parser.add_argument( 23 | "--all-versions", 24 | action="store_true", 25 | help="Build all appropriate versions of each package.", 26 | ) 27 | parser.add_argument( 28 | "-s", 29 | "--subset", 30 | choices=[ 31 | "non-py", 32 | "py-any", 33 | "py", 34 | "smoke", 35 | "smoke-non-py", 36 | "smoke-py", 37 | "non-smoke", 38 | "all", 39 | ], 40 | default="all", 41 | help=( 42 | "The subset of packages to compile. One of: non-py (all non-Python " 43 | "packages), py-any (python packages that aren't platform dependent), " 44 | "py (all Python packages), smoke (only packages needed " 45 | "to do a support testbed check), smoke-non-py (only non-Python " 46 | "smoke packages), smoke-py (only Python smoke packages), non-smoke " 47 | "(all non-smoke packages), or all. Defaults to all." 48 | ), 49 | ) 50 | 51 | parser.add_argument( 52 | "host", 53 | help=( 54 | "The host platform(s) to target. One of the top-level platform (android, " 55 | "iOS, tvOS, watchOS); or a platform:version:arch triple (e.g., " 56 | "iphonesimulator:12.0:x86_64 or android:21:arm64-v8a)." 57 | ), 58 | ) 59 | parser.add_argument( 60 | "build_targets", 61 | nargs="*", 62 | default=None, 63 | help=( 64 | "Name of a package in ./recipes; or if it contains a slash, path " 65 | "to a recipe directory. Add ':' to override the version; " 66 | "add '::' to override the build number; add '::' " 67 | "to override both the version and the build number." 68 | ), 69 | ) 70 | 71 | args = parser.parse_args() 72 | 73 | try: 74 | platforms = [ 75 | (sdk, CrossVEnv.BASE_VERSION[args.host], arch) 76 | for sdk, arch in CrossVEnv.HOST_SDKS[args.host] 77 | ] 78 | except KeyError: 79 | parts = args.host.split(":") 80 | if len(parts) == 2: 81 | # Derive the base version from the provided SDK 82 | OS_MAP = { 83 | platform[0]: os_name 84 | for os_name, platforms in CrossVEnv.HOST_SDKS.items() 85 | for platform in platforms 86 | } 87 | host = OS_MAP[parts[0]] 88 | platforms = [(parts[0], CrossVEnv.BASE_VERSION[host], parts[1])] 89 | elif len(parts) == 3: 90 | platforms = [parts] 91 | else: 92 | print() 93 | print("Invalid host. Host should be:") 94 | print( 95 | " * the name of an operating system (android, iOS, tvOS, watchOS); or" 96 | ) 97 | print(" * a tuple of abi:arch (e.g., iphoneos:arm64); or") 98 | print(" * a triple of abi:version:arch (e.g., iphoneos:12.0:arm64).") 99 | print() 100 | sys.exit(1) 101 | 102 | build_targets = args.build_targets or [] 103 | 104 | successes = [] 105 | failures = [] 106 | for build_target in build_targets: 107 | if Path(build_target).is_dir(): 108 | # If the build target is a directory, just build what it says. 109 | if args.all_versions: 110 | print("Ignoring --all-versions on an explicit recipe") 111 | 112 | package_name_or_recipe = build_target 113 | build_number = None 114 | target_versions = [None] 115 | else: 116 | # Target is a recipe. Look for version/build overrides 117 | parts = build_target.split(":") 118 | package_name_or_recipe = parts[0] 119 | 120 | try: 121 | requested_version = parts[1] if parts[1] else None 122 | try: 123 | build_number = int(parts[2]) 124 | except IndexError: 125 | build_number = None 126 | except IndexError: 127 | requested_version = None 128 | build_number = None 129 | 130 | # If --all-versions was specified, build the list of versions. 131 | if args.all_versions: 132 | if requested_version: 133 | print("Specific version requested; ignoring --all-versions") 134 | target_versions = [requested_version] 135 | else: 136 | target_versions = get_pypi_versions(package_name_or_recipe) 137 | else: 138 | target_versions = [requested_version] 139 | 140 | for version in target_versions: 141 | # First build of each version must be clean; 142 | # subsequent builds will be isolated by 143 | 144 | first = True 145 | build_platforms = platforms 146 | 147 | # Build the package for each required platform. 148 | for sdk, sdk_version, arch in build_platforms: 149 | package = Package( 150 | package_name_or_recipe, 151 | version=version, 152 | build_number=build_number, 153 | sdk=sdk, 154 | sdk_version=sdk_version, 155 | arch=arch, 156 | ) 157 | cross_venv = CrossVEnv(sdk=sdk, sdk_version=sdk_version, arch=arch) 158 | builder = package.builder(cross_venv) 159 | success = builder.build(clean=first) 160 | 161 | # If the build was successful, subsequent passes don't need to be clean. 162 | if success: 163 | first = False 164 | successes.append((package_name_or_recipe, version, cross_venv.tag)) 165 | else: 166 | failures.append((package_name_or_recipe, version, cross_venv.tag)) 167 | 168 | if successes: 169 | print() 170 | print("Successful builds for:") 171 | for name, version, tag in successes: 172 | print(f" * {name} {version if version else '(default version)'} ({tag})") 173 | print() 174 | 175 | if failures: 176 | print() 177 | print("Failed builds for:") 178 | for name, version, tag in failures: 179 | print(f" * {name} {version if version else '(default version)'} ({tag})") 180 | print() 181 | sys.exit(1) 182 | 183 | 184 | if __name__ == "__main__": 185 | main() 186 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | image: macos-monterey 2 | 3 | skip_branch_with_pr: true 4 | 5 | environment: 6 | PYTHON_VERSION: 3.12.7 7 | PYTHON_SHORT_VERSION: 3.12 8 | CF_ACCESS_KEY_ID: 9 | secure: +m1fzbrEPRecXKCCMn4uA781PAASzJSWAxuJj1c7ctLfWbi5oW4PMnowPK96XtQ5 10 | CF_SECRET_ACCESS_KEY: 11 | secure: siQTjK+IAmy+zcTSO0d/dnyU/SHC52+gaW8xOT3GFqW8dyRAWr7YXtCU0QvlIC5MFVnbEmgDcDKqINaWN1iD5Cuuw/QAFsF1L/zDnQSvAtE= 12 | CF_ENDPOINT_URL: 13 | secure: lSQBfrqIXIOAYhA0NGej7Pfll1wOSKTTFwQCl8N8lvI22uI5CA/UjRKaqw6KlIZMcXvqTP1w11CVqC2CWnyM3hK857X2tAe8nkO8KT0DCzw= 14 | CF_BUCKET_NAME: flet-simple 15 | GEMFURY_TOKEN: 16 | secure: trYGM65OQ1+HYnOYOe/NOHrofLpP3bz64nHwVWPJhiUIYll3MrrQd7ilFNp+zSkI 17 | MOBILE_FORGE_CACHE_DOWNLOADS_OFF: 1 18 | 19 | matrix: 20 | - job_name: 'Android: zope.interface 7.2' 21 | job_group: build_android 22 | FORGE_ARCH: android 23 | FORGE_PACKAGES: >- 24 | zope.interface:7.2 25 | BUILD_NUMBER: 1 26 | 27 | - job_name: 'iOS: zope.interface 7.2' 28 | job_group: build_ios 29 | FORGE_ARCH: iOS 30 | FORGE_PACKAGES: >- 31 | zope.interface:7.2 32 | BUILD_NUMBER: 1 33 | 34 | # ================================================== 35 | 36 | # - job_name: 'Android arm64-v8a: opencv-python' 37 | # job_group: build_android 38 | # FORGE_ARCH: 'android:arm64-v8a' 39 | # FORGE_PACKAGES: numpy:2.1.1 opencv-python:4.10.0.84 40 | # BUILD_NUMBER: 1 41 | 42 | # - job_name: 'Android armeabi-v7a: opencv-python' 43 | # job_group: build_android 44 | # FORGE_ARCH: 'android:armeabi-v7a' 45 | # FORGE_PACKAGES: numpy:2.1.1 opencv-python:4.10.0.84 46 | # BUILD_NUMBER: 1 47 | 48 | # - job_name: 'Android x86_64: opencv-python' 49 | # job_group: build_android 50 | # FORGE_ARCH: 'android:x86_64' 51 | # FORGE_PACKAGES: numpy:2.1.1 opencv-python:4.10.0.84 52 | # BUILD_NUMBER: 1 53 | 54 | # - job_name: 'Android x86: opencv-python' 55 | # job_group: build_android 56 | # FORGE_ARCH: 'android:x86' 57 | # FORGE_PACKAGES: numpy:2.1.1 opencv-python:4.10.0.84 58 | # BUILD_NUMBER: 1 59 | 60 | # - job_name: 'iOS iphone arm64: opencv-python' 61 | # job_group: build_ios 62 | # FORGE_ARCH: 'iphoneos:arm64' 63 | # FORGE_PACKAGES: numpy:2.1.1 opencv-python:4.10.0.84 64 | # BUILD_NUMBER: 1 65 | 66 | # - job_name: 'iOS simulator arm64: opencv-python' 67 | # job_group: build_ios 68 | # FORGE_ARCH: 'iphonesimulator:arm64' 69 | # FORGE_PACKAGES: numpy:2.1.1 opencv-python:4.10.0.84 70 | # BUILD_NUMBER: 1 71 | 72 | # - job_name: 'iOS simulator x86_64: opencv-python' 73 | # job_group: build_ios 74 | # FORGE_ARCH: 'iphonesimulator:x86_64' 75 | # FORGE_PACKAGES: numpy:2.1.1 opencv-python:4.10.0.84 76 | # BUILD_NUMBER: 1 77 | 78 | # - job_name: 'Android: pydantic-core, pillow, lru-dict, contourpy, kiwisolver, aiohttp, bitarray, argon2-cffi-binding, bcrypt, cryptography, brotli, websockets' 79 | # job_group: build_android 80 | # FORGE_ARCH: android 81 | # FORGE_PACKAGES: >- 82 | # cffi:1.17.1 83 | # flet-libcpp-shared:27.2.12479018 84 | # flet-libjpeg:3.0.90 85 | # flet-libpng:1.6.43 86 | # flet-libfreetype:2.13.3 87 | # pillow:10.4.0 88 | # lru-dict:1.3.0 89 | # yarl:1.11.1 90 | # contourpy:1.3.0 91 | # kiwisolver:1.4.7 92 | # aiohttp:3.9.5 93 | # bitarray:2.9.2 94 | # argon2-cffi-bindings:21.2.0 95 | # bcrypt:4.2.0 96 | # cryptography:43.0.1 97 | # brotli:1.1.0 98 | # pydantic-core:2.23.3 99 | # websockets:13.0.1 100 | # time-machine:2.16.0 101 | # markupsafe:2.1.5 102 | # BUILD_NUMBER: 1 103 | 104 | # - job_name: 'Android: numpy, matplotlib, pandas, blis' 105 | # job_group: build_android 106 | # FORGE_ARCH: android 107 | # FORGE_PACKAGES: >- 108 | # flet-libcpp-shared:27.2.12479018 109 | # numpy:1.26.4 110 | # numpy:2.1.1 111 | # flet-libjpeg:3.0.90 112 | # matplotlib:3.9.2 113 | # pandas:2.2.2 114 | # blis:1.0.0 115 | # BUILD_NUMBER: 1 116 | 117 | # - job_name: 'iOS: pillow, lru-dict, yarl, contourpy, kiwisolver, aiohttp, bitarray, websockets' 118 | # job_group: build_ios 119 | # FORGE_ARCH: iOS 120 | # FORGE_PACKAGES: >- 121 | # flet-libjpeg:3.0.90 122 | # flet-libpng:1.6.43 123 | # flet-libfreetype:2.13.3 124 | # pillow:10.4.0 125 | # lru-dict:1.3.0 126 | # yarl:1.11.1 127 | # contourpy:1.3.0 128 | # kiwisolver:1.4.7 129 | # aiohttp:3.9.5 130 | # bitarray:2.9.2 131 | # websockets:13.0.1 132 | # time-machine:2.16.0 133 | # markupsafe:2.1.5 134 | # BUILD_NUMBER: 1 135 | 136 | # - job_name: 'iOS: cffi, argon2-cffi-bindings, bcrypt, cryptography, brotli' 137 | # job_group: build_ios 138 | # FORGE_ARCH: iOS 139 | # FORGE_PACKAGES: >- 140 | # cffi:1.17.1 141 | # argon2-cffi-bindings:21.2.0 142 | # bcrypt:4.2.0 143 | # cryptography:43.0.1 144 | # brotli:1.1.0 145 | # BUILD_NUMBER: 1 146 | 147 | # - job_name: 'iOS: pydantic-core' 148 | # job_group: build_ios 149 | # FORGE_ARCH: iOS 150 | # FORGE_PACKAGES: >- 151 | # pydantic-core:2.23.3 152 | # BUILD_NUMBER: 1 153 | 154 | # - job_name: 'iOS: numpy, matplotlib, pandas, blis' 155 | # job_group: build_ios 156 | # FORGE_ARCH: iOS 157 | # FORGE_PACKAGES: >- 158 | # numpy:1.26.4 159 | # numpy:2.1.1 160 | # flet-libjpeg:3.0.90 161 | # matplotlib:3.9.2 162 | # pandas:2.2.2 163 | # blis:1.0.0 164 | # BUILD_NUMBER: 1 165 | 166 | # - job_name: Re-build Simple index 167 | # job_group: rebuild_index 168 | # job_depends_on: build_android, build_ios 169 | 170 | stack: 171 | - python $PYTHON_SHORT_VERSION 172 | 173 | on_success: 174 | - sh: | 175 | if test -d logs; then 176 | find logs -type f -iname *.log -exec appveyor PushArtifact {} \; 177 | fi 178 | 179 | on_failure: 180 | - sh: | 181 | if test -d errors; then 182 | find errors -type f -iname *.log -exec appveyor PushArtifact {} \; 183 | fi 184 | 185 | for: 186 | # ====================================== 187 | # Build Android packages 188 | # ====================================== 189 | 190 | - matrix: 191 | only: 192 | - job_group: build_android 193 | 194 | environment: 195 | APPVEYOR_BUILD_WORKER_IMAGE: ubuntu-gce-c 196 | NDK_VERSION: r27c 197 | 198 | install: 199 | - sudo apt install sqlite3 200 | - . .ci/common.sh 201 | 202 | # download Python for Android 203 | - python_android_dir=$HOME/projects/python-build/android 204 | - curl -#OL https://github.com/flet-dev/python-build/releases/download/v${PYTHON_SHORT_VERSION}/python-android-mobile-forge-${PYTHON_SHORT_VERSION}.tar.gz 205 | - mkdir -p $python_android_dir 206 | - tar -xzf python-android-mobile-forge-${PYTHON_SHORT_VERSION}.tar.gz -C $python_android_dir 207 | 208 | # install Android NDK 209 | - .ci/install_ndk.sh 210 | 211 | # install Rust 212 | - curl https://sh.rustup.rs -sSf | sh -s -- -y 213 | - . "$HOME/.cargo/env" 214 | - export PATH="$PATH:$HOME/.cargo/bin" 215 | - rustup target add aarch64-linux-android 216 | - rustup target add arm-linux-androideabi 217 | - rustup target add x86_64-linux-android 218 | - rustup target add i686-linux-android 219 | 220 | # configure forge 221 | - export MOBILE_FORGE_ANDROID_SUPPORT_PATH=$python_android_dir 222 | - source ./setup.sh $PYTHON_VERSION 223 | 224 | build_script: 225 | - sh: | 226 | IFS=' ' read -r -a packages <<< "$FORGE_PACKAGES" 227 | for package in "${packages[@]}"; do 228 | forge $FORGE_ARCH $package:$BUILD_NUMBER || exit 1 229 | done 230 | 231 | # cleanup 232 | - rm dist/bzip2-* 233 | - rm dist/xz-* 234 | - rm dist/openssl-* 235 | - rm dist/libffi-* 236 | 237 | deploy_script: 238 | # - pip install boto3 239 | # - python .ci/publish-wheels.py dist 240 | - publish_to_pypi dist/*.whl 241 | 242 | test: off 243 | 244 | # ====================================== 245 | # Build iOS packages 246 | # ====================================== 247 | 248 | - matrix: 249 | only: 250 | - job_group: build_ios 251 | 252 | environment: 253 | APPVEYOR_BUILD_WORKER_IMAGE: macos-sonoma 254 | 255 | install: 256 | - . .ci/common.sh 257 | 258 | # download Python for iOS 259 | - python_ios_dir=$HOME/projects/python-build/darwin/Python-Apple-support 260 | - curl -#OL https://github.com/flet-dev/python-build/releases/download/v${PYTHON_SHORT_VERSION}/python-ios-mobile-forge-${PYTHON_SHORT_VERSION}.tar.gz 261 | - mkdir -p $python_ios_dir 262 | - tar -xzf python-ios-mobile-forge-${PYTHON_SHORT_VERSION}.tar.gz -C $python_ios_dir 263 | 264 | # install Rust 265 | - curl https://sh.rustup.rs -sSf | sh -s -- -y 266 | - . "$HOME/.cargo/env" 267 | - export PATH="$PATH:$HOME/.cargo/bin" 268 | - rustup target add aarch64-apple-ios 269 | - rustup target add aarch64-apple-ios-sim 270 | - rustup target add x86_64-apple-ios 271 | 272 | # configure forge 273 | - export MOBILE_FORGE_IOS_SUPPORT_PATH=$python_ios_dir 274 | - source ./setup.sh $PYTHON_VERSION 275 | 276 | # refresh PATH 277 | - export PATH="$PATH:$HOME/.cargo/bin" 278 | 279 | build_script: 280 | - sh: | 281 | IFS=' ' read -r -a packages <<< "$FORGE_PACKAGES" 282 | for package in "${packages[@]}"; do 283 | forge $FORGE_ARCH $package:$BUILD_NUMBER || exit 1 284 | done 285 | 286 | # cleanup 287 | - rm dist/bzip2-* 288 | - rm dist/xz-* 289 | - rm dist/libffi-* 290 | - rm dist/openssl-* 291 | 292 | deploy_script: 293 | # - pip install boto3 294 | # - python .ci/publish-wheels.py dist 295 | - publish_to_pypi dist/*.whl 296 | 297 | test: off 298 | 299 | # ====================================== 300 | # Rebuild Simple index 301 | # ====================================== 302 | 303 | - matrix: 304 | only: 305 | - job_group: rebuild_index 306 | 307 | environment: 308 | APPVEYOR_BUILD_WORKER_IMAGE: ubuntu 309 | 310 | deploy_script: 311 | - python -m ensurepip --upgrade 312 | - pip3 install --upgrade setuptools 313 | - pip3 install boto3 314 | - python .ci/rebuild-simple-index.py 315 | 316 | test: off 317 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Mobile Forge 2 | ============ 3 | 4 | This is a forge-like environment that can be used to build wheels for mobile platforms. 5 | It is currently only tested for iOS, but in theory, it should also be usable for 6 | Android. Contributions to verify Android support, and to add more package recipes, are 7 | definitely encouraged. 8 | 9 | Usage 10 | ----- 11 | 12 | Before using Mobile Forge, you'll need to compile Python for your build platform (e.g., 13 | your laptop), and for host platform (e.g., for iOS). It may be helpful to use a project 14 | like `Python-Apple-support `__ to 15 | manage this compilation process. 16 | 17 | Using Python-Apple-support 18 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 19 | 20 | If you *do* use Python-Apple-support, this repo contains an activation script that will 21 | configure your environment so it's ready to use. 22 | 23 | 1. Set an environment variable declaring the location of your Python-Apple-support 24 | checkout:: 25 | 26 | $ export PYTHON_APPLE_SUPPORT=/path/to/Python-Apple-support 27 | 28 | 2. Clone this repository, and run the activate script for the Python version you want to 29 | use:: 30 | 31 | $ git clone https://github.com/beeware/mobile-forge.git 32 | $ cd mobile-forge 33 | $ source ./setup-iOS.sh 3.11 34 | 35 | This will create a Python virtual environment, install mobile forge, and provide 36 | some hints at forge commands you can run. 37 | 38 | If a virtual environment already exists, it will be activated, and the same hints 39 | displayed. 40 | 41 | The hard way 42 | ~~~~~~~~~~~~ 43 | 44 | If you're *not* using Python-Apple-support, the setup process requires more manual steps:: 45 | 46 | 1. Create and activate a virtual environment using the Python build platform, using the 47 | build platform Python compiled in step 1. 48 | 49 | 2. Clone this repository, and install it into your freshly created virtual environment:: 50 | 51 | (venv3.11) $ git clone https://github.com/beeware/mobile-forge.git 52 | (venv3.11) $ cd mobile-forge 53 | (venv3.11) $ pip install -e . 54 | 55 | 3. Ensure your ``PATH`` contains any tools that were necessary to compile the host CPython, 56 | and does *not* contain any macOS development libraries. Mobile-forge will clean the ``PATH`` 57 | to remove known problematic paths (e.g., paths added by Homebrew, rbenv, npm, etc). 58 | 59 | 4. Set environment variables that define the location of the Python executable for each 60 | of the **host** platforms you intend to target. For iOS, this means defining 61 | 3 environment variables:: 62 | 63 | (venv3.11) $ export MOBILE_FORGE_IPHONEOS_ARM64=... 64 | (venv3.11) $ export MOBILE_FORGE_IPHONESIMULATOR_ARM64=... 65 | (venv3.11) $ export MOBILE_FORGE_IPHONESIMULATOR_X86_64=... 66 | 67 | 5. Build a package. The ``packages`` folder contains recipes for packages. ``lru-dict`` 68 | is a good first package to try:: 69 | 70 | (venv3.11) $ forge iOS lru-dict 71 | 72 | Or, to build a wheel for a single architecture:: 73 | 74 | (venv3.11) $ forge iphonesimulator:12.0:arm64 lru-dict 75 | 76 | Once this command completes, there should be a wheel for each platform in the ``dist`` 77 | folder. A log for each successful build will be in the ``logs`` folder; a log for each 78 | unsuccessful build (if there are any) will be in the ``errors`` folder. 79 | 80 | The special snowflakes 81 | ~~~~~~~~~~~~~~~~~~~~~~ 82 | 83 | Mobile Forge is trying to support multiple packages, building on multiple Python 84 | versions, for multiple architectures; and some of those Python versions were released 85 | before the release of ARM64 macOS hardware. As a result, some versions of some packages 86 | have some quirks that must be taken into account. 87 | 88 | Pandas 89 | ^^^^^^ 90 | 91 | Pandas uses a meta-package named ``oldest-supported-numpy`` to ensure ABI compatibility 92 | during compilation. However, this can install a different version of numpy, depending on 93 | the platform. This is especially problematic for Python 3.9, because the minimum 94 | supported version for Python 3.9 on ARM64 is different to the version that is installed 95 | for x86_64. Mobile-forge produces a replacement ``oldest-supported-numpy`` package, tagged 96 | as version 2999.1.1, which ensures that consistent versions are available for build 97 | purposes; however, this wheel *should not* be published. 98 | 99 | Cryptography 100 | ^^^^^^^^^^^^ 101 | 102 | Cryptography currently builds a *very* old version (3.4.8). This is the last version 103 | that could be built without a Rust compiler. The recipe works as is on all Python 104 | versions *except* Python 3.8 on ARM64, because there was no ARM64-compatible wheel 105 | published for cffi 1.15.1. However, if you run:: 106 | 107 | $ pip wheel -w dist --no-deps cffi==1.15.1 108 | 109 | you can build a universal Python3.8 CFFI wheel for CFFI 1.15.1, which can be used to 110 | satisfy this build-time requirement. 111 | 112 | What now? 113 | --------- 114 | 115 | To include these wheels in a test project, you can add the ``dist`` folder as a links 116 | source in your ``requires`` definition in your Briefcase ``pyproject.toml``. For 117 | example, the following will install the ``lru-dict`` wheels you've just compiled:: 118 | 119 | requires = [ 120 | "--find-links", "/path/to/mobile-forge/dist", 121 | "lru-dict", 122 | ] 123 | 124 | Adding your own packages 125 | ------------------------ 126 | 127 | If there's a package that you want that doesn't have an existing recipe, you can add a 128 | recipe for that package. 129 | 130 | Create a directory in ``recipes``. The name of the directory must be in PyPI normalized 131 | form (PEP 503). Alternatively, you can create this directory somewhere else, and pass 132 | its path when calling ``forge``. 133 | 134 | Inside the recipe directory, add the following files. 135 | 136 | * A `meta.yaml` file. This supports a subset of Conda syntax, defined in `meta-schema.yaml`. 137 | * A `test.py` file (or `test` package), to run on a target installation. This should contain a 138 | pytest suite which imports the package and does some basic checks. 139 | * Optionally, one or more patch files in a folder named ``patches``. These patches will be 140 | applied when the source code is unpacked for a given platform. 141 | * For non-Python packages, a ``build.sh`` script. This is the script that will be executed 142 | in the build environment build the package. This script should invoke any ``configure``, 143 | ``make``, or any other compilation steps needed to build the package. This script will be 144 | executed in an environment that defines the following environment variables: 145 | 146 | - ``AR`` - the ``AR`` value used to compile the host Python, as determined from 147 | ``sysconfig`` 148 | - ``CC`` - the ``CC`` value used to compile the host Python, as determined from 149 | ``sysconfig``. 150 | - ``CFLAGS`` - the ``CFLAGS`` value used to compile the host Python, as determined 151 | from ``sysconfig``, augmented with the include paths for the SDK, and 152 | ``opt/include`` in the host environment's site-packages. 153 | - ``LDFLAGS`` - the ``CFLAGS`` value used to compile the host Python, as determined 154 | from ``sysconfig``, augmented with the library paths for the SDK, and 155 | ``opt/lib`` in the host environment's site-packages. 156 | - ``CPU_COUNT`` - The number of CPUs that are available, as determined by 157 | ``multiprocessing.cpu_count()`` 158 | - ``HOST_TRIPLET`` - the GCC compiler triplet for the host platform (e.g., 159 | ``aarch64-apple-ios12.0-simulator``) 160 | - ``BUILD_TRIPLET`` - the GCC compiler triplet for the build platform (e.g., 161 | ``aarch64-apple-darwin``) 162 | - ``PREFIX`` - a location where the compiled package can be installed in preparation 163 | for packaging. 164 | 165 | This script should install the package into ``$PREFIX``. Mobile Forge will package any 166 | content installed into ``$PREFIX`` into a "wheel" that can be installed as a host 167 | requirement. 168 | 169 | Python-based projects 170 | ~~~~~~~~~~~~~~~~~~~~~ 171 | 172 | All Python projects are compiled using ``python -m build``, using a clean `crossenv 173 | `__ virtual environment for each platform of a 174 | package. Any PEP518 build requirements will be included in both the host and build 175 | environments. 176 | 177 | If you're lucky, all you'll need to do is define a ``meta.yaml`` that describes the 178 | package name and version: e.g.,:: 179 | 180 | package: 181 | name: blis 182 | version: 0.4.1 183 | 184 | If this doesn't result in a successful build, it will likely be for one of the following 185 | reasons: 186 | 187 | 1. **The build process has a dependency on a system library**. For example, Pillow has a 188 | dependency on ``libjpeg``. ``libjpeg`` isn't available on PyPI; but it *is* possible 189 | to build a "wheel" for ``libjpeg``, so it can be specified as a requirement. 190 | 191 | A non-python "wheel" is constructed by compiling the package for your target platform, 192 | then installing it into a folder named ``opt``. As a result of this "install", you'll 193 | usually end up with an ``opt/include`` and ``opt/lib`` folder; Mobile Forge will then 194 | wrap up this ``opt`` folder in a wheel, along with Python wheel metadata. 195 | 196 | When this "wheel" is specified as a host requirement, the "wheel" will be unpacked 197 | into the site packages folder of your cross-compilation host environment. This path 198 | the ``include`` and ``lib`` paths will be automatically included in the 199 | ``CFLAGS``/``LDFLAGS`` environment variables when the Python build is executed. 200 | 201 | 2. **The build process has a dependency on external tooling**. Mobile Forge will 202 | configure a C and C++ compiler using the same configuration that was used to compile 203 | the support libraries; however a package may require addition build tooling (e.g., a 204 | Fortran compiler) to complete the build. If this is the case, you'll need to find a 205 | version of the tool that can target mobile platforms, and work out how to modify the 206 | build process to apply any necessary compiler flags. 207 | 208 | 3. **The build script has platform-specific logic**. For example, 209 | if the ``setup.py`` file contain an ``if sys.platform == ...`` clauses, it is unlikely 210 | that a mobile platform will trigger the right logic. 211 | 212 | If you need to make any alterations to a project's source code for a build to succeed, 213 | you can provide those patches by putting them in one or more files in a folder named 214 | ``patches`` in the recipe folder. These patches will be applied once the source code 215 | has been unpacked. 216 | 217 | Configure-based projects 218 | ~~~~~~~~~~~~~~~~~~~~~~~~ 219 | 220 | If the project includes a `configure` script, you will likely need to provide a patch 221 | for `config.sub`. `config.sub` is the tools used by `configure` to identify the 222 | architecture and machine type; however, it doesn't currently recognize the host triples 223 | used by Apple. If you get the error:: 224 | 225 | checking host system type... Invalid configuration `arm64-apple-ios': machine `arm64-apple' not recognized 226 | configure: error: /bin/sh config/config.sub arm64-apple-ios failed 227 | 228 | you will need to patch `config.sub`. There are several examples of patched `config.sub` 229 | scripts in the packages contained in this repository, and in the Python-Apple-support 230 | project; it is quite possible one of those patches can be used for the library you are 231 | trying to compile. The `config.sub` script has a datestamp at the top of the file; that 232 | can be used to identify which patch you will need. 233 | 234 | Community 235 | --------- 236 | 237 | Mobile Forge is part of the `BeeWare suite`_. You can talk to the community through: 238 | 239 | * `@beeware@fosstodon.org on Mastodon `__ 240 | 241 | * `Discord `__ 242 | 243 | * The Mobile Forge `Github Discussions forum `__ 244 | 245 | We foster a welcoming and respectful community as described in our 246 | `BeeWare Community Code of Conduct`_. 247 | 248 | Contributing 249 | ------------ 250 | 251 | If you experience problems with Mobile Forge, `log them on GitHub`_. If you 252 | want to contribute code, please `fork the code`_ and `submit a pull request`_. 253 | 254 | .. _BeeWare suite: http://beeware.org 255 | .. _Read The Docs: https://briefcase.readthedocs.io 256 | .. _BeeWare Community Code of Conduct: http://beeware.org/community/behavior/ 257 | .. _log them on Github: https://github.com/beeware/mobile-forge/issues 258 | .. _fork the code: https://github.com/beeware/mobile-forge 259 | .. _submit a pull request: https://github.com/beeware/mobile-forge/pulls 260 | 261 | Acknowledgements 262 | ---------------- 263 | 264 | This project draws significantly on the implementation and knowledge developed in the 265 | `Chaquopy package builder 266 | `__. Although this is 267 | largely a "clean room" reimplementation of that project, many details from that project 268 | have been used in the development of this one. 269 | -------------------------------------------------------------------------------- /recipes/opencv-python/patches/mobile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/a.diff b/a.diff 2 | new file mode 100644 3 | index 0000000..e69de29 4 | diff --git a/opencv/CMakeLists.txt b/opencv/CMakeLists.txt 5 | index f5e39b4..1bd8629 100644 6 | --- a/opencv/CMakeLists.txt 7 | +++ b/opencv/CMakeLists.txt 8 | @@ -674,7 +674,7 @@ endif() 9 | ocv_cmake_hook(POST_CMAKE_BUILD_OPTIONS) 10 | 11 | # --- Python Support --- 12 | -if(NOT IOS AND NOT XROS) 13 | +if(NOT XROS) 14 | include(cmake/OpenCVDetectPython.cmake) 15 | endif() 16 | 17 | diff --git a/opencv/cmake/OpenCVDetectPython.cmake b/opencv/cmake/OpenCVDetectPython.cmake 18 | index a23fba6..4eb1126 100644 19 | --- a/opencv/cmake/OpenCVDetectPython.cmake 20 | +++ b/opencv/cmake/OpenCVDetectPython.cmake 21 | @@ -123,7 +123,6 @@ if(NOT ${found}) 22 | if(_found) 23 | set(_version_major_minor "${_version_major}.${_version_minor}") 24 | 25 | - if(NOT ANDROID AND NOT APPLE_FRAMEWORK) 26 | ocv_check_environment_variables(${library_env} ${include_dir_env}) 27 | if(NOT ${${library_env}} STREQUAL "") 28 | set(PYTHON_LIBRARY "${${library_env}}") 29 | @@ -144,8 +143,8 @@ if(NOT ${found}) 30 | # Copy outputs 31 | set(_libs_found ${PYTHONLIBS_FOUND}) 32 | set(_libraries ${PYTHON_LIBRARIES}) 33 | - set(_include_path ${PYTHON_INCLUDE_PATH}) 34 | - set(_include_dirs ${PYTHON_INCLUDE_DIRS}) 35 | + set(_include_path ${PYTHON_INCLUDE_DIR}) 36 | + set(_include_dirs ${PYTHON_INCLUDE_DIR}) 37 | set(_debug_libraries ${PYTHON_DEBUG_LIBRARIES}) 38 | set(_libs_version_string ${PYTHONLIBS_VERSION_STRING}) 39 | set(_debug_library ${PYTHON_DEBUG_LIBRARY}) 40 | @@ -173,9 +172,8 @@ if(NOT ${found}) 41 | unset(PYTHON_INCLUDE_DIR CACHE) 42 | unset(PYTHON_INCLUDE_DIR2 CACHE) 43 | endif() 44 | - endif() 45 | 46 | - if(NOT ANDROID AND NOT IOS AND NOT XROS) 47 | + if(NOT XROS) 48 | if(CMAKE_HOST_UNIX) 49 | execute_process(COMMAND ${_executable} -c "from sysconfig import *; print(get_path('purelib'))" 50 | RESULT_VARIABLE _cvpy_process 51 | @@ -240,7 +238,7 @@ if(NOT ${found}) 52 | OUTPUT_STRIP_TRAILING_WHITESPACE) 53 | endif() 54 | endif() 55 | - endif(NOT ANDROID AND NOT IOS AND NOT XROS) 56 | + endif(NOT XROS) 57 | endif() 58 | 59 | # Export return values 60 | diff --git a/opencv/modules/core/misc/python/pyopencv_umat.hpp b/opencv/modules/core/misc/python/pyopencv_umat.hpp 61 | index 2e91cd5..9905bd0 100644 62 | --- a/opencv/modules/core/misc/python/pyopencv_umat.hpp 63 | +++ b/opencv/modules/core/misc/python/pyopencv_umat.hpp 64 | @@ -7,7 +7,7 @@ typedef std::vector vector_Range; 65 | CV_PY_TO_CLASS(UMat) 66 | CV_PY_FROM_CLASS(UMat) 67 | 68 | -static bool cv_mappable_to(const Ptr& src, Ptr& dst) 69 | +static bool cv_mappable_to(const cv::Ptr& src, cv::Ptr& dst) 70 | { 71 | //dst.reset(new UMat(src->getUMat(ACCESS_RW))); 72 | dst.reset(new UMat()); 73 | diff --git a/opencv/modules/dnn/misc/python/pyopencv_dnn.hpp b/opencv/modules/dnn/misc/python/pyopencv_dnn.hpp 74 | index d729cd8..346b079 100644 75 | --- a/opencv/modules/dnn/misc/python/pyopencv_dnn.hpp 76 | +++ b/opencv/modules/dnn/misc/python/pyopencv_dnn.hpp 77 | @@ -117,14 +117,14 @@ public: 78 | } 79 | } 80 | 81 | - static Ptr create(dnn::LayerParams ¶ms) 82 | + static cv::Ptr create(dnn::LayerParams ¶ms) 83 | { 84 | std::map >::iterator it = pyLayers.find(params.type); 85 | if (it == pyLayers.end()) 86 | CV_Error(Error::StsNotImplemented, "Layer with a type \"" + params.type + 87 | "\" is not implemented"); 88 | CV_Assert(!it->second.empty()); 89 | - return Ptr(new pycvLayer(params, it->second.back())); 90 | + return cv::Ptr(new pycvLayer(params, it->second.back())); 91 | } 92 | 93 | virtual bool getMemoryShapes(const std::vector > &inputs, 94 | diff --git a/opencv/modules/python/CMakeLists.txt b/opencv/modules/python/CMakeLists.txt 95 | index 93eab8c..e41b720 100644 96 | --- a/opencv/modules/python/CMakeLists.txt 97 | +++ b/opencv/modules/python/CMakeLists.txt 98 | @@ -3,7 +3,7 @@ 99 | # ---------------------------------------------------------------------------- 100 | if(DEFINED OPENCV_INITIAL_PASS) # OpenCV build 101 | 102 | -if(ANDROID OR APPLE_FRAMEWORK OR WINRT) 103 | +if(WINRT) 104 | ocv_module_disable_(python2) 105 | ocv_module_disable_(python3) 106 | return() 107 | diff --git a/opencv/modules/python/src2/cv2.cpp b/opencv/modules/python/src2/cv2.cpp 108 | index a7837a6..21ab682 100644 109 | --- a/opencv/modules/python/src2/cv2.cpp 110 | +++ b/opencv/modules/python/src2/cv2.cpp 111 | @@ -25,16 +25,16 @@ typedef std::vector vector_int; 112 | typedef std::vector vector_float; 113 | typedef std::vector vector_double; 114 | typedef std::vector vector_size_t; 115 | -typedef std::vector vector_Point; 116 | +typedef std::vector vector_Point; 117 | typedef std::vector vector_Point2f; 118 | typedef std::vector vector_Point3f; 119 | -typedef std::vector vector_Size; 120 | +typedef std::vector vector_Size; 121 | typedef std::vector vector_Vec2f; 122 | typedef std::vector vector_Vec3f; 123 | typedef std::vector vector_Vec4f; 124 | typedef std::vector vector_Vec6f; 125 | typedef std::vector vector_Vec4i; 126 | -typedef std::vector vector_Rect; 127 | +typedef std::vector vector_Rect; 128 | typedef std::vector vector_Rect2d; 129 | typedef std::vector vector_RotatedRect; 130 | typedef std::vector vector_KeyPoint; 131 | @@ -47,7 +47,7 @@ typedef std::vector vector_string; 132 | typedef std::vector vector_Scalar; 133 | 134 | typedef std::vector > vector_vector_char; 135 | -typedef std::vector > vector_vector_Point; 136 | +typedef std::vector > vector_vector_Point; 137 | typedef std::vector > vector_vector_Point2f; 138 | typedef std::vector > vector_vector_Point3f; 139 | typedef std::vector > vector_vector_DMatch; 140 | diff --git a/opencv/modules/python/src2/cv2_convert.cpp b/opencv/modules/python/src2/cv2_convert.cpp 141 | index 35766b4..5d7758f 100644 142 | --- a/opencv/modules/python/src2/cv2_convert.cpp 143 | +++ b/opencv/modules/python/src2/cv2_convert.cpp 144 | @@ -753,7 +753,7 @@ PyObject* pyopencv_from(const std::string& value) 145 | // --- Size 146 | 147 | template<> 148 | -bool pyopencv_to(PyObject* obj, Size& sz, const ArgInfo& info) 149 | +bool pyopencv_to(PyObject* obj, cv::Size& sz, const ArgInfo& info) 150 | { 151 | RefWrapper values[] = {RefWrapper(sz.width), 152 | RefWrapper(sz.height)}; 153 | @@ -761,7 +761,7 @@ bool pyopencv_to(PyObject* obj, Size& sz, const ArgInfo& info) 154 | } 155 | 156 | template<> 157 | -PyObject* pyopencv_from(const Size& sz) 158 | +PyObject* pyopencv_from(const cv::Size& sz) 159 | { 160 | return Py_BuildValue("(ii)", sz.width, sz.height); 161 | } 162 | @@ -783,7 +783,7 @@ PyObject* pyopencv_from(const Size_& sz) 163 | // --- Rect 164 | 165 | template<> 166 | -bool pyopencv_to(PyObject* obj, Rect& r, const ArgInfo& info) 167 | +bool pyopencv_to(PyObject* obj, cv::Rect& r, const ArgInfo& info) 168 | { 169 | RefWrapper values[] = {RefWrapper(r.x), RefWrapper(r.y), 170 | RefWrapper(r.width), 171 | @@ -792,7 +792,7 @@ bool pyopencv_to(PyObject* obj, Rect& r, const ArgInfo& info) 172 | } 173 | 174 | template<> 175 | -PyObject* pyopencv_from(const Rect& r) 176 | +PyObject* pyopencv_from(const cv::Rect& r) 177 | { 178 | return Py_BuildValue("(iiii)", r.x, r.y, r.width, r.height); 179 | } 180 | @@ -939,14 +939,14 @@ PyObject* pyopencv_from(const Range& r) 181 | // --- Point 182 | 183 | template<> 184 | -bool pyopencv_to(PyObject* obj, Point& p, const ArgInfo& info) 185 | +bool pyopencv_to(PyObject* obj, cv::Point& p, const ArgInfo& info) 186 | { 187 | RefWrapper values[] = {RefWrapper(p.x), RefWrapper(p.y)}; 188 | return parseSequence(obj, values, info); 189 | } 190 | 191 | template<> 192 | -PyObject* pyopencv_from(const Point& p) 193 | +PyObject* pyopencv_from(const cv::Point& p) 194 | { 195 | return Py_BuildValue("(ii)", p.x, p.y); 196 | } 197 | diff --git a/opencv/modules/python/src2/gen2.py b/opencv/modules/python/src2/gen2.py 198 | index 29a9195..c5ee777 100755 199 | --- a/opencv/modules/python/src2/gen2.py 200 | +++ b/opencv/modules/python/src2/gen2.py 201 | @@ -42,7 +42,7 @@ gen_template_check_self = Template(""" 202 | return failmsgp("Incorrect type of self (must be '${name}' or its derivative)"); 203 | ${pname} _self_ = ${cvt}(self1); 204 | """) 205 | -gen_template_call_constructor_prelude = Template("""new (&(self->v)) Ptr<$cname>(); // init Ptr with placement new 206 | +gen_template_call_constructor_prelude = Template("""new (&(self->v)) cv::Ptr<$cname>(); // init Ptr with placement new 207 | if(self) """) 208 | 209 | gen_template_call_constructor = Template("""self->v.reset(new ${cname}${py_args})""") 210 | @@ -435,7 +435,7 @@ class ClassInfo(object): 211 | return 'CVPY_TYPE({}, {}, {}, {}, {}, {}, "{}")\n'.format( 212 | self.export_name, 213 | self.class_id, 214 | - self.cname if self.issimple else "Ptr<{}>".format(self.cname), 215 | + self.cname if self.issimple else "cv::Ptr<{}>".format(self.cname), 216 | self.original_name if self.issimple else "Ptr", 217 | baseptr, 218 | constructor_name, 219 | @@ -446,7 +446,7 @@ class ClassInfo(object): 220 | 221 | def handle_ptr(tp): 222 | if tp.startswith('Ptr_'): 223 | - tp = 'Ptr<' + "::".join(tp.split('_')[1:]) + '>' 224 | + tp = 'cv::Ptr<' + "::".join(tp.split('_')[1:]) + '>' 225 | return tp 226 | 227 | 228 | @@ -736,6 +736,16 @@ class FuncVariant(object): 229 | self.args[argno].py_outputarg = True 230 | self.py_outlist = outlist 231 | 232 | +def prefix_ambiguous_ios(val: str): 233 | + cnames = ["Rect", "Point", "Size"] 234 | + r = val 235 | + if val in cnames or val.startswith("Ptr<"): 236 | + r = f"cv::{r}" 237 | + for cn in cnames: 238 | + r = r.replace(f"<{cn}>", f"") 239 | + if r.startswith(f"{cn}("): 240 | + r = f"cv::{r}" 241 | + return r 242 | 243 | class FuncInfo(object): 244 | def __init__(self, classname, name, cname, isconstructor, namespace, is_static): 245 | @@ -841,8 +851,8 @@ class FuncInfo(object): 246 | if not self.is_static: 247 | code += gen_template_check_self.substitute( 248 | name=selfinfo.name, 249 | - cname=selfinfo.cname if selfinfo.issimple else "Ptr<{}>".format(selfinfo.cname), 250 | - pname=(selfinfo.cname + '*') if selfinfo.issimple else "Ptr<{}>".format(selfinfo.cname), 251 | + cname=selfinfo.cname if selfinfo.issimple else "cv::Ptr<{}>".format(selfinfo.cname), 252 | + pname=(selfinfo.cname + '*') if selfinfo.issimple else "cv::Ptr<{}>".format(selfinfo.cname), 253 | cvt='' if selfinfo.issimple else '*' 254 | ) 255 | fullname = selfinfo.wname + "." + fullname 256 | @@ -894,7 +904,7 @@ class FuncInfo(object): 257 | else: 258 | if tp in all_classes: 259 | tp_classinfo = all_classes[tp] 260 | - cname_of_value = tp_classinfo.cname if tp_classinfo.issimple else "Ptr<{}>".format(tp_classinfo.cname) 261 | + cname_of_value = tp_classinfo.cname if tp_classinfo.issimple else "cv::Ptr<{}>".format(tp_classinfo.cname) 262 | arg_type_info = ArgTypeInfo(cname_of_value, FormatStrings.object, defval0, True) 263 | assert not (a.is_smart_ptr and tp_classinfo.issimple), "Can't pass 'simple' type as Ptr<>" 264 | if not a.is_smart_ptr and not tp_classinfo.issimple: 265 | @@ -944,9 +954,9 @@ class FuncInfo(object): 266 | if a.outputarg and not a.inputarg: 267 | defval = "" 268 | if defval: 269 | - code_decl += " %s %s=%s;\n" % (arg_type_info.atype, a.name, defval) 270 | + code_decl += " %s %s=%s;\n" % (prefix_ambiguous_ios(arg_type_info.atype), a.name, prefix_ambiguous_ios(defval)) 271 | else: 272 | - code_decl += " %s %s;\n" % (arg_type_info.atype, a.name) 273 | + code_decl += " %s %s;\n" % (prefix_ambiguous_ios(arg_type_info.atype), a.name) 274 | 275 | if not code_args.endswith("("): 276 | code_args += ", " 277 | @@ -974,7 +984,7 @@ class FuncInfo(object): 278 | code_prelude = "" 279 | code_fcall = "" 280 | if v.rettype: 281 | - code_decl += " " + v.rettype + " retval;\n" 282 | + code_decl += " " + prefix_ambiguous_ios(v.rettype) + " retval;\n" 283 | code_fcall += "retval = " 284 | if not v.isphantom and ismethod and not self.is_static: 285 | code_fcall += "_self_->" + self.cname 286 | @@ -1390,11 +1400,11 @@ class PythonWrapperGenerator(object): 287 | self.code_types.write(gen_template_map_type_cvt.substitute(name=classinfo.name, cname=classinfo.cname)) 288 | else: 289 | mappable_code = "\n".join([ 290 | - gen_template_mappable.substitute(cname=classinfo.cname, mappable=mappable) 291 | + gen_template_mappable.substitute(cname=classinfo.cname, mappable=prefix_ambiguous_ios(mappable)) 292 | for mappable in classinfo.mappables]) 293 | code = gen_template_type_decl.substitute( 294 | name=classinfo.name, 295 | - cname=classinfo.cname if classinfo.issimple else "Ptr<{}>".format(classinfo.cname), 296 | + cname=classinfo.cname if classinfo.issimple else "cv::Ptr<{}>".format(classinfo.cname), 297 | mappable_code=mappable_code 298 | ) 299 | self.code_types.write(code) 300 | diff --git a/opencv/modules/python/src2/pycompat.hpp b/opencv/modules/python/src2/pycompat.hpp 301 | index 05a3909..6d49fcc 100644 302 | --- a/opencv/modules/python/src2/pycompat.hpp 303 | +++ b/opencv/modules/python/src2/pycompat.hpp 304 | @@ -138,7 +138,7 @@ bool pyopencv_to(PyObject* dst, TYPE& src, const ArgInfo& info) 305 | { \ 306 | if (!dst || dst == Py_None) \ 307 | return true; \ 308 | - Ptr ptr; \ 309 | + cv::Ptr ptr; \ 310 | \ 311 | if (!pyopencv_to(dst, ptr, info)) return false; \ 312 | src = *ptr; \ 313 | @@ -149,7 +149,7 @@ bool pyopencv_to(PyObject* dst, TYPE& src, const ArgInfo& info) 314 | template<> \ 315 | PyObject* pyopencv_from(const TYPE& src) \ 316 | { \ 317 | - Ptr ptr(new TYPE()); \ 318 | + cv::Ptr ptr(new TYPE()); \ 319 | \ 320 | *ptr = src; \ 321 | return pyopencv_from(ptr); \ 322 | @@ -161,7 +161,7 @@ bool pyopencv_to(PyObject* dst, TYPE*& src, const ArgInfo& info) 323 | { \ 324 | if (!dst || dst == Py_None) \ 325 | return true; \ 326 | - Ptr ptr; \ 327 | + cv::Ptr ptr; \ 328 | \ 329 | if (!pyopencv_to(dst, ptr, info)) return false; \ 330 | src = ptr; \ 331 | @@ -171,7 +171,7 @@ bool pyopencv_to(PyObject* dst, TYPE*& src, const ArgInfo& info) 332 | #define CV_PY_FROM_CLASS_PTR(TYPE) \ 333 | static PyObject* pyopencv_from(TYPE*& src) \ 334 | { \ 335 | - return pyopencv_from(Ptr(src)); \ 336 | + return pyopencv_from(cv::Ptr(src)); \ 337 | } 338 | 339 | #define CV_PY_TO_ENUM(TYPE) \ 340 | diff --git a/opencv/modules/videoio/misc/python/pyopencv_videoio.hpp b/opencv/modules/videoio/misc/python/pyopencv_videoio.hpp 341 | index e729c86..c5779d3 100644 342 | --- a/opencv/modules/videoio/misc/python/pyopencv_videoio.hpp 343 | +++ b/opencv/modules/videoio/misc/python/pyopencv_videoio.hpp 344 | @@ -23,7 +23,7 @@ bool pyopencv_to(PyObject *o, std::vector& apis, const Arg 345 | 346 | template<> bool pyopencv_to(PyObject* obj, cv::VideoCapture& stream, const ArgInfo& info) 347 | { 348 | - Ptr * obj_getp = nullptr; 349 | + cv::Ptr * obj_getp = nullptr; 350 | if (!pyopencv_VideoCapture_getp(obj, obj_getp)) 351 | return (failmsgp("Incorrect type of self (must be 'VideoCapture' or its derivative)") != nullptr); 352 | 353 | diff --git a/pyproject.toml b/pyproject.toml 354 | index 71de7f9..740eabe 100644 355 | --- a/pyproject.toml 356 | +++ b/pyproject.toml 357 | @@ -9,5 +9,5 @@ requires = [ 358 | "numpy>=2.0.0; python_version>='3.9'", 359 | "pip", 360 | "scikit-build>=0.14.0", 361 | - "setuptools==59.2.0", 362 | + "setuptools", 363 | ] 364 | diff --git a/setup.py b/setup.py 365 | index 48d5a65..125b9b4 100644 366 | --- a/setup.py 367 | +++ b/setup.py 368 | @@ -140,7 +140,7 @@ def main(): 369 | [ r"python/cv2/py.typed" ] if sys.version_info >= (3, 6) else [] 370 | , 371 | "cv2.data": [ # OPENCV_OTHER_INSTALL_PATH 372 | - ("etc" if os.name == "nt" else "share/opencv4") + r"/haarcascades/.*\.xml" 373 | + ("etc" if os.name == "nt" else "sdk/etc" if platform.system() == "Android" else "share/opencv4") + r"/haarcascades/.*\.xml" 374 | ], 375 | "cv2.gapi": [ 376 | "python/cv2" + r"/gapi/.*\.py" 377 | -------------------------------------------------------------------------------- /src/forge/cross.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import itertools 5 | import os 6 | import shutil 7 | import sys 8 | import sysconfig 9 | from os.path import abspath 10 | from pathlib import Path 11 | 12 | from forge import subprocess 13 | 14 | 15 | class CrossVEnv: 16 | BASE_VERSION = { 17 | "android": "24", 18 | "iOS": "13.0", 19 | "tvOS": "12.0", 20 | "watchOS": "4.0", 21 | } 22 | 23 | HOST_SDKS = { 24 | "android": [ 25 | ("android", "arm64-v8a"), 26 | ("android", "armeabi-v7a"), 27 | ("android", "x86_64"), 28 | ("android", "x86"), 29 | ], 30 | "iOS": [ 31 | ("iphoneos", "arm64"), 32 | ("iphonesimulator", "arm64"), 33 | ("iphonesimulator", "x86_64"), 34 | ], 35 | "tvOS": [ 36 | ("appletvos", "arm64"), 37 | ("appletvsimulator", "arm64"), 38 | ("appletvsimulator", "x86_64"), 39 | ], 40 | "watchOS": [ 41 | ("watchos", "arm64_32"), 42 | ("watchsimulator", "arm64"), 43 | ("watchsimulator", "x86_64"), 44 | ], 45 | } 46 | 47 | PLATFORM_TRIPLET = { 48 | "android": "linux-android", 49 | "iphoneos": "apple-ios", 50 | "iphonesimulator": "apple-ios-simulator", 51 | "appletvos": "apple-tvos", 52 | "appletvsimulator": "apple-tvos-simulator", 53 | "watchos": "apple-watchos", 54 | "watchsimulator": "apple-watchos-simulator", 55 | } 56 | 57 | XCFRAMEWORK_SLICES = { 58 | ("iphonesimulator", "arm64"): "ios-arm64_x86_64-simulator", 59 | ("iphonesimulator", "x86_64"): "ios-arm64_x86_64-simulator", 60 | ("iphoneos", "arm64"): "ios-arm64", 61 | ("appletvsimulator", "arm64"): "tvos-arm64_x86_64-simulator", 62 | ("appletvsimulator", "x86_64"): "tvos-arm64_x86_64-simulator", 63 | ("appletvos", "arm64"): "tvos-arm64", 64 | ("watchsimulator", "arm64"): "watchos-arm64_x86_64-simulator", 65 | ("watchsimulator", "x86_64"): "watchos-arm64_x86_64-simulator", 66 | ("watchos", "arm64_32"): "watchos-arm64_32", 67 | } 68 | 69 | ANDROID_PLATFORM_TRIPLET = { 70 | "arm64-v8a": "aarch64-linux-android", 71 | "armeabi-v7a": "arm-linux-androideabi", 72 | "x86_64": "x86_64-linux-android", 73 | "x86": "i686-linux-android", 74 | } 75 | 76 | def __init__(self, sdk, sdk_version, arch): 77 | self.sdk = sdk 78 | self.sdk_version = sdk_version 79 | self.arch = arch 80 | 81 | self.host_os = { 82 | sdk: host_os for host_os, sdks in self.HOST_SDKS.items() for sdk, _ in sdks 83 | }[self.sdk] 84 | self.platform_identifier = self._platform_identifier(sdk, sdk_version, arch) 85 | self.tag = ( 86 | self._platform_identifier(sdk, sdk_version, arch) 87 | .replace("-", "_") 88 | .replace(".", "_") 89 | ) 90 | self.venv_name = f"venv3.{sys.version_info.minor}-{self.tag}" 91 | if sdk == "android": 92 | self.platform_triplet = self.ANDROID_PLATFORM_TRIPLET[arch] 93 | else: 94 | self.platform_triplet = f"{self.arch}-{self.PLATFORM_TRIPLET[sdk]}" 95 | 96 | # Prime the on-demand variable cache 97 | self._sysconfig_data = None 98 | self._scheme_paths = None 99 | self._install_root = None 100 | self._sdk_root = None 101 | 102 | def __str__(self): 103 | return self.venv_name 104 | 105 | def exists(self) -> bool: 106 | """Does the cross environment exist?""" 107 | return self.venv_path.is_dir() 108 | 109 | @property 110 | def host_python_home(self): 111 | support_path = Path( 112 | os.getenv(f"MOBILE_FORGE_{self.host_os.upper()}_SUPPORT_PATH") 113 | ) 114 | return ( 115 | ( 116 | support_path 117 | / "support" 118 | / f"3.{sys.version_info.minor}" 119 | / self.host_os 120 | / "Python.xcframework" 121 | / self.XCFRAMEWORK_SLICES[(self.sdk, self.arch)] 122 | ) 123 | if self.host_os == "iOS" 124 | else next( 125 | (support_path / "install" / self.host_os / self.arch).glob( 126 | f"python-3.{sys.version_info.minor}.*" 127 | ) 128 | ) 129 | ) 130 | 131 | @property 132 | def venv_path(self) -> Path: 133 | """The location of the cross environment on disk.""" 134 | if self.location is None: 135 | raise RuntimeError("Cross environment hasn't been created.") 136 | return self.location / self.venv_name 137 | 138 | @property 139 | def sysconfig_data(self) -> dict[str, str]: 140 | """The sysconfig data for the cross environment.""" 141 | if self._sysconfig_data is None: 142 | # Run a script in the cross-venv that outputs the config variables 143 | config_var_repr = self.check_output( 144 | [ 145 | "python", 146 | "-c", 147 | "import sysconfig; print(sysconfig.get_config_vars())", 148 | ], 149 | encoding="UTF-8", 150 | ) 151 | 152 | # Parse the output of the previous command as Python, 153 | # turning it back into a dict. 154 | config = {} 155 | exec(f"data = {config_var_repr}", config, config) 156 | self._sysconfig_data = config["data"] 157 | 158 | return self._sysconfig_data 159 | 160 | @property 161 | def scheme_paths(self) -> dict[str, str]: 162 | """The install scheme paths for the cross environment.""" 163 | if self._scheme_paths is None: 164 | # Run a script in the cross-venv that outputs the config variables 165 | config_var_repr = self.check_output( 166 | [ 167 | "python", 168 | "-c", 169 | "import sysconfig; print(sysconfig.get_paths())", 170 | ], 171 | encoding="UTF-8", 172 | ) 173 | 174 | # Parse the output of the previous command as Python, 175 | # turning it back into a dict. 176 | config = {} 177 | exec(f"data = {config_var_repr}", config, config) 178 | self._scheme_paths = config["data"] 179 | 180 | return self._scheme_paths 181 | 182 | @property 183 | def install_root(self) -> Path: 184 | """The path that serves as the installation root for native libraries. 185 | 186 | This is the /opt folder inside the site-packages of the cross environment, so 187 | that native libraries can be installed as wheels. 188 | """ 189 | if self._install_root is None: 190 | # Run a script to get the last element of the cross-venv's sys.path. 191 | # This should be the site-packages folder of the cross environment. 192 | cross_site_packages = self.check_output( 193 | [ 194 | "python", 195 | "-c", 196 | "import sys; print(sys.path[-1])", 197 | ], 198 | encoding="UTF-8", 199 | ).strip() 200 | self._install_root = Path(cross_site_packages) / "opt" 201 | if self.venv_path not in self._install_root.parents: 202 | raise RuntimeError( 203 | f"Install root {self._install_root} doesn't appear to be " 204 | "in the cross environment" 205 | ) 206 | 207 | return self._install_root 208 | 209 | @property 210 | def sdk_root(self) -> Path: 211 | """The path that contains the platform's SDK. 212 | 213 | This is the root folder where adding `/include` gives the include path, and 214 | `/lib` give the library path. 215 | """ 216 | if self._sdk_root is None: 217 | # Run a script to get the last element of the cross-venv's sys.path. 218 | # This should be the site-packages folder of the cross environment. 219 | cross_site_packages = self.check_output( 220 | ["xcrun", "--show-sdk-path", "--sdk", self.sdk], 221 | encoding="UTF-8", 222 | ).strip() 223 | self._sdk_root = Path(cross_site_packages) 224 | 225 | return self._sdk_root 226 | 227 | @classmethod 228 | def _platform_identifier(self, sdk, version, arch): 229 | if sdk == "android": 230 | if version is None: 231 | version = 21 232 | identifier = f"{sdk}-{version}-{arch}" 233 | elif sdk in {"iphoneos", "iphonesimulator"}: 234 | if version is None: 235 | version = "13.0" 236 | identifier = f"ios-{version}-{arch}-{sdk}" 237 | elif sdk in {"appletvos", "appletvsimulator"}: 238 | if version is None: 239 | version = "12.0" 240 | identifier = f"tvos-{version}-{arch}-{sdk}" 241 | elif sdk in {"watchos", "watchsimulator"}: 242 | if version is None: 243 | version = "4.0" 244 | identifier = f"watchos-{version}-{arch}-{sdk}" 245 | else: 246 | raise ValueError(f"Don't know how to build wheels for {sdk}") 247 | return identifier 248 | 249 | def create( 250 | self, 251 | location=None, 252 | clean=False, 253 | ): 254 | """Create a new cross compilation virtual environment. 255 | 256 | :param location: The location in which to create the cross env. Defaults to the 257 | current working directory. 258 | :param clean: Should a pre-existing environment matching the same descriptor 259 | be removed and recreated? 260 | :raises: ``RuntimeError`` if an environment matching the requested host already 261 | exists, and ``clean=False``. 262 | """ 263 | host_python = self.host_python_home / f"bin/python3.{sys.version_info.minor}" 264 | if not host_python.is_file(): 265 | raise RuntimeError(f"Can't find host python {host_python}") 266 | 267 | if self.host_os == "iOS": 268 | self.sysconfigdata_name = ( 269 | f"_sysconfigdata__{self.host_os.lower()}_{self.arch}-{self.sdk}" 270 | ) 271 | 272 | host_sysconfig = ( 273 | self.host_python_home 274 | / f"lib/python3.{sys.version_info.minor}" 275 | / f"{self.sysconfigdata_name}.py" 276 | ) 277 | else: 278 | host_sysconfig = next( 279 | (self.host_python_home / f"lib/python3.{sys.version_info.minor}").glob( 280 | "_sysconfigdata__*.py" 281 | ) 282 | ) 283 | self.sysconfigdata_name = host_sysconfig.stem 284 | 285 | if not host_sysconfig.is_file(): 286 | raise RuntimeError(f"Can't find host sysconfig {host_sysconfig}") 287 | 288 | self.location = Path(location).resolve() if location else Path.cwd() 289 | if self.exists(): 290 | if clean: 291 | print(f"Removing old {self} environment...") 292 | shutil.rmtree(self.venv_path) 293 | else: 294 | raise RuntimeError(f"Environment {self} already exists.") 295 | 296 | print(f"Creating {self}...") 297 | try: 298 | subprocess.run( 299 | None, # Creating the cross venv isn't logged. 300 | [ 301 | sys.executable, 302 | "-m", 303 | "crossenv", 304 | "--sysconfigdata-file", 305 | str(host_sysconfig), 306 | str(host_python), 307 | self.venv_path, 308 | ], 309 | **self.cross_kwargs({}), 310 | ) 311 | except subprocess.CalledProcessError: 312 | raise RuntimeError(f"Unable to create cross platform environment {self}.") 313 | 314 | print("Verifying cross-platform environment...") 315 | self.verify() 316 | print("done.") 317 | print() 318 | print(f"Cross platform-environment {self} created.") 319 | 320 | def verify(self): 321 | # python returns the cross-platform host tag. 322 | output = self.check_output( 323 | ["python", "-c", "import sysconfig; print(sysconfig.get_platform())"], 324 | encoding="UTF-8", 325 | ).strip() 326 | if output != self.platform_identifier: 327 | raise RuntimeError( 328 | f"Cross platform python should be {self.platform_identifier}; got {output}" 329 | ) 330 | 331 | # python is the same version as the local python 332 | local_python_version = sys.version.split(" ")[0] 333 | python_version = self.check_output( 334 | ["python", "-c", "import sys; print(sys.version.split(' ')[0])"], 335 | encoding="UTF-8", 336 | ).strip() 337 | if python_version != local_python_version: 338 | raise RuntimeError( 339 | f"Cross platform python should be {local_python_version!r}; got {python_version!r}" 340 | ) 341 | 342 | # build-python returns the build environment tag. 343 | output = self.check_output( 344 | ["build-python", "-c", "import sysconfig; print(sysconfig.get_platform())"], 345 | encoding="UTF-8", 346 | ).strip() 347 | if output != sysconfig.get_platform(): 348 | raise RuntimeError( 349 | f"Cross platform build-python should be {sysconfig.get_platform()}; got {output}" 350 | ) 351 | 352 | # build-python is the same version as the local python 353 | build_python_version = self.check_output( 354 | ["build-python", "-c", "import sys; print(sys.version.split(' ')[0])"], 355 | encoding="UTF-8", 356 | ).strip() 357 | if build_python_version != local_python_version: 358 | raise RuntimeError( 359 | f"Cross platform build-python should be {local_python_version}; got {build_python_version}" 360 | ) 361 | 362 | # cross-python returns the cross-platform host tag. 363 | output = self.check_output( 364 | ["cross-python", "-c", "import sysconfig; print(sysconfig.get_platform())"], 365 | encoding="UTF-8", 366 | ).strip() 367 | if output != self.platform_identifier: 368 | raise RuntimeError( 369 | f"Cross platform cross-python should be {self.platform_identifier}; got {output}" 370 | ) 371 | 372 | # cross-python is the same version as the local python 373 | cross_python_version = self.check_output( 374 | ["cross-python", "-c", "import sys; print(sys.version.split(' ')[0])"], 375 | encoding="UTF-8", 376 | ).strip() 377 | if cross_python_version != local_python_version: 378 | raise RuntimeError( 379 | f"Cross platform python should be {local_python_version}; got {cross_python_version}" 380 | ) 381 | 382 | def cross_kwargs(self, kwargs): 383 | venv_kwargs = kwargs.copy() 384 | env = venv_kwargs.get("env", {}) 385 | 386 | # Ensure the path is clean, and doesn't include any non-iOS paths. 387 | env["PATH"] = os.pathsep.join( 388 | [ 389 | str(self.host_python_home / "bin"), 390 | str(self.venv_path / "cross" / "bin"), 391 | str(self.venv_path / "build" / "bin"), 392 | str(self.venv_path / "bin"), 393 | str(self.venv_path / self.venv_path.name / "bin"), 394 | str(Path.home() / ".cargo/bin"), 395 | "/usr/bin", 396 | "/bin", 397 | "/usr/sbin", 398 | "/sbin", 399 | "/Library/Apple/usr/bin", 400 | ][1 if self.host_os == "android" else 0 :] 401 | ) 402 | 403 | # Set VIRTUALENV to the active venv 404 | env["VIRTUAL_ENV"] = str(self.venv_path / self.venv_path.name) 405 | 406 | # Remove PYTHONHOME if it's set 407 | try: 408 | del env["PYTHONHOME"] 409 | except KeyError: 410 | pass 411 | 412 | venv_kwargs["env"] = env 413 | return venv_kwargs 414 | 415 | def check_output(self, args, **kwargs): 416 | return subprocess.check_output(args, **self.cross_kwargs(kwargs)) 417 | 418 | def run(self, logfile, *args, **kwargs): 419 | """Run a command in the cross environment. 420 | 421 | This passes the provided arguments directly to invocation of ``subprocess.run``; 422 | however, the ``kwargs`` will be modified to make the process appear to be in an 423 | activated virtual environment. This will: 424 | 425 | * Modify the ``PATH`` to remove the build virtualenv's bin folder, and add the 426 | cross-env's ``bin`` folder, and remove any other path that could be a source 427 | of stray libraries (e.g, Homebrew) and remove the current virtualenv path. 428 | * Set the ``VIRTUAL_ENV`` environment variable 429 | * Remove the ``PYTHONHOME`` environment variable, if it exists. 430 | 431 | If ``env`` is passed in as a keyword argument, the values in that environment 432 | will be augmented by the virtualenv changes. 433 | 434 | For auditing purposes, the final kwargs used at runtime will be output to the 435 | console. 436 | 437 | :param logfile: An open file handle to which all output will be logged. 438 | :param args: The list of command line arguments 439 | :param kwargs: Any extra arguments to pass to the ``subprocess.run`` invocation. 440 | """ 441 | return subprocess.run(logfile, *args, **self.cross_kwargs(kwargs)) 442 | 443 | def pip_install( 444 | self, 445 | logfile, 446 | packages, 447 | update=False, 448 | build=False, 449 | paths=None, 450 | ): 451 | """Install packages into the cross environment. 452 | 453 | :param packages: The list of package names/specifiers to install. 454 | :param update: Should the package be updated ("-U") 455 | :param build: Should the package be installed in the build environment? Defaults 456 | to installing in the host environment. 457 | :param paths: The paths to search for additional wheels ("--find-links"). 458 | """ 459 | # build-pip is a script; pip is a shim with a hashbang that points 460 | # at a python interpreter, which we can't invoke with subprocess. 461 | self.run( 462 | logfile, 463 | (["build-pip"] if build else ["python", "-m", "pip"]) 464 | + [ 465 | "install", 466 | "--disable-pip-version-check", 467 | ] 468 | # If we're doing a host build, require binary packages. 469 | # build environment can use non-binary packages. 470 | + ( 471 | [] 472 | if build 473 | else [ 474 | "--only-binary", 475 | ":all:", 476 | ] 477 | ) 478 | # Update packages if requested 479 | + (["-U"] if update else []) 480 | # Include the local wheels paths if provided. 481 | + ( 482 | list(itertools.chain(*(["--find-links", str(path)] for path in paths))) 483 | if paths 484 | else [] 485 | ) 486 | + (["--extra-index-url", "https://pypi.flet.dev"]) 487 | # Finally, the list of packages to install. 488 | + packages, 489 | ) 490 | 491 | 492 | def main(): 493 | parser = argparse.ArgumentParser() 494 | parser.add_argument("-v", "--verbose", action="store_true", help="Log more detail") 495 | 496 | parser.add_argument( 497 | "--clean", 498 | action="store_true", 499 | help="Create a clean cross-platform virtual environment", 500 | ) 501 | 502 | parser.add_argument( 503 | "--sdk", 504 | choices=sorted( 505 | {sdk[0] for sdks in CrossVEnv.HOST_SDKS.values() for sdk in sdks} 506 | ), 507 | required=True, 508 | help="The host SDK to target.", 509 | ) 510 | parser.add_argument( 511 | "--sdk-version", 512 | default=None, 513 | help="The compatibility version for the host SDK.", 514 | ) 515 | parser.add_argument( 516 | "--arch", required=True, help="The CPU architecture for the host." 517 | ) 518 | 519 | args = parser.parse_args() 520 | 521 | try: 522 | cross_venv = CrossVEnv( 523 | sdk=args.sdk, 524 | sdk_version=args.sdk_version, 525 | arch=args.arch, 526 | ) 527 | cross_venv.create(clean=args.clean) 528 | except RuntimeError as e: 529 | print() 530 | print(f"ERROR: {e}") 531 | sys.exit(1) 532 | 533 | 534 | if __name__ == "__main__": 535 | main() 536 | --------------------------------------------------------------------------------