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.
This commit is contained in:
Tamás Bálint Misius
2025-05-10 15:17:48 +02:00
parent ad9a63128b
commit 0096d27d25
3 changed files with 147 additions and 4 deletions

View File

@@ -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

View File

@@ -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 += [

View File

@@ -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'''
<!DOCTYPE html>
<html lang="en">
<body>
<p>Remember, this is being served locally, so it will not be able to connect to <a href="@SERVER@">@SERVER@</a>. To enable this, serve the following files there:</p>
<ul>
<li>@APP_EXE@.js</li>
<li>@APP_EXE@.wasm</li>
<li>@APP_EXE@.wasm.map for debugging purposes</li>
</ul>
<p id="status" style="display: none;">Loading...</p>
<canvas style="display: none;" class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
<script type='text/javascript'>
(() => {{
var promise;
window.create_@APP_EXE_JSSAFE@_loader = () => {{
if (promise === undefined) {{
promise = new Promise((resolve, reject) => {{
const script = document.createElement('script');
script.onload = () => {{
resolve(window.create_@APP_EXE_JSSAFE@);
}};
document.head.appendChild(script);
script.src = '@APP_EXE@.js';
}});
}}
return promise;
}};
}})();
(() => {{
var canvas = document.getElementById('canvas');
var status = document.getElementById('status');
window.mark_presentable = function() {{
canvas.style.display = 'initial';
}};
window.onerror = (event) => {{
status.innerText = 'Exception thrown, see JavaScript console';
status.style.display = 'initial';
}};
create_@APP_EXE_JSSAFE@_loader().then(create_@APP_EXE_JSSAFE@ => create_@APP_EXE_JSSAFE@({{
canvas: (() => {{
canvas.addEventListener('webglcontextlost', e => {{
alert('WebGL context lost. You will need to reload the page.'); e.preventDefault();
}}, false);
return canvas;
}})(),
print: console.log,
printErr: console.log,
}}));
}})();
</script>
</body>
</html>
''')
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()