FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat(cli)!: overhaul ios embedding, devices, and release builds by owenthcarey · Pull Request #18 · pythonnative/pythonnative · GitHub

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .gradle  (1) .h  (1) .kt  (1) .md  (8) .pbxproj  (1) .plist  (1) .py  (32) .storyboard  (1) .swift  (4) .xml  (1) .yml  (1) All 11 file types selected
Only manifest files
Deleted files Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
26 changes: 23 additions & 3 deletions .github/workflows/release.yml
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,31 @@ jobs:
- name: Install build tools
run: python -m pip install -U pip build

# TEMPORARY: inlined replacement for the
# python-semantic-release/python-semantic-release@v9 action. That
# action builds its Docker image at job time with GitPython
# unpinned, and GitPython 3.1.60 (2026-08-25) removed
# Actor.name_email_regex, which crashes every semantic-release
# config load (python-semantic-release issue #1475). This step
# mirrors the action: same git identity, same `version` command,
# and a `released` output for the steps below. Restore the action
# once the fix (python-semantic-release PR #1477) is released.
- name: Python Semantic Release
id: release
uses: python-semantic-release/python-semantic-release@v9
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name "github-actions"
git config user.email "actions@github.com"
pip install "python-semantic-release==9.21.2" "gitpython<3.1.60"
tags_before=$(git tag | wc -l)
semantic-release -v version
tags_after=$(git tag | wc -l)
if [ "$tags_after" -gt "$tags_before" ]; then
echo "released=true" >> "$GITHUB_OUTPUT"
else
echo "released=false" >> "$GITHUB_OUTPUT"
fi

- name: Build package
if: steps.release.outputs.released == 'true'
Expand Down
10 changes: 8 additions & 2 deletions docs/api/cli.md
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ the documented behavior never drifts from the code.
with Fast Refresh, the fastest way to iterate on UI. Flags:
`--width`, `--height`, `--title`, `--no-hot-reload`. See the
[Desktop preview guide](../guides/desktop-preview.md).
- `pn devices [android|ios]`: list connected devices, emulators, and
simulators with the identifiers `--device` accepts.
- `pn run android|ios`: build and run on a connected device or
simulator. Flags: `--prepare-only`, `--hot-reload`, `--no-logs`.
simulator. Flags: `--device` (target a specific device by identifier
or name), `--prepare-only`, `--hot-reload`, `--no-logs`.
- `pn logs android|ios`: stream logs from the running app without
rebuilding.
- `pn build android|ios`: build distributable artifacts (release by
default). Flag: `--debug` for the debug variant. See
default). Flags: `--debug` for the debug variant, `--upload` to send
an iOS release build to App Store Connect. See
[Building for release](../guides/building-for-release.md).
- `pn app-id android|ios`: print the resolved application id (Android)
or bundle id (iOS), handy for scripts and CI.
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/architecture.md
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
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ See [Mental model](mental-model.md) for a wider comparison table.

## iOS flow (rubicon-objc)

- The iOS template (Swift plus PythonKit) boots Python and calls
- The iOS template (Swift with CPython embedded through the C API) boots Python and calls
[`create_screen`][pythonnative.create_screen] internally with the
current `UIViewController` pointer.
- The reconciler creates UIKit views and attaches them to the
Expand Down
8 changes: 8 additions & 0 deletions docs/guides/android.md
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ directly; just export your component and configure the entry point in
pn run android
```

With several devices or emulators connected, list them and target one:

```bash
pn devices android
pn run android --device emulator-5554
```

Or to only prepare the project without building:

```bash
Expand All @@ -46,6 +53,7 @@ the template use, so you get Python output without the usual logcat noise:

Press Ctrl+C to stop streaming. Pass `--no-logs` to skip log streaming
entirely (useful in CI or when you'd rather watch logs from Android Studio).
To re-attach later without rebuilding, run `pn logs android`.

If you need unfiltered output, run `adb logcat` yourself in another terminal.

Expand Down
46 changes: 31 additions & 15 deletions docs/guides/building-for-release.md
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
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,35 @@ the release build so the resulting APK/AAB are signed and upload-ready.

## iOS

`pn build ios` archives the app for a device with `xcodebuild archive`,
embeds the device CPython slice into the archive, and exports a signed
`.ipa` with `xcodebuild -exportArchive`. Outputs:
`pn build ios` archives the app with `xcodebuild archive` and exports a
signed `.ipa` with `xcodebuild -exportArchive`. The Python framework,
standard library, and app code are installed and signed during the
Xcode build itself, so the archive needs no post-processing. Outputs:

```
build/ios/ios_template/build/export/*.ipa
build/ios/ios_template/build/ios_template.xcarchive
build/ios/export/*.ipa
build/ios/ios_template.xcarchive
```

!!! warning "Experimental"
The device archive/export path embeds and re-signs the embedded
Python framework. Treat it as experimental and verify on a real
device before relying on it for store submission.
To send the build straight to App Store Connect instead of exporting a
local `.ipa`, set `export_method = "app-store"` and pass `--upload`:

```bash
pn build ios --upload
```

The upload uses the App Store Connect credentials Xcode has stored; in
CI, provide an ASC API key through Xcode's standard mechanisms or
upload the exported `.ipa` with `xcrun altool`/Transporter instead.

### Bytecode-only bundles

Release builds byte-compile your `app/` sources and every bundled
package to `.pyc` and drop the `.py` files, which shrinks the bundle
and avoids shipping plain-text source. Because bytecode is
version-specific, this requires the Python running `pn` to match
`app.python_version`; otherwise `pn` prints a notice and ships `.py`
sources instead.

### Signing

Expand All @@ -127,13 +143,13 @@ iOS has no system Python, so PythonNative embeds CPython from the
[Python-Apple-support](https://github.com/beeware/Python-Apple-support)
project. On the first iOS build, `pn` downloads the pinned, checksum-
verified runtime for your `app.python_version` and caches it under
`build/ios/ios_runtime/`. The correct slice (Simulator vs. device) is
embedded into the app bundle along with the standard library, your
`app/` sources, the bundled `pythonnative` package, and any pure-Python
`[requirements].packages`.
`build/ios/ios_runtime/`. The Xcode build links `Python.xcframework`,
installs the standard library, and bundles your `app/` sources, the
`pythonnative` package, and any pure-Python `[requirements].packages`.

iOS currently ships a verified runtime for **Python 3.11**; set
`python_version = "3.11"` in `[app]` for device builds.
Pinned, verified runtimes exist for **Python 3.10, 3.11, and 3.12**;
set `python_version` in `[app]` accordingly. Unpinned versions are
rejected rather than fetched unverified.

---

Expand Down
8 changes: 5 additions & 3 deletions docs/guides/configuration.md
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ build = 1 # integer build number
python_version = "3.11" # embedded CPython version
orientation = "portrait" # portrait | landscape | all
entry_point = "app/main.py" # module whose `App` is mounted
url_schemes = ["myapp"] # deep-link schemes the app handles

[permissions]
camera = "Scan receipts with your camera."
Expand All @@ -42,7 +43,7 @@ export_method = "development" # development | ad-hoc | app-store | enterprise
[android]
min_sdk = 24
target_sdk = 34
abi_filters = ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]
# abi_filters = ["arm64-v8a", "x86_64"] # the default; add 32-bit ABIs if needed

[android.signing]
# keystore = "release.keystore"
Expand All @@ -66,8 +67,9 @@ Core identity, shared by both platforms.
| `display_name` | string | `name` | The label shown under the icon on the home screen. |
| `version` | string | `"1.0.0"` | Marketing version (`CFBundleShortVersionString` / `versionName`). One to four dot-separated numbers. |
| `build` | integer | `1` | Build number (`CFBundleVersion` / `versionCode`). Must be a positive integer; bump it for every store upload. |
| `python_version` | string | `"3.11"` | Embedded CPython version. One of `3.10`, `3.11`, `3.12`. iOS currently ships a pinned, verified build for `3.11`. |
| `python_version` | string | `"3.11"` | Embedded CPython version. One of `3.10`, `3.11`, `3.12`; every listed version has a pinned, checksum-verified iOS runtime. |
| `orientation` | string | `"portrait"` | `portrait`, `landscape`, or `all`. |
| `url_schemes` | list of strings | `[]` | Custom deep-link URL schemes (e.g. `["myapp"]` handles `myapp://…`). Wired into `CFBundleURLTypes` on iOS and a `VIEW` intent filter on Android; inbound URLs reach `pn.Linking`. |
| `entry_point` | string | `"app/main.py"` | The module whose top-level `App` component is mounted. `app/main.py` → imported as `app.main`. |

!!! tip "Per-platform id overrides"
Expand Down Expand Up @@ -180,7 +182,7 @@ provisioning_profile = "My App Distribution"
| `target_sdk` | integer | `34` | Target API level. Must be ≥ `min_sdk`. |
| `compile_sdk` | integer | `34` | SDK level the project compiles against. |
| `application_id` | string | `app.id` | Override the Android application id (and package). |
| `abi_filters` | list of strings | all four | Native ABIs to include: `armeabi-v7a`, `arm64-v8a`, `x86`, `x86_64`. Trim to shrink the APK. |
| `abi_filters` | list of strings | `["arm64-v8a", "x86_64"]` | Native ABIs to include. The default covers 64-bit devices and emulators; add `armeabi-v7a` or `x86` only if you must support 32-bit hardware (each ABI adds roughly 30 MB). |
| `permissions` | list of strings | `[]` | Extra **raw** Android permission strings appended to the ones derived from `[permissions]`. |

### `[android.signing]`
Expand Down
27 changes: 25 additions & 2 deletions docs/guides/ios.md
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ Basic steps to build and run an iOS project generated by `pn`.

## What gets generated

`pn run ios` unpacks the bundled iOS template (Swift + PythonKit, with optional Rubicon-ObjC) into `build/ios/ios_template` and copies your `app/` under `build/ios/app/` for later integration steps. The template zip is bundled with the package, so no network is required to scaffold.
`pn run ios` unpacks the bundled iOS template (a Swift Xcode project) into `build/ios/ios_template` and stages three things at the project root:

The default `ViewController.swift` initializes PythonKit, prints the Python version, and attempts to import `rubicon.objc` if present.
- `Python.xcframework`: the embedded CPython runtime for your `app.python_version` (downloaded once, checksum-verified, and cached under `build/ios/ios_runtime/`).
- `app/`: your project's Python sources.
- `app_packages/`: the `pythonnative` package, `rubicon-objc`, and any `[requirements].packages`.

The Xcode project links the framework at build time and embeds CPython through its C API (`PythonRuntime.swift`); an "Install Python runtime" build phase installs the standard library and converts binary modules into signed frameworks. The built `.app` is therefore complete as soon as `xcodebuild` finishes, with no post-build patching. If Python fails to start or your root component fails to mount, the app shows a full-screen error report with the Python traceback instead of failing silently.

## Component model

Expand All @@ -23,6 +27,16 @@ directly; just export your component and configure the entry point in
pn run ios
```

To target a specific Simulator, or a physical device, list the available targets and pass one to `--device`:

```bash
pn devices ios
pn run ios --device "iPhone 15 Pro"
pn run ios --device 00008120-001234567890ABCD
```

Running on a physical device requires `[ios].development_team` in `pythonnative.toml` (for code signing), a paired device, and Developer Mode enabled on the device (Settings > Privacy & Security > Developer Mode).

Or prepare without building:

```bash
Expand All @@ -47,6 +61,15 @@ interacting with the app from Xcode or Console.app, or when running in
a non-interactive context like CI (see the `e2e.yml` workflow for an
example).

To re-attach to a running app later without rebuilding, use:

```bash
pn logs ios
```

For a physical device, use Console.app or Xcode's Devices and
Simulators window; `simctl` console attach is Simulator-only.

### Requirements

- **Xcode 14 or newer.** `simctl launch --console-pty` was added in
Expand Down
30 changes: 29 additions & 1 deletion docs/guides/native-modules.md
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
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,18 @@ async def setup_reminder():
`request_permission()` is required on iOS and on Android 13+. On
older Android the call returns `True` without prompting.

For server-sent (remote) pushes, declare the `remote_notifications`
capability in `pythonnative.toml`, then register with APNs:

```python
token = await pn.Notifications.get_device_token() # APNs hex token
```

Your server passes the token to APNs to address this install. The
simulator has no APNs connection, so test on a real device. Android
remote push requires Firebase Cloud Messaging, which the built-in
module doesn't wire up; `get_device_token()` returns `None` there.

## Clipboard

[`Clipboard`][pythonnative.Clipboard] reads and writes the system
Expand Down Expand Up @@ -193,7 +205,7 @@ async def share_link():
## Linking

[`Linking`][pythonnative.Linking] opens URLs, deep links, and the app's
Settings page, and reports the URL that launched the app.
Settings page, and delivers inbound deep links to your app.

```python
if pn.Linking.can_open_url("tel:+15551234567"):
Expand All @@ -203,6 +215,22 @@ launch_url = pn.Linking.get_initial_url() # deep link the app opened with
pn.Linking.open_settings() # this app's entry in Settings
```

To receive deep links, declare your schemes in `pythonnative.toml`:

```toml
[app]
url_schemes = ["myapp"]
```

Opening `myapp://orders/42` now launches (or foregrounds) your app on
both platforms. The URL that cold-started the app is returned by
`get_initial_url()`; URLs that arrive while the app is running reach
subscribers:

```python
unsubscribe = pn.Linking.add_listener(lambda url: navigate_to(url))
```

## Permissions (runtime)

[`Permissions`][pythonnative.Permissions] normalizes the iOS/Android
Expand Down
19 changes: 15 additions & 4 deletions docs/guides/permissions.md
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ permissions, and (where relevant) iOS background modes.
| `face_id` | Biometric auth | `NSFaceIDUsageDescription` | `USE_BIOMETRIC` |
| `bluetooth` | Nearby Bluetooth devices | `NSBluetoothAlwaysUsageDescription` | `BLUETOOTH_CONNECT`, `BLUETOOTH_SCAN` |
| `speech_recognition` | Speech recognition (iOS) | `NSSpeechRecognitionUsageDescription` | – |
| `notifications` | Local/push notifications | – | `POST_NOTIFICATIONS` |
| `notifications` | Show local notifications | – | `POST_NOTIFICATIONS` |
| `remote_notifications` | Server-sent (remote) push | – (adds the `aps-environment` entitlement and `remote-notification` background mode) | `POST_NOTIFICATIONS` |
| `vibration` | Haptics / vibration | – | `VIBRATE` |
| `background_audio` | Play audio in background | – (adds `audio` background mode) | `FOREGROUND_SERVICE` |
| `background_fetch` | Periodic background fetch | – (adds `fetch` background mode) | – |
Expand All @@ -64,9 +65,19 @@ need no iOS usage string, so declaring them as `true` is enough.

### Background modes

`location_always`, `background_audio`, and `background_fetch` also add
the corresponding values to the iOS `UIBackgroundModes` array
(`location`, `audio`, `fetch`) so the OS keeps your app scheduled.
`location_always`, `background_audio`, `background_fetch`, and
`remote_notifications` also add the corresponding values to the iOS
`UIBackgroundModes` array (`location`, `audio`, `fetch`,
`remote-notification`) so the OS keeps your app scheduled.

### Entitlements

Some capabilities need a code-signing entitlement rather than an
`Info.plist` key. Declaring `remote_notifications` generates an
`.entitlements` file with `aps-environment` and wires it into the
build, so `await pn.Notifications.get_device_token()` can register with
APNs. (Distribution builds get the production APNs environment
automatically when Xcode re-signs the archive for the store.)

## Always-on permissions

Expand Down
Loading
Loading

Back | FazBrowse Home | New Git URL