FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
pythonnative/src/pythonnative/project/runtime_assets.py at main · andevsr/pythonnative · GitHub
andevsr
pythonnative
Repository navigation
Code
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
pythonnative
/
src
/
pythonnative
/
project
/
runtime_assets.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
184 lines (154 loc) · 6.71 KB
Breadcrumbs
pythonnative
/
src
/
pythonnative
/
project
/
runtime_assets.py
Copy path
File metadata and controls
184 lines (154 loc) · 6.71 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
"""Embedded CPython runtime acquisition for iOS builds.
iOS apps can't rely on a system Python, so PythonNative bundles a copy of
CPython built for iOS by the excellent
[Python-Apple-support](https://github.com/beeware/Python-Apple-support)
project. This module downloads the pinned release asset for the
project's ``app.python_version``, verifies its checksum, extracts it
once (cached under the build directory), and exposes the path to
``Python.xcframework``.
The xcframework is linked and embedded by the bundled Xcode template at
build time; its ``build/utils.sh`` helper (shipped inside the framework
by BeeWare) installs the standard library and converts binary modules
into signed frameworks during the Xcode build. There is no post-build
copy step.
Android doesn't need any of this: Chaquopy ships its own CPython via
Gradle, so there's no Android equivalent here.
"""
from
__future__
import
annotations
import
hashlib
import
tarfile
import
urllib
.
request
from
dataclasses
import
dataclass
from
pathlib
import
Path
from
typing
import
Callable
,
Optional
# Pinned, checksum-verified Python-Apple-support assets. Every version in
# ``config.SUPPORTED_PYTHON_VERSIONS`` must have an entry here; iOS builds
# refuse to run against an unpinned, unverified runtime.
PINNED_ASSETS
=
{
"3.10"
: (
"3.10-b14"
,
"Python-3.10-iOS-support.b14.tar.gz"
,
"a6da479a67be74569813af77179dc8ac83a5e685324408110bfc04632166e404"
,
),
"3.11"
: (
"3.11-b9"
,
"Python-3.11-iOS-support.b9.tar.gz"
,
"56810335d2b73558f7a16b6b2f7ad855b88c3007b200cbaecae9d6a25a2d1ecc"
,
),
"3.12"
: (
"3.12-b9"
,
"Python-3.12-iOS-support.b9.tar.gz"
,
"a3be9e278c742911db54dd3045bd7451928813508771c9acf14b4af75294edd2"
,
),
}
_DOWNLOAD_URL
=
"https://github.com/beeware/Python-Apple-support/releases/download/{tag}/{name}"
_USER_AGENT
=
"pythonnative-cli"
Logger
=
Callable
[[
str
],
None
]
@
dataclass
class
IOSRuntime
:
"""A resolved, extracted iOS CPython support package.
Attributes:
python_version: The CPython ``major.minor`` version.
xcframework_dir: Path to the extracted ``Python.xcframework``.
"""
python_version
:
str
xcframework_dir
:
Path
@
property
def
install_script
(
self
)
->
Path
:
"""Path to BeeWare's ``utils.sh`` build helper inside the framework."""
return
self
.
xcframework_dir
/
"build"
/
"utils.sh"
def
_sha256
(
path
:
Path
)
->
str
:
digest
=
hashlib
.
sha256
()
with
open
(
path
,
"rb"
)
as
handle
:
for
chunk
in
iter
(
lambda
:
handle
.
read
(
1024
*
1024
),
b""
):
digest
.
update
(
chunk
)
return
digest
.
hexdigest
()
def
_safe_extract
(
tar_path
:
Path
,
dest
:
Path
)
->
None
:
"""Extract a tarball, refusing entries that escape ``dest``."""
dest
=
dest
.
resolve
()
with
tarfile
.
open
(
tar_path
,
"r:gz"
)
as
tar
:
members
=
tar
.
getmembers
()
for
member
in
members
:
target
=
(
dest
/
member
.
name
).
resolve
()
if
not
str
(
target
).
startswith
(
str
(
dest
)):
raise
RuntimeError
(
f"Refusing to extract unsafe path:
{
member
.
name
}
"
)
# ``filter='data'`` (3.12+) blocks unsafe members; older Pythons
# fall back to the manual check above.
try
:
tar
.
extractall
(
dest
,
filter
=
"data"
)
except
TypeError
:
tar
.
extractall
(
dest
)
def
_locate_runtime
(
extract_root
:
Path
,
python_version
:
str
)
->
IOSRuntime
:
xcframework
=
extract_root
/
"Python.xcframework"
if
not
xcframework
.
is_dir
():
raise
RuntimeError
(
"Python.xcframework not found in extracted Python-Apple-support package."
)
runtime
=
IOSRuntime
(
python_version
=
python_version
,
xcframework_dir
=
xcframework
)
if
not
runtime
.
install_script
.
is_file
():
raise
RuntimeError
(
"The extracted Python.xcframework is missing build/utils.sh; the support "
"package layout is older than PythonNative expects. Delete the "
"build/ios_runtime cache and re-run to fetch the pinned asset."
)
return
runtime
def
prepare_ios_runtime
(
cache_dir
:
Path
,
python_version
:
str
=
"3.12"
,
*
,
log
:
Optional
[
Logger
]
=
None
,
)
->
IOSRuntime
:
"""Download (if needed), verify, and extract the iOS CPython package.
The download and extraction are cached under ``cache_dir`` so repeat
builds are fast. Only pinned, checksum-verified versions are
accepted; there is no unverified fallback.
Args:
cache_dir: Directory to store downloads and extractions in.
python_version: CPython ``major.minor`` to fetch.
log: Optional callback for progress messages.
Returns:
A resolved [`IOSRuntime`][pythonnative.project.runtime_assets.IOSRuntime].
Raises:
RuntimeError: If the version has no pinned asset, the download
fails, the checksum doesn't match, or the package layout is
unexpected.
"""
emit
:
Logger
=
log
or
(
lambda
_message
:
None
)
cache_dir
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
pinned
=
PINNED_ASSETS
.
get
(
python_version
)
if
pinned
is
None
:
supported
=
", "
.
join
(
sorted
(
PINNED_ASSETS
))
raise
RuntimeError
(
f"No pinned iOS runtime for Python
{
python_version
}
. "
f"Set app.python_version to one of:
{
supported
}
."
)
tag
,
asset_name
,
expected_sha
=
pinned
extract_root
=
cache_dir
/
f"python-
{
python_version
}
"
if
extract_root
.
is_dir
():
try
:
return
_locate_runtime
(
extract_root
,
python_version
)
except
RuntimeError
:
# Stale/partial extraction: re-extract below.
pass
url
=
_DOWNLOAD_URL
.
format
(
tag
=
tag
,
name
=
asset_name
)
tar_path
=
cache_dir
/
asset_name
if
not
tar_path
.
exists
()
or
_sha256
(
tar_path
)
!=
expected_sha
:
emit
(
f"Downloading embedded Python runtime (
{
python_version
}
iOS):
{
asset_name
}
"
)
req
=
urllib
.
request
.
Request
(
url
,
headers
=
{
"User-Agent"
:
_USER_AGENT
})
try
:
with
urllib
.
request
.
urlopen
(
req
)
as
response
,
open
(
tar_path
,
"wb"
)
as
handle
:
handle
.
write
(
response
.
read
())
except
OSError
as
exc
:
tar_path
.
unlink
(
missing_ok
=
True
)
raise
RuntimeError
(
f"Could not download the iOS Python runtime from
{
url
}
:
{
exc
}
. "
"Check your network connection and re-run."
)
from
exc
actual
=
_sha256
(
tar_path
)
if
actual
!=
expected_sha
:
tar_path
.
unlink
(
missing_ok
=
True
)
raise
RuntimeError
(
f"Checksum mismatch for
{
asset_name
}
: expected
{
expected_sha
}
, got
{
actual
}
. "
"The download may be corrupt; re-run to try again."
)
emit
(
"Extracting embedded Python runtime..."
)
extract_root
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
_safe_extract
(
tar_path
,
extract_root
)
return
_locate_runtime
(
extract_root
,
python_version
)
Back
|
FazBrowse Home
|
New Git URL