diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 509fdc0b..00000000 --- a/.appveyor.yml +++ /dev/null @@ -1,283 +0,0 @@ -image: macos-monterey - -skip_branch_with_pr: true - -environment: - PYTHON_VERSION: 3.12.4 - PYTHON_SHORT_VERSION: 3.12 - CF_ACCESS_KEY_ID: - secure: +m1fzbrEPRecXKCCMn4uA781PAASzJSWAxuJj1c7ctLfWbi5oW4PMnowPK96XtQ5 - CF_SECRET_ACCESS_KEY: - secure: siQTjK+IAmy+zcTSO0d/dnyU/SHC52+gaW8xOT3GFqW8dyRAWr7YXtCU0QvlIC5MFVnbEmgDcDKqINaWN1iD5Cuuw/QAFsF1L/zDnQSvAtE= - CF_ENDPOINT_URL: - secure: lSQBfrqIXIOAYhA0NGej7Pfll1wOSKTTFwQCl8N8lvI22uI5CA/UjRKaqw6KlIZMcXvqTP1w11CVqC2CWnyM3hK857X2tAe8nkO8KT0DCzw= - CF_BUCKET_NAME: flet-simple - - matrix: - # - job_name: 'Android: websockets' - # job_group: build_android - # FORGE_ARCH: android - # FORGE_PACKAGES: >- - # websockets:12.0 - - # - job_name: 'iOS: websockets' - # job_group: build_ios - # FORGE_ARCH: iOS - # FORGE_PACKAGES: >- - # websockets:12.0 - - - job_name: 'Android arm64-v8a: opencv-python' - job_group: build_android - FORGE_ARCH: 'android:arm64-v8a' - FORGE_PACKAGES: numpy:2.0.0 opencv-python:4.10.0.84 - - - job_name: 'Android armeabi-v7a: opencv-python' - job_group: build_android - FORGE_ARCH: 'android:armeabi-v7a' - FORGE_PACKAGES: numpy:2.0.0 opencv-python:4.10.0.84 - - - job_name: 'Android x86_64: opencv-python' - job_group: build_android - FORGE_ARCH: 'android:x86_64' - FORGE_PACKAGES: numpy:2.0.0 opencv-python:4.10.0.84 - - - job_name: 'Android x86: opencv-python' - job_group: build_android - FORGE_ARCH: 'android:x86' - FORGE_PACKAGES: numpy:2.0.0 opencv-python:4.10.0.84 - - - job_name: 'iOS iphone arm64: opencv-python' - job_group: build_ios - FORGE_ARCH: 'iphoneos:arm64' - FORGE_PACKAGES: numpy:2.0.0 opencv-python:4.10.0.84 - - - job_name: 'iOS simulator arm64: opencv-python' - job_group: build_ios - FORGE_ARCH: 'iphonesimulator:arm64' - FORGE_PACKAGES: numpy:2.0.0 opencv-python:4.10.0.84 - - - job_name: 'iOS simulator x86_64: opencv-python' - job_group: build_ios - FORGE_ARCH: 'iphonesimulator:x86_64' - FORGE_PACKAGES: numpy:2.0.0 opencv-python:4.10.0.84 - - - job_name: 'Android: pydantic-core, pillow, lru-dict, contourpy, kiwisolver, aiohttp, bitarray, argon2-cffi-binding, bcrypt, cryptography, brotli, websockets' - job_group: build_android - FORGE_ARCH: android - FORGE_PACKAGES: >- - cffi:1.16.0 - libjpeg:3.0.3 - libpng:1.6.43 - freetype:2.13.2 - pillow:10.3.0 - lru-dict:1.3.0 - yarl:1.9.4 - contourpy:1.2.1 - kiwisolver:1.4.5 - aiohttp:3.9.5 - bitarray:2.9.2 - argon2-cffi-bindings:21.2.0 - bcrypt:4.1.3 - cryptography:42.0.7 - brotli:1.1.0 - pydantic-core:2.18.4 - websockets:12.0 - - - job_name: 'Android: numpy, matplotlib, pandas, blis' - job_group: build_android - FORGE_ARCH: android - FORGE_PACKAGES: >- - numpy:1.26.4 - numpy:2.0.0 - matplotlib:3.9.0 - pandas:2.2.2 - blis:0.9.1 - - - job_name: 'iOS: pillow, lru-dict, yarl, contourpy, kiwisolver, aiohttp, bitarray, websockets' - job_group: build_ios - FORGE_ARCH: iOS - FORGE_PACKAGES: >- - libjpeg:3.0.3 - libpng:1.6.43 - freetype:2.13.2 - pillow:10.3.0 - lru-dict:1.3.0 - yarl:1.9.4 - contourpy:1.2.1 - kiwisolver:1.4.5 - aiohttp:3.9.5 - bitarray:2.9.2 - websockets:12.0 - - - job_name: 'iOS: cffi, argon2-cffi-bindings, bcrypt, cryptography, brotli' - job_group: build_ios - FORGE_ARCH: iOS - FORGE_PACKAGES: >- - cffi:1.16.0 - argon2-cffi-bindings:21.2.0 - bcrypt:4.1.3 - cryptography:42.0.7 - brotli:1.1.0 - - - job_name: 'iOS: pydantic-core' - job_group: build_ios - FORGE_ARCH: iOS - FORGE_PACKAGES: >- - pydantic-core:2.18.4 - - - job_name: 'iOS: numpy, matplotlib, pandas, blis' - job_group: build_ios - FORGE_ARCH: iOS - FORGE_PACKAGES: >- - numpy:1.26.4 - numpy:2.0.0 - matplotlib:3.9.0 - pandas:2.2.2 - blis:0.9.1 - - - job_name: Re-build Simple index - job_group: rebuild_index - job_depends_on: build_android, build_ios - -stack: -- python $PYTHON_SHORT_VERSION - -on_success: -- sh: | - if test -d logs; then - find logs -type f -iname *.log -exec appveyor PushArtifact {} \; - fi - -on_failure: -- sh: | - if test -d errors; then - find errors -type f -iname *.log -exec appveyor PushArtifact {} \; - fi - -for: - # ====================================== - # Build Android packages - # ====================================== - - - matrix: - only: - - job_group: build_android - - environment: - APPVEYOR_BUILD_WORKER_IMAGE: ubuntu-gce-c - NDK_VERSION: r27-beta2 - - install: - # download Python for Android - - python_android_dir=$HOME/projects/python-android - - curl -#OL https://github.com/flet-dev/python-android/releases/download/v${PYTHON_VERSION}/python-android-install-${PYTHON_VERSION}.tar.gz - - mkdir -p $python_android_dir - - tar -xzf python-android-install-${PYTHON_VERSION}.tar.gz -C $python_android_dir - - # install Android NDK - - .ci/install_ndk.sh - - # configure forge - - export PYTHON_ANDROID_SUPPORT=$python_android_dir - - source ./setup.sh $PYTHON_VERSION - - # install Rust - - curl https://sh.rustup.rs -sSf | sh -s -- -y - - . "$HOME/.cargo/env" - - export PATH="$PATH:$HOME/.cargo/bin" - - rustup target add aarch64-linux-android - - rustup target add arm-linux-androideabi - - rustup target add x86_64-linux-android - - rustup target add i686-linux-android - - build_script: - - sh: | - IFS=' ' read -r -a packages <<< "$FORGE_PACKAGES" - for package in "${packages[@]}"; do - forge $FORGE_ARCH $package || exit 1 - done - - # cleanup - - rm dist/ninja-* - - rm dist/cmake-* - - rm dist/bzip2-* - - rm dist/xz-* - - rm dist/libffi-* - - rm dist/openssl-* - - deploy_script: - - pip install boto3 - - python .ci/publish-wheels.py dist - - test: off - - # ====================================== - # Build iOS packages - # ====================================== - - - matrix: - only: - - job_group: build_ios - - environment: - APPVEYOR_BUILD_WORKER_IMAGE: macos-sonoma - - install: - # download Python for iOS - - python_ios_dir=$HOME/projects/python-ios - - curl -#OL https://github.com/flet-dev/python-ios/releases/download/v${PYTHON_VERSION}/python-ios-install-${PYTHON_VERSION}.tar.gz - - mkdir -p $python_ios_dir - - tar -xzf python-ios-install-${PYTHON_VERSION}.tar.gz -C $python_ios_dir - - # install Rust - - curl https://sh.rustup.rs -sSf | sh -s -- -y - - . "$HOME/.cargo/env" - - export PATH="$PATH:$HOME/.cargo/bin" - - rustup target add aarch64-apple-ios - - rustup target add aarch64-apple-ios-sim - - rustup target add x86_64-apple-ios - - # configure forge - - export PYTHON_APPLE_SUPPORT=$python_ios_dir - - source ./setup.sh $PYTHON_VERSION - - # refresh PATH - - export PATH="$PATH:$HOME/.cargo/bin" - - build_script: - - sh: | - IFS=' ' read -r -a packages <<< "$FORGE_PACKAGES" - for package in "${packages[@]}"; do - forge $FORGE_ARCH $package || exit 1 - done - - # cleanup - - rm dist/ninja-* - - rm dist/cmake-* - - rm dist/bzip2-* - - rm dist/xz-* - - rm dist/libffi-* - - rm dist/openssl-* - - deploy_script: - - pip install boto3 - - python .ci/publish-wheels.py dist - - test: off - - # ====================================== - # Rebuild Simple index - # ====================================== - - - matrix: - only: - - job_group: rebuild_index - - environment: - APPVEYOR_BUILD_WORKER_IMAGE: ubuntu - - deploy_script: - - pip3 install boto3 - - python .ci/rebuild-simple-index.py - - test: off diff --git a/.ci/common.sh b/.ci/common.sh new file mode 100644 index 00000000..6073bf34 --- /dev/null +++ b/.ci/common.sh @@ -0,0 +1,5 @@ +function publish_to_pypi() { + for wheel in "$@"; do + curl -F package=@$wheel https://$GEMFURY_TOKEN@push.fury.io/flet/ + done +} diff --git a/.ci/install_ndk.sh b/.ci/install_ndk.sh index 0193e155..cd4cea50 100755 --- a/.ci/install_ndk.sh +++ b/.ci/install_ndk.sh @@ -46,4 +46,6 @@ if [[ -z "${NDK_HOME-}" ]]; then fi else echo "NDK home: $NDK_HOME" -fi \ No newline at end of file +fi + +export NDK_HOME diff --git a/.claude/skills/native-recipe-bumps/SKILL.md b/.claude/skills/native-recipe-bumps/SKILL.md new file mode 100644 index 00000000..c3776b63 --- /dev/null +++ b/.claude/skills/native-recipe-bumps/SKILL.md @@ -0,0 +1,232 @@ +--- +name: native-recipe-bumps +description: Playbook for bumping native-library recipes in mobile-forge (libxml2, libxslt, openssl-class C deps and their consumers). Covers the Jinja-templated meta.yaml pattern for version-conditional URLs / patches / host pins, the build.sh quirks for cross-compiling autotools projects to iOS and Android (NDK r27d, API 24, Python 3.12), and the recurring pitfalls (iconv on Android, iOS static-only builds, bash 3.2 + set -u, etc). +--- + +# Bumping native-library recipes in mobile-forge + +This skill captures conventions for editing recipes in `recipes//` so that: +- the new version builds on iPhoneOS, iPhoneSimulator, and Android API 24, and +- the recipe stays back-compatible — flipping one Jinja `version` line at the top reverts to the previously-pinned version (URL, patches, host deps follow automatically). + +## File layout per recipe + +``` +recipes// + meta.yaml # rendered through Jinja before YAML parsing + build.sh # optional; for autotools / make-based deps + patches/ + mobile-.patch # one per supported version line + mobile-.patch +``` + +## meta.yaml: the Jinja idiom + +`src/forge/package.py` runs the file through `jinja2.Template(...).render(sdk=..., sdk_version=..., arch=..., version=..., py_version=...)` *before* `yaml.safe_load`. Two patterns matter: + +**1. Comment-prefixed Jinja (`# {% ... %}`)** — the only form that keeps the YAML linter happy. `{% set %}` / `{% if %}` lines that don't produce YAML output should always be `# {% ... %}`. The `#` plus blank rendered output is a no-op for YAML. This is the same idiom `recipes/numpy/meta.yaml` uses. + +**2. Single conditional block sets every dependent variable** — version, host-dep versions, patch filename, anything else that branches by version. Then the body of the file just interpolates `{{ var }}`. Avoid scattering multiple `{% if %}` blocks throughout the file. + +Canonical shape (from `recipes/flet-libxslt/meta.yaml`): + +```yaml +# {% set version = "1.1.45" %} +# {% if version == "1.1.32" %} +# {% set libxml2_version = "2.9.8" %} +# {% set patch = "mobile-1.1.32.patch" %} +# {% else %} +# {% set libxml2_version = "2.15.3" %} +# {% set patch = "mobile-1.1.45.patch" %} +# {% endif %} + +package: + name: flet-libxslt + version: '{{ version }}' + +source: + url: https://download.gnome.org/sources/libxslt/{{ version.rsplit('.', 1)[0] }}/libxslt-{{ version }}.tar.xz + +requirements: + host: + - flet-libxml2 {{ libxml2_version }} + +patches: + - {{ patch }} +``` + +To go back to 1.1.32: change one line at the top — URL, host requirement, and patch all flip in lockstep. + +### URL templating for GNOME tarballs + +`https://download.gnome.org/sources///-.tar.xz` — directory is major.minor, file is full version: + +``` +url: https://download.gnome.org/sources/libxml2/{{ version.rsplit('.', 1)[0] }}/libxml2-{{ version }}.tar.xz +``` + +`version.rsplit('.', 1)[0]` turns `2.15.3` → `2.15`, `2.9.8` → `2.9`. + +### SDK-conditional script_env + +The Jinja `sdk` variable holds `'iphoneos'`, `'iphonesimulator'`, or `'android'`. The framework formats `script_env.LDFLAGS / CFLAGS / CPPFLAGS` by *appending* to the compiler-derived value (other keys are set verbatim). Use this for platform-specific link flags: + +```yaml +build: + script_env: + WITH_XML2_CONFIG: '{platlib}/opt/bin/xml2-config' +# {% if sdk != 'android' %} + LDFLAGS: -liconv +# {% endif %} +``` + +`numpy/meta.yaml` writes `sdk == 'iOS'` — that branch never matches the values that are actually passed (the per-slice SDK names). Don't copy that comparison; use `sdk == 'iphoneos'` / `sdk == 'iphonesimulator'` / `sdk == 'android'`. + +## Patches + +Patches in `meta.yaml`'s `patches:` list are simple filenames in `patches/`. The framework has *no* conditional-patch support — don't extend the schema for it. Put the conditional in Jinja: + +- one patch file per supported version line, named `mobile-.patch` +- `# {% set patch = "mobile-X.Y.x.patch" %}` inside the version block +- `patches: [{{ patch }}]` in the body + +When a patch needs to apply across both old and new versions (e.g. lxml's `setupinfo.py` macOS-SDK filter), keep it as a single `mobile.patch` and don't introduce conditional naming. Verify with `patch --dry-run -p1 --ignore-whitespace < patches/mobile.patch` against both extracted tarballs before committing. + +### Renaming with `git mv` + +When splitting `mobile.patch` into `mobile-X.Y.x.patch` + `mobile-A.B.x.patch`, do `git mv` for the original then add the new file — git detects the rename and history is preserved. + +## build.sh patterns + +### Bash 3.2 + `set -u` compatibility + +macOS still ships bash 3.2. Two gotchas: + +- **No bash arrays for optional flags** — `"${arr[@]}"` on an empty array under `set -u` errors with `unbound variable`. Use a plain string: + ```bash + if [ "$CROSS_VENV_SDK" = "android" ]; then + iconv_arg=--without-iconv + else + iconv_arg=--with-iconv + fi + ./configure ... $iconv_arg + ``` +- **`shopt -s nullglob` for cleanup globs** — without it, `rm -r $PREFIX/lib/*.la` passes literal `*.la` when nothing matches and fails. Combined with `rm -rf` it makes cleanup tolerant of layout changes between versions. + +### Cleanup recipe + +```bash +shopt -s nullglob +rm -rf $PREFIX/share +rm -rf $PREFIX/lib/cmake $PREFIX/lib/pkgconfig $PREFIX/lib/*.la $PREFIX/lib/*.sh +``` + +**Do *not* delete `*.a`.** iOS only builds static archives. Removing them leaves `lib/` empty, and downstream consumers (lxml, libxslt) that want to link statically have nothing to find. Android only produces `*.so` so the `.a` line would be a no-op there anyway. + +### Available env vars in build.sh + +The framework exposes (see `compile()` in `src/forge/build.py`): + +- `HOST_TRIPLET`, `HOST_ARCH`, `BUILD_TRIPLET` +- `SDK`, `SDK_VERSION`, `SDK_ROOT` (empty for Android) +- `CROSS_VENV_SDK` — same as `SDK`, the canonical "is this Android?" check +- `PREFIX` — install root (`/wheel/opt`) +- `PYTHON_PREFIX`, `PLATLIB` +- `CPU_COUNT`, plus `CC` / `CXX` / `AR` / `STRIP` / `RANLIB` / `CFLAGS` / `CPPFLAGS` / `LDFLAGS` + +There is **no** `RECIPE_DIR` env var. Don't try to apply patches from build.sh — let the framework's `patch_source()` do it. + +### Skipping CLI binary subdirs + +When a project's autotools build links a CLI tool against the library and that tool can't be linked on iOS (e.g. xsltproc using libxml2 symbols not in the iOS SDK's `libxml2.tbd`), restrict recursion: + +```bash +make -j $CPU_COUNT V=1 SUBDIRS='lib1 lib2' +make install SUBDIRS='lib1 lib2' +``` + +This is cleaner than fighting the linker — wheels don't ship CLI tools anyway. + +## Cross-compile pitfalls (catalogue) + +- **Android NDK r27d API 24 has no `iconv`** in bionic (added in API 28). For libxml2 ≥ 2.10 configure makes iconv mandatory by default (silent soft-fail in 2.9.x). Pass `--without-iconv` for Android only; iOS has system iconv. +- **iOS builds static-only**, Android builds shared-only with this toolchain. Don't assume both produce both. +- **iOS SDK ships `libxml2.tbd` with an *old* libxml2 API.** When statically linking our newer libxml2 into a CLI binary, the linker pulls the SDK stub for unresolved transitive symbols and fails. For a wheel target this only matters if you build a binary; for shared-object Python extensions, dyld resolves at load time so it's fine. +- **iOS linker doesn't auto-add `-liconv`.** When libxml2 is built with iconv and linked statically into something else, the consumer must add `-liconv` explicitly. lxml's `setupinfo.libraries()` lists `xslt exslt xml2 z m` only, so push `-liconv` via `script_env.LDFLAGS` for non-Android. +- **macOS SDK include leaks into cross-build.** lxml's `xml2-config --cflags` parsing picks up `-I…/MacOSX.sdk/usr/include`. The recipe ships a `mobile.patch` to filter that out — apply or carry forward when bumping lxml. +- **Header reshuffles.** libxml2 < 2.15 installs to `$includedir/libxml2/libxml`; the build.sh `mv $PREFIX/include/libxml2/libxml $PREFIX/include` flatten still applies in 2.15.x — re-check on future bumps. +- **`libxml2.syms` was removed upstream around 2.10.** Old `mobile.patch`es that comment out `docb*` / `xmlDllMain` symbols don't apply to ≥ 2.10 and are unnecessary there (modern config.sub already handles `*-apple-ios`). +- **`config.sub` in modern releases handles `*-apple-ios` natively** but still rejects `*-apple-ios-simulator` (kernel=ios, os=simulator combo not whitelisted). The minimal patch is to add an `ios-simulator*)` case in the `case $basic_os in` block that sets `kernel=` and `os=$basic_os`. + +## Verification before re-running `forge build` + +Cheap checks worth doing in-shell, without spinning up the cross-venv: + +```bash +# Render meta.yaml with both target versions and inspect the parsed result +source venv3.12/bin/activate && python -c " +import jinja2, yaml +with open('recipes//meta.yaml') as f: + tpl = f.read() +for v in ['', '']: + src = tpl.replace('', v, 1) if v != '' else tpl + rendered = jinja2.Template(src).render(sdk='iphoneos', sdk_version='13.0', arch='arm64', version=None, py_version=None) + print(yaml.safe_load(rendered)) +" + +# Confirm patches still apply against fresh tarballs +cd /tmp && tar xf -.tar.xz && cd - +patch --dry-run -p1 --ignore-whitespace < /path/to/recipes//patches/mobile-.patch + +# Quick triplet sanity check on a config.sub patch +./config.sub aarch64-apple-ios-simulator +./config.sub x86_64-apple-ios-simulator +./config.sub aarch64-linux-android +``` + +Render with both `sdk='iphoneos'` and `sdk='android'` whenever the file has SDK conditionals. + +## Build / debug loop + +`forge` takes a *host* (top-level platform name like `iOS`/`android`, or a `platform:arch` / `platform:version:arch` triple) followed by one or more recipe names. There is no `build` subcommand. + +```bash +# Single arch — fastest iteration, good for quick tests +forge iphoneos:arm64 flet-libxslt +forge iphonesimulator:arm64 flet-libxslt +forge iphonesimulator:x86_64 flet-libxslt +forge android:arm64-v8a flet-libxslt +forge android:armeabi-v7a flet-libxslt +forge android:x86_64 flet-libxslt +forge android:x86 flet-libxslt + +# All arches for one platform +forge iOS flet-libxslt +forge android flet-libxslt + +# Override the version without editing meta.yaml (':') +forge android flet-libxslt:1.1.32 +forge iphoneos:arm64 lxml:5.3.0 + +# Override version + build number (':::' or '::') +forge android flet-libxslt:1.1.45::1 + +# Useful flags +forge --clean iphoneos:arm64 flet-libxml2 # wipe build dir first +forge -v iOS lxml # verbose log +forge --all-versions iOS lxml # build every supported version +``` + +Recipes can also be addressed by path (anything containing a slash): `forge iOS ./recipes/lxml`. + +After a failure, the latest log lives at `errors/--.log` (or `errors/--cp312-.log` for Python packages). It includes the full stderr+stdout *plus* the recipe's environment dumped near the bottom — useful for confirming `CROSS_VENV_SDK`, `PREFIX`, etc. were what you expected. + +When a build mostly succeeds and dies in cleanup, look at the last `<<< Return code: N` line and the immediately preceding shell error — most "failed" libxml2/libxslt builds are post-install `rm` errors, not real build failures. + +## Recipes that already follow these conventions + +- `recipes/flet-libxml2/` — Jinja version + iconv conditional in build.sh, two version-suffixed patches. +- `recipes/flet-libxslt/` — single Jinja block sets version + libxml2 dep + patch; SUBDIRS override to skip xsltproc. +- `recipes/lxml/` — version-conditional libxml2/libxslt host pins; SDK-conditional `LDFLAGS=-liconv`; carries `mobile.patch` for the macOS SDK include filter. +- `recipes/flet-libopaque/` — minimal `{% set version %}` + URL template, no version branching needed. +- `recipes/numpy/` — selective patch via Jinja + override-version (`{% if version and version < (2, 0) %}`); shows the override-driven pattern when versions need to be flippable from the CLI rather than the meta.yaml itself. diff --git a/.github/workflows/build-wheels-with-cibuildwheel.yml b/.github/workflows/build-wheels-with-cibuildwheel.yml new file mode 100644 index 00000000..42129b02 --- /dev/null +++ b/.github/workflows/build-wheels-with-cibuildwheel.yml @@ -0,0 +1,43 @@ +name: wheels-android + +on: + workflow_dispatch: + +env: + UV_PYTHON: "3.12" # cibuildwheel runner python; not the target + +jobs: + build_android_wheels: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup uv + uses: astral-sh/setup-uv@v6 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + - name: Set up Android SDK + uses: android-actions/setup-android@v3 + + - name: Build Android wheels + env: + CIBW_PLATFORM: android + CIBW_BUILD: "cp313-android_*" + CIBW_ARCHS_ANDROID: "arm64_v8a x86_64" + ANDROID_API_LEVEL: "24" + run: | + wget https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz + tar xf websockets-16.0.tar.gz + cd websockets-16.0 + uvx cibuildwheel --output-dir wheelhouse + + - uses: actions/upload-artifact@v4 + with: + name: wheels-android + path: websockets-16.0/wheelhouse/*.whl \ No newline at end of file diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml new file mode 100644 index 00000000..14a18070 --- /dev/null +++ b/.github/workflows/build-wheels.yml @@ -0,0 +1,179 @@ +name: Build wheels + +on: + push: + pull_request: + workflow_dispatch: + inputs: + archs: + description: "Architectures (comma-separated, e.g. android,iOS)" + required: false + default: "android,iOS" + packages: + description: "Packages (comma-separated, e.g. pillow:11.1.0,pydantic-core:2.33.2)" + required: false + default: "pydantic-core:2.33.2" + publish: + description: "Publish to PyPI" + type: boolean + default: false + +env: + UV_PYTHON: "3.12.12" + MOBILE_FORGE_CACHE_DOWNLOADS_OFF: "1" + NDK_VERSION: r27d + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Get changed recipes + id: changed-recipes + uses: tj-actions/changed-files@v45 + with: + files: recipes/** + dir_names: true + dir_names_max_depth: 2 + + - id: detect-packages + shell: bash + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + INPUT_PACKAGES: ${{ inputs.packages }} + CHANGED_DIRS: ${{ steps.changed-recipes.outputs.all_changed_files }} + run: | + SMOKE_TEST="pydantic-core:2.33.2" + if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then + pkgs="${INPUT_PACKAGES:-$SMOKE_TEST}" + else + pkgs="" + for dir in $CHANGED_DIRS; do + [[ -f "$dir/meta.yaml" ]] || continue # skip deleted recipes + pkgs="${pkgs:+$pkgs,}${dir#recipes/}:" + done + pkgs="${pkgs:-$SMOKE_TEST}" + fi + echo "Detected packages: $pkgs" + echo "packages=$pkgs" >> "$GITHUB_OUTPUT" + + - id: set-matrix + shell: bash + run: | + ARCHS="${{ inputs.archs || 'android,iOS' }}" + PACKAGES="${{ steps.detect-packages.outputs.packages }}" + + matrix='{"include":[' + first=true + for arch in $(echo "$ARCHS" | tr ',' ' '); do + for pkg in $(echo "$PACKAGES" | tr ',' ' '); do + pkg_name="${pkg%%:*}" + if [ "$first" = true ]; then first=false; else matrix+=','; fi + if [[ "$arch" == "android" ]]; then + runner="ubuntu-latest" + platform="android" + rust_targets="aarch64-linux-android,arm-linux-androideabi,x86_64-linux-android,i686-linux-android" + else + runner="macos-latest" + platform="ios" + rust_targets="aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios" + fi + matrix+="{\"job_name\":\"${platform}: ${pkg_name}\",\"artifact_name\":\"${platform}-${pkg_name}\",\"runner\":\"$runner\",\"platform\":\"$platform\",\"forge_arch\":\"$arch\",\"forge_packages\":\"$pkg\",\"rust_targets\":\"$rust_targets\"}" + done + done + matrix+=']}' + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + + build: + needs: setup + name: ${{ matrix.job_name }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.matrix) }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup uv + uses: astral-sh/setup-uv@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.rust_targets }} + + - name: Build wheels + shell: bash + env: + FORGE_ARCH: ${{ matrix.forge_arch }} + FORGE_PACKAGES: ${{ matrix.forge_packages }} + PLATFORM: ${{ matrix.platform }} + run: | + set -euxo pipefail + + PYTHON_SHORT_VERSION="${UV_PYTHON%.*}" + + . .ci/common.sh + export MOBILE_FORGE_ANDROID_SUPPORT_PATH="" + export MOBILE_FORGE_IOS_SUPPORT_PATH="" + + if [[ "$PLATFORM" == "android" ]]; then + sudo apt-get update + sudo apt-get install -y sqlite3 + + python_android_dir="$HOME/projects/python-build/android" + curl -#OL "https://github.com/flet-dev/python-build/releases/download/v${PYTHON_SHORT_VERSION}/python-android-mobile-forge-${PYTHON_SHORT_VERSION}.tar.gz" + mkdir -p "$python_android_dir" + tar -xzf "python-android-mobile-forge-${PYTHON_SHORT_VERSION}.tar.gz" -C "$python_android_dir" + export MOBILE_FORGE_ANDROID_SUPPORT_PATH="$python_android_dir" + + . .ci/install_ndk.sh + else + python_ios_dir="$HOME/projects/python-build/darwin/Python-Apple-support" + curl -#OL "https://github.com/flet-dev/python-build/releases/download/v${PYTHON_SHORT_VERSION}/python-ios-mobile-forge-${PYTHON_SHORT_VERSION}.tar.gz" + mkdir -p "$python_ios_dir" + tar -xzf "python-ios-mobile-forge-${PYTHON_SHORT_VERSION}.tar.gz" -C "$python_ios_dir" + export MOBILE_FORGE_IOS_SUPPORT_PATH="$python_ios_dir" + fi + + source ./setup.sh "$UV_PYTHON" + + IFS=' ' read -r -a packages <<< "$FORGE_PACKAGES" + for package in "${packages[@]}"; do + forge "$FORGE_ARCH" "$package" + done + + # Drop the support-tree dep wheels produced by make_dep_wheels.py + # iOS deps: bzip2, libffi, mpdecimal, openssl, xz + # Android deps: bzip2, libffi, openssl, sqlite, xz + rm -f dist/bzip2-* dist/libffi-* dist/mpdecimal-* dist/openssl-* dist/sqlite-* dist/xz-* + + - name: Publish wheels + if: ${{ hashFiles('dist/*.whl') != '' && (inputs.publish || (github.event_name == 'push' && github.ref == 'refs/heads/python3.12')) }} + shell: bash + env: + GEMFURY_TOKEN: ${{ secrets.GEMFURY_TOKEN }} + run: | + set -euxo pipefail + . .ci/common.sh + publish_to_pypi dist/*.whl + + - name: Upload logs on success + if: ${{ success() && hashFiles('logs/*.log') != '' }} + uses: actions/upload-artifact@v4 + with: + name: logs-${{ matrix.artifact_name }}-${{ github.run_id }}-${{ github.run_attempt }} + path: logs/*.log + + - name: Upload errors on failure + if: ${{ failure() && hashFiles('errors/*.log') != '' }} + uses: actions/upload-artifact@v4 + with: + name: errors-${{ matrix.artifact_name }}-${{ github.run_id }}-${{ github.run_attempt }} + path: errors/*.log diff --git a/make_dep_wheels.py b/make_dep_wheels.py index c862b82a..411ae65b 100644 --- a/make_dep_wheels.py +++ b/make_dep_wheels.py @@ -16,6 +16,56 @@ from pathlib import Path +def get_versions_path(os_name): + support = Path( + os.environ[ + ( + "MOBILE_FORGE_ANDROID_SUPPORT_PATH" + if os_name == "android" + else "MOBILE_FORGE_IOS_SUPPORT_PATH" + ) + ] + ) + return ( + support + / "support" + / ".".join(sys.version.split(".")[:2]) + / os_name + / "VERSIONS" + ) + + +def get_dependencies(os_name): + versions_file = get_versions_path(os_name) + dependencies = [] + with versions_file.open(encoding="utf-8") as f: + for line in f: + match = re.match(r"^([^:]+):\s+(.+)$", line.strip()) + if not match: + continue + key = match[1] + if ( + key.lower() == "python version" + or key.lower() == "build" + or key.lower().startswith("min ") + ): + continue + dependencies.append(key) + return dependencies + + +def get_targets(os_name): + if os_name == "android": + if sys.version_info[:2] >= (3, 13): + return ["arm64-v8a", "x86_64"] + return ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"] + return [ + "iphoneos.arm64", + "iphonesimulator.arm64", + "iphonesimulator.x86_64", + ] + + def make_wheel(package, os_name, target): """Create a target-specific wheel for a given package. @@ -26,27 +76,24 @@ def make_wheel(package, os_name, target): :param os_name: The OS name to target (e.g., "iOS") :param target: The target specifier (e.g., "iphoneos.arm64") """ - support = Path(os.environ["PYTHON_ANDROID_SUPPORT" if os_name == "android" else "PYTHON_APPLE_SUPPORT"]) - - versions_file = ( - support - / "support" - / ".".join(sys.version.split(".")[:2]) - / os_name - / "VERSIONS" - ) + support = get_versions_path(os_name).parents[3] + versions_file = get_versions_path(os_name) with versions_file.open(encoding="utf-8") as f: versions = f.read() - package_version_build = re.search(rf"^{package}: (.*)", versions, re.MULTILINE)[1] - min_version = re.search(rf"^Min {os_name} version: (.*)", versions, re.MULTILINE)[1] + package_version_build = re.search( + rf"^{package}: (.*)", versions, re.MULTILINE | re.IGNORECASE + )[1] + min_version = re.search(rf"^Min {os_name} version: (.*)", versions, re.MULTILINE | re.IGNORECASE)[1] package_version, package_build = package_version_build.split("-") target_parts = target.split(".") target_parts.reverse() wheel_target = "_".join(target_parts) - wheel_tag = f"py3-none-{os_name}_{min_version}_{wheel_target.replace('-', '_')}".lower().replace(".", "_") + wheel_tag = f"py3-none-{os_name}_{min_version}_{wheel_target.replace('-', '_')}".lower().replace( + ".", "_" + ) wheel_file = ( Path("dist") / f"{package.lower()}-{package_version_build}-{wheel_tag}.whl" @@ -124,28 +171,7 @@ def make_wheel(package, os_name, target): if __name__ == "__main__": os_name = sys.argv[1] - for target in { - "android": [ - "arm64-v8a", - "armeabi-v7a", - "x86_64", - "x86" - ], - "iOS": [ - "iphoneos.arm64", - "iphonesimulator.arm64", - "iphonesimulator.x86_64", - ], - "tvOS": [ - "appletvos.arm64", - "appletvsimulator.arm64", - "appletvsimulator.x86_64", - ], - "watchOS": [ - "watchos.arm64_32", - "watchsimulator.arm64", - "watchsimulator.x86_64", - ], - }[os_name]: - for dep in ["BZip2", "XZ", "libFFI", "OpenSSL"]: + dependencies = get_dependencies(os_name) + for target in get_targets(os_name): + for dep in dependencies: make_wheel(dep, os_name, target) diff --git a/pyproject.toml b/pyproject.toml index 884131d5..6dd11818 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,14 +6,12 @@ build-backend = "setuptools.build_meta" name = "mobile-forge" version = "2023.0.0" description = "A tool to manage building cross-platform binary wheels for mobile devices" -readme = { file = "README.md", content-type = "text/x-rst"} +readme = { file = "README.md", content-type = "text/x-rst" } requires-python = ">=3.8" license = { file = "LICENSE" } -authors = [ - {name = "Russell Keith-Magee", email = "russell@keith-magee.com"} -] +authors = [{ name = "Russell Keith-Magee", email = "russell@keith-magee.com" }] maintainers = [ - {name = "Russell Keith-Magee", email = "russell@keith-magee.com"} + { name = "Russell Keith-Magee", email = "russell@keith-magee.com" }, ] classifiers = [ "Development Status :: 3 - Alpha", @@ -31,7 +29,9 @@ classifiers = [ dependencies = [ # Currently using a fork of crossenv to get iOS fixes. # Replace when/if these are merged and released. - "crossenv @ git+https://github.com/flet-dev/crossenv@ios-support", + "crossenv @ git+https://github.com/flet-dev/crossenv@flet", + #"crossenv @ git+https://github.com/freakboy3742/crossenv@f0f07129eb06ea16d180650a26a02df2b948b888", + #"crossenv @ git+https://github.com/benfogle/crossenv", "httpx == 0.27.0", "Jinja2 == 3.1.3", "jsonschema == 4.21.1", @@ -42,9 +42,7 @@ dependencies = [ ] [project.optional-dependencies] -dev = [ - "pre-commit==3.7.0", -] +dev = ["pre-commit==3.7.0"] [project.urls] Homepage = "https://beeware.org" @@ -58,11 +56,7 @@ forge-env = "forge.cross:main" [tool.isort] profile = "black" -skip_glob = [ - "docs/conf.py", - "venv*", - "local", -] +skip_glob = ["docs/conf.py", "venv*", "local"] multi_line_output = 3 [tool.codespell] diff --git a/recipes/aiohttp/meta.yaml b/recipes/aiohttp/meta.yaml index abef3d71..71224793 100644 --- a/recipes/aiohttp/meta.yaml +++ b/recipes/aiohttp/meta.yaml @@ -1,3 +1,6 @@ package: name: aiohttp version: 3.9.5 + +build: + number: 4 diff --git a/recipes/argon2-cffi-bindings/meta.yaml b/recipes/argon2-cffi-bindings/meta.yaml index 52bf786e..9620c2a6 100644 --- a/recipes/argon2-cffi-bindings/meta.yaml +++ b/recipes/argon2-cffi-bindings/meta.yaml @@ -2,6 +2,5 @@ package: name: argon2-cffi-bindings version: 21.2.0 -requirements: - build: - - cffi 1.16.0 +build: + number: 4 diff --git a/recipes/bcrypt/meta.yaml b/recipes/bcrypt/meta.yaml index 17ce120a..82201ecf 100644 --- a/recipes/bcrypt/meta.yaml +++ b/recipes/bcrypt/meta.yaml @@ -1,8 +1,8 @@ package: name: bcrypt - version: 4.1.3 + version: 4.2.0 -requirements: - build: - - setuptools_rust @ git+https://github.com/flet-dev/setuptools-rust@ios-support - - cffi 1.16.0 +build: + number: 4 + script_env: + _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' \ No newline at end of file diff --git a/recipes/biopython/meta.yaml b/recipes/biopython/meta.yaml new file mode 100644 index 00000000..1d64f33d --- /dev/null +++ b/recipes/biopython/meta.yaml @@ -0,0 +1,6 @@ +package: + name: biopython + version: "1.87" + +build: + number: 0 diff --git a/recipes/bitarray/LICENSE b/recipes/bitarray/LICENSE deleted file mode 100644 index a82526bf..00000000 --- a/recipes/bitarray/LICENSE +++ /dev/null @@ -1,46 +0,0 @@ -PYTHON SOFTWARE FOUNDATION LICENSE ----------------------------------- - -1. This LICENSE AGREEMENT is between Ilan Schnell, and the Individual or -Organization ("Licensee") accessing and otherwise using this software -("bitarray") in source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, Ilan Schnell -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use bitarray -alone or in any derivative version, provided, however, that Ilan Schnell's -License Agreement and Ilan Schnell's notice of copyright, i.e., "Copyright (c) -2008 - 2021 Ilan Schnell; All Rights Reserved" are retained in bitarray -alone or in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates bitarray or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to bitarray. - -4. Ilan Schnell is making bitarray available to Licensee on an "AS IS" -basis. ILAN SCHNELL MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, ILAN SCHNELL MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF BITARRAY WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. ILAN SCHNELL SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF BITARRAY -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING BITARRAY, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between Ilan Schnell -and Licensee. This License Agreement does not grant permission to use Ilan -Schnell trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using bitarray, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. diff --git a/recipes/bitarray/meta.yaml b/recipes/bitarray/meta.yaml index d1bff373..fd3f930d 100644 --- a/recipes/bitarray/meta.yaml +++ b/recipes/bitarray/meta.yaml @@ -1,3 +1,6 @@ package: name: bitarray - version: 2.9.2 + version: 3.6.1 + +build: + number: 4 diff --git a/recipes/blis/meta.yaml b/recipes/blis/meta.yaml index 76b02d3c..effaa8d8 100644 --- a/recipes/blis/meta.yaml +++ b/recipes/blis/meta.yaml @@ -1,12 +1,13 @@ package: name: blis - version: 0.9.1 + version: 1.0.0 -patches: - - mobile.patch +build: + number: 4 requirements: - build: - - numpy 1.26.4 host: - - numpy 1.26.4 \ No newline at end of file + - numpy ^2.0.0 + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/blis/patches/mobile.patch b/recipes/blis/patches/mobile.patch index 28559404..479017ad 100644 --- a/recipes/blis/patches/mobile.patch +++ b/recipes/blis/patches/mobile.patch @@ -1,7 +1,9 @@ +diff --git a/blis/_src/frame/thread/bli_pthread.c b/blis/_src/frame/thread/bli_pthread.c +index a099356..6d5fe03 100644 --- a/blis/_src/frame/thread/bli_pthread.c +++ b/blis/_src/frame/thread/bli_pthread.c @@ -594,7 +594,7 @@ int bli_pthread_barrier_wait - return 0; + return 0; } -#elif defined(__APPLE__) || defined(_MSC_VER) // !defined(BLIS_DISABLE_SYSTEM) @@ -23,10 +25,18 @@ index d5158ff..bf3fbe5 100644 // For OS X, we must define the barrier types ourselves since Apple does // not implement barriers in their variant of pthreads. diff --git a/setup.py b/setup.py -index 332cab3..bdd673e 100644 +index d0944c9..6b3c19e 100644 --- a/setup.py +++ b/setup.py -@@ -37,6 +37,10 @@ PLATFORM_TO_ARCH = { +@@ -21,6 +21,7 @@ import subprocess + import sys + import platform + import numpy ++import sysconfig + + + PLATFORM_TO_ARCH = { +@@ -36,6 +37,10 @@ PLATFORM_TO_ARCH = { MOD_NAMES = ["blis.cy", "blis.py"] @@ -34,4 +44,25 @@ index 332cab3..bdd673e 100644 +os.environ["BLIS_ARCH"] = "generic" +os.environ["BLIS_COMPILER"] = os.environ["CC"] + - print("BLIS_COMPILER?", os.environ.get("BLIS_COMPILER", "None")) \ No newline at end of file + print("BLIS_COMPILER?", os.environ.get("BLIS_COMPILER", "None")) + + +@@ -220,6 +225,9 @@ class ExtensionBuilder(build_ext, build_ext_options): + objects = [] + platform_arch = platform + "-" + py_arch + compiler = self.get_compiler_name() ++ host_triplet = sysconfig.get_platform().split("-") ++ print("Host triplet:", host_triplet) ++ + with open(os.path.join(BLIS_DIR, "make", "%s.jsonl" % platform_arch)) as file_: + env = {} + for line in file_: +@@ -255,6 +263,8 @@ class ExtensionBuilder(build_ext, build_ext_options): + spec["flags"] = [ + f for f in spec["flags"] if "visibility=hidden" not in f + ] ++ if len(host_triplet) == 4 and host_triplet[0] == "ios": ++ spec["flags"].append(f"-mios-version-min={host_triplet[1]}") + objects.append(self.build_object(env=env, **spec)) + return objects + diff --git a/recipes/brotli/meta.yaml b/recipes/brotli/meta.yaml index ad8c51ad..37863a41 100644 --- a/recipes/brotli/meta.yaml +++ b/recipes/brotli/meta.yaml @@ -1,3 +1,6 @@ package: name: Brotli version: 1.1.0 + +build: + number: 4 diff --git a/recipes/cffi/meta.yaml b/recipes/cffi/meta.yaml index c29eccfe..506cc3e6 100644 --- a/recipes/cffi/meta.yaml +++ b/recipes/cffi/meta.yaml @@ -1,10 +1,13 @@ package: name: cffi - version: 1.16.0 + version: 1.17.1 -patches: - - mobile.patch +build: + number: 4 requirements: host: - - libffi 3.4.4 + - libffi + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/contourpy/meta.yaml b/recipes/contourpy/meta.yaml index 9190a767..0f2e97bc 100644 --- a/recipes/contourpy/meta.yaml +++ b/recipes/contourpy/meta.yaml @@ -1,13 +1,20 @@ package: name: contourpy - version: 1.2.1 + version: 1.3.1 + +requirements: + build: + - ninja + - cmake + - meson + host: + - pybind11 +# {% if sdk == 'android' %} + - flet-libcpp-shared >=27.2.12479018 +# {% endif %} build: + number: 5 backend-args: - -Csetup-args=--cross-file - -Csetup-args={MESON_CROSS_FILE} - -requirements: - host: - - meson - - ninja \ No newline at end of file diff --git a/recipes/coolprop/meta.yaml b/recipes/coolprop/meta.yaml new file mode 100644 index 00000000..7f9c1918 --- /dev/null +++ b/recipes/coolprop/meta.yaml @@ -0,0 +1,41 @@ +package: + name: coolprop + version: 7.2.0 + +requirements: + build: + - cmake +# {% if sdk == 'android' %} + host: + - flet-libcpp-shared >=27.2.12479018 +# {% endif %} + +patches: + - mkdir-cython-output.patch + +build: + number: 1 + script_env: +# {% if sdk == 'android' %} + CMAKE_ARGS: >- + -DCMAKE_TOOLCHAIN_FILE={NDK_ROOT}/build/cmake/android.toolchain.cmake + -DANDROID_ABI={ANDROID_ABI} + -DANDROID_PLATFORM=android-{ANDROID_API_LEVEL} + -DANDROID_STL=c++_shared + -DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=16384 + -DCMAKE_MODULE_LINKER_FLAGS=-Wl,-z,max-page-size=16384 + -DPython_LIBRARY={prefix}/lib/libpython{py_version_short}.so + -DPython_INCLUDE_DIR={prefix}/include/python{py_version_short} + -DPython3_LIBRARY={prefix}/lib/libpython{py_version_short}.so + -DPython3_INCLUDE_DIR={prefix}/include/python{py_version_short} +# {% else %} + CMAKE_ARGS: >- + -DCMAKE_SYSTEM_NAME=iOS + -DCMAKE_OSX_SYSROOT={{ sdk }} + -DCMAKE_OSX_DEPLOYMENT_TARGET={{ sdk_version }} + -DCMAKE_OSX_ARCHITECTURES={{ arch }} + -DPython_LIBRARY={prefix}/lib/libpython{py_version_short}.dylib + -DPython_INCLUDE_DIR={prefix}/include/python{py_version_short} + -DPython3_LIBRARY={prefix}/lib/libpython{py_version_short}.dylib + -DPython3_INCLUDE_DIR={prefix}/include/python{py_version_short} +# {% endif %} diff --git a/recipes/coolprop/patches/mkdir-cython-output.patch b/recipes/coolprop/patches/mkdir-cython-output.patch new file mode 100644 index 00000000..c1968094 --- /dev/null +++ b/recipes/coolprop/patches/mkdir-cython-output.patch @@ -0,0 +1,12 @@ +--- a/wrappers/Python/CMakeLists.txt ++++ b/wrappers/Python/CMakeLists.txt +@@ -137,6 +137,9 @@ set(CYTHON_FLAGS + --directive c_string_encoding=ascii + ) + ++# Cython does not create the output's parent directory; create it ourselves. ++file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/CoolProp") ++ + # Generate CoolProp module + add_custom_command( + OUTPUT CoolProp/CoolProp.cpp diff --git a/recipes/cryptography/meta.yaml b/recipes/cryptography/meta.yaml index 86f4274e..a64edca3 100644 --- a/recipes/cryptography/meta.yaml +++ b/recipes/cryptography/meta.yaml @@ -1,16 +1,13 @@ package: name: cryptography - version: 42.0.7 - -build: - script_env: - OPENSSL_STATIC: 1 - OPENSSL_DIR: '{platlib}/opt' + version: 43.0.1 requirements: - build: - - setuptools_rust @ git+https://github.com/flet-dev/setuptools-rust@ios-support - host: - - cffi 1.16.0 - openssl ^3.0.12 + +build: + number: 4 + script_env: + OPENSSL_DIR: '{platlib}/opt' + _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' \ No newline at end of file diff --git a/recipes/fiona/meta.yaml b/recipes/fiona/meta.yaml new file mode 100644 index 00000000..962160c0 --- /dev/null +++ b/recipes/fiona/meta.yaml @@ -0,0 +1,21 @@ +package: + name: fiona + version: 1.10.1 + +requirements: + host: + - flet-libgdal 3.10.0 + +build: + number: 4 + script_env: + GDAL_VERSION: 3.10.0 + GDAL_LIB_PATH: '{platlib}/opt/lib' + GDAL_INCLUDE_PATH: '{platlib}/opt/include' + GDAL_LIBS: gdal +# {% if sdk != 'android' %} + LDFLAGS: '-undefined dynamic_lookup' +# {% endif %} + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/fiona/patches/mobile.patch b/recipes/fiona/patches/mobile.patch new file mode 100644 index 00000000..6b6d0b5b --- /dev/null +++ b/recipes/fiona/patches/mobile.patch @@ -0,0 +1,26 @@ +diff --git a/setup.py b/setup.py +index e1f48a6..6c04d39 100644 +--- a/setup.py ++++ b/setup.py +@@ -9,7 +9,6 @@ import sys + from setuptools import setup + from setuptools.extension import Extension + +- + # Ensure minimum version of Python is running + if sys.version_info[0:2] < (3, 6): + raise RuntimeError('Fiona requires Python>=3.6') +@@ -85,6 +84,13 @@ if 'clean' not in sys.argv: + else: + logging.warning("Failed to get options via gdal-config: %s", str(e)) + ++ if 'GDAL_LIB_PATH' in os.environ: ++ library_dirs.extend(os.environ['GDAL_LIB_PATH'].split(":")) ++ if 'GDAL_INCLUDE_PATH' in os.environ: ++ include_dirs.extend(os.environ['GDAL_INCLUDE_PATH'].split(":")) ++ if 'GDAL_LIBS' in os.environ: ++ libraries.extend(os.environ['GDAL_LIBS'].split(",")) ++ + # Get GDAL API version from environment variable. + if 'GDAL_VERSION' in os.environ: + gdalversion = os.environ['GDAL_VERSION'] diff --git a/recipes/fiona/test_fiona.py b/recipes/fiona/test_fiona.py new file mode 100644 index 00000000..6b81d9cd --- /dev/null +++ b/recipes/fiona/test_fiona.py @@ -0,0 +1 @@ +# TBD \ No newline at end of file diff --git a/recipes/flet-libcpp-shared/build.sh b/recipes/flet-libcpp-shared/build.sh new file mode 100755 index 00000000..3ffec39c --- /dev/null +++ b/recipes/flet-libcpp-shared/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -eu + +if [[ "$CROSS_VENV_SDK" != "android" ]]; then + echo "This package can be built for Android only." + exit 1 +fi + +toolchain=$(echo $NDK_ROOT/toolchains/llvm/prebuilt/*) +export LIBC_SHARED_SO="$toolchain/sysroot/usr/lib/${HOST_TRIPLET}/libc++_shared.so" + +mkdir -p $PREFIX/lib +cp $LIBC_SHARED_SO $PREFIX/lib diff --git a/recipes/flet-libcpp-shared/meta.yaml b/recipes/flet-libcpp-shared/meta.yaml new file mode 100644 index 00000000..96147f47 --- /dev/null +++ b/recipes/flet-libcpp-shared/meta.yaml @@ -0,0 +1,9 @@ +package: + name: flet-libcpp-shared + version: 27.3.13750724 + +build: + number: 4 + +source: + url: https://github.com/flet-dev/awesome-flet/archive/refs/heads/main.zip \ No newline at end of file diff --git a/recipes/flet-libcrc32c/build.sh b/recipes/flet-libcrc32c/build.sh new file mode 100755 index 00000000..0fdc5e15 --- /dev/null +++ b/recipes/flet-libcrc32c/build.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -eu + +if [ $CROSS_VENV_SDK == "android" ]; then + cmake \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_PLATFORM=$SDK_VERSION \ + -DANDROID_ABI=$ANDROID_ABI \ + -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ + -DCRC32C_BUILD_TESTS=0 \ + -DCRC32C_BUILD_BENCHMARKS=0 \ + -DCRC32C_USE_GLOG=0 \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=1 \ + -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" +else + cmake \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DCMAKE_OSX_SYSROOT=$SDK \ + -DCMAKE_OSX_ARCHITECTURES=$HOST_ARCH \ + -DCRC32C_BUILD_TESTS=0 \ + -DCRC32C_BUILD_BENCHMARKS=0 \ + -DCRC32C_USE_GLOG=0 \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$PREFIX +fi + +make -j $CPU_COUNT +make install + +# cleanup +rm -r $PREFIX/lib/cmake \ No newline at end of file diff --git a/recipes/flet-libcrc32c/meta.yaml b/recipes/flet-libcrc32c/meta.yaml new file mode 100644 index 00000000..40114603 --- /dev/null +++ b/recipes/flet-libcrc32c/meta.yaml @@ -0,0 +1,13 @@ +package: + name: flet-libcrc32c + version: 1.1.2 + +build: + number: 4 + +source: + url: https://github.com/google/crc32c/archive/refs/tags/1.1.2.tar.gz + +requirements: + build: + - cmake \ No newline at end of file diff --git a/recipes/flet-libcurl/build.sh b/recipes/flet-libcurl/build.sh new file mode 100755 index 00000000..36391d8d --- /dev/null +++ b/recipes/flet-libcurl/build.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eu + +OPENSSL_PREFIX="$PLATLIB/opt" +if [ ! -f "$OPENSSL_PREFIX/include/openssl/ssl.h" ] || \ + { [ ! -f "$OPENSSL_PREFIX/lib/libssl.a" ] && [ ! -f "$OPENSSL_PREFIX/lib/libssl.so" ]; } || \ + { [ ! -f "$OPENSSL_PREFIX/lib/libcrypto.a" ] && [ ! -f "$OPENSSL_PREFIX/lib/libcrypto.so" ]; }; then + OPENSSL_PREFIX="$PYTHON_PREFIX" +fi + +PKG_CONFIG=false ./configure --host=$HOST_TRIPLET --prefix=$PREFIX --with-openssl="$OPENSSL_PREFIX" +make -j $CPU_COUNT +make install + +rm -r $PREFIX/{bin,share} +rm -r $PREFIX/lib/{*.la,pkgconfig} + +if [ $CROSS_VENV_SDK == "android" ]; then + rm -r $PREFIX/lib/*.a +fi diff --git a/recipes/flet-libcurl/meta.yaml b/recipes/flet-libcurl/meta.yaml new file mode 100644 index 00000000..c4e3d547 --- /dev/null +++ b/recipes/flet-libcurl/meta.yaml @@ -0,0 +1,21 @@ +{% set version = "8.11.0" %} + +package: + name: flet-libcurl + version: '{{ version }}' + +source: + url: https://curl.se/download/curl-{{ version }}.tar.gz + +build: + number: 4 + +requirements: + host: + # {% if sdk in ['iphoneos', 'iphonesimulator'] %} + - openssl >=3.0.15 + # {% endif %} + - flet-libpsl 0.21.5 + +patches: + - config.patch diff --git a/recipes/flet-libcurl/patches/config.patch b/recipes/flet-libcurl/patches/config.patch new file mode 100644 index 00000000..7eecc34f --- /dev/null +++ b/recipes/flet-libcurl/patches/config.patch @@ -0,0 +1,13 @@ +diff --git a/config.sub b/config.sub +index dba16e8..215b8e2 100755 +--- a/config.sub ++++ b/config.sub +@@ -1792,6 +1792,8 @@ case $kernel-$os in + ;; + *-eabi* | *-gnueabi*) + ;; ++ ios-simulator) ++ ;; + -*) + # Blank kernel with real OS is always fine. + ;; diff --git a/recipes/freetype/build.sh b/recipes/flet-libfreetype/build.sh similarity index 87% rename from recipes/freetype/build.sh rename to recipes/flet-libfreetype/build.sh index 2c7ad0f9..53ccf720 100755 --- a/recipes/freetype/build.sh +++ b/recipes/flet-libfreetype/build.sh @@ -12,5 +12,8 @@ rmdir $PREFIX/include/freetype2 # has an SONAME of libfreetype.so, so there's no conflict. # rm -r $PREFIX/lib/{*.a,*.la,pkgconfig} rm -r $PREFIX/lib/{*.la,pkgconfig} - rm -r $PREFIX/share + +if [ $CROSS_VENV_SDK == "android" ]; then + rm -r $PREFIX/lib/*.a +fi \ No newline at end of file diff --git a/recipes/flet-libfreetype/meta.yaml b/recipes/flet-libfreetype/meta.yaml new file mode 100644 index 00000000..511ec378 --- /dev/null +++ b/recipes/flet-libfreetype/meta.yaml @@ -0,0 +1,15 @@ +package: + name: flet-libfreetype + version: 2.13.3 + +build: + number: 2 + +source: + url: https://download.savannah.gnu.org/releases/freetype/freetype-2.13.3.tar.gz + +patches: + - config.patch + +about: + license_file: docs/FTL.TXT diff --git a/recipes/flet-libfreetype/patches/config.patch b/recipes/flet-libfreetype/patches/config.patch new file mode 100644 index 00000000..7b355451 --- /dev/null +++ b/recipes/flet-libfreetype/patches/config.patch @@ -0,0 +1,30 @@ +diff --git a/builds/unix/config.sub b/builds/unix/config.sub +index 4aaae46..526f2d4 100755 +--- a/builds/unix/config.sub ++++ b/builds/unix/config.sub +@@ -155,6 +155,7 @@ case $1 in + | storm-chaos* \ + | uclinux-gnu* \ + | uclinux-uclibc* \ ++ | ios*-simulator \ + | windows-* ) + basic_machine=$field1 + basic_os=$maybe_os +@@ -1727,6 +1728,8 @@ case $os in + obj=$os + os= + ;; ++ ios | ios-simulator) ++ ;; + *) + # No normalization, but not necessarily accepted, that comes below. + ;; +@@ -2253,6 +2256,8 @@ case $kernel-$os-$obj in + # None (no kernel, i.e. freestanding / bare metal), + # can be paired with an machine code file format + ;; ++ ios-simulator-) ++ ;; + -*-) + # Blank kernel with real OS is always fine. + ;; diff --git a/recipes/flet-libgdal/build.sh b/recipes/flet-libgdal/build.sh new file mode 100755 index 00000000..b9008c81 --- /dev/null +++ b/recipes/flet-libgdal/build.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -eu + +mkdir build +cd build + +if [ $CROSS_VENV_SDK == "android" ]; then + cmake .. \ + -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_PLATFORM=$SDK_VERSION \ + -DANDROID_ABI=$ANDROID_ABI \ + -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \ + -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER \ + -DCMAKE_FIND_USE_CMAKE_SYSTEM_PATH=NO \ + -DPROJ_LIBRARY=$PLATLIB/opt/lib/libproj.so \ + -DPROJ_INCLUDE_DIR=$PLATLIB/opt/include \ + -DSQLite3_LIBRARY=$PYTHON_PREFIX/lib/libsqlite3_python.so \ + -DSQLite3_INCLUDE_DIR=$PYTHON_PREFIX/include \ + -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF \ + -DOGR_BUILD_OPTIONAL_DRIVERS=OFF \ + -DGDAL_USE_EXPAT=OFF \ + -DGDAL_USE_OPENSSL=OFF \ + -DGDAL_USE_CURL=OFF \ + -DGDAL_USE_LIBXML2=OFF \ + -DBUILD_APPS=OFF \ + -DBUILD_TESTING=OFF \ + -DBUILD_PYTHON_BINDINGS=OFF +else + cmake .. \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DCMAKE_OSX_SYSROOT=$SDK \ + -DCMAKE_OSX_ARCHITECTURES=$HOST_ARCH \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \ + -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER \ + -DCMAKE_FIND_USE_CMAKE_SYSTEM_PATH=NO \ + -DCMAKE_CXX_FLAGS="$CFLAGS" \ + -DGDAL_USE_EXTERNAL_LIBS=OFF \ + -DPROJ_LIBRARY=$PLATLIB/opt/lib/libproj.a \ + -DPROJ_INCLUDE_DIR=$PLATLIB/opt/include \ + -DSQLite3_LIBRARY=$SDK_ROOT/usr/lib/libsqlite3.tbd \ + -DSQLite3_INCLUDE_DIR=$SDK_ROOT/usr/include \ + -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF \ + -DOGR_BUILD_OPTIONAL_DRIVERS=OFF \ + -DBUILD_APPS=OFF \ + -DBUILD_TESTING=OFF +fi + +cmake --build . -j $CPU_COUNT +cmake --build . --target install + +rm -rf $PREFIX/{bin,share} +rm -rf $PREFIX/lib/{cmake,pkgconfig} \ No newline at end of file diff --git a/recipes/flet-libgdal/meta.yaml b/recipes/flet-libgdal/meta.yaml new file mode 100644 index 00000000..dec9cc4c --- /dev/null +++ b/recipes/flet-libgdal/meta.yaml @@ -0,0 +1,17 @@ +{% set version = "3.10.0" %} + +package: + name: flet-libgdal + version: '{{ version }}' + +source: + url: https://github.com/OSGeo/gdal/releases/download/v{{ version }}/gdal-{{ version }}.tar.gz + +build: + number: 4 + +requirements: + build: + - cmake + host: + - flet-libproj 9.5.0 \ No newline at end of file diff --git a/recipes/flet-libgeos/build.sh b/recipes/flet-libgeos/build.sh new file mode 100755 index 00000000..dfb261c5 --- /dev/null +++ b/recipes/flet-libgeos/build.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -eu + +if [ $CROSS_VENV_SDK == "android" ]; then + cmake \ + -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_PLATFORM=$SDK_VERSION \ + -DANDROID_ABI=$ANDROID_ABI \ + -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DBUILD_TESTING=0 +else + cmake \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DCMAKE_OSX_SYSROOT=$SDK \ + -DCMAKE_OSX_ARCHITECTURES=$HOST_ARCH \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_TESTING=0 +fi + +make -j $CPU_COUNT +make install + +rm -rf $PREFIX/bin +rm -rf $PREFIX/lib/{cmake,pkgconfig} \ No newline at end of file diff --git a/recipes/flet-libgeos/meta.yaml b/recipes/flet-libgeos/meta.yaml new file mode 100644 index 00000000..5e9f89b7 --- /dev/null +++ b/recipes/flet-libgeos/meta.yaml @@ -0,0 +1,13 @@ +package: + name: flet-libgeos + version: 3.13.0 + +build: + number: 4 + +source: + url: http://download.osgeo.org/geos/geos-3.13.0.tar.bz2 + +requirements: + build: + - cmake \ No newline at end of file diff --git a/recipes/libjpeg/build.sh b/recipes/flet-libjpeg/build.sh similarity index 72% rename from recipes/libjpeg/build.sh rename to recipes/flet-libjpeg/build.sh index 85e14b8e..a3cee1e0 100755 --- a/recipes/libjpeg/build.sh +++ b/recipes/flet-libjpeg/build.sh @@ -5,13 +5,15 @@ set -eu if [ $CROSS_VENV_SDK == "android" ]; then cmake -G"Unix Makefiles" \ -DCMAKE_SYSTEM_NAME=Android \ - -DANDROID_PLATFORM=24 \ + -DANDROID_PLATFORM=$SDK_VERSION \ -DANDROID_ABI=$ANDROID_ABI \ -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ - -DWITH_SIMD=OFF \ + -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ -DCMAKE_INSTALL_PREFIX=$PREFIX . else cmake -G"Unix Makefiles" \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DCMAKE_SYSTEM_PROCESSOR=$HOST_ARCH \ -DCMAKE_INSTALL_PREFIX=$PREFIX . fi @@ -20,3 +22,4 @@ make install rm -r $PREFIX/{bin,share} rm -r $PREFIX/lib/{pkgconfig,cmake} +find "$PREFIX/lib/" -name "*.dylib" -exec rm -rf {} \; \ No newline at end of file diff --git a/recipes/flet-libjpeg/meta.yaml b/recipes/flet-libjpeg/meta.yaml new file mode 100644 index 00000000..38a6fbd7 --- /dev/null +++ b/recipes/flet-libjpeg/meta.yaml @@ -0,0 +1,13 @@ +package: + name: flet-libjpeg + version: 3.0.90 + +source: + url: https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/3.0.90/libjpeg-turbo-3.0.90.tar.gz + +build: + number: 4 + +requirements: + build: + - cmake \ No newline at end of file diff --git a/recipes/flet-libjq/build.sh b/recipes/flet-libjq/build.sh new file mode 100755 index 00000000..a546dbc4 --- /dev/null +++ b/recipes/flet-libjq/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -eu + +./configure --host=$HOST_TRIPLET --prefix=$PREFIX --with-oniguruma=builtin +make -j $CPU_COUNT +make install + +rm -r $PREFIX/{bin,share} +rm -r $PREFIX/lib/{*.la,pkgconfig} + +if [ $CROSS_VENV_SDK == "android" ]; then + rm -r $PREFIX/lib/*.a +fi \ No newline at end of file diff --git a/recipes/flet-libjq/meta.yaml b/recipes/flet-libjq/meta.yaml new file mode 100644 index 00000000..400c32b4 --- /dev/null +++ b/recipes/flet-libjq/meta.yaml @@ -0,0 +1,18 @@ +{% set version = "1.7.1" %} + +package: + name: flet-libjq + version: '{{ version }}' + +source: + url: https://github.com/jqlang/jq/releases/download/jq-{{ version }}/jq-{{ version }}.tar.gz + +build: + number: 4 + +requirements: + build: + - cmake + +patches: + - config.patch \ No newline at end of file diff --git a/recipes/flet-libjq/patches/config.patch b/recipes/flet-libjq/patches/config.patch new file mode 100644 index 00000000..d4a35e70 --- /dev/null +++ b/recipes/flet-libjq/patches/config.patch @@ -0,0 +1,26 @@ +diff --git a/config/config.sub b/config/config.sub +index dba16e8..215b8e2 100755 +--- a/config/config.sub ++++ b/config/config.sub +@@ -1792,6 +1792,8 @@ case $kernel-$os in + ;; + *-eabi* | *-gnueabi*) + ;; ++ ios-simulator) ++ ;; + -*) + # Blank kernel with real OS is always fine. + ;; +diff --git a/modules/oniguruma/config.sub b/modules/oniguruma/config.sub +index 0753e30..fe182ac 100755 +--- a/modules/oniguruma/config.sub ++++ b/modules/oniguruma/config.sub +@@ -1753,6 +1753,8 @@ case $kernel-$os in + ;; + *-eabi* | *-gnueabi*) + ;; ++ ios-simulator) ++ ;; + -*) + # Blank kernel with real OS is always fine. + ;; diff --git a/recipes/flet-libopaque/build.sh b/recipes/flet-libopaque/build.sh new file mode 100755 index 00000000..9df7f940 --- /dev/null +++ b/recipes/flet-libopaque/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -eu + +cd src +make -j $CPU_COUNT +make install + +rm -r $PREFIX/bin +rm -r $PREFIX/lib/*.a + +if [ $CROSS_VENV_SDK != "android" ]; then + mv $PREFIX/lib/libopaque.so $PREFIX/../libopaque.so +fi \ No newline at end of file diff --git a/recipes/flet-libopaque/meta.yaml b/recipes/flet-libopaque/meta.yaml new file mode 100644 index 00000000..b5804dff --- /dev/null +++ b/recipes/flet-libopaque/meta.yaml @@ -0,0 +1,19 @@ +{% set version = "0.99.8" %} + +package: + name: flet-libopaque + version: '{{ version }}' + +build: + number: 4 + +source: + url: https://github.com/stef/libopaque/archive/refs/tags/v{{ version }}.tar.gz + +requirements: + host: + - flet-libsodium 1.0.20 + - flet-liboprf 0.5.0 + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/flet-libopaque/patches/mobile.patch b/recipes/flet-libopaque/patches/mobile.patch new file mode 100644 index 00000000..309ba605 --- /dev/null +++ b/recipes/flet-libopaque/patches/mobile.patch @@ -0,0 +1,81 @@ +diff --git a/src/makefile b/src/makefile +index 42e70bb..d2759e5 100644 +--- a/src/makefile ++++ b/src/makefile +@@ -6,34 +6,35 @@ CFLAGS?=-march=native -Wall -O2 -g -fstack-protector-strong -D_FORTIFY_SOURCE=2 + -Warray-bounds -fsanitize=bounds -fsanitize-undefined-trap-on-error -ftrapv $(DEFINES) + #-fstrict-flex-arrays + CFLAGS+= -std=c99 -fpic +-LDFLAGS=-g $(LIBS) ++LDFLAGS+=-g $(LIBS) + CC?=gcc + AEXT=a + SOVER=0 ++SOEXT=so + + AR?=ar + +-UNAME := $(shell uname -s) +-ARCH := $(shell uname -m) +-ifeq ($(UNAME),Darwin) +- SOEXT=dylib +- SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/libopaque.$(SOEXT) +-else +- CFLAGS+=-Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now \ +- -fsanitize=signed-integer-overflow -fsanitize-undefined-trap-on-error +- # -mbranch-protection=standard -fstrict-flex-arrays=3 +- SOEXT=so +- SOFLAGS=-Wl,-soname,libopaque.$(SOEXT).$(SOVER) +- ifeq ($(ARCH),x86_64) +- CFLAGS+=-fcf-protection=full +- endif +- +- ifeq ($(ARCH),parisc64) +- else ifeq ($(ARCH),parisc64) +- else +- CFLAGS+=-fstack-clash-protection +- endif +-endif ++# UNAME := $(shell uname -s) ++# ARCH := $(shell uname -m) ++# ifeq ($(UNAME),Darwin) ++# SOEXT=dylib ++# SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/libopaque.$(SOEXT) ++# else ++# CFLAGS+=-Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now \ ++# -fsanitize=signed-integer-overflow -fsanitize-undefined-trap-on-error ++# # -mbranch-protection=standard -fstrict-flex-arrays=3 ++# SOEXT=so ++# SOFLAGS=-Wl,-soname,libopaque.$(SOEXT).$(SOVER) ++# ifeq ($(ARCH),x86_64) ++# CFLAGS+=-fcf-protection=full ++# endif ++ ++# ifeq ($(ARCH),parisc64) ++# else ifeq ($(ARCH),parisc64) ++# else ++# CFLAGS+=-fstack-clash-protection ++# endif ++# endif + + SODIUM_NEWER_THAN_1_0_18 := $(shell pkgconf --atleast-version=1.0.19 libsodium; echo $$?) + ifeq ($(SODIUM_NEWER_THAN_1_0_18),1) +@@ -58,7 +59,7 @@ ifneq (, $(shell which pandoc)) + endif + + +-all: libopaque.$(SOEXT) libopaque.$(AEXT) tests utils/opaque $(MANPAGES) ++all: libopaque.$(SOEXT) libopaque.$(AEXT) + + debug: DEFINES=-DTRACE -DNORANDOM + debug: all +@@ -154,8 +155,7 @@ man-uninstall: + + $(DESTDIR)$(PREFIX)/lib/libopaque.$(SOEXT): libopaque.$(SOEXT) + mkdir -p $(DESTDIR)$(PREFIX)/lib +- cp $< $@.$(SOVER) +- ln -fs $@.$(SOVER) $@ ++ cp $< $@ + + $(DESTDIR)$(PREFIX)/lib/libopaque.$(AEXT): libopaque.$(AEXT) + mkdir -p $(DESTDIR)$(PREFIX)/lib diff --git a/recipes/flet-liboprf/build.sh b/recipes/flet-liboprf/build.sh new file mode 100755 index 00000000..5674236b --- /dev/null +++ b/recipes/flet-liboprf/build.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -eu + +cd src +make -j $CPU_COUNT +make install + +if [ $CROSS_VENV_SDK == "android" ]; then + rm -r $PREFIX/lib/*.a +else + rm -r $PREFIX/lib/*.so +fi \ No newline at end of file diff --git a/recipes/flet-liboprf/meta.yaml b/recipes/flet-liboprf/meta.yaml new file mode 100644 index 00000000..76c0486d --- /dev/null +++ b/recipes/flet-liboprf/meta.yaml @@ -0,0 +1,20 @@ +{% set version = "0.5.0" %} + +package: + name: flet-liboprf + version: '{{ version }}' + +source: + url: https://github.com/stef/liboprf/archive/refs/tags/v{{ version }}.tar.gz + +requirements: + host: + - flet-libsodium 1.0.20 + +build: + number: 4 + script_env: + CFLAGS: '-Qunused-arguments -Wno-unreachable-code' + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/flet-liboprf/patches/mobile.patch b/recipes/flet-liboprf/patches/mobile.patch new file mode 100644 index 00000000..c11515ea --- /dev/null +++ b/recipes/flet-liboprf/patches/mobile.patch @@ -0,0 +1,167 @@ +diff --git a/src/makefile b/src/makefile +index f7819af..b76083f 100644 +--- a/src/makefile ++++ b/src/makefile +@@ -7,33 +7,33 @@ CFLAGS?=-march=native -Wall -O2 -g \ + -fstack-protector-strong -fasynchronous-unwind-tables -fpic \ + -ftrapv -D_GLIBCXX_ASSERTIONS $(DEFINES) + +-LDFLAGS?=-lsodium -loprf-noiseXK -Lnoise_xk ++LDFLAGS_PRIVATE=$(LDFLAGS) -lsodium -loprf-noiseXK -Lnoise_xk + CC?=gcc + SOEXT?=so + STATICEXT?=a + SOVER=0 + +-UNAME := $(shell uname -s) +-ARCH := $(shell uname -m) +-ifeq ($(UNAME),Darwin) +- SOEXT=dylib +- SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/liboprf.$(SOEXT) +-else +- CFLAGS+=-Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now -Wtrampolines \ +- -fsanitize=signed-integer-overflow -fsanitize-undefined-trap-on-error +- #-fstrict-flex-arrays=3 -mbranch-protection=standard +- SOEXT=so +- SOFLAGS=-Wl,-soname,liboprf.$(SOEXT).$(SOVER) +- ifeq ($(ARCH),x86_64) +- CFLAGS+=-fcf-protection=full +- endif +- +- ifeq ($(ARCH),parisc64) +- else ifeq ($(ARCH),parisc64) +- else +- CFLAGS+=-fstack-clash-protection +- endif +-endif ++# UNAME := $(shell uname -s) ++# ARCH := $(shell uname -m) ++# ifeq ($(UNAME),Darwin) ++# SOEXT=dylib ++# SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/liboprf.$(SOEXT) ++# else ++# CFLAGS+=-Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now -Wtrampolines \ ++# -fsanitize=signed-integer-overflow -fsanitize-undefined-trap-on-error ++# #-fstrict-flex-arrays=3 -mbranch-protection=standard ++# SOEXT=so ++# SOFLAGS=-Wl,-soname,liboprf.$(SOEXT).$(SOVER) ++# ifeq ($(ARCH),x86_64) ++# CFLAGS+=-fcf-protection=full ++# endif ++ ++# ifeq ($(ARCH),parisc64) ++# else ifeq ($(ARCH),parisc64) ++# else ++# CFLAGS+=-fstack-clash-protection ++# endif ++# endif + + CFLAGS+=$(INCLUDES) + +@@ -55,16 +55,16 @@ asan: + else + CFLAGS+=-fstack-clash-protection + endif +-asan: LDFLAGS+= -fsanitize=address -static-libasan ++asan: LDFLAGS_PRIVATE+= -fsanitize=address -static-libasan + asan: all + + AR ?= ar + + liboprf.$(SOEXT): $(SOURCES) noise_xk/liboprf-noiseXK.$(SOEXT) +- $(CC) $(CFLAGS) -fPIC -shared $(SOFLAGS) -o $@ $^ $(LDFLAGS) ++ $(CC) $(CFLAGS) -fPIC -shared $(SOFLAGS) -o $@ $(SOURCES) $(LDFLAGS_PRIVATE) + + liboprf-corrupt-dkg.$(SOEXT): $(SOURCES) noise_xk/liboprf-noiseXK.$(SOEXT) +- $(CC) $(CFLAGS) -DUNITTEST -DUNITTEST_CORRUPT -fPIC -shared $(SOFLAGS) -o $@ $^ $(LDFLAGS) ++ $(CC) $(CFLAGS) -DUNITTEST -DUNITTEST_CORRUPT -fPIC -shared $(SOFLAGS) -o $@ $(SOURCES) $(LDFLAGS_PRIVATE) + + liboprf.$(STATICEXT): $(OBJECTS) + $(AR) rcs $@ $^ +@@ -98,8 +98,7 @@ uninstall-noiseXK: + + $(DESTDIR)$(PREFIX)/lib/liboprf.$(SOEXT): liboprf.$(SOEXT) + mkdir -p $(DESTDIR)$(PREFIX)/lib +- cp $< $@.$(SOVER) +- ln -sf $@.$(SOVER) $@ ++ cp $< $@ + + $(DESTDIR)$(PREFIX)/lib/liboprf.$(STATICEXT): liboprf.$(STATICEXT) + mkdir -p $(DESTDIR)$(PREFIX)/lib +diff --git a/src/noise_xk/makefile b/src/noise_xk/makefile +index 8d69ae9..b8fc7b9 100644 +--- a/src/noise_xk/makefile ++++ b/src/noise_xk/makefile +@@ -1,5 +1,5 @@ + PREFIX?=/usr/local +-LDFLAGS=-lsodium ++LDFLAGS_PRIVATE=$(LDFLAGS) -lsodium + SOURCES=src/Noise_XK.c src/XK.c + + CFLAGS += -Iinclude -I include/karmel -I include/karmel/minimal \ +@@ -17,26 +17,26 @@ SOEXT?=so + STATICEXT?=a + SOVER=0 + +-UNAME := $(shell uname -s) +-ARCH := $(shell uname -m) +-ifeq ($(UNAME),Darwin) +- SOEXT=dylib +- SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/liboprf-noiseXK.$(SOEXT) +-else +- ifeq ($(shell uname),Linux) +- CFLAGS += -Wl,--error-unresolved-symbols -Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack +- SOEXT=so +- SOFLAGS=-Wl,-soname,liboprf-noiseXK.$(SOEXT).$(SOVER) +- endif +- ifeq ($(ARCH),x86_64) +- CFLAGS+=-fcf-protection=full +- endif +- ifeq ($(ARCH),parisc64) +- else ifeq ($(ARCH),parisc64) +- else +- CFLAGS+=-fstack-clash-protection +- endif +-endif ++# UNAME := $(shell uname -s) ++# ARCH := $(shell uname -m) ++# ifeq ($(UNAME),Darwin) ++# SOEXT=dylib ++# SOFLAGS=-Wl,-install_name,$(DESTDIR)$(PREFIX)/lib/liboprf-noiseXK.$(SOEXT) ++# else ++# ifeq ($(shell uname),Linux) ++# CFLAGS += -Wl,--error-unresolved-symbols -Wl,-z,defs -Wl,-z,relro -Wl,-z,noexecstack ++# SOEXT=so ++# SOFLAGS=-Wl,-soname,liboprf-noiseXK.$(SOEXT).$(SOVER) ++# endif ++# ifeq ($(ARCH),x86_64) ++# CFLAGS+=-fcf-protection=full ++# endif ++# ifeq ($(ARCH),parisc64) ++# else ifeq ($(ARCH),parisc64) ++# else ++# CFLAGS+=-fstack-clash-protection ++# endif ++# endif + + OBJS += $(patsubst %.c,%.o,$(SOURCES)) + +@@ -48,7 +48,7 @@ AR ?= ar + $(AR) rcs $@ $^ + + %.$(SOEXT): $(OBJS) +- $(CC) $(CFLAGS) -fPIC -shared $(SOFLAGS) -o $@ $^ $(LDFLAGS) ++ $(CC) $(CFLAGS) -fPIC -shared $(SOFLAGS) -o $@ $^ $(LDFLAGS_PRIVATE) + + clean: + rm -rf *.so *.a src/*.o +@@ -64,8 +64,7 @@ uninstall: $(DESTDIR)$(PREFIX)/lib/liboprf-noiseXK.$(SOEXT) $(DESTDIR)$(PREFIX)/ + + $(DESTDIR)$(PREFIX)/lib/liboprf-noiseXK.$(SOEXT): liboprf-noiseXK.$(SOEXT) + mkdir -p $(DESTDIR)$(PREFIX)/lib/ +- cp $< $@.$(SOVER) +- ln -sf $@.$(SOVER) $@ ++ cp $< $@ + + $(DESTDIR)$(PREFIX)/lib/liboprf-noiseXK.$(STATICEXT): liboprf-noiseXK.$(STATICEXT) + mkdir -p $(DESTDIR)$(PREFIX)/lib/ diff --git a/recipes/libpng/build.sh b/recipes/flet-libpng/build.sh similarity index 100% rename from recipes/libpng/build.sh rename to recipes/flet-libpng/build.sh diff --git a/recipes/libpng/meta.yaml b/recipes/flet-libpng/meta.yaml similarity index 81% rename from recipes/libpng/meta.yaml rename to recipes/flet-libpng/meta.yaml index a3d00a75..9614942a 100644 --- a/recipes/libpng/meta.yaml +++ b/recipes/flet-libpng/meta.yaml @@ -1,9 +1,9 @@ package: - name: libpng + name: flet-libpng version: 1.6.43 build: - number: 1 + number: 4 source: url: https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.43.tar.gz diff --git a/recipes/libpng/patches/config.patch b/recipes/flet-libpng/patches/config.patch similarity index 100% rename from recipes/libpng/patches/config.patch rename to recipes/flet-libpng/patches/config.patch diff --git a/recipes/flet-libproj/build.sh b/recipes/flet-libproj/build.sh new file mode 100755 index 00000000..64e77fb8 --- /dev/null +++ b/recipes/flet-libproj/build.sh @@ -0,0 +1,41 @@ +#!/bin/bash +set -eu + +if [ $CROSS_VENV_SDK == "android" ]; then + cmake \ + -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_PLATFORM=$SDK_VERSION \ + -DANDROID_ABI=$ANDROID_ABI \ + -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DBUILD_TESTING=0 \ + -DTIFF_LIBRARY="$PLATLIB/opt/lib/libtiff.so" \ + -DTIFF_INCLUDE_DIR="$PLATLIB/opt/include" \ + -DCURL_LIBRARY="$PLATLIB/opt/lib/libcurl.so" \ + -DCURL_INCLUDE_DIR="$PLATLIB/opt/include" \ + -DSQLite3_LIBRARY=$PYTHON_PREFIX/lib/libsqlite3_python.so \ + -DSQLite3_INCLUDE_DIR=$PYTHON_PREFIX/include +else + cmake \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DCMAKE_OSX_SYSROOT=$SDK \ + -DCMAKE_OSX_ARCHITECTURES=$HOST_ARCH \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_TESTING=0 \ + -DTIFF_LIBRARY="$PLATLIB/opt/lib/libtiff.a" \ + -DTIFF_INCLUDE_DIR="$PLATLIB/opt/include" \ + -DCURL_LIBRARY="$PLATLIB/opt/lib/libcurl.a" \ + -DCURL_INCLUDE_DIR="$PLATLIB/opt/include" \ + -DSQLite3_LIBRARY=$SDK_ROOT/usr/lib/libsqlite3.tbd \ + -DSQLite3_INCLUDE_DIR=$SDK_ROOT/usr/include +fi + +cmake --build . -j $CPU_COUNT +cmake --build . --target install + +rm -rf $PREFIX/{bin,share} +rm -rf $PREFIX/lib/{cmake,pkgconfig} \ No newline at end of file diff --git a/recipes/flet-libproj/meta.yaml b/recipes/flet-libproj/meta.yaml new file mode 100644 index 00000000..7b040427 --- /dev/null +++ b/recipes/flet-libproj/meta.yaml @@ -0,0 +1,22 @@ +{% set version = "9.5.0" %} + +package: + name: flet-libproj + version: '{{ version }}' + +source: + url: https://download.osgeo.org/proj/proj-{{ version }}.tar.gz + +build: + number: 4 +# {% if sdk != 'android' %} + script_env: + LDFLAGS: '-undefined dynamic_lookup' +# {% endif %} + +requirements: + build: + - cmake + host: + - flet-libtiff 4.7.0 + - flet-libcurl 8.11.0 \ No newline at end of file diff --git a/recipes/flet-libpsl/build.sh b/recipes/flet-libpsl/build.sh new file mode 100755 index 00000000..93734586 --- /dev/null +++ b/recipes/flet-libpsl/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -eu + +./configure --host=$HOST_TRIPLET --build=$BUILD_TRIPLET --prefix=$PREFIX --disable-runtime +make -j $CPU_COUNT +make install + +rm -r $PREFIX/{bin,share} +rm -r $PREFIX/lib/{*.la,pkgconfig} + +if [ $CROSS_VENV_SDK == "android" ]; then + rm -r $PREFIX/lib/*.a +fi \ No newline at end of file diff --git a/recipes/flet-libpsl/meta.yaml b/recipes/flet-libpsl/meta.yaml new file mode 100644 index 00000000..92047cea --- /dev/null +++ b/recipes/flet-libpsl/meta.yaml @@ -0,0 +1,14 @@ +{% set version = "0.21.5" %} + +package: + name: flet-libpsl + version: '{{ version }}' + +source: + url: https://github.com/rockdaboot/libpsl/releases/download/{{ version }}/libpsl-{{ version }}.tar.gz + +build: + number: 4 + +patches: + - config.patch \ No newline at end of file diff --git a/recipes/flet-libpsl/patches/config.patch b/recipes/flet-libpsl/patches/config.patch new file mode 100644 index 00000000..3000a67f --- /dev/null +++ b/recipes/flet-libpsl/patches/config.patch @@ -0,0 +1,13 @@ +diff --git a/build-aux/config.sub b/build-aux/config.sub +index dba16e8..215b8e2 100755 +--- a/build-aux/config.sub ++++ b/build-aux/config.sub +@@ -1792,6 +1792,8 @@ case $kernel-$os in + ;; + *-eabi* | *-gnueabi*) + ;; ++ ios-simulator) ++ ;; + -*) + # Blank kernel with real OS is always fine. + ;; diff --git a/recipes/flet-libpyjni/build.sh b/recipes/flet-libpyjni/build.sh new file mode 100755 index 00000000..57d0b107 --- /dev/null +++ b/recipes/flet-libpyjni/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -eu + +mkdir build +cd build + +if [ $CROSS_VENV_SDK == "android" ]; then + cmake .. \ + -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_PLATFORM=$SDK_VERSION \ + -DANDROID_ABI=$ANDROID_ABI \ + -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=1 \ + -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" +else + echo "flet-libpyjni library can be built for Android only." + exit 1 +fi + +make -j $CPU_COUNT +make install \ No newline at end of file diff --git a/recipes/flet-libpyjni/meta.yaml b/recipes/flet-libpyjni/meta.yaml new file mode 100644 index 00000000..52b15198 --- /dev/null +++ b/recipes/flet-libpyjni/meta.yaml @@ -0,0 +1,13 @@ +package: + name: flet-libpyjni + version: 1.0.1 + +build: + number: 4 + +source: + url: https://github.com/flet-dev/libpyjni/releases/download/v1.0.1/pyjni-1.0.1.tar.gz + +requirements: + build: + - cmake \ No newline at end of file diff --git a/recipes/flet-libsodium/build.sh b/recipes/flet-libsodium/build.sh new file mode 100755 index 00000000..d59a4e14 --- /dev/null +++ b/recipes/flet-libsodium/build.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -eu + +if [ $CROSS_VENV_SDK != "android" ]; then + case $HOST_TRIPLET in + arm64-apple-ios) + HOST_TRIPLET=arm-apple-darwin23 + ;; + arm64-apple-ios-simulator) + HOST_TRIPLET=aarch64-apple-darwin23 + ;; + x86_64-apple-ios-simulator) + HOST_TRIPLET=x86_64-apple-darwin23 + ;; + *) + echo "Unknown host triplet: '$HOST_TRIPLET'" + exit 1 + ;; + esac +fi + +./configure --host=$HOST_TRIPLET --prefix=$PREFIX --disable-soname-versions +make -j $CPU_COUNT +make install + +rm -r $PREFIX/lib/{*.la,pkgconfig} + +if [ $CROSS_VENV_SDK == "android" ]; then + rm -r $PREFIX/lib/*.a +else + mv $PREFIX/lib/libsodium.dylib $PREFIX/../libsodium.so +fi \ No newline at end of file diff --git a/recipes/flet-libsodium/meta.yaml b/recipes/flet-libsodium/meta.yaml new file mode 100644 index 00000000..568622d1 --- /dev/null +++ b/recipes/flet-libsodium/meta.yaml @@ -0,0 +1,11 @@ +{% set version = "1.0.20" %} + +package: + name: flet-libsodium + version: '{{ version }}' + +build: + number: 4 + +source: + url: https://github.com/jedisct1/libsodium/releases/download/{{ version }}-RELEASE/libsodium-{{ version }}.tar.gz \ No newline at end of file diff --git a/recipes/flet-libtiff/build.sh b/recipes/flet-libtiff/build.sh new file mode 100755 index 00000000..948c279f --- /dev/null +++ b/recipes/flet-libtiff/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -eu + +./configure --host=$HOST_TRIPLET --prefix=$PREFIX --disable-docs +make -j $CPU_COUNT +make install + +rm -r $PREFIX/bin +rm -rf $PREFIX/lib/{*.la,*xx.*,pkgconfig} + +if [ $CROSS_VENV_SDK == "android" ]; then + rm -r $PREFIX/lib/*.a +fi \ No newline at end of file diff --git a/recipes/flet-libtiff/meta.yaml b/recipes/flet-libtiff/meta.yaml new file mode 100644 index 00000000..ea05b71b --- /dev/null +++ b/recipes/flet-libtiff/meta.yaml @@ -0,0 +1,18 @@ +{% set version = "4.7.0" %} + +package: + name: flet-libtiff + version: '{{ version }}' + +source: + url: https://download.osgeo.org/libtiff/tiff-{{ version }}.tar.gz + +build: + number: 4 + +requirements: + host: + - flet-libjpeg 3.0.90 + +patches: + - config.patch \ No newline at end of file diff --git a/recipes/flet-libtiff/patches/config.patch b/recipes/flet-libtiff/patches/config.patch new file mode 100644 index 00000000..a853b1e5 --- /dev/null +++ b/recipes/flet-libtiff/patches/config.patch @@ -0,0 +1,13 @@ +diff --git a/config/config.sub b/config/config.sub +index 4aaae46..1692095 100755 +--- a/config/config.sub ++++ b/config/config.sub +@@ -2259,6 +2259,8 @@ case $kernel-$os-$obj in + --*) + # Blank kernel and OS with real machine code file format is always fine. + ;; ++ ios-simulator*-) ++ ;; + *-*-*) + echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2 + exit 1 diff --git a/recipes/flet-libxml2/build.sh b/recipes/flet-libxml2/build.sh new file mode 100755 index 00000000..9d8bc951 --- /dev/null +++ b/recipes/flet-libxml2/build.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eu + +# Android NDK bionic does not expose iconv until API 28; we target 24. +if [ "$CROSS_VENV_SDK" = "android" ]; then + iconv_arg=--without-iconv +else + iconv_arg=--with-iconv +fi + +./configure --host=$HOST_TRIPLET --prefix=$PREFIX --without-python $iconv_arg +make -j $CPU_COUNT +make install + +mv $PREFIX/include/libxml2/libxml $PREFIX/include +rm -r $PREFIX/include/libxml2 + +shopt -s nullglob +rm -rf $PREFIX/share +rm -rf $PREFIX/lib/cmake $PREFIX/lib/pkgconfig $PREFIX/lib/*.la $PREFIX/lib/*.sh \ No newline at end of file diff --git a/recipes/flet-libxml2/meta.yaml b/recipes/flet-libxml2/meta.yaml new file mode 100755 index 00000000..607b99ae --- /dev/null +++ b/recipes/flet-libxml2/meta.yaml @@ -0,0 +1,19 @@ +# {% set version = "2.15.3" %} +# {% if version.startswith('2.9.') %} +# {% set patch = "mobile-2.9.x.patch" %} +# {% else %} +# {% set patch = "mobile-2.15.x.patch" %} +# {% endif %} + +package: + name: flet-libxml2 + version: '{{ version }}' + +build: + number: 1 + +source: + url: https://download.gnome.org/sources/libxml2/{{ version.rsplit('.', 1)[0] }}/libxml2-{{ version }}.tar.xz + +patches: + - {{ patch }} diff --git a/recipes/flet-libxml2/patches/mobile-2.15.x.patch b/recipes/flet-libxml2/patches/mobile-2.15.x.patch new file mode 100644 index 00000000..1a267163 --- /dev/null +++ b/recipes/flet-libxml2/patches/mobile-2.15.x.patch @@ -0,0 +1,14 @@ +diff --git a/config.sub b/config.sub +--- a/config.sub ++++ b/config.sub +@@ -1323,6 +1323,10 @@ + nto-qnx*) + kernel=nto + os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'` ++ ;; ++ ios-simulator*) ++ kernel= ++ os=$basic_os + ;; + *-*) + # shellcheck disable=SC2162 diff --git a/recipes/flet-libxml2/patches/mobile-2.9.x.patch b/recipes/flet-libxml2/patches/mobile-2.9.x.patch new file mode 100644 index 00000000..b4dd4a1b --- /dev/null +++ b/recipes/flet-libxml2/patches/mobile-2.9.x.patch @@ -0,0 +1,55 @@ +diff --git a/config.sub b/config.sub +index 7b334f9..cdb35e9 100755 +--- a/config.sub ++++ b/config.sub +@@ -355,6 +355,10 @@ case $basic_machine in + xscaleel) + basic_machine=armel-unknown + ;; ++ arm64-apple | *-ios) ++ basic_machine=$basic_machine-unknown ++ os=-none ++ ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and +diff --git a/libxml2.syms b/libxml2.syms +index 370dcf1..3c4a3dc 100644 +--- a/libxml2.syms ++++ b/libxml2.syms +@@ -453,16 +453,16 @@ LIBXML2_2.4.30 { + xmlNanoFTPUpdateURL; + + # DOCBparser +- docbCreateFileParserCtxt; +- docbCreatePushParserCtxt; +- docbEncodeEntities; +- docbFreeParserCtxt; +- docbParseChunk; +- docbParseDoc; +- docbParseDocument; +- docbParseFile; +- docbSAXParseDoc; +- docbSAXParseFile; ++# docbCreateFileParserCtxt; ++# docbCreatePushParserCtxt; ++# docbEncodeEntities; ++# docbFreeParserCtxt; ++# docbParseChunk; ++# docbParseDoc; ++# docbParseDocument; ++# docbParseFile; ++# docbSAXParseDoc; ++# docbSAXParseFile; + + # xpath + xmlXPathCastBooleanToNumber; +@@ -2187,7 +2187,7 @@ LIBXML2_2.6.29 { + global: + + # threads +- xmlDllMain; ++ # xmlDllMain; + } LIBXML2_2.6.28; + + LIBXML2_2.6.32 { diff --git a/recipes/flet-libxslt/build.sh b/recipes/flet-libxslt/build.sh new file mode 100755 index 00000000..f865c203 --- /dev/null +++ b/recipes/flet-libxslt/build.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -eu + +export CFLAGS="-Wno-error=incompatible-function-pointer-types" +export LIBS="-lxml2" + +./configure --host=$HOST_TRIPLET --prefix=$PREFIX --without-crypto --without-python \ + --with-libxml-include-prefix=$PLATLIB/opt/include \ + --with-libxml-libs-prefix=$PLATLIB/opt/lib +# Skip the xsltproc CLI / doc / tests subdirs: xsltproc links against +# libxml2 and on iOS the SDK's bundled libxml2.tbd predates 1.1.45's +# usage of xmlCtxtParseDocument / xmlXPathValuePush, so the binary +# fails to link. The wheel only needs the libraries. +make -j $CPU_COUNT V=1 SUBDIRS='libxslt libexslt' +make install SUBDIRS='libxslt libexslt' + +shopt -s nullglob +rm -rf $PREFIX/share +rm -rf $PREFIX/lib/libxslt-* $PREFIX/lib/pkgconfig $PREFIX/lib/cmake $PREFIX/lib/*.la $PREFIX/lib/*.sh \ No newline at end of file diff --git a/recipes/flet-libxslt/meta.yaml b/recipes/flet-libxslt/meta.yaml new file mode 100755 index 00000000..6704b837 --- /dev/null +++ b/recipes/flet-libxslt/meta.yaml @@ -0,0 +1,25 @@ +# {% set version = "1.1.45" %} +# {% if version == "1.1.32" %} +# {% set libxml2_version = "2.9.8" %} +# {% set patch = "mobile-1.1.32.patch" %} +# {% else %} +# {% set libxml2_version = "2.15.3" %} +# {% set patch = "mobile-1.1.45.patch" %} +# {% endif %} + +package: + name: flet-libxslt + version: '{{ version }}' + +build: + number: 1 + +source: + url: https://download.gnome.org/sources/libxslt/{{ version.rsplit('.', 1)[0] }}/libxslt-{{ version }}.tar.xz + +requirements: + host: + - flet-libxml2 {{ libxml2_version }} + +patches: + - {{ patch }} diff --git a/recipes/flet-libxslt/patches/mobile-1.1.32.patch b/recipes/flet-libxslt/patches/mobile-1.1.32.patch new file mode 100644 index 00000000..cdb9fe6b --- /dev/null +++ b/recipes/flet-libxslt/patches/mobile-1.1.32.patch @@ -0,0 +1,23 @@ +diff --git a/config.sub b/config.sub +index 7b334f9..a32642c 100755 +--- a/config.sub ++++ b/config.sub +@@ -316,7 +316,8 @@ case $basic_machine in + | visium \ + | we32k \ + | x86 | xc16x | xstormy16 | xtensa \ +- | z8k | z80) ++ | z8k | z80 \ ++ | arm64-apple | *-ios) + basic_machine=$basic_machine-unknown + ;; + c54x) +@@ -1539,7 +1540,7 @@ case $os in + ;; + -nacl*) + ;; +- -ios) ++ -ios | -simulator) + ;; + -none) + ;; diff --git a/recipes/flet-libxslt/patches/mobile-1.1.45.patch b/recipes/flet-libxslt/patches/mobile-1.1.45.patch new file mode 100644 index 00000000..1a267163 --- /dev/null +++ b/recipes/flet-libxslt/patches/mobile-1.1.45.patch @@ -0,0 +1,14 @@ +diff --git a/config.sub b/config.sub +--- a/config.sub ++++ b/config.sub +@@ -1323,6 +1323,10 @@ + nto-qnx*) + kernel=nto + os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'` ++ ;; ++ ios-simulator*) ++ kernel= ++ os=$basic_os + ;; + *-*) + # shellcheck disable=SC2162 diff --git a/recipes/flet-libyaml/build.sh b/recipes/flet-libyaml/build.sh new file mode 100755 index 00000000..1e25e5f9 --- /dev/null +++ b/recipes/flet-libyaml/build.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -eu + +if [ $CROSS_VENV_SDK != "android" ]; then + case $HOST_TRIPLET in + arm64-apple-ios) + HOST_TRIPLET=arm-apple-darwin23 + ;; + arm64-apple-ios-simulator) + HOST_TRIPLET=aarch64-apple-darwin23 + ;; + x86_64-apple-ios-simulator) + HOST_TRIPLET=x86_64-apple-darwin23 + ;; + *) + echo "Unknown host triplet: '$HOST_TRIPLET'" + exit 1 + ;; + esac +fi + +# On Android we want libyaml as a shared library — pyyaml's `_yaml.so` links +# against it dynamically and forge ships `libyaml.so` in `opt/lib/`. +# On iOS we additionally need the static archive (`libyaml.a`) so pyyaml's +# `-lyaml` resolves at link time. iOS pyyaml statically links libyaml into +# `_yaml.cpython-*.so`, mirroring how PyNaCl statically links libsodium via +# the `libsodium.a` that flet-libsodium ships in `opt/lib/`. +if [ $CROSS_VENV_SDK == "android" ]; then + ./configure --host=$HOST_TRIPLET --prefix=$PREFIX --disable-static +else + ./configure --host=$HOST_TRIPLET --prefix=$PREFIX +fi + +# Rewrite libyaml_la_LDFLAGS to drop libtool's library-versioning flags and to +# work around forge's `-F ""` quoting on iOS: +# +# - Default flags are `-no-undefined -release $(YAML_LT_RELEASE) -version-info $(YAML_LT_CURRENT):$(YAML_LT_REVISION):$(YAML_LT_AGE)`. +# `-release` + `-version-info` make libtool produce a versioned dylib +# (`libyaml-0.2.dylib` / `libyaml-0.so`). On Android that forced PyYAML's +# `-lyaml` to need a separate `libyaml.so → libyaml-0.so` shim; on iOS the +# versioned install_name path doesn't exist yet at link time and clang dies. +# `-avoid-version` makes libtool skip versioning entirely → plain +# `libyaml.so` / `libyaml.dylib` with matching soname/install_name. +# - `-pthread` is benign filler. forge injects `-F ""` +# into LDFLAGS for iOS, but libtool re-emits it as bare `-F ` (empty +# arg) — clang then consumes the NEXT token as the framework path. With +# no filler, that next token is `-install_name` and the install_name flag +# silently disappears, dropping clang into "treat /path/libyaml.dylib as a +# source file" mode → "no such file or directory". libsodium happens to +# have `-pthread` in this slot from its own LDFLAGS, which is why +# libsodium builds cleanly; we add it here for the same reason. +sed -i.bak 's/^\(libyaml_la_LDFLAGS *=\).*$/\1 -no-undefined -avoid-version -pthread/' src/Makefile +rm src/Makefile.bak + +make -j $CPU_COUNT +make install + +rm -r $PREFIX/lib/{*.la,pkgconfig} + +if [ $CROSS_VENV_SDK != "android" ]; then + mv $PREFIX/lib/libyaml.dylib $PREFIX/../libyaml.so +fi diff --git a/recipes/flet-libyaml/meta.yaml b/recipes/flet-libyaml/meta.yaml new file mode 100644 index 00000000..80a5da46 --- /dev/null +++ b/recipes/flet-libyaml/meta.yaml @@ -0,0 +1,9 @@ +package: + name: flet-libyaml + version: 0.2.5 + +build: + number: 1 + +source: + url: https://github.com/yaml/libyaml/releases/download/0.2.5/yaml-0.2.5.tar.gz diff --git a/recipes/freetype/meta.yaml b/recipes/freetype/meta.yaml deleted file mode 100644 index 893e31c4..00000000 --- a/recipes/freetype/meta.yaml +++ /dev/null @@ -1,15 +0,0 @@ -package: - name: freetype - version: 2.13.2 - -build: - number: 1 - -source: - url: https://download.flet.dev/freetype/freetype-2.13.2.tar.gz - -patches: - - config.patch - -about: - license_file: docs/FTL.TXT diff --git a/recipes/freetype/patches/config.patch b/recipes/freetype/patches/config.patch deleted file mode 100644 index 7458d301..00000000 --- a/recipes/freetype/patches/config.patch +++ /dev/null @@ -1,29 +0,0 @@ -index 6ae2502..ab90b57 100755 ---- a/builds/unix/config.sub -+++ b/builds/unix/config.sub -@@ -146,6 +146,7 @@ case $1 in - | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ - | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ - | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \ -+ | ios*-simulator | tvos*-simulator | watchos*-simulator \ - | windows-* ) - basic_machine=$field1 - basic_os=$maybe_os -@@ -1492,6 +1493,8 @@ case $os in - ;; - esac - ;; -+ ios | ios-simulator) -+ ;; - *) - # No normalization, but not necessarily accepted, that comes below. - ;; -@@ -1797,6 +1800,8 @@ case $kernel-$os in - # None (no kernel, i.e. freestanding / bare metal), - # can be paired with an output format "OS" - ;; -+ ios-simulator) -+ ;; - -*) - # Blank kernel with real OS is always fine. - ;; \ No newline at end of file diff --git a/recipes/gdal/meta.yaml b/recipes/gdal/meta.yaml new file mode 100644 index 00000000..26c031c6 --- /dev/null +++ b/recipes/gdal/meta.yaml @@ -0,0 +1,20 @@ +package: + name: gdal + version: 3.10.0 + +requirements: + host: + - flet-libgdal 3.10.0 + +build: + number: 4 + script_env: + GDAL_VERSION: 3.10.0 + GDAL_PREFIX: '{platlib}/opt' + GDAL_CFLAGS: '' +# {% if sdk != 'android' %} + LDFLAGS: '-undefined dynamic_lookup' +# {% endif %} + +patches: + - config.patch \ No newline at end of file diff --git a/recipes/gdal/patches/config.patch b/recipes/gdal/patches/config.patch new file mode 100644 index 00000000..58f84d8d --- /dev/null +++ b/recipes/gdal/patches/config.patch @@ -0,0 +1,14 @@ +diff --git a/setup.py b/setup.py +index 5c6ac95..26bb5fa 100644 +--- a/setup.py ++++ b/setup.py +@@ -228,6 +228,9 @@ class gdal_ext(build_ext): + + def get_gdal_config(self, option): + try: ++ var_name = f"GDAL_{option.upper()}" ++ if var_name in os.environ: ++ return os.environ[var_name] + return fetch_config(option, gdal_config=self.gdal_config) + except gdal_config_error: + msg = 'Could not find gdal-config. Make sure you have installed the GDAL native library and development headers.' diff --git a/recipes/gdal/test_gdal.py b/recipes/gdal/test_gdal.py new file mode 100644 index 00000000..6b81d9cd --- /dev/null +++ b/recipes/gdal/test_gdal.py @@ -0,0 +1 @@ +# TBD \ No newline at end of file diff --git a/recipes/google-crc32c/meta.yaml b/recipes/google-crc32c/meta.yaml new file mode 100644 index 00000000..349b81a1 --- /dev/null +++ b/recipes/google-crc32c/meta.yaml @@ -0,0 +1,14 @@ +package: + name: google-crc32c + version: 1.6.0 + +requirements: + host: + - flet-libcrc32c 1.1.2 + +build: + number: 4 +# {% if sdk != 'android' %} + script_env: + LDFLAGS: '-lc++' +# {% endif %} \ No newline at end of file diff --git a/recipes/google-crc32c/test.py b/recipes/google-crc32c/test.py new file mode 100644 index 00000000..7e761bf6 --- /dev/null +++ b/recipes/google-crc32c/test.py @@ -0,0 +1,15 @@ +import unittest + + +class TestGoogleCrc32c(unittest.TestCase): + + # Based on https://github.com/googleapis/python-crc32c/blob/main/tests/test___init__.py + def test_basic(self): + import google_crc32c + + self.assertEqual("c", google_crc32c.implementation) + for data, expected in [(b"", 0x00000000), + (b"\x00" * 32, 0x8A9136AA), + (bytes(range(32)), 0x46DD794E)]: + with self.subTest(data=data): + self.assertEqual(expected, google_crc32c.value(data)) diff --git a/recipes/greenlet/meta.yaml b/recipes/greenlet/meta.yaml new file mode 100644 index 00000000..90104e66 --- /dev/null +++ b/recipes/greenlet/meta.yaml @@ -0,0 +1,10 @@ +package: + name: greenlet + version: 3.1.1 + +build: + number: 4 +# {% if sdk != 'android' %} + script_env: + CXXFLAGS: -std=c++14 +# {% endif %} \ No newline at end of file diff --git a/recipes/grpcio/meta.yaml b/recipes/grpcio/meta.yaml new file mode 100644 index 00000000..1afda7c5 --- /dev/null +++ b/recipes/grpcio/meta.yaml @@ -0,0 +1,28 @@ +package: + name: grpcio + version: 1.67.1 + +build: + number: 5 + script_env: +# {% if sdk == 'android' %} + GRPC_PYTHON_BUILD_SYSTEM_OPENSSL: '1' + GRPC_PYTHON_BUILD_SYSTEM_ZLIB: '1' + PLATFORM: android + OPENSSL_ROOT_DIR: '{platlib}/opt' + CFLAGS: '-U__ANDROID_API__ -D__ANDROID_API__={{ sdk_version }} -Wno-reserved-user-defined-literal' + LDFLAGS: '-llog -L{platlib}/opt/lib' +# {% else %} + CXXFLAGS: -std=c++14 -Wno-c++11-narrowing + LDFLAGS: '-framework CoreFoundation' +# {% endif %} + +patches: + - mobile.patch + +# {% if sdk == 'android' %} +requirements: + host: + - openssl >=3.0.15 + - flet-libcpp-shared >=27.2.12479018 +# {% endif %} \ No newline at end of file diff --git a/recipes/grpcio/patches/mobile.patch b/recipes/grpcio/patches/mobile.patch new file mode 100644 index 00000000..89c6c1ac --- /dev/null +++ b/recipes/grpcio/patches/mobile.patch @@ -0,0 +1,49 @@ +--- a/setup.py 2026-03-31 12:55:12 ++++ b/setup.py 2026-03-31 12:56:03 +@@ -58,12 +58,14 @@ + os.path.join("third_party", "cares"), + os.path.join("third_party", "cares", "cares"), + ) +-if "darwin" in sys.platform: ++if "darwin" in sys.platform or "ios" in sys.platform: + CARES_INCLUDE += (os.path.join("third_party", "cares", "config_darwin"),) + if "freebsd" in sys.platform: + CARES_INCLUDE += (os.path.join("third_party", "cares", "config_freebsd"),) + if "linux" in sys.platform: + CARES_INCLUDE += (os.path.join("third_party", "cares", "config_linux"),) ++if "android" in sys.platform: ++ CARES_INCLUDE += (os.path.join("third_party", "cares", "config_android"),) + if "openbsd" in sys.platform: + CARES_INCLUDE += (os.path.join("third_party", "cares", "config_openbsd"),) + RE2_INCLUDE = (os.path.join("third_party", "re2"),) +@@ -302,7 +304,9 @@ + lambda x: "third_party/boringssl" not in x, CORE_C_FILES + ) + CORE_C_FILES = filter(lambda x: "src/boringssl" not in x, CORE_C_FILES) +- SSL_INCLUDE = (os.path.join("/usr", "include", "openssl"),) ++ # Use cross-compiled OpenSSL headers when OPENSSL_ROOT_DIR is set ++ _ssl_prefix = os.environ.get("OPENSSL_ROOT_DIR", "/usr") ++ SSL_INCLUDE = (os.path.join(_ssl_prefix, "include"),) + + if BUILD_WITH_SYSTEM_ZLIB: + CORE_C_FILES = filter(lambda x: "third_party/zlib" not in x, CORE_C_FILES) +@@ -329,14 +333,17 @@ + + ADDRESS_SORTING_INCLUDE + + CARES_INCLUDE + + RE2_INCLUDE +- + SSL_INCLUDE + + UPB_INCLUDE + + UPB_GRPC_GENERATED_INCLUDE + + UPBDEFS_GRPC_GENERATED_INCLUDE + + UTF8_RANGE_INCLUDE + + XXHASH_INCLUDE +- + ZLIB_INCLUDE + ) ++ ++# On Android, SSL_INCLUDE uses OPENSSL_ROOT_DIR; skip ZLIB_INCLUDE to avoid /usr/include ++EXTENSION_INCLUDE_DIRECTORIES += SSL_INCLUDE ++if "android" not in sys.platform: ++ EXTENSION_INCLUDE_DIRECTORIES += ZLIB_INCLUDE + + EXTENSION_LIBRARIES = () + if "linux" in sys.platform: diff --git a/recipes/grpcio/test_grpcio.py b/recipes/grpcio/test_grpcio.py new file mode 100644 index 00000000..6b81d9cd --- /dev/null +++ b/recipes/grpcio/test_grpcio.py @@ -0,0 +1 @@ +# TBD \ No newline at end of file diff --git a/recipes/jiter/meta.yaml b/recipes/jiter/meta.yaml new file mode 100644 index 00000000..e88513f4 --- /dev/null +++ b/recipes/jiter/meta.yaml @@ -0,0 +1,8 @@ +package: + name: jiter + version: 0.8.2 + +build: + number: 4 + script_env: + _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' \ No newline at end of file diff --git a/recipes/jq/meta.yaml b/recipes/jq/meta.yaml new file mode 100644 index 00000000..7b78f433 --- /dev/null +++ b/recipes/jq/meta.yaml @@ -0,0 +1,12 @@ +package: + name: jq + version: 1.8.0 + +requirements: + host: + - flet-libjq 1.7.1 + +build: + number: 4 + script_env: + JQPY_USE_SYSTEM_LIBS: 1 \ No newline at end of file diff --git a/recipes/kiwisolver/meta.yaml b/recipes/kiwisolver/meta.yaml index 913230cf..811876fe 100644 --- a/recipes/kiwisolver/meta.yaml +++ b/recipes/kiwisolver/meta.yaml @@ -1,7 +1,12 @@ package: name: kiwisolver - version: 1.4.5 + version: 1.4.7 +build: + number: 4 + +# {% if sdk == 'android' %} requirements: - build: - - cppy 1.2.1 + host: + - flet-libcpp-shared 27.2.12479018 +# {% endif %} \ No newline at end of file diff --git a/recipes/libjpeg/meta.yaml b/recipes/libjpeg/meta.yaml deleted file mode 100644 index abfbec17..00000000 --- a/recipes/libjpeg/meta.yaml +++ /dev/null @@ -1,9 +0,0 @@ -package: - name: libjpeg - version: 3.0.3 - -source: - url: https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/3.0.3/libjpeg-turbo-3.0.3.tar.gz - -build: - number: 1 \ No newline at end of file diff --git a/recipes/lru-dict/meta.yaml b/recipes/lru-dict/meta.yaml index 6b2ba9af..680fcda9 100644 --- a/recipes/lru-dict/meta.yaml +++ b/recipes/lru-dict/meta.yaml @@ -1,3 +1,6 @@ package: name: lru-dict version: 1.3.0 + +build: + number: 4 diff --git a/recipes/lxml/meta.yaml b/recipes/lxml/meta.yaml new file mode 100644 index 00000000..fa41ef2c --- /dev/null +++ b/recipes/lxml/meta.yaml @@ -0,0 +1,31 @@ +# {% set version = "6.1.0" %} +# {% if version.startswith('5.') %} +# {% set libxml2_version = "2.9.8" %} +# {% set libxslt_version = "1.1.32" %} +# {% else %} +# {% set libxml2_version = "2.15.3" %} +# {% set libxslt_version = "1.1.45" %} +# {% endif %} + +package: + name: lxml + version: '{{ version }}' + +build: + number: 1 + script_env: + WITH_XML2_CONFIG: '{platlib}/opt/bin/xml2-config' + WITH_XSLT_CONFIG: '{platlib}/opt/bin/xslt-config' +# {% if sdk != 'android' %} + # libxml2 on iOS is built with iconv; lxml's setup.py doesn't add + # -liconv to the link line, so do it here. + LDFLAGS: -liconv +# {% endif %} + +patches: + - mobile.patch + +requirements: + host: + - flet-libxslt {{ libxslt_version }} + - flet-libxml2 {{ libxml2_version }} diff --git a/recipes/lxml/patches/mobile.patch b/recipes/lxml/patches/mobile.patch new file mode 100644 index 00000000..171be5ba --- /dev/null +++ b/recipes/lxml/patches/mobile.patch @@ -0,0 +1,30 @@ +diff --git a/setupinfo.py b/setupinfo.py +index 97e3399..634183a 100644 +--- a/setupinfo.py ++++ b/setupinfo.py +@@ -1,12 +1,13 @@ +-import sys + import io + import os + import os.path + import subprocess +- +-from setuptools.command.build_ext import build_ext as _build_ext ++import sys + from distutils.core import Extension + from distutils.errors import CompileError, DistutilsOptionError ++ ++from setuptools.command.build_ext import build_ext as _build_ext ++ + from versioninfo import get_base_dir + + try: +@@ -330,7 +331,7 @@ def include_dirs(static_include_dirs): + result = [] + possible_include_dirs = flags('cflags') + for possible_include_dir in possible_include_dirs: +- if possible_include_dir.startswith('-I'): ++ if possible_include_dir.startswith('-I') and not possible_include_dir.endswith('/MacOSX.sdk/usr/include'): + result.append(possible_include_dir[2:]) + return result + diff --git a/recipes/lxml/test_lxml.py b/recipes/lxml/test_lxml.py new file mode 100644 index 00000000..ace631a2 --- /dev/null +++ b/recipes/lxml/test_lxml.py @@ -0,0 +1,15 @@ +import unittest + + +class TestLxml(unittest.TestCase): + + def test_basic(self): + from lxml import etree + + parent = etree.fromstring( + "" + ) + self.assertEqual("parent", parent.tag) + self.assertEqual(2, len(parent)) + self.assertEqual("one", parent[0].get("name")) + self.assertEqual("two", parent[1].get("name")) diff --git a/recipes/markupsafe/meta.yaml b/recipes/markupsafe/meta.yaml new file mode 100644 index 00000000..700d466d --- /dev/null +++ b/recipes/markupsafe/meta.yaml @@ -0,0 +1,6 @@ +package: + name: MarkupSafe + version: 2.1.5 + +build: + number: 4 diff --git a/recipes/matplotlib/meta.yaml b/recipes/matplotlib/meta.yaml index 2f33fc2d..5b1cbec2 100644 --- a/recipes/matplotlib/meta.yaml +++ b/recipes/matplotlib/meta.yaml @@ -1,14 +1,25 @@ package: name: matplotlib - version: 3.9.0 + version: 3.10.0 requirements: - host: - - meson + build: - ninja - - numpy 2.0.0 + - meson + host: + - numpy ^2.0.0 + - pybind11 + - flet-libjpeg 3.0.90 build: + number: 4 +# {% if sdk == 'android' and arch in ['armeabi-v7a', 'x86'] %} + script_env: + CPPFLAGS: -Wno-c++11-narrowing +# {% endif %} backend-args: - -Csetup-args=--cross-file - - -Csetup-args={MESON_CROSS_FILE} \ No newline at end of file + - -Csetup-args={MESON_CROSS_FILE} + +# potential issues: +# https://github.com/godotengine/godot/pull/101036/files \ No newline at end of file diff --git a/recipes/msgpack/meta.yaml b/recipes/msgpack/meta.yaml new file mode 100644 index 00000000..293c7c43 --- /dev/null +++ b/recipes/msgpack/meta.yaml @@ -0,0 +1,6 @@ +package: + name: msgpack + version: 1.1.0 + +build: + number: 4 diff --git a/recipes/msgspec/meta.yaml b/recipes/msgspec/meta.yaml new file mode 100644 index 00000000..b9842c22 --- /dev/null +++ b/recipes/msgspec/meta.yaml @@ -0,0 +1,10 @@ +package: + name: msgspec + version: 0.18.6 + +build: + number: 4 + +requirements: + build: + - setuptools ^69.5.1 \ No newline at end of file diff --git a/recipes/numpy/meta.yaml b/recipes/numpy/meta.yaml index 5d28cb9f..ff8aa45c 100644 --- a/recipes/numpy/meta.yaml +++ b/recipes/numpy/meta.yaml @@ -3,15 +3,25 @@ package: name: numpy - version: 1.26.4 + version: 2.2.2 requirements: - host: -# - chaquopy-openblas 0.2.20 + build: - ninja - - meson +{% if sdk == 'android' %} + host: + - flet-libcpp-shared >=27.2.12479018 +{% endif %} + +patches: +{% if version and version < (2, 0) %} + - mobile-1.26.4.patch +{% else %} + - mobile-2.2.2.patch +{% endif %} build: + number: 5 script_env: NPY_DISABLE_SVML: 1 @@ -23,8 +33,8 @@ build: meson: properties: -# {% if sdk == 'android' and arch in ['arm64-v8a', 'x86_64'] %} +# {% if sdk == 'iOS' or (sdk == 'android' and arch in ['arm64-v8a', 'x86_64']) %} longdouble_format: IEEE_QUAD_LE # {% else %} longdouble_format: IEEE_DOUBLE_LE -# {% endif %} \ No newline at end of file +# {% endif %} diff --git a/recipes/numpy/patches/mobile-1.26.4.patch b/recipes/numpy/patches/mobile-1.26.4.patch new file mode 100644 index 00000000..12dde573 --- /dev/null +++ b/recipes/numpy/patches/mobile-1.26.4.patch @@ -0,0 +1,13 @@ +diff --git a/vendored-meson/meson/mesonbuild/linkers/linkers.py b/vendored-meson/meson/mesonbuild/linkers/linkers.py +index 7b3202c..2bf5e19 100644 +--- a/vendored-meson/meson/mesonbuild/linkers/linkers.py ++++ b/vendored-meson/meson/mesonbuild/linkers/linkers.py +@@ -767,7 +767,7 @@ def get_allow_undefined_args(self) -> T.List[str]: + return self._apply_prefix('-undefined,dynamic_lookup') + + def get_std_shared_module_args(self, options: 'KeyedOptionDictType') -> T.List[str]: +- return ['-bundle'] + self._apply_prefix('-undefined,dynamic_lookup') ++ return ['-dynamiclib'] + self._apply_prefix('-undefined,dynamic_lookup') + + def get_pie_args(self) -> T.List[str]: + return [] diff --git a/recipes/numpy/patches/mobile-2.2.2.patch b/recipes/numpy/patches/mobile-2.2.2.patch new file mode 100644 index 00000000..77e972a1 --- /dev/null +++ b/recipes/numpy/patches/mobile-2.2.2.patch @@ -0,0 +1,13 @@ +diff --git a/vendored-meson/meson/mesonbuild/linkers/linkers.py b/vendored-meson/meson/mesonbuild/linkers/linkers.py +index 4eec82e..ca4586f 100644 +--- a/vendored-meson/meson/mesonbuild/linkers/linkers.py ++++ b/vendored-meson/meson/mesonbuild/linkers/linkers.py +@@ -761,7 +761,7 @@ def get_allow_undefined_args(self) -> T.List[str]: + return self._apply_prefix('-undefined,dynamic_lookup') + + def get_std_shared_module_args(self, options: 'KeyedOptionDictType') -> T.List[str]: +- return ['-bundle'] + self._apply_prefix('-undefined,dynamic_lookup') ++ return ['-dynamiclib'] + self._apply_prefix('-undefined,dynamic_lookup') + + def get_pie_args(self) -> T.List[str]: + return [] diff --git a/recipes/numpy/test_numpy.py b/recipes/numpy/test_numpy.py index ee7122bf..51a7d333 100644 --- a/recipes/numpy/test_numpy.py +++ b/recipes/numpy/test_numpy.py @@ -20,3 +20,26 @@ def test_performance(): duration = time() - start_time print(f"{duration:.3f}") assert duration < 0.7 + + +def test_fft(): + """Forces _pocketfft_umath.so to load — the canary for the libc++_shared + Android dep.""" + import numpy as np + + # 8-point FFT of a pure cosine at frequency k=2. The real-input FFT of + # cos(2π · k · n / N) has two equal-magnitude peaks at bins k and N-k. + x = np.cos(2 * np.pi * 2 * np.arange(8) / 8) + spectrum = np.fft.fft(x) + magnitudes = np.abs(spectrum) + + # Peaks at bins 2 and 6 with magnitude N/2 = 4 for unit-amplitude cosine. + assert magnitudes[2] > 3.9, f"bin 2 magnitude = {magnitudes[2]}" + assert magnitudes[6] > 3.9, f"bin 6 magnitude = {magnitudes[6]}" + # All other bins should be ~0 (within fp noise). + other = max(float(magnitudes[i]) for i in (0, 1, 3, 4, 5, 7)) + assert other < 1e-6, f"unexpected non-zero bin: {other}" + + # Round-trip: inverse FFT recovers the original signal. + recovered = np.fft.ifft(spectrum).real + assert np.allclose(recovered, x) diff --git a/recipes/opaque/meta.yaml b/recipes/opaque/meta.yaml new file mode 100644 index 00000000..a96fc3f3 --- /dev/null +++ b/recipes/opaque/meta.yaml @@ -0,0 +1,10 @@ +package: + name: opaque + version: 0.2.0 + +build: + number: 4 + +requirements: + host: + - flet-libopaque 0.99.8 \ No newline at end of file diff --git a/recipes/opencv-python/meta.yaml b/recipes/opencv-python/meta.yaml index 8881b1dc..a3830d0c 100644 --- a/recipes/opencv-python/meta.yaml +++ b/recipes/opencv-python/meta.yaml @@ -2,12 +2,17 @@ package: name: opencv-python version: 4.10.0.84 +requirements: + host: + - numpy ^2.0.0 + patches: - mobile.patch -# {% if sdk == 'android' %} build: + number: 4 script_env: +# {% if sdk == 'android' %} CMAKE_ARGS: >- -DANDROID=ON -DWITH_IPP=OFF @@ -23,18 +28,19 @@ build: -DANDROID_NATIVE_API_LEVEL={ANDROID_API_LEVEL} -DANDROID_ALLOW_UNDEFINED_VERSION_SCRIPT_SYMBOLS=1 -DCMAKE_TOOLCHAIN_FILE={NDK_ROOT}/build/cmake/android.toolchain.cmake + -DCMAKE_SHARED_LINKER_FLAGS="-Wl,-z,max-page-size=16384" + -DCMAKE_MODULE_LINKER_FLAGS="-Wl,-z,max-page-size=16384" -DOPENCV_FORCE_PYTHON_LIBS=ON -DPYTHON3_INCLUDE_PATH={prefix}/include/python{py_version_short} -DPYTHON3_LIBRARIES={prefix}/lib/libpython{py_version_short}.so -DPYTHON3_NUMPY_INCLUDE_DIRS={platlib}/numpy/_core/include # {% else %} -build: - script_env: CMAKE_ARGS: >- -DAPPLE_FRAMEWORK=ON -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_SYSTEM_PROCESSOR=aarch64 -DCMAKE_OSX_SYSROOT={{ sdk }} + -DCMAKE_OSX_DEPLOYMENT_TARGET={{ sdk_version }} -DCMAKE_OSX_ARCHITECTURES={{ arch }} -DWITH_IPP=OFF -DWITH_ITT=OFF @@ -47,10 +53,4 @@ build: -DPYTHON3_INCLUDE_PATH={prefix}/include/python{py_version_short} -DPYTHON3_LIBRARIES={prefix}/lib/libpython{py_version_short}.so -DPYTHON3_NUMPY_INCLUDE_DIRS={platlib}/numpy/_core/include -# {% endif %} - -requirements: - build: - - cmake - - scikit-build @ git+https://github.com/flet-dev/scikit-build@ios-android-support - - numpy 2.0.0 +# {% endif %} \ No newline at end of file diff --git a/recipes/orjson/meta.yaml b/recipes/orjson/meta.yaml new file mode 100644 index 00000000..1a7edd2a --- /dev/null +++ b/recipes/orjson/meta.yaml @@ -0,0 +1,8 @@ +package: + name: orjson + version: 3.11.9 + +build: + number: 1 + script_env: + _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' diff --git a/recipes/orjson/test_orjson.py b/recipes/orjson/test_orjson.py new file mode 100644 index 00000000..3789c7d3 --- /dev/null +++ b/recipes/orjson/test_orjson.py @@ -0,0 +1,26 @@ +def test_basic(): + """Confirm the wheel loads and round-trips a representative payload.""" + import orjson + + payload = { + "library": "orjson", + "version": orjson.__version__, + "active": True, + "tags": ["mobile", "python", "flet"], + "ratio": 3.141592653589793, + "nothing": None, + } + + encoded = orjson.dumps(payload) + assert isinstance(encoded, bytes) # orjson returns bytes, not str + + decoded = orjson.loads(encoded) + assert decoded == payload + + +def test_numeric_precision(): + """Round-trip a float at the f64 precision boundary.""" + import orjson + + pi = 3.141592653589793 + assert orjson.loads(orjson.dumps(pi)) == pi diff --git a/recipes/pandas/meta.yaml b/recipes/pandas/meta.yaml index 5eac73f0..5234d422 100644 --- a/recipes/pandas/meta.yaml +++ b/recipes/pandas/meta.yaml @@ -1,15 +1,34 @@ package: name: pandas - version: 2.2.2 + version: 2.2.3 + +# {% if not version or version >= (2,1) %} +# pandas >= 2.1.0 requirements: build: - - numpy 2.0.0 - host: - - meson - ninja + - meson + host: + - numpy ^2.0.0 +# {% if sdk == 'android' %} + - flet-libcpp-shared 27.2.12479018 +# {% endif %} + +patches: + - mobile.patch build: + number: 4 backend-args: - -Csetup-args=--cross-file - - -Csetup-args={MESON_CROSS_FILE} \ No newline at end of file + - -Csetup-args={MESON_CROSS_FILE} + +# {% else %} +# pandas < 2.1.0 + +requirements: + host: + - numpy 1.26.4 + +# {% endif %} \ No newline at end of file diff --git a/recipes/pandas/patches/mobile.patch b/recipes/pandas/patches/mobile.patch new file mode 100644 index 00000000..7160a732 --- /dev/null +++ b/recipes/pandas/patches/mobile.patch @@ -0,0 +1,15 @@ +diff --git a/pyproject.toml b/pyproject.toml +index 238abd8..37de5c7 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -2,8 +2,8 @@ + # Minimum requirements for the build system to execute. + # See https://github.com/scipy/scipy/pull/12940 for the AIX issue. + requires = [ +- "meson-python==0.13.1", +- "meson==1.2.1", ++ "meson-python==0.15.0", ++ #"meson==1.2.1", + "wheel", + "Cython~=3.0.5", # Note: sync with setup.py, environment.yml and asv.conf.json + # Force numpy higher than 2.0, so that built wheels are compatible diff --git a/recipes/pendulum/meta.yaml b/recipes/pendulum/meta.yaml new file mode 100644 index 00000000..7aed052d --- /dev/null +++ b/recipes/pendulum/meta.yaml @@ -0,0 +1,14 @@ +package: + name: pendulum + version: 3.0.0 + +build: + number: 4 + script_env: + _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' +# {% if sdk == 'iphonesimulator' %} + CFLAGS_aarch64-apple-ios-sim: "--target=arm64-apple-ios13.0-simulator" +# {% endif %} + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/pendulum/patches/mobile.patch b/recipes/pendulum/patches/mobile.patch new file mode 100644 index 00000000..8d410b4c --- /dev/null +++ b/recipes/pendulum/patches/mobile.patch @@ -0,0 +1,12 @@ +diff --git a/rust/src/helpers.rs b/rust/src/helpers.rs +index 364075a..8c5fbe8 100644 +--- a/rust/src/helpers.rs ++++ b/rust/src/helpers.rs +@@ -56,7 +56,7 @@ pub fn local_time( + seconds -= (10957 * SECS_PER_DAY as usize) as isize; + year += 30; // == 2000 + } else { +- seconds += ((146_097 - 10957) * SECS_PER_DAY as usize) as isize; ++ seconds += ((146_097 - 10957) as u64 * SECS_PER_DAY as u64) as isize; + year -= 370; // == 1600 + } diff --git a/recipes/pillow/meta.yaml b/recipes/pillow/meta.yaml index b69db90f..b8df623e 100644 --- a/recipes/pillow/meta.yaml +++ b/recipes/pillow/meta.yaml @@ -1,12 +1,32 @@ package: name: Pillow - version: 10.3.0 - -patches: - - setup.patch + version: 11.1.0 requirements: host: # PNG support is internal: libpng is not used. - - libjpeg 3.0.3 - - freetype 2.13.2 + - flet-libjpeg 3.0.90 + - flet-libfreetype 2.13.3 + +# {% if not version or version >= (11,0,0) %} +# pillow >= 11.x + +patches: + - setup-11.x.patch + +# {% else %} +# pillow <= 10.x + +patches: + - setup-10.x.patch + +# {% endif %} + +build: + number: 4 +# {% if sdk != 'android' %} + script_env: + # libfreetype references both libz and libbz2 + # but doesn't link them into the static library + LDFLAGS: -lz -lbz2 +# {% endif %} diff --git a/recipes/pillow/patches/setup.patch b/recipes/pillow/patches/setup-10.x.patch similarity index 76% rename from recipes/pillow/patches/setup.patch rename to recipes/pillow/patches/setup-10.x.patch index 80b29b02..21bff57e 100644 --- a/recipes/pillow/patches/setup.patch +++ b/recipes/pillow/patches/setup-10.x.patch @@ -1,8 +1,8 @@ diff --git a/setup.py b/setup.py -index ac401dd..a5a9938 100644 +index 0abfaad..7b077d4 100644 --- a/setup.py +++ b/setup.py -@@ -341,9 +341,7 @@ class pil_build_ext(build_ext): +@@ -342,9 +342,7 @@ class pil_build_ext(build_ext): return True if value in configuration.get(option, []) else None def initialize_options(self): @@ -13,7 +13,7 @@ index ac401dd..a5a9938 100644 self.add_imaging_libs = "" build_ext.initialize_options(self) for x in self.feature: -@@ -421,10 +419,19 @@ class pil_build_ext(build_ext): +@@ -422,10 +420,22 @@ class pil_build_ext(build_ext): self.extensions.remove(extension) break @@ -23,33 +23,38 @@ index ac401dd..a5a9938 100644 + sdk = { + ("ios", False): ["--sdk", "iphoneos"], + ("ios", True): ["--sdk", "iphonesimulator"], -+ ("tvs", False): ["--sdk", "appletvos"], -+ ("tvs", True): ["--sdk", "appletvsimulator"], ++ ("tvos", False): ["--sdk", "appletvos"], ++ ("tvos", True): ["--sdk", "appletvsimulator"], + ("watchos", False): ["--sdk", "watchos"], + ("watchos", True): ["--sdk", "watchsimulator"], + ("darwin", False): [], -+ }[sys.platform, getattr(sys.implementation, "_simulator", False)] ++ }[ ++ sys.platform, ++ getattr(sys.implementation, "_multiarch", "").endswith("simulator"), ++ ] sdk_path = ( - subprocess.check_output(["xcrun", "--show-sdk-path"]) + subprocess.check_output(["xcrun", "--show-sdk-path"] + sdk) .strip() .decode("latin1") ) -@@ -577,11 +584,15 @@ class pil_build_ext(build_ext): +@@ -580,13 +590,18 @@ class pil_build_ext(build_ext): _add_directory(library_dirs, "/usr/X11/lib") _add_directory(include_dirs, "/usr/X11/include") - sdk_path = self.get_macos_sdk_path() -+ sdk_path = self.get_apple_sdk_path() -+ if sdk_path: -+ _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) -+ _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) -+ elif sys.platform in ("ios", "tvos", "watchos"): + sdk_path = self.get_apple_sdk_path() if sdk_path: _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) -- + for extension in self.extensions: extension.extra_compile_args = ["-Wno-nullability-completeness"] ++ elif sys.platform in {"ios", "tvos", "watchos"}: ++ sdk_path = self.get_apple_sdk_path() ++ if sdk_path: ++ _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) ++ _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) elif ( + sys.platform.startswith("linux") + or sys.platform.startswith("gnu") diff --git a/recipes/pillow/patches/setup-11.x.patch b/recipes/pillow/patches/setup-11.x.patch new file mode 100644 index 00000000..b438622c --- /dev/null +++ b/recipes/pillow/patches/setup-11.x.patch @@ -0,0 +1,40 @@ +diff --git a/setup.py b/setup.py +--- a/setup.py ++++ b/setup.py +@@ -355,9 +355,7 @@ class pil_build_ext(build_ext): + return True if value in configuration.get(option, []) else None + + def initialize_options(self) -> None: +- self.disable_platform_guessing = self.check_configuration( +- "platform-guessing", "disable" +- ) ++ self.disable_platform_guessing = True + self.add_imaging_libs = "" + build_ext.initialize_options(self) + for x in self.feature: +@@ -550,8 +548,10 @@ class pil_build_ext(build_ext): + for d in os.environ[k].split(os.path.pathsep): + _add_directory(library_dirs, d) + +- _add_directory(library_dirs, os.path.join(sys.prefix, "lib")) +- _add_directory(include_dirs, os.path.join(sys.prefix, "include")) ++ # Skip sys.prefix paths when cross-compiling to avoid host headers ++ if not self.disable_platform_guessing: ++ _add_directory(library_dirs, os.path.join(sys.prefix, "lib")) ++ _add_directory(include_dirs, os.path.join(sys.prefix, "include")) + + # + # add platform directories +@@ -684,6 +684,12 @@ class pil_build_ext(build_ext): + self.compiler.include_dirs = include_dirs + self.compiler.include_dirs + + # ++ # When cross-compiling, remove host system include/lib paths that ++ # leak in from the build Python's sysconfig (e.g. /usr/include). ++ if self.disable_platform_guessing: ++ self.compiler.include_dirs = [d for d in self.compiler.include_dirs if not d.startswith("/usr/")] ++ self.compiler.library_dirs = [d for d in self.compiler.library_dirs if not d.startswith("/usr/")] ++ # + # look for available libraries + + feature = self.feature diff --git a/recipes/primp/meta.yaml b/recipes/primp/meta.yaml new file mode 100644 index 00000000..8c2275a0 --- /dev/null +++ b/recipes/primp/meta.yaml @@ -0,0 +1,6 @@ +package: + name: primp + version: 1.3.1 + +build: + number: 1 diff --git a/recipes/protobuf/meta.yaml b/recipes/protobuf/meta.yaml new file mode 100644 index 00000000..38777832 --- /dev/null +++ b/recipes/protobuf/meta.yaml @@ -0,0 +1,6 @@ +package: + name: protobuf + version: 5.28.3 + +build: + number: 4 diff --git a/recipes/protobuf/test_protobuf.py b/recipes/protobuf/test_protobuf.py new file mode 100644 index 00000000..6b81d9cd --- /dev/null +++ b/recipes/protobuf/test_protobuf.py @@ -0,0 +1 @@ +# TBD \ No newline at end of file diff --git a/recipes/pycryptodome/meta.yaml b/recipes/pycryptodome/meta.yaml new file mode 100644 index 00000000..b57c3896 --- /dev/null +++ b/recipes/pycryptodome/meta.yaml @@ -0,0 +1,9 @@ +package: + name: pycryptodome + version: 3.21.0 + +build: + number: 4 + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/pycryptodome/patches/mobile.patch b/recipes/pycryptodome/patches/mobile.patch new file mode 100644 index 00000000..08e26fe8 --- /dev/null +++ b/recipes/pycryptodome/patches/mobile.patch @@ -0,0 +1,31 @@ +diff --git a/lib/Crypto/Util/_raw_api.py b/lib/Crypto/Util/_raw_api.py +index e0065c3..3b14e00 100644 +--- a/lib/Crypto/Util/_raw_api.py ++++ b/lib/Crypto/Util/_raw_api.py +@@ -30,6 +30,7 @@ + + import os + import abc ++import pathlib + import sys + from Crypto.Util.py3compat import byte_string + from Crypto.Util._file_system import pycryptodome_filename +@@ -302,13 +303,17 @@ def load_pycryptodome_raw_lib(name, cdecl): + split = name.split(".") + dir_comps, basename = split[:-1], split[-1] + attempts = [] +- for ext in extension_suffixes: ++ for ext in extension_suffixes + [".fwork"]: + try: + filename = basename + ext + full_name = pycryptodome_filename(dir_comps, filename) + if not os.path.isfile(full_name): + attempts.append("Not found '%s'" % filename) + continue ++ if full_name.endswith(".fwork"): ++ with open(full_name, 'r') as f: ++ framework_binary = f.read().strip() ++ full_name = str(pathlib.Path(sys.executable).parent.joinpath(framework_binary)) + return load_lib(full_name, cdecl) + except OSError as exp: + attempts.append("Cannot load '%s': %s" % (filename, str(exp))) diff --git a/recipes/pycryptodomex/meta.yaml b/recipes/pycryptodomex/meta.yaml new file mode 100644 index 00000000..2f81852d --- /dev/null +++ b/recipes/pycryptodomex/meta.yaml @@ -0,0 +1,9 @@ +package: + name: pycryptodomex + version: 3.21.0 + +build: + number: 4 + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/pycryptodomex/patches/mobile.patch b/recipes/pycryptodomex/patches/mobile.patch new file mode 100644 index 00000000..b9d5f9a7 --- /dev/null +++ b/recipes/pycryptodomex/patches/mobile.patch @@ -0,0 +1,31 @@ +diff --git a/lib/Cryptodome/Util/_raw_api.py b/lib/Cryptodome/Util/_raw_api.py +index e0065c3..3b14e00 100644 +--- a/lib/Cryptodome/Util/_raw_api.py ++++ b/lib/Cryptodome/Util/_raw_api.py +@@ -30,6 +30,7 @@ + + import os + import abc ++import pathlib + import sys + from Cryptodome.Util.py3compat import byte_string + from Cryptodome.Util._file_system import pycryptodome_filename +@@ -302,13 +303,17 @@ def load_pycryptodome_raw_lib(name, cdecl): + split = name.split(".") + dir_comps, basename = split[:-1], split[-1] + attempts = [] +- for ext in extension_suffixes: ++ for ext in extension_suffixes + [".fwork"]: + try: + filename = basename + ext + full_name = pycryptodome_filename(dir_comps, filename) + if not os.path.isfile(full_name): + attempts.append("Not found '%s'" % filename) + continue ++ if full_name.endswith(".fwork"): ++ with open(full_name, 'r') as f: ++ framework_binary = f.read().strip() ++ full_name = str(pathlib.Path(sys.executable).parent.joinpath(framework_binary)) + return load_lib(full_name, cdecl) + except OSError as exp: + attempts.append("Cannot load '%s': %s" % (filename, str(exp))) diff --git a/recipes/pydantic-core/meta.yaml b/recipes/pydantic-core/meta.yaml index bda176e1..cb3ef624 100644 --- a/recipes/pydantic-core/meta.yaml +++ b/recipes/pydantic-core/meta.yaml @@ -1,7 +1,8 @@ package: name: pydantic-core - version: 2.18.4 + version: 2.33.2 -requirements: - host: - - maturin @ git+https://github.com/flet-dev/maturin@python-host-platform-var \ No newline at end of file +build: + number: 4 + script_env: + _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' \ No newline at end of file diff --git a/recipes/pyjnius/meta.yaml b/recipes/pyjnius/meta.yaml new file mode 100644 index 00000000..2c33aa00 --- /dev/null +++ b/recipes/pyjnius/meta.yaml @@ -0,0 +1,15 @@ +package: + name: pyjnius + version: 1.6.1 + +build: + number: 4 + +patches: + - mobile.patch + +requirements: + build: + - Cython <3.1 + host: + - flet-libpyjni 1.0.1 \ No newline at end of file diff --git a/recipes/pyjnius/patches/mobile.patch b/recipes/pyjnius/patches/mobile.patch new file mode 100644 index 00000000..2c3e83cd --- /dev/null +++ b/recipes/pyjnius/patches/mobile.patch @@ -0,0 +1,140 @@ +diff --git a/jnius/env.py b/jnius/env.py +index ea12d82..4111845 100644 +--- a/jnius/env.py ++++ b/jnius/env.py +@@ -288,7 +288,7 @@ class MacOsXJavaLocation(UnixJavaLocation): + + class AndroidJavaLocation(UnixJavaLocation): + def get_libraries(self): +- return ['SDL2', 'log'] ++ return ['log', 'pyjni'] + + def get_include_dirs(self): + # When cross-compiling for Android, we should not use the include dirs +diff --git a/jnius/jnius_conversion.pxi b/jnius/jnius_conversion.pxi +index 2e0b48d..e474445 100644 +--- a/jnius/jnius_conversion.pxi ++++ b/jnius/jnius_conversion.pxi +@@ -719,7 +719,7 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except + + elif definition[0] == 'L': + defstr = str_for_c(definition[1:-1]) +- j_class = j_env[0].FindClass(j_env, defstr) ++ j_class = PyJni_FindClass(defstr) + + if j_class == NULL: + raise JavaException( +diff --git a/jnius/jnius_export_class.pxi b/jnius/jnius_export_class.pxi +index a688e2b..7018adf 100644 +--- a/jnius/jnius_export_class.pxi ++++ b/jnius/jnius_export_class.pxi +@@ -146,7 +146,7 @@ class MetaJavaClass(MetaJavaBase): + + if NULL == obj: + for interface in getattr(value, '__javainterfaces__', []): +- obj = j_env[0].FindClass(j_env, str_for_c(interface)) ++ obj = PyJni_FindClass(str_for_c(interface)) + if obj == NULL: + j_env[0].ExceptionClear(j_env) + elif 0 != j_env[0].IsAssignableFrom(j_env, obj, me.j_cls): +@@ -177,11 +177,11 @@ class MetaJavaClass(MetaJavaBase): + cdef JNIEnv *j_env = get_jnienv() + + if __javainterfaces__ and __javabaseclass__: +- baseclass = j_env[0].FindClass(j_env, __javabaseclass__) ++ baseclass = PyJni_FindClass(__javabaseclass__) + interfaces = malloc(sizeof(jclass) * len(__javainterfaces__)) + + for n, i in enumerate(__javainterfaces__): +- interfaces[n] = j_env[0].FindClass(j_env, i) ++ interfaces[n] = PyJni_FindClass(i) + + getProxyClass = j_env[0].GetStaticMethodID( + j_env, baseclass, "getProxyClass", +@@ -206,8 +206,7 @@ class MetaJavaClass(MetaJavaBase): + ' {0}'.format(__javaclass__)) + else: + class_name = str_for_c(__javaclass__) +- jcs.j_cls = j_env[0].FindClass(j_env, +- class_name) ++ jcs.j_cls = PyJni_FindClass(class_name) + if jcs.j_cls == NULL: + raise JavaException('Unable to find the class' + ' {0}'.format(__javaclass__)) +diff --git a/jnius/jnius_export_func.pxi b/jnius/jnius_export_func.pxi +index 3a76dd5..c3e1375 100644 +--- a/jnius/jnius_export_func.pxi ++++ b/jnius/jnius_export_func.pxi +@@ -18,7 +18,7 @@ def find_javaclass(namestr): + cdef jclass jc + cdef JNIEnv *j_env = get_jnienv() + +- jc = j_env[0].FindClass(j_env, name) ++ jc = PyJni_FindClass(name) + check_exception(j_env) + + cls = Class(noinstance=True) +diff --git a/jnius/jnius_jvm_android.pxi b/jnius/jnius_jvm_android.pxi +index dae6b31..17fe0e2 100644 +--- a/jnius/jnius_jvm_android.pxi ++++ b/jnius/jnius_jvm_android.pxi +@@ -1,6 +1,7 @@ + # on android, rely on SDL to get the JNI env +-cdef extern JNIEnv *SDL_AndroidGetJNIEnv() ++cdef extern JNIEnv *PyJni_AndroidGetJNIEnv() + ++cdef extern jclass *PyJni_FindClass(const char* className) + + cdef JNIEnv *get_platform_jnienv() except NULL: +- return SDL_AndroidGetJNIEnv() ++ return PyJni_AndroidGetJNIEnv() +diff --git a/jnius/jnius_utils.pxi b/jnius/jnius_utils.pxi +index ef5d6ab..a6edb0c 100644 +--- a/jnius/jnius_utils.pxi ++++ b/jnius/jnius_utils.pxi +@@ -164,14 +164,14 @@ cdef void check_assignable_from_str(JNIEnv *env, source, target) except *: + return + + s_source = str_for_c(source) +- cls_source = env[0].FindClass(env, s_source) ++ cls_source = PyJni_FindClass(s_source) + + if cls_source == NULL: + raise JavaException('Unable to find the class for {0!r}'.format( + source)) + + s_target = str_for_c(target) +- cls_target = env[0].FindClass(env, s_target) ++ cls_target = PyJni_FindClass(s_target) + + if cls_target == NULL: + raise JavaException('Unable to find the class for {0!r}'.format( +@@ -238,7 +238,7 @@ cdef void check_assignable_from(JNIEnv *env, JavaClass jc, signature) except *: + # we got an object that doesn't match with the signature + # check if we can use it. + s = str_for_c(signature) +- cls = env[0].FindClass(env, s) ++ cls = PyJni_FindClass(s) + if cls == NULL: + raise JavaException('Unable to found the class for {0!r}'.format( + signature)) +diff --git a/setup.py b/setup.py +index 3cca8c1..a7f40f0 100644 +--- a/setup.py ++++ b/setup.py +@@ -54,14 +54,7 @@ PXI_FILES = [ + EXTRA_LINK_ARGS = [] + + # detect Python for android +-PLATFORM = sys.platform +-NDKPLATFORM = getenv('NDKPLATFORM') +-if NDKPLATFORM is not None and getenv('LIBLINK'): +- PLATFORM = 'android' +- +-# detect platform +-if PLATFORM == 'android': +- PYX_FILES = [fn[:-3] + 'c' for fn in PYX_FILES] ++PLATFORM = 'android' + + JAVA=get_java_setup(PLATFORM) + diff --git a/recipes/pymongo/meta.yaml b/recipes/pymongo/meta.yaml new file mode 100644 index 00000000..79a9cbb7 --- /dev/null +++ b/recipes/pymongo/meta.yaml @@ -0,0 +1,6 @@ +package: + name: pymongo + version: 4.10.1 + +build: + number: 4 diff --git a/recipes/pynacl/meta.yaml b/recipes/pynacl/meta.yaml new file mode 100644 index 00000000..46fd8671 --- /dev/null +++ b/recipes/pynacl/meta.yaml @@ -0,0 +1,13 @@ +package: + name: PyNaCl + version: 1.5.0 + +build: + number: 4 + +requirements: + host: + - flet-libsodium 1.0.20 + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/pynacl/patches/mobile.patch b/recipes/pynacl/patches/mobile.patch new file mode 100644 index 00000000..24d54087 --- /dev/null +++ b/recipes/pynacl/patches/mobile.patch @@ -0,0 +1,23 @@ +diff --git a/setup.py b/setup.py +index 96d4b32..045314f 100644 +--- a/setup.py ++++ b/setup.py +@@ -29,7 +29,6 @@ from setuptools import Distribution, setup + from setuptools.command.build_clib import build_clib as _build_clib + from setuptools.command.build_ext import build_ext as _build_ext + +- + requirements = [] + setup_requirements = ["setuptools"] + test_requirements = [ +@@ -218,8 +217,8 @@ setup( + package_data={"nacl": ["py.typed"]}, + ext_package="nacl", + cffi_modules=["src/bindings/build.py:ffi"], +- cmdclass={"build_clib": build_clib, "build_ext": build_ext}, +- distclass=Distribution, ++ # cmdclass={"build_ext": build_ext}, ++ # distclass=Distribution, + zip_safe=False, + classifiers=[ + "Programming Language :: Python :: Implementation :: CPython", diff --git a/recipes/pyobjus/meta.yaml b/recipes/pyobjus/meta.yaml new file mode 100644 index 00000000..15d6fd1a --- /dev/null +++ b/recipes/pyobjus/meta.yaml @@ -0,0 +1,13 @@ +package: + name: pyobjus + version: 1.2.3 + +build: + number: 1 + +patches: + - mobile.patch + +requirements: + host: + - libffi \ No newline at end of file diff --git a/recipes/pyobjus/patches/mobile.patch b/recipes/pyobjus/patches/mobile.patch new file mode 100644 index 00000000..ba16e9f0 --- /dev/null +++ b/recipes/pyobjus/patches/mobile.patch @@ -0,0 +1,86 @@ +diff --git a/pyobjus/_runtime.h b/pyobjus/_runtime.h +index c31b7ba..6d0cf85 100644 +--- a/pyobjus/_runtime.h ++++ b/pyobjus/_runtime.h +@@ -1,6 +1,6 @@ + #include + #include +-#include ++#include + #include + #include + #include +diff --git a/pyobjus/common.pxi b/pyobjus/common.pxi +index 3a17bbb..4f43c6a 100644 +--- a/pyobjus/common.pxi ++++ b/pyobjus/common.pxi +@@ -109,7 +109,7 @@ cdef extern from "objc/runtime.h": + objc_method_description* protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount) + + +-cdef extern from "ffi/ffi.h": ++cdef extern from "ffi.h": + ctypedef unsigned long ffi_arg + ctypedef signed long ffi_sarg + ctypedef enum: FFI_TYPE_STRUCT +diff --git a/setup.py b/setup.py +index 0de7708..c8deb36 100644 +--- a/setup.py ++++ b/setup.py +@@ -20,13 +20,7 @@ if kivy_ios_root is not None: + print("Pyobjus platform is {}".format(dev_platform)) + + # OSX +-files = [] +-if dev_platform == 'darwin': +- files = ['pyobjus.pyx'] +-# iOS +-elif dev_platform == 'ios': +- files = ['pyobjus.c'] +- ++files = ['pyobjus.pyx'] + + class PyObjusBuildExt(build_ext, object): + +@@ -43,13 +37,10 @@ class PyObjusBuildExt(build_ext, object): + # The following essentially supply a dynamically generated subclass + # that mix in the cython version of build_ext so that the + # functionality provided will also be executed. +- if dev_platform != 'ios': +- from Cython.Distutils import build_ext as cython_build_ext +- build_ext_cls = type( +- 'PyObjusBuildExt', (PyObjusBuildExt, cython_build_ext), {}) +- return super(PyObjusBuildExt, cls).__new__(build_ext_cls) +- else: +- return super(PyObjusBuildExt, cls).__new__(cls) ++ from Cython.Distutils import build_ext as cython_build_ext ++ build_ext_cls = type( ++ 'PyObjusBuildExt', (PyObjusBuildExt, cython_build_ext), {}) ++ return super(PyObjusBuildExt, cls).__new__(build_ext_cls) + + def build_extensions(self): + # create a configuration file for pyobjus (export the platform) +@@ -57,11 +48,9 @@ class PyObjusBuildExt(build_ext, object): + config_pxi_need_update = True + config_pxi = 'DEF PLATFORM = "{}"\n'.format(dev_platform) + config_pxi += 'DEF ARCH = "{}"\n'.format(arch) +- if dev_platform == 'ios': +- cython3 = False # Assume Cython 0.29, which is what we use for kivy-ios (ATM) +- else: +- import Cython +- cython3 = Cython.__version__.startswith('3.') ++ ++ import Cython ++ cython3 = Cython.__version__.startswith('3.') + config_pxi += f"DEF PYOBJUS_CYTHON_3 = {cython3}" + if exists(config_pxi_fn): + with open(config_pxi_fn) as fd: +@@ -73,7 +62,7 @@ class PyObjusBuildExt(build_ext, object): + super().build_extensions() + + +-libraries = ['ffi'] ++libraries = ['ffi', 'objc'] + library_dirs = [] + extra_compile_args = [] + extra_link_args = [] diff --git a/recipes/pyogrio/meta.yaml b/recipes/pyogrio/meta.yaml new file mode 100644 index 00000000..fa6c36fe --- /dev/null +++ b/recipes/pyogrio/meta.yaml @@ -0,0 +1,17 @@ +package: + name: pyogrio + version: 0.10.0 + +requirements: + host: + - flet-libgdal 3.10.0 + +build: + number: 4 + script_env: + GDAL_VERSION: 3.10.0 + GDAL_LIBRARY_PATH: '{platlib}/opt/lib' + GDAL_INCLUDE_PATH: '{platlib}/opt/include' +# {% if sdk != 'android' %} + LDFLAGS: '-undefined dynamic_lookup' +# {% endif %} \ No newline at end of file diff --git a/recipes/pyogrio/test_pyogrio.py b/recipes/pyogrio/test_pyogrio.py new file mode 100644 index 00000000..6b81d9cd --- /dev/null +++ b/recipes/pyogrio/test_pyogrio.py @@ -0,0 +1 @@ +# TBD \ No newline at end of file diff --git a/recipes/pyproj/meta.yaml b/recipes/pyproj/meta.yaml new file mode 100644 index 00000000..0d26cf64 --- /dev/null +++ b/recipes/pyproj/meta.yaml @@ -0,0 +1,19 @@ +package: + name: pyproj + version: 3.7.0 + +build: + number: 4 + script_env: + PROJ_VERSION: 9.5.0 + PROJ_DIR: '{platlib}/opt' +# {% if sdk != 'android' %} + LDFLAGS: '-undefined dynamic_lookup' +# {% endif %} + +requirements: + host: + - flet-libproj 9.5.0 + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/pyproj/patches/mobile.patch b/recipes/pyproj/patches/mobile.patch new file mode 100644 index 00000000..aa542c16 --- /dev/null +++ b/recipes/pyproj/patches/mobile.patch @@ -0,0 +1,15 @@ +diff --git a/setup.py b/setup.py +index 9987cff..b56a0fc 100644 +--- a/setup.py ++++ b/setup.py +@@ -194,7 +194,9 @@ def get_extension_modules(): + "include_dirs": include_dirs, + "library_dirs": library_dirs, + "runtime_library_dirs": ( +- library_dirs if os.name != "nt" and sys.platform != "cygwin" else None ++ library_dirs ++ if os.name != "nt" and sys.platform != "cygwin" and sys.platform != "ios" ++ else None + ), + "libraries": get_libraries(library_dirs), + } diff --git a/recipes/pysodium/meta.yaml b/recipes/pysodium/meta.yaml new file mode 100644 index 00000000..2bc8811e --- /dev/null +++ b/recipes/pysodium/meta.yaml @@ -0,0 +1,10 @@ +package: + name: pysodium + version: 0.7.18 + +build: + number: 4 + +requirements: + host: + - flet-libsodium 1.0.20 \ No newline at end of file diff --git a/recipes/pyxirr/meta.yaml b/recipes/pyxirr/meta.yaml new file mode 100644 index 00000000..f76acfb1 --- /dev/null +++ b/recipes/pyxirr/meta.yaml @@ -0,0 +1,6 @@ +package: + name: pyxirr + version: 0.10.8 + +build: + number: 1 diff --git a/recipes/pyxirr/test_pyxirr.py b/recipes/pyxirr/test_pyxirr.py new file mode 100644 index 00000000..4a5aede7 --- /dev/null +++ b/recipes/pyxirr/test_pyxirr.py @@ -0,0 +1,22 @@ +def test_basic(): + """Proves the wheel loads and the Rust solver works: irr() finds the rate + where npv() == 0, so we check both the known value and the npv round-trip.""" + import pyxirr + + amounts = [-100, 39, 59, 55, 20] + r = pyxirr.irr(amounts) + assert abs(r - 0.2809484211599611) < 1e-6 + assert abs(pyxirr.npv(r, amounts)) < 1e-6 + + +def test_xirr(): + """XIRR is the function pyxirr is named after. Exercises date parsing and + the day-count engine (defaults to ACT_365F) on top of the solver.""" + import pyxirr + from datetime import date + + dates = [date(2020, 1, 1), date(2021, 1, 1), date(2022, 1, 1)] + amounts = [-1000, 750, 500] + rate = pyxirr.xirr(dates, amounts) + assert abs(rate - 0.17500926461545202) < 1e-4 + assert abs(pyxirr.xnpv(rate, dates, amounts)) < 1e-4 diff --git a/recipes/pyyaml/meta.yaml b/recipes/pyyaml/meta.yaml new file mode 100644 index 00000000..c3be4692 --- /dev/null +++ b/recipes/pyyaml/meta.yaml @@ -0,0 +1,13 @@ +package: + name: PyYAML + version: 6.0.2 + +build: + number: 5 + +requirements: + host: + # Without libyaml's headers + shared library on the build root, PyYAML's + # setup.py silently skips the `_yaml` C extension (`DistutilsPlatformError` + # → `log.warn("skipping build_ext")`) and ships a pure-Python wheel. + - flet-libyaml 0.2.5 diff --git a/recipes/pyyaml/test_pyyaml.py b/recipes/pyyaml/test_pyyaml.py new file mode 100644 index 00000000..62b451ad --- /dev/null +++ b/recipes/pyyaml/test_pyyaml.py @@ -0,0 +1,42 @@ +def test_basic(): + """Round-trip a small document through PyYAML's C-loader and C-dumper.""" + import yaml + + doc = { + "name": "mobile-forge", + "components": ["recipes", "tests", "ci"], + "android": {"api": 24, "abi": ["arm64-v8a", "x86_64"]}, + "iOS": {"min": "13.0"}, + } + text = yaml.safe_dump(doc, sort_keys=True) + assert yaml.safe_load(text) == doc + + +def test_c_extension(): + """The C accelerator (_yaml) is what this recipe primarily exists for. + + PyYAML exposes `CSafeDumper`/`CSafeLoader` only when the `_yaml` C + extension successfully imports — otherwise they're simply absent + from the `yaml` package namespace (no exception, no None — just + missing names). Probe by importing `_yaml` and checking it carries + the Cython-emitted `CParser` class. That assertion fires both when + the .so was never shipped AND when libyaml fails to load at import + time on the device.""" + import _yaml + + assert hasattr(_yaml, "CParser"), ( + "PyYAML's _yaml C extension loaded but is missing CParser — " + "libyaml probably failed to load at import time" + ) + + +def test_csafedumper_binding(): + """The user-facing surface: `from yaml import CSafeDumper, CSafeLoader`. + + Functionally subsumed by test_c_extension (cyaml.py exposes these + classes iff `_yaml.CParser` exists), but kept as a separate test + because (a) this is the import shape real apps break on and (b) a + clean ImportError here points a future debugger straight at the + `_yaml`/libyaml chain instead of an obscure attribute-missing + surprise downstream.""" + from yaml import CSafeDumper, CSafeLoader # noqa: F401 diff --git a/recipes/rasterio/meta.yaml b/recipes/rasterio/meta.yaml new file mode 100644 index 00000000..4ced6b06 --- /dev/null +++ b/recipes/rasterio/meta.yaml @@ -0,0 +1,25 @@ +package: + name: rasterio + version: 1.5.0 + +requirements: + host: + - flet-libgdal 3.10.0 + - numpy ^2.0.0 +# {% if sdk == 'android' %} + - flet-libcpp-shared >=27.2.12479018 +# {% endif %} + +build: + number: 2 + script_env: + GDAL_VERSION: 3.10.0 + GDAL_LIB_PATH: '{platlib}/opt/lib' + GDAL_INCLUDE_PATH: '{platlib}/opt/include' + GDAL_LIBS: gdal +# {% if sdk != 'android' %} + LDFLAGS: '-undefined dynamic_lookup' +# {% endif %} + +patches: + - mobile.patch diff --git a/recipes/rasterio/patches/mobile.patch b/recipes/rasterio/patches/mobile.patch new file mode 100644 index 00000000..ddb84079 --- /dev/null +++ b/recipes/rasterio/patches/mobile.patch @@ -0,0 +1,17 @@ +diff --git a/setup.py b/setup.py +--- a/setup.py ++++ b/setup.py +@@ -251,6 +251,13 @@ + e, + ) + ++ if 'GDAL_LIB_PATH' in os.environ: ++ library_dirs.extend(os.environ['GDAL_LIB_PATH'].split(":")) ++ if 'GDAL_INCLUDE_PATH' in os.environ: ++ include_dirs.extend(os.environ['GDAL_INCLUDE_PATH'].split(":")) ++ if 'GDAL_LIBS' in os.environ: ++ libraries.extend(os.environ['GDAL_LIBS'].split(",")) ++ + # Get GDAL API version from environment variable. + if 'GDAL_VERSION' in os.environ: + gdalversion = os.environ['GDAL_VERSION'] diff --git a/recipes/regex/meta.yaml b/recipes/regex/meta.yaml new file mode 100644 index 00000000..3d6ac1b0 --- /dev/null +++ b/recipes/regex/meta.yaml @@ -0,0 +1,6 @@ +package: + name: regex + version: 2024.11.6 + +build: + number: 4 diff --git a/recipes/rpds-py/meta.yaml b/recipes/rpds-py/meta.yaml new file mode 100644 index 00000000..12297f6b --- /dev/null +++ b/recipes/rpds-py/meta.yaml @@ -0,0 +1,8 @@ +package: + name: rpds-py + version: 0.23.1 + +build: + number: 4 + script_env: + _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' \ No newline at end of file diff --git a/recipes/ruamel.yaml.clib/meta.yaml b/recipes/ruamel.yaml.clib/meta.yaml new file mode 100644 index 00000000..0a2e8158 --- /dev/null +++ b/recipes/ruamel.yaml.clib/meta.yaml @@ -0,0 +1,6 @@ +package: + name: ruamel.yaml.clib + version: 0.2.12 + +build: + number: 1 diff --git a/recipes/selectolax/meta.yaml b/recipes/selectolax/meta.yaml new file mode 100644 index 00000000..7d482f29 --- /dev/null +++ b/recipes/selectolax/meta.yaml @@ -0,0 +1,6 @@ +package: + name: selectolax + version: 0.4.10 + +build: + number: 0 diff --git a/recipes/selectolax/test_selectolax.py b/recipes/selectolax/test_selectolax.py new file mode 100644 index 00000000..a7e6f17e --- /dev/null +++ b/recipes/selectolax/test_selectolax.py @@ -0,0 +1,16 @@ +def test_modest_parser(): + """Parse HTML + CSS-select with the Modest engine.""" + from selectolax.parser import HTMLParser + + tree = HTMLParser("

hello

world

") + nodes = tree.css("p.x") + assert [n.text() for n in nodes] == ["hello", "world"] + + +def test_lexbor_parser(): + """Parse HTML + CSS-select with the Lexbor engine.""" + from selectolax.lexbor import LexborHTMLParser + + tree = LexborHTMLParser("
  • a
  • b
  • c
") + items = tree.css("li") + assert [n.text() for n in items] == ["a", "b", "c"] diff --git a/recipes/shapely/meta.yaml b/recipes/shapely/meta.yaml new file mode 100644 index 00000000..b50c9594 --- /dev/null +++ b/recipes/shapely/meta.yaml @@ -0,0 +1,14 @@ +package: + name: shapely + version: 2.0.6 + +build: + number: 4 + +requirements: + host: + - flet-libgeos 3.13.0 + - numpy ^2.0.0 + +patches: + - mobile.patch \ No newline at end of file diff --git a/recipes/shapely/patches/mobile.patch b/recipes/shapely/patches/mobile.patch new file mode 100644 index 00000000..43aa6df8 --- /dev/null +++ b/recipes/shapely/patches/mobile.patch @@ -0,0 +1,75 @@ +diff --git a/setup.py b/setup.py +index d49d722..ca3c433 100644 +--- a/setup.py ++++ b/setup.py +@@ -73,39 +73,39 @@ def get_geos_paths(): + "libraries": ["geos_c"], + } + +- geos_version = get_geos_config("--version") +- if not geos_version: +- log.warning( +- "Could not find geos-config executable. Either append the path to geos-config" +- " to PATH or manually provide the include_dirs, library_dirs, libraries and " +- "other link args for compiling against a GEOS version >=%s.", +- MIN_GEOS_VERSION, +- ) +- return {} +- +- def version_tuple(ver): +- return tuple(int(itm) if itm.isnumeric() else itm for itm in ver.split(".")) +- +- if version_tuple(geos_version) < version_tuple(MIN_GEOS_VERSION): +- raise ImportError( +- f"GEOS version should be >={MIN_GEOS_VERSION}, found {geos_version}" +- ) +- +- libraries = [] ++ # geos_version = get_geos_config("--version") ++ # if not geos_version: ++ # log.warning( ++ # "Could not find geos-config executable. Either append the path to geos-config" ++ # " to PATH or manually provide the include_dirs, library_dirs, libraries and " ++ # "other link args for compiling against a GEOS version >=%s.", ++ # MIN_GEOS_VERSION, ++ # ) ++ # return {} ++ ++ # def version_tuple(ver): ++ # return tuple(int(itm) if itm.isnumeric() else itm for itm in ver.split(".")) ++ ++ # if version_tuple(geos_version) < version_tuple(MIN_GEOS_VERSION): ++ # raise ImportError( ++ # f"GEOS version should be >={MIN_GEOS_VERSION}, found {geos_version}" ++ # ) ++ ++ libraries = ["geos", "geos_c"] + library_dirs = [] + include_dirs = ["./src"] +- extra_link_args = [] +- for item in get_geos_config("--cflags").split(): +- if item.startswith("-I"): +- include_dirs.extend(item[2:].split(":")) +- +- for item in get_geos_config("--clibs").split(): +- if item.startswith("-L"): +- library_dirs.extend(item[2:].split(":")) +- elif item.startswith("-l"): +- libraries.append(item[2:]) +- else: +- extra_link_args.append(item) ++ extra_link_args = ["-undefined", "dynamic_lookup"] if sys.platform == "ios" else [] ++ # for item in get_geos_config("--cflags").split(): ++ # if item.startswith("-I"): ++ # include_dirs.extend(item[2:].split(":")) ++ ++ # for item in get_geos_config("--clibs").split(): ++ # if item.startswith("-L"): ++ # library_dirs.extend(item[2:].split(":")) ++ # elif item.startswith("-l"): ++ # libraries.append(item[2:]) ++ # else: ++ # extra_link_args.append(item) + + return { + "include_dirs": include_dirs, diff --git a/recipes/sqlalchemy/meta.yaml b/recipes/sqlalchemy/meta.yaml new file mode 100644 index 00000000..6fe0f3e2 --- /dev/null +++ b/recipes/sqlalchemy/meta.yaml @@ -0,0 +1,6 @@ +package: + name: sqlalchemy + version: 2.0.36 + +build: + number: 4 diff --git a/recipes/tiktoken/meta.yaml b/recipes/tiktoken/meta.yaml new file mode 100644 index 00000000..3300bb0f --- /dev/null +++ b/recipes/tiktoken/meta.yaml @@ -0,0 +1,8 @@ +package: + name: tiktoken + version: 0.9.0 + +build: + number: 4 + script_env: + _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' \ No newline at end of file diff --git a/recipes/time-machine/meta.yaml b/recipes/time-machine/meta.yaml new file mode 100644 index 00000000..caec2e55 --- /dev/null +++ b/recipes/time-machine/meta.yaml @@ -0,0 +1,6 @@ +package: + name: time-machine + version: 2.16.0 + +build: + number: 4 diff --git a/recipes/tokenizers/meta.yaml b/recipes/tokenizers/meta.yaml new file mode 100644 index 00000000..2562faab --- /dev/null +++ b/recipes/tokenizers/meta.yaml @@ -0,0 +1,8 @@ +package: + name: tokenizers + version: 0.21.0 + +build: + number: 4 + script_env: + _PYTHON_SYSCONFIGDATA_NAME: '{sysconfigdata_name}' \ No newline at end of file diff --git a/recipes/ujson/meta.yaml b/recipes/ujson/meta.yaml new file mode 100644 index 00000000..96af0659 --- /dev/null +++ b/recipes/ujson/meta.yaml @@ -0,0 +1,17 @@ +package: + name: ujson + version: 5.12.1 + +build: + number: 1 + +# {% if sdk == 'android' %} +# ujson links its bundled double-conversion C++ code against NDK's libc++. +# NDK r27 defaults to libc++_shared, so the .so has a runtime dlopen() on +# libc++_shared.so. flet-libcpp-shared ships that .so to jniLibs// via +# serious_python_android/android/build.gradle's copyOpt_ task. +# iOS uses Apple's system libc++ — no runtime dep needed. +requirements: + host: + - flet-libcpp-shared >=27.2.12479018 +# {% endif %} \ No newline at end of file diff --git a/recipes/ujson/test_ujson.py b/recipes/ujson/test_ujson.py new file mode 100644 index 00000000..93ff57c2 --- /dev/null +++ b/recipes/ujson/test_ujson.py @@ -0,0 +1,23 @@ +def test_basic(): + import ujson + + data = { + "name": "flet", + "version": 1, + "active": True, + "tags": ["mobile", "python"], + "ratio": 3.141592653589793, + "nothing": None, + } + encoded = ujson.dumps(data) + assert isinstance(encoded, str) + + decoded = ujson.loads(encoded) + assert decoded == data + + +def test_double_conversion(): + import ujson + + pi = 3.141592653589793 + assert ujson.loads(ujson.dumps(pi)) == pi diff --git a/recipes/websockets/meta.yaml b/recipes/websockets/meta.yaml index 7e331d03..fdc85c2a 100644 --- a/recipes/websockets/meta.yaml +++ b/recipes/websockets/meta.yaml @@ -1,3 +1,6 @@ package: name: websockets - version: '12.0' \ No newline at end of file + version: 13.0.1 + +build: + number: 4 diff --git a/recipes/yarl/meta.yaml b/recipes/yarl/meta.yaml index ed7d6881..f4f15591 100644 --- a/recipes/yarl/meta.yaml +++ b/recipes/yarl/meta.yaml @@ -1,7 +1,10 @@ package: name: yarl - version: 1.9.4 + version: 1.11.1 + +build: + number: 4 requirements: build: - - cython 3.0.7 \ No newline at end of file + - cython \ No newline at end of file diff --git a/recipes/zope.interface/meta.yaml b/recipes/zope.interface/meta.yaml new file mode 100644 index 00000000..5bb53148 --- /dev/null +++ b/recipes/zope.interface/meta.yaml @@ -0,0 +1,6 @@ +package: + name: zope.interface + version: '7.2' + +build: + number: 4 diff --git a/recipes/zstandard/meta.yaml b/recipes/zstandard/meta.yaml new file mode 100644 index 00000000..d194e1f4 --- /dev/null +++ b/recipes/zstandard/meta.yaml @@ -0,0 +1,6 @@ +package: + name: zstandard + version: 0.23.0 + +build: + number: 4 diff --git a/setup.sh b/setup.sh index a4c87f6f..1201bc0b 100755 --- a/setup.sh +++ b/setup.sh @@ -5,7 +5,7 @@ usage() { echo echo "for example:" echo - echo " source $1 3.12" + echo " source $1 3.13" echo } @@ -24,72 +24,42 @@ if [ -z "$1" ]; then return fi -PYTHON_URL_PREFIX=https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3+20240415 +if ! command -v uv &> /dev/null; then + echo "Error: uv is not installed. Install it with:" + echo " curl -LsSf https://astral.sh/uv/install.sh | sh" + return +fi PYTHON_VERSION=$1 -read python_version_major python_version_minor < <(echo $PYTHON_VERSION | sed -E 's/^([0-9]+)\.([0-9]+).*/\1 \2/') -PYTHON_VER=$python_version_major.$python_version_minor -CMAKE_VERSION="3.27.4" +PYTHON_VER="${PYTHON_VERSION%.*}" echo "Python version: $PYTHON_VERSION" echo "Python short version: $PYTHON_VER" -if [[ -z "$PYTHON_APPLE_SUPPORT" && -z "$PYTHON_ANDROID_SUPPORT" ]]; then - echo "Neither PYTHON_APPLE_SUPPORT nor PYTHON_ANDROID_SUPPORT are defined." +if [[ -z "$MOBILE_FORGE_IOS_SUPPORT_PATH" && -z "$MOBILE_FORGE_ANDROID_SUPPORT_PATH" ]]; then + echo "Neither MOBILE_FORGE_IOS_SUPPORT_PATH nor MOBILE_FORGE_ANDROID_SUPPORT_PATH are defined." return fi -if [ ! -z "$VIRTUAL_ENV" ]; then - deactivate -fi - venv_dir="$(pwd)/venv$PYTHON_VER" if [ ! -d $venv_dir ]; then echo "Creating Python $PYTHON_VER virtual environment for build in $venv_dir..." - if ! [ -d "tools/python" ]; then - if [ $(uname) = "Darwin" ]; then - # macOS - if [ $(uname -m) = "arm64" ]; then - PYTHON_SUFFIX="aarch64-apple-darwin-install_only.tar.gz" - else - PYTHON_SUFFIX="x86_64-apple-darwin-install_only.tar.gz" - fi - else - # Linux - if [ $(uname -m) = "arm64" ]; then - PYTHON_SUFFIX="aarch64-unknown-linux-gnu-install_only.tar.gz" - else - PYTHON_SUFFIX="x86_64_v3-unknown-linux-gnu-install_only.tar.gz" - fi - fi - - if ! [ -f "downloads/python-${PYTHON_VERSION}-${PYTHON_SUFFIX}" ]; then - echo "Downloading Python ${PYTHON_VERSION}" - mkdir -p downloads - curl --location --progress-bar "${PYTHON_URL_PREFIX}-${PYTHON_SUFFIX}" --output "downloads/python-${PYTHON_VERSION}-${PYTHON_SUFFIX}" - fi - - mkdir -p tools - tar -xzf "downloads/python-${PYTHON_VERSION}-${PYTHON_SUFFIX}" -C tools - fi - - tools/python/bin/python -m venv $venv_dir + uv venv --seed --python="$PYTHON_VERSION" $venv_dir source $venv_dir/bin/activate - pip install -U pip - pip install -e . wheel + uv pip install -e . echo "Building platform dependency wheels..." - if [ ! -z "$PYTHON_APPLE_SUPPORT" ]; then + if [ ! -z "$MOBILE_FORGE_IOS_SUPPORT_PATH" ]; then python -m make_dep_wheels iOS if [ $? -ne 0 ]; then return fi fi - if [ ! -z "$PYTHON_ANDROID_SUPPORT" ]; then + if [ ! -z "$MOBILE_FORGE_ANDROID_SUPPORT_PATH" ]; then python -m make_dep_wheels android if [ $? -ne 0 ]; then return @@ -104,112 +74,51 @@ else fi # configure iOS paths -if [ ! -z "$PYTHON_APPLE_SUPPORT" ]; then - - if [ ! -d $PYTHON_APPLE_SUPPORT/install ]; then - echo "PYTHON_APPLE_SUPPORT does not point at a valid location." - return - fi +if [ ! -z "$MOBILE_FORGE_IOS_SUPPORT_PATH" ]; then - if [ ! -e $PYTHON_APPLE_SUPPORT/install/iOS/iphoneos.arm64/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "PYTHON_APPLE_SUPPORT does not appear to contain a Python $PYTHON_VERSION iOS ARM64 device binary." + if [ ! -d $MOBILE_FORGE_IOS_SUPPORT_PATH/support/$PYTHON_VER/iOS/Python.xcframework ]; then + echo "MOBILE_FORGE_IOS_SUPPORT_PATH does not point at a valid location." return fi - if [ ! -e $PYTHON_APPLE_SUPPORT/install/iOS/iphonesimulator.arm64/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "PYTHON_APPLE_SUPPORT does not appear to contain a Python $PYTHON_VERSION iOS ARM64 simulator binary." + if [ ! -e $MOBILE_FORGE_IOS_SUPPORT_PATH/support/$PYTHON_VER/iOS/Python.xcframework/ios-arm64/bin/python$PYTHON_VER ]; then + echo "MOBILE_FORGE_IOS_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION iOS ARM64 device binary." return fi - if [ ! -e $PYTHON_APPLE_SUPPORT/install/iOS/iphonesimulator.x86_64/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "PYTHON_APPLE_SUPPORT does not appear to contain a Python $PYTHON_VERSION iOS x86-64 simulator binary." + if [ ! -e $MOBILE_FORGE_IOS_SUPPORT_PATH/support/$PYTHON_VER/iOS/Python.xcframework/ios-arm64_x86_64-simulator/bin/python$PYTHON_VER ]; then + echo "MOBILE_FORGE_IOS_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION iOS ARM64/x86_64 simulator binaries." return fi - echo "PYTHON_APPLE_SUPPORT: $PYTHON_APPLE_SUPPORT" - - export MOBILE_FORGE_IPHONEOS_ARM64=$PYTHON_APPLE_SUPPORT/install/iOS/iphoneos.arm64/python-$PYTHON_VERSION/bin/python$PYTHON_VER - export MOBILE_FORGE_IPHONESIMULATOR_ARM64=$PYTHON_APPLE_SUPPORT/install/iOS/iphonesimulator.arm64/python-$PYTHON_VERSION/bin/python$PYTHON_VER - export MOBILE_FORGE_IPHONESIMULATOR_X86_64=$PYTHON_APPLE_SUPPORT/install/iOS/iphonesimulator.x86_64/python-$PYTHON_VERSION/bin/python$PYTHON_VER - - export PATH="$PATH:$PYTHON_APPLE_SUPPORT/support/$PYTHON_VER/iOS/bin" + echo "MOBILE_FORGE_IOS_SUPPORT_PATH: $MOBILE_FORGE_IOS_SUPPORT_PATH" fi # configure Android paths -if [ ! -z "$PYTHON_ANDROID_SUPPORT" ]; then - if [ ! -e $PYTHON_ANDROID_SUPPORT/install/android/arm64-v8a/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "PYTHON_ANDROID_SUPPORT does not appear to contain a Python $PYTHON_VERSION Android arm64-v8a device binary." +if [ ! -z "$MOBILE_FORGE_ANDROID_SUPPORT_PATH" ]; then + if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/arm64-v8a/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then + echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android arm64-v8a device binary." return fi - if [ ! -e $PYTHON_ANDROID_SUPPORT/install/android/armeabi-v7a/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "PYTHON_ANDROID_SUPPORT does not appear to contain a Python $PYTHON_VERSION Android armeabi-v7a device binary." + if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/x86_64/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then + echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android x86_64 device binary." return fi - if [ ! -e $PYTHON_ANDROID_SUPPORT/install/android/x86_64/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "PYTHON_ANDROID_SUPPORT does not appear to contain a Python $PYTHON_VERSION Android x86_64 device binary." - return - fi - - if [ ! -e $PYTHON_ANDROID_SUPPORT/install/android/x86/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "PYTHON_ANDROID_SUPPORT does not appear to contain a Python $PYTHON_VERSION Android x86 device binary." - return - fi - - echo "PYTHON_ANDROID_SUPPORT: $PYTHON_ANDROID_SUPPORT" - - export MOBILE_FORGE_ANDROID_ARM64_V8A=$PYTHON_ANDROID_SUPPORT/install/android/arm64-v8a/python-$PYTHON_VERSION/bin/python$PYTHON_VER - export MOBILE_FORGE_ANDROID_ARMEABI_V7A=$PYTHON_ANDROID_SUPPORT/install/android/armeabi-v7a/python-$PYTHON_VERSION/bin/python$PYTHON_VER - export MOBILE_FORGE_ANDROID_X86_64=$PYTHON_ANDROID_SUPPORT/install/android/x86_64/python-$PYTHON_VERSION/bin/python$PYTHON_VER - export MOBILE_FORGE_ANDROID_X86=$PYTHON_ANDROID_SUPPORT/install/android/x86/python-$PYTHON_VERSION/bin/python$PYTHON_VER -fi - -# Ensure CMake is installed -if [ $(uname) = "Darwin" ]; then - if ! [ -d "tools/CMake.app" ]; then - if ! [ -f "downloads/cmake-${CMAKE_VERSION}-macos-universal.tar.gz" ]; then - echo "Downloading CMake" - mkdir -p downloads - curl --location --progress-bar "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-macos-universal.tar.gz" --output downloads/cmake-${CMAKE_VERSION}-macos-universal.tar.gz + if [ "$PYTHON_VER" = "3.12" ]; then + if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/armeabi-v7a/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then + echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android armeabi-v7a device binary." + return fi - echo "Installing CMake" - mkdir -p tools - tar -xzf downloads/cmake-${CMAKE_VERSION}-macos-universal.tar.gz - mv cmake-${CMAKE_VERSION}-macos-universal/CMake.app tools - rm -rf cmake-${CMAKE_VERSION}-macos-universal - fi - export PATH="$PATH:$(pwd)/tools/CMake.app/Contents/bin" -else - if ! [ -d "tools/cmake" ]; then - if ! [ -f "downloads/cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz" ]; then - echo "Downloading CMake" - mkdir -p downloads - curl --location --progress-bar "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz" --output downloads/cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz + if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/x86/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then + echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android x86 device binary." + return fi - - echo "Installing CMake" - mkdir -p tools - tar -xzf downloads/cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz - mv cmake-${CMAKE_VERSION}-linux-x86_64/bin tools/cmake - rm -rf cmake-${CMAKE_VERSION}-linux-x86_64 fi - export PATH="$PATH:$(pwd)/tools/cmake" -fi - -# Create wheels for ninja that can be installed in the host environment -if ! [ -f "dist/ninja-1.11.1-py3-none-any.whl" ]; then - echo "Downloading Ninja" - python -m pip wheel --no-deps -w dist ninja==1.11.1 - mv dist/ninja-1.11.1-*.whl dist/ninja-1.11.1-py3-none-any.whl -fi -# Create wheels for cmake that can be installed in the host environment -if ! [ -f "dist/cmake-3.29.6-py3-none-any.whl" ]; then - echo "Downloading CMake" - python -m pip wheel --no-deps -w dist cmake==3.29.6 - mv dist/cmake-3.29.6-*.whl dist/cmake-3.29.6-py3-none-any.whl + echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH: $MOBILE_FORGE_ANDROID_SUPPORT_PATH" fi echo @@ -232,4 +141,4 @@ echo " forge iphoneos:arm64 lru-dict" echo echo "Build all applicable versions of lru-dict for all iOS targets:" echo " forge iOS --all-versions lru-dict" -echo \ No newline at end of file +echo diff --git a/src/forge/__main__.py b/src/forge/__main__.py index 7a4678c5..17ed8f14 100644 --- a/src/forge/__main__.py +++ b/src/forge/__main__.py @@ -99,83 +99,7 @@ def main(): print() sys.exit(1) - # Targets that generate py3-none-any wheels only need to be built on a single - # platform. - py_any_targets = [ - "oldest-supported-numpy", - ] - - if not args.build_targets: - build_targets = [] - - if args.subset in {"all", "py-any", "smoke"}: - build_targets.extend(py_any_targets) - - if args.subset in {"all", "non-py", "smoke", "smoke-non-py"}: - build_targets.extend( - [ - "libjpeg", - "freetype", - ] - ) - - if args.subset in {"all", "non-py", "non-smoke"}: - build_targets.extend( - [ - "libpng", - ] - ) - - # Pandas uses a meta-package called "oldest-supported-numpy" which installs, - # predictably, the oldest version of numpy known to work on a given Python - # version. This is done for Python ABI compatibility. - oldest_supported_numpy = { - 8: "numpy:1.17.3", - 9: "numpy:1.19.3", - 10: "numpy:1.21.6", - 11: "numpy:1.23.2", - 12: "numpy:1.26.0", - 13: "numpy:1.26.0", - }[sys.version_info.minor] - - if args.subset in {"all", "py", "smoke", "smoke-py"}: - build_targets.extend( - [ - "lru-dict", - "pillow", - "numpy", - ] - # On Python 3.12 and 3.13, the oldest supported numpy *is* the only version of - # numpy that is supported. - + ( - [ - oldest_supported_numpy, - ] - if sys.version_info.minor in {12, 13} - else [] - ) - + [ - "pandas", - "cffi", - "cryptography", - ] - ) - - if args.subset in {"all", "py", "non-smoke"}: - build_targets.extend( - [ - "aiohttp", - "argon2-cffi", - "bcrypt", - "bitarray", - "blis", - "brotli", - "typed-ast", - "yarl", - ] - ) - else: - build_targets = args.build_targets + build_targets = args.build_targets or [] successes = [] failures = [] @@ -218,11 +142,7 @@ def main(): # subsequent builds will be isolated by first = True - # Packages that generate -py3-none-any wheels only need to be built on a single platform. - if package_name_or_recipe in py_any_targets: - build_platforms = platforms[:1] - else: - build_platforms = platforms + build_platforms = platforms # Build the package for each required platform. for sdk, sdk_version, arch in build_platforms: diff --git a/src/forge/build.py b/src/forge/build.py index db9b32ae..ef7da5c5 100644 --- a/src/forge/build.py +++ b/src/forge/build.py @@ -4,11 +4,12 @@ import os import re import shutil +import struct import sys import tarfile import zipfile -from abc import ABC, abstractmethod -from email import generator, message +from abc import ABC, abstractmethod, abstractproperty +from email import generator, message, parser from pathlib import Path from typing import TYPE_CHECKING @@ -64,8 +65,10 @@ def install_requirements(self, target): requirements = [] for requirement in self.package.meta["requirements"][target]: try: - package, version = requirement.split() - if version.startswith("^"): + package, version = requirement.split(maxsplit=1) + if version.startswith((">=", "<=", "!=", "==", "~=", ">", "<")): + specifier = f"{package}{version}" + elif version.startswith("^"): specifier = f"{package}>={version[1:]}" elif version.startswith("~"): specifier = f"{package}~={version[1:]}" @@ -79,12 +82,51 @@ def install_requirements(self, target): self.cross_venv.pip_install( self.log_file, requirements, - wheels_path=Path.cwd() / "dist", + paths=[Path.cwd() / "dist"], build=target == "build", ) else: log(self.log_file, f"No {target} requirements.") + def fix_host_tool_shims(self): + python_path = ( + self.cross_venv.venv_path + / "cross" + / "bin" + / f"python3.{sys.version_info.minor}" + ) + for shim in (self.cross_venv.venv_path / "cross" / "bin").iterdir(): + with open(shim, "r") as f: + lines = f.readlines() + if len(lines) > 0 and lines[0].strip() == f"#!{python_path}": + log(self.log_file, f"Fixing host shim: {shim}") + with open(shim, "w") as f: + f.writelines( + [ + "#!/bin/sh\n", + "'''exec' {} \"$0\" \"$@\"\n".format(python_path), + "' '''\n\n", + ] + + lines[1:] + ) + elif ( + len(lines) > 2 + and lines[0].strip() == "#!/bin/sh" + and lines[1].startswith("'''exec' ") + and lines[2].startswith("' '''") + and lines[2].strip() != "' '''" + ): + # Repair legacy malformed shim output where the separator line was + # accidentally merged with Python code (e.g. "' '''import sys"). + log(self.log_file, f"Repairing malformed host shim: {shim}") + suffix = lines[2][len("' '''") :] + repaired = [lines[0], lines[1], "' '''\n"] + if suffix: + repaired.append(suffix) + repaired += lines[3:] + with open(shim, "w") as f: + f.writelines(repaired) + @abstractmethod def download_source_url(self): ... @@ -106,6 +148,13 @@ def unpack_source(self): self.log_file, f"Unpacking {self.source_archive_path.relative_to(Path.cwd())}...", ) + # Determine the stripping level. By default, this is 1; + # but some source types can override. + try: + strip = self.package.meta["source"]["strip"] + except (TypeError, KeyError): + strip = 1 + # Some packages (e.g., brotli) have uploaded a .tar.gz file... that is # actually a zipfile (!). if tarfile.is_tarfile(self.source_archive_path): @@ -123,7 +172,7 @@ def members(tf: tarfile.TarFile, strip=1): with tarfile.open(self.source_archive_path) as tf: tf.extractall( path=self.build_path, - members=members(tf, strip=1), + members=members(tf, strip=strip) if strip else None, ) elif zipfile.is_zipfile(self.source_archive_path): # Strip the top level folder. @@ -141,7 +190,7 @@ def members(zf, strip=1): zf.extractall( path=self.build_path, - members=members(zf, strip=1), + members=members(zf, strip=strip) if strip else None, ) else: raise RuntimeError( @@ -160,7 +209,14 @@ def patch_source(self): # not anything dependent on the Python environment. subprocess.run( self.log_file, - ["patch", "-p1", "--ignore-whitespace", "--input", str(patchfile)], + [ + "patch", + "-p1", + "--ignore-whitespace", + "--quiet", + "--input", + str(patchfile), + ], cwd=self.build_path, ) patched = True @@ -178,7 +234,10 @@ def prepare(self, clean=True): ) shutil.rmtree(self.build_path) - if not self.source_archive_path.is_file(): + if ( + os.getenv(f"MOBILE_FORGE_CACHE_DOWNLOADS_OFF") + or not self.source_archive_path.is_file() + ): log(self.log_file, f"\n[{self.cross_venv}] Download package sources") self.download_source() @@ -195,6 +254,7 @@ def prepare(self, clean=True): log(self.log_file, f"\n[{self.cross_venv}] Install forge host requirements") self.install_requirements("host") + self.fix_host_tool_shims() log(self.log_file, f"\n[{self.cross_venv}] Install forge build requirements") self.install_requirements("build") @@ -206,21 +266,17 @@ def compile_env(self, **kwargs) -> dict[str, str]: ar = sysconfig_data["AR"] cc = sysconfig_data["CC"] cxx = sysconfig_data["CXX"] - strip = ( - str(Path(ar).parent.joinpath("llvm-strip")) - if ar and self.cross_venv.sdk == "android" - else "strip" - ) - + strip = "strip" + ranlib = "ranlib" cflags = self.cross_venv.sysconfig_data["CFLAGS"] cppflags = self.cross_venv.sysconfig_data["CPPFLAGS"] + ndk_sysroot = None # Add install root include if (install_root / "include").is_dir(): cflags += f" -I{install_root}/include" if self.cross_venv.sdk != "android": - # Pre Python 3.11 versions included BZip2 and XZ includes in CFLAGS. Remove them. cflags = re.sub(r"-I.*/merge/iOS/.*/bzip2-.*/include", "", cflags) cflags = re.sub(r"-I.*/merge/iOS/.*/xs-.*/include", "", cflags) @@ -234,6 +290,45 @@ def compile_env(self, **kwargs) -> dict[str, str]: if (self.cross_venv.sdk_root / "usr" / "include").is_dir(): cflags += f" -I{self.cross_venv.sdk_root}/usr/include" + cppflags += f" -mios-version-min={self.cross_venv.sdk_version}" + else: + # Some Python Android support archives reference an embedded NDK path + # that isn't present in CI. If NDK_HOME is set, re-point missing + # compiler/binutils paths to that installed NDK toolchain. + ndk_home = os.environ.get("NDK_HOME") + if ndk_home: + prebuilt_dirs = list( + (Path(ndk_home) / "toolchains" / "llvm" / "prebuilt").glob("*") + ) + if prebuilt_dirs: + ndk_bin = prebuilt_dirs[0] / "bin" + if not Path(cc).is_file(): + cc = str(ndk_bin / Path(cc).name) + if not Path(cxx).is_file(): + cxx = str(ndk_bin / Path(cxx).name) + if not Path(ar).is_file(): + ar = str(ndk_bin / Path(ar).name) + if not Path(strip).is_file(): + strip = str(ndk_bin / "llvm-strip") + if not Path(ranlib).is_file(): + ranlib = str(ndk_bin / "llvm-ranlib") + + # Derive strip/ranlib from the final AR location when available. + if ar: + ar_parent = Path(ar).parent + derived_strip = ar_parent / "llvm-strip" + derived_ranlib = ar_parent / "llvm-ranlib" + if derived_strip.is_file(): + strip = str(derived_strip) + if derived_ranlib.is_file(): + ranlib = str(derived_ranlib) + ndk_sysroot = Path(cc).parent.parent / "sysroot" + if (ndk_sysroot / "usr" / "include").is_dir(): + cflags += f" -I{ndk_sysroot}/usr/include" + if self.cross_venv.sdk != "android": + strip = "strip" + ranlib = "ranlib" + ldflags = self.cross_venv.sysconfig_data["LDFLAGS"] # -lpython3.x @@ -243,12 +338,34 @@ def compile_env(self, **kwargs) -> dict[str, str]: if (install_root / "lib").is_dir(): ldflags += f" -L{install_root}/lib" + if self.cross_venv.sdk == "android" and ndk_sysroot: + ndk_triplet_lib = ( + ndk_sysroot + / "usr" + / "lib" + / self.cross_venv.platform_triplet + / str(self.cross_venv.sdk_version) + ) + ndk_arch_lib = ( + ndk_sysroot / "usr" / "lib" / self.cross_venv.platform_triplet + ) + if ndk_triplet_lib.is_dir(): + ldflags += f" -L{ndk_triplet_lib}" + elif ndk_arch_lib.is_dir(): + ldflags += f" -L{ndk_arch_lib}" + + # 16 KB page alignment required by Google Play (Android 15+) + ldflags += " -Wl,-z,max-page-size=16384" + # cargo_ldflags = re.sub(r"-march=[\w-]+", "", ldflags) cargo_ldflags = " -L{}/lib".format(self.cross_venv.sysconfig_data["prefix"]) cargo_ldflags += " -C link-arg=-undefined -C link-arg=dynamic_lookup" - if self.cross_venv.sdk != "android": + if self.cross_venv.sdk == "android": + # 16 KB page alignment required by Google Play (Android 15+) + cargo_ldflags += " -C link-arg=-z -C link-arg=max-page-size=16384" + if self.cross_venv.sdk != "android": # Replace any hard-coded reference to -isysroot with the actual reference ldflags = re.sub( r"-isysroot \w+", f"-isysroot={self.cross_venv.sdk_root}", ldflags @@ -258,6 +375,10 @@ def compile_env(self, **kwargs) -> dict[str, str]: if (self.cross_venv.sdk_root / "usr" / "lib").is_dir(): ldflags += f" -L{self.cross_venv.sdk_root}/usr/lib" + # Add the framework path + ldflags += f' -F "{self.cross_venv.host_python_home}"' + cargo_ldflags += f" -C link-arg=-F{self.cross_venv.host_python_home} -C link-arg=-framework -C link-arg=Python" + cargo_build_target = ( { "arm64-apple-ios": "aarch64-apple-ios", @@ -275,6 +396,7 @@ def compile_env(self, **kwargs) -> dict[str, str]: "CC": cc, "CXX": cxx, "STRIP": strip, + "RANLIB": ranlib, "CFLAGS": cflags, "CPPFLAGS": cppflags, "LDFLAGS": ldflags, @@ -289,8 +411,14 @@ def compile_env(self, **kwargs) -> dict[str, str]: "PYO3_CROSS_PYTHON_VERSION": self.cross_venv.sysconfig_data[ "py_version_short" ], - "PYO3_CROSS_LIB_DIR": "{}/lib".format( - self.cross_venv.sysconfig_data["prefix"] + # pyo3 expects a directory containing _sysconfigdata__*.py. + # Newer Apple support layouts place this outside prefix/lib. + "PYO3_CROSS_LIB_DIR": str( + ( + self.cross_venv.host_sysconfig.parent + if self.cross_venv.host_sysconfig is not None + else Path(self.cross_venv.sysconfig_data["prefix"]) / "lib" + ) ), } env.update(kwargs) @@ -298,7 +426,26 @@ def compile_env(self, **kwargs) -> dict[str, str]: if self.cross_venv.sdk == "android": cc_parts = cc.split("/") env["NDK_ROOT"] = "/".join(cc_parts[: cc_parts.index("toolchains")]) + env["NDK_SYSROOT"] = str( + ndk_sysroot or (Path(cc).parent.parent / "sysroot") + ) env["ANDROID_ABI"] = self.cross_venv.arch + env["ANDROID_API_LEVEL"] = str(self.cross_venv.sdk_version) + env["HOST_TRIPLET"] = self.cross_venv.platform_triplet + + script_vars = { + **env, + **self.cross_venv.scheme_paths, + **self.cross_venv.sysconfig_data, + "sysconfigdata_name": self.cross_venv.sysconfigdata_name, + } + + # Set up any additional environment variables needed in the script environment. + for key, value in self.package.meta["build"]["script_env"].items(): + if key in ["LDFLAGS", "CFLAGS", "CPPFLAGS"]: + env[key] += " " + str(value).format(**script_vars) + else: + env[key] = str(value).format(**script_vars) # Add in some user environment keys that are useful for key in [ @@ -355,13 +502,217 @@ def _build(self): """Build the package.""" ... + def read_message_file(self, filename: Path): + return parser.Parser().parse(filename.open("r")) + + def write_message_file(self, filename: Path, data): + msg = message.Message() + for key, value in data.items(): + msg[key] = value + + # I don't know whether maxheaderlen is required, but it's used by bdist_wheel. + with filename.open("w", encoding="utf-8") as f: + generator.Generator(f, maxheaderlen=0).flatten(msg) + + @property + def wheel_tag(self) -> str: + return f"py3-none-{self.cross_venv.tag}" + + def _rewrite_absolute_needed(self, so_path: Path): + # Some libraries (notably libpython built without DT_SONAME) end up + # recorded in DT_NEEDED by their absolute build-host path when linked + # via CMake's absolute-path style. That path won't exist on the + # target device. Shift each DT_NEEDED d_val past the last '/' in the + # existing string — no string rewriting needed because the basename + # already lives at the suffix of the absolute path. + with open(so_path, "r+b") as f: + if f.read(4) != b"\x7fELF": + return + ei_class = struct.unpack("B", f.read(1))[0] + if ei_class == 1: + # ELF32: 4-byte fields, 8-byte dyn entries, 40-byte sections. + shoff_pos, shoff_fmt = 32, " " + f"'{name[slash + 1:].decode(errors='replace')}'", + ) + + def _check_elf_alignment(self, so_path: Path): + """Verify that all PT_LOAD segments are 16KB-aligned.""" + MIN_ALIGNMENT = 16384 + with open(so_path, "rb") as f: + magic = f.read(4) + if magic != b"\x7fELF": + return + ei_class = struct.unpack("B", f.read(1))[0] + if ei_class != 2: # skip 32-bit ELFs + return + + # Read e_phoff, e_phentsize, e_phnum from ELF64 header + f.seek(32) + e_phoff = struct.unpack("= {MIN_ALIGNMENT}. " + f"Library is not 16KB page-aligned." + ) + log(self.log_file, f"[{self.cross_venv}] {so_path.name}: 16KB alignment OK") + + def fix_wheel(self, wheel_dir: Path): + + log(self.log_file, f"[{self.cross_venv}] Fixing wheel contents") + + # Normalize wheel tags to forge platform tags so repacked wheels use + # android_24_arm64_v8a / ios_13_0_arm64_iphoneos style platform tags. + # Preserve the Python/ABI part the upstream build wrote (e.g. maturin + # emits `cp37-abi3-*` for cryptography); only the platform component + # is swapped. Falls back to self.wheel_tag when no Tag was written. + wheel_metadata_path = next(wheel_dir.glob("*.dist-info")) / "WHEEL" + wheel_metadata = self.read_message_file(wheel_metadata_path) + upstream_tags = wheel_metadata.get_all("Tag", []) + del wheel_metadata["Tag"] + new_tags = [] + for tag in upstream_tags: + py, abi, _platform = tag.rsplit("-", 2) + new_tags.append(f"{py}-{abi}-{self.cross_venv.tag}") + if not new_tags: + new_tags = [self.wheel_tag] + for tag in new_tags: + wheel_metadata["Tag"] = tag + self.write_message_file(wheel_metadata_path, wheel_metadata) + + if self.cross_venv.sdk == "android": + env = self.compile_env() + + for so in wheel_dir.glob("**/*.so"): + log(self.log_file, f"[{self.cross_venv}] Stripping {so}") + self.cross_venv.run( + self.log_file, + [env["STRIP"], "--strip-unneeded", str(so)], + ) + + # Rewrite any absolute-path DT_NEEDED entries to their basename + # (e.g. libpython linked by absolute path under CMake builds). + for so in wheel_dir.glob("**/*.so"): + self._rewrite_absolute_needed(so) + + # Verify 16KB page alignment (required by Google Play) + for so in wheel_dir.glob("**/*.so"): + self._check_elf_alignment(so) + + # add missing requirements from "host" + if len(self.package.meta["requirements"]["host"]): + metadata_path = next(wheel_dir.glob("*.dist-info")) / "METADATA" + metadata = self.read_message_file(metadata_path) + for req in self.package.meta["requirements"]["host"]: + if req.startswith("flet-"): + log( + self.log_file, + f"[{self.cross_venv}] Adding {req} requirement to METADATA", + ) + parts = req.split(" ", 1) + req_name = parts[0] + if len(parts) > 1: + req_ver = parts[1] + if req_ver[0].isdigit(): + req_ver = f"=={req_ver}" + metadata["Requires-Dist"] = f"{req_name} ({req_ver})" + else: + metadata["Requires-Dist"] = req_name + self.write_message_file(metadata_path, metadata) + class SimplePackageBuilder(Builder): """A builder for projects that have a build.sh entry point.""" @property def source_archive_path(self) -> Path: - url = self.package.meta["source"]["url"] + url = self.download_source_url() filename = url.split("/")[-1] return Path.cwd() / "downloads" / filename @@ -389,7 +740,12 @@ def log_file_path(self) -> Path: ) def download_source_url(self): - return self.package.meta["source"]["url"] + return self.package.meta["source"]["url"].format( + version=self.package.meta["package"]["version"], + build=self.package.meta["build"]["number"], + sdk=self.cross_venv.sdk, + arch=self.cross_venv.arch, + ) def prepare(self, clean=True): # Always clean a non-Python build. @@ -398,23 +754,16 @@ def prepare(self, clean=True): log(self.log_file, f"\n[{self.cross_venv}] Installing wheel-building tools") self.cross_venv.pip_install(self.log_file, ["wheel"], build=True) - def write_message_file(self, filename, data): - msg = message.Message() - for key, value in data.items(): - msg[key] = value - - # I don't know whether maxheaderlen is required, but it's used by bdist_wheel. - with filename.open("w", encoding="utf-8") as f: - generator.Generator(f, maxheaderlen=0).flatten(msg) - def make_wheel(self): build_num = str(self.package.meta["build"]["number"]) name = canonicalize_name(self.package.name) version = canonicalize_version(self.package.version) - info_path = self.build_path / "wheel" / f"{name}-{version}.dist-info" + info_path = ( + self.build_path / "wheel" / f"{name.replace('-', '_')}-{version}.dist-info" + ) log(self.log_file, f"\n[{self.cross_venv}] Writing wheel metadata") - info_path.mkdir() + info_path.mkdir(exist_ok=True) # Write the packaging metadata self.write_message_file( @@ -424,7 +773,7 @@ def make_wheel(self): "Root-Is-Purelib": "false", "Generator": "mobile-forge", "Build": build_num, - "Tag": f"py3-none-{self.cross_venv.tag}", + "Tag": self.wheel_tag, }, ) self.write_message_file( @@ -438,8 +787,13 @@ def make_wheel(self): }, ) + # fix wheel before packaging + self.fix_wheel(self.build_path / "wheel") + # Re-pack the wheel file log(self.log_file, f"\n[{self.cross_venv}] Packing wheel") + dist_dir = Path.cwd() / "dist" + dist_dir.mkdir(parents=True, exist_ok=True) self.cross_venv.run( self.log_file, [ @@ -449,7 +803,7 @@ def make_wheel(self): "pack", str(self.build_path / "wheel"), "--dest-dir", - str(Path.cwd() / "dist"), + str(dist_dir), "--build-number", str(build_num), ], @@ -465,9 +819,19 @@ def compile(self): env=self.compile_env( **{ "HOST_TRIPLET": self.cross_venv.platform_triplet, + "HOST_ARCH": self.cross_venv.arch, + "SDK": self.cross_venv.sdk, + "SDK_VERSION": self.cross_venv.sdk_version, + "SDK_ROOT": ( + str(self.cross_venv.sdk_root) + if self.cross_venv.sdk != "android" + else "" + ), "BUILD_TRIPLET": f"{os.uname().machine}-apple-darwin", "CPU_COUNT": str(multiprocessing.cpu_count()), "PREFIX": str(self.build_path / "wheel" / "opt"), + "PYTHON_PREFIX": self.cross_venv.sysconfig_data["prefix"], + "PLATLIB": self.cross_venv.scheme_paths["platlib"], } ), ) @@ -516,6 +880,11 @@ def log_file_path(self) -> Path: / f"{self.package.name}-{self.package.version}-cp3{sys.version_info.minor}-{self.cross_venv.tag}.log" ) + @property + def wheel_tag(self) -> str: + py_tag = f"cp3{sys.version_info.minor}" + return f"{py_tag}-{py_tag}-{self.cross_venv.tag}" + def download_source_url(self): return get_pypi_source_urls(self.package.name)[self.package.version] @@ -534,17 +903,22 @@ def prepare(self, clean=True): pyproject = tomllib.load(f) # Install the build requirements in the cross environment - self.cross_venv.pip_install( - self.log_file, - ["build", "wheel"] + pyproject["build-system"]["requires"], - wheels_path=Path.cwd() / "dist", - ) + # self.cross_venv.pip_install( + # self.log_file, + # ["build", "wheel"] + pyproject["build-system"]["requires"], + # paths=[Path.cwd() / "dist"], + # ) # Install the build requirements in the build environment self.cross_venv.pip_install( self.log_file, - ["build", "wheel"] + pyproject["build-system"]["requires"], - wheels_path=Path.cwd() / "dist", + ["build", "wheel"] + + ( + pyproject["build-system"]["requires"] + if "build-system" in pyproject + else [] + ), + paths=[Path.cwd() / "dist"], build=True, ) else: @@ -584,6 +958,12 @@ def _create_meson_cross(self, env: dict[str, str]): "cpp": env["CXX"], "ar": env["AR"], "strip": env["STRIP"], + "python": str( + self.cross_venv.venv_path + / "cross" + / "bin" + / f"python3.{sys.version_info.minor}" + ), }, "built-in options": { "c_args": env["CFLAGS"], @@ -591,7 +971,7 @@ def _create_meson_cross(self, env: dict[str, str]): "c_links_args": env["LDFLAGS"], "cpp_links_args": env["LDFLAGS"], }, - "properties": {"needs_exe_wrapper": True}, + "properties": {"needs_exe_wrapper": False}, "host_machine": { "cpu_family": cpu_family, "cpu": cpu, @@ -621,17 +1001,12 @@ def _create_meson_cross(self, env: dict[str, str]): return meson_cross def _build(self): - env = self.compile_env() - script_vars = {**env, **self.cross_venv.scheme_paths, **self.cross_venv.sysconfig_data} - - # Set up any additional environment variables needed in the script environment. - for key, value in self.package.meta["build"]["script_env"].items(): - env[key] = str(value).format(**script_vars) - # Set the cross host platform in the environment - env["_PYTHON_HOST_PLATFORM"] = self.cross_venv.platform_identifier + env["_PYTHON_HOST_PLATFORM"] = self.cross_venv._tag_identifier( + self.cross_venv.sdk, self.cross_venv.sdk_version, self.cross_venv.arch + ) meson_cross_file = self._create_meson_cross(env) @@ -653,6 +1028,12 @@ def _build(self): else [] ) + # build wheel to a temp dir + tmp_dist = self.build_path / "tmp_dist" + if tmp_dist.exists(): + shutil.rmtree(tmp_dist) + tmp_dist.mkdir(parents=True, exist_ok=True) + self.cross_venv.run( self.log_file, [ @@ -662,9 +1043,57 @@ def _build(self): "--no-isolation", "--wheel", "--outdir", - str(Path.cwd() / "dist"), + str(tmp_dist), ] + backend_args, cwd=self.build_path, env=env, ) + tmp_wheel = next(tmp_dist.glob("*.whl")) + + # unpack wheel to a temp directory + tmp_wheel_dir = self.build_path / "tmp_wheel" + if tmp_wheel_dir.exists(): + shutil.rmtree(tmp_wheel_dir) + tmp_wheel_dir.mkdir(parents=True, exist_ok=True) + + log(self.log_file, f"\n[{self.cross_venv}] Unpacking wheel to temp directory") + self.cross_venv.run( + self.log_file, + [ + "build-python", + "-m", + "wheel", + "unpack", + "--dest", + str(tmp_wheel_dir), + str(tmp_wheel), + ], + ) + + tmp_wheel_dir = next(tmp_wheel_dir.iterdir()) + + # fix wheel + self.fix_wheel(tmp_wheel_dir) + + # re-pack the wheel to "dist" + log(self.log_file, f"\n[{self.cross_venv}] Packing wheel to dist") + dist_dir = Path.cwd() / "dist" + dist_dir.mkdir(parents=True, exist_ok=True) + pack_args = [ + "build-python", + "-m", + "wheel", + "pack", + str(tmp_wheel_dir), + "--dest-dir", + str(dist_dir), + ] + if self.package.meta["build"]["number"]: + pack_args.extend( + ["--build-number", str(self.package.meta["build"]["number"])] + ) + self.cross_venv.run( + self.log_file, + pack_args, + ) diff --git a/src/forge/cross.py b/src/forge/cross.py index 34e3f8a3..33a96340 100644 --- a/src/forge/cross.py +++ b/src/forge/cross.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import itertools import os import shutil import sys @@ -14,18 +15,19 @@ class CrossVEnv: BASE_VERSION = { "android": "24", - "iOS": "12.0", - "tvOS": "7.0", + "iOS": "13.0", + "tvOS": "12.0", "watchOS": "4.0", } + ANDROID_HOST_ARCHS = ( + ("arm64-v8a", "x86_64") + if sys.version_info[:2] >= (3, 13) + else ("arm64-v8a", "armeabi-v7a", "x86_64", "x86") + ) + HOST_SDKS = { - "android": [ - ("android", "arm64-v8a"), - ("android", "armeabi-v7a"), - ("android", "x86_64"), - ("android", "x86"), - ], + "android": [("android", arch) for arch in ANDROID_HOST_ARCHS], "iOS": [ ("iphoneos", "arm64"), ("iphonesimulator", "arm64"), @@ -53,21 +55,42 @@ class CrossVEnv: "watchsimulator": "apple-watchos-simulator", } + XCFRAMEWORK_SLICES = { + ("iphonesimulator", "arm64"): "ios-arm64_x86_64-simulator", + ("iphonesimulator", "x86_64"): "ios-arm64_x86_64-simulator", + ("iphoneos", "arm64"): "ios-arm64", + ("appletvsimulator", "arm64"): "tvos-arm64_x86_64-simulator", + ("appletvsimulator", "x86_64"): "tvos-arm64_x86_64-simulator", + ("appletvos", "arm64"): "tvos-arm64", + ("watchsimulator", "arm64"): "watchos-arm64_x86_64-simulator", + ("watchsimulator", "x86_64"): "watchos-arm64_x86_64-simulator", + ("watchos", "arm64_32"): "watchos-arm64_32", + } + ANDROID_PLATFORM_TRIPLET = { "arm64-v8a": "aarch64-linux-android", "armeabi-v7a": "arm-linux-androideabi", "x86_64": "x86_64-linux-android", "x86": "i686-linux-android", } + ANDROID_PLATFORM_MACHINE = { + "arm64-v8a": "aarch64", + "armeabi-v7a": "arm", + "x86_64": "x86_64", + "x86": "i686", + } def __init__(self, sdk, sdk_version, arch): self.sdk = sdk self.sdk_version = sdk_version self.arch = arch + self.host_os = { + sdk: host_os for host_os, sdks in self.HOST_SDKS.items() for sdk, _ in sdks + }[self.sdk] self.platform_identifier = self._platform_identifier(sdk, sdk_version, arch) self.tag = ( - self._platform_identifier(sdk, sdk_version, arch) + self._tag_identifier(sdk, sdk_version, arch) .replace("-", "_") .replace(".", "_") ) @@ -82,6 +105,7 @@ def __init__(self, sdk, sdk_version, arch): self._scheme_paths = None self._install_root = None self._sdk_root = None + self.host_sysconfig = None def __str__(self): return self.venv_name @@ -90,6 +114,28 @@ def exists(self) -> bool: """Does the cross environment exist?""" return self.venv_path.is_dir() + @property + def host_python_home(self): + support_path = Path( + os.getenv(f"MOBILE_FORGE_{self.host_os.upper()}_SUPPORT_PATH") + ) + return ( + ( + support_path + / "support" + / f"3.{sys.version_info.minor}" + / self.host_os + / "Python.xcframework" + / self.XCFRAMEWORK_SLICES[(self.sdk, self.arch)] + ) + if self.host_os == "iOS" + else next( + (support_path / "install" / self.host_os / self.arch).glob( + f"python-3.{sys.version_info.minor}.*" + ) + ) + ) + @property def venv_path(self) -> Path: """The location of the cross environment on disk.""" @@ -187,18 +233,21 @@ def sdk_root(self) -> Path: return self._sdk_root @classmethod - def _platform_identifier(cls, sdk, version, arch): + def _platform_identifier(self, sdk, version, arch): if sdk == "android": - if version is None: - version = 21 - identifier = f"{sdk}-{version}-{arch}" + if sys.version_info[:2] >= (3, 13): + identifier = f"{sdk}-{self.ANDROID_PLATFORM_MACHINE[arch]}" + else: + if version is None: + version = 21 + identifier = f"{sdk}-{version}-{arch}" elif sdk in {"iphoneos", "iphonesimulator"}: if version is None: - version = "12.0" + version = "13.0" identifier = f"ios-{version}-{arch}-{sdk}" elif sdk in {"appletvos", "appletvsimulator"}: if version is None: - version = "7.0" + version = "12.0" identifier = f"tvos-{version}-{arch}-{sdk}" elif sdk in {"watchos", "watchsimulator"}: if version is None: @@ -208,6 +257,14 @@ def _platform_identifier(cls, sdk, version, arch): raise ValueError(f"Don't know how to build wheels for {sdk}") return identifier + @classmethod + def _tag_identifier(self, sdk, version, arch): + if sdk == "android": + if version is None: + version = 21 + return f"{sdk}-{version}-{arch}" + return self._platform_identifier(sdk, version, arch) + def create( self, location=None, @@ -222,17 +279,47 @@ def create( :raises: ``RuntimeError`` if an environment matching the requested host already exists, and ``clean=False``. """ - env_key = ( - f"MOBILE_FORGE_{self.sdk.upper()}_{self.arch.upper().replace('-', '_')}" - ) - host_python = os.getenv(env_key) - if host_python is None: - raise RuntimeError( - f"Host Python not defined. Set the {env_key} environment variable with " - "the location of the host Python's binary." + host_python = self.host_python_home / f"bin/python3.{sys.version_info.minor}" + if not host_python.is_file(): + raise RuntimeError(f"Can't find host python {host_python}") + + if self.host_os == "iOS": + self.sysconfigdata_name = ( + f"_sysconfigdata__{self.host_os.lower()}_{self.arch}-{self.sdk}" + ) + + legacy_host_sysconfig = ( + self.host_python_home + / f"lib/python3.{sys.version_info.minor}" + / f"{self.sysconfigdata_name}.py" + ) + platform_config_host_sysconfig = ( + self.host_python_home + / "platform-config" + / f"{self.arch}-{self.sdk}" + / f"{self.sysconfigdata_name}.py" + ) + if legacy_host_sysconfig.is_file(): + host_sysconfig = legacy_host_sysconfig + elif platform_config_host_sysconfig.is_file(): + host_sysconfig = platform_config_host_sysconfig + else: + raise RuntimeError( + "Can't find host sysconfig. Tried: " + f"{legacy_host_sysconfig} and {platform_config_host_sysconfig}" + ) + else: + host_sysconfig = next( + (self.host_python_home / f"lib/python3.{sys.version_info.minor}").glob( + "_sysconfigdata__*.py" + ) ) - elif not Path(host_python).is_file(): - raise RuntimeError(f"Environment {self} already exists.") + self.sysconfigdata_name = host_sysconfig.stem + + self.host_sysconfig = host_sysconfig + + if self.host_os != "iOS" and not host_sysconfig.is_file(): + raise RuntimeError(f"Can't find host sysconfig {host_sysconfig}") self.location = Path(location).resolve() if location else Path.cwd() if self.exists(): @@ -250,9 +337,12 @@ def create( sys.executable, "-m", "crossenv", + "--sysconfigdata-file", + str(host_sysconfig), str(host_python), self.venv_path, ], + **self.cross_kwargs({}), ) except subprocess.CalledProcessError: raise RuntimeError(f"Unable to create cross platform environment {self}.") @@ -329,31 +419,21 @@ def cross_kwargs(self, kwargs): venv_kwargs = kwargs.copy() env = venv_kwargs.get("env", {}) - # Remove the current venv from the path, and add the cross-env and the - # build-env, and clean out any other problematic paths. - clean_path = [ - p - for p in os.getenv("PATH").split(os.pathsep)[1:] - if not ( - # Exclude rbenv, npm, and other language environments - p.startswith(f"{Path.home()}/.") - and not p.startswith(f"{Path.home()}/.cargo") - # Exclude homebrew - or p.startswith("/opt") - # Exclude local python installs - or p.startswith("/Library/Frameworks") - # Exclude cryptexd - or p.startswith("/var") - or p.startswith("/System") - ) - ] - + # Ensure the path is clean, and doesn't include any non-iOS paths. env["PATH"] = os.pathsep.join( [ + str(self.host_python_home / "bin"), + str(self.venv_path / "cross" / "bin"), + str(self.venv_path / "build" / "bin"), str(self.venv_path / "bin"), str(self.venv_path / self.venv_path.name / "bin"), - ] - + clean_path + str(Path.home() / ".cargo/bin"), + "/usr/bin", + "/bin", + "/usr/sbin", + "/sbin", + "/Library/Apple/usr/bin", + ][1 if self.host_os == "android" else 0 :] ) # Set VIRTUALENV to the active venv @@ -402,7 +482,7 @@ def pip_install( packages, update=False, build=False, - wheels_path=None, + paths=None, ): """Install packages into the cross environment. @@ -410,14 +490,17 @@ def pip_install( :param update: Should the package be updated ("-U") :param build: Should the package be installed in the build environment? Defaults to installing in the host environment. - :param wheels_path: A path to search for additional wheels ("--find-links"). + :param paths: The paths to search for additional wheels ("--find-links"). """ # build-pip is a script; pip is a shim with a hashbang that points # at a python interpreter, which we can't invoke with subprocess. self.run( logfile, (["build-pip"] if build else ["python", "-m", "pip"]) - + ["install", "--disable-pip-version-check"] + + [ + "install", + "--disable-pip-version-check", + ] # If we're doing a host build, require binary packages. # build environment can use non-binary packages. + ( @@ -430,8 +513,13 @@ def pip_install( ) # Update packages if requested + (["-U"] if update else []) - # Include the local wheels path if provided. - + (["--find-links", str(wheels_path)] if wheels_path else []) + # Include the local wheels paths if provided. + + ( + list(itertools.chain(*(["--find-links", str(path)] for path in paths))) + if paths + else [] + ) + + (["--extra-index-url", "https://pypi.flet.dev"]) # Finally, the list of packages to install. + packages, ) @@ -463,12 +551,6 @@ def main(): parser.add_argument( "--arch", required=True, help="The CPU architecture for the host." ) - parser.add_argument( - "host_python", - metavar="DIR", - type=abspath, - help="Path to the python executable of the Python built for the host platform.", - ) args = parser.parse_args() @@ -478,10 +560,7 @@ def main(): sdk_version=args.sdk_version, arch=args.arch, ) - cross_venv.create( - host_python=Path(args.host_python), - clean=args.clean, - ) + cross_venv.create(clean=args.clean) except RuntimeError as e: print() print(f"ERROR: {e}") diff --git a/src/forge/package.py b/src/forge/package.py index 58317590..c3200111 100644 --- a/src/forge/package.py +++ b/src/forge/package.py @@ -8,12 +8,7 @@ import jsonschema import yaml -from forge.build import ( - Builder, - CMakePackageBuilder, - PythonPackageBuilder, - SimplePackageBuilder, -) +from forge.build import Builder, PythonPackageBuilder, SimplePackageBuilder from forge.cross import CrossVEnv @@ -22,7 +17,7 @@ def __init__( self, package_name_or_recipe: str, version: str | None, - build_number: str | None, + build_number: int | None, sdk: str, sdk_version: str, arch: str, @@ -118,11 +113,7 @@ def builder(self, cross_venv: CrossVEnv) -> Builder: :param cross_venv: The cross-platform environment to use for the build :returns: A builder for the package. """ - if self.meta["source"] == "pypi": - return PythonPackageBuilder(cross_venv=cross_venv, package=self) + if (self.recipe_path / "build.sh").exists(): + return SimplePackageBuilder(cross_venv=cross_venv, package=self) else: - if "cmake" in self.meta["requirements"]["build"]: - self.meta["requirements"]["build"].remove("cmake") - return CMakePackageBuilder(cross_venv=cross_venv, package=self) - else: - return SimplePackageBuilder(cross_venv=cross_venv, package=self) + return PythonPackageBuilder(cross_venv=cross_venv, package=self)