| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,13 +7,21 @@ the documented behavior never drifts from the code. | |||
| 7 | 7 | ## Subcommands | |
| 8 | 8 | ||
| 9 | 9 | - `pn init [name]`: scaffold a new project (creates `app/`, | |
| 10 | - `pythonnative.json`, `requirements.txt`, `.gitignore`). | ||
| 10 | + `pythonnative.toml`, `.gitignore`). Flag: `--force` to overwrite | ||
| 11 | + existing files. See [Configuration](../guides/configuration.md). | ||
| 12 | + - `pn doctor [android|ios]`: diagnose the local toolchain and validate | ||
| 13 | + `pythonnative.toml`. Exits non-zero when something will block a build. | ||
| 11 | 14 | - `pn preview [component]`: render the app in a desktop (Tkinter) window | |
| 12 | 15 | with Fast Refresh — the fastest way to iterate on UI. Flags: | |
| 13 | 16 | `--width`, `--height`, `--title`, `--no-hot-reload`. See the | |
| 14 | 17 | [Desktop preview guide](../guides/desktop-preview.md). | |
| 15 | 18 | - `pn run android|ios`: build and run on a connected device or | |
| 16 | 19 | simulator. Flags: `--prepare-only`, `--hot-reload`, `--no-logs`. | |
| 20 | + - `pn build android|ios`: build distributable artifacts (release by | ||
| 21 | + default). Flag: `--debug` for the debug variant. See | ||
| 22 | + [Building for release](../guides/building-for-release.md). | ||
| 23 | + - `pn app-id android|ios`: print the resolved application id (Android) | ||
| 24 | + or bundle id (iOS) — handy for scripts and CI. | ||
| 17 | 25 | - `pn clean`: remove the local `build/` directory. | |
| 18 | 26 | ||
| 19 | 27 | ::: pythonnative.cli.pn | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -187,8 +187,8 @@ def App(): | |||
| 187 | 187 | The entry point [`create_screen`][pythonnative.create_screen] is called | |
| 188 | 188 | internally by native templates to bootstrap your root component. You | |
| 189 | 189 | don't call it directly: name your top-level component `App` (so the | |
| 190 | - templates can find it by convention) and `pythonnative.json` points | ||
| 191 | - at the module that defines it. | ||
| 190 | + templates can find it by convention) and `app.entry_point` in | ||
| 191 | + `pythonnative.toml` points at the module that defines it. | ||
| 192 | 192 | ||
| 193 | 193 | ## State and re-rendering | |
| 194 | 194 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,8 +14,9 @@ pn init MyApp | |||
| 14 | 14 | This scaffolds: | |
| 15 | 15 | ||
| 16 | 16 | - `app/` with a minimal `main.py` | |
| 17 | - - `pythonnative.json` project config | ||
| 18 | - - `requirements.txt` | ||
| 17 | + - `pythonnative.toml` — your project configuration: app id, version, | ||
| 18 | + permissions, assets, and signing. See | ||
| 19 | + [Configuration](guides/configuration.md). | ||
| 19 | 20 | - `.gitignore` | |
| 20 | 21 | ||
| 21 | 22 | A minimal `app/main.py` looks like: | |
@@ -66,6 +67,34 @@ Key ideas: | |||
| 66 | 67 | ||
| 67 | 68 | When the root `Stack.Navigator` is rendered inside the host's first screen, `navigate(...)` and `go_back()` drive the **native** navigation controller (UINavigationController on iOS, AndroidX Navigation Component on Android). Each pushed screen runs in its own reconciler host, so state on the previous screen is preserved by the platform stack. | |
| 68 | 69 | ||
| 70 | + ## Configure your app | ||
| 71 | + | ||
| 72 | + Everything about your app's *identity* — its bundle/application id, | ||
| 73 | + display name, version, the device permissions it requests, its icon and | ||
| 74 | + splash, third-party packages, and signing — lives in a single | ||
| 75 | + `pythonnative.toml` at the project root: | ||
| 76 | + | ||
| 77 | + ```toml | ||
| 78 | + [app] | ||
| 79 | + id = "com.example.myapp" | ||
| 80 | + name = "myapp" | ||
| 81 | + display_name = "My App" | ||
| 82 | + version = "1.0.0" | ||
| 83 | + build = 1 | ||
| 84 | + | ||
| 85 | + [permissions] | ||
| 86 | + camera = "Scan receipts with your camera." | ||
| 87 | + notifications = true | ||
| 88 | + | ||
| 89 | + [assets] | ||
| 90 | + icon = "assets/icon.png" | ||
| 91 | + ``` | ||
| 92 | + | ||
| 93 | + The build system reads this file for every command, so `pn run`, | ||
| 94 | + `pn build`, `pn doctor`, and `pn app-id` all stay in sync. See the full | ||
| 95 | + [Configuration reference](guides/configuration.md) and the | ||
| 96 | + [Permissions guide](guides/permissions.md). | ||
| 97 | + | ||
| 69 | 98 | ## Preview on your desktop | |
| 70 | 99 | ||
| 71 | 100 | The fastest way to iterate is `pn preview`, which renders your app in a | |
@@ -177,6 +206,36 @@ pn run android --no-logs | |||
| 177 | 206 | pn run ios --no-logs | |
| 178 | 207 | ``` | |
| 179 | 208 | ||
| 209 | + ## Check your toolchain | ||
| 210 | + | ||
| 211 | + Before your first build, run `pn doctor` to verify the local toolchain | ||
| 212 | + (Java/Android SDK for Android; Xcode/Simulator and a signing team for | ||
| 213 | + iOS) and validate your `pythonnative.toml`: | ||
| 214 | + | ||
| 215 | + ```bash | ||
| 216 | + pn doctor # check everything | ||
| 217 | + pn doctor android # only Android-relevant checks | ||
| 218 | + pn doctor ios # only iOS-relevant checks | ||
| 219 | + ``` | ||
| 220 | + | ||
| 221 | + It prints `[ok]` / `[!]` / `[x]` for each check and exits non-zero when | ||
| 222 | + something will block a build, so it's safe to run in CI. | ||
| 223 | + | ||
| 224 | + ## Build for release | ||
| 225 | + | ||
| 226 | + When you're ready to ship, `pn build` produces signed, distributable | ||
| 227 | + artifacts: | ||
| 228 | + | ||
| 229 | + ```bash | ||
| 230 | + pn build android # release APK + AAB | ||
| 231 | + pn build ios # signed .ipa via xcodebuild archive/export | ||
| 232 | + ``` | ||
| 233 | + | ||
| 234 | + Release builds need signing configured in `pythonnative.toml` (a | ||
| 235 | + keystore for Android, a development team for iOS). See | ||
| 236 | + [Building for release](guides/building-for-release.md) for the full | ||
| 237 | + walkthrough. | ||
| 238 | + | ||
| 180 | 239 | ## Clean | |
| 181 | 240 | ||
| 182 | 241 | Remove the build artifacts safely: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,7 +15,7 @@ The native Android template uses | |||
| 15 | 15 | [`create_screen`][pythonnative.create_screen] internally to bootstrap your | |
| 16 | 16 | root component inside a `ScreenFragment`. You don't call `create_screen` | |
| 17 | 17 | directly; just export your component and configure the entry point in | |
| 18 | - `pythonnative.json`. | ||
| 18 | + `pythonnative.toml` (`app.entry_point`). | ||
| 19 | 19 | ||
| 20 | 20 | ## Run | |
| 21 | 21 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,216 @@ | |||
| 1 | + # Building for release | ||
| 2 | + | ||
| 3 | + `pn run` is for iterating on a device or simulator. When you're ready to | ||
| 4 | + ship, `pn build` produces standalone, distributable artifacts: | ||
| 5 | + | ||
| 6 | + ```bash | ||
| 7 | + pn build android # release APK + AAB | ||
| 8 | + pn build ios # signed .ipa via xcodebuild archive + export | ||
| 9 | + ``` | ||
| 10 | + | ||
| 11 | + Pass `--debug` to build the debug variant instead (a debug APK on | ||
| 12 | + Android; a Simulator `.app` on iOS): | ||
| 13 | + | ||
| 14 | + ```bash | ||
| 15 | + pn build android --debug | ||
| 16 | + pn build ios --debug | ||
| 17 | + ``` | ||
| 18 | + | ||
| 19 | + `pn build` reads identity, permissions, assets, and signing from | ||
| 20 | + [`pythonnative.toml`](configuration.md). Run [`pn doctor`](#check-first-pn-doctor) | ||
| 21 | + first to confirm the toolchain is ready. | ||
| 22 | + | ||
| 23 | + --- | ||
| 24 | + | ||
| 25 | + ## Check first: `pn doctor` | ||
| 26 | + | ||
| 27 | + ```bash | ||
| 28 | + pn doctor android | ||
| 29 | + pn doctor ios | ||
| 30 | + ``` | ||
| 31 | + | ||
| 32 | + `pn doctor` validates `pythonnative.toml` and checks the platform | ||
| 33 | + toolchain — for Android: `adb`, a JDK, and whether release signing is | ||
| 34 | + configured; for iOS: macOS, Xcode, `simctl`, and a development team. It | ||
| 35 | + exits non-zero on anything that will block a build, so you can gate CI | ||
| 36 | + on it. | ||
| 37 | + | ||
| 38 | + --- | ||
| 39 | + | ||
| 40 | + ## Android | ||
| 41 | + | ||
| 42 | + `pn build android` runs `assembleRelease` and `bundleRelease`, producing | ||
| 43 | + both an APK and a Play-ready AAB. Artifacts are reported at the end of | ||
| 44 | + the build and live under the staged project: | ||
| 45 | + | ||
| 46 | + ``` | ||
| 47 | + build/android/android_template/app/build/outputs/apk/release/app-release.apk | ||
| 48 | + build/android/android_template/app/build/outputs/bundle/release/app-release.aab | ||
| 49 | + ``` | ||
| 50 | + | ||
| 51 | + ### Signing | ||
| 52 | + | ||
| 53 | + Without signing configured, Gradle emits an **unsigned** release APK | ||
| 54 | + (`app-release-unsigned.apk`) — fine for inspection, not for the store. | ||
| 55 | + To produce signed artifacts, create a keystore once: | ||
| 56 | + | ||
| 57 | + ```bash | ||
| 58 | + keytool -genkeypair -v -keystore release.keystore \ | ||
| 59 | + -alias myapp -keyalg RSA -keysize 2048 -validity 10000 | ||
| 60 | + ``` | ||
| 61 | + | ||
| 62 | + …then point `pythonnative.toml` at it: | ||
| 63 | + | ||
| 64 | + ```toml | ||
| 65 | + [android.signing] | ||
| 66 | + keystore = "release.keystore" | ||
| 67 | + key_alias = "myapp" | ||
| 68 | + # store_password_env / key_password_env default to | ||
| 69 | + # PN_ANDROID_KEYSTORE_PASSWORD / PN_ANDROID_KEY_PASSWORD | ||
| 70 | + ``` | ||
| 71 | + | ||
| 72 | + Passwords are **never** stored in the config — only the names of the | ||
| 73 | + environment variables that hold them. Provide the secrets at build time: | ||
| 74 | + | ||
| 75 | + ```bash | ||
| 76 | + export PN_ANDROID_KEYSTORE_PASSWORD=… | ||
| 77 | + export PN_ANDROID_KEY_PASSWORD=… | ||
| 78 | + pn build android | ||
| 79 | + ``` | ||
| 80 | + | ||
| 81 | + When signing is configured, `pn` injects a Gradle `signingConfig` into | ||
| 82 | + the release build so the resulting APK/AAB are signed and upload-ready. | ||
| 83 | + | ||
| 84 | + !!! tip "Keep keystores out of git" | ||
| 85 | + Commit neither the keystore nor the passwords. Store the keystore as | ||
| 86 | + a CI secret/file and inject the passwords via environment variables. | ||
| 87 | + | ||
| 88 | + --- | ||
| 89 | + | ||
| 90 | + ## iOS | ||
| 91 | + | ||
| 92 | + `pn build ios` archives the app for a device with `xcodebuild archive`, | ||
| 93 | + embeds the device CPython slice into the archive, and exports a signed | ||
| 94 | + `.ipa` with `xcodebuild -exportArchive`. Outputs: | ||
| 95 | + | ||
| 96 | + ``` | ||
| 97 | + build/ios/ios_template/build/export/*.ipa | ||
| 98 | + build/ios/ios_template/build/ios_template.xcarchive | ||
| 99 | + ``` | ||
| 100 | + | ||
| 101 | + !!! warning "Experimental" | ||
| 102 | + The device archive/export path embeds and re-signs the embedded | ||
| 103 | + Python framework. Treat it as experimental and verify on a real | ||
| 104 | + device before relying on it for store submission. | ||
| 105 | + | ||
| 106 | + ### Signing | ||
| 107 | + | ||
| 108 | + Set your Apple Developer Team ID and an export method: | ||
| 109 | + | ||
| 110 | + ```toml | ||
| 111 | + [ios] | ||
| 112 | + development_team = "ABCDE12345" | ||
| 113 | + | ||
| 114 | + [ios.signing] | ||
| 115 | + export_method = "app-store" # development | ad-hoc | app-store | enterprise | ||
| 116 | + provisioning_profile = "My App Distribution" | ||
| 117 | + ``` | ||
| 118 | + | ||
| 119 | + `development_team` drives signing during `archive`; `export_method` and | ||
| 120 | + the optional `provisioning_profile` are written into the | ||
| 121 | + `exportOptions.plist` that `xcodebuild -exportArchive` consumes. If | ||
| 122 | + export fails, the error points you back at `[ios.signing]`. | ||
| 123 | + | ||
| 124 | + ### Embedded Python runtime | ||
| 125 | + | ||
| 126 | + iOS has no system Python, so PythonNative embeds CPython from the | ||
| 127 | + [Python-Apple-support](https://github.com/beeware/Python-Apple-support) | ||
| 128 | + project. On the first iOS build, `pn` downloads the pinned, checksum- | ||
| 129 | + verified runtime for your `app.python_version` and caches it under | ||
| 130 | + `build/ios/ios_runtime/`. The correct slice (Simulator vs. device) is | ||
| 131 | + embedded into the app bundle along with the standard library, your | ||
| 132 | + `app/` sources, the bundled `pythonnative` package, and any pure-Python | ||
| 133 | + `[requirements].packages`. | ||
| 134 | + | ||
| 135 | + iOS currently ships a verified runtime for **Python 3.11**; set | ||
| 136 | + `python_version = "3.11"` in `[app]` for device builds. | ||
| 137 | + | ||
| 138 | + --- | ||
| 139 | + | ||
| 140 | + ## App icon and splash | ||
| 141 | + | ||
| 142 | + Provide a single high-resolution source image and PythonNative renders | ||
| 143 | + every per-platform, per-density variant at build time: | ||
| 144 | + | ||
| 145 | + ```toml | ||
| 146 | + [assets] | ||
| 147 | + icon = "assets/icon.png" # 1024x1024 PNG | ||
| 148 | + splash = "assets/splash.png" | ||
| 149 | + ``` | ||
| 150 | + | ||
| 151 | + - **iOS** — a universal `AppIcon.appiconset` (Xcode resizes the rest) | ||
| 152 | + and a `Splash` image set referenced by the generated launch screen. | ||
| 153 | + - **Android** — `mipmap-*` launcher icons at every density plus a round | ||
| 154 | + variant, and a centered icon for the Android 12+ splash screen. | ||
| 155 | + | ||
| 156 | + Image processing needs [Pillow](https://python-pillow.org/), an optional | ||
| 157 | + dependency: | ||
| 158 | + | ||
| 159 | + ```bash | ||
| 160 | + pip install 'pythonnative[build]' | ||
| 161 | + ``` | ||
| 162 | + | ||
| 163 | + If Pillow isn't installed the build still succeeds — it just keeps the | ||
| 164 | + template's default assets. `pn doctor` reports whether Pillow is | ||
| 165 | + available. | ||
| 166 | + | ||
| 167 | + --- | ||
| 168 | + | ||
| 169 | + ## Versioning | ||
| 170 | + | ||
| 171 | + Two fields in `[app]` drive the store-visible version and the internal | ||
| 172 | + build number: | ||
| 173 | + | ||
| 174 | + ```toml | ||
| 175 | + [app] | ||
| 176 | + version = "1.2.0" # CFBundleShortVersionString / versionName | ||
| 177 | + build = 7 # CFBundleVersion / versionCode (bump every upload) | ||
| 178 | + ``` | ||
| 179 | + | ||
| 180 | + Both stores reject a new upload that reuses an existing build number, so | ||
| 181 | + increment `build` for each submission. | ||
| 182 | + | ||
| 183 | + --- | ||
| 184 | + | ||
| 185 | + ## Continuous integration | ||
| 186 | + | ||
| 187 | + A typical release job: | ||
| 188 | + | ||
| 189 | + ```bash | ||
| 190 | + pip install 'pythonnative[build]' | ||
| 191 | + pn doctor android # fail fast on a misconfigured runner | ||
| 192 | + export PN_ANDROID_KEYSTORE_PASSWORD=$KEYSTORE_PW | ||
| 193 | + export PN_ANDROID_KEY_PASSWORD=$KEY_PW | ||
| 194 | + pn build android | ||
| 195 | + ``` | ||
| 196 | + | ||
| 197 | + `pn app-id android` / `pn app-id ios` print the resolved id, which is | ||
| 198 | + handy for downstream steps (uploaders, smoke tests) that need it without | ||
| 199 | + re-parsing the config. | ||
| 200 | + | ||
| 201 | + --- | ||
| 202 | + | ||
| 203 | + ## Prepare without building | ||
| 204 | + | ||
| 205 | + To hand off to Android Studio or Xcode instead of building from the CLI, | ||
| 206 | + stage and configure the native project without compiling: | ||
| 207 | + | ||
| 208 | + ```bash | ||
| 209 | + pn run android --prepare-only | ||
| 210 | + pn run ios --prepare-only | ||
| 211 | + ``` | ||
| 212 | + | ||
| 213 | + This writes a fully configured project (identity, permissions, icons, | ||
| 214 | + relocated Android package) under `build/`, which you can open and build | ||
| 215 | + with the native IDE — useful for debugging signing or build issues with | ||
| 216 | + the platform's own tooling. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments