|
| 1 | +# Usage Guide |
| 2 | + |
| 3 | +A Step-by-Step guide on how to embed a Python interpreter in a MacOS app (The process for an iOS app should be quite similar). |
| 4 | +No need to delete the Sandbox (you need it to be able to submit your MacOS App to the App Store). |
| 5 | +No need to `Disable Library Validation`. |
| 6 | + |
| 7 | +1. Add PythonKit SPM: |
| 8 | +https://github.com/pvieito/PythonKit |
| 9 | + |
| 10 | +2. Download Released framework for the desired Python version (for MacOS platform): |
| 11 | +https://github.com/beeware/Python-Apple-support |
| 12 | + |
| 13 | +3. Extract the `python-stdlib` and `Python.xcframework` from the `tag.gz` archive. |
| 14 | + |
| 15 | +4. Copy `python-stdlib` and `Python.xcframework` to the root of the MacOS App, preferably via Xcode. |
| 16 | + |
| 17 | +5. Xcode General -> Frameworks: |
| 18 | + 5.1. Should already be there: |
| 19 | + - `Python.xcframework` is set as `Do Not Embed` |
| 20 | + - `PythonKit` |
| 21 | + 5.2. Add additional required framework: |
| 22 | + - `SystemConfiguration.framework` set as `Do Not Embed` |
| 23 | + |
| 24 | +6. Xcode `Build Phases`: |
| 25 | + 6.1. Verify `Copy Bundle Resources` contains `python-stdlib`. |
| 26 | + 6.2. Add bash script to Sign `.so` binaries in `python-stdlib/lib-dynload/`: |
| 27 | + IMPORTANT NOTE: `.so` binaries must be signed with your TeamID, if you need to use `Sign and Run Locally` it will be signed as ad-hoc, and you will need to `Disable Library Validation`. |
| 28 | +```bash |
| 29 | +set -e |
| 30 | +echo "Signing as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)" |
| 31 | +find "$CODESIGNING_FOLDER_PATH/Contents/Resources/python-stdlib/lib-dynload" -name "*.so" -exec /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \; |
| 32 | +``` |
| 33 | + |
| 34 | +7. Create a file called `module.modulemap` with the following code: |
| 35 | +``` |
| 36 | +module Python { |
| 37 | + umbrella header "Python.h" |
| 38 | + export * |
| 39 | + link "Python" |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +8. Place the `module.modulemap` file inside the `Python.xcframework/macos-arm64_x86_64/Headers/`. |
| 44 | +This will allow us to do `import Python` |
| 45 | + |
| 46 | +9. Init Python at runtime, as early as possible: |
| 47 | +```swift |
| 48 | +import Python |
| 49 | + |
| 50 | +guard let stdLibPath = Bundle.main.path(forResource: "python-stdlib", ofType: nil) else { return } |
| 51 | +guard let libDynloadPath = Bundle.main.path(forResource: "python-stdlib/lib-dynload", ofType: nil) else { return } |
| 52 | +setenv("PYTHONHOME", stdLibPath, 1) |
| 53 | +setenv("PYTHONPATH", "\(stdLibPath):\(libDynloadPath)", 1) |
| 54 | +Py_Initialize() |
| 55 | +// we now have a Python interpreter ready to be used |
| 56 | +``` |
| 57 | + |
| 58 | +10. Run test code: |
| 59 | +```swift |
| 60 | +import PythonKit |
| 61 | + |
| 62 | +let sys = Python.import("sys") |
| 63 | +print("Python Version: \(sys.version_info.major).\(sys.version_info.minor)") |
| 64 | +print("Python Encoding: \(sys.getdefaultencoding().upper())") |
| 65 | +print("Python Path: \(sys.path)") |
| 66 | + |
| 67 | +_ = Python.import("math") // verifies `lib-dynload` is found and signed successfully |
| 68 | +``` |
| 69 | + |
| 70 | +11. To integrate 3rd party python code and dependencies, you will need to make sure `PYTHONPATH` contains their paths; |
| 71 | +And then you can just do `Python.import(" <SOME LIB> ")`. |
| 72 | + |
| 73 | +You're in business. |
0 commit comments