From 0096d27d250dc6bcc33a67695eea1195ee9413d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20B=C3=A1lint=20Misius?= Date: Sat, 10 May 2025 15:17:48 +0200 Subject: [PATCH] Add run-target serve-locally to emscripten builds Invoke with: ninja serve-locally Also fix emscripten builds on some systems where emscripten suddenly needs USE_PTHREADS (?????). Also fix JS-unsafe app_exe values breaking builds. --- meson.build | 21 ++++-- resources/meson.build | 10 +++ resources/serve-wasm.template.py | 120 +++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 resources/serve-wasm.template.py diff --git a/meson.build b/meson.build index 91154e553..6b4baff30 100644 --- a/meson.build +++ b/meson.build @@ -202,6 +202,7 @@ project_cpp_args = [] fftw_dep = dependency('fftw3f', static: is_static) threads_dep = dependency('threads') if host_platform == 'emscripten' + app_exe_jssafe = app_exe.underscorify() zlib_dep = [] png_dep = [] sdl2_dep = [] @@ -215,19 +216,20 @@ if host_platform == 'emscripten' '-s', 'EXPORTED_RUNTIME_METHODS=ccall,cwrap', '-s', 'FS_DEBUG', '-s', 'MODULARIZE', - '-s', 'EXPORT_NAME=create_' + app_exe, + '-s', 'EXPORT_NAME=create_' + app_exe_jssafe, '-Wl,-u,_emscripten_run_callback_on_thread', '-lidbfs.js', + '--source-map-base=./', ] emcc_args = [ '-s', 'USE_SDL=2', '-s', 'USE_BZIP2=1', '-s', 'USE_LIBPNG', + '-s', 'USE_PTHREADS', '-s', 'USE_ZLIB=1', '-s', 'DISABLE_EXCEPTION_CATCHING=0', + '-gsource-map', ] - project_link_args += [ '--source-map-base=./' ] - emcc_args += [ '-gsource-map' ] project_link_args += emcc_args project_cpp_args += emcc_args else @@ -542,7 +544,7 @@ if get_option('build_powder') ) subdir('android') else - executable( + powder_exe = executable( app_exe, sources: powder_files, include_directories: project_inc, @@ -623,3 +625,14 @@ if get_option('clang_tidy') ], ) endif + +if host_platform == 'emscripten' + run_target( + 'serve-locally', + command: [ + python3_prog, + serve_wasm_py, + ], + depends: powder_exe, + ) +endif diff --git a/resources/meson.build b/resources/meson.build index 7eec9548b..62c905656 100644 --- a/resources/meson.build +++ b/resources/meson.build @@ -115,6 +115,16 @@ elif host_platform == 'linux' output: 'appdata.xml', configuration: conf_data, ) +elif host_platform == 'emscripten' + servewasm_conf_data = configuration_data() + servewasm_conf_data.set('SERVER', server) + servewasm_conf_data.set('APP_EXE', app_exe) + servewasm_conf_data.set('APP_EXE_JSSAFE', app_exe_jssafe) + serve_wasm_py = configure_file( + input: 'serve-wasm.template.py', + output: 'serve-wasm.py', + configuration: servewasm_conf_data, + ) endif embedded_files += [ diff --git a/resources/serve-wasm.template.py b/resources/serve-wasm.template.py new file mode 100644 index 000000000..e1d130a9e --- /dev/null +++ b/resources/serve-wasm.template.py @@ -0,0 +1,120 @@ +from OpenSSL import crypto +import http.server +import json +import os +import re +import ssl +import sys +import time + +HTTP_HOST = '127.0.0.1' +HTTP_PORT = 8000 +HTTP_INDEX = 'serve-wasm.index.html' +KEY_FILE = 'serve-wasm.key.pem' +CERT_FILE = 'serve-wasm.cert.crt' +CERT_CN = 'serve-wasm.py self-signed certificate for @APP_EXE@.js' +CERT_TTL = 30 * 24 * 60 * 60 + +print(f'generating index for @APP_EXE@.js') +with open(HTTP_INDEX, 'wt') as f: + f.write(f''' + + + +

Remember, this is being served locally, so it will not be able to connect to @SERVER@. To enable this, serve the following files there:

+ + + + + + +''') + +def remove_if_too_old(path): + if os.path.isfile(path): + diff = time.time() - os.path.getmtime(path) + if diff > CERT_TTL / 2: + os.remove(path) + +remove_if_too_old(CERT_FILE) +remove_if_too_old(KEY_FILE) + +if not (os.path.isfile(CERT_FILE) and os.path.isfile(KEY_FILE)): + print('generating keypair') + key = crypto.PKey() + key.generate_key(crypto.TYPE_RSA, 2048) + cert = crypto.X509() + cert.get_subject().CN = CERT_CN + cert.set_serial_number(int(time.time())) + cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notAfter(CERT_TTL) + cert.set_issuer(cert.get_subject()) + cert.set_pubkey(key) + cert.sign(key, 'sha256') + with open(CERT_FILE, 'wt') as f: + f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8')) + with open(KEY_FILE, 'wt') as f: + f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key).decode('utf-8')) + +class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): + index_pages = (HTTP_INDEX, ) + + def end_headers(self): + print(self.index_pages) + self.send_my_headers() + http.server.SimpleHTTPRequestHandler.end_headers(self) + + def send_my_headers(self): + self.send_header('Cross-Origin-Opener-Policy', 'same-origin') + self.send_header('Cross-Origin-Embedder-Policy', 'require-corp') + +server_address = (HTTP_HOST, HTTP_PORT) +httpd = http.server.HTTPServer(server_address, MyHTTPRequestHandler) +ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +ssl_context.load_cert_chain(certfile=CERT_FILE, keyfile=KEY_FILE) +httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True) +print(f'serving at https://{HTTP_HOST}:{HTTP_PORT}, Ctrl+C to exit') +httpd.serve_forever()