pythonnative 0.40.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonnative/__init__.py +411 -0
- pythonnative/_ios_log.py +92 -0
- pythonnative/_yoga.cp313-win_amd64.pyd +0 -0
- pythonnative/alerts.py +236 -0
- pythonnative/animated.py +1687 -0
- pythonnative/animation_graph.py +78 -0
- pythonnative/appearance.py +124 -0
- pythonnative/bootstrap.py +100 -0
- pythonnative/bridge/__init__.py +302 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +170 -0
- pythonnative/bridge/commits.py +114 -0
- pythonnative/bridge/fake.py +243 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/bridge/web.py +489 -0
- pythonnative/cli/__init__.py +7 -0
- pythonnative/cli/pn.py +1214 -0
- pythonnative/component.py +271 -0
- pythonnative/components/__init__.py +111 -0
- pythonnative/components/_base.py +166 -0
- pythonnative/components/controls.py +521 -0
- pythonnative/components/layout.py +518 -0
- pythonnative/components/lists.py +785 -0
- pythonnative/components/media.py +204 -0
- pythonnative/components/overlays.py +107 -0
- pythonnative/components/pressable.py +252 -0
- pythonnative/components/structural.py +167 -0
- pythonnative/components/text.py +291 -0
- pythonnative/devclient.py +824 -0
- pythonnative/devserver/__init__.py +34 -0
- pythonnative/devserver/server.py +718 -0
- pythonnative/devserver/static/animation_graph.js +82 -0
- pythonnative/devserver/static/bridge.js +147 -0
- pythonnative/devserver/static/colors.js +70 -0
- pythonnative/devserver/static/host.js +552 -0
- pythonnative/devserver/static/index.html +60 -0
- pythonnative/devserver/static/layout.js +85 -0
- pythonnative/devserver/static/preview.css +415 -0
- pythonnative/devserver/static/renderer.js +1994 -0
- pythonnative/devserver/static/shell.js +345 -0
- pythonnative/devserver/static/yoga/LICENSE +21 -0
- pythonnative/devserver/static/yoga/binaries/yoga-wasm-base64-esm.js +75 -0
- pythonnative/devserver/static/yoga/src/generated/YGEnums.d.ts +189 -0
- pythonnative/devserver/static/yoga/src/generated/YGEnums.js +211 -0
- pythonnative/devserver/static/yoga/src/generated/YGEnums.js.map +1 -0
- pythonnative/devserver/static/yoga/src/index.d.ts +12 -0
- pythonnative/devserver/static/yoga/src/index.js +16 -0
- pythonnative/devserver/static/yoga/src/index.js.map +1 -0
- pythonnative/devserver/static/yoga/src/load.d.ts +11 -0
- pythonnative/devserver/static/yoga/src/load.js +17 -0
- pythonnative/devserver/static/yoga/src/load.js.map +1 -0
- pythonnative/devserver/static/yoga/src/wrapAssembly.d.ts +155 -0
- pythonnative/devserver/static/yoga/src/wrapAssembly.js +125 -0
- pythonnative/devserver/static/yoga/src/wrapAssembly.js.map +1 -0
- pythonnative/devserver/watcher.py +267 -0
- pythonnative/devserver/ws.py +421 -0
- pythonnative/diagnostics.py +241 -0
- pythonnative/element.py +159 -0
- pythonnative/equality.py +19 -0
- pythonnative/events.py +231 -0
- pythonnative/gestures.py +1333 -0
- pythonnative/hooks.py +1621 -0
- pythonnative/hosts/__init__.py +63 -0
- pythonnative/hosts/base.py +596 -0
- pythonnative/hosts/native.py +302 -0
- pythonnative/hot_reload.py +585 -0
- pythonnative/layout.py +328 -0
- pythonnative/mutations.py +142 -0
- pythonnative/native/android/build.gradle +52 -0
- pythonnative/native/android/gradle.properties +3 -0
- pythonnative/native/android/settings.gradle +12 -0
- pythonnative/native/android/src/main/AndroidManifest.xml +11 -0
- pythonnative/native/android/src/main/cpp/CMakeLists.txt +9 -0
- pythonnative/native/android/src/main/cpp/yoga_jni.cpp +52 -0
- pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeModules.kt +5 -0
- pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeProps.kt +3705 -0
- pythonnative/native/android/src/main/java/com/pythonnative/generated/PNContracts.kt +129 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/PNBridge.kt +261 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationGraph.kt +108 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +257 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/CommitState.kt +106 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +40 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +117 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +37 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +32 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +162 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +86 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScreenStackManager.kt +110 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +250 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +159 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/NativeLayout.kt +145 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/YogaNode.kt +29 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +61 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +158 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +99 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +188 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNAccessibilityDelegate.kt +48 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNBorderDrawable.kt +76 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNFrameLayout.kt +49 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/PromiseTest.kt +89 -0
- pythonnative/native/ios/Package.swift +23 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimationGraph.swift +199 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimator.swift +312 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNBridge.swift +255 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNCommit.swift +104 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNRegistry.swift +142 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNTransaction.swift +166 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNButtonManagers.swift +207 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNImageManager.swift +222 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScreenStackManager.swift +107 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextInputManager.swift +297 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +155 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeModules.swift +1 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeProps.swift +3775 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Generated/PNContracts.swift +46 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Layout/PNLayout.swift +173 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/HostModule.swift +80 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/PNNativeModule.swift +168 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/PermissionModules.swift +266 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNViewController.swift +237 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNAnimationGraphTests.swift +26 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNCommitTests.swift +86 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNManagerTests.swift +126 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNModuleTests.swift +111 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/native/yoga/LICENSE +21 -0
- pythonnative/native/yoga/Package.swift +9 -0
- pythonnative/native/yoga/VENDORED.md +2 -0
- pythonnative/native/yoga/include/PNStyle.h +10 -0
- pythonnative/native/yoga/include/module.modulemap +5 -0
- pythonnative/native/yoga/include/yoga/YGConfig.h +158 -0
- pythonnative/native/yoga/include/yoga/YGEnums.h +142 -0
- pythonnative/native/yoga/include/yoga/YGMacros.h +99 -0
- pythonnative/native/yoga/include/yoga/YGNode.h +304 -0
- pythonnative/native/yoga/include/yoga/YGNodeLayout.h +35 -0
- pythonnative/native/yoga/include/yoga/YGNodeStyle.h +130 -0
- pythonnative/native/yoga/include/yoga/YGPixelGrid.h +29 -0
- pythonnative/native/yoga/include/yoga/YGValue.h +84 -0
- pythonnative/native/yoga/include/yoga/Yoga.h +21 -0
- pythonnative/native/yoga/python.cpp +16 -0
- pythonnative/native/yoga/style.cpp +272 -0
- pythonnative/native/yoga/yoga/CMakeLists.txt +41 -0
- pythonnative/native/yoga/yoga/YGConfig.cpp +92 -0
- pythonnative/native/yoga/yoga/YGConfig.h +158 -0
- pythonnative/native/yoga/yoga/YGEnums.cpp +262 -0
- pythonnative/native/yoga/yoga/YGEnums.h +142 -0
- pythonnative/native/yoga/yoga/YGMacros.h +99 -0
- pythonnative/native/yoga/yoga/YGNode.cpp +366 -0
- pythonnative/native/yoga/yoga/YGNode.h +304 -0
- pythonnative/native/yoga/yoga/YGNodeLayout.cpp +92 -0
- pythonnative/native/yoga/yoga/YGNodeLayout.h +35 -0
- pythonnative/native/yoga/yoga/YGNodeStyle.cpp +405 -0
- pythonnative/native/yoga/yoga/YGNodeStyle.h +130 -0
- pythonnative/native/yoga/yoga/YGPixelGrid.cpp +22 -0
- pythonnative/native/yoga/yoga/YGPixelGrid.h +29 -0
- pythonnative/native/yoga/yoga/YGValue.cpp +20 -0
- pythonnative/native/yoga/yoga/YGValue.h +84 -0
- pythonnative/native/yoga/yoga/Yoga.h +21 -0
- pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.cpp +563 -0
- pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.h +41 -0
- pythonnative/native/yoga/yoga/algorithm/Align.h +72 -0
- pythonnative/native/yoga/yoga/algorithm/Baseline.cpp +78 -0
- pythonnative/native/yoga/yoga/algorithm/Baseline.h +21 -0
- pythonnative/native/yoga/yoga/algorithm/BoundAxis.h +79 -0
- pythonnative/native/yoga/yoga/algorithm/Cache.cpp +121 -0
- pythonnative/native/yoga/yoga/algorithm/Cache.h +30 -0
- pythonnative/native/yoga/yoga/algorithm/CalculateLayout.cpp +2428 -0
- pythonnative/native/yoga/yoga/algorithm/CalculateLayout.h +38 -0
- pythonnative/native/yoga/yoga/algorithm/FlexDirection.h +120 -0
- pythonnative/native/yoga/yoga/algorithm/FlexLine.cpp +124 -0
- pythonnative/native/yoga/yoga/algorithm/FlexLine.h +75 -0
- pythonnative/native/yoga/yoga/algorithm/PixelGrid.cpp +132 -0
- pythonnative/native/yoga/yoga/algorithm/PixelGrid.h +29 -0
- pythonnative/native/yoga/yoga/algorithm/SizingMode.h +73 -0
- pythonnative/native/yoga/yoga/algorithm/TrailingPosition.h +44 -0
- pythonnative/native/yoga/yoga/config/Config.cpp +135 -0
- pythonnative/native/yoga/yoga/config/Config.h +92 -0
- pythonnative/native/yoga/yoga/debug/AssertFatal.cpp +53 -0
- pythonnative/native/yoga/yoga/debug/AssertFatal.h +29 -0
- pythonnative/native/yoga/yoga/debug/Log.cpp +107 -0
- pythonnative/native/yoga/yoga/debug/Log.h +34 -0
- pythonnative/native/yoga/yoga/enums/Align.h +47 -0
- pythonnative/native/yoga/yoga/enums/BoxSizing.h +40 -0
- pythonnative/native/yoga/yoga/enums/Dimension.h +40 -0
- pythonnative/native/yoga/yoga/enums/Direction.h +41 -0
- pythonnative/native/yoga/yoga/enums/Display.h +41 -0
- pythonnative/native/yoga/yoga/enums/Edge.h +47 -0
- pythonnative/native/yoga/yoga/enums/Errata.h +41 -0
- pythonnative/native/yoga/yoga/enums/ExperimentalFeature.h +39 -0
- pythonnative/native/yoga/yoga/enums/FlexDirection.h +42 -0
- pythonnative/native/yoga/yoga/enums/Gutter.h +41 -0
- pythonnative/native/yoga/yoga/enums/Justify.h +44 -0
- pythonnative/native/yoga/yoga/enums/LogLevel.h +44 -0
- pythonnative/native/yoga/yoga/enums/MeasureMode.h +41 -0
- pythonnative/native/yoga/yoga/enums/NodeType.h +40 -0
- pythonnative/native/yoga/yoga/enums/Overflow.h +41 -0
- pythonnative/native/yoga/yoga/enums/PhysicalEdge.h +21 -0
- pythonnative/native/yoga/yoga/enums/PositionType.h +41 -0
- pythonnative/native/yoga/yoga/enums/Unit.h +42 -0
- pythonnative/native/yoga/yoga/enums/Wrap.h +41 -0
- pythonnative/native/yoga/yoga/enums/YogaEnums.h +85 -0
- pythonnative/native/yoga/yoga/event/event.cpp +87 -0
- pythonnative/native/yoga/yoga/event/event.h +130 -0
- pythonnative/native/yoga/yoga/module.modulemap +21 -0
- pythonnative/native/yoga/yoga/node/CachedMeasurement.h +53 -0
- pythonnative/native/yoga/yoga/node/LayoutResults.cpp +48 -0
- pythonnative/native/yoga/yoga/node/LayoutResults.h +122 -0
- pythonnative/native/yoga/yoga/node/LayoutableChildren.h +148 -0
- pythonnative/native/yoga/yoga/node/Node.cpp +443 -0
- pythonnative/native/yoga/yoga/node/Node.h +337 -0
- pythonnative/native/yoga/yoga/numeric/Comparison.h +81 -0
- pythonnative/native/yoga/yoga/numeric/FloatOptional.h +93 -0
- pythonnative/native/yoga/yoga/style/SmallValueBuffer.h +133 -0
- pythonnative/native/yoga/yoga/style/Style.h +757 -0
- pythonnative/native/yoga/yoga/style/StyleLength.h +107 -0
- pythonnative/native/yoga/yoga/style/StyleValueHandle.h +98 -0
- pythonnative/native/yoga/yoga/style/StyleValuePool.h +127 -0
- pythonnative/native_modules/__init__.py +124 -0
- pythonnative/native_modules/app_state.py +99 -0
- pythonnative/native_modules/battery.py +75 -0
- pythonnative/native_modules/biometrics.py +42 -0
- pythonnative/native_modules/camera.py +65 -0
- pythonnative/native_modules/clipboard.py +46 -0
- pythonnative/native_modules/fallback.py +324 -0
- pythonnative/native_modules/file_system.py +145 -0
- pythonnative/native_modules/haptics.py +75 -0
- pythonnative/native_modules/linking.py +123 -0
- pythonnative/native_modules/location.py +75 -0
- pythonnative/native_modules/net_info.py +112 -0
- pythonnative/native_modules/notifications.py +129 -0
- pythonnative/native_modules/permissions.py +108 -0
- pythonnative/native_modules/registry.py +526 -0
- pythonnative/native_modules/secure_store.py +52 -0
- pythonnative/native_modules/share.py +51 -0
- pythonnative/native_views/__init__.py +375 -0
- pythonnative/native_views/base.py +316 -0
- pythonnative/native_views/bridge_backend.py +277 -0
- pythonnative/navigation/__init__.py +103 -0
- pythonnative/navigation/container.py +154 -0
- pythonnative/navigation/handle.py +540 -0
- pythonnative/navigation/hooks.py +133 -0
- pythonnative/navigation/host.py +58 -0
- pythonnative/navigation/linking.py +200 -0
- pythonnative/navigation/navigators.py +659 -0
- pythonnative/navigation/screen.py +149 -0
- pythonnative/navigation/state.py +269 -0
- pythonnative/net.py +248 -0
- pythonnative/platform.py +166 -0
- pythonnative/platform_metrics.py +252 -0
- pythonnative/preview.py +287 -0
- pythonnative/profiling.py +101 -0
- pythonnative/project/__init__.py +68 -0
- pythonnative/project/android.py +557 -0
- pythonnative/project/builder.py +817 -0
- pythonnative/project/config.py +773 -0
- pythonnative/project/deps.py +719 -0
- pythonnative/project/devices.py +304 -0
- pythonnative/project/doctor.py +283 -0
- pythonnative/project/fingerprint.py +171 -0
- pythonnative/project/icons.py +247 -0
- pythonnative/project/ios.py +325 -0
- pythonnative/project/lockfile.py +100 -0
- pythonnative/project/permissions.py +361 -0
- pythonnative/project/plugins.py +519 -0
- pythonnative/project/runtime_assets.py +183 -0
- pythonnative/py.typed +0 -0
- pythonnative/query.py +153 -0
- pythonnative/reconciler/__init__.py +29 -0
- pythonnative/reconciler/boundaries.py +375 -0
- pythonnative/reconciler/children.py +88 -0
- pythonnative/reconciler/core.py +1165 -0
- pythonnative/reconciler/layout_pass.py +393 -0
- pythonnative/reconciler/vnode.py +266 -0
- pythonnative/refresh.py +55 -0
- pythonnative/runtime.py +306 -0
- pythonnative/scheduler.py +159 -0
- pythonnative/sdk/__init__.py +153 -0
- pythonnative/sdk/_components.py +427 -0
- pythonnative/sdk/builtins.py +91 -0
- pythonnative/sdk/codegen.py +149 -0
- pythonnative/sdk/module_codegen.py +184 -0
- pythonnative/sdk/schema.py +224 -0
- pythonnative/sdk/templates/contracts.kt +46 -0
- pythonnative/sdk/templates/contracts.swift +46 -0
- pythonnative/storage.py +184 -0
- pythonnative/style.py +841 -0
- pythonnative/suspense.py +183 -0
- pythonnative/templates/android_template/app/build.gradle +75 -0
- pythonnative/templates/android_template/app/proguard-rules.pro +21 -0
- pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +24 -0
- pythonnative/templates/android_template/app/src/debug/AndroidManifest.xml +7 -0
- pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +32 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +130 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -0
- pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
- pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +30 -0
- pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +10 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +22 -0
- pythonnative/templates/android_template/app/src/main/res/values/colors.xml +5 -0
- pythonnative/templates/android_template/app/src/main/res/values/strings.xml +5 -0
- pythonnative/templates/android_template/app/src/main/res/values/themes.xml +9 -0
- pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +7 -0
- pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +13 -0
- pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +19 -0
- pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +17 -0
- pythonnative/templates/android_template/build.gradle +10 -0
- pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
- pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +6 -0
- pythonnative/templates/android_template/gradle.properties +23 -0
- pythonnative/templates/android_template/gradlew +185 -0
- pythonnative/templates/android_template/gradlew.bat +89 -0
- pythonnative/templates/android_template/settings.gradle +17 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +51 -0
- pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
- pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +6 -0
- pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +25 -0
- pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
- pythonnative/templates/ios_template/ios_template/Info.plist +28 -0
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +390 -0
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +61 -0
- pythonnative/templates/ios_template/ios_template/ViewController.swift +23 -0
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +745 -0
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +36 -0
- pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +41 -0
- pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +32 -0
- pythonnative/testing/__init__.py +53 -0
- pythonnative/testing/backend.py +276 -0
- pythonnative/testing/harness.py +369 -0
- pythonnative/utils.py +145 -0
- pythonnative-0.40.0.dist-info/DELVEWHEEL +2 -0
- pythonnative-0.40.0.dist-info/METADATA +150 -0
- pythonnative-0.40.0.dist-info/RECORD +418 -0
- pythonnative-0.40.0.dist-info/WHEEL +5 -0
- pythonnative-0.40.0.dist-info/entry_points.txt +2 -0
- pythonnative-0.40.0.dist-info/licenses/LICENSE +21 -0
- pythonnative-0.40.0.dist-info/top_level.txt +1 -0
- pythonnative.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
"""Build orchestration for PythonNative projects.
|
|
2
|
+
|
|
3
|
+
The [`Builder`][pythonnative.project.builder.Builder] ties the pieces
|
|
4
|
+
together: it stages the bundled native template, runs the platform
|
|
5
|
+
[`configurators`][pythonnative.project.android], stages the Python
|
|
6
|
+
sources and (on iOS) the embedded CPython runtime into the native
|
|
7
|
+
project, and invokes the native toolchains (Gradle / Xcode).
|
|
8
|
+
|
|
9
|
+
On iOS, the staged project is self-contained: ``Python.xcframework`` is
|
|
10
|
+
linked at build time and an Xcode build phase installs the standard
|
|
11
|
+
library and app code into the bundle, so ``xcodebuild`` output is
|
|
12
|
+
directly runnable and archivable with no post-build patching.
|
|
13
|
+
Third-party packages are resolved per destination (see
|
|
14
|
+
[`deps`][pythonnative.project.deps]) into ``app_packages.iphoneos`` and
|
|
15
|
+
``app_packages.iphonesimulator``; the build phase installs the slice
|
|
16
|
+
matching the SDK being built.
|
|
17
|
+
|
|
18
|
+
All shell-outs go through a small
|
|
19
|
+
[`CommandRunner`][pythonnative.project.builder.CommandRunner] abstraction
|
|
20
|
+
so the orchestration logic can be unit tested with a recording fake
|
|
21
|
+
instead of a real device toolchain. The default
|
|
22
|
+
[`SubprocessRunner`][pythonnative.project.builder.SubprocessRunner] simply
|
|
23
|
+
delegates to :mod:`subprocess`.
|
|
24
|
+
|
|
25
|
+
Device interaction that genuinely needs a device (booting simulators,
|
|
26
|
+
installing, launching, streaming logs, hot reload) lives in the CLI; the
|
|
27
|
+
builder stops at producing installable/archivable artifacts.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import compileall
|
|
33
|
+
import json
|
|
34
|
+
import os
|
|
35
|
+
import platform as platform_module
|
|
36
|
+
import shutil
|
|
37
|
+
import subprocess
|
|
38
|
+
import sys
|
|
39
|
+
from dataclasses import dataclass, field
|
|
40
|
+
from importlib import resources
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import Callable, List, Optional, Sequence, Union
|
|
43
|
+
|
|
44
|
+
from . import android as android_config
|
|
45
|
+
from . import deps, runtime_assets
|
|
46
|
+
from . import ios as ios_config
|
|
47
|
+
from . import plugins as native_plugins
|
|
48
|
+
from .android import AndroidLayout
|
|
49
|
+
from .config import AppConfig
|
|
50
|
+
from .ios import IOSLayout
|
|
51
|
+
|
|
52
|
+
Logger = Callable[[str], None]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class BuildError(Exception):
|
|
56
|
+
"""Raised when a native build step fails.
|
|
57
|
+
|
|
58
|
+
Carries a user-facing message; the CLI prints it and exits non-zero.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ======================================================================
|
|
63
|
+
# Command runner abstraction
|
|
64
|
+
# ======================================================================
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class CommandResult:
|
|
69
|
+
"""The outcome of a single command invocation.
|
|
70
|
+
|
|
71
|
+
Attributes:
|
|
72
|
+
returncode: Process exit status.
|
|
73
|
+
stdout: Captured standard output (empty unless captured).
|
|
74
|
+
stderr: Captured standard error (empty unless captured).
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
returncode: int
|
|
78
|
+
stdout: str = ""
|
|
79
|
+
stderr: str = ""
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def ok(self) -> bool:
|
|
83
|
+
"""Whether the command exited successfully (return code 0)."""
|
|
84
|
+
return self.returncode == 0
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class CommandRunner:
|
|
88
|
+
"""Protocol for running external commands.
|
|
89
|
+
|
|
90
|
+
Implementations execute a command and return a
|
|
91
|
+
[`CommandResult`][pythonnative.project.builder.CommandResult]. Tests
|
|
92
|
+
provide a recording fake; production uses
|
|
93
|
+
[`SubprocessRunner`][pythonnative.project.builder.SubprocessRunner].
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def run(
|
|
97
|
+
self,
|
|
98
|
+
args: Sequence[str],
|
|
99
|
+
*,
|
|
100
|
+
cwd: Optional[Path] = None,
|
|
101
|
+
env: Optional[dict] = None,
|
|
102
|
+
capture: bool = False,
|
|
103
|
+
) -> CommandResult:
|
|
104
|
+
"""Run a command.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
args: The command and its arguments.
|
|
108
|
+
cwd: Working directory, or ``None`` for the current one.
|
|
109
|
+
env: Full environment mapping, or ``None`` to inherit.
|
|
110
|
+
capture: Whether to capture and return stdout/stderr.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
A [`CommandResult`][pythonnative.project.builder.CommandResult].
|
|
114
|
+
"""
|
|
115
|
+
raise NotImplementedError
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class SubprocessRunner(CommandRunner):
|
|
119
|
+
"""A [`CommandRunner`][pythonnative.project.builder.CommandRunner] backed by :mod:`subprocess`."""
|
|
120
|
+
|
|
121
|
+
def run(
|
|
122
|
+
self,
|
|
123
|
+
args: Sequence[str],
|
|
124
|
+
*,
|
|
125
|
+
cwd: Optional[Path] = None,
|
|
126
|
+
env: Optional[dict] = None,
|
|
127
|
+
capture: bool = False,
|
|
128
|
+
) -> CommandResult:
|
|
129
|
+
"""Execute ``args`` with :func:`subprocess.run`."""
|
|
130
|
+
completed = subprocess.run(
|
|
131
|
+
list(args),
|
|
132
|
+
cwd=str(cwd) if cwd else None,
|
|
133
|
+
env=env,
|
|
134
|
+
capture_output=capture,
|
|
135
|
+
text=True,
|
|
136
|
+
)
|
|
137
|
+
return CommandResult(
|
|
138
|
+
returncode=completed.returncode,
|
|
139
|
+
stdout=completed.stdout or "" if capture else "",
|
|
140
|
+
stderr=completed.stderr or "" if capture else "",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ======================================================================
|
|
145
|
+
# Result/state dataclasses
|
|
146
|
+
# ======================================================================
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass
|
|
150
|
+
class PreparedProject:
|
|
151
|
+
"""A staged, configured project ready to build.
|
|
152
|
+
|
|
153
|
+
Attributes:
|
|
154
|
+
platform: ``"android"`` or ``"ios"``.
|
|
155
|
+
build_dir: The ``build/<platform>`` directory.
|
|
156
|
+
project_dir: The staged native project directory.
|
|
157
|
+
app_id: The resolved application id / bundle id for the platform.
|
|
158
|
+
android: Android layout (when ``platform == "android"``).
|
|
159
|
+
ios: iOS layout (when ``platform == "ios"``).
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
platform: str
|
|
163
|
+
build_dir: Path
|
|
164
|
+
project_dir: Path
|
|
165
|
+
app_id: str
|
|
166
|
+
android: Optional[AndroidLayout] = None
|
|
167
|
+
ios: Optional[IOSLayout] = None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass
|
|
171
|
+
class BuildArtifacts:
|
|
172
|
+
"""Paths to artifacts produced by a release/standalone build.
|
|
173
|
+
|
|
174
|
+
Attributes:
|
|
175
|
+
paths: Output artifact paths (APK/AAB/IPA/.app).
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
paths: List[Path] = field(default_factory=list)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ======================================================================
|
|
182
|
+
# Template staging
|
|
183
|
+
# ======================================================================
|
|
184
|
+
|
|
185
|
+
_TEMPLATE_NAMES = {"android": "android_template", "ios": "ios_template"}
|
|
186
|
+
|
|
187
|
+
# A source checkout accumulates Gradle, SwiftPM, and Xcode outputs next to
|
|
188
|
+
# the native library sources; a staged project must start from a clean tree.
|
|
189
|
+
_TEMPLATE_IGNORE = shutil.ignore_patterns(
|
|
190
|
+
"build",
|
|
191
|
+
".build",
|
|
192
|
+
".gradle",
|
|
193
|
+
".cxx",
|
|
194
|
+
".kotlin",
|
|
195
|
+
".swiftpm",
|
|
196
|
+
"xcuserdata",
|
|
197
|
+
"DerivedData",
|
|
198
|
+
"__pycache__",
|
|
199
|
+
"*.pyc",
|
|
200
|
+
".DS_Store",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def template_name(platform: str) -> str:
|
|
205
|
+
"""The bundled template directory name for ``platform``.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
BuildError: For an unknown platform.
|
|
209
|
+
"""
|
|
210
|
+
try:
|
|
211
|
+
return _TEMPLATE_NAMES[platform]
|
|
212
|
+
except KeyError:
|
|
213
|
+
raise BuildError(f"Unknown platform: {platform!r} (expected 'android' or 'ios').") from None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def template_source(platform: str) -> Path:
|
|
217
|
+
"""Where the bundled template for ``platform`` lives in this installation."""
|
|
218
|
+
return Path(__file__).resolve().parents[1] / "templates" / template_name(platform)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def stage_template(template_name: str, destination: Path) -> Path:
|
|
222
|
+
"""Copy a bundled native template into ``destination``.
|
|
223
|
+
|
|
224
|
+
Resolution order mirrors the historical CLI: a local source checkout
|
|
225
|
+
first (so dev edits take effect immediately), then installed package
|
|
226
|
+
data via :mod:`importlib.resources`.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
template_name: ``"android_template"`` or ``"ios_template"``.
|
|
230
|
+
destination: Parent directory; the template lands at
|
|
231
|
+
``destination/<template_name>``.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
The path to the staged template directory.
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
BuildError: If no bundled copy can be located.
|
|
238
|
+
"""
|
|
239
|
+
dest_path = destination / template_name
|
|
240
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
241
|
+
|
|
242
|
+
# Dev-first: local source package templates.
|
|
243
|
+
local = Path(__file__).resolve().parents[1] / "templates" / template_name
|
|
244
|
+
if local.is_dir():
|
|
245
|
+
shutil.copytree(local, dest_path, dirs_exist_ok=True, ignore=_TEMPLATE_IGNORE)
|
|
246
|
+
_stage_runtime(template_name, dest_path)
|
|
247
|
+
return dest_path
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
candidate = resources.files("pythonnative").joinpath("templates").joinpath(template_name)
|
|
251
|
+
with resources.as_file(candidate) as resolved:
|
|
252
|
+
if Path(resolved).is_dir():
|
|
253
|
+
shutil.copytree(resolved, dest_path, dirs_exist_ok=True, ignore=_TEMPLATE_IGNORE)
|
|
254
|
+
_stage_runtime(template_name, dest_path)
|
|
255
|
+
return dest_path
|
|
256
|
+
except (ModuleNotFoundError, FileNotFoundError, OSError):
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
raise BuildError(
|
|
260
|
+
f"Could not find bundled template {template_name!r}. Reinstall pythonnative or run from a source checkout."
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _stage_runtime(name: str, destination: Path) -> None:
|
|
265
|
+
platform = name.removesuffix("_template")
|
|
266
|
+
source = Path(__file__).resolve().parents[1] / "native"
|
|
267
|
+
runtime_name = "PythonNativeKit" if platform == "ios" else "pythonnative"
|
|
268
|
+
managed = "Sources/PythonNativeKit" if platform == "ios" else "src/main/java/com/pythonnative/runtime"
|
|
269
|
+
staged_sources = destination / runtime_name / managed
|
|
270
|
+
if staged_sources.exists():
|
|
271
|
+
for path in staged_sources.rglob("*"):
|
|
272
|
+
rel = path.relative_to(staged_sources)
|
|
273
|
+
if path.is_file() and rel.parts[0] not in {"Plugins", "PluginResources", "Generated", "plugins"}:
|
|
274
|
+
if not (source / platform / managed / rel).exists():
|
|
275
|
+
path.unlink()
|
|
276
|
+
shutil.copytree(source / platform, destination / runtime_name, dirs_exist_ok=True, ignore=_TEMPLATE_IGNORE)
|
|
277
|
+
shutil.copytree(source / "yoga", destination / "yoga", dirs_exist_ok=True, ignore=_TEMPLATE_IGNORE)
|
|
278
|
+
from ..sdk.codegen import generate
|
|
279
|
+
|
|
280
|
+
generated = destination / "contracts"
|
|
281
|
+
generate(generated)
|
|
282
|
+
if platform == "ios":
|
|
283
|
+
output = destination / runtime_name / "Sources/PythonNativeKit/Generated"
|
|
284
|
+
extension = "swift"
|
|
285
|
+
else:
|
|
286
|
+
output = destination / runtime_name / "src/main/java/com/pythonnative/generated"
|
|
287
|
+
extension = "kt"
|
|
288
|
+
output.mkdir(parents=True, exist_ok=True)
|
|
289
|
+
for name in ("PNContracts", "NativeProps", "NativeModules"):
|
|
290
|
+
shutil.copy2(generated / f"{name}.{extension}", output / f"{name}.{extension}")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# ======================================================================
|
|
294
|
+
# The builder
|
|
295
|
+
# ======================================================================
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class Builder:
|
|
299
|
+
"""Stages, configures, and builds a PythonNative project.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
config: The validated app configuration.
|
|
303
|
+
runner: Command runner (defaults to
|
|
304
|
+
[`SubprocessRunner`][pythonnative.project.builder.SubprocessRunner]).
|
|
305
|
+
log: Progress logger (defaults to :func:`print`).
|
|
306
|
+
build_root: Override for the ``build/`` directory.
|
|
307
|
+
dev_lib_root: Override for the ``pythonnative`` package directory
|
|
308
|
+
to bundle (defaults to the running package).
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
def __init__(
|
|
312
|
+
self,
|
|
313
|
+
config: AppConfig,
|
|
314
|
+
*,
|
|
315
|
+
runner: Optional[CommandRunner] = None,
|
|
316
|
+
log: Optional[Logger] = None,
|
|
317
|
+
build_root: Optional[Path] = None,
|
|
318
|
+
dev_lib_root: Optional[Path] = None,
|
|
319
|
+
) -> None:
|
|
320
|
+
self.config = config
|
|
321
|
+
self.runner = runner or SubprocessRunner()
|
|
322
|
+
self.log: Logger = log or print
|
|
323
|
+
self.build_root = build_root or (config.project_root / "build")
|
|
324
|
+
# The currently-running pythonnative package is bundled into the app
|
|
325
|
+
# (works for both a source checkout and a pip install).
|
|
326
|
+
self.dev_lib_root = dev_lib_root or Path(__file__).resolve().parents[1]
|
|
327
|
+
|
|
328
|
+
# -- Preparation ----------------------------------------------------
|
|
329
|
+
|
|
330
|
+
def prepare(
|
|
331
|
+
self,
|
|
332
|
+
platform: str,
|
|
333
|
+
*,
|
|
334
|
+
release: bool = False,
|
|
335
|
+
ios_sdks: Sequence[str] = deps.IOS_SDKS,
|
|
336
|
+
) -> PreparedProject:
|
|
337
|
+
"""Stage and configure the native project for ``platform``.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
platform: ``"android"`` or ``"ios"``.
|
|
341
|
+
release: Prepare for a release/store build: byte-compile
|
|
342
|
+
the staged Python code and drop the ``.py`` sources from
|
|
343
|
+
the bundle (on Android through Chaquopy's ``pyc.src``).
|
|
344
|
+
Debug builds keep the sources so tracebacks show code and
|
|
345
|
+
the on-device dev client can see what it already runs.
|
|
346
|
+
ios_sdks: Which iOS SDKs to resolve third-party packages
|
|
347
|
+
for: ``"iphoneos"`` (devices, archives) and/or
|
|
348
|
+
``"iphonesimulator"``. Each becomes an
|
|
349
|
+
``app_packages.<sdk>`` slice in the staged project.
|
|
350
|
+
Defaults to both; the CLI narrows it to the destination
|
|
351
|
+
it is about to build so a package that only has a
|
|
352
|
+
Simulator wheel doesn't block a Simulator run.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
A [`PreparedProject`][pythonnative.project.builder.PreparedProject].
|
|
356
|
+
|
|
357
|
+
Raises:
|
|
358
|
+
BuildError: For an unknown platform, a staging failure, or a
|
|
359
|
+
requirement with no wheel for a requested iOS SDK.
|
|
360
|
+
"""
|
|
361
|
+
if platform not in _TEMPLATE_NAMES:
|
|
362
|
+
raise BuildError(f"Unknown platform: {platform!r} (expected 'android' or 'ios').")
|
|
363
|
+
|
|
364
|
+
build_dir = self.build_root / platform
|
|
365
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
366
|
+
project_dir = stage_template(_TEMPLATE_NAMES[platform], build_dir)
|
|
367
|
+
targets = (
|
|
368
|
+
deps.android_targets(self.config) if platform == "android" else deps.ios_targets(self.config, sdks=ios_sdks)
|
|
369
|
+
)
|
|
370
|
+
plugins = self._discover_plugins(targets)
|
|
371
|
+
|
|
372
|
+
if platform == "android":
|
|
373
|
+
layout = android_config.configure(
|
|
374
|
+
project_dir,
|
|
375
|
+
self.config,
|
|
376
|
+
dev_lib_root=self.dev_lib_root,
|
|
377
|
+
release=release,
|
|
378
|
+
log=self.log,
|
|
379
|
+
)
|
|
380
|
+
native_plugins.stage_android_plugins(project_dir, plugins, log=self.log)
|
|
381
|
+
self._stage_contracts(project_dir, platform, plugins, [layout.python_root / "pythonnative"])
|
|
382
|
+
return PreparedProject(
|
|
383
|
+
platform=platform,
|
|
384
|
+
build_dir=build_dir,
|
|
385
|
+
project_dir=project_dir,
|
|
386
|
+
app_id=layout.application_id,
|
|
387
|
+
android=layout,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
ios_layout = ios_config.configure(project_dir, self.config, log=self.log)
|
|
391
|
+
native_plugins.stage_ios_plugins(project_dir, plugins, log=self.log)
|
|
392
|
+
self._stage_ios_python(project_dir, release=release, sdks=ios_sdks)
|
|
393
|
+
self._stage_contracts(
|
|
394
|
+
project_dir,
|
|
395
|
+
platform,
|
|
396
|
+
plugins,
|
|
397
|
+
[project_dir / target.slice_name / "pythonnative" for target in targets],
|
|
398
|
+
)
|
|
399
|
+
self._link_ios_runtime(project_dir)
|
|
400
|
+
return PreparedProject(
|
|
401
|
+
platform=platform,
|
|
402
|
+
build_dir=build_dir,
|
|
403
|
+
project_dir=project_dir,
|
|
404
|
+
app_id=ios_layout.bundle_id,
|
|
405
|
+
ios=ios_layout,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
def _stage_contracts(
|
|
409
|
+
self,
|
|
410
|
+
project_dir: Path,
|
|
411
|
+
platform: str,
|
|
412
|
+
plugins: Sequence[native_plugins.NativePlugin],
|
|
413
|
+
python_roots: Sequence[Path],
|
|
414
|
+
) -> None:
|
|
415
|
+
from ..sdk import schema
|
|
416
|
+
from ..sdk.codegen import generate
|
|
417
|
+
|
|
418
|
+
components, modules = dict(schema.COMPONENTS), dict(schema.MODULES)
|
|
419
|
+
try:
|
|
420
|
+
for plugin in plugins:
|
|
421
|
+
if plugin.contracts is not None:
|
|
422
|
+
schema.load_manifest(json.loads((plugin.root / plugin.contracts).read_text(encoding="utf-8")))
|
|
423
|
+
generated = project_dir / "contracts"
|
|
424
|
+
generate(generated)
|
|
425
|
+
output = project_dir / (
|
|
426
|
+
"PythonNativeKit/Sources/PythonNativeKit/Generated"
|
|
427
|
+
if platform == "ios"
|
|
428
|
+
else "pythonnative/src/main/java/com/pythonnative/generated"
|
|
429
|
+
)
|
|
430
|
+
extension = "swift" if platform == "ios" else "kt"
|
|
431
|
+
for name in ("PNContracts", "NativeProps", "NativeModules"):
|
|
432
|
+
shutil.copy2(generated / f"{name}.{extension}", output / f"{name}.{extension}")
|
|
433
|
+
for root in python_roots:
|
|
434
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
435
|
+
shutil.copy2(generated / "schema.json", root / "_native_contracts.json")
|
|
436
|
+
finally:
|
|
437
|
+
schema.COMPONENTS.clear()
|
|
438
|
+
schema.COMPONENTS.update(components)
|
|
439
|
+
schema.MODULES.clear()
|
|
440
|
+
schema.MODULES.update(modules)
|
|
441
|
+
|
|
442
|
+
def _discover_plugins(self, targets: Sequence[deps.Target] = ()) -> List[native_plugins.NativePlugin]:
|
|
443
|
+
"""Collect native plugins from entry points and ``[plugins].paths``.
|
|
444
|
+
|
|
445
|
+
Raises:
|
|
446
|
+
BuildError: If a project-local plugin directory is invalid.
|
|
447
|
+
"""
|
|
448
|
+
try:
|
|
449
|
+
installed = native_plugins.discover_target_plugins(self.config, targets, self.runner)
|
|
450
|
+
local = native_plugins.discover_plugins(
|
|
451
|
+
include_environment=False,
|
|
452
|
+
extra_paths=[self.config.resolve_path(p) for p in self.config.plugin_paths],
|
|
453
|
+
log=self.log,
|
|
454
|
+
)
|
|
455
|
+
names = {plugin.name for plugin in installed}
|
|
456
|
+
if any(plugin.name in names for plugin in local):
|
|
457
|
+
raise native_plugins.PluginError("A plugin appears in both target requirements and [plugins].paths")
|
|
458
|
+
return installed + local
|
|
459
|
+
except native_plugins.PluginError as exc:
|
|
460
|
+
raise BuildError(f"Invalid native plugin: {exc}") from exc
|
|
461
|
+
|
|
462
|
+
def _stage_ios_python(self, project_dir: Path, *, release: bool, sdks: Sequence[str]) -> None:
|
|
463
|
+
"""Stage ``app/`` and one ``app_packages.<sdk>/`` slice per SDK.
|
|
464
|
+
|
|
465
|
+
``app/`` is a bundle resource. The package slices are not: the
|
|
466
|
+
"Install Python runtime" build phase copies the slice matching
|
|
467
|
+
``EFFECTIVE_PLATFORM_NAME`` into the bundle as ``app_packages``
|
|
468
|
+
and converts any binary modules inside it into signed
|
|
469
|
+
frameworks. Every slice carries the ``pythonnative`` package
|
|
470
|
+
(pure Python) plus the project's requirements resolved for that
|
|
471
|
+
SDK's wheel tags.
|
|
472
|
+
"""
|
|
473
|
+
app_dir = project_dir / "app"
|
|
474
|
+
if app_dir.exists():
|
|
475
|
+
shutil.rmtree(app_dir)
|
|
476
|
+
for stale in project_dir.glob("app_packages*"):
|
|
477
|
+
if stale.is_dir():
|
|
478
|
+
shutil.rmtree(stale)
|
|
479
|
+
else:
|
|
480
|
+
stale.unlink()
|
|
481
|
+
|
|
482
|
+
app_src = self.config.project_root / "app"
|
|
483
|
+
if not app_src.is_dir():
|
|
484
|
+
raise BuildError(f"No app/ directory found at {app_src}; nothing to bundle.")
|
|
485
|
+
shutil.copytree(app_src, app_dir)
|
|
486
|
+
|
|
487
|
+
slices: List[Path] = []
|
|
488
|
+
for target in deps.ios_targets(self.config, sdks=sdks):
|
|
489
|
+
packages_dir = project_dir / target.slice_name
|
|
490
|
+
packages_dir.mkdir(parents=True, exist_ok=True)
|
|
491
|
+
slices.append(packages_dir)
|
|
492
|
+
|
|
493
|
+
# The framework has no Python-side native dependencies (the
|
|
494
|
+
# bridge into PythonNativeKit is ctypes), so it is copied
|
|
495
|
+
# verbatim into every slice.
|
|
496
|
+
if self.dev_lib_root.is_dir():
|
|
497
|
+
shutil.copytree(
|
|
498
|
+
self.dev_lib_root,
|
|
499
|
+
packages_dir / "pythonnative",
|
|
500
|
+
dirs_exist_ok=True,
|
|
501
|
+
ignore=android_config.LIB_IGNORE,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
if self.config.requirements:
|
|
505
|
+
self.log(f"Resolving {len(self.config.requirements)} requirement(s) for {target.label}...")
|
|
506
|
+
try:
|
|
507
|
+
deps.install(self.config, target, packages_dir, runner=self.runner)
|
|
508
|
+
except deps.DependencyError as exc:
|
|
509
|
+
raise BuildError(str(exc)) from exc
|
|
510
|
+
|
|
511
|
+
if release:
|
|
512
|
+
self._compile_ios_bytecode(app_dir, *slices)
|
|
513
|
+
|
|
514
|
+
def _compile_ios_bytecode(self, *roots: Path) -> None:
|
|
515
|
+
"""Byte-compile staged Python code and drop the ``.py`` sources.
|
|
516
|
+
|
|
517
|
+
Ships ``.pyc`` only: smaller bundles, faster cold start, and no
|
|
518
|
+
plain-text source in the store artifact. Requires the host
|
|
519
|
+
interpreter to match ``app.python_version``, since bytecode is
|
|
520
|
+
version-specific.
|
|
521
|
+
"""
|
|
522
|
+
host_version = f"{sys.version_info[0]}.{sys.version_info[1]}"
|
|
523
|
+
if host_version != self.config.python_version:
|
|
524
|
+
self.log(
|
|
525
|
+
f"Skipping bytecode compilation: the build host runs Python {host_version} "
|
|
526
|
+
f"but app.python_version is {self.config.python_version}, and bytecode is "
|
|
527
|
+
"version-specific. The app will ship .py sources. Run pn with Python "
|
|
528
|
+
f"{self.config.python_version} to ship bytecode."
|
|
529
|
+
)
|
|
530
|
+
return
|
|
531
|
+
for root in roots:
|
|
532
|
+
# legacy=True writes module.pyc next to module.py (no
|
|
533
|
+
# __pycache__), which the embedded interpreter imports
|
|
534
|
+
# directly once the .py files are gone.
|
|
535
|
+
if not compileall.compile_dir(str(root), quiet=1, legacy=True, optimize=1):
|
|
536
|
+
raise BuildError(f"Byte-compiling {root} failed; fix the syntax error above.")
|
|
537
|
+
for source in root.rglob("*.py"):
|
|
538
|
+
source.unlink()
|
|
539
|
+
self.log("Compiled Python sources to bytecode for release.")
|
|
540
|
+
|
|
541
|
+
def _link_ios_runtime(self, project_dir: Path) -> None:
|
|
542
|
+
"""Make ``Python.xcframework`` available at the project root.
|
|
543
|
+
|
|
544
|
+
A symlink into the shared runtime cache is preferred (the
|
|
545
|
+
framework is hundreds of megabytes); staging falls back to a
|
|
546
|
+
copy when symlinks are unavailable.
|
|
547
|
+
"""
|
|
548
|
+
runtime = self._ios_runtime()
|
|
549
|
+
link = project_dir / "Python.xcframework"
|
|
550
|
+
if link.is_symlink() or link.is_file():
|
|
551
|
+
link.unlink()
|
|
552
|
+
elif link.is_dir():
|
|
553
|
+
shutil.rmtree(link)
|
|
554
|
+
try:
|
|
555
|
+
link.symlink_to(runtime.xcframework_dir, target_is_directory=True)
|
|
556
|
+
except OSError:
|
|
557
|
+
shutil.copytree(runtime.xcframework_dir, link)
|
|
558
|
+
self.log(f"Linked Python.xcframework (Python {self.config.python_version}).")
|
|
559
|
+
|
|
560
|
+
# -- Android builds -------------------------------------------------
|
|
561
|
+
|
|
562
|
+
def install_android_debug(self, prepared: PreparedProject) -> None:
|
|
563
|
+
"""Build and install the debug APK on a connected device/emulator.
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
prepared: A prepared Android project.
|
|
567
|
+
|
|
568
|
+
Raises:
|
|
569
|
+
BuildError: If the Gradle build fails.
|
|
570
|
+
"""
|
|
571
|
+
self._gradlew(prepared, ["installDebug"])
|
|
572
|
+
|
|
573
|
+
def android_debug_apk(self, prepared: PreparedProject) -> Optional[Path]:
|
|
574
|
+
"""The debug APK produced by the last Gradle build, if it exists."""
|
|
575
|
+
outputs = prepared.project_dir / "app" / "build" / "outputs"
|
|
576
|
+
candidates = _existing(outputs.rglob("*-debug.apk"))
|
|
577
|
+
return candidates[0] if candidates else None
|
|
578
|
+
|
|
579
|
+
def build_android(self, prepared: PreparedProject, *, debug: bool = False) -> BuildArtifacts:
|
|
580
|
+
"""Assemble standalone Android artifacts (APK + AAB).
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
prepared: A prepared Android project.
|
|
584
|
+
debug: Build the debug variant instead of release.
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
The produced artifact paths.
|
|
588
|
+
|
|
589
|
+
Raises:
|
|
590
|
+
BuildError: If the Gradle build fails.
|
|
591
|
+
"""
|
|
592
|
+
if debug:
|
|
593
|
+
self._gradlew(prepared, ["assembleDebug"])
|
|
594
|
+
outputs = prepared.project_dir / "app" / "build" / "outputs"
|
|
595
|
+
return BuildArtifacts(paths=_existing(outputs.rglob("*-debug.apk")))
|
|
596
|
+
|
|
597
|
+
self._gradlew(prepared, ["assembleRelease", "bundleRelease"])
|
|
598
|
+
outputs = prepared.project_dir / "app" / "build" / "outputs"
|
|
599
|
+
candidates = list(outputs.rglob("*-release.apk")) + list(outputs.rglob("*-release.aab"))
|
|
600
|
+
if not config_has_android_signing(self.config):
|
|
601
|
+
candidates += list(outputs.rglob("*-release-unsigned.apk"))
|
|
602
|
+
return BuildArtifacts(paths=_existing(candidates))
|
|
603
|
+
|
|
604
|
+
def _gradlew(self, prepared: PreparedProject, tasks: Sequence[str]) -> None:
|
|
605
|
+
gradlew = prepared.project_dir / "gradlew"
|
|
606
|
+
if gradlew.exists():
|
|
607
|
+
os.chmod(gradlew, 0o755)
|
|
608
|
+
env = self._android_env()
|
|
609
|
+
result = self.runner.run(["./gradlew", *tasks], cwd=prepared.project_dir, env=env)
|
|
610
|
+
if not result.ok:
|
|
611
|
+
raise BuildError(f"Gradle build failed ({' '.join(tasks)}). See output above.")
|
|
612
|
+
|
|
613
|
+
def _android_env(self) -> dict:
|
|
614
|
+
env = dict(os.environ)
|
|
615
|
+
if sys.platform == "darwin" and not env.get("JAVA_HOME"):
|
|
616
|
+
try:
|
|
617
|
+
jdk = subprocess.check_output(["brew", "--prefix", "openjdk@17"], text=True).strip()
|
|
618
|
+
if jdk:
|
|
619
|
+
env["JAVA_HOME"] = jdk
|
|
620
|
+
except Exception:
|
|
621
|
+
pass
|
|
622
|
+
return env
|
|
623
|
+
|
|
624
|
+
# -- iOS builds -----------------------------------------------------
|
|
625
|
+
|
|
626
|
+
def build_ios_simulator(self, prepared: PreparedProject) -> Path:
|
|
627
|
+
"""Build the iOS app for the simulator.
|
|
628
|
+
|
|
629
|
+
The Xcode build produces a complete, runnable ``.app``: the
|
|
630
|
+
Python framework is linked and the "Install Python runtime"
|
|
631
|
+
build phase embeds the standard library and staged sources.
|
|
632
|
+
|
|
633
|
+
Args:
|
|
634
|
+
prepared: A prepared iOS project.
|
|
635
|
+
|
|
636
|
+
Returns:
|
|
637
|
+
Path to the built ``.app``.
|
|
638
|
+
|
|
639
|
+
Raises:
|
|
640
|
+
BuildError: If the build fails.
|
|
641
|
+
"""
|
|
642
|
+
derived = prepared.project_dir / "build"
|
|
643
|
+
settings = ios_config.build_settings(self.config)
|
|
644
|
+
# The simulator runs on the host CPU, and BeeWare's runtime
|
|
645
|
+
# install script requires a single-architecture build (its
|
|
646
|
+
# stdlib layout is per-arch), so pin ARCHS to the host.
|
|
647
|
+
host_arch = "arm64" if platform_module.machine() == "arm64" else "x86_64"
|
|
648
|
+
settings.append(f"ARCHS={host_arch}")
|
|
649
|
+
result = self.runner.run(
|
|
650
|
+
[
|
|
651
|
+
"xcodebuild",
|
|
652
|
+
"-project",
|
|
653
|
+
ios_config.PROJECT_FILE,
|
|
654
|
+
"-scheme",
|
|
655
|
+
ios_config.PROJECT_NAME,
|
|
656
|
+
"-configuration",
|
|
657
|
+
"Debug",
|
|
658
|
+
"-destination",
|
|
659
|
+
"generic/platform=iOS Simulator",
|
|
660
|
+
"-derivedDataPath",
|
|
661
|
+
str(derived),
|
|
662
|
+
"build",
|
|
663
|
+
*settings,
|
|
664
|
+
],
|
|
665
|
+
cwd=prepared.project_dir,
|
|
666
|
+
)
|
|
667
|
+
if not result.ok:
|
|
668
|
+
raise BuildError("xcodebuild (simulator) failed. See output above.")
|
|
669
|
+
|
|
670
|
+
app_path = derived / "Build" / "Products" / "Debug-iphonesimulator" / ios_config.APP_BUNDLE_NAME
|
|
671
|
+
if not app_path.is_dir():
|
|
672
|
+
raise BuildError(f"Built app not found at {app_path}.")
|
|
673
|
+
return app_path
|
|
674
|
+
|
|
675
|
+
def build_ios_device(self, prepared: PreparedProject) -> Path:
|
|
676
|
+
"""Build a signed Debug app for a physical iOS device.
|
|
677
|
+
|
|
678
|
+
Requires ``[ios].development_team`` for automatic signing.
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
prepared: A prepared iOS project.
|
|
682
|
+
|
|
683
|
+
Returns:
|
|
684
|
+
Path to the built ``.app``.
|
|
685
|
+
|
|
686
|
+
Raises:
|
|
687
|
+
BuildError: If the build fails or signing is not set up.
|
|
688
|
+
"""
|
|
689
|
+
if not self.config.ios.development_team:
|
|
690
|
+
raise BuildError(
|
|
691
|
+
"Running on a physical iOS device requires code signing. "
|
|
692
|
+
"Set [ios].development_team in pythonnative.toml."
|
|
693
|
+
)
|
|
694
|
+
derived = prepared.project_dir / "build"
|
|
695
|
+
settings = ios_config.build_settings(self.config)
|
|
696
|
+
settings.append("-allowProvisioningUpdates")
|
|
697
|
+
result = self.runner.run(
|
|
698
|
+
[
|
|
699
|
+
"xcodebuild",
|
|
700
|
+
"-project",
|
|
701
|
+
ios_config.PROJECT_FILE,
|
|
702
|
+
"-scheme",
|
|
703
|
+
ios_config.PROJECT_NAME,
|
|
704
|
+
"-configuration",
|
|
705
|
+
"Debug",
|
|
706
|
+
"-destination",
|
|
707
|
+
"generic/platform=iOS",
|
|
708
|
+
"-derivedDataPath",
|
|
709
|
+
str(derived),
|
|
710
|
+
"build",
|
|
711
|
+
*settings,
|
|
712
|
+
],
|
|
713
|
+
cwd=prepared.project_dir,
|
|
714
|
+
)
|
|
715
|
+
if not result.ok:
|
|
716
|
+
raise BuildError("xcodebuild (device) failed. See output above.")
|
|
717
|
+
|
|
718
|
+
app_path = derived / "Build" / "Products" / "Debug-iphoneos" / ios_config.APP_BUNDLE_NAME
|
|
719
|
+
if not app_path.is_dir():
|
|
720
|
+
raise BuildError(f"Built app not found at {app_path}.")
|
|
721
|
+
return app_path
|
|
722
|
+
|
|
723
|
+
def build_ios_archive(self, prepared: PreparedProject, *, upload: bool = False) -> BuildArtifacts:
|
|
724
|
+
"""Archive the iOS app and export a signed IPA (or upload it).
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
prepared: A prepared iOS project (use ``release=True``).
|
|
728
|
+
upload: Upload directly to App Store Connect instead of
|
|
729
|
+
exporting a local ``.ipa`` (requires
|
|
730
|
+
``export_method = "app-store"`` and stored ASC
|
|
731
|
+
credentials in Xcode).
|
|
732
|
+
|
|
733
|
+
Returns:
|
|
734
|
+
The produced ``.ipa`` (and ``.xcarchive``) paths.
|
|
735
|
+
|
|
736
|
+
Raises:
|
|
737
|
+
BuildError: If archiving or export fails.
|
|
738
|
+
"""
|
|
739
|
+
archive_path = prepared.build_dir / "ios_template.xcarchive"
|
|
740
|
+
settings = ios_config.build_settings(self.config, for_archive=True)
|
|
741
|
+
result = self.runner.run(
|
|
742
|
+
[
|
|
743
|
+
"xcodebuild",
|
|
744
|
+
"-project",
|
|
745
|
+
ios_config.PROJECT_FILE,
|
|
746
|
+
"-scheme",
|
|
747
|
+
ios_config.PROJECT_NAME,
|
|
748
|
+
"-configuration",
|
|
749
|
+
"Release",
|
|
750
|
+
"-destination",
|
|
751
|
+
"generic/platform=iOS",
|
|
752
|
+
"-archivePath",
|
|
753
|
+
str(archive_path),
|
|
754
|
+
"-allowProvisioningUpdates",
|
|
755
|
+
"archive",
|
|
756
|
+
*settings,
|
|
757
|
+
],
|
|
758
|
+
cwd=prepared.project_dir,
|
|
759
|
+
)
|
|
760
|
+
if not result.ok:
|
|
761
|
+
raise BuildError("xcodebuild archive failed. See output above.")
|
|
762
|
+
|
|
763
|
+
export_dir = prepared.build_dir / "export"
|
|
764
|
+
options = ios_config.write_export_options(
|
|
765
|
+
self.config, prepared.build_dir / "exportOptions.plist", upload=upload
|
|
766
|
+
)
|
|
767
|
+
result = self.runner.run(
|
|
768
|
+
[
|
|
769
|
+
"xcodebuild",
|
|
770
|
+
"-exportArchive",
|
|
771
|
+
"-archivePath",
|
|
772
|
+
str(archive_path),
|
|
773
|
+
"-exportOptionsPlist",
|
|
774
|
+
str(options),
|
|
775
|
+
"-exportPath",
|
|
776
|
+
str(export_dir),
|
|
777
|
+
"-allowProvisioningUpdates",
|
|
778
|
+
],
|
|
779
|
+
cwd=prepared.project_dir,
|
|
780
|
+
)
|
|
781
|
+
if not result.ok:
|
|
782
|
+
raise BuildError("xcodebuild -exportArchive failed. Check signing settings in [ios.signing].")
|
|
783
|
+
|
|
784
|
+
return BuildArtifacts(paths=_existing(list(export_dir.rglob("*.ipa")) + [archive_path]))
|
|
785
|
+
|
|
786
|
+
def _ios_runtime(self) -> runtime_assets.IOSRuntime:
|
|
787
|
+
cache = self.build_root / "ios_runtime"
|
|
788
|
+
try:
|
|
789
|
+
return runtime_assets.prepare_ios_runtime(cache, self.config.python_version, log=self.log)
|
|
790
|
+
except RuntimeError as exc:
|
|
791
|
+
raise BuildError(str(exc)) from exc
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
# ======================================================================
|
|
795
|
+
# Helpers
|
|
796
|
+
# ======================================================================
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def config_has_android_signing(config: AppConfig) -> bool:
|
|
800
|
+
"""Return whether the config defines an Android release signing key.
|
|
801
|
+
|
|
802
|
+
Args:
|
|
803
|
+
config: The app configuration.
|
|
804
|
+
|
|
805
|
+
Returns:
|
|
806
|
+
``True`` if a keystore and alias are configured.
|
|
807
|
+
"""
|
|
808
|
+
return config.android.signing.is_configured
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
def _existing(paths: Union[Sequence[Path], object]) -> List[Path]:
|
|
812
|
+
result: List[Path] = []
|
|
813
|
+
for path in paths: # type: ignore[union-attr]
|
|
814
|
+
candidate = Path(path)
|
|
815
|
+
if candidate.exists() and candidate not in result:
|
|
816
|
+
result.append(candidate)
|
|
817
|
+
return result
|