|
| 1 | +import os |
| 2 | +import sys |
| 3 | +import wasmtime |
| 4 | +import pathlib |
| 5 | +import hashlib |
| 6 | +import appdirs |
| 7 | +try: |
| 8 | + from importlib import resources as importlib_resources |
| 9 | + importlib_resources.files |
| 10 | +except (ImportError, AttributeError): |
| 11 | + import importlib_resources |
| 12 | + |
| 13 | + |
| 14 | +def run_wasm(__package__, wasm_filename, *, resources, argv): |
| 15 | + # load the WebAssembly application |
| 16 | + module_binary = importlib_resources.read_binary(__package__, wasm_filename) |
| 17 | + module_digest = hashlib.sha1(module_binary).hexdigest() |
| 18 | + |
| 19 | + wasi_cfg = wasmtime.WasiConfig() |
| 20 | + |
| 21 | + # inherit standard I/O handles |
| 22 | + wasi_cfg.inherit_stdin() |
| 23 | + wasi_cfg.inherit_stdout() |
| 24 | + wasi_cfg.inherit_stderr() |
| 25 | + |
| 26 | + # use provided argv |
| 27 | + wasi_cfg.argv = argv |
| 28 | + |
| 29 | + # preopens for package resources |
| 30 | + for resource in resources: |
| 31 | + wasi_cfg.preopen_dir(str(importlib_resources.files(__package__) / resource), |
| 32 | + "/" + resource) |
| 33 | + |
| 34 | + # preopens for absolute paths |
| 35 | + if os.name == "nt": |
| 36 | + for letter in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ": |
| 37 | + wasi_cfg.preopen_dir(letter + ":\\", letter + ":") |
| 38 | + else: |
| 39 | + wasi_cfg.preopen_dir("/", "/") |
| 40 | + |
| 41 | + # preopens for relative paths |
| 42 | + wasi_cfg.preopen_dir(".", ".") |
| 43 | + for level in range(len(pathlib.Path().cwd().parts)): |
| 44 | + wasi_cfg.preopen_dir(str(pathlib.Path("").joinpath(*[".."] * level)), |
| 45 | + "/".join([".."] * level)) |
| 46 | + |
| 47 | + # compute path to cache |
| 48 | + default_cache_path = appdirs.user_cache_dir("YoWASP", appauthor=False) |
| 49 | + cache_path = pathlib.Path(os.getenv("YOWASP_CACHE_DIR", default_cache_path)) |
| 50 | + cache_filename = (cache_path / __package__ / wasm_filename / module_digest) |
| 51 | + |
| 52 | + # compile WebAssembly to machine code, or load cached |
| 53 | + engine = wasmtime.Engine() |
| 54 | + if cache_filename.exists(): |
| 55 | + module = wasmtime.Module.deserialize_file(engine, str(cache_filename)) |
| 56 | + else: |
| 57 | + print("Preparing to run {}. This might take a while...".format(argv[0]), file=sys.stderr) |
| 58 | + module = wasmtime.Module(engine, module_binary) |
| 59 | + cache_filename.parent.mkdir(parents=True, exist_ok=True) |
| 60 | + with cache_filename.open("wb") as cache_file: |
| 61 | + cache_file.write(module.serialize()) |
| 62 | + |
| 63 | + # run compiled code |
| 64 | + linker = wasmtime.Linker(engine) |
| 65 | + linker.define_wasi() |
| 66 | + store = wasmtime.Store(engine) |
| 67 | + store.set_wasi(wasi_cfg) |
| 68 | + app = linker.instantiate(store, module) |
| 69 | + linker.define_instance(store, "app", app) |
| 70 | + try: |
| 71 | + app.exports(store)["_start"](store) |
| 72 | + return 0 |
| 73 | + except wasmtime.ExitTrap as trap: |
| 74 | + return trap.code |
0 commit comments