Skip to content

Commit 591019e

Browse files
ambvhoodmanemerwok
authored
[3.14] gh-124621: Emscripten: Support pyrepl in browser (GH-136931) (GH-136988)
Basic support for pyrepl in Emscripten. Limitations: * requires JSPI * no signal handling implemented As followup work, it would be nice to implement a webworker variant for when JSPI is not available and proper signal handling. Because it requires JSPI, it doesn't work in Safari. Firefox requires setting an experimental flag. All the Chromiums have full support since May. Until we make it work without JSPI, let's keep the original web_example around. (cherry picked from commit c933a6b) Co-authored-by: Hood Chatham <roberthoodchatham@gmail.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl> Co-authored-by: Éric <merwok@netwok.org>
1 parent 8e3c3b5 commit 591019e

File tree

13 files changed

+502
-39
lines changed

13 files changed

+502
-39
lines changed

Lib/_pyrepl/trace.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import os
4+
import sys
45

56
# types
67
if False:
@@ -12,10 +13,22 @@
1213
trace_file = open(trace_filename, "a")
1314

1415

15-
def trace(line: str, *k: object, **kw: object) -> None:
16-
if trace_file is None:
17-
return
18-
if k or kw:
19-
line = line.format(*k, **kw)
20-
trace_file.write(line + "\n")
21-
trace_file.flush()
16+
17+
if sys.platform == "emscripten":
18+
from posix import _emscripten_log
19+
20+
def trace(line: str, *k: object, **kw: object) -> None:
21+
if "PYREPL_TRACE" not in os.environ:
22+
return
23+
if k or kw:
24+
line = line.format(*k, **kw)
25+
_emscripten_log(line)
26+
27+
else:
28+
def trace(line: str, *k: object, **kw: object) -> None:
29+
if trace_file is None:
30+
return
31+
if k or kw:
32+
line = line.format(*k, **kw)
33+
trace_file.write(line + "\n")
34+
trace_file.flush()

Lib/test/test_fcntl.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import sys
99
import unittest
1010
from test.support import (
11-
cpython_only, get_pagesize, is_apple, requires_subprocess, verbose
11+
cpython_only, get_pagesize, is_apple, requires_subprocess, verbose, is_emscripten
1212
)
1313
from test.support.import_helper import import_module
1414
from test.support.os_helper import TESTFN, unlink, make_bad_fd
@@ -211,6 +211,7 @@ def test_fcntl_f_getpath(self):
211211
@unittest.skipUnless(
212212
hasattr(fcntl, "F_SETPIPE_SZ") and hasattr(fcntl, "F_GETPIPE_SZ"),
213213
"F_SETPIPE_SZ and F_GETPIPE_SZ are not available on all platforms.")
214+
@unittest.skipIf(is_emscripten, "Emscripten pipefs doesn't support these")
214215
def test_fcntl_f_pipesize(self):
215216
test_pipe_r, test_pipe_w = os.pipe()
216217
try:

Makefile.pre.in

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ build_wasm: check-clean-src $(BUILDPYTHON) platform sharedmods \
804804
python-config checksharedmods
805805

806806
.PHONY: build_emscripten
807-
build_emscripten: build_wasm web_example
807+
build_emscripten: build_wasm web_example web_example_pyrepl_jspi
808808

809809
# Check that the source is clean when building out of source.
810810
.PHONY: check-clean-src
@@ -1095,26 +1095,28 @@ $(DLLLIBRARY) libpython$(LDVERSION).dll.a: $(LIBRARY_OBJS)
10951095

10961096
# wasm32-emscripten browser web example
10971097

1098-
WEBEX_DIR=$(srcdir)/Tools/wasm/emscripten/web_example/
1098+
EMSCRIPTEN_DIR=$(srcdir)/Tools/wasm/emscripten
1099+
WEBEX_DIR=$(EMSCRIPTEN_DIR)/web_example/
1100+
1101+
ZIP_STDLIB=python$(VERSION)$(ABI_THREAD).zip
1102+
$(ZIP_STDLIB): $(srcdir)/Lib/*.py $(srcdir)/Lib/*/*.py \
1103+
$(EMSCRIPTEN_DIR)/wasm_assets.py \
1104+
Makefile pybuilddir.txt Modules/Setup.local
1105+
$(PYTHON_FOR_BUILD) $(EMSCRIPTEN_DIR)/wasm_assets.py \
1106+
--buildroot . --prefix $(prefix) -o $@
1107+
10991108
web_example/index.html: $(WEBEX_DIR)/index.html
11001109
@mkdir -p web_example
11011110
@cp $< $@
11021111

1103-
web_example/python.worker.mjs: $(WEBEX_DIR)/python.worker.mjs
1112+
web_example/server.py: $(WEBEX_DIR)/server.py
11041113
@mkdir -p web_example
11051114
@cp $< $@
11061115

1107-
web_example/server.py: $(WEBEX_DIR)/server.py
1116+
web_example/$(ZIP_STDLIB): $(ZIP_STDLIB)
11081117
@mkdir -p web_example
11091118
@cp $< $@
11101119

1111-
WEB_STDLIB=web_example/python$(VERSION)$(ABI_THREAD).zip
1112-
$(WEB_STDLIB): $(srcdir)/Lib/*.py $(srcdir)/Lib/*/*.py \
1113-
$(WEBEX_DIR)/wasm_assets.py \
1114-
Makefile pybuilddir.txt Modules/Setup.local
1115-
$(PYTHON_FOR_BUILD) $(WEBEX_DIR)/wasm_assets.py \
1116-
--buildroot . --prefix $(prefix) -o $@
1117-
11181120
web_example/python.mjs web_example/python.wasm: $(BUILDPYTHON)
11191121
@if test $(HOST_GNU_TYPE) != 'wasm32-unknown-emscripten' ; then \
11201122
echo "Can only build web_example when target is Emscripten" ;\
@@ -1124,7 +1126,35 @@ web_example/python.mjs web_example/python.wasm: $(BUILDPYTHON)
11241126
cp python.wasm web_example/python.wasm
11251127

11261128
.PHONY: web_example
1127-
web_example: web_example/python.mjs web_example/python.worker.mjs web_example/index.html web_example/server.py $(WEB_STDLIB)
1129+
web_example: web_example/python.mjs web_example/index.html web_example/server.py web_example/$(ZIP_STDLIB)
1130+
1131+
WEBEX2=web_example_pyrepl_jspi
1132+
WEBEX2_DIR=$(EMSCRIPTEN_DIR)/$(WEBEX2)/
1133+
1134+
$(WEBEX2)/python.mjs $(WEBEX2)/python.wasm: $(BUILDPYTHON)
1135+
@if test $(HOST_GNU_TYPE) != 'wasm32-unknown-emscripten' ; then \
1136+
echo "Can only build web_example when target is Emscripten" ;\
1137+
exit 1 ;\
1138+
fi
1139+
@mkdir -p $(WEBEX2)
1140+
@cp python.mjs $(WEBEX2)/python.mjs
1141+
@cp python.wasm $(WEBEX2)/python.wasm
1142+
1143+
$(WEBEX2)/index.html: $(WEBEX2_DIR)/index.html
1144+
@mkdir -p $(WEBEX2)
1145+
@cp $< $@
1146+
1147+
$(WEBEX2)/src.mjs: $(WEBEX2_DIR)/src.mjs
1148+
@mkdir -p $(WEBEX2)
1149+
@cp $< $@
1150+
1151+
$(WEBEX2)/$(ZIP_STDLIB): $(ZIP_STDLIB)
1152+
@mkdir -p $(WEBEX2)
1153+
@cp $< $@
1154+
1155+
.PHONY: web_example_pyrepl_jspi
1156+
web_example_pyrepl_jspi: $(WEBEX2)/python.mjs $(WEBEX2)/index.html $(WEBEX2)/src.mjs $(WEBEX2)/$(ZIP_STDLIB)
1157+
11281158

11291159
############################################################################
11301160
# Header files
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pyrepl now works in Emscripten.

Modules/clinic/posixmodule.c.h

Lines changed: 79 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/posixmodule.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16953,6 +16953,25 @@ os__emscripten_debugger_impl(PyObject *module)
1695316953
emscripten_debugger();
1695416954
Py_RETURN_NONE;
1695516955
}
16956+
16957+
EM_JS(void, emscripten_log_impl_js, (const char* arg), {
16958+
console.warn(UTF8ToString(arg));
16959+
});
16960+
16961+
/*[clinic input]
16962+
os._emscripten_log
16963+
arg: str
16964+
16965+
Log something to the JS console. Emscripten only.
16966+
[clinic start generated code]*/
16967+
16968+
static PyObject *
16969+
os__emscripten_log_impl(PyObject *module, const char *arg)
16970+
/*[clinic end generated code: output=9749e5e293c42784 input=350aa1f70bc1e905]*/
16971+
{
16972+
emscripten_log_impl_js(arg);
16973+
Py_RETURN_NONE;
16974+
}
1695616975
#endif /* __EMSCRIPTEN__ */
1695716976

1695816977

@@ -17172,6 +17191,7 @@ static PyMethodDef posix_methods[] = {
1717217191
OS__IS_INPUTHOOK_INSTALLED_METHODDEF
1717317192
OS__CREATE_ENVIRON_METHODDEF
1717417193
OS__EMSCRIPTEN_DEBUGGER_METHODDEF
17194+
OS__EMSCRIPTEN_LOG_METHODDEF
1717517195
{NULL, NULL} /* Sentinel */
1717617196
};
1717717197

0 commit comments

Comments
 (0)