Skip to content

Commit

Permalink
pythongh-126898 Emscripten support: Use es6 modules
Browse files Browse the repository at this point in the history
We delete `node_pre.js` and `--pre-js`. Instead that logic moves to
`node_entry.mjs`. In order to access `FS` from outside of `python.mjs`, we need
to add `-sEXPORTED_RUNTIME_METHODS=FS`. Instead of invoking `python.js`
directly, we will invoke `node_entry.mjs`.

Special care has to be taken to ensure that we get `sys.executable` correct. It
should point to the original path that was invoked, without symlinks resolved. I
updated the generated `python.sh` to calculate it's own absolute path with
`realpath -s $0` and pass that in. Then `node_entry.mjs` sets `thisProgram` to
the path passed in. I manually validated this by creating a symlink to
`python.sh` and invoking it. `sys.executable` and `sys._base_exectable`
indeed point to the symlink, not to the build directory `python.sh` and not to
`node_entry.mjs`.
  • Loading branch information
hoodmane committed Nov 16, 2024
1 parent 2313f84 commit 48eacca
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 30 deletions.
36 changes: 25 additions & 11 deletions Tools/wasm/emscripten/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@
import contextlib
import functools
import os

try:
from os import process_cpu_count as cpu_count
except ImportError:
from os import cpu_count
from pathlib import Path
import shutil
import subprocess
import sys
import sysconfig
import tempfile
from pathlib import Path
from textwrap import dedent

try:
from os import process_cpu_count as cpu_count
except ImportError:
from os import cpu_count

WASM_DIR = Path(__file__).parent.parent
CHECKOUT = WASM_DIR.parent.parent

EMSCRIPTEN_DIR = Path(__file__).parent
CHECKOUT = EMSCRIPTEN_DIR.parent.parent.parent

CROSS_BUILD_DIR = CHECKOUT / "cross-build"
BUILD_DIR = CROSS_BUILD_DIR / "build"
Expand Down Expand Up @@ -72,7 +74,7 @@ def wrapper(context):
print("⎯" * terminal_width)
print("📁", working_dir)
if clean_ok and getattr(context, "clean", False) and working_dir.exists():
print(f"🚮 Deleting directory (--clean)...")
print("🚮 Deleting directory (--clean)...")
shutil.rmtree(working_dir)

working_dir.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -207,9 +209,21 @@ def configure_emscripten_python(context, working_dir):
quiet=context.quiet,
)

python_js = working_dir / "python.js"
shutil.copy(EMSCRIPTEN_DIR / "node_entry.mjs", working_dir / "node_entry.mjs")

node_entry = working_dir / "node_entry.mjs"
exec_script = working_dir / "python.sh"
exec_script.write_text(f'#!/bin/sh\nexec {host_runner} {python_js} "$@"\n')
exec_script.write_text(
dedent(
f"""\
#!/bin/sh
# We compute our own path, not following symlinks and pass it in so that
# node_entry.mjs can set sys.executable correctly.
exec {host_runner} {node_entry} "$(realpath -s $0)" "$@"
"""
)
)
exec_script.chmod(0o755)
print(f"🏃‍♀️ Created {exec_script} ... ")
sys.stdout.flush()
Expand Down
30 changes: 30 additions & 0 deletions Tools/wasm/emscripten/node_entry.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import EmscriptenModule from "./python.mjs";
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

if (process?.versions?.node) {
const nodeVersion = Number(process.versions.node.split(".", 1)[0]);
if (nodeVersion < 18) {
process.stderr.write(
`Node version must be >= 18, got version ${process.version}\n`,
);
process.exit(1);
}
}

const settings = {
preRun(Module) {
const __dirname = dirname(fileURLToPath(import.meta.url));
Module.FS.mkdirTree("/lib/");
Module.FS.mount(Module.FS.filesystems.NODEFS, { root: __dirname + "/lib/" }, "/lib/");
},
// The first three arguments are: "node", path to this file, path to
// python.sh. After that come the arguments the user passed to python.sh.
arguments: process.argv.slice(3),
// Ensure that sys.executable, sys._base_executable, etc point to python.sh
// not to this file. To properly handle symlinks, python.sh needs to compute
// its own path.
thisProgram: process.argv[2],
};

await EmscriptenModule(settings);
15 changes: 0 additions & 15 deletions Tools/wasm/emscripten/node_pre.js

This file was deleted.

4 changes: 2 additions & 2 deletions configure

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

4 changes: 2 additions & 2 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1327,7 +1327,7 @@ AC_ARG_WITH([suffix],
)
], [
AS_CASE([$ac_sys_system],
[Emscripten], [EXEEXT=.js],
[Emscripten], [EXEEXT=.mjs],
[WASI], [EXEEXT=.wasm],
[EXEEXT=]
)
Expand Down Expand Up @@ -2328,6 +2328,7 @@ AS_CASE([$ac_sys_system],
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_IF([enable_wasm_dynamic_linking], [yes], [
AS_VAR_APPEND([LINKFORSHARED], [" -sMAIN_MODULE"])
Expand All @@ -2341,7 +2342,6 @@ AS_CASE([$ac_sys_system],
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"])
AS_VAR_APPEND([LDFLAGS_NODIST], [" --pre-js=\$(srcdir)/Tools/wasm/emscripten/node_pre.js"])
WASM_LINKFORSHARED_DEBUG="-gseparate-dwarf --emit-symbol-map"
AS_VAR_IF([wasm_debug], [yes], [
Expand Down

0 comments on commit 48eacca

Please sign in to comment.