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,519 @@
|
|
|
1
|
+
"""Native plugin staging for ``pn build``.
|
|
2
|
+
|
|
3
|
+
A PyPI package can ship Swift and Kotlin next to its Python facade. It
|
|
4
|
+
declares an entry point in the ``pythonnative.plugins`` group whose
|
|
5
|
+
value is an importable package directory containing:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
my_blur/native/
|
|
9
|
+
pn_plugin.json
|
|
10
|
+
ios/BlurViewManager.swift
|
|
11
|
+
android/com/example/myblur/BlurViewManager.kt
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
``pn_plugin.json`` names the registration entry on each platform:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{"ios": {"entry": "MyBlurPlugin"}, "android": {"entry": "com.example.myblur.MyBlurPlugin"}}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
At build time this module copies each plugin's sources into the staged
|
|
21
|
+
native project (``PythonNativeKit/Sources/PythonNativeKit/Plugins/<name>``
|
|
22
|
+
on iOS, ``pythonnative/src/main/java`` on Android) and regenerates the
|
|
23
|
+
registration file that calls every entry's ``register``. SwiftPM and
|
|
24
|
+
Gradle compile whatever lands under those roots, so no project-file
|
|
25
|
+
surgery is needed.
|
|
26
|
+
|
|
27
|
+
Projects can also bundle plugins that aren't installed as distributions
|
|
28
|
+
by listing directories under ``[plugins].paths`` in ``pythonnative.toml``;
|
|
29
|
+
each path must contain the same ``pn_plugin.json`` layout.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import json
|
|
35
|
+
import re
|
|
36
|
+
import shutil
|
|
37
|
+
from dataclasses import dataclass, field
|
|
38
|
+
from importlib import import_module
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"ENTRY_POINT_GROUP",
|
|
44
|
+
"MANIFEST_NAME",
|
|
45
|
+
"NativePlugin",
|
|
46
|
+
"PluginError",
|
|
47
|
+
"discover_plugins",
|
|
48
|
+
"load_plugin",
|
|
49
|
+
"stage_android_plugins",
|
|
50
|
+
"stage_ios_plugins",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
ENTRY_POINT_GROUP = "pythonnative.plugins"
|
|
54
|
+
"""Entry-point group whose values name plugin directories (as packages)."""
|
|
55
|
+
|
|
56
|
+
MANIFEST_NAME = "pn_plugin.json"
|
|
57
|
+
"""File name of the per-plugin manifest."""
|
|
58
|
+
|
|
59
|
+
Logger = Callable[[str], None]
|
|
60
|
+
|
|
61
|
+
_IOS_PLUGIN_DIR = ("PythonNativeKit", "Sources", "PythonNativeKit", "Plugins")
|
|
62
|
+
_IOS_REGISTRATION = (*_IOS_PLUGIN_DIR, "PNPluginRegistration.swift")
|
|
63
|
+
_ANDROID_SOURCE_ROOT = ("pythonnative", "src", "main", "java")
|
|
64
|
+
_ANDROID_REGISTRATION = (*_ANDROID_SOURCE_ROOT, "com", "pythonnative", "runtime", "plugins", "GeneratedPlugins.kt")
|
|
65
|
+
|
|
66
|
+
_SWIFT_IDENT = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
67
|
+
_KOTLIN_FQN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+$")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PluginError(RuntimeError):
|
|
71
|
+
"""A plugin manifest or source layout is invalid."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class NativePlugin:
|
|
76
|
+
"""A discovered native plugin.
|
|
77
|
+
|
|
78
|
+
Attributes:
|
|
79
|
+
name: Stable identifier (the entry-point name or the directory
|
|
80
|
+
name for path plugins). Used for the iOS subfolder.
|
|
81
|
+
root: Directory containing ``pn_plugin.json``.
|
|
82
|
+
ios_entry: Swift type conforming to ``PNPlugin``, or ``None``
|
|
83
|
+
when the plugin has no iOS side.
|
|
84
|
+
android_entry: Fully qualified Kotlin object implementing
|
|
85
|
+
``PNPlugin``, or ``None`` when the plugin has no Android side.
|
|
86
|
+
ios_sources: Swift files under ``ios/``.
|
|
87
|
+
android_sources: Kotlin files under ``android/`` (relative
|
|
88
|
+
paths are preserved so packages land in the right folders).
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
name: str
|
|
92
|
+
root: Path
|
|
93
|
+
ios_entry: Optional[str] = None
|
|
94
|
+
android_entry: Optional[str] = None
|
|
95
|
+
ios_sources: Sequence[Path] = field(default_factory=tuple)
|
|
96
|
+
android_sources: Sequence[Path] = field(default_factory=tuple)
|
|
97
|
+
ios_resources: Sequence[Path] = field(default_factory=tuple)
|
|
98
|
+
android_resources: Sequence[Path] = field(default_factory=tuple)
|
|
99
|
+
android_assets: Sequence[Path] = field(default_factory=tuple)
|
|
100
|
+
contracts: Optional[Path] = None
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def has_ios(self) -> bool:
|
|
104
|
+
"""Whether this plugin contributes Swift code."""
|
|
105
|
+
return self.ios_entry is not None
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def has_android(self) -> bool:
|
|
109
|
+
"""Whether this plugin contributes Kotlin code."""
|
|
110
|
+
return self.android_entry is not None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ======================================================================
|
|
114
|
+
# Discovery
|
|
115
|
+
# ======================================================================
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def load_plugin(root: Path, *, name: Optional[str] = None) -> NativePlugin:
|
|
119
|
+
"""Read ``pn_plugin.json`` under ``root`` and collect its sources.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
root: Directory containing the manifest.
|
|
123
|
+
name: Identifier override (defaults to the directory name).
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The parsed plugin.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
PluginError: If the manifest is missing or malformed, or an
|
|
130
|
+
entry is declared without any matching sources.
|
|
131
|
+
"""
|
|
132
|
+
root = Path(root).resolve()
|
|
133
|
+
manifest_path = root / MANIFEST_NAME
|
|
134
|
+
if not manifest_path.is_file():
|
|
135
|
+
raise PluginError(f"{root} has no {MANIFEST_NAME}.")
|
|
136
|
+
try:
|
|
137
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
138
|
+
except (OSError, ValueError) as exc:
|
|
139
|
+
raise PluginError(f"Could not parse {manifest_path}: {exc}") from exc
|
|
140
|
+
if not isinstance(manifest, dict):
|
|
141
|
+
raise PluginError(f"{manifest_path} must contain a JSON object.")
|
|
142
|
+
|
|
143
|
+
plugin_name = name or manifest.get("name") or root.name
|
|
144
|
+
if not _SWIFT_IDENT.match(str(plugin_name).replace("-", "_")):
|
|
145
|
+
raise PluginError(f"Plugin name {plugin_name!r} must be an identifier.")
|
|
146
|
+
plugin_name = str(plugin_name).replace("-", "_")
|
|
147
|
+
|
|
148
|
+
ios_entry = _entry(manifest, "ios", manifest_path)
|
|
149
|
+
android_entry = _entry(manifest, "android", manifest_path)
|
|
150
|
+
if ios_entry is None and android_entry is None:
|
|
151
|
+
raise PluginError(f"{manifest_path} declares neither an 'ios' nor an 'android' entry.")
|
|
152
|
+
|
|
153
|
+
ios_sources: List[Path] = []
|
|
154
|
+
android_sources: List[Path] = []
|
|
155
|
+
if ios_entry is not None:
|
|
156
|
+
if not _SWIFT_IDENT.match(ios_entry):
|
|
157
|
+
raise PluginError(f"{manifest_path}: ios.entry {ios_entry!r} is not a Swift type name.")
|
|
158
|
+
ios_sources = sorted(p.relative_to(root) for p in (root / "ios").rglob("*.swift") if p.is_file())
|
|
159
|
+
if not ios_sources:
|
|
160
|
+
raise PluginError(f"{root} declares an iOS entry but ios/ contains no .swift files.")
|
|
161
|
+
if android_entry is not None:
|
|
162
|
+
if not _KOTLIN_FQN.match(android_entry):
|
|
163
|
+
raise PluginError(f"{manifest_path}: android.entry {android_entry!r} must be a fully qualified name.")
|
|
164
|
+
android_sources = sorted(
|
|
165
|
+
p.relative_to(root) for p in (root / "android").rglob("*") if p.is_file() and p.suffix in (".kt", ".java")
|
|
166
|
+
)
|
|
167
|
+
if not android_sources:
|
|
168
|
+
raise PluginError(f"{root} declares an Android entry but android/ contains no .kt or .java files.")
|
|
169
|
+
|
|
170
|
+
def resources(platform: str, key: str) -> tuple[Path, ...]:
|
|
171
|
+
section = manifest.get(platform, {})
|
|
172
|
+
patterns = section.get(key, []) if isinstance(section, dict) else []
|
|
173
|
+
if not isinstance(patterns, list) or not all(isinstance(item, str) for item in patterns):
|
|
174
|
+
raise PluginError(f"{manifest_path}: {platform}.{key} must be a list of relative globs")
|
|
175
|
+
selected: set[Path] = set()
|
|
176
|
+
for pattern in patterns:
|
|
177
|
+
if Path(pattern).is_absolute() or ".." in Path(pattern).parts:
|
|
178
|
+
raise PluginError(f"Resource pattern escapes the plugin: {pattern}")
|
|
179
|
+
for resource in root.glob(pattern):
|
|
180
|
+
if resource.is_file():
|
|
181
|
+
if not resource.resolve().is_relative_to(root):
|
|
182
|
+
raise PluginError(f"Resource symlink escapes the plugin: {resource}")
|
|
183
|
+
selected.add(resource.relative_to(root))
|
|
184
|
+
return tuple(sorted(selected))
|
|
185
|
+
|
|
186
|
+
contract_path = Path(manifest["contracts"]) if "contracts" in manifest else None
|
|
187
|
+
if contract_path is not None and (
|
|
188
|
+
not (root / contract_path).is_file() or not (root / contract_path).resolve().is_relative_to(root)
|
|
189
|
+
):
|
|
190
|
+
raise PluginError(f"{manifest_path}: contracts must name a file inside the plugin")
|
|
191
|
+
return NativePlugin(
|
|
192
|
+
name=plugin_name,
|
|
193
|
+
root=root,
|
|
194
|
+
ios_entry=ios_entry,
|
|
195
|
+
android_entry=android_entry,
|
|
196
|
+
ios_sources=tuple(ios_sources),
|
|
197
|
+
android_sources=tuple(android_sources),
|
|
198
|
+
ios_resources=resources("ios", "resources"),
|
|
199
|
+
android_resources=resources("android", "resources"),
|
|
200
|
+
android_assets=resources("android", "assets"),
|
|
201
|
+
contracts=contract_path,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _entry(manifest: Dict[str, object], platform: str, manifest_path: Path) -> Optional[str]:
|
|
206
|
+
section = manifest.get(platform)
|
|
207
|
+
if section is None:
|
|
208
|
+
return None
|
|
209
|
+
if not isinstance(section, dict) or not isinstance(section.get("entry"), str):
|
|
210
|
+
raise PluginError(f"{manifest_path}: '{platform}' must be an object with a string 'entry'.")
|
|
211
|
+
return str(section["entry"])
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def discover_plugins(
|
|
215
|
+
*,
|
|
216
|
+
extra_paths: Iterable[Path] = (),
|
|
217
|
+
include_environment: bool = True,
|
|
218
|
+
log: Optional[Logger] = None,
|
|
219
|
+
) -> List[NativePlugin]:
|
|
220
|
+
"""Find every plugin visible to this interpreter.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
extra_paths: Plugin directories to include in addition to the
|
|
224
|
+
``pythonnative.plugins`` entry points (from
|
|
225
|
+
``[plugins].paths`` in the project config).
|
|
226
|
+
include_environment: Inspect this interpreter's installed entry points.
|
|
227
|
+
Mobile builds pass ``False`` and inspect the target wheels instead.
|
|
228
|
+
log: Optional progress logger; broken entry points are reported
|
|
229
|
+
here and skipped rather than failing the build.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Plugins in a stable order (entry points sorted by name, then
|
|
233
|
+
explicit paths in the order given).
|
|
234
|
+
"""
|
|
235
|
+
emit: Logger = log or (lambda _message: None)
|
|
236
|
+
plugins: List[NativePlugin] = []
|
|
237
|
+
seen: set = set()
|
|
238
|
+
|
|
239
|
+
for ep_name, target in sorted(_entry_points().items() if include_environment else []):
|
|
240
|
+
try:
|
|
241
|
+
root = _resolve_entry_point(target)
|
|
242
|
+
plugin = load_plugin(root, name=ep_name)
|
|
243
|
+
except (PluginError, ImportError, AttributeError) as exc:
|
|
244
|
+
emit(f"Skipping native plugin {ep_name!r}: {exc}")
|
|
245
|
+
continue
|
|
246
|
+
if plugin.name in seen:
|
|
247
|
+
emit(f"Skipping duplicate native plugin {plugin.name!r}.")
|
|
248
|
+
continue
|
|
249
|
+
seen.add(plugin.name)
|
|
250
|
+
plugins.append(plugin)
|
|
251
|
+
|
|
252
|
+
for path in extra_paths:
|
|
253
|
+
plugin = load_plugin(Path(path))
|
|
254
|
+
if plugin.name in seen:
|
|
255
|
+
raise PluginError(f"Native plugin {plugin.name!r} is declared twice.")
|
|
256
|
+
seen.add(plugin.name)
|
|
257
|
+
plugins.append(plugin)
|
|
258
|
+
|
|
259
|
+
return plugins
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _entry_points() -> Dict[str, str]:
|
|
263
|
+
try:
|
|
264
|
+
from importlib.metadata import entry_points
|
|
265
|
+
except ImportError: # pragma: no cover - Python < 3.8
|
|
266
|
+
return {}
|
|
267
|
+
try:
|
|
268
|
+
eps = entry_points()
|
|
269
|
+
except Exception:
|
|
270
|
+
return {}
|
|
271
|
+
selected = []
|
|
272
|
+
if hasattr(eps, "select"):
|
|
273
|
+
selected = list(eps.select(group=ENTRY_POINT_GROUP))
|
|
274
|
+
else: # pragma: no cover - importlib.metadata < 3.10
|
|
275
|
+
getter = getattr(eps, "get", None)
|
|
276
|
+
if callable(getter):
|
|
277
|
+
selected = list(getter(ENTRY_POINT_GROUP, []))
|
|
278
|
+
return {ep.name: ep.value for ep in selected}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _resolve_entry_point(target: str) -> Path:
|
|
282
|
+
"""Turn ``"pkg.subpkg"`` (or ``"pkg.subpkg:attr"``) into a directory."""
|
|
283
|
+
module_name, _, attr = target.partition(":")
|
|
284
|
+
module = import_module(module_name.strip())
|
|
285
|
+
if attr:
|
|
286
|
+
value = getattr(module, attr.strip())
|
|
287
|
+
return Path(str(value))
|
|
288
|
+
location = getattr(module, "__file__", None)
|
|
289
|
+
if location is None:
|
|
290
|
+
raise PluginError(f"{module_name!r} is a namespace package; point the entry point at a regular package.")
|
|
291
|
+
return Path(location).resolve().parent
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# ======================================================================
|
|
295
|
+
# Staging
|
|
296
|
+
# ======================================================================
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def stage_ios_plugins(project_dir: Path, plugins: Sequence[NativePlugin], *, log: Optional[Logger] = None) -> None:
|
|
300
|
+
"""Copy Swift sources into ``PythonNativeKit`` and write the registration file.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
project_dir: The staged ``ios_template`` directory.
|
|
304
|
+
plugins: Plugins to bundle; ones without an iOS side are skipped.
|
|
305
|
+
log: Optional progress logger.
|
|
306
|
+
"""
|
|
307
|
+
emit: Logger = log or (lambda _message: None)
|
|
308
|
+
plugins_dir = Path(project_dir).joinpath(*_IOS_PLUGIN_DIR)
|
|
309
|
+
registration = Path(project_dir).joinpath(*_IOS_REGISTRATION)
|
|
310
|
+
plugins_dir.mkdir(parents=True, exist_ok=True)
|
|
311
|
+
|
|
312
|
+
# Wipe previous plugin folders so removed plugins don't linger in an
|
|
313
|
+
# incremental build directory; the registration file is regenerated.
|
|
314
|
+
for child in plugins_dir.iterdir():
|
|
315
|
+
if child.is_dir():
|
|
316
|
+
shutil.rmtree(child)
|
|
317
|
+
|
|
318
|
+
bundled = [p for p in plugins if p.has_ios]
|
|
319
|
+
for plugin in bundled:
|
|
320
|
+
target = plugins_dir / plugin.name
|
|
321
|
+
for rel in plugin.ios_sources:
|
|
322
|
+
dest = target / Path(*rel.parts[1:]) # strip the leading "ios/"
|
|
323
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
324
|
+
shutil.copy2(plugin.root / rel, dest)
|
|
325
|
+
resource_root = Path(project_dir) / "PythonNativeKit/Sources/PythonNativeKit/PluginResources"
|
|
326
|
+
if resource_root.exists():
|
|
327
|
+
shutil.rmtree(resource_root)
|
|
328
|
+
for plugin in bundled:
|
|
329
|
+
for rel in plugin.ios_resources:
|
|
330
|
+
target = resource_root / plugin.name / rel
|
|
331
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
332
|
+
shutil.copy2(plugin.root / rel, target)
|
|
333
|
+
package = Path(project_dir) / "PythonNativeKit/Package.swift"
|
|
334
|
+
if package.exists():
|
|
335
|
+
contents = package.read_text(encoding="utf-8")
|
|
336
|
+
contents = contents.replace(', resources: [.process("PluginResources")]', "")
|
|
337
|
+
if resource_root.exists():
|
|
338
|
+
contents = contents.replace(
|
|
339
|
+
'path: "Sources/PythonNativeKit"',
|
|
340
|
+
'path: "Sources/PythonNativeKit", resources: [.process("PluginResources")]',
|
|
341
|
+
)
|
|
342
|
+
package.write_text(contents, encoding="utf-8")
|
|
343
|
+
registration.write_text(_render_swift_registration(bundled), encoding="utf-8")
|
|
344
|
+
if bundled:
|
|
345
|
+
emit(
|
|
346
|
+
f"Bundled {len(bundled)} native plugin(s) into PythonNativeKit: " + ", ".join(p.name for p in bundled) + "."
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def stage_android_plugins(project_dir: Path, plugins: Sequence[NativePlugin], *, log: Optional[Logger] = None) -> None:
|
|
351
|
+
"""Copy Kotlin sources into the ``pythonnative`` module and write the registration file.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
project_dir: The staged ``android_template`` directory.
|
|
355
|
+
plugins: Plugins to bundle; ones without an Android side are skipped.
|
|
356
|
+
log: Optional progress logger.
|
|
357
|
+
"""
|
|
358
|
+
emit: Logger = log or (lambda _message: None)
|
|
359
|
+
source_root = Path(project_dir).joinpath(*_ANDROID_SOURCE_ROOT)
|
|
360
|
+
registration = Path(project_dir).joinpath(*_ANDROID_REGISTRATION)
|
|
361
|
+
source_root.mkdir(parents=True, exist_ok=True)
|
|
362
|
+
managed_manifest = Path(project_dir) / ".pn-plugin-files.json"
|
|
363
|
+
if managed_manifest.exists():
|
|
364
|
+
for relative in json.loads(managed_manifest.read_text(encoding="utf-8")):
|
|
365
|
+
old = Path(project_dir) / relative
|
|
366
|
+
if old.resolve().is_relative_to(Path(project_dir).resolve()) and old.is_file():
|
|
367
|
+
old.unlink()
|
|
368
|
+
managed = []
|
|
369
|
+
|
|
370
|
+
bundled = [p for p in plugins if p.has_android]
|
|
371
|
+
for plugin in bundled:
|
|
372
|
+
for rel in plugin.android_sources:
|
|
373
|
+
dest = source_root / Path(*rel.parts[1:]) # strip the leading "android/"
|
|
374
|
+
if dest.exists() and dest.read_bytes() != (plugin.root / rel).read_bytes():
|
|
375
|
+
raise PluginError(f"Conflicting plugin source: {dest}")
|
|
376
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
377
|
+
shutil.copy2(plugin.root / rel, dest)
|
|
378
|
+
managed.append(str(dest.relative_to(project_dir)))
|
|
379
|
+
native_root = Path(project_dir) / "pythonnative/src/main"
|
|
380
|
+
for plugin in bundled:
|
|
381
|
+
for kind, resources in (("res", plugin.android_resources), ("assets", plugin.android_assets)):
|
|
382
|
+
for rel in resources:
|
|
383
|
+
parts = rel.parts
|
|
384
|
+
# Android resource type directories (drawable, values, and so on) stay intact.
|
|
385
|
+
index = parts.index(kind) + 1 if kind in parts else max(0, len(parts) - 2)
|
|
386
|
+
target = native_root / kind / Path(*parts[index:])
|
|
387
|
+
if kind == "assets":
|
|
388
|
+
target = native_root / kind / plugin.name / Path(*parts[index:])
|
|
389
|
+
if target.exists() and target.read_bytes() != (plugin.root / rel).read_bytes():
|
|
390
|
+
raise PluginError(f"Conflicting plugin resource: {target}")
|
|
391
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
392
|
+
shutil.copy2(plugin.root / rel, target)
|
|
393
|
+
managed.append(str(target.relative_to(project_dir)))
|
|
394
|
+
registration.parent.mkdir(parents=True, exist_ok=True)
|
|
395
|
+
registration.write_text(_render_kotlin_registration(bundled), encoding="utf-8")
|
|
396
|
+
managed_manifest.write_text(json.dumps(sorted(managed)) + "\n", encoding="utf-8")
|
|
397
|
+
if bundled:
|
|
398
|
+
emit(
|
|
399
|
+
f"Bundled {len(bundled)} native plugin(s) into the pythonnative module: "
|
|
400
|
+
+ ", ".join(p.name for p in bundled)
|
|
401
|
+
+ "."
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _render_swift_registration(plugins: Sequence[NativePlugin]) -> str:
|
|
406
|
+
calls = "".join(f" {p.ios_entry}.register(into: registry)\n" for p in plugins)
|
|
407
|
+
body = f" {{\n{calls} }}" if calls else " {}"
|
|
408
|
+
return (
|
|
409
|
+
"// Generated by `pn build`. Do not edit: the PythonNative build tool\n"
|
|
410
|
+
"// overwrites this file with one `register(into:)` call per bundled plugin.\n"
|
|
411
|
+
"\n"
|
|
412
|
+
"/// Registration hook for plugins copied into `Sources/Plugins` by `pn build`.\n"
|
|
413
|
+
"public enum PNGeneratedPlugins {\n"
|
|
414
|
+
" /// Register every generated plugin into `registry`.\n"
|
|
415
|
+
f" public static func registerAll(into registry: PNRegistry){body}\n"
|
|
416
|
+
"}\n"
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _render_kotlin_registration(plugins: Sequence[NativePlugin]) -> str:
|
|
421
|
+
calls = "".join(f" {p.android_entry}.register(registry)\n" for p in plugins)
|
|
422
|
+
if calls:
|
|
423
|
+
method = f" fun registerAll(registry: PNRegistry) {{\n{calls} }}\n"
|
|
424
|
+
else:
|
|
425
|
+
method = ' @Suppress("UNUSED_PARAMETER")\n fun registerAll(registry: PNRegistry) {}\n'
|
|
426
|
+
return (
|
|
427
|
+
"package com.pythonnative.runtime.plugins\n"
|
|
428
|
+
"\n"
|
|
429
|
+
"import com.pythonnative.runtime.bridge.PNRegistry\n"
|
|
430
|
+
"\n"
|
|
431
|
+
"/**\n"
|
|
432
|
+
" * Plugin registrations generated by `pn build`.\n"
|
|
433
|
+
" *\n"
|
|
434
|
+
" * The build tool overwrites this file with one `register` call per\n"
|
|
435
|
+
" * plugin entry declared in the project's `pythonnative.plugins` entry\n"
|
|
436
|
+
" * points. Nothing else may depend on its content beyond [registerAll].\n"
|
|
437
|
+
" */\n"
|
|
438
|
+
"object GeneratedPlugins {\n"
|
|
439
|
+
" /** Register every generated plugin with `registry`. */\n"
|
|
440
|
+
f"{method}"
|
|
441
|
+
"}\n"
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def discover_target_plugins(config: Any, targets: Sequence[Any], runner: Any) -> List[NativePlugin]:
|
|
446
|
+
"""Discover native source manifests from the wheels selected for each device.
|
|
447
|
+
|
|
448
|
+
Target wheels are inspected as data. Their Python code isn't imported into
|
|
449
|
+
the build interpreter, so binary extensions and host-only installations
|
|
450
|
+
don't affect native plugin discovery.
|
|
451
|
+
"""
|
|
452
|
+
import hashlib
|
|
453
|
+
import zipfile
|
|
454
|
+
|
|
455
|
+
from . import deps
|
|
456
|
+
from .lockfile import read, target_key
|
|
457
|
+
|
|
458
|
+
if not config.requirements:
|
|
459
|
+
return []
|
|
460
|
+
selected: Dict[str, NativePlugin] = {}
|
|
461
|
+
fingerprints: Dict[str, str] = {}
|
|
462
|
+
lock = read(config)
|
|
463
|
+
for target in targets:
|
|
464
|
+
cache = config.project_root / "build" / "plugin_wheels" / target_key(target).replace(":", "_")
|
|
465
|
+
cache.mkdir(parents=True, exist_ok=True)
|
|
466
|
+
if lock is not None:
|
|
467
|
+
entry = lock["targets"].get(target_key(target))
|
|
468
|
+
if entry is None:
|
|
469
|
+
raise PluginError(f"pn.lock has no {target.label}; run 'pn deps --lock'.")
|
|
470
|
+
urls = [package["url"] + "#sha256=" + package["sha256"] for package in entry["packages"]]
|
|
471
|
+
else:
|
|
472
|
+
resolution = deps.resolve(config, target, runner=runner)
|
|
473
|
+
if not resolution.ok:
|
|
474
|
+
raise PluginError(f"Cannot resolve plugins for {target.label}: {resolution.error}")
|
|
475
|
+
urls = [package.url for package in resolution.packages]
|
|
476
|
+
if urls:
|
|
477
|
+
command = deps.pip_base_args(config, target)
|
|
478
|
+
command[3] = "download"
|
|
479
|
+
result = runner.run([*command, "--no-deps", "--dest", str(cache), *urls], capture=True)
|
|
480
|
+
if not result.ok:
|
|
481
|
+
raise PluginError(f"Could not download plugin metadata for {target.label}: {result.stderr}")
|
|
482
|
+
# Only inspect wheels in this resolution, excluding removed cached packages.
|
|
483
|
+
names = {url.split("#", 1)[0].rsplit("/", 1)[-1] for url in urls}
|
|
484
|
+
for wheel in sorted(cache.glob("*.whl")):
|
|
485
|
+
if wheel.name not in names:
|
|
486
|
+
continue
|
|
487
|
+
with zipfile.ZipFile(wheel) as archive:
|
|
488
|
+
manifests = [name for name in archive.namelist() if name.endswith("/" + MANIFEST_NAME)]
|
|
489
|
+
if not manifests:
|
|
490
|
+
continue
|
|
491
|
+
digest = hashlib.sha256(wheel.read_bytes()).hexdigest()
|
|
492
|
+
destination = cache / "extracted" / digest
|
|
493
|
+
if not destination.exists():
|
|
494
|
+
for info in archive.infolist():
|
|
495
|
+
if Path(info.filename).is_absolute() or ".." in Path(info.filename).parts:
|
|
496
|
+
raise PluginError(f"Unsafe wheel path: {info.filename}")
|
|
497
|
+
destination.mkdir(parents=True)
|
|
498
|
+
archive.extractall(destination)
|
|
499
|
+
for manifest_path in manifests:
|
|
500
|
+
plugin = load_plugin((destination / manifest_path).parent)
|
|
501
|
+
fingerprint = hashlib.sha256()
|
|
502
|
+
fingerprint.update((plugin.root / MANIFEST_NAME).read_bytes())
|
|
503
|
+
if plugin.contracts is not None:
|
|
504
|
+
fingerprint.update((plugin.root / plugin.contracts).read_bytes())
|
|
505
|
+
for rel in [
|
|
506
|
+
*plugin.ios_sources,
|
|
507
|
+
*plugin.android_sources,
|
|
508
|
+
*plugin.ios_resources,
|
|
509
|
+
*plugin.android_resources,
|
|
510
|
+
*plugin.android_assets,
|
|
511
|
+
]:
|
|
512
|
+
fingerprint.update(str(rel).encode())
|
|
513
|
+
fingerprint.update((plugin.root / rel).read_bytes())
|
|
514
|
+
value = fingerprint.hexdigest()
|
|
515
|
+
if plugin.name in fingerprints and fingerprints[plugin.name] != value:
|
|
516
|
+
raise PluginError(f"Plugin {plugin.name} has different native source across target wheels")
|
|
517
|
+
fingerprints[plugin.name] = value
|
|
518
|
+
selected[plugin.name] = plugin
|
|
519
|
+
return [selected[name] for name in sorted(selected)]
|