recipe: pycares 5.0.1 - #118
Merged
Merged
Conversation
Adds a recipe for [pycares](https://github.com/saghul/pycares) 5.0.1 — Python bindings for [c-ares](https://c-ares.org/), the asynchronous DNS resolver behind [aiodns](https://github.com/aio-libs/aiodns). Requested in [flet#6805](flet-dev/flet#6805). - [Docs](recipes/pycares/README.md) - [Example](recipes/pycares/examples/dns-lookup) `socket.getaddrinfo` blocks, and asyncio only moves that block onto a thread-pool worker. c-ares speaks DNS itself, so lookups are genuinely concurrent — which is the whole reason to carry it on a phone. aiodns is pure Python and needs no recipe of its own; cffi is already published. ## Recipe shape A plain setuptools sdist with one CFFI (API-mode) extension, one patch. pycares' `setup.py` builds the vendored `deps/c-ares` (1.34.6) with CMake and links the resulting `libcares.a` into the extension through `extra_objects`, but its `cmake_args` list is hardcoded and configures for the build host — an unpatched cross build folds a macOS static library into the mobile `.so`. `mobile.patch` appends `shlex.split(os.environ['FORGE_CMAKE_ARGS'])` last, so the recipe's toolchain args win, including over upstream's `-DCMAKE_OSX_DEPLOYMENT_TARGET=10.12`. That is the whole adaptation. `sys.platform` is `android`/`ios` under the crossenv, so none of `setup.py`'s `darwin`/`linux`/`win32` branches match and the things that would have needed patching never fire: no `-lrt` on Android (bionic has no librt), no macOS deployment target on iOS. No extra link flags on either platform — iOS `libcares.a` resolves entirely against libSystem, Android's against bionic. Two configure-time facts the wheel depends on, both verified in the build logs: `CARES_THREADS` (`import pycares` raises outright if `ares_threadsafety()` is false) and the target-side probes — `HAVE___SYSTEM_PROPERTY_GET` on Android, kqueue/epoll for the event thread. Configured against the host they yield a green wheel that dies at `Channel()`. ## Validation 6/6 slices built clean first try. On-device 6/6 tests EXIT 0 on both an API-34 arm64 emulator and an iPhone 16 Pro simulator. Wheel hygiene checked per slice: correct `Machine` per ABI, all Android `LOAD` segments aligned `0x4000`, `DT_NEEDED` limited to `libc`/`libm`/`libdl` + `libpython`, iOS `LC_BUILD_VERSION` platform 2 on device and 7 on the simulators. The tests are network-free by design, so the `dns-lookup` example was also run against a real network on both platforms — that is what surfaced the one thing consumers must know: **On Android c-ares finds no system nameservers and every lookup fails until the app supplies its own.** Android 8 removed the `net.dns*` properties and the replacement is Java-only, reachable solely through `ares_library_init_android()`, which pycares does not expose. c-ares then falls back to `127.0.0.1:53` rather than failing loudly. On the emulator: `auto-discovered servers: ['127.0.0.1:53']`, system DNS → `DNSError: (11, 'Could not contact DNS servers')`, explicit `1.1.1.1`/`8.8.8.8` → resolves. iOS is unaffected — c-ares reads the system configuration and both paths work. The README leads with this; the example shows both outcomes on screen.
…ig APIs [skip ci] From the pycares recipe. `new-mobile-recipe`: a shape row + deep-dive for an sdist that vendors a C library and builds it with its OWN cmake call inside `build_ext` — `CMAKE_ARGS` does nothing there, so the recipe patches in a `FORGE_CMAKE_ARGS` extend. Also extends the platform-predicate trap to `sys.platform` (crossenv sets it to `android`/`ios`), with the note that matching nothing is sometimes the correct outcome. `forge-error-catalogue`: the build-time entry for that shape — it fails GREEN, because a host-configured vendored lib still links whenever host and target arch agree — plus a runtime entry for the Android class it belongs to: a library that reads system configuration through a Java-only API gets nothing under Flet, and seeds a default rather than erroring. c-ares is the worked example (127.0.0.1:53, iOS unaffected). Also corrects the aiodns wheel size in the pycares README.
The recipe README went from 5 links to 57 and the example README from 5 to 8 — every API, spec, platform behaviour and file it names now points at where a reader can follow it up: CPython (`socket.getaddrinfo`, the asyncio loop methods, `asyncio.gather`, the stdlib IDNA codec), c-ares man pages (`ares_library_init_android`, `ares_search`, `ares_getaddrinfo`, `ares_strerror`, `ares_inet_pton`, `ares_threadsafety`), pycares' own `docs/channel.rst`, aiodns' API section, CFFI's API-mode and `ffi.cdef` docs, Android references (`ConnectivityManager`, `LinkProperties.getDnsServers()`, `INTERNET`, the Android 8 behaviour changes), Apple's `SCDynamicStore` / `NSLocalNetworkUsageDescription` / TN3179, the resolv.conf and epoll/kqueue man pages, Flet's `page.run_thread` and Android permissions docs, the three public resolvers named, and the recipe's own `meta.yaml` / `mobile.patch` / tests / example. Also fixes a link that was already broken — `flet.dev/docs/cookbook/native-python-apis` 404s; pyjnius now points at its own docs — and puts the `getDnsServers()` URL in a pointy-bracket destination, since the parentheses would otherwise terminate the Markdown link early. All 46 external URLs verified 200; both files parsed with a CommonMark parser and every relative target checked to exist.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a recipe for pycares 5.0.1 — Python bindings for c-ares, the asynchronous DNS resolver that aiodns is built on. Requested in flet#6805.
socket.getaddrinfoblocks, and asyncio only moves that block onto a thread-pool worker — so resolution is bounded by the pool, not the network. c-ares speaks DNS itself and keeps hundreds of lookups in flight, which is the reason to carry it on a phone. aiodns needs no recipe of its own (pure Python on PyPI) andcffiis already published, so this one recipe is the whole ask.Recipe shape
A plain setuptools sdist with one CFFI (API-mode) extension and one patch — but a shape this repo hasn't had before. pycares'
setup.pybuilds the vendoreddeps/c-ares(1.34.6) with its ownsubprocessCMake call insidebuild_ext, then links the resultinglibcares.athroughextra_objects. No CMake build backend is involved, soCMAKE_ARGSdoes nothing — the argument list is a literal insetup.py, configured for the build host.mobile.patchmakes that list extensible, in one line of substance:Appended after upstream's own platform block so the recipe's toolchain arguments win — repeated
-Don a cmake command line is last-one-wins, which is also how upstream's-DCMAKE_OSX_DEPLOYMENT_TARGET=10.12gets overridden without editing its line.Getting this wrong fails green. A host-configured
libcares.astill links whenever host and target arch agree (macOS arm64 objects go into anios_arm64extension without complaint), and the configure-time probes answer for macOS. Two of those probes are load-bearing and were verified in the build logs:CARES_THREADS(import pycaresraisesRuntimeErroroutright ifares_threadsafety()is false) andHAVE___SYSTEM_PROPERTY_GET/ epoll / kqueue for the event thread.That is the entire adaptation. Everything else falls out because
sys.platformisandroid/iosunder the crossenv, so none ofsetup.py'sdarwin/linux/win32branches match — which is exactly right: no-lrton Android (bionic has no librt), no macOS deployment target on iOS. No extra link flags on either platform; the iOSlibcares.aresolves entirely against libSystem and Android's against bionic.Validation
6/6 slices built clean first try. CI green across the full matrix — 18 wheels (3.12/3.13/3.14 × 6 slices) — with the on-device
console.logartifacts showing6 passed/EXIT 0on both platforms. Also run locally on an API-34 arm64 emulator and an iPhone 16 Pro simulator.Wheel hygiene per slice: correct
Machineper ABI, every AndroidLOADsegment aligned0x4000,DT_NEEDEDlimited tolibc/libm/libdlpluslibpython, iOSLC_BUILD_VERSIONplatform 2 on device and 7 on the simulators,otool -Lshowing only libSystem and the Python framework. ~116–143 KB compressed per slice.The recipe tests are deliberately network-free, so they cannot catch a resolver that builds but can't reach anything. The
dns-lookupexample was run against a real network on both platforms to close that gap — and that is what surfaced the consumer note below.Changes
recipes/pycares/—meta.yaml, one patch, 6 on-device tests,README.md, and a runnable example..claude/skills/— the new recipe shape innew-mobile-recipe, plus twoforge-error-catalogueentries (the green-but-host-configured build class above, and the Android runtime class below). Separable; drop the commit if you'd rather keep skills out of a recipe PR.