flet-build-template/{{cookiecutter.out_dir}}/web/pyodide/python_cli_entry.mjs

148 lines
4.0 KiB
JavaScript

import { loadPyodide } from "./pyodide.mjs";
import { readdirSync } from "fs";
/**
* Determine which native top level directories to mount into the Emscripten
* file system.
*
* This is a bit brittle, if the machine has a top level directory with certain
* names it is possible this could break. The most surprising one here is tmp, I
* am not sure why but if we link tmp then the process silently fails.
*/
function dirsToMount() {
return readdirSync("/")
.filter((dir) => !["dev", "lib", "proc"].includes(dir))
.map((dir) => "/" + dir);
}
const thisProgramFlag = "--this-program=";
const thisProgramIndex = process.argv.findIndex((x) =>
x.startsWith(thisProgramFlag),
);
const args = process.argv.slice(thisProgramIndex + 1);
const _sysExecutable = process.argv[thisProgramIndex].slice(
thisProgramFlag.length,
);
function fsInit(FS) {
const mounts = dirsToMount();
for (const mount of mounts) {
FS.mkdirTree(mount);
FS.mount(FS.filesystems.NODEFS, { root: mount }, mount);
}
}
async function main() {
let py;
try {
py = await loadPyodide({
args,
_sysExecutable,
env: Object.assign(
{
PYTHONINSPECT: "",
},
process.env,
{ HOME: process.cwd() },
),
fullStdLib: false,
fsInit,
});
} catch (e) {
if (e.constructor.name !== "ExitStatus") {
throw e;
}
// If the user passed `--help`, `--version`, or a set of command line
// arguments that is invalid in some way, we will exit here.
process.exit(e.status);
}
py.setStdout();
py.setStderr();
let sideGlobals = py.runPython("{}");
function handleExit(code) {
if (code === undefined) {
code = 0;
}
if (py._module._Py_FinalizeEx() < 0) {
code = 120;
}
// It's important to call `process.exit` immediately after
// `_Py_FinalizeEx` because otherwise any asynchronous tasks still
// scheduled will segfault.
process.exit(code);
}
sideGlobals.set("handleExit", handleExit);
py.runPython(
`
from pyodide._package_loader import SITE_PACKAGES, should_load_dynlib
from pyodide.ffi import to_js
import re
dynlibs_to_load = to_js([
str(path) for path in SITE_PACKAGES.glob("**/*.so*")
if should_load_dynlib(path)
])
`,
{ globals: sideGlobals },
);
const dynlibs = sideGlobals.get("dynlibs_to_load");
for (const dynlib of dynlibs) {
try {
await py._module.API.loadDynlib(dynlib);
} catch (e) {
console.error("Failed to load lib ", dynlib);
console.error(e);
}
}
py.runPython(
`
import asyncio
# Keep the event loop alive until all tasks are finished, or SystemExit or
# KeyboardInterupt is raised.
loop = asyncio.get_event_loop()
# Make sure we don't run _no_in_progress_handler before we finish _run_main.
loop._in_progress += 1
loop._no_in_progress_handler = handleExit
loop._system_exit_handler = handleExit
loop._keyboard_interrupt_handler = lambda: handleExit(130)
# Make shutil.get_terminal_size tell the terminal size accurately.
import shutil
from js.process import stdout
import os
def get_terminal_size(fallback=(80, 24)):
columns = getattr(stdout, "columns", None)
rows = getattr(stdout, "rows", None)
if columns is None:
columns = fallback[0]
if rows is None:
rows = fallback[1]
return os.terminal_size((columns, rows))
shutil.get_terminal_size = get_terminal_size
`,
{ globals: sideGlobals },
);
let errcode;
try {
if (py._module.jspiSupported) {
errcode = await py._module.promisingRunMain();
} else {
errcode = py._module._run_main();
}
} catch (e) {
if (e.constructor.name === "ExitStatus") {
process.exit(e.status);
}
py._api.fatal_error(e);
}
if (errcode) {
process.exit(errcode);
}
py.runPython("loop._decrement_in_progress()", { globals: sideGlobals });
}
main().catch((e) => {
console.error(e);
process.exit(1);
});