Skip to content

Commit

Permalink
gh-127111: Make web example work again
Browse files Browse the repository at this point in the history
I moved the web example from `Tools/wasm` into `Tools/wasm/emscripten/web_example`.
I also added a new target `build_emscripten` which is `build_wasm` but also
builds the web_example. The web_example needs:
1. python.html, copied
2. python.worker.mjs copied
3. python.mjs and python.wasm output from the main linking of the Python interpreter
4. The webserver that sets COOP and COEP
5. python3.14.zip

This last is created by the `wasm_assets.py` script, which required a pretty
small set of changes to work fine for us.

The last thing that should be done is the `python.worker.mjs` script should be
made independent of the Python version: currently 3.14 is hard coded. I ran
into trouble doing this, so maybe I can leave it to a followup.
  • Loading branch information
hoodmane committed Nov 21, 2024
1 parent ff2278e commit 215c295
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 50 deletions.
51 changes: 32 additions & 19 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,6 @@ SRCDIRS= @SRCDIRS@
# Other subdirectories
SUBDIRSTOO= Include Lib Misc

# assets for Emscripten browser builds
WASM_ASSETS_DIR=.$(prefix)
WASM_STDLIB=$(WASM_ASSETS_DIR)/lib/python$(VERSION)/os.py

# Files and directories to be distributed
CONFIGFILES= configure configure.ac acconfig.h pyconfig.h.in Makefile.pre.in
DISTFILES= README.rst ChangeLog $(CONFIGFILES)
Expand Down Expand Up @@ -737,6 +733,9 @@ build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform sh
build_wasm: check-clean-src $(BUILDPYTHON) platform sharedmods \
python-config checksharedmods

.PHONY: build_emscripten
build_emscripten: build_wasm web_example

# Check that the source is clean when building out of source.
.PHONY: check-clean-src
check-clean-src:
Expand Down Expand Up @@ -1016,23 +1015,38 @@ $(DLLLIBRARY) libpython$(LDVERSION).dll.a: $(LIBRARY_OBJS)
else true; \
fi

# wasm32-emscripten browser build
# wasm assets directory is relative to current build dir, e.g. "./usr/local".
# --preload-file turns a relative asset path into an absolute path.
# wasm32-emscripten browser web example

WEBEX_DIR=$(srcdir)/Tools/wasm/emscripten/web_example/
web_example/python.html: $(WEBEX_DIR)/python.html
@mkdir -p web_example
@cp $< $@

web_example/python.worker.mjs: $(WEBEX_DIR)/python.worker.mjs
@mkdir -p web_example
@cp $< $@

.PHONY: wasm_stdlib
wasm_stdlib: $(WASM_STDLIB)
$(WASM_STDLIB): $(srcdir)/Lib/*.py $(srcdir)/Lib/*/*.py \
$(srcdir)/Tools/wasm/wasm_assets.py \
web_example/server.py: $(WEBEX_DIR)/server.py
@mkdir -p web_example
@cp $< $@

WEB_STDLIB=web_example/python$(VERSION)$(ABI_THREAD).zip
$(WEB_STDLIB): $(srcdir)/Lib/*.py $(srcdir)/Lib/*/*.py \
$(WEBEX_DIR)/wasm_assets.py \
Makefile pybuilddir.txt Modules/Setup.local
$(PYTHON_FOR_BUILD) $(srcdir)/Tools/wasm/wasm_assets.py \
--buildroot . --prefix $(prefix)
$(PYTHON_FOR_BUILD) $(WEBEX_DIR)/wasm_assets.py \
--buildroot . --prefix $(prefix) -o $@

python.html: $(srcdir)/Tools/wasm/python.html python.worker.js
@cp $(srcdir)/Tools/wasm/python.html $@
web_example/python.mjs web_example/python.wasm: $(BUILDPYTHON)
@if test $(HOST_GNU_TYPE) != 'wasm32-unknown-emscripten' ; then \
echo "Can only build web_example when target is Emscripten" ;\
exit 1 ;\
fi
cp python.mjs web_example/python.mjs
cp python.wasm web_example/python.wasm

python.worker.js: $(srcdir)/Tools/wasm/python.worker.js
@cp $(srcdir)/Tools/wasm/python.worker.js $@
.PHONY: web_example
web_example: web_example/python.mjs web_example/python.worker.mjs web_example/python.html web_example/server.py $(WEB_STDLIB)

############################################################################
# Header files
Expand Down Expand Up @@ -3053,8 +3067,7 @@ clean-retain-profile: pycremoval
find build -name '*.py[co]' -exec rm -f {} ';' || true
-rm -f pybuilddir.txt
-rm -f _bootstrap_python
-rm -f python.html python*.js python.data python*.symbols python*.map
-rm -f $(WASM_STDLIB)
-rm -rf web_example python.mjs python.wasm python*.symbols python*.map
-rm -f Programs/_testembed Programs/_freeze_module
-rm -rf Python/deepfreeze
-rm -f Python/frozen_modules/*.h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

async initialiseWorker() {
if (!this.worker) {
this.worker = new Worker(this.workerURL)
this.worker = new Worker(this.workerURL, {type: "module"})
this.worker.addEventListener('message', this.handleMessageFromWorker)
}
}
Expand Down Expand Up @@ -347,7 +347,7 @@
programRunning(false)
}

const pythonWorkerManager = new WorkerManager('./python.worker.js', stdio, readyCallback, finishedCallback)
const pythonWorkerManager = new WorkerManager('./python.worker.mjs', stdio, readyCallback, finishedCallback)
}
</script>
</head>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import createEmscriptenModule from "./python.mjs";

class StdinBuffer {
constructor() {
this.sab = new SharedArrayBuffer(128 * Int32Array.BYTES_PER_ELEMENT)
Expand Down Expand Up @@ -59,29 +61,42 @@ const stderr = (charCode) => {

const stdinBuffer = new StdinBuffer()

var Module = {
const emscriptenSettings = {
noInitialRun: true,
stdin: stdinBuffer.stdin,
stdout: stdout,
stderr: stderr,
onRuntimeInitialized: () => {
postMessage({type: 'ready', stdinBuffer: stdinBuffer.sab})
},
async preRun(Module) {
// TODO: remove fixed version number
// Prevent complaints about not finding exec-prefix by making a lib-dynload directory
Module.FS.mkdirTree("/lib/python3.14/lib-dynload/");
Module.addRunDependency("install-stdlib");
const resp = await fetch("python3.14.zip");
const stdlibBuffer = await resp.arrayBuffer();
Module.FS.writeFile(`/lib/python314.zip`, new Uint8Array(stdlibBuffer), { canOwn: true });
Module.removeRunDependency("install-stdlib");
}
}

onmessage = (event) => {
const modulePromise = createEmscriptenModule(emscriptenSettings);


onmessage = async (event) => {
if (event.data.type === 'run') {
const Module = await modulePromise;
if (event.data.files) {
for (const [filename, contents] of Object.entries(event.data.files)) {
Module.FS.writeFile(filename, contents)
}
}
const ret = callMain(event.data.args)
const ret = Module.callMain(event.data.args);
postMessage({
type: 'finished',
returnCode: ret
})
}
}

importScripts('python.js')
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@


class MyHTTPRequestHandler(server.SimpleHTTPRequestHandler):
extensions_map = server.SimpleHTTPRequestHandler.extensions_map.copy()
extensions_map.update(
{
".wasm": "application/wasm",
}
)

def end_headers(self) -> None:
self.send_my_headers()
super().end_headers()
Expand All @@ -44,3 +37,4 @@ def main() -> None:

if __name__ == "__main__":
main()

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from typing import Dict

# source directory
SRCDIR = pathlib.Path(__file__).parent.parent.parent.absolute()
SRCDIR = pathlib.Path(__file__).parents[4].absolute()
SRCDIR_LIB = SRCDIR / "Lib"


Expand Down Expand Up @@ -131,7 +131,7 @@ def filterfunc(filename: str) -> bool:
return pathname not in args.omit_files_absolute

with zipfile.PyZipFile(
args.wasm_stdlib_zip,
args.output,
mode="w",
compression=args.compression,
optimize=optimize,
Expand Down Expand Up @@ -195,6 +195,12 @@ def path(val: str) -> pathlib.Path:
default=pathlib.Path("/usr/local"),
type=path,
)
parser.add_argument(
"-o", "--output",
help="output file",
type=path,
)



def main() -> None:
Expand All @@ -204,7 +210,6 @@ def main() -> None:
args.srcdir = SRCDIR
args.srcdir_lib = SRCDIR_LIB
args.wasm_root = args.buildroot / relative_prefix
args.wasm_stdlib_zip = args.wasm_root / WASM_STDLIB_ZIP
args.wasm_stdlib = args.wasm_root / WASM_STDLIB
args.wasm_dynload = args.wasm_root / WASM_DYNLOAD

Expand Down Expand Up @@ -234,12 +239,10 @@ def main() -> None:
args.wasm_dynload.mkdir(parents=True, exist_ok=True)
marker = args.wasm_dynload / ".empty"
marker.touch()
# os.py is a marker for finding the correct lib directory.
shutil.copy(args.srcdir_lib / "os.py", args.wasm_stdlib)
# The rest of stdlib that's useful in a WASM context.
create_stdlib_zip(args)
size = round(args.wasm_stdlib_zip.stat().st_size / 1024**2, 2)
parser.exit(0, f"Created {args.wasm_stdlib_zip} ({size} MiB)\n")
size = round(args.output.stat().st_size / 1024**2, 2)
parser.exit(0, f"Created {args.output} ({size} MiB)\n")


if __name__ == "__main__":
Expand Down
13 changes: 8 additions & 5 deletions configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 9 additions & 6 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1854,9 +1854,13 @@ if test "$Py_OPT" = 'true' ; then
LDFLAGS_NODIST="$LDFLAGS_NODIST -fno-semantic-interposition"
], [], [-Werror])
])
elif test "$ac_sys_system" = "Emscripten" -o "$ac_sys_system" = "WASI"; then
dnl Emscripten does not support shared extensions yet. Build
dnl "python.[js,wasm]", "pybuilddir.txt", and "platform" files.
elif test "$ac_sys_system" = "Emscripten"; then
dnl Build "python.[js,wasm]", "pybuilddir.txt", and "platform" files.
DEF_MAKE_ALL_RULE="build_emscripten"
REQUIRE_PGO="no"
DEF_MAKE_RULE="all"
elif test "$ac_sys_system" = "WASI"; then
dnl Build "python.wasm", "pybuilddir.txt", and "platform" files.
DEF_MAKE_ALL_RULE="build_wasm"
REQUIRE_PGO="no"
DEF_MAKE_RULE="all"
Expand Down Expand Up @@ -2321,14 +2325,14 @@ AS_CASE([$ac_sys_system],
AS_VAR_IF([Py_DEBUG], [yes], [wasm_debug=yes], [wasm_debug=no])
dnl Start with 20 MB and allow to grow
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sALLOW_MEMORY_GROWTH -sTOTAL_MEMORY=20971520"])
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sALLOW_MEMORY_GROWTH -sINITIAL_MEMORY=20971520"])
dnl map int64_t and uint64_t to JS bigint
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sWASM_BIGINT"])
dnl Include file system support
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sFORCE_FILESYSTEM -lidbfs.js -lnodefs.js -lproxyfs.js -lworkerfs.js"])
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sEXPORTED_RUNTIME_METHODS=FS"])
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sEXPORTED_RUNTIME_METHODS=FS,callMain,ENV"])
AS_VAR_IF([enable_wasm_dynamic_linking], [yes], [
AS_VAR_APPEND([LINKFORSHARED], [" -sMAIN_MODULE"])
Expand All @@ -2339,7 +2343,6 @@ AS_CASE([$ac_sys_system],
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sUSE_PTHREADS"])
AS_VAR_APPEND([LINKFORSHARED], [" -sPROXY_TO_PTHREAD"])
])
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sALLOW_MEMORY_GROWTH"])
dnl not completely sure whether or not we want -sEXIT_RUNTIME, keeping it for now.
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sEXIT_RUNTIME"])
WASM_LINKFORSHARED_DEBUG="-gseparate-dwarf --emit-symbol-map"
Expand Down

0 comments on commit 215c295

Please sign in to comment.