Skip to content

Commit 2f2f220

Browse files
authored
More updates to USAGE and README (#250)
* Adds details on `python-config` and `testbed` * Adds a bare bones interpreter instantiation example * Clarifies the difference between instantiating the interpreter, and accessing the Python API with PythonKit * Adds header exclusions to the modulemap to silence build warnings.
1 parent 5cd3512 commit 2f2f220

File tree

3 files changed

+265
-43
lines changed

3 files changed

+265
-43
lines changed

README.rst

+26-9
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,6 @@ Each support package contains:
8383

8484
* ``VERSIONS``, a text file describing the specific versions of code used to build the
8585
support package;
86-
* ``platform-site``, a folder that contains site customization scripts that can be used
87-
to make your local Python install look like it is an on-device install for each of the
88-
underlying target architectures supported by the platform. This is needed because when
89-
you run ``pip`` you'll be on a macOS machine with a specific architecture; if ``pip``
90-
tries to install a binary package, it will install a macOS binary wheel (which won't
91-
work on iOS/tvOS/watchOS). However, if you add the ``platform-site`` folder to your
92-
``PYTHONPATH`` when invoking pip, the site customization will make your Python install
93-
return ``platform`` and ``sysconfig`` responses consistent with on-device behavior,
94-
which will cause ``pip`` to install platform-appropriate packages.
9586
* ``Python.xcframework``, a multi-architecture build of the Python runtime library
9687

9788
On iOS/tvOS/watchOS, the ``Python.xcframework`` contains a
@@ -105,6 +96,32 @@ needed to build packages. This is required because Xcode uses the ``xcrun``
10596
alias to dynamically generate the name of binaries, but a lot of C tooling
10697
expects that ``CC`` will not contain spaces.
10798

99+
Each slice of an iOS/tvOS/watchOS XCframework also contains a
100+
``platform-config`` folder with a subfolder for each supported architecture in
101+
that slice. These subfolders can be used to make a macOS Python environment
102+
behave as if it were on an iOS/tvOS/watchOS device. This works in one of two
103+
ways:
104+
105+
1. **A sitecustomize.py script**. If the ``platform-config`` subfolder is on
106+
your ``PYTHONPATH`` when a Python interpreter is started, a site
107+
customization will be applied that patches methods in ``sys``, ``sysconfig``
108+
and ``platform`` that are used to identify the system.
109+
110+
2. **A make_cross_venv.py script**. If you call ``make_cross_venv.py``,
111+
providing the location of a virtual environment, the script will add some
112+
files to the ``site-packages`` folder of that environment that will
113+
automatically apply the same set of patches as the ``sitecustomize.py``
114+
script whenever the environment is activated, without any need to modify
115+
``PYTHONPATH``. If you use ``build`` to create an isolated PEP 517
116+
environment to build a wheel, these patches will also be applied to the
117+
isolated build environment that is created.
118+
119+
iOS distributions also contain a copy of the iOS ``testbed`` project - an Xcode
120+
project that can be used to run test suites of Python code. See the `CPython
121+
documentation on testing packages
122+
<https://docs.python.org/3/using/ios.html#testing-a-python-package>`__ for
123+
details on how to use this testbed.
124+
108125
For a detailed instructions on using the support package in your own project,
109126
see the `usage guide <./USAGE.md>`__
110127

USAGE.md

+79-34
Original file line numberDiff line numberDiff line change
@@ -25,60 +25,105 @@ guides:
2525
For tvOS and watchOS, you should be able to broadly follow the instructions in
2626
the iOS guide.
2727

28+
### Using Objective C
29+
30+
Once you've added the Python XCframework to your project, you'll need to
31+
initialize the Python runtime in your Objective C code (This is step 10 of the
32+
iOS guide linked above). This initialization should generally be done as early
33+
as possible in the application's lifecycle, but definitely needs to be done
34+
before you invoke Python code.
35+
36+
As a *bare minimum*, you can do the following:
37+
38+
1. Import the Python C API headers:
39+
```objc
40+
#include <Python/Python.h>
41+
```
42+
43+
2. Initialize the Python interpreter:
44+
```objc
45+
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
46+
NSString *pythonHome = [NSString stringWithFormat:@"%@/python", resourcePath, nil];
47+
NSString *pythonPath = [NSString stringWithFormat:@"%@/lib/python3.13", python_home, nil];
48+
NSString *libDynloadPath = [NSString stringWithFormat:@"%@/lib/python3.13/lib-dynload", python_home, nil];
49+
NSString *appPath = [NSString stringWithFormat:@"%@/app", resourcePath, nil];
50+
51+
setenv("PYTHONHOME", pythonHome, 1);
52+
setenv("PYTHONPATH", [NSString stringWithFormat:@"%@:%@:%@", pythonpath, libDynloadPath, appPath, nil]);
53+
54+
Py_Initialize();
55+
56+
// we now have a Python interpreter ready to be used
57+
```
58+
References to a specific Python version should reflect the version of
59+
Python you are using.
60+
61+
Again - this is the *bare minimum* initialization. In practice, you will likely
62+
need to configure other aspects of the Python interpreter using the
63+
`PyPreConfig` and `PyConfig` mechanisms. Consult the [Python documentation on
64+
interpreter configuration](https://docs.python.org/3/c-api/init_config.html) for
65+
more details on the configuration options that are available. You may find the
66+
[bootstrap mainline code used by
67+
Briefcase](https://github.com/beeware/briefcase-iOS-Xcode-template/blob/main/%7B%7B%20cookiecutter.format%20%7D%7D/%7B%7B%20cookiecutter.class_name%20%7D%7D/main.m)
68+
a helpful point of comparison.
69+
70+
### Using Swift
71+
72+
If you want to use Swift instead of Objective C, the bare minimum initialization
73+
code will look something like this:
74+
75+
1. Import the Python framework:
76+
```swift
77+
import Python
78+
```
79+
80+
2. Initialize the Python interpreter:
81+
```swift
82+
guard let pythonHome = Bundle.main.path(forResource: "python", ofType: nil) else { return }
83+
guard let pythonPath = Bundle.main.path(forResource: "python/lib/python3.13", ofType: nil) else { return }
84+
guard let libDynloadPath = Bundle.main.path(forResource: "python/lib/python3.13/lib-dynload", ofType: nil) else { return }
85+
let appPath = Bundle.main.path(forResource: "app", ofType: nil)
86+
87+
setenv("PYTHONHOME", pythonHome, 1)
88+
setenv("PYTHONPATH", [pythonPath, libDynloadPath, appPath].compactMap { $0 }.joined(separator: ":"), 1)
89+
Py_Initialize()
90+
// we now have a Python interpreter ready to be used
91+
```
92+
93+
Again, references to a specific Python version should reflect the version of
94+
Python you are using; and you will likely need to use `PyPreConfig` and
95+
`PreConfig` APIs.
96+
2897
## Accessing the Python runtime
2998

3099
There are 2 ways to access the Python runtime in your project code.
31100

32101
### Embedded C API
33102

34103
You can use the [Python Embedded C
35-
API](https://docs.python.org/3/extending/embedding.html) to instantiate a Python
36-
interpreter. This is the approach taken by Briefcase; you may find the bootstrap
37-
mainline code generated by Briefcase a helpful guide to what is needed to start
38-
an interpreter and run Python code.
104+
API](https://docs.python.org/3/extending/embedding.html) to invoke Python code
105+
and interact with Python objects. This is a raw C API that is accesible to both
106+
Objective C and Swift.
39107

40108
### PythonKit
41109

42-
An alternate approach is to use
110+
If you're using Swift, an alternate approach is to use
43111
[PythonKit](https://github.com/pvieito/PythonKit). PythonKit is a package that
44112
provides a Swift API to running Python code.
45113

46114
To use PythonKit in your project, add the Python Apple Support package to your
47-
project as described above; then:
48-
49-
1. Add PythonKit to your project using the Swift Package manager. See the
50-
PythonKit documentation for details.
115+
project and instantiate a Python interpreter as described above; then add
116+
PythonKit to your project using the Swift Package manager (see the [PythonKit
117+
documentation](https://github.com/pvieito/PythonKit) for details).
51118

52-
2. In your Swift code, initialize the Python runtime. This should generally be
53-
done as early as possible in the application's lifecycle, but definitely
54-
needs to be done before you invoke Python code. References to a specific
55-
Python version should reflect the version of Python you are using:
119+
Once you've done this, you can import PythonKit:
56120
```swift
57-
import Python
58-
59-
guard let pythonHome = Bundle.main.path(forResource: "python", ofType: nil) else { return }
60-
guard let pythonPath = Bundle.main.path(forResource: "python/lib/python3.13", ofType: nil) else { return }
61-
guard let libDynloadPath = Bundle.main.path(forResource: "python/lib/python3.13/lib-dynload", ofType: nil) else { return }
62-
let appPath = Bundle.main.path(forResource: "app", ofType: nil)
63-
64-
setenv("PYTHONHOME", pythonHome, 1)
65-
setenv("PYTHONPATH", [pythonPath, libDynloadPath, appPath].compactMap { $0 }.joined(separator: ":"), 1)
66-
Py_Initialize()
67-
// we now have a Python interpreter ready to be used
121+
import PythonKit
68122
```
69-
70-
3. Invoke Python code in your app. For example:
123+
and use the PythonKit Swift API to interact with Python code:
71124
```swift
72-
import PythonKit
73-
74125
let sys = Python.import("sys")
75126
print("Python Version: \(sys.version_info.major).\(sys.version_info.minor)")
76127
print("Python Encoding: \(sys.getdefaultencoding().upper())")
77128
print("Python Path: \(sys.path)")
78-
79-
_ = Python.import("math") // verifies `lib-dynload` is found and signed successfully
80129
```
81-
82-
To integrate 3rd party python code and dependencies, you will need to make sure
83-
`PYTHONPATH` contains their paths; once this has been done, you can run
84-
`Python.import("<lib name>")`. to import that module from inside swift.

patch/Python/module.modulemap

+160
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,164 @@ module Python {
22
umbrella header "Python.h"
33
export *
44
link "Python"
5+
6+
exclude header "datetime.h"
7+
exclude header "dynamic_annotations.h"
8+
exclude header "errcode.h"
9+
exclude header "frameobject.h"
10+
exclude header "marshal.h"
11+
exclude header "opcode_ids.h"
12+
exclude header "opcode.h"
13+
exclude header "osdefs.h"
14+
exclude header "py_curses.h"
15+
exclude header "pyconfig-arm64.h"
16+
exclude header "pyconfig-x86_64.h"
17+
exclude header "pyconfig-arm32_64.h"
18+
exclude header "pydtrace.h"
19+
exclude header "pyexpat.h"
20+
exclude header "structmember.h"
21+
22+
exclude header "cpython/frameobject.h"
23+
exclude header "cpython/pthread_stubs.h"
24+
exclude header "cpython/pyatomic_msc.h"
25+
exclude header "cpython/pyatomic_std.h"
26+
exclude header "cpython/pystats.h"
27+
28+
exclude header "internal/mimalloc/mimalloc.h"
29+
exclude header "internal/mimalloc/mimalloc/atomic.h"
30+
exclude header "internal/mimalloc/mimalloc/internal.h"
31+
exclude header "internal/mimalloc/mimalloc/prim.h"
32+
exclude header "internal/mimalloc/mimalloc/track.h"
33+
exclude header "internal/mimalloc/mimalloc/types.h"
34+
35+
exclude header "internal/pycore_abstract.h"
36+
exclude header "internal/pycore_asdl.h"
37+
exclude header "internal/pycore_ast_state.h"
38+
exclude header "internal/pycore_ast.h"
39+
exclude header "internal/pycore_atexit.h"
40+
exclude header "internal/pycore_audit.h"
41+
exclude header "internal/pycore_backoff.h"
42+
exclude header "internal/pycore_bitutils.h"
43+
exclude header "internal/pycore_blocks_output_buffer.h"
44+
exclude header "internal/pycore_brc.h"
45+
exclude header "internal/pycore_bytes_methods.h"
46+
exclude header "internal/pycore_bytesobject.h"
47+
exclude header "internal/pycore_call.h"
48+
exclude header "internal/pycore_capsule.h"
49+
exclude header "internal/pycore_cell.h"
50+
exclude header "internal/pycore_ceval_state.h"
51+
exclude header "internal/pycore_ceval.h"
52+
exclude header "internal/pycore_code.h"
53+
exclude header "internal/pycore_codecs.h"
54+
exclude header "internal/pycore_compile.h"
55+
exclude header "internal/pycore_complexobject.h"
56+
exclude header "internal/pycore_condvar.h"
57+
exclude header "internal/pycore_context.h"
58+
exclude header "internal/pycore_critical_section.h"
59+
exclude header "internal/pycore_crossinterp_data_registry.h"
60+
exclude header "internal/pycore_crossinterp.h"
61+
exclude header "internal/pycore_debug_offsets.h"
62+
exclude header "internal/pycore_descrobject.h"
63+
exclude header "internal/pycore_dict_state.h"
64+
exclude header "internal/pycore_dict.h"
65+
exclude header "internal/pycore_dtoa.h"
66+
exclude header "internal/pycore_emscripten_signal.h"
67+
exclude header "internal/pycore_emscripten_trampoline.h"
68+
exclude header "internal/pycore_exceptions.h"
69+
exclude header "internal/pycore_faulthandler.h"
70+
exclude header "internal/pycore_fileutils_windows.h"
71+
exclude header "internal/pycore_fileutils.h"
72+
exclude header "internal/pycore_floatobject.h"
73+
exclude header "internal/pycore_flowgraph.h"
74+
exclude header "internal/pycore_format.h"
75+
exclude header "internal/pycore_frame.h"
76+
exclude header "internal/pycore_freelist_state.h"
77+
exclude header "internal/pycore_freelist.h"
78+
exclude header "internal/pycore_function.h"
79+
exclude header "internal/pycore_gc.h"
80+
exclude header "internal/pycore_genobject.h"
81+
exclude header "internal/pycore_getopt.h"
82+
exclude header "internal/pycore_gil.h"
83+
exclude header "internal/pycore_global_objects_fini_generated.h"
84+
exclude header "internal/pycore_global_objects.h"
85+
exclude header "internal/pycore_global_strings.h"
86+
exclude header "internal/pycore_hamt.h"
87+
exclude header "internal/pycore_hashtable.h"
88+
exclude header "internal/pycore_identifier.h"
89+
exclude header "internal/pycore_import.h"
90+
exclude header "internal/pycore_importdl.h"
91+
exclude header "internal/pycore_index_pool.h"
92+
exclude header "internal/pycore_initconfig.h"
93+
exclude header "internal/pycore_instruction_sequence.h"
94+
exclude header "internal/pycore_instruments.h"
95+
exclude header "internal/pycore_interp.h"
96+
exclude header "internal/pycore_intrinsics.h"
97+
exclude header "internal/pycore_jit.h"
98+
exclude header "internal/pycore_list.h"
99+
exclude header "internal/pycore_llist.h"
100+
exclude header "internal/pycore_lock.h"
101+
exclude header "internal/pycore_long.h"
102+
exclude header "internal/pycore_magic_number.h"
103+
exclude header "internal/pycore_memoryobject.h"
104+
exclude header "internal/pycore_mimalloc.h"
105+
exclude header "internal/pycore_modsupport.h"
106+
exclude header "internal/pycore_moduleobject.h"
107+
exclude header "internal/pycore_namespace.h"
108+
exclude header "internal/pycore_object_alloc.h"
109+
exclude header "internal/pycore_object_deferred.h"
110+
exclude header "internal/pycore_object_stack.h"
111+
exclude header "internal/pycore_object_state.h"
112+
exclude header "internal/pycore_object.h"
113+
exclude header "internal/pycore_obmalloc_init.h"
114+
exclude header "internal/pycore_obmalloc.h"
115+
exclude header "internal/pycore_opcode_metadata.h"
116+
exclude header "internal/pycore_opcode_utils.h"
117+
exclude header "internal/pycore_optimizer.h"
118+
exclude header "internal/pycore_parking_lot.h"
119+
exclude header "internal/pycore_parser.h"
120+
exclude header "internal/pycore_pathconfig.h"
121+
exclude header "internal/pycore_pyarena.h"
122+
exclude header "internal/pycore_pyatomic_ft_wrappers.h"
123+
exclude header "internal/pycore_pybuffer.h"
124+
exclude header "internal/pycore_pyerrors.h"
125+
exclude header "internal/pycore_pyhash.h"
126+
exclude header "internal/pycore_pylifecycle.h"
127+
exclude header "internal/pycore_pymath.h"
128+
exclude header "internal/pycore_pymem_init.h"
129+
exclude header "internal/pycore_pymem.h"
130+
exclude header "internal/pycore_pystate.h"
131+
exclude header "internal/pycore_pystats.h"
132+
exclude header "internal/pycore_pythonrun.h"
133+
exclude header "internal/pycore_pythread.h"
134+
exclude header "internal/pycore_qsbr.h"
135+
exclude header "internal/pycore_range.h"
136+
exclude header "internal/pycore_runtime_init_generated.h"
137+
exclude header "internal/pycore_runtime_init.h"
138+
exclude header "internal/pycore_runtime.h"
139+
exclude header "internal/pycore_semaphore.h"
140+
exclude header "internal/pycore_setobject.h"
141+
exclude header "internal/pycore_signal.h"
142+
exclude header "internal/pycore_sliceobject.h"
143+
exclude header "internal/pycore_stackref.h"
144+
exclude header "internal/pycore_strhex.h"
145+
exclude header "internal/pycore_structseq.h"
146+
exclude header "internal/pycore_symtable.h"
147+
exclude header "internal/pycore_sysmodule.h"
148+
exclude header "internal/pycore_time.h"
149+
exclude header "internal/pycore_token.h"
150+
exclude header "internal/pycore_traceback.h"
151+
exclude header "internal/pycore_tracemalloc.h"
152+
exclude header "internal/pycore_tstate.h"
153+
exclude header "internal/pycore_tuple.h"
154+
exclude header "internal/pycore_typeobject.h"
155+
exclude header "internal/pycore_typevarobject.h"
156+
exclude header "internal/pycore_ucnhash.h"
157+
exclude header "internal/pycore_unicodeobject_generated.h"
158+
exclude header "internal/pycore_unicodeobject.h"
159+
exclude header "internal/pycore_unionobject.h"
160+
exclude header "internal/pycore_uniqueid.h"
161+
exclude header "internal/pycore_uop_ids.h"
162+
exclude header "internal/pycore_uop_metadata.h"
163+
exclude header "internal/pycore_warnings.h"
164+
exclude header "internal/pycore_weakref.h"
5165
}

0 commit comments

Comments
 (0)