Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-127111: Emscripten Make web example work again #127113

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
}
)
Comment on lines -17 to -22
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now the default behavior of SimpleHTTPRequestHandler.


def end_headers(self) -> None:
self.send_my_headers()
super().end_headers()
Expand All @@ -42,5 +35,6 @@ def main() -> None:
bind=args.bind,
)


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 All @@ -28,9 +28,7 @@
WASM_STDLIB_ZIP = (
WASM_LIB / f"python{sys.version_info.major}{sys.version_info.minor}.zip"
)
WASM_STDLIB = (
WASM_LIB / f"python{sys.version_info.major}.{sys.version_info.minor}"
)
WASM_STDLIB = WASM_LIB / f"python{sys.version_info.major}.{sys.version_info.minor}"
WASM_DYNLOAD = WASM_STDLIB / "lib-dynload"


Expand Down Expand Up @@ -114,9 +112,7 @@ def get_sysconfigdata(args: argparse.Namespace) -> pathlib.Path:
assert isinstance(args.builddir, pathlib.Path)
data_name: str = sysconfig._get_sysconfigdata_name() # type: ignore[attr-defined]
if not data_name.startswith(SYSCONFIG_NAMES):
raise ValueError(
f"Invalid sysconfig data name '{data_name}'.", SYSCONFIG_NAMES
)
raise ValueError(f"Invalid sysconfig data name '{data_name}'.", SYSCONFIG_NAMES)
filename = data_name + ".py"
return args.builddir / filename

Expand All @@ -131,7 +127,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 +191,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 +206,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 +235,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"])
Copy link
Contributor Author

@hoodmane hoodmane Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This setting has changed names. It's not really relevant to this PR, I could separate it into a separate NFC commit.


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"])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use callMain in python.worker.mjs now so we need to ask for it to be exported.


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"])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also set this on line 2328. It's harmless but unnecessary to set it twice.

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
Loading