diff --git a/.gitignore b/.gitignore
index 5d67442b54..ca50cf4952 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,17 @@
/libgap*.dll*
/libgap*.so*
+# emscripten / wasm build outputs (etc/emscripten/build.sh and friends)
+/native-build/
+/extern/emscripten/
+/web-example/
+/packages.tar.gz
+/gap.html
+/gap.js
+/gap.wasm
+/gap.worker.js
+/gap-fs.json
+
/bin/gap*.sh
/bin/*-*/
# Compiling the `xgap` package creates the file `xgap.sh`
diff --git a/etc/emscripten/Dockerfile b/etc/emscripten/Dockerfile
new file mode 100644
index 0000000000..e3bff3986e
--- /dev/null
+++ b/etc/emscripten/Dockerfile
@@ -0,0 +1,36 @@
+# Build environment for compiling GAP to WebAssembly via Emscripten.
+#
+# Pinned to emsdk 3.1.23: GAP relies on subtle memory/stack behaviour
+# (notably the ASYNCIFY interaction with GASMAN) that has broken on newer
+# emscripten releases.
+
+FROM emscripten/emsdk:3.1.23
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ autoconf \
+ automake \
+ libtool \
+ make \
+ python3 \
+ ca-certificates \
+ curl \
+ bison \
+ byacc \
+ m4 \
+ && rm -rf /var/lib/apt/lists/*
+
+# Allow running as a non-root host UID without breaking the emscripten cache.
+RUN mkdir -p /emsdk/upstream/emscripten/cache \
+ && chmod -R 0777 /emsdk/upstream/emscripten/cache
+
+# Cache the GAP package distribution tarball so container runs don't
+# re-download it from GitHub each time. The URL pins to the "latest"
+# release on the PackageDistro side, so this snapshot drifts as upstream
+# tags new releases — rebuild the image (--no-cache) to refresh.
+ARG GAP_PACKAGES_URL=https://github.com/gap-system/PackageDistro/releases/download/latest/packages.tar.gz
+RUN curl --fail --location --silent --show-error \
+ --output /opt/gap-packages.tar.gz \
+ "$GAP_PACKAGES_URL" \
+ && chmod 0644 /opt/gap-packages.tar.gz
+
+WORKDIR /gap
diff --git a/etc/emscripten/README.md b/etc/emscripten/README.md
index 5946302efe..e9624f69e2 100644
--- a/etc/emscripten/README.md
+++ b/etc/emscripten/README.md
@@ -1,26 +1,122 @@
-Code to allow building gap to WASM using Emscripten.
+# GAP in the browser
-Files:
+Build GAP as a WebAssembly module and serve it as a self-contained website.
+The terminal interface uses [xterm-pty](https://github.com/mame/xterm-pty),
+so the resulting page behaves like a normal GAP REPL.
-- `build.sh`: Run as `etc/emscripten/build.sh` from a fresh copy of GAP.
+## Quick start
-- `web-template`: Uses 'xterm-pty' to create a "nice" interface to the Wasm GAP.
+From a fresh GAP checkout, with either Docker or Podman installed:
-- `build_startup_manifest.js`: Run it in the web root directory to build `startup_manifest.json` that contains resources to preload.
+```sh
+etc/emscripten/build-in-docker.sh
+cd web-example
+../etc/emscripten/serve.py
+```
+
+Then open . The first build takes 10–30 minutes; the
+docker image, the GAP package distribution, and the GMP/zlib builds are all
+cached for subsequent runs.
+
+To pick up newer GAP packages from upstream, force a fresh image build:
+
+```sh
+docker build --no-cache -t gap-emscripten-build:3.1.23 etc/emscripten/
+```
+
+On Apple Silicon (and other non-amd64 hosts), the build runs `linux/amd64`
+under emulation, since `emscripten/emsdk:3.1.23` is amd64-only on Docker
+Hub. `build-in-docker.sh` pins the platform explicitly so the layer cache
+holds across runs.
+
+The output directory `web-example/` is fully self-contained — copy it to any
+static host (see "Hosting" below for the headers it needs).
+
+## Building without Docker
+
+If you already have emsdk 3.1.23 sourced in your shell, you can run the
+underlying build directly:
+
+```sh
+etc/emscripten/build.sh
+etc/emscripten/assemble-website.sh
+```
+
+emsdk 3.1.23 is the only version we test against. GAP relies on subtle
+ASYNCIFY/GASMAN interactions that have broken on newer emsdk releases.
-See 'run-web-demo.sh' as an example on how to set up a working website.
+## Hosting
-Note that this demo uses xterm-pty, a library which provides a terminal interface
-for emscripten-compiled programs. This uses a javascript feature called
-"SharedArrayBuffer", which requires some headers are returned by the server:
+The xterm-pty terminal uses `SharedArrayBuffer`, which browsers only allow
+when the page is served with these two headers:
```
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
```
-For more details, see for [this article](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).
+`serve.py` is a 20-line stdlib-only Python server that adds them. For
+GitHub Pages and other static hosts that don't let you set headers,
+`web-template/coi-serviceworker.js` is included as a workaround (it
+re-fetches resources through a service worker that adds the headers).
+
+## Files
+
+| File | Role |
+| ---- | ---- |
+| `build-in-docker.sh` | One-stop entry point. Builds the image, runs `build.sh` inside, then `assemble-website.sh`. |
+| `Dockerfile` | Pinned `emscripten/emsdk:3.1.23` with autotools, python3, bison/byacc/m4, and a baked-in copy of the GAP package distribution tarball at `/opt/gap-packages.tar.gz`. |
+| `build.sh` | Configures and builds GMP, zlib, and GAP itself for wasm. |
+| `assemble-website.sh` | Copies the build outputs and data directories (`pkg`, `lib`, `grp`, …) into `web-example/`. |
+| `generate_gap_fs_json.py` | Reads file paths on stdin, writes `gap-fs.json` (the manifest of every file in the virtual FS). |
+| `startup_manifest.json` | List of files to fetch eagerly at startup, captured from a real GAP run. Anything not in this list is fetched lazily on first read. See "Updating the startup manifest" below for how to refresh it. |
+| `serve.py` | Local server that adds the COOP/COEP headers. |
+| `web-template/` | Static UI: `index.html`, the worker scripts, the FS init shim, and the COOP/COEP service worker for hosts where you can't set headers. |
+
+## Updating the startup manifest
+
+`startup_manifest.json` lists files (relative to the GAP root) that the FS
+init shim downloads up front instead of lazily. The current list was
+captured from a real GAP run reaching its prompt, so it includes both
+the core library bootstrap (`lib/init.g`, `lib/read*.g`, …) and any
+default-loaded packages. Entries that no longer exist in the build are
+silently ignored, so it is safe to leave stale entries in place; it is
+also safe to leave the list empty (every file becomes lazy).
+
+The manifest is a startup-time optimisation, not a correctness mechanism:
+a wrong list never breaks the build, it only makes startup slower (files
+GAP needs but the manifest omits get fetched lazily, one round-trip each)
+or wastes bandwidth (files in the manifest that GAP doesn't actually
+read are downloaded anyway). So it's worth refreshing when something
+changes the set of files read at startup — most importantly when the
+default loaded packages change, but also after large library reshuffles.
+
+To regenerate it after such changes:
+
+1. Build the website (`build-in-docker.sh`) and serve it (`serve.py`).
+2. **Empty the served manifest before capturing.** Replace
+ `web-example/startup_manifest.json` with `[]` (or delete it).
+ Otherwise the existing entries are eagerly pre-fetched at startup,
+ appear in `fetchedUrls`, and you'll just round-trip the old list.
+ Editing the served file is enough — no rebuild is needed.
+3. Open the page and wait for the GAP prompt to appear. Every file that
+ GAP actually reads now goes through the lazy `XHR` path and gets
+ captured.
+4. Open devtools and read the captured URLs from the page's JS console:
+ `window.fetchedUrls` is an array of every unique URL the worker
+ requested. Chrome/Firefox provide a `copy()` console helper:
+ `copy(JSON.stringify(fetchedUrls))` puts the JSON on your clipboard.
+5. Strip non-GAP-FS entries (`gap.js`, `gap.wasm`, `gap-fs.json`, the
+ xterm CDN URLs) and write the result to
+ `etc/emscripten/startup_manifest.json` (so it's checked in and gets
+ picked up by the next `assemble-website.sh`). A `jq` filter that
+ keeps just GAP filesystem paths:
+
+ ```sh
+ jq '[.[] | select(test("^(pkg|lib|grp|tst|doc|hpcgap|dev|benchmark)/"))]' \
+ fetched-urls.json > etc/emscripten/startup_manifest.json
+ ```
-The file "coi-serviceworker.js" works around this problem on Github pages. This won't
-work locally, so "server.rb" is a simple ruby script, which just starts a web-server
-which returns the required headers.
\ No newline at end of file
+The bookkeeping lives in `web-template/gap-fs.js` (wraps `fetch` and
+`XMLHttpRequest.open` to report URLs to the main thread) and
+`web-template/index.html` (accumulates them onto `window.fetchedUrls`).
diff --git a/etc/emscripten/assemble-website.sh b/etc/emscripten/assemble-website.sh
new file mode 100755
index 0000000000..13367e5c94
--- /dev/null
+++ b/etc/emscripten/assemble-website.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+#
+# Assemble a self-contained website from a completed wasm build.
+# Outputs ./web-example/ relative to the GAP source root.
+#
+# Run after etc/emscripten/build.sh (or have build-in-docker.sh call it).
+
+set -euo pipefail
+
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
+ROOT_DIR=$(cd "$SCRIPT_DIR/../.." && pwd)
+OUT_DIR="$ROOT_DIR/web-example"
+
+cd "$ROOT_DIR"
+
+for f in gap.js gap.wasm gap.worker.js gap-fs.json; do
+ if [[ ! -f $f ]]; then
+ echo "Error: missing build output '$f'. Run etc/emscripten/build.sh first." >&2
+ exit 1
+ fi
+done
+
+# Always start from a clean output directory. Merging into a previous
+# web-example/ confuses cp when a tree has changed shape between runs
+# (e.g. pkg/X switching between a symlink and a real directory).
+rm -rf "$OUT_DIR"
+mkdir -p "$OUT_DIR"
+
+cp "$SCRIPT_DIR"/web-template/* "$OUT_DIR"/
+cp "$SCRIPT_DIR"/startup_manifest.json "$OUT_DIR"/
+cp gap.js gap.wasm gap.worker.js gap-fs.json "$OUT_DIR"/
+cp LICENSE COPYRIGHT "$OUT_DIR"/
+
+# Data directories. These are referenced by gap-fs.json and either eagerly
+# loaded (if listed in startup_manifest.json) or lazily fetched on first
+# read by Emscripten's createLazyFile.
+#
+# -L follows symlinks so that user setups where pkg/X is a symlink into
+# a separate git/X checkout produce a self-contained output tree (the
+# user often wants to copy web-example/ to another machine or static
+# host where those symlinks would dangle).
+for d in pkg lib grp tst doc hpcgap dev benchmark; do
+ cp -RL "$d" "$OUT_DIR"/
+done
+
+echo "Assembled website at $OUT_DIR"
diff --git a/etc/emscripten/build-in-docker.sh b/etc/emscripten/build-in-docker.sh
new file mode 100755
index 0000000000..169bc271ab
--- /dev/null
+++ b/etc/emscripten/build-in-docker.sh
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+#
+# One-stop shop: build GAP for the web inside a pinned container.
+#
+# Usage (run from the GAP source tree):
+# etc/emscripten/build-in-docker.sh
+#
+# Output: ./web-example/ with a self-contained website. Copy it anywhere
+# and serve with COOP/COEP headers (etc/emscripten/serve.py is one option).
+
+set -euo pipefail
+
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
+ROOT_DIR=$(cd "$SCRIPT_DIR/../.." && pwd)
+
+RUNTIME="${CONTAINER_RUNTIME:-}"
+if [[ -z "$RUNTIME" ]]; then
+ if command -v podman >/dev/null 2>&1; then
+ RUNTIME=podman
+ elif command -v docker >/dev/null 2>&1; then
+ RUNTIME=docker
+ else
+ echo "Error: neither podman nor docker found in PATH." >&2
+ echo "Install one, or set CONTAINER_RUNTIME explicitly." >&2
+ exit 1
+ fi
+fi
+
+IMAGE_TAG="gap-emscripten-build:3.1.23"
+
+# Pin the platform. emscripten/emsdk:3.1.23 is amd64-only on Docker Hub,
+# so on Apple Silicon (or any non-amd64 host) the runtime would otherwise
+# renegotiate the platform on every build — that mismatch invalidates the
+# FROM layer's cache and cascades through the whole image, defeating the
+# layer cache even with --layers.
+PLATFORM="linux/amd64"
+
+echo ">> Using container runtime: $RUNTIME (platform: $PLATFORM)"
+echo ">> Building image $IMAGE_TAG (cached after first run)"
+
+# --layers is the podman/buildah flag for "use the layer cache"; older
+# podman versions default it to false. Docker has caching on by default
+# and rejects the flag, so only pass it for podman.
+declare -a BUILD_ARGS=(--platform "$PLATFORM")
+if [[ "$RUNTIME" != "docker" ]]; then
+ BUILD_ARGS+=(--layers)
+fi
+"$RUNTIME" build "${BUILD_ARGS[@]}" -t "$IMAGE_TAG" -f "$SCRIPT_DIR/Dockerfile" "$SCRIPT_DIR"
+
+# Run as the host user where possible so build outputs are not root-owned.
+declare -a USER_ARGS=()
+if [[ "$RUNTIME" == "docker" ]]; then
+ USER_ARGS=(--user "$(id -u):$(id -g)" -e HOME=/tmp)
+else
+ # Rootless podman maps host UID to container root by default.
+ USER_ARGS=(--userns=keep-id)
+fi
+
+echo ">> Building GAP inside container"
+"$RUNTIME" run --platform "$PLATFORM" --rm \
+ -v "$ROOT_DIR:/gap" \
+ -w /gap \
+ "${USER_ARGS[@]}" \
+ "$IMAGE_TAG" \
+ bash etc/emscripten/build.sh
+
+echo ">> Assembling website"
+bash "$SCRIPT_DIR/assemble-website.sh"
+
+cat </dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
-if ! command -v emmake &> /dev/null; then
- echo Please install, and source, emscripten
- echo This script was tested with version 3.1.23
- echo See https://emscripten.org/docs/getting_started/downloads.html for install instructions
+if ! command -v emmake >/dev/null 2>&1; then
+ echo "Please install and source emscripten." >&2
+ echo "This script is tested with emsdk 3.1.23." >&2
+ echo "See https://emscripten.org/docs/getting_started/downloads.html" >&2
exit 1
-fi;
+fi
# Build the configure script if this is a fresh git checkout
if [[ ! -f ./configure ]]; then
./autogen.sh
fi
-# First build a standard GAP install, for some files
-# we will need during building
+# First build a standard GAP install: we need ffgen and gap-nocomp on the
+# host to generate sources that the wasm build cannot run itself.
(
mkdir -p native-build
cd native-build
if [[ ! -f config.status ]]; then
../configure
fi
- make -j8
+ make -j"$JOBS"
)
AUX_BUILD=$PWD/extern/emscripten/build
AUX_PREFIX=$PWD/extern/emscripten/install
-mkdir -p "$AUX_BUILD"
-mkdir -p "$AUX_PREFIX"
+mkdir -p "$AUX_BUILD" "$AUX_PREFIX"
+# A 32-bit build is required by GAP's small-integer representation, so we
+# pin --build to i686-pc-linux-gnu. This may need revisiting if GAP ever
+# moves to a 64-bit-friendly small-integer encoding.
(
mkdir -p "$AUX_BUILD/gmp"
- cd "$AUX_BUILD/gmp" &&
+ cd "$AUX_BUILD/gmp"
if [[ ! -f config.status ]]; then
CC_FOR_BUILD=/usr/bin/gcc ABI=standard \
- emconfigure $BASEDIR/extern/gmp/configure \
- --build i686-pc-linux-gnu --host none \
- --disable-assembly --enable-cxx \
- --prefix=$AUX_PREFIX
- fi &&
- emmake make -j8 &&
+ emconfigure "$BASEDIR/extern/gmp/configure" \
+ --build i686-pc-linux-gnu --host none \
+ --disable-assembly --enable-cxx \
+ --prefix="$AUX_PREFIX"
+ fi
+ emmake make -j"$JOBS"
emmake make install
)
(
mkdir -p "$AUX_BUILD/zlib"
- cd "$AUX_BUILD/zlib" &&
+ cd "$AUX_BUILD/zlib"
if [[ ! -f Makefile ]]; then
- emconfigure $BASEDIR/extern/zlib/configure --prefix=$AUX_PREFIX
- fi;
- emmake make -j8 &&
+ emconfigure "$BASEDIR/extern/zlib/configure" --prefix="$AUX_PREFIX"
+ fi
+ emmake make -j"$JOBS"
emmake make install
)
-# There are two problems with building GAP
-# 1) GAP builds some executables (ffgen and gap-nocomp), which it wants to
-# execute while building. We get these files from 'native-build'.
-# 2) 'configure' gets confused by some of the LDFLAGS we need, so we have to pass
-# them in to 'make'
+# Two quirks of building GAP under emscripten:
+# 1) GAP runs ffgen and gap-nocomp during the build. We copy the host-built
+# versions in from native-build/ further down.
+# 2) configure rejects some LDFLAGS we need at link time, so we pass them
+# only at make-time.
#
-# These options are:
-# -sASYNCIFY -- we don't care about ASYNC, but this forces the compiler to output
-# all variables onto the stack, which is required for GASMAN
-# Note we could use 'ALLOW_MEMORY_GROWTH', both we don't currently, we instead set
-# a big memory window.
-# -O2 : Some optimisation
-# EXEEXT=.html -- this is actually a GAP makefile option, it lets us make the
-# output 'gap.html', which makes emscripten output a html page we can load
-# --pre-js lazy_fs.js : Prepend lazy_fs.js that is generated for lazy loading
-
-# Run configure if we don't have a makefile, or someone configured this
-# GAP for standard building (emscripten builds will use 'emcc')
-if [[ ! -f GNUmakefile ]] || ! grep '/emcc' GNUmakefile > /dev/null; then
+# Link-time flags worth noting:
+# -sASYNCIFY forces variables onto the stack (required by GASMAN).
+# -O2 enables some optimisation.
+# EXEEXT=.html a GAP makefile knob that produces gap.html, the html
+# shim emscripten generates for loading the wasm module.
+
+if [[ ! -f GNUmakefile ]] || ! grep -q '/emcc' GNUmakefile; then
+ # Wipe any in-tree state from a prior native (or mismatched) build.
+ # Stale build/deps/*.d files reference build/ffdata.h, which under
+ # emcc would be regenerated by a non-executable JS shim; stale .o
+ # files have the wrong architecture. Configure regenerates build/.
+ rm -rf build ffgen
emconfigure ./configure ABI=32 \
- --with-gmp=$AUX_PREFIX \
- --with-zlib=$AUX_PREFIX \
- LDFLAGS="-s ASYNCIFY=1 -O2"
-fi;
+ --with-gmp="$AUX_PREFIX" \
+ --with-zlib="$AUX_PREFIX" \
+ LDFLAGS="-s ASYNCIFY=1 -O2"
+fi
-# Get full required packages
-emmake make bootstrap-pkg-full
+# Provide the GAP package distribution without re-downloading it on every
+# build. Preference order:
+# 1. An existing pkg/ directory.
+# 2. packages.tar.gz already on the host (carried in the source mount).
+# 3. The tarball baked into the docker image at /opt/gap-packages.tar.gz.
+# 4. As a last resort, GAP's own bootstrap-pkg-full target downloads it.
+# Skipping straight to tar avoids `bootstrap-pkg-full`, which calls
+# curl -L -O unconditionally and overwrites packages.tar.gz on every run.
+if [[ ! -d pkg ]]; then
+ if [[ ! -f packages.tar.gz && -f /opt/gap-packages.tar.gz ]]; then
+ cp /opt/gap-packages.tar.gz packages.tar.gz
+ fi
+ if [[ -f packages.tar.gz ]]; then
+ mkdir pkg
+ (cd pkg && tar xzf ../packages.tar.gz)
+ else
+ emmake make bootstrap-pkg-full
+ fi
+fi
-# Copy in files from native_build
+# Copy host-built generated sources into place
cp native-build/build/c_*.c native-build/build/ffdata.* src/
-# Dynamically find and append ALL required files to the JS array
-# The flag -type f is safe because the only symbolic link is 'tst/mockpkg/Makefile.gappkg',
-# which is safe to ignore
-find pkg lib grp tst doc hpcgap dev benchmark -type f | python3 etc/emscripten/generate_gap_fs_json.py
-
-if [ $? -ne 0 ]; then
- echo "Build aborted: generate_gap_fs_json.py failed."
- exit 1
-fi
+# Build the file list that will be served. -L follows symlinks so that
+# users' local development setups (e.g. replacing pkg/foo with a symlink
+# to a git checkout under git/foo) are picked up; -type f then drops any
+# symlinks themselves.
+find -L pkg lib grp tst doc hpcgap dev benchmark -type f \
+ | python3 etc/emscripten/generate_gap_fs_json.py
-# The EXEEXT is usually for windows, but here it lets us set GAP's extension,
-# which lets us produce a html page to run GAP in.
-emmake make -j8 LDFLAGS="-lidbfs.js -s ASYNCIFY=1 -sTOTAL_STACK=32mb -sASYNCIFY_STACK_SIZE=32000000 -sINITIAL_MEMORY=2048mb -O2" EXEEXT=".html"
+emmake make -j"$JOBS" \
+ LDFLAGS="-lidbfs.js -s ASYNCIFY=1 -sTOTAL_STACK=32mb -sASYNCIFY_STACK_SIZE=32000000 -sINITIAL_MEMORY=2048mb -O2" \
+ EXEEXT=".html"
diff --git a/etc/emscripten/build_startup_manifest.js b/etc/emscripten/build_startup_manifest.js
deleted file mode 100755
index edb8594b10..0000000000
--- a/etc/emscripten/build_startup_manifest.js
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/env node
-"use strict";
-
-const http = require('http');
-const fs = require('fs');
-const path = require('path');
-
-const PORT = 9999;
-const BASE_DIR = process.cwd();
-const LOG_FILE = path.join(BASE_DIR, 'startup_manifest.json');
-
-const requestedFiles = new Set();
-fs.writeFileSync(LOG_FILE, '[\n]');
-
-const server = http.createServer((req, res) => {
- let urlPath = req.url.split('?')[0];
- if (urlPath === '/') {
- urlPath = '/index.html';
- }
-
- let localDiskPath = urlPath;
- try {
- localDiskPath = decodeURIComponent(urlPath);
- } catch (e) {}
-
- const filePath = path.join(BASE_DIR, localDiskPath);
-
- fs.readFile(filePath, (err, data) => {
- if (err) {
- console.error(`[404] Ignored missing file: ${localDiskPath}`);
- res.writeHead(404);
- res.end(`404: File not found`);
- return;
- }
-
- const ignored = ['/favicon.ico', '/index.html', '/startup_manifest.json', 'coi-serviceworker.js', 'gap-worker.js', 'gap-fs.js', 'gap.js', 'gap.wasm', 'gap-fs.json'];
- if (!ignored.includes(localDiskPath)) {
- let manifestPath = localDiskPath.startsWith('/') ? localDiskPath.substring(1) : localDiskPath;
-
- if (!requestedFiles.has(manifestPath)) {
- requestedFiles.add(manifestPath);
-
- const manifest = Array.from(requestedFiles);
- fs.writeFileSync(LOG_FILE, JSON.stringify(manifest, null, 4));
-
- console.log(`[Loaded & Logged] ${manifestPath} (Total: ${requestedFiles.size})`);
- }
- }
-
- let contentType = 'application/octet-stream';
- if (urlPath.endsWith('.html')) contentType = 'text/html';
- else if (urlPath.endsWith('.js')) contentType = 'text/javascript';
- else if (urlPath.endsWith('.wasm')) contentType = 'application/wasm';
- else if (urlPath.endsWith('.css')) contentType = 'text/css';
-
- res.writeHead(200, {
- 'Content-Type': contentType,
- 'Cross-Origin-Opener-Policy': 'same-origin',
- 'Cross-Origin-Embedder-Policy': 'require-corp',
- 'Access-Control-Allow-Origin': '*'
- });
-
- res.end(data);
- });
-});
-
-server.listen(PORT, '0.0.0.0', () => {
- console.log(`\n Tracker running at http://localhost:${PORT}/`);
- console.log(`Serving files from: ${BASE_DIR}`);
- console.log(`Logging valid loaded files to: ${LOG_FILE}\n`);
-});
diff --git a/etc/emscripten/generate_gap_fs_json.py b/etc/emscripten/generate_gap_fs_json.py
index dbf5447bba..917f0a2a38 100755
--- a/etc/emscripten/generate_gap_fs_json.py
+++ b/etc/emscripten/generate_gap_fs_json.py
@@ -1,18 +1,11 @@
-import sys
-import json
-import os
-
-def main():
- paths = [line.strip() for line in sys.stdin if line.strip()]
+#!/usr/bin/env python3
+"""Read a list of file paths from stdin (one per line) and write them as a
+JSON array to gap-fs.json in the current directory."""
- try:
- with open('gap-fs.json', 'w', encoding='utf-8') as f:
- json.dump(paths, f, separators=(',', ':'))
-
- print(f"Successfully wrote {len(paths)} files to gap-fs.json", file=sys.stderr)
- except Exception as e:
- print(f"Failed to write gap-fs.json: {e}", file=sys.stderr)
- sys.exit(1)
+import json
+import sys
-if __name__ == "__main__":
- main()
+paths = [line.strip() for line in sys.stdin if line.strip()]
+with open("gap-fs.json", "w", encoding="utf-8") as f:
+ json.dump(paths, f, separators=(",", ":"))
+print(f"wrote {len(paths)} files to gap-fs.json", file=sys.stderr)
diff --git a/etc/emscripten/run-web-demo.sh b/etc/emscripten/run-web-demo.sh
deleted file mode 100755
index a82eb948a9..0000000000
--- a/etc/emscripten/run-web-demo.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env bash
-
-set -eux
-
-echo This script assumes you have already run 'build.sh'
-
-mkdir -p web-example
-cp etc/emscripten/web-template/* web-example/
-cp gap.js gap.wasm gap.worker.js gap-fs.json web-example/
-
-cp -r pkg lib grp tst doc hpcgap dev benchmark web-example/
-cd web-example
-../etc/emscripten/server.rb
diff --git a/etc/emscripten/serve.py b/etc/emscripten/serve.py
new file mode 100755
index 0000000000..7d089f0c54
--- /dev/null
+++ b/etc/emscripten/serve.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+"""Serve the current directory with the COOP/COEP headers required by
+xterm-pty's SharedArrayBuffer usage. Run from the assembled web-example/
+(or any directory containing the gap.* artifacts).
+
+ ./serve.py # listens on 8080
+ ./serve.py 9000 # listens on 9000
+"""
+
+from __future__ import annotations
+
+import http.server
+import socketserver
+import sys
+
+
+class CrossOriginIsolatedHandler(http.server.SimpleHTTPRequestHandler):
+ def end_headers(self):
+ self.send_header("Cross-Origin-Opener-Policy", "same-origin")
+ self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
+ super().end_headers()
+
+
+def main() -> None:
+ port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
+ with socketserver.TCPServer(("", port), CrossOriginIsolatedHandler) as httpd:
+ print(f"Serving on http://localhost:{port}/ (Ctrl-C to stop)")
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ print()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/etc/emscripten/server.rb b/etc/emscripten/server.rb
deleted file mode 100755
index 642c9ad167..0000000000
--- a/etc/emscripten/server.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env ruby
-# Taken from https://github.com/mame/xterm-pty/
-
-require "webrick"
-
-class Server < WEBrick::HTTPServer
- def service(req, res)
- super
- res["Cross-Origin-Opener-Policy"] = "same-origin"
- res["Cross-Origin-Embedder-Policy"] = "require-corp"
- end
-end
-
-Server.new(Port: 8080, DocumentRoot: ".").start
-
diff --git a/etc/emscripten/startup_manifest.json b/etc/emscripten/startup_manifest.json
new file mode 100644
index 0000000000..1782ec1272
--- /dev/null
+++ b/etc/emscripten/startup_manifest.json
@@ -0,0 +1,617 @@
+[
+ "lib/cmdleditx.g",
+ "grp/basic.gd",
+ "grp/classic.gd",
+ "grp/conformal.gd",
+ "grp/perf.gd",
+ "grp/suzuki.gd",
+ "grp/ree.gd",
+ "grp/simple.gd",
+ "grp/imf.gd",
+ "grp/glzmodmz.gd",
+ "grp/clasmax.grp",
+ "grp/basicpcg.gi",
+ "grp/basicprm.gi",
+ "grp/basicmat.gi",
+ "grp/basicfp.gi",
+ "grp/perf.grp",
+ "grp/classic.gi",
+ "grp/conformal.gi",
+ "grp/suzuki.gi",
+ "grp/ree.gi",
+ "grp/simple.gi",
+ "grp/imf.grp",
+ "grp/imf.gi",
+ "grp/glzmodmz.gi",
+ "pkg/4ti2interface/PackageInfo.g",
+ "pkg/aclib/PackageInfo.g",
+ "pkg/agt/PackageInfo.g",
+ "pkg/alco/PackageInfo.g",
+ "pkg/alnuth/PackageInfo.g",
+ "pkg/atlasrep/PackageInfo.g",
+ "pkg/automata/PackageInfo.g",
+ "pkg/automgrp/PackageInfo.g",
+ "pkg/autpgrp/PackageInfo.g",
+ "pkg/browse/PackageInfo.g",
+ "pkg/cap/PackageInfo.g",
+ "pkg/caratinterface/PackageInfo.g",
+ "pkg/cddinterface/PackageInfo.g",
+ "pkg/circle/PackageInfo.g",
+ "pkg/classicpres/PackageInfo.g",
+ "pkg/cohomolo/PackageInfo.g",
+ "pkg/congruence/PackageInfo.g",
+ "pkg/corefreesub/PackageInfo.g",
+ "pkg/corelg/PackageInfo.g",
+ "pkg/crime/PackageInfo.g",
+ "pkg/crisp/PackageInfo.g",
+ "pkg/cryst/PackageInfo.g",
+ "pkg/crystcat/PackageInfo.g",
+ "pkg/ctbllib/PackageInfo.g",
+ "pkg/cubefree/PackageInfo.g",
+ "pkg/deepthought/PackageInfo.g",
+ "pkg/design/PackageInfo.g",
+ "pkg/difsets/PackageInfo.g",
+ "pkg/edim/PackageInfo.g",
+ "pkg/example/PackageInfo.g",
+ "pkg/examplesforhomalg/PackageInfo.g",
+ "pkg/factint/PackageInfo.g",
+ "pkg/fga/PackageInfo.g",
+ "pkg/fining/PackageInfo.g",
+ "pkg/float/PackageInfo.g",
+ "pkg/format/PackageInfo.g",
+ "pkg/forms/PackageInfo.g",
+ "pkg/fplsa/PackageInfo.g",
+ "pkg/francy/PackageInfo.g",
+ "pkg/fwtree/PackageInfo.g",
+ "pkg/gapdoc/PackageInfo.g",
+ "pkg/gauss/PackageInfo.g",
+ "pkg/gaussforhomalg/PackageInfo.g",
+ "pkg/gbnp/PackageInfo.g",
+ "pkg/generalizedmorphismsforcap/PackageInfo.g",
+ "pkg/genss/PackageInfo.g",
+ "pkg/gradedmodules/PackageInfo.g",
+ "pkg/gradedringforhomalg/PackageInfo.g",
+ "pkg/groupoids/PackageInfo.g",
+ "pkg/grpconst/PackageInfo.g",
+ "pkg/guarana/PackageInfo.g",
+ "pkg/guava/PackageInfo.g",
+ "pkg/hap/PackageInfo.g",
+ "pkg/hapcryst/PackageInfo.g",
+ "pkg/hecke/PackageInfo.g",
+ "pkg/help/PackageInfo.g",
+ "pkg/homalg/PackageInfo.g",
+ "pkg/homalgtocas/PackageInfo.g",
+ "pkg/ibnp/PackageInfo.g",
+ "pkg/idrel/PackageInfo.g",
+ "pkg/inducereduce/PackageInfo.g",
+ "pkg/intpic/PackageInfo.g",
+ "pkg/io_forhomalg/PackageInfo.g",
+ "pkg/irredsol/PackageInfo.g",
+ "pkg/itc/PackageInfo.g",
+ "pkg/jupyterviz/PackageInfo.g",
+ "pkg/kan/PackageInfo.g",
+ "pkg/kbmag/PackageInfo.g",
+ "pkg/laguna/PackageInfo.g",
+ "pkg/liealgdb/PackageInfo.g",
+ "pkg/liepring/PackageInfo.g",
+ "pkg/liering/PackageInfo.g",
+ "pkg/linearalgebraforcap/PackageInfo.g",
+ "pkg/lins/PackageInfo.g",
+ "pkg/localizeringforhomalg/PackageInfo.g",
+ "pkg/log/ace.log",
+ "pkg/log/anupq.err",
+ "pkg/log/anupq.log",
+ "pkg/log/anupq.out",
+ "pkg/log/browse.log",
+ "pkg/log/caratinterface.err",
+ "pkg/log/caratinterface.log",
+ "pkg/log/caratinterface.out",
+ "pkg/log/cddinterface.err",
+ "pkg/log/cddinterface.log",
+ "pkg/log/cddinterface.out",
+ "pkg/log/cohomolo.err",
+ "pkg/log/cohomolo.log",
+ "pkg/log/cohomolo.out",
+ "pkg/log/crypting.log",
+ "pkg/log/curlinterface.log",
+ "pkg/log/cvec.log",
+ "pkg/log/datastructures.log",
+ "pkg/log/deepthought.log",
+ "pkg/log/digraphs.log",
+ "pkg/log/edim.log",
+ "pkg/log/example.log",
+ "pkg/log/fail.log",
+ "pkg/log/ferret.log",
+ "pkg/log/float.log",
+ "pkg/log/fplsa.err",
+ "pkg/log/fplsa.log",
+ "pkg/log/fplsa.out",
+ "pkg/log/gauss.log",
+ "pkg/log/grape.err",
+ "pkg/log/grape.log",
+ "pkg/log/grape.out",
+ "pkg/log/guava.err",
+ "pkg/log/guava.log",
+ "pkg/log/guava.out",
+ "pkg/log/io.log",
+ "pkg/log/json.log",
+ "pkg/log/kbmag.log",
+ "pkg/log/normalizinterface.err",
+ "pkg/log/normalizinterface.log",
+ "pkg/log/normalizinterface.out",
+ "pkg/log/nq.log",
+ "pkg/log/orb.log",
+ "pkg/log/profiling.log",
+ "pkg/log/semigroups.err",
+ "pkg/log/semigroups.log",
+ "pkg/log/semigroups.out",
+ "pkg/log/simpcomp.err",
+ "pkg/log/simpcomp.log",
+ "pkg/log/simpcomp.out",
+ "pkg/log/uuid.log",
+ "pkg/log/xgap.err",
+ "pkg/log/xgap.log",
+ "pkg/log/xgap.out",
+ "pkg/log/zeromqinterface.log",
+ "pkg/loops/PackageInfo.g",
+ "pkg/lpres/PackageInfo.g",
+ "pkg/majoranaalgebras/PackageInfo.g",
+ "pkg/mapclass/PackageInfo.g",
+ "pkg/matgrp/PackageInfo.g",
+ "pkg/matricesforhomalg/PackageInfo.g",
+ "pkg/modisom/PackageInfo.g",
+ "pkg/modulepresentationsforcap/PackageInfo.g",
+ "pkg/modules/PackageInfo.g",
+ "pkg/monoidalcategories/PackageInfo.g",
+ "pkg/nconvex/PackageInfo.g",
+ "pkg/nilmat/PackageInfo.g",
+ "pkg/nock/PackageInfo.g",
+ "pkg/numericalsgps/PackageInfo.g",
+ "pkg/openmath/PackageInfo.g",
+ "pkg/orb/PackageInfo.g",
+ "pkg/permut/PackageInfo.g",
+ "pkg/polenta/PackageInfo.g",
+ "pkg/polycyclic/PackageInfo.g",
+ "pkg/polymaking/PackageInfo.g",
+ "pkg/primgrp/PackageInfo.g",
+ "pkg/qdistrnd/PackageInfo.g",
+ "pkg/qpa/PackageInfo.g",
+ "pkg/quagroup/PackageInfo.g",
+ "pkg/radiroot/PackageInfo.g",
+ "pkg/rcwa/PackageInfo.g",
+ "pkg/rds/PackageInfo.g",
+ "pkg/repndecomp/PackageInfo.g",
+ "pkg/repsn/PackageInfo.g",
+ "pkg/resclasses/PackageInfo.g",
+ "pkg/ringsforhomalg/PackageInfo.g",
+ "pkg/sco/PackageInfo.g",
+ "pkg/scscp/PackageInfo.g",
+ "pkg/sglppow/PackageInfo.g",
+ "pkg/sgpviz/PackageInfo.g",
+ "pkg/simpcomp/PackageInfo.g",
+ "pkg/singular/PackageInfo.g",
+ "pkg/sl2reps/PackageInfo.g",
+ "pkg/sla/PackageInfo.g",
+ "pkg/smallantimagmas/PackageInfo.g",
+ "pkg/smallclassnr/PackageInfo.g",
+ "pkg/smallgrp/PackageInfo.g",
+ "pkg/smallsemi/PackageInfo.g",
+ "pkg/sonata/PackageInfo.g",
+ "pkg/sophus/PackageInfo.g",
+ "pkg/sotgrps/PackageInfo.g",
+ "pkg/spinsym/PackageInfo.g",
+ "pkg/symbcompcc/PackageInfo.g",
+ "pkg/thelma/PackageInfo.g",
+ "pkg/tomlib/PackageInfo.g",
+ "pkg/toolsforhomalg/PackageInfo.g",
+ "pkg/toric/PackageInfo.g",
+ "pkg/transgrp/PackageInfo.g",
+ "pkg/twistedconjugacy/PackageInfo.g",
+ "pkg/typeset/PackageInfo.g",
+ "pkg/ugaly/PackageInfo.g",
+ "pkg/unipot/PackageInfo.g",
+ "pkg/unitlib/PackageInfo.g",
+ "pkg/walrus/PackageInfo.g",
+ "pkg/wedderga/PackageInfo.g",
+ "pkg/wpe/PackageInfo.g",
+ "pkg/xgap/PackageInfo.g",
+ "pkg/xmod/PackageInfo.g",
+ "pkg/xmodalg/PackageInfo.g",
+ "pkg/yangbaxter/PackageInfo.g",
+ "pkg/gapdoc/init.g",
+ "pkg/gapdoc/lib/UnicodeTools.gd",
+ "pkg/gapdoc/lib/PrintUtil.gd",
+ "pkg/gapdoc/lib/Text.gd",
+ "pkg/gapdoc/lib/ComposeXML.gd",
+ "pkg/gapdoc/lib/XMLParser.gd",
+ "pkg/gapdoc/lib/GAPDoc.gd",
+ "pkg/gapdoc/lib/BibTeX.gd",
+ "pkg/gapdoc/lib/BibXMLextTools.gd",
+ "pkg/gapdoc/lib/GAPDoc2LaTeX.gd",
+ "pkg/gapdoc/lib/GAPDoc2Text.gd",
+ "pkg/gapdoc/lib/GAPDoc2HTML.gd",
+ "pkg/gapdoc/lib/Make.g",
+ "pkg/gapdoc/lib/Examples.gd",
+ "pkg/gapdoc/read.g",
+ "pkg/gapdoc/lib/UnicodeTools.gi",
+ "pkg/gapdoc/lib/UnicodeTabs.g",
+ "pkg/gapdoc/lib/PrintUtil.gi",
+ "pkg/gapdoc/lib/Text.gi",
+ "pkg/gapdoc/lib/ComposeXML.gi",
+ "pkg/gapdoc/lib/XMLParser.gi",
+ "pkg/gapdoc/lib/gapdocdtdinfo.g",
+ "pkg/gapdoc/lib/GAPDoc.gi",
+ "pkg/gapdoc/lib/BibTeX.gi",
+ "pkg/gapdoc/lib/bibxmlextinfo.g",
+ "pkg/gapdoc/lib/BibXMLextTools.gi",
+ "pkg/gapdoc/lib/GAPDoc2LaTeX.gi",
+ "pkg/gapdoc/lib/latexhead.tex",
+ "pkg/gapdoc/lib/GAPDoc2Text.gi",
+ "pkg/gapdoc/lib/TextThemes.g",
+ "pkg/gapdoc/lib/GAPDoc2HTML.gi",
+ "pkg/gapdoc/lib/Examples.gi",
+ "pkg/gapdoc/lib/HelpBookHandler.g",
+ "pkg/primgrp/init.g",
+ "pkg/primgrp/lib/primitiv.gd",
+ "pkg/primgrp/lib/irredsol.gd",
+ "pkg/primgrp/read.g",
+ "pkg/primgrp/lib/primitiv.grp",
+ "pkg/primgrp/lib/primitiv.gi",
+ "pkg/primgrp/lib/irredsol.grp",
+ "pkg/primgrp/lib/irredsol.gi",
+ "pkg/primgrp/lib/cohorts.grp",
+ "pkg/smallgrp/init.g",
+ "pkg/smallgrp/gap/small.gd",
+ "pkg/smallgrp/read.g",
+ "pkg/smallgrp/gap/small.gi",
+ "pkg/smallgrp/gap/smlgp1.g",
+ "pkg/smallgrp/gap/idgrp1.g",
+ "pkg/smallgrp/gap/smlinfo.gi",
+ "pkg/smallgrp/small2/smlgp2.g.gz",
+ "pkg/smallgrp/small3/smlgp3.g.gz",
+ "pkg/smallgrp/small4/smlgp4.g.gz",
+ "pkg/smallgrp/small5/smlgp5.g.gz",
+ "pkg/smallgrp/small6/smlgp6.g.gz",
+ "pkg/smallgrp/small7/smlgp7.g.gz",
+ "pkg/smallgrp/small8/smlgp8.g.gz",
+ "pkg/smallgrp/small9/smlgp9.g.gz",
+ "pkg/smallgrp/small10/smlgp10.g.gz",
+ "pkg/smallgrp/small11/smlgp11.g.gz",
+ "pkg/smallgrp/id2/idgrp2.g.gz",
+ "pkg/smallgrp/id3/idgrp3.g.gz",
+ "pkg/smallgrp/id4/idgrp4.g.gz",
+ "pkg/smallgrp/id5/idgrp5.g.gz",
+ "pkg/smallgrp/id6/idgrp6.g.gz",
+ "pkg/smallgrp/id9/idgrp9.g.gz",
+ "pkg/smallgrp/id10/idgrp10.g.gz",
+ "pkg/transgrp/init.g",
+ "pkg/transgrp/lib/trans.gd",
+ "pkg/transgrp/read.g",
+ "pkg/transgrp/lib/trans.grp",
+ "pkg/transgrp/lib/trans.gi",
+ "pkg/autpgrp/init.g",
+ "pkg/autpgrp/gap/autos.gd",
+ "pkg/autpgrp/read.g",
+ "pkg/autpgrp/gap/general.gi",
+ "pkg/autpgrp/gap/autoops.gi",
+ "pkg/autpgrp/gap/matrix.gi",
+ "pkg/autpgrp/gap/nicestab.gi",
+ "pkg/autpgrp/gap/initmat.gi",
+ "pkg/autpgrp/gap/initperm.gi",
+ "pkg/autpgrp/gap/hybrstab.gi",
+ "pkg/autpgrp/gap/matrstab.gi",
+ "pkg/autpgrp/gap/orbstab.gi",
+ "pkg/autpgrp/gap/autos.gi",
+ "pkg/autpgrp/gap/pcpres.gi",
+ "pkg/autpgrp/gap/countcl.gi",
+ "pkg/alnuth/init.g",
+ "pkg/alnuth/gap/setup.gd",
+ "pkg/alnuth/gap/factors.gd",
+ "pkg/alnuth/gap/field.gd",
+ "pkg/alnuth/gap/kantin.gd",
+ "pkg/polycyclic/init.g",
+ "pkg/polycyclic/gap/matrix/matrix.gd",
+ "pkg/polycyclic/gap/basic/infos.gd",
+ "pkg/polycyclic/gap/basic/collect.gd",
+ "pkg/polycyclic/gap/basic/pcpelms.gd",
+ "pkg/polycyclic/gap/basic/pcpgrps.gd",
+ "pkg/polycyclic/gap/basic/pcppcps.gd",
+ "pkg/polycyclic/gap/basic/grphoms.gd",
+ "pkg/polycyclic/gap/basic/basic.gd",
+ "pkg/polycyclic/gap/cohom/cohom.gd",
+ "pkg/polycyclic/gap/matrep/matrep.gd",
+ "pkg/polycyclic/gap/matrep/unitri.gd",
+ "pkg/polycyclic/gap/pcpgrp/pcpgrp.gd",
+ "pkg/polycyclic/gap/pcpgrp/torsion.gd",
+ "pkg/polycyclic/gap/exam/exam.gd",
+ "pkg/polycyclic/gap/obsolete.gd",
+ "pkg/alnuth/read.g",
+ "pkg/alnuth/defs.g",
+ "pkg/alnuth/gap/setup.gi",
+ "pkg/alnuth/gap/factors.gi",
+ "pkg/alnuth/gap/kantin.gi",
+ "pkg/alnuth/gap/matfield.gi",
+ "pkg/alnuth/gap/polfield.gi",
+ "pkg/alnuth/gap/field.gi",
+ "pkg/alnuth/gap/unithom.gi",
+ "pkg/alnuth/gap/matunits.gi",
+ "pkg/alnuth/gap/rels.gi",
+ "pkg/alnuth/gap/present.gi",
+ "pkg/alnuth/gap/isom.gi",
+ "pkg/alnuth/gap/rationals.gi",
+ "pkg/alnuth/exam/unimod.gi",
+ "pkg/alnuth/exam/rationals.gi",
+ "pkg/alnuth/exam/fields.gi",
+ "pkg/polycyclic/read.g",
+ "pkg/polycyclic/gap/matrix/rowbases.gi",
+ "pkg/polycyclic/gap/matrix/latbases.gi",
+ "pkg/polycyclic/gap/matrix/lattices.gi",
+ "pkg/polycyclic/gap/matrix/modules.gi",
+ "pkg/polycyclic/gap/matrix/triangle.gi",
+ "pkg/polycyclic/gap/matrix/hnf.gi",
+ "pkg/polycyclic/gap/basic/collect.gi",
+ "pkg/polycyclic/gap/basic/colftl.gi",
+ "pkg/polycyclic/gap/basic/colcom.gi",
+ "pkg/polycyclic/gap/basic/coldt.gi",
+ "pkg/polycyclic/gap/basic/colsave.gi",
+ "pkg/polycyclic/gap/basic/pcpelms.gi",
+ "pkg/polycyclic/gap/basic/pcppcps.gi",
+ "pkg/polycyclic/gap/basic/pcpgrps.gi",
+ "pkg/polycyclic/gap/basic/pcppara.gi",
+ "pkg/polycyclic/gap/basic/pcpexpo.gi",
+ "pkg/polycyclic/gap/basic/pcpsers.gi",
+ "pkg/polycyclic/gap/basic/grphoms.gi",
+ "pkg/polycyclic/gap/basic/pcpfact.gi",
+ "pkg/polycyclic/gap/basic/chngpcp.gi",
+ "pkg/polycyclic/gap/basic/convert.gi",
+ "pkg/polycyclic/gap/basic/orbstab.gi",
+ "pkg/polycyclic/gap/basic/construct.gi",
+ "pkg/polycyclic/gap/cohom/cohom.gi",
+ "pkg/polycyclic/gap/cohom/addgrp.gi",
+ "pkg/polycyclic/gap/cohom/general.gi",
+ "pkg/polycyclic/gap/cohom/solabel.gi",
+ "pkg/polycyclic/gap/cohom/solcohom.gi",
+ "pkg/polycyclic/gap/cohom/twocohom.gi",
+ "pkg/polycyclic/gap/cohom/intcohom.gi",
+ "pkg/polycyclic/gap/cohom/onecohom.gi",
+ "pkg/polycyclic/gap/cohom/grpext.gi",
+ "pkg/polycyclic/gap/cohom/grpcom.gi",
+ "pkg/polycyclic/gap/cohom/norcom.gi",
+ "pkg/polycyclic/gap/action/extend.gi",
+ "pkg/polycyclic/gap/action/basepcgs.gi",
+ "pkg/polycyclic/gap/action/freegens.gi",
+ "pkg/polycyclic/gap/action/dixon.gi",
+ "pkg/polycyclic/gap/action/kernels.gi",
+ "pkg/polycyclic/gap/action/orbstab.gi",
+ "pkg/polycyclic/gap/action/orbnorm.gi",
+ "pkg/polycyclic/gap/pcpgrp/general.gi",
+ "pkg/polycyclic/gap/pcpgrp/inters.gi",
+ "pkg/polycyclic/gap/pcpgrp/grpinva.gi",
+ "pkg/polycyclic/gap/pcpgrp/torsion.gi",
+ "pkg/polycyclic/gap/pcpgrp/maxsub.gi",
+ "pkg/polycyclic/gap/pcpgrp/findex.gi",
+ "pkg/polycyclic/gap/pcpgrp/nindex.gi",
+ "pkg/polycyclic/gap/pcpgrp/nilpot.gi",
+ "pkg/polycyclic/gap/pcpgrp/polyz.gi",
+ "pkg/polycyclic/gap/pcpgrp/pcpattr.gi",
+ "pkg/polycyclic/gap/pcpgrp/wreath.gi",
+ "pkg/polycyclic/gap/pcpgrp/fitting.gi",
+ "pkg/polycyclic/gap/pcpgrp/centcon.gi",
+ "pkg/polycyclic/gap/pcpgrp/normcon.gi",
+ "pkg/polycyclic/gap/pcpgrp/schur.gi",
+ "pkg/polycyclic/gap/pcpgrp/tensor.gi",
+ "pkg/polycyclic/gap/matrep/matrep.gi",
+ "pkg/polycyclic/gap/matrep/affine.gi",
+ "pkg/polycyclic/gap/matrep/unitri.gi",
+ "pkg/polycyclic/gap/exam/pcplib.gi",
+ "pkg/polycyclic/gap/exam/matlib.gi",
+ "pkg/polycyclic/gap/exam/nqlib.gi",
+ "pkg/polycyclic/gap/exam/generic.gi",
+ "pkg/polycyclic/gap/exam/bgnilp.gi",
+ "pkg/polycyclic/gap/exam/metacyc.gi",
+ "pkg/polycyclic/gap/exam/metagrp.gi",
+ "pkg/polycyclic/gap/cover/const/bas.gi",
+ "pkg/polycyclic/gap/cover/const/orb.gi",
+ "pkg/polycyclic/gap/cover/const/aut.gi",
+ "pkg/polycyclic/gap/cover/const/com.gi",
+ "pkg/polycyclic/gap/cover/const/cov.gi",
+ "pkg/crisp/init.g",
+ "pkg/crisp/lib/classes.gd",
+ "pkg/crisp/lib/grpclass.gd",
+ "pkg/crisp/lib/fitting.gd",
+ "pkg/crisp/lib/schunck.gd",
+ "pkg/crisp/lib/form.gd",
+ "pkg/crisp/lib/projector.gd",
+ "pkg/crisp/lib/injector.gd",
+ "pkg/crisp/lib/normpro.gd",
+ "pkg/crisp/lib/solveeq.gd",
+ "pkg/crisp/lib/compl.gd",
+ "pkg/crisp/lib/radical.gd",
+ "pkg/crisp/lib/residual.gd",
+ "pkg/crisp/lib/util.gd",
+ "pkg/crisp/lib/samples.gd",
+ "pkg/crisp/lib/socle.gd",
+ "pkg/crisp/read.g",
+ "pkg/crisp/lib/classes.gi",
+ "pkg/crisp/lib/grpclass.gi",
+ "pkg/crisp/lib/fitting.gi",
+ "pkg/crisp/lib/schunck.gi",
+ "pkg/crisp/lib/form.gi",
+ "pkg/crisp/lib/projector.gi",
+ "pkg/crisp/lib/injector.gi",
+ "pkg/crisp/lib/normpro.gi",
+ "pkg/crisp/lib/solveeq.gi",
+ "pkg/crisp/lib/compl.gi",
+ "pkg/crisp/lib/radical.gi",
+ "pkg/crisp/lib/residual.gi",
+ "pkg/crisp/lib/util.gi",
+ "pkg/crisp/lib/samples.gi",
+ "pkg/crisp/lib/socle.gi",
+ "pkg/factint/init.g",
+ "pkg/factint/lib/factint.gd",
+ "pkg/factint/read.g",
+ "pkg/factint/tables/3k2k.g",
+ "pkg/factint/tables/akbk.g",
+ "pkg/factint/tables/factorial.g",
+ "pkg/factint/tables/fibo.g",
+ "pkg/factint/tables/primorial.g",
+ "pkg/factint/lib/general.gi",
+ "pkg/factint/lib/pminus1.gi",
+ "pkg/factint/lib/pplus1.gi",
+ "pkg/factint/lib/ecm.gi",
+ "pkg/factint/lib/cfrac.gi",
+ "pkg/factint/lib/mpqs.gi",
+ "pkg/fga/init.g",
+ "pkg/fga/lib/util.gd",
+ "pkg/fga/lib/Iterated.gd",
+ "pkg/fga/lib/Autom.gd",
+ "pkg/fga/lib/FreeGrps.gd",
+ "pkg/fga/lib/ReprAct.gd",
+ "pkg/fga/lib/Normal.gd",
+ "pkg/fga/lib/ExtAutom.gd",
+ "pkg/fga/lib/Hom.gd",
+ "pkg/fga/lib/AutGrp.gd",
+ "pkg/fga/lib/Intsect.gd",
+ "pkg/fga/lib/Whitehd.gd",
+ "pkg/fga/read.g",
+ "pkg/fga/lib/util.gi",
+ "pkg/fga/lib/Iterated.gi",
+ "pkg/fga/lib/Autom.gi",
+ "pkg/fga/lib/FreeGrps.gi",
+ "pkg/fga/lib/ReprAct.gi",
+ "pkg/fga/lib/Normal.gi",
+ "pkg/fga/lib/Central.gi",
+ "pkg/fga/lib/Index.gi",
+ "pkg/fga/lib/ExtAutom.gi",
+ "pkg/fga/lib/Hom.gi",
+ "pkg/fga/lib/AutGrp.gi",
+ "pkg/fga/lib/Intsect.gi",
+ "pkg/fga/lib/ReprActT.gi",
+ "pkg/fga/lib/Whitehd.gi",
+ "pkg/irredsol/init.g",
+ "pkg/irredsol/lib/util.g",
+ "pkg/irredsol/lib/util.gd",
+ "pkg/irredsol/lib/matmeths.gd",
+ "pkg/irredsol/lib/loading.gd",
+ "pkg/irredsol/lib/loadfp.gd",
+ "pkg/irredsol/lib/access.gd",
+ "pkg/irredsol/lib/iterators.gd",
+ "pkg/irredsol/lib/recognize.gd",
+ "pkg/irredsol/lib/primitive.gd",
+ "pkg/irredsol/lib/recognizeprim.gd",
+ "pkg/irredsol/lib/obsolete.gd",
+ "pkg/irredsol/read.g",
+ "pkg/irredsol/lib/matmeths.gi",
+ "pkg/irredsol/lib/loading.gi",
+ "pkg/irredsol/lib/loadfp.gi",
+ "pkg/irredsol/lib/access.gi",
+ "pkg/irredsol/lib/iterators.gi",
+ "pkg/irredsol/lib/recognize.gi",
+ "pkg/irredsol/lib/primitive.gi",
+ "pkg/irredsol/lib/recognizeprim.gi",
+ "pkg/irredsol/lib/util.gi",
+ "pkg/irredsol/lib/obsolete.gi",
+ "pkg/sophus/init.g",
+ "pkg/sophus/gap/sophus.gd",
+ "pkg/sophus/read.g",
+ "pkg/sophus/gap/general.gi",
+ "pkg/sophus/gap/lienp.gi",
+ "pkg/sophus/gap/liesct.gi",
+ "pkg/sophus/gap/liecover.gi",
+ "pkg/sophus/gap/nicestab.gi",
+ "pkg/sophus/gap/lieautoops.gi",
+ "pkg/sophus/gap/initauts.gi",
+ "pkg/sophus/gap/lieautgrp.gi",
+ "pkg/sophus/gap/allowable.gi",
+ "pkg/sophus/gap/descendant.gi",
+ "pkg/sophus/gap/lieisom.gi",
+ "pkg/sophus/gap/io.gi",
+ "pkg/laguna/init.g",
+ "pkg/laguna/lib/laguna.gd",
+ "pkg/laguna/lib/laguna.g",
+ "pkg/laguna/read.g",
+ "pkg/laguna/lib/laguna.gi",
+ "pkg/radiroot/init.g",
+ "pkg/radiroot/lib/Radicals.gd",
+ "pkg/radiroot/lib/SplittField.gd",
+ "pkg/radiroot/lib/Manipulations.gd",
+ "pkg/radiroot/lib/Strings.gd",
+ "pkg/radiroot/lib/Maple.gd",
+ "pkg/radiroot/read.g",
+ "pkg/radiroot/lib/Radicals.gi",
+ "pkg/radiroot/lib/SplittField.gi",
+ "pkg/radiroot/lib/Manipulations.gi",
+ "pkg/radiroot/lib/Strings.gi",
+ "pkg/radiroot/lib/Maple.gi",
+ "pkg/aclib/init.g",
+ "pkg/aclib/gap/groups.gd",
+ "pkg/cryst/init.g",
+ "pkg/cryst/gap/common.gd",
+ "pkg/cryst/gap/cryst.gd",
+ "pkg/cryst/gap/hom.gd",
+ "pkg/cryst/gap/wyckoff.gd",
+ "pkg/cryst/gap/zass.gd",
+ "pkg/cryst/gap/max.gd",
+ "pkg/cryst/gap/color.gd",
+ "pkg/cryst/gap/equiv.gd",
+ "pkg/cryst/grp/spacegrp.gd",
+ "pkg/crystcat/init.g",
+ "pkg/crystcat/lib/crystcat.gd",
+ "pkg/polenta/init.g",
+ "pkg/polenta/lib/finite.gd",
+ "pkg/polenta/lib/info.gd",
+ "pkg/polenta/lib/basic.gd",
+ "pkg/polenta/exam/test.gd",
+ "pkg/polenta/lib/cpcs.gd",
+ "pkg/polenta/lib/present.gd",
+ "pkg/polenta/lib/solvable.gd",
+ "pkg/polenta/lib/series.gd",
+ "pkg/polenta/lib/subgroups.gd",
+ "pkg/polenta/lib/ispolyz.gd",
+ "pkg/aclib/read.g",
+ "pkg/aclib/gap/matgrp3.gi",
+ "pkg/aclib/gap/matgrp4.gi",
+ "pkg/aclib/gap/matgrp.gi",
+ "pkg/aclib/gap/pcpgrp3.gi",
+ "pkg/aclib/gap/pcpgrp4.gi",
+ "pkg/aclib/gap/pcpgrp.gi",
+ "pkg/aclib/gap/betti.gi",
+ "pkg/aclib/gap/union.gi",
+ "pkg/aclib/gap/extend.gi",
+ "pkg/aclib/gap/crystgrp.gi",
+ "pkg/cryst/read.g",
+ "pkg/cryst/gap/common.gi",
+ "pkg/cryst/gap/hom.gi",
+ "pkg/cryst/gap/cryst.gi",
+ "pkg/cryst/gap/cryst2.gi",
+ "pkg/cryst/gap/fpgrp.gi",
+ "pkg/cryst/gap/zass.gi",
+ "pkg/cryst/gap/max.gi",
+ "pkg/cryst/gap/wyckoff.gi",
+ "pkg/cryst/gap/color.gi",
+ "pkg/cryst/gap/noxgap.gi",
+ "pkg/cryst/gap/pcpgrp.gi",
+ "pkg/cryst/gap/orbstab.gi",
+ "pkg/cryst/gap/equiv.gi",
+ "pkg/cryst/grp/spacegrp.grp",
+ "pkg/cryst/grp/spacegrp.gi",
+ "pkg/crystcat/read.g",
+ "pkg/crystcat/grp/crystcat.grp",
+ "pkg/crystcat/lib/crystcat.gi",
+ "pkg/crystcat/lib/normalizer.gi",
+ "pkg/polenta/read.g",
+ "pkg/polenta/lib/finite.gi",
+ "pkg/polenta/lib/basic.gi",
+ "pkg/polenta/lib/unipo.gi",
+ "pkg/polenta/lib/series.gi",
+ "pkg/polenta/lib/semi.gi",
+ "pkg/polenta/lib/ispoly.gi",
+ "pkg/polenta/lib/cpcs.gi",
+ "pkg/polenta/lib/present.gi",
+ "pkg/polenta/lib/isom.gi",
+ "pkg/polenta/lib/solvable.gi",
+ "pkg/polenta/exam/exam.gi",
+ "pkg/polenta/exam/test.gi",
+ "pkg/polenta/lib/subgroups.gi",
+ "pkg/polenta/lib/ispolyz.gi"
+]
\ No newline at end of file
diff --git a/etc/emscripten/web-template/gap-fs.js b/etc/emscripten/web-template/gap-fs.js
index f3a1467d42..ca8ea4f23d 100644
--- a/etc/emscripten/web-template/gap-fs.js
+++ b/etc/emscripten/web-template/gap-fs.js
@@ -1,3 +1,29 @@
+// Instrument fetch and XMLHttpRequest so the page can collect the list of
+// URLs the worker actually requests, for rebuilding startup_manifest.json
+// without scraping the browser network panel. Each unique URL is posted
+// to the main thread as { type: "gap-fetched", url: ... }.
+(function instrumentFetches() {
+ const seen = new Set();
+ const report = (raw) => {
+ if (typeof raw !== "string") return;
+ if (seen.has(raw)) return;
+ seen.add(raw);
+ self.postMessage({ type: "gap-fetched", url: raw });
+ };
+
+ const origFetch = self.fetch;
+ self.fetch = function(input, init) {
+ report(typeof input === "string" ? input : input && input.url);
+ return origFetch.apply(this, arguments);
+ };
+
+ const origOpen = XMLHttpRequest.prototype.open;
+ XMLHttpRequest.prototype.open = function(method, url) {
+ report(url);
+ return origOpen.apply(this, arguments);
+ };
+})();
+
self.Module = self.Module || {};
self.Module.preRun = self.Module.preRun || [];
@@ -43,8 +69,12 @@ self.Module.preRun.push(function() {
if (p.startsWith('/')) p = p.substring(1);
startupSet.add(p);
});
+ } else {
+ console.info("startup_manifest.json not present; falling back to fully lazy loading");
}
- } catch (e) {}
+ } catch (e) {
+ console.warn("Failed to load startup_manifest.json:", e);
+ }
var fetchPromises = fileList.map(async function(appPath) {
var fetchRelativePath = appPath.split('/').map(encodeURIComponent).join('/');
@@ -58,16 +88,15 @@ self.Module.preRun.push(function() {
FS.stat(idbfsPath);
FS.writeFile(finalAppPath, FS.readFile(idbfsPath));
} catch (e) {
- try {
- const response = await fetch(fetchPath);
- if (response.ok) {
- const buffer = await response.arrayBuffer();
- const data = new Uint8Array(buffer);
- FS.writeFile(finalAppPath, data);
- FS.writeFile(idbfsPath, data);
- needsSave = true;
- }
- } catch (fetchErr) {}
+ const response = await fetch(fetchPath);
+ if (!response.ok) {
+ throw new Error("Failed to fetch startup file " + fetchPath + ": " + response.status);
+ }
+ const buffer = await response.arrayBuffer();
+ const data = new Uint8Array(buffer);
+ FS.writeFile(finalAppPath, data);
+ FS.writeFile(idbfsPath, data);
+ needsSave = true;
}
} else {
var parts = appPath.split('/');
diff --git a/etc/emscripten/web-template/index.html b/etc/emscripten/web-template/index.html
index b51df45fb8..0439b28ef9 100755
--- a/etc/emscripten/web-template/index.html
+++ b/etc/emscripten/web-template/index.html
@@ -1,22 +1,185 @@
-
+
+
- gap-wasm
+
+
+ GAP in the browser
+
-
+
+
GAP in the browser
+
+ A WebAssembly build of GAP
+ running entirely in your browser — useful for trying things out
+ without installing. Not all packages work, working memory is
+ capped, and performance is reduced compared to a native build.
+ For real work, install GAP from
+ www.gap-system.org.
+
+
+
+
+ What is this? · Licensing
+
+ GAP is a system for
+ computational discrete algebra, with particular emphasis on
+ computational group theory. This page is a self-contained
+ WebAssembly build, served as a static site and run inside a
+ Web Worker; you interact with it through an
+ xterm-pty
+ terminal in the page below.
+
+
+ To get started, try 1+1;,
+ Factorial(20);, or SymmetricGroup(5);.
+ The full GAP
+ manuals apply, with two caveats: some packages won't load
+ (anything that needs a native compiler or system library), and
+ anything that wants the local filesystem won't work.
+
+
+ Files downloaded on first visit are cached in your browser's
+ IndexedDB, so subsequent visits start much faster. Clear the
+ site data to reset the cache.
+
+ Loading GAP… The first visit downloads several tens of megabytes;
+ subsequent visits are cached in your browser and load much faster.
+
+
+
+ This page needs SharedArrayBuffer, which is only
+ available when the page is served with the headers
+ Cross-Origin-Opener-Policy: same-origin and
+ Cross-Origin-Embedder-Policy: require-corp. The
+ bundled service worker handles this on hosts where you can't set
+ headers (e.g. GitHub Pages); for local hosting use
+ etc/emscripten/serve.py.
+