From ff18398264349d97844abfe229f9834c273165dd Mon Sep 17 00:00:00 2001 From: Christopher Jefferson Date: Sat, 9 May 2026 22:34:40 +0800 Subject: [PATCH] emscripten: add Docker-based one-stop build and polish demo page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reworks the emscripten build to be reproducible inside a pinned emsdk:3.1.23 container, replacing the previous mix of shell, Ruby, and Node helpers. New entry point: etc/emscripten/build-in-docker.sh builds the image, runs the wasm build inside it, and assembles a self-contained web-example/ directory copyable to any static host. Build pipeline: - Dockerfile pins emsdk 3.1.23 (newer versions break GASMAN's ASYNCIFY interaction), installs autotools/python3/bison/byacc/m4, and bakes the GAP package distribution tarball into the image so repeated runs don't re-download it. - build.sh: cleaned up (dead error-check, $(nproc), pipefail); wipes stale build/ before reconfiguring under emcc to avoid cross-arch contamination from prior native in-tree builds; uses find -L so users' pkg/X -> git/X dev symlinks are picked up. - build-in-docker.sh: pins --platform linux/amd64 on both build and run so the layer cache survives on Apple Silicon hosts. - assemble-website.sh: replaces run-web-demo.sh; wipes the output dir and uses cp -RL to dereference symlinks for portability. - serve.py: replaces server.rb (drops the Ruby dep); stdlib-only Python server with COOP/COEP headers. Demo page (web-template/index.html): - Header with title, disclaimer, and gap-system.org links. - Collapsible "What is this? · Licensing" disclosure with brief intro, getting-started commands, IndexedDB caching note, and links to the bundled LICENSE and COPYRIGHT. - Loading notice that auto-hides on the first xterm render. - SharedArrayBuffer detection with a clear error pointing at COOP/COEP if the page is served without the right headers. - Footer with main-site/docs/source links. - Terminal at 85x40 (wider to dodge an apparent off-by-one). Manifest workflow: - gap-fs.js wraps fetch and XMLHttpRequest.open and reports each unique URL to the main thread; the page exposes window.fetchedUrls for devtools-driven manifest regeneration. Replaces the previous Node tracker server (build_startup_manifest.js). - startup_manifest.json captured from a real GAP run and committed. Removes: build_startup_manifest.js, run-web-demo.sh, server.rb. .gitignore: adds web-example/, native-build/, packages.tar.gz, the top-level wasm artefacts, and /extern/emscripten/. --- .gitignore | 11 + etc/emscripten/Dockerfile | 36 ++ etc/emscripten/README.md | 122 ++++- etc/emscripten/assemble-website.sh | 46 ++ etc/emscripten/build-in-docker.sh | 80 +++ etc/emscripten/build.sh | 140 ++--- etc/emscripten/build_startup_manifest.js | 71 --- etc/emscripten/generate_gap_fs_json.py | 25 +- etc/emscripten/run-web-demo.sh | 13 - etc/emscripten/serve.py | 35 ++ etc/emscripten/server.rb | 15 - etc/emscripten/startup_manifest.json | 617 +++++++++++++++++++++++ etc/emscripten/web-template/gap-fs.js | 51 +- etc/emscripten/web-template/index.html | 181 ++++++- 14 files changed, 1236 insertions(+), 207 deletions(-) create mode 100644 etc/emscripten/Dockerfile create mode 100755 etc/emscripten/assemble-website.sh create mode 100755 etc/emscripten/build-in-docker.sh delete mode 100755 etc/emscripten/build_startup_manifest.js delete mode 100755 etc/emscripten/run-web-demo.sh create mode 100755 etc/emscripten/serve.py delete mode 100755 etc/emscripten/server.rb create mode 100644 etc/emscripten/startup_manifest.json 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. +

+

+ GAP is free software, distributed under the + GNU General Public License (version 2 or + later). See the copyright notice for + the full statement. +

+
+ +
+ 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. +
+ +
+
+
+ + +