20
20
SRCDIR_LIB = SRCDIR / "Lib"
21
21
22
22
# sysconfig data relative to build dir.
23
- SYSCONFIGDATA_GLOB = "build/lib.*/_sysconfigdata_*.py"
23
+ SYSCONFIGDATA = pathlib .PurePath (
24
+ "build" ,
25
+ f"lib.emscripten-wasm32-{ sys .version_info .major } .{ sys .version_info .minor } " ,
26
+ "_sysconfigdata__emscripten_wasm32-emscripten.py" ,
27
+ )
24
28
25
29
# Library directory relative to $(prefix).
26
30
WASM_LIB = pathlib .PurePath ("lib" )
38
42
OMIT_FILES = (
39
43
# regression tests
40
44
"test/" ,
41
- # user interfaces: TK, curses
42
- "curses/" ,
43
- "idlelib/" ,
44
- "tkinter/" ,
45
- "turtle.py" ,
46
- "turtledemo/" ,
47
45
# package management
48
46
"ensurepip/" ,
49
47
"venv/" ,
50
48
# build system
51
49
"distutils/" ,
52
50
"lib2to3/" ,
53
- # concurrency
54
- "concurrent/" ,
55
- "multiprocessing/" ,
56
51
# deprecated
57
52
"asyncore.py" ,
58
53
"asynchat.py" ,
59
- # Synchronous network I/O and protocols are not supported; for example,
60
- # socket.create_connection() raises an exception:
61
- # "BlockingIOError: [Errno 26] Operation in progress".
54
+ "uu.py" ,
55
+ "xdrlib.py" ,
56
+ # other platforms
57
+ "_aix_support.py" ,
58
+ "_bootsubprocess.py" ,
59
+ "_osx_support.py" ,
60
+ # webbrowser
61
+ "antigravity.py" ,
62
+ "webbrowser.py" ,
63
+ # Pure Python implementations of C extensions
64
+ "_pydecimal.py" ,
65
+ "_pyio.py" ,
66
+ # Misc unused or large files
67
+ "pydoc_data/" ,
68
+ "msilib/" ,
69
+ )
70
+
71
+ # Synchronous network I/O and protocols are not supported; for example,
72
+ # socket.create_connection() raises an exception:
73
+ # "BlockingIOError: [Errno 26] Operation in progress".
74
+ OMIT_NETWORKING_FILES = (
62
75
"cgi.py" ,
63
76
"cgitb.py" ,
64
77
"email/" ,
65
78
"ftplib.py" ,
66
79
"http/" ,
67
80
"imaplib.py" ,
81
+ "mailbox.py" ,
82
+ "mailcap.py" ,
68
83
"nntplib.py" ,
69
84
"poplib.py" ,
70
85
"smtpd.py" ,
77
92
"urllib/response.py" ,
78
93
"urllib/robotparser.py" ,
79
94
"wsgiref/" ,
80
- "xmlrpc/" ,
81
- # dbm / gdbm
82
- "dbm/" ,
83
- # other platforms
84
- "_aix_support.py" ,
85
- "_bootsubprocess.py" ,
86
- "_osx_support.py" ,
87
- # webbrowser
88
- "antigravity.py" ,
89
- "webbrowser.py" ,
90
- # ctypes
91
- "ctypes/" ,
92
- # Pure Python implementations of C extensions
93
- "_pydecimal.py" ,
94
- "_pyio.py" ,
95
- # Misc unused or large files
96
- "pydoc_data/" ,
97
- "msilib/" ,
98
95
)
99
96
97
+ OMIT_MODULE_FILES = {
98
+ "_asyncio" : ["asyncio/" ],
99
+ "audioop" : ["aifc.py" , "sunau.py" , "wave.py" ],
100
+ "_crypt" : ["crypt.py" ],
101
+ "_curses" : ["curses/" ],
102
+ "_ctypes" : ["ctypes/" ],
103
+ "_decimal" : ["decimal.py" ],
104
+ "_dbm" : ["dbm/ndbm.py" ],
105
+ "_gdbm" : ["dbm/gnu.py" ],
106
+ "_json" : ["json/" ],
107
+ "_multiprocessing" : ["concurrent/" , "multiprocessing/" ],
108
+ "pyexpat" : ["xml/" , "xmlrpc/" ],
109
+ "readline" : ["rlcompleter.py" ],
110
+ "_sqlite3" : ["sqlite3/" ],
111
+ "_ssl" : ["ssl.py" ],
112
+ "_tkinter" : ["idlelib/" , "tkinter/" , "turtle.py" , "turtledemo/" ],
113
+
114
+ "_zoneinfo" : ["zoneinfo/" ],
115
+ }
116
+
100
117
# regression test sub directories
101
118
OMIT_SUBDIRS = (
102
119
"ctypes/test/" ,
105
122
)
106
123
107
124
108
- OMIT_ABSOLUTE = {SRCDIR_LIB / name for name in OMIT_FILES }
109
- OMIT_SUBDIRS_ABSOLUTE = tuple (str (SRCDIR_LIB / name ) for name in OMIT_SUBDIRS )
110
-
111
-
112
- def filterfunc (name : str ) -> bool :
113
- return not name .startswith (OMIT_SUBDIRS_ABSOLUTE )
114
-
115
-
116
125
def create_stdlib_zip (
117
- args : argparse .Namespace , compression : int = zipfile .ZIP_DEFLATED , * , optimize : int = 0
126
+ args : argparse .Namespace ,
127
+ * ,
128
+ optimize : int = 0 ,
118
129
) -> None :
119
- sysconfig_data = list (args .builddir .glob (SYSCONFIGDATA_GLOB ))
120
- if not sysconfig_data :
121
- raise ValueError ("No sysconfigdata file found" )
130
+ def filterfunc (name : str ) -> bool :
131
+ return not name .startswith (args .omit_subdirs_absolute )
122
132
123
133
with zipfile .PyZipFile (
124
- args .wasm_stdlib_zip , mode = "w" , compression = compression , optimize = 0
134
+ args .wasm_stdlib_zip , mode = "w" , compression = args . compression , optimize = optimize
125
135
) as pzf :
136
+ if args .compresslevel is not None :
137
+ pzf .compresslevel = args .compresslevel
138
+ pzf .writepy (args .sysconfig_data )
126
139
for entry in sorted (args .srcdir_lib .iterdir ()):
127
140
if entry .name == "__pycache__" :
128
141
continue
129
- if entry in OMIT_ABSOLUTE :
142
+ if entry in args . omit_files_absolute :
130
143
continue
131
144
if entry .name .endswith (".py" ) or entry .is_dir ():
132
145
# writepy() writes .pyc files (bytecode).
133
146
pzf .writepy (entry , filterfunc = filterfunc )
134
- for entry in sysconfig_data :
135
- pzf .writepy (entry )
147
+
148
+
149
+ def detect_extension_modules (args : argparse .Namespace ):
150
+ modules = {}
151
+
152
+ # disabled by Modules/Setup.local ?
153
+ with open (args .builddir / "Makefile" ) as f :
154
+ for line in f :
155
+ if line .startswith ("MODDISABLED_NAMES=" ):
156
+ disabled = line .split ("=" , 1 )[1 ].strip ().split ()
157
+ for modname in disabled :
158
+ modules [modname ] = False
159
+ break
160
+
161
+ # disabled by configure?
162
+ with open (args .sysconfig_data ) as f :
163
+ data = f .read ()
164
+ loc = {}
165
+ exec (data , globals (), loc )
166
+
167
+ for name , value in loc ["build_time_vars" ].items ():
168
+ if value not in {"yes" , "missing" , "disabled" , "n/a" }:
169
+ continue
170
+ if not name .startswith ("MODULE_" ):
171
+ continue
172
+ if name .endswith (("_CFLAGS" , "_DEPS" , "_LDFLAGS" )):
173
+ continue
174
+ modname = name .removeprefix ("MODULE_" ).lower ()
175
+ if modname not in modules :
176
+ modules [modname ] = value == "yes"
177
+ return modules
136
178
137
179
138
180
def path (val : str ) -> pathlib .Path :
@@ -147,7 +189,10 @@ def path(val: str) -> pathlib.Path:
147
189
type = path ,
148
190
)
149
191
parser .add_argument (
150
- "--prefix" , help = "install prefix" , default = pathlib .Path ("/usr/local" ), type = path
192
+ "--prefix" ,
193
+ help = "install prefix" ,
194
+ default = pathlib .Path ("/usr/local" ),
195
+ type = path ,
151
196
)
152
197
153
198
@@ -162,6 +207,27 @@ def main():
162
207
args .wasm_stdlib = args .wasm_root / WASM_STDLIB
163
208
args .wasm_dynload = args .wasm_root / WASM_DYNLOAD
164
209
210
+ # bpo-17004: zipimport supports only zlib compression.
211
+ # Emscripten ZIP_STORED + -sLZ4=1 linker flags results in larger file.
212
+ args .compression = zipfile .ZIP_DEFLATED
213
+ args .compresslevel = 9
214
+
215
+ args .sysconfig_data = args .builddir / SYSCONFIGDATA
216
+ if not args .sysconfig_data .is_file ():
217
+ raise ValueError (f"sysconfigdata file { SYSCONFIGDATA } missing." )
218
+
219
+ extmods = detect_extension_modules (args )
220
+ omit_files = list (OMIT_FILES )
221
+ omit_files .extend (OMIT_NETWORKING_FILES )
222
+ for modname , modfiles in OMIT_MODULE_FILES .items ():
223
+ if not extmods .get (modname ):
224
+ omit_files .extend (modfiles )
225
+
226
+ args .omit_files_absolute = {args .srcdir_lib / name for name in omit_files }
227
+ args .omit_subdirs_absolute = tuple (
228
+ str (args .srcdir_lib / name ) for name in OMIT_SUBDIRS
229
+ )
230
+
165
231
# Empty, unused directory for dynamic libs, but required for site initialization.
166
232
args .wasm_dynload .mkdir (parents = True , exist_ok = True )
167
233
marker = args .wasm_dynload / ".empty"
@@ -170,7 +236,7 @@ def main():
170
236
shutil .copy (args .srcdir_lib / "os.py" , args .wasm_stdlib )
171
237
# The rest of stdlib that's useful in a WASM context.
172
238
create_stdlib_zip (args )
173
- size = round (args .wasm_stdlib_zip .stat ().st_size / 1024 ** 2 , 2 )
239
+ size = round (args .wasm_stdlib_zip .stat ().st_size / 1024 ** 2 , 2 )
174
240
parser .exit (0 , f"Created { args .wasm_stdlib_zip } ({ size } MiB)\n " )
175
241
176
242
0 commit comments