├── 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 |
--------------------------------------------------------------------------------