Skip to content

Commit 1a4c41f

Browse files
authored
Merge pull request beeware#171 from eldare/usage_step_by_step
README - add MACOS_USAGE_GUIDE.md with a detailed step-by-step on how to embed in a MacOS app
2 parents d20cf5c + d3ca240 commit 1a4c41f

File tree

2 files changed

+122
-28
lines changed

2 files changed

+122
-28
lines changed

README.rst

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -106,34 +106,8 @@ install return ``platform`` and ``sysconfig`` responses consistent with
106106
on-device behavior, which will cause ``pip`` to install platform-appropriate
107107
packages.
108108

109-
To add a support package to your own Xcode project:
110-
111-
1. Drag ``Python.xcframework`` and ``python-stdlib`` into your Xcode project
112-
tree.
113-
2. Ensure that these two objects are added to any targets that need to use
114-
them;
115-
3. Add a custom build phase to purge any binary modules for the platform you are
116-
*not* targetting; and
117-
4. Add a custom build phase to sign any of the binary modules in your app.
118-
5. Add CPython API code to your app to create an instance of the Python
119-
interpreter.
120-
121-
For examples of the scripts needed for steps 3 and 4, and the code needed for
122-
step 5, compare with a project generated with Briefcase.
123-
124-
On macOS, you must also either:
125-
1. Enable the "Disable Library Validation" entitlement (found on the "Signing
126-
& Capabilities" tab in XCode); or
127-
2. Sign your app with a Development or Distribution certificate. This will
128-
require a paid Apple Developer subscription.
129-
130-
It is not possible to use an ad-hoc signing certificate with the "Disable
131-
Library Validation" entitlement disabled.
132-
133-
On iOS/tvOS/watchOS, you can use the default developer certificate for deploying
134-
to a device simulator. However, to deploy to a physical device (including your
135-
own), you will require a Development or Distribution certificate, which requires
136-
a paid Apple Developer subscription.
109+
For a detailed instructions on using the support package in your own project,
110+
see the [usage guide](./USAGE.md)
137111

138112
Building binary wheels
139113
----------------------

USAGE.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Usage Guide
2+
3+
## The easy way
4+
5+
The easist way to use these packages is by creating a project with `Briefcase
6+
<https://github.com/beeware/briefcase>`__. Briefcase will download pre-compiled
7+
versions of these support packages, and add them to an XCode project (or
8+
pre-build stub application, in the case of macOS).
9+
10+
## The manual way
11+
12+
The Python support package *can* be manually added to any Xcode project;
13+
however, you'll need to perform some steps manually (essentially reproducing what
14+
Briefcase is doing)
15+
16+
**NOTE** Briefcase usage is the officially supported approach for using this
17+
support package. If you are experiencing diffculties, one approach for debugging
18+
is to generate a "Hello World" project with Briefcase, and compare the project that
19+
Briefcase has generated with your own project.
20+
21+
To add this support package to your own project:
22+
23+
1. [Download a release tarball for your desired Python version and Apple platform](https://github.com/beeware/Python-Apple-support/releases)
24+
25+
2. Add the `python-stdlib` and `Python.xcframework` to your Xcode project. Both
26+
the `python-stdlib` folder and the `Python.xcframework` should be members of
27+
any target that needs to use Python.
28+
29+
3. In Xcode, select the root node of the project tree, and select the target you
30+
want to build.
31+
32+
4. Select "General" -> "Frameworks, Libraries and Embedded Content", and ensure
33+
that `Python.xcframework` is on the list of frameworks. It should be marked
34+
"Do not embed".
35+
36+
5. Select "General" -> "Build Phases", and ensure that the `python-stdlib` folder
37+
is listed in the "Copy Bundle Resources" step.
38+
39+
6. Add a new "Run script" build phase named "Sign Python Binary Modules", with the following content:
40+
41+
```bash
42+
set -e
43+
echo "Signing as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)"
44+
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 {} \;
45+
```
46+
47+
You will now be able to access the Python runtime in your Python code.
48+
49+
If you are on iOS, you will be able to deploy to an iOS simulator without specifying
50+
development team; however, you will need to specify a valid development team to sign
51+
the binaries for deployment onto a physical device (or for submission to the App Store).
52+
53+
If you are on macOS, you will need to specify a valid development team to run
54+
the app. If you don't want to specify a development team in your project, you
55+
will also need to enable the "Disable Library Validation" entitlement under
56+
"Signing & Capabilities" -> "Hardened Runtime" for your project.
57+
58+
## Accessing the Python runtime
59+
60+
There are 2 ways to access the Python runtime in your project code.
61+
62+
### Embedded C API.
63+
64+
You can use the [Python Embedded C
65+
API](https://docs.python.org/3/extending/embedding.html) to instantiate a Python
66+
interpreter. This is the approach taken by Briefcase; you may find the bootstrap
67+
mainline code generated by Briefcase a helpful guide to what is needed to start
68+
an interpreter and run Python code.
69+
70+
### PythonKit
71+
72+
An alternate approach is to use
73+
[PythonKit](https://github.com/pvieito/PythonKit). PythonKit is a package that
74+
provides a Swift API to running Python code.
75+
76+
To use PythonKit in your project:
77+
78+
1. Add PythonKit to your project using the Swift Package manager. See the
79+
PythonKit documentation for details.
80+
81+
2. Create a file called `module.modulemap` inside
82+
`Python.xcframework/macos-arm64_x86_64/Headers/`, containing the following
83+
code:
84+
```
85+
module Python {
86+
umbrella header "Python.h"
87+
export *
88+
link "Python"
89+
}
90+
```
91+
92+
3. In your Swift code, initialize the Python runtime. This should generally be
93+
done as early as possible in the application's lifecycle, but definitely
94+
needs to be done before you invoke Python code:
95+
```swift
96+
import Python
97+
98+
guard let stdLibPath = Bundle.main.path(forResource: "python-stdlib", ofType: nil) else { return }
99+
guard let libDynloadPath = Bundle.main.path(forResource: "python-stdlib/lib-dynload", ofType: nil) else { return }
100+
setenv("PYTHONHOME", stdLibPath, 1)
101+
setenv("PYTHONPATH", "\(stdLibPath):\(libDynloadPath)", 1)
102+
Py_Initialize()
103+
// we now have a Python interpreter ready to be used
104+
```
105+
106+
5. Invoke Python code in your app. For example:
107+
```swift
108+
import PythonKit
109+
110+
let sys = Python.import("sys")
111+
print("Python Version: \(sys.version_info.major).\(sys.version_info.minor)")
112+
print("Python Encoding: \(sys.getdefaultencoding().upper())")
113+
print("Python Path: \(sys.path)")
114+
115+
_ = Python.import("math") // verifies `lib-dynload` is found and signed successfully
116+
```
117+
118+
To integrate 3rd party python code and dependencies, you will need to make sure
119+
`PYTHONPATH` contains their paths; once this has been done, you can run
120+
`Python.import("<lib name>")`. to import that module from inside swift.

0 commit comments

Comments
 (0)