Add TCL-embedded WASM build variant alongside the existing non-TCL build

Bump VERSION to 8.3.645.

magic.wasm can now be built as two variants packaged in the same npm
release: notcl/ (legacy, magic's own parser) and tcl/ (intubun/tcl 9.x
statically linked, commands evaluated by Tcl_EvalEx). The TCL fork is
pinned via npm/tcl.ref and cloned/built by magic itself — the tcl/
checkout is treated as read-only and built out-of-source into
magic/build-tcl-wasm/.

Configure layer:
- New usingTk variable decoupled from usingTcl in scripts/configure.in
  + scripts/configure, so --with-tcl --without-tk is finally a valid
  combination. Native Linux Tcl+Tk builds keep their previous behaviour
  (both flags default to enabled).
- When usingTk is empty, configure passes -DMAGIC_NO_TK so the small
  number of remaining Tk callsites in tcltk/tclmagic.{h,c} compile out,
  and TKCOMMON_SRCS / USE_TK_STUBS are omitted from the link.

WASM build orchestration:
- toolchains/emscripten/build-tcl-wasm.sh builds libtcl9.x.a + libtclstub.a
  + tclConfig.sh out-of-source from a pristine intubun/tcl checkout.
- npm/build.sh grew a --variant=<tcl|notcl|both> flag and writes its
  outputs into npm/tcl/ and npm/notcl/. It also clones intubun/tcl with
  autocrlf=false at the SHA pinned by npm/tcl.ref.
- magic/Makefile (WASM block only): magicWasm.o is now compiled with
  DFLAGS_NOSTUB so Tcl_CreateInterp resolves to libtcl9.x directly
  before tclStubsPtr is set. magic.js link pulls in LIB_SPECS_NOSTUB
  and -ltclstub. After rules.mak include, magic: is a phony alias for
  magic.js so the generic ${MODULE} recipe doesn't fight it.
- toolchains/emscripten/defs.mak: add -sUSE_ZLIB=1 (libtcl9 references
  zlib), replace -sSTACK_SIZE=N with -Wl,-z,stack-size=N (emcc >=5
  rejects the setting form).
- magic/magicWasm.c bootstraps the embedded interp under MAGIC_WRAPPER
  (Tcl_CreateInterp -> Tcl_Init -> Tclmagic_Init) and routes
  run_command through Tcl_EvalEx.
- magic/magicTop.c: gate MagicVersion/Revision/CompileTime on
  !MAGIC_WRAPPER so they don't collide with the copies in
  tcltk/tclmagic.c when both objects land in the same wasm binary.

npm package:
- Subpath exports: ".", "./tcl", "./notcl". Default import keeps the
  pre-existing non-TCL behaviour for backward compatibility.
- examples/smoke-tcl.mjs exercises the TCL variant.

CI:
- main-wasm.yml clones intubun/tcl at the pinned ref, builds both
  variants via npm/build.sh --variant=both, runs the existing notcl
  test suite and the new TCL smoke test, and publishes only on a
  v<x.y.z>... git tag. Tag name (minus the leading v) becomes the
  npm version.
This commit is contained in:
Enno Schnackenberg 2026-05-17 21:41:03 +02:00 committed by R. Timothy Edwards
parent 8d61bae1f1
commit e45db485d8
21 changed files with 708 additions and 208 deletions

View File

@ -1,13 +1,16 @@
name: CI-wasm name: CI-wasm
# Builds the Magic WebAssembly target on every push and pull request. # Builds the Magic WebAssembly target (both the non-TCL and TCL variants)
# When the VERSION file changes on the default branch, the package is # on every push and pull request as a CI check. **Publishing** only happens
# additionally published to GitHub Packages (npm.pkg.github.com) as # when a release tag of the form v<x.y.z>... is pushed — that gate is the
# @<owner>/magic-vlsi-wasm — no manual tag or token required. # manual release trigger:
# Tim Edwards updates VERSION to trigger a new release; the scope resolves
# automatically to the repo owner, so forks publish under their own namespace.
# #
# WASM is architecture-independent — built once on x86-64, usable everywhere. # # bump magic/VERSION and/or npm/tcl.ref, commit, push to default branch
# git tag v8.3.638
# git push origin v8.3.638
#
# The tag name (minus the leading "v") becomes the published npm version.
# Forks publish under their own namespace via the @<owner>/ scope.
on: on:
push: push:
@ -70,32 +73,32 @@ jobs:
echo "===== emcc -dM -E - ====="; echo | emcc -dM -E - | sort echo "===== emcc -dM -E - ====="; echo | emcc -dM -E - | sort
echo "===== em++ -dM -E - ====="; echo | em++ -dM -E - | sort echo "===== em++ -dM -E - ====="; echo | em++ -dM -E - | sort
- name: Build WASM # Clone intubun/tcl into a sibling directory at the pinned ref from
# npm/tcl.ref. npm/build.sh would do this on its own, but doing it as
# an explicit step makes the resolved SHA visible at the top of the
# job log and keeps the build step's output focused on the C build.
# The TCL source tree is treated as read-only — the actual WASM build
# runs inside magic (toolchains/emscripten/build-tcl-wasm.sh).
- name: Pin and clone intubun/tcl
run: |
. npm/tcl.ref
: "${TCL_REPO_URL:=https://github.com/intubun/tcl.git}"
: "${TCL_REF:=main}"
echo "Pinned TCL: $TCL_REF ($TCL_REPO_URL)"
# autocrlf=false: ubuntu-latest is already LF, but make it explicit.
git -c core.autocrlf=false clone "$TCL_REPO_URL" ../tcl
( cd ../tcl && git checkout --detach "$TCL_REF" )
- name: Build WASM — both variants (tcl + notcl)
run: | run: |
source ./emsdk/emsdk_env.sh source ./emsdk/emsdk_env.sh
# --without/--disable flags: no WASM library available for these features bash npm/build.sh --variant=both
CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1" emconfigure ./configure \
--without-cairo --without-opengl --without-x --without-tk --without-tcl \
--disable-readline --disable-compression \
--host=asmjs-unknown-emscripten \
--target=asmjs-unknown-emscripten
# Append WASM linker flags and activate the WASM link target
cat toolchains/emscripten/defs.mak >> defs.mak
# Echo the merged defs.mak so CI logs show the exact build config
echo "===== defs.mak ====="; cat defs.mak; echo "===== defs.mak ====="
# Build in order: techs must exist before mains (--embed-file embeds them)
emmake make depend
emmake make -j$(nproc) modules libs
emmake make techs
emmake make mains
- name: Copy WASM artifacts into npm/ - name: Run example tests (non-TCL variant)
run: | run: cd npm && npm test
cp magic/magic.js npm/
cp magic/magic.wasm npm/
- name: Run example tests - name: Run smoke test (TCL variant)
run: cd npm && npm run test run: cd npm && npm run test:tcl
# Dump generated text outputs (.ext, .spice, .cif, …) into the CI log # Dump generated text outputs (.ext, .spice, .cif, …) into the CI log
# so a regression in extraction / netlisting / cifoutput is visible # so a regression in extraction / netlisting / cifoutput is visible
@ -112,21 +115,40 @@ jobs:
esac esac
done done
- name: Set package version and scope # The release gate. We publish a new npm version only when a tag of the
# shape v<x.y.z>... is pushed. The tag name (minus the leading "v") is
# taken as the npm version verbatim — so `v8.3.638` → npm 8.3.638.
- name: Determine release version (tag-driven only)
id: release
run: |
if [ "${{ github.event_name }}" = "push" ] && \
echo "${{ github.ref }}" | grep -Eq '^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+'; then
tag="${GITHUB_REF#refs/tags/}"
echo "publish=true" >> "$GITHUB_OUTPUT"
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
echo "Tag release: $tag → npm version ${tag#v}"
else
# For non-tag CI runs, use a dev-suffixed version so the packed
# tarball is still consumable for local inspection / artifact upload.
base=$(cat VERSION)
date=$(git show -s --format=%cs | tr -d '-')
hash=$(git show -s --format=%h)
echo "publish=false" >> "$GITHUB_OUTPUT"
echo "version=${base}-${date}.${hash}" >> "$GITHUB_OUTPUT"
echo "Non-tag build: will not publish."
fi
- name: Set package version and scope
env:
VERSION: ${{ steps.release.outputs.version }}
run: | run: |
base=$(cat VERSION) # e.g. 8.3.637
date=$(git show -s --format=%cs | tr -d '-') # e.g. 20260414
hash=$(git show -s --format=%h) # e.g. d157eea
VERSION="${base}-${date}.${hash}"
# Scope the package to the repo owner so it lands in the right # Scope the package to the repo owner so it lands in the right
# GitHub Packages namespace regardless of who hosts the repo. # GitHub Packages namespace regardless of who hosts the repo.
# e.g. @rtimothyedwards/magic-vlsi-wasm on Tim's repo,
# @intubun/magic-vlsi-wasm on a fork.
SCOPED_NAME="@${{ github.repository_owner }}/magic-vlsi-wasm" SCOPED_NAME="@${{ github.repository_owner }}/magic-vlsi-wasm"
cd npm cd npm
npm pkg set name="$SCOPED_NAME" npm pkg set name="$SCOPED_NAME"
npm pkg set publishConfig.registry="https://npm.pkg.github.com" npm pkg set publishConfig.registry="https://npm.pkg.github.com"
npm version "$VERSION" --no-git-tag-version npm version "$VERSION" --no-git-tag-version --allow-same-version
- name: Pack - name: Pack
run: ./npm/pack.sh run: ./npm/pack.sh
@ -137,24 +159,8 @@ jobs:
name: magic-vlsi-wasm-npm name: magic-vlsi-wasm-npm
path: npm/*.tgz path: npm/*.tgz
- name: Check if VERSION changed
id: version_changed
if: github.event_name == 'push'
run: |
if echo "${{ github.ref }}" | grep -q '^refs/tags/'; then
echo "changed=true" >> $GITHUB_OUTPUT
elif [ "${{ github.ref }}" = "refs/heads/${{ github.event.repository.default_branch }}" ]; then
if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '^VERSION$'; then
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "changed=false" >> $GITHUB_OUTPUT
fi
else
echo "changed=false" >> $GITHUB_OUTPUT
fi
- name: Publish to GitHub Packages - name: Publish to GitHub Packages
if: steps.version_changed.outputs.changed == 'true' && github.event.inputs.dry_run != 'true' if: steps.release.outputs.publish == 'true' && github.event.inputs.dry_run != 'true'
run: cd npm && npm publish run: cd npm && npm publish
env: env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@ -54,6 +54,7 @@ magic/magic.js
magic/magic.js.symbols magic/magic.js.symbols
magic/magic.symbols magic/magic.symbols
magic/magic.wasm magic/magic.wasm
build-tcl-wasm/
net2ir/net2ir net2ir/net2ir
net2ir/net2ir.js net2ir/net2ir.js
net2ir/net2ir.wasm net2ir/net2ir.wasm

View File

@ -42,11 +42,25 @@ LIBS += ${GR_LIBS} ${READLINE_LIBS} -lm ${LD_EXTRA_LIBS} \
CLEANS += tclmagic${SHDLIB_EXT} libtclmagic${SHDLIB_EXT}.a proto.magicrc CLEANS += tclmagic${SHDLIB_EXT} libtclmagic${SHDLIB_EXT}.a proto.magicrc
ifeq (${MAKE_WASM},1) ifeq (${MAKE_WASM},1)
magic: magic.js # magicWasm.c bootstraps the embedded Tcl interp by calling Tcl_CreateInterp /
# Tcl_Init before tclStubsPtr is initialised. With -DUSE_TCL_STUBS those calls
# expand to (*tclStubsPtr->...)() and dereference a NULL stubs pointer; so
# this one file must be compiled with DFLAGS_NOSTUB (= DFLAGS without
# -DUSE_TCL_STUBS). All other files keep using stubs.
magicWasm.o: magicWasm.c
@echo --- compiling magic/magicWasm.o '(no Tcl stubs)'
${RM} magicWasm.o
${CC} ${CFLAGS} ${CPPFLAGS} ${DFLAGS_NOSTUB} -c magicWasm.c
# Pull in BOTH the main TCL archive (LIB_SPECS_NOSTUB → -ltcl9.x) and the
# stub bootstrap archive (-L${TCL_LIB_DIR} -ltclstub). Magic's source uses
# USE_TCL_STUBS macros, so tclStubsPtr (defined in libtclstub.a) and
# Tcl_InitStubs must be present in the same binary as the actual TCL
# implementation from libtcl9.x.a.
magic.js: lib${MODULE}.o ${EXTRA_LIBS} magic.js: lib${MODULE}.o ${EXTRA_LIBS}
@echo --- building main magic WASM @echo --- building main magic WASM
${RM} magic.js magic.wasm ${RM} magic.js magic.wasm
${CC} ${CFLAGS} ${CPPFLAGS} ${DFLAGS} lib${MODULE}.o ${EXTRA_LIBS} -o magic.js ${LIBS} ${CC} ${CFLAGS} ${CPPFLAGS} ${DFLAGS} lib${MODULE}.o ${EXTRA_LIBS} -o magic.js ${LIBS} ${LIB_SPECS_NOSTUB} -L${TCL_LIB_DIR} -ltclstub
endif endif
main: magic proto.magicrc main: magic proto.magicrc
@ -93,3 +107,14 @@ $(DESTDIR)${INSTALL_SYSDIR}/magicps.pro: magicps.pro
${CP} magicps.pro $(DESTDIR)${INSTALL_SYSDIR}/magicps.pro ${CP} magicps.pro $(DESTDIR)${INSTALL_SYSDIR}/magicps.pro
include ${MAGICDIR}/rules.mak include ${MAGICDIR}/rules.mak
ifeq (${MAKE_WASM},1)
# rules.mak defines `${MODULE}` (= `magic`) with a recipe that links a native
# executable without the TCL libraries. For the WASM build the real artifact
# is `magic.js` (+ `magic.wasm`), so override the target to be a phony alias
# that just rebuilds magic.js. Must come after the include so make uses this
# recipe instead of rules.mak's.
.PHONY: magic
magic: magic.js
@:
endif

View File

@ -59,8 +59,14 @@ main(int argc, char *argv[])
* here, nor its format. It is updated by the Makefile in this directory. * here, nor its format. It is updated by the Makefile in this directory.
* *
* The version string originates at the top of scripts/config. * The version string originates at the top of scripts/config.
*
* Under MAGIC_WRAPPER (Tcl-embedded builds), tclmagic.c owns these globals;
* defining them here as well would produce duplicate-symbol errors when both
* objects end up in the same binary (as in the WASM build).
*/ */
#ifndef MAGIC_WRAPPER
char *MagicVersion = MAGIC_VERSION; char *MagicVersion = MAGIC_VERSION;
char *MagicRevision = MAGIC_REVISION; char *MagicRevision = MAGIC_REVISION;
char *MagicCompileTime = MAGIC_BUILDDATE; char *MagicCompileTime = MAGIC_BUILDDATE;
#endif

View File

@ -8,6 +8,10 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#ifdef MAGIC_WRAPPER
#include "tcltk/tclmagic.h"
#endif
#include "utils/main.h" #include "utils/main.h"
#include "utils/magic.h" #include "utils/magic.h"
#include "utils/paths.h" #include "utils/paths.h"
@ -38,6 +42,13 @@ magicWasmEnsureCadRoot(void)
return 0; return 0;
} }
#ifdef MAGIC_WRAPPER
/* Forward decl — Tclmagic_Init installs all magic Tcl commands and calls
* Tcl_InitStubs(), which sets tclStubsPtr. Without this, any Tcl_X macro
* dereferences a NULL stubs pointer at runtime (crashes the wasm). */
extern int Tclmagic_Init(Tcl_Interp *interp);
#endif
EMSCRIPTEN_KEEPALIVE int EMSCRIPTEN_KEEPALIVE int
magic_wasm_init(void) magic_wasm_init(void)
{ {
@ -53,6 +64,40 @@ magic_wasm_init(void)
if (magicWasmEnsureCadRoot() != 0) if (magicWasmEnsureCadRoot() != 0)
return -1; return -1;
#ifdef MAGIC_WRAPPER
/* In wrapper mode, magic's code (and our PaExpand path expansion) reaches
* for `magicinterp` to resolve $env vars via Tcl_GetVar. In the normal
* Linux flow Tclmagic_Init() is called by tclsh after dlopen(); here we
* embed the interp directly, so we have to bootstrap it before
* magicMainInit() runs anything that might touch Tcl.
*
* Note: we deliberately avoid TxError here in MAGIC_WRAPPER mode
* TxError flushes via Tcl_EvalEx through tclStubsPtr, which only becomes
* non-NULL after Tclmagic_Init -> Tcl_InitStubs. So early errors go
* straight to stderr. */
if (magicinterp == NULL)
{
Tcl_Interp *interp = Tcl_CreateInterp();
if (interp == NULL)
{
fprintf(stderr, "magic_wasm_init: Tcl_CreateInterp returned NULL\n");
return -1;
}
/* Tcl_Init loads /init.tcl from the Tcl library directory; in our
* embedded VFS that script isn't shipped, so failure here is expected
* and non-fatal the interpreter itself is still usable for embedded
* evaluation, which is all we need. */
(void)Tcl_Init(interp);
consoleinterp = interp;
if (Tclmagic_Init(interp) != TCL_OK)
{
fprintf(stderr, "magic_wasm_init: Tclmagic_Init failed: %s\n",
Tcl_GetStringResult(interp));
return -1;
}
}
#endif
return magicMainInit(5, argv); return magicMainInit(5, argv);
} }
@ -75,7 +120,15 @@ magic_wasm_run_command(const char *command)
TxSetPoint(GrScreenRect.r_xtop / 2, GrScreenRect.r_ytop / 2, TxSetPoint(GrScreenRect.r_xtop / 2, GrScreenRect.r_ytop / 2,
WIND_UNKNOWN_WINDOW); WIND_UNKNOWN_WINDOW);
#ifdef MAGIC_WRAPPER
/* In wrapper mode the command is Tcl. Evaluate it via the magic interp;
* the magic backend is reachable through ::magic:: ensemble commands. */
if (magicinterp == NULL)
return -1;
return Tcl_EvalEx(magicinterp, command, -1, 0);
#else
return TxDispatchString(command, FALSE); return TxDispatchString(command, FALSE);
#endif
} }
EMSCRIPTEN_KEEPALIVE int EMSCRIPTEN_KEEPALIVE int

8
npm/.gitignore vendored
View File

@ -1,5 +1,13 @@
# Variant build outputs (regenerable via npm/build.sh).
tcl/magic.js
tcl/magic.wasm
notcl/magic.js
notcl/magic.wasm
# Pre-restructure artifacts (just in case anyone still has them locally).
magic.js magic.js
magic.wasm magic.wasm
*.tgz *.tgz
node_modules/ node_modules/
package-lock.json package-lock.json

View File

@ -2,9 +2,13 @@
# Build Magic WASM and copy artifacts into this npm/ directory. # Build Magic WASM and copy artifacts into this npm/ directory.
# #
# Usage: # Usage:
# npm/build.sh [--release] [--test] [--pack] # npm/build.sh [--variant=<tcl|notcl|both>] [--release] [--test] [--pack]
# #
# --release Omit debug symbols (-g). # --variant=tcl Build only the TCL-embedded variant → npm/tcl/
# --variant=notcl Build only the plain (no Tcl/Tk) variant → npm/notcl/
# --variant=both Build both (default)
#
# --release Omit debug symbols (-g) and build with -O2.
# --test Run `npm run test` after copying artifacts. # --test Run `npm run test` after copying artifacts.
# --pack Run `npm pack` after copying artifacts (and tests, if given). # --pack Run `npm pack` after copying artifacts (and tests, if given).
# #
@ -17,20 +21,33 @@
# EMSDK_DIR Path to an activated emsdk checkout. # EMSDK_DIR Path to an activated emsdk checkout.
# If set, emsdk_env.sh is sourced from there. # If set, emsdk_env.sh is sourced from there.
# If unset, emcc must already be on PATH (e.g. sourced externally). # If unset, emcc must already be on PATH (e.g. sourced externally).
# TCL_REPO Override the path to the intubun/tcl checkout (default:
# ../tcl relative to this magic checkout). Used by the TCL
# variant only.
set -euo pipefail set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")" REPO_ROOT="$(dirname "$SCRIPT_DIR")"
# The TCL variant builds against a sibling clone of intubun/tcl (pristine —
# magic never modifies it). The build itself happens inside magic under
# build-tcl-wasm/, so the TCL source tree stays clean.
TCL_REPO="${TCL_REPO:-$(dirname "$REPO_ROOT")/tcl}"
TCL_BUILD_DIR="${TCL_BUILD_DIR:-$REPO_ROOT/build-tcl-wasm}"
TCL_WASM_PREFIX="$TCL_BUILD_DIR/install"
OPT_RELEASE=0 OPT_RELEASE=0
OPT_TEST=0 OPT_TEST=0
OPT_PACK=0 OPT_PACK=0
OPT_VARIANT=both
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--release) OPT_RELEASE=1 ;; --release) OPT_RELEASE=1 ;;
--test) OPT_TEST=1 ;; --test) OPT_TEST=1 ;;
--pack) OPT_PACK=1 ;; --pack) OPT_PACK=1 ;;
--variant=tcl) OPT_VARIANT=tcl ;;
--variant=notcl) OPT_VARIANT=notcl ;;
--variant=both) OPT_VARIANT=both ;;
*) echo "Unknown option: $arg" >&2; exit 1 ;; *) echo "Unknown option: $arg" >&2; exit 1 ;;
esac esac
done done
@ -76,50 +93,120 @@ sed_strip_cr() {
sed 's/\r//' "$file" > "$tmp" && cat "$tmp" > "$file" && rm "$tmp" sed 's/\r//' "$file" > "$tmp" && cat "$tmp" > "$file" && rm "$tmp"
} }
# --- clean -------------------------------------------------------------------
cd "$REPO_ROOT"
# Only distclean if there's something to clean. A stale `|| true` here would
# hide real failures (e.g. broken toolchain) on a fresh checkout.
if [ -f defs.mak ]; then
emmake make distclean || true
fi
rm -f defs.mak database/database.h
# --- configure ---------------------------------------------------------------
# Strip Windows CRLF line endings (no-op on Linux-native files).
sed_strip_cr configure
find scripts/ -type f -print0 | while IFS= read -r -d '' f; do sed_strip_cr "$f"; done
if [ $OPT_RELEASE -eq 1 ]; then if [ $OPT_RELEASE -eq 1 ]; then
EXTRA_CFLAGS=" -O2" EXTRA_CFLAGS=" -O2"
else else
EXTRA_CFLAGS=" -g" EXTRA_CFLAGS=" -g"
fi fi
CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1${EXTRA_CFLAGS}" \ # --- TCL fork: locate, pin, prebuild (TCL variant only) ---------------------
emconfigure ./configure \ # Reads npm/tcl.ref to get the upstream URL + commit SHA. If the TCL source
--without-cairo --without-opengl --without-x --without-tk --without-tcl \ # tree does not exist yet, clone it (with autocrlf=false to keep configure
--disable-readline --disable-compression \ # parseable on Windows hosts). If it does exist, just check out the pinned
--host=asmjs-unknown-emscripten \ # ref — no auto-fetch, so releases stay reproducible.
--target=asmjs-unknown-emscripten #
# The TCL source tree is treated as read-only. The actual WASM build runs in
# $TCL_BUILD_DIR (inside magic), driven by
# toolchains/emscripten/build-tcl-wasm.sh.
ensure_tcl_built() {
local TCL_REF_FILE="$SCRIPT_DIR/tcl.ref"
if [ -f "$TCL_REF_FILE" ]; then
# shellcheck source=/dev/null
. "$TCL_REF_FILE"
fi
: "${TCL_REPO_URL:=https://github.com/intubun/tcl.git}"
: "${TCL_REF:=main}"
cat toolchains/emscripten/defs.mak >> defs.mak if [ ! -d "$TCL_REPO/.git" ]; then
echo "=== cloning $TCL_REPO_URL into $TCL_REPO ==="
git -c core.autocrlf=false clone "$TCL_REPO_URL" "$TCL_REPO"
fi
# --- build ------------------------------------------------------------------- ( cd "$TCL_REPO"
emmake make depend current_sha=$(git rev-parse HEAD 2>/dev/null || echo "")
emmake make -j"$(ncpu)" modules libs if [ "$current_sha" != "$TCL_REF" ]; then
emmake make techs git fetch --quiet origin
emmake make mains git checkout --quiet --detach "$TCL_REF"
fi
echo "Using TCL at $(git rev-parse HEAD) ($TCL_REPO_URL)"
)
# --- copy artifacts ---------------------------------------------------------- # Build TCL for WASM if it hasn't been built yet. The presence of
cp magic/magic.js "$SCRIPT_DIR/" # tclConfig.sh in the install prefix is the canonical "TCL is built" marker.
cp magic/magic.wasm "$SCRIPT_DIR/" if [ ! -f "$TCL_WASM_PREFIX/lib/tclConfig.sh" ]; then
echo "Copied magic.js and magic.wasm to npm/" echo "=== building TCL for WASM into $TCL_BUILD_DIR (one-time) ==="
bash "$REPO_ROOT/toolchains/emscripten/build-tcl-wasm.sh" \
--src="$TCL_REPO" --out="$TCL_BUILD_DIR"
fi
}
# --- build a single variant --------------------------------------------------
# Each variant gets a fresh configure run because the two configurations
# select different code paths (MAGIC_WRAPPER on/off, MAGIC_NO_TK, link flags),
# so the object cache from one variant is not compatible with the other.
build_variant() {
local variant=$1
local out_dir="$SCRIPT_DIR/$variant"
echo
echo "==============================================================="
echo "=== building variant: $variant"
echo "==============================================================="
cd "$REPO_ROOT"
# Full clean — distclean removes the generated defs.mak and module objects.
if [ -f defs.mak ]; then
emmake make distclean || true
fi
rm -f defs.mak database/database.h
# Strip Windows CRLF line endings (no-op on Linux-native files).
sed_strip_cr configure
find scripts/ -type f -print0 | while IFS= read -r -d '' f; do sed_strip_cr "$f"; done
if [ "$variant" = "tcl" ]; then
ensure_tcl_built
CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1${EXTRA_CFLAGS}" \
emconfigure ./configure \
--without-cairo --without-opengl --without-x --without-tk \
--with-tcl="$TCL_WASM_PREFIX/lib" \
--with-tclincls="$TCL_WASM_PREFIX/include" \
--with-tcllibs="$TCL_WASM_PREFIX/lib" \
--disable-readline --disable-compression \
--host=asmjs-unknown-emscripten \
--target=asmjs-unknown-emscripten
else
CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1${EXTRA_CFLAGS}" \
emconfigure ./configure \
--without-cairo --without-opengl --without-x \
--without-tk --without-tcl \
--disable-readline --disable-compression \
--host=asmjs-unknown-emscripten \
--target=asmjs-unknown-emscripten
fi
cat toolchains/emscripten/defs.mak >> defs.mak
emmake make depend
emmake make -j"$(ncpu)" modules libs
emmake make techs
emmake make mains
mkdir -p "$out_dir"
cp magic/magic.js "$out_dir/"
cp magic/magic.wasm "$out_dir/"
echo "Copied magic.js + magic.wasm into npm/$variant/"
}
case "$OPT_VARIANT" in
tcl|notcl) build_variant "$OPT_VARIANT" ;;
both) build_variant notcl
build_variant tcl ;;
esac
# --- optional test ----------------------------------------------------------- # --- optional test -----------------------------------------------------------
# Runs the same smoke test that CI runs (see .github/workflows/main.yml). # Runs the same smoke test that CI runs (see .github/workflows/main-wasm.yml).
if [ $OPT_TEST -eq 1 ]; then if [ $OPT_TEST -eq 1 ]; then
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
npm run test npm run test

View File

@ -1,11 +1,14 @@
// Shared utilities for Magic WASM examples. // Shared utilities for Magic WASM examples.
import createMagicModule from '../magic.js'; //
// These examples drive the non-TCL variant (legacy magic command parser).
// For Tcl-eval semantics, see examples/smoke-tcl.mjs.
import createMagicModule from '../notcl/magic.js';
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { dirname, resolve, basename } from 'node:path'; import { dirname, resolve, basename } from 'node:path';
export const EXAMPLES_DIR = dirname(fileURLToPath(import.meta.url)); export const EXAMPLES_DIR = dirname(fileURLToPath(import.meta.url));
export const wasmBinary = readFileSync(resolve(EXAMPLES_DIR, '../magic.wasm')); export const wasmBinary = readFileSync(resolve(EXAMPLES_DIR, '../notcl/magic.wasm'));
export const DEFAULT_TECH = 'scmos'; export const DEFAULT_TECH = 'scmos';
export const DEFAULT_MAG = resolve(EXAMPLES_DIR, 'min.mag'); export const DEFAULT_MAG = resolve(EXAMPLES_DIR, 'min.mag');
export const DEFAULT_OUT = resolve(EXAMPLES_DIR, 'output'); export const DEFAULT_OUT = resolve(EXAMPLES_DIR, 'output');

View File

@ -0,0 +1,39 @@
// Smoke-test that confirms the TCL interpreter is live inside magic.wasm.
//
// In wrapper mode magic_wasm_run_command routes its argument to
// Tcl_EvalEx(magicinterp, ...). So:
// - pure Tcl (`set x 42; puts ...`) should work
// - magic commands are available as ::magic:: ensemble commands too
//
// Run: node npm/examples/smoke-tcl.mjs
// Pull in the TCL-enabled variant explicitly via the /tcl subpath export.
import createMagic from '../tcl.js';
const m = await createMagic();
const status = m.init();
if (status !== 0) {
console.error(`magic_wasm_init failed: ${status}`);
process.exit(1);
}
console.log('magic_wasm_init: OK');
function runTcl(label, command) {
const rc = m.runCommand(command);
console.log(`[rc=${rc}] ${label}: ${command}`);
return rc;
}
// 1. Pure Tcl arithmetic — proves the TCL interp is parsing.
runTcl('tcl-set', 'set tcl_smoke_x 42');
runTcl('tcl-expr', 'set tcl_smoke_y [expr {$tcl_smoke_x * 2}]');
// 2. Tcl introspection — magic should publish a Tclmagic package.
runTcl('tcl-info', 'puts "tcl_version=$tcl_version patchlevel=$tcl_patchLevel"');
runTcl('tcl-pkgs', 'puts "packages=[package names]"');
// 3. A real magic command via the wrapper.
runTcl('magic-help', 'magic::help');
console.log('done');

View File

@ -1,14 +1,24 @@
import MagicModuleFactory from './magic.js'; // Default entry point: the non-TCL build.
//
// This preserves the original API and behavior of magic-vlsi-wasm. Magic
// commands ("tech load sky130A", "load /work/inv", …) are dispatched through
// magic's legacy parser; no Tcl interpreter is involved.
//
// For the TCL-enabled build (commands are evaluated by Tcl_EvalEx, exposing
// the full Tcl 9 runtime alongside the ::magic:: command ensemble), import
// from "magic-vlsi-wasm/tcl" instead.
import MagicModuleFactory from './notcl/magic.js';
async function createMagic(options = {}) { async function createMagic(options = {}) {
const module = await MagicModuleFactory(options); const module = await MagicModuleFactory(options);
const init = module.cwrap('magic_wasm_init', 'number', []); const init = module.cwrap('magic_wasm_init', 'number', []);
const runCommand = module.cwrap('magic_wasm_run_command', 'number', ['string']); const runCommand = module.cwrap('magic_wasm_run_command', 'number', ['string']);
const sourceFile = module.cwrap('magic_wasm_source_file', 'number', ['string']); const sourceFile = module.cwrap('magic_wasm_source_file', 'number', ['string']);
const update = module.cwrap('magic_wasm_update', null, []); const update = module.cwrap('magic_wasm_update', null, []);
return { init, runCommand, sourceFile, update, FS: module.FS }; return { init, runCommand, sourceFile, update, FS: module.FS, variant: 'notcl' };
} }
export { createMagic }; export { createMagic };

6
npm/notcl.js Normal file
View File

@ -0,0 +1,6 @@
// Explicit non-TCL entry point: import from "magic-vlsi-wasm/notcl".
// Identical to the default `magic-vlsi-wasm` import — exists so callers can
// be explicit about which variant they want.
export { createMagic } from './index.js';
export { default } from './index.js';

View File

@ -1,34 +1,38 @@
{ {
"name": "magic-vlsi-wasm", "name": "magic-vlsi-wasm",
"version": "0.0.0-dev", "version": "0.0.0-dev",
"description": "Magic VLSI Layout Tool — headless WebAssembly build", "description": "Magic VLSI Layout Tool — headless WebAssembly build (TCL + non-TCL variants)",
"type": "module", "type": "module",
"main": "index.js", "main": "index.js",
"types": "index.d.ts", "types": "index.d.ts",
"exports": { "exports": {
".": { ".": { "import": "./index.js", "types": "./index.d.ts" },
"import": "./index.js", "./tcl": { "import": "./tcl.js", "types": "./index.d.ts" },
"types": "./index.d.ts" "./notcl": { "import": "./notcl.js", "types": "./index.d.ts" }
}
}, },
"files": [ "files": [
"index.js", "index.js",
"index.d.ts", "index.d.ts",
"magic.js", "tcl.js",
"magic.wasm", "notcl.js",
"tcl/magic.js",
"tcl/magic.wasm",
"notcl/magic.js",
"notcl/magic.wasm",
"examples/", "examples/",
"LICENSE", "LICENSE",
"README.md" "README.md"
], ],
"scripts": { "scripts": {
"example": "node examples/extract.js", "example": "node examples/extract.js",
"test": "node examples/all.js", "test": "node examples/all.js",
"test:gds": "node examples/gds.js", "test:tcl": "node examples/smoke-tcl.mjs",
"test:drc": "node examples/drc.js", "test:gds": "node examples/gds.js",
"test:cif": "node examples/cif.js" "test:drc": "node examples/drc.js",
"test:cif": "node examples/cif.js"
}, },
"keywords": ["magic", "vlsi", "eda", "wasm", "webassembly", "layout"], "keywords": ["magic", "vlsi", "eda", "wasm", "webassembly", "layout", "tcl"],
"license": "HPND", "license": "HPND",
"engines": { "engines": {
"node": ">=18" "node": ">=18"

33
npm/tcl.js Normal file
View File

@ -0,0 +1,33 @@
// TCL-enabled entry point: import from "magic-vlsi-wasm/tcl".
//
// In this variant magic.wasm embeds a full Tcl 9 interpreter (from
// intubun/tcl, pinned via magic/npm/tcl.ref) and `runCommand(str)` calls
// Tcl_EvalEx(magicinterp, str, ...). Pure Tcl works:
//
// await magic.runCommand('set x 42');
// await magic.runCommand('puts $tcl_version');
//
// Magic commands are exposed as the ::magic:: ensemble:
//
// await magic.runCommand('magic::tech load sky130A');
// await magic.runCommand('magic::load /work/inv');
//
// (Bare command names like "tech load …" are not imported into the global
// namespace by this build — invoke them with the ::magic:: prefix, or set
// up `namespace import ::magic::*` yourself after init().)
import MagicModuleFactory from './tcl/magic.js';
async function createMagic(options = {}) {
const module = await MagicModuleFactory(options);
const init = module.cwrap('magic_wasm_init', 'number', []);
const runCommand = module.cwrap('magic_wasm_run_command', 'number', ['string']);
const sourceFile = module.cwrap('magic_wasm_source_file', 'number', ['string']);
const update = module.cwrap('magic_wasm_update', null, []);
return { init, runCommand, sourceFile, update, FS: module.FS, variant: 'tcl' };
}
export { createMagic };
export default createMagic;

16
npm/tcl.ref Normal file
View File

@ -0,0 +1,16 @@
# Pin for the TCL fork that the WASM build links against.
#
# Format: shell-style "VAR=VALUE" lines (no spaces around =).
# Lines starting with # or blank lines are ignored.
#
# To take a newer TCL release into magic-wasm:
# 1. Update TCL_REF below to the desired commit SHA (or tag/branch).
# 2. Bump magic/VERSION as usual.
# 3. Commit + push. CI rebuilds and republishes.
#
# Or use the `update-tcl` GitHub Actions workflow (workflow_dispatch only) to
# fetch a target ref from intubun/tcl and open a PR that just rewrites this
# file — you review and merge.
TCL_REPO_URL=https://github.com/intubun/tcl.git
TCL_REF=84b23291b0dd811d642abef4ec7a55473c3eccb3

112
scripts/configure vendored
View File

@ -6276,6 +6276,7 @@ magic_with_tk_libraries=""
usingOGL=1 usingOGL=1
usingTcl=1 usingTcl=1
usingTk=1
usingOA=0 usingOA=0
usingCairo=1 usingCairo=1
usingPython3=1 usingPython3=1
@ -7254,10 +7255,12 @@ if test "${with_tcl+set}" = set; then :
magic_with_tcl=$withval magic_with_tcl=$withval
if test "$withval" = "no" -o "$withval" = "NO"; then if test "$withval" = "no" -o "$withval" = "NO"; then
usingTcl= usingTcl=
usingTk=
elif test $usingScheme ; then elif test $usingScheme ; then
echo Attempt to enable both Tcl and Scheme interpreters. echo Attempt to enable both Tcl and Scheme interpreters.
echo Disabling Tcl, and using Scheme instead. echo Disabling Tcl, and using Scheme instead.
usingTcl= usingTcl=
usingTk=
fi fi
fi fi
@ -7268,6 +7271,9 @@ fi
# Check whether --with-tk was given. # Check whether --with-tk was given.
if test "${with_tk+set}" = set; then : if test "${with_tk+set}" = set; then :
withval=$with_tk; magic_with_tk=$withval withval=$with_tk; magic_with_tk=$withval
if test "$withval" = "no" -o "$withval" = "NO"; then
usingTk=
fi
fi fi
@ -7397,7 +7403,7 @@ fi
# Find the Tk build configuration file "tkConfig.sh" # Find the Tk build configuration file "tkConfig.sh"
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if test $usingTcl ; then if test $usingTk ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for tkConfig.sh" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for tkConfig.sh" >&5
$as_echo_n "checking for tkConfig.sh... " >&6; } $as_echo_n "checking for tkConfig.sh... " >&6; }
@ -7477,8 +7483,8 @@ $as_echo "${tk_config_sh}" >&6; }
if test "x$tk_config_sh" = "x" ; then if test "x$tk_config_sh" = "x" ; then
echo "can't find Tk configuration script \"tkConfig.sh\"" echo "can't find Tk configuration script \"tkConfig.sh\""
echo "Reverting to non-Tcl compilation" echo "Reverting to non-Tk compilation"
usingTcl= usingTk=
fi fi
fi fi
@ -7488,7 +7494,9 @@ fi
if test $usingTcl ; then if test $usingTcl ; then
. $tcl_config_sh . $tcl_config_sh
. $tk_config_sh if test $usingTk ; then
. $tk_config_sh
fi
# Should probably trust the config file contents, but this configure # Should probably trust the config file contents, but this configure
# file checks the Tcl and Tk include and lib directories. Since # file checks the Tcl and Tk include and lib directories. Since
@ -7504,21 +7512,24 @@ if test $usingTcl ; then
tmpstr=${TCL_LIB_SPEC#*-L} tmpstr=${TCL_LIB_SPEC#*-L}
TCL_LIB_DIR=${tmpstr% -l*} TCL_LIB_DIR=${tmpstr% -l*}
tmpstr=${TK_LIB_SPEC#*-L}
TK_LIB_DIR=${tmpstr% -l*}
TCL_INC_DIR=${TCL_INCLUDE_SPEC#*-I} TCL_INC_DIR=${TCL_INCLUDE_SPEC#*-I}
TK_INC_DIR=${TK_INCLUDE_SPEC#*-I} if test $usingTk ; then
tmpstr=${TK_LIB_SPEC#*-L}
TK_LIB_DIR=${tmpstr% -l*}
TK_INC_DIR=${TK_INCLUDE_SPEC#*-I}
if test "$TCL_VERSION" = "7.6" -a "$TK_VERSION" = "4.2" ; then if test "$TCL_VERSION" = "7.6" -a "$TK_VERSION" = "4.2" ; then
: :
elif test "$TCL_VERSION" = "7.5" -a "$TK_VERSION" = "4.1" ; then elif test "$TCL_VERSION" = "7.5" -a "$TK_VERSION" = "4.1" ; then
: :
elif test "$TCL_VERSION" = "$TK_VERSION" ; then elif test "$TCL_VERSION" = "$TK_VERSION" ; then
: :
else else
echo "Mismatched Tcl/Tk versions ($TCL_VERSION != $TK_VERSION)" echo "Mismatched Tcl/Tk versions ($TCL_VERSION != $TK_VERSION)"
echo "Reverting to non-Tcl compile" echo "Reverting to non-Tcl compile"
usingTcl= usingTcl=
usingTk=
fi
fi fi
fi fi
@ -7551,14 +7562,14 @@ if test $usingTcl ; then
fi fi
fi fi
if test $usingTcl ; then if test $usingTk ; then
if test "x${magic_with_tk_includes}" != "x" ; then if test "x${magic_with_tk_includes}" != "x" ; then
if test -r "${magic_with_tk_includes}/tk.h" ; then if test -r "${magic_with_tk_includes}/tk.h" ; then
TK_INC_DIR=${magic_with_tk_includes} TK_INC_DIR=${magic_with_tk_includes}
else else
echo "Can't find tk.h in \"${magic_with_tk_includes}\"" echo "Can't find tk.h in \"${magic_with_tk_includes}\""
echo "Reverting to non-Tcl compile" echo "Reverting to non-Tk compile"
usingTcl= usingTk=
fi fi
else else
for dir in \ for dir in \
@ -7575,8 +7586,8 @@ if test $usingTcl ; then
done done
if test "x${TK_INC_DIR}" = "x" ; then if test "x${TK_INC_DIR}" = "x" ; then
echo "Can't find tk.h header file" echo "Can't find tk.h header file"
echo "Reverting to non-Tcl compile" echo "Reverting to non-Tk compile"
usingTcl= usingTk=
fi fi
fi fi
fi fi
@ -7599,14 +7610,14 @@ if test $usingTcl ; then
if test "x${TCL_LIB_SPEC}" = "x" ; then if test "x${TCL_LIB_SPEC}" = "x" ; then
TCL_LIB_SPEC="-l${TCL_LIB_NAME}" TCL_LIB_SPEC="-l${TCL_LIB_NAME}"
fi fi
if test "x${TK_LIB_SPEC}" = "x" ; then if test $usingTk -a "x${TK_LIB_SPEC}" = "x" ; then
TK_LIB_SPEC="-l${TK_LIB_NAME}" TK_LIB_SPEC="-l${TK_LIB_NAME}"
fi fi
# Find the version of "wish" that corresponds to TCL_EXEC_PREFIX # Find the version of "wish" that corresponds to TCL_EXEC_PREFIX
# We really ought to run "ldd" to confirm that the linked libraries match. # We really ought to run "ldd" to confirm that the linked libraries match.
if test "x${magic_with_wish_binary}" = "x" ; then if test $usingTk -a "x${magic_with_wish_binary}" = "x" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for wish executable" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for wish executable" >&5
$as_echo_n "checking for wish executable... " >&6; } $as_echo_n "checking for wish executable... " >&6; }
for dir in \ for dir in \
@ -7638,7 +7649,7 @@ $as_echo "no" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${WISH_EXE}" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${WISH_EXE}" >&5
$as_echo "${WISH_EXE}" >&6; } $as_echo "${WISH_EXE}" >&6; }
fi fi
else elif test $usingTk ; then
WISH_EXE=${magic_with_wish_binary} WISH_EXE=${magic_with_wish_binary}
fi fi
@ -7731,7 +7742,7 @@ $as_echo "${TCLSH_EXE}" >&6; }
fi fi
fi fi
if test $usingTcl ; then if test $usingTk ; then
if test "x${magic_with_tk_libraries}" != "x" ; then if test "x${magic_with_tk_libraries}" != "x" ; then
for libname in \ for libname in \
"${magic_with_tk_libraries}/${TCL_LIB_FILE}" \ "${magic_with_tk_libraries}/${TCL_LIB_FILE}" \
@ -7746,8 +7757,8 @@ if test $usingTcl ; then
done done
if test "x${TK_LIB_DIR}" = "x" ; then if test "x${TK_LIB_DIR}" = "x" ; then
echo "Can't find tk library in \"${magic_with_tk_libraries}\"" echo "Can't find tk library in \"${magic_with_tk_libraries}\""
echo "Reverting to non-Tcl compile" echo "Reverting to non-Tk compile"
usingTcl= usingTk=
fi fi
else else
for libname in \ for libname in \
@ -7762,8 +7773,8 @@ if test $usingTcl ; then
done done
if test "x${TK_LIB_DIR}" = "x" ; then if test "x${TK_LIB_DIR}" = "x" ; then
echo "Can't find tk library" echo "Can't find tk library"
echo "Reverting to non-Tcl compile" echo "Reverting to non-Tk compile"
usingTcl= usingTk=
fi fi
fi fi
fi fi
@ -8638,7 +8649,12 @@ if test $usingTcl ; then
extra_libs="$extra_libs \${MAGICDIR}/tcltk/libtcltk.o" extra_libs="$extra_libs \${MAGICDIR}/tcltk/libtcltk.o"
extra_defs="$extra_defs -DTCL_DIR=\\\"\${TCLDIR}\\\"" extra_defs="$extra_defs -DTCL_DIR=\\\"\${TCLDIR}\\\""
stub_defs="$stub_defs -DUSE_TCL_STUBS -DUSE_TK_STUBS" if test $usingTk ; then
stub_defs="$stub_defs -DUSE_TCL_STUBS -DUSE_TK_STUBS"
else
stub_defs="$stub_defs -DUSE_TCL_STUBS -DMAGIC_NO_TK"
extra_defs="$extra_defs -DMAGIC_NO_TK"
fi
elif test $usingScheme ; then elif test $usingScheme ; then
modules="$modules lisp" modules="$modules lisp"
unused="$unused tcltk" unused="$unused tcltk"
@ -8681,7 +8697,9 @@ if test $usingTcl ; then
gr_libs="$gr_libs -lX11" gr_libs="$gr_libs -lX11"
fi fi
fi fi
gr_srcs="$gr_srcs \${TKCOMMON_SRCS}" if test $usingTk ; then
gr_srcs="$gr_srcs \${TKCOMMON_SRCS}"
fi
else else
if test $usingX11 ; then if test $usingX11 ; then
gr_dflags="$gr_dflags -DX11 -DXLIB" gr_dflags="$gr_dflags -DX11 -DXLIB"
@ -8791,23 +8809,25 @@ if test $usingTcl ; then
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# #
# Tk libraries and header files # Tk libraries and header files (skipped under --without-tk)
# #
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if test "${TK_INC_DIR}" != "/usr/include" ; then if test $usingTk ; then
INC_SPECS="${INC_SPECS} -I${TK_INC_DIR}" if test "${TK_INC_DIR}" != "/usr/include" ; then
fi INC_SPECS="${INC_SPECS} -I${TK_INC_DIR}"
if test "${TK_LIB_DIR}" = "/usr/lib" -o \ fi
"${TK_LIB_DIR}" = "/usr/lib64" ; then if test "${TK_LIB_DIR}" = "/usr/lib" -o \
LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} ${TK_LIB_SPEC}" "${TK_LIB_DIR}" = "/usr/lib64" ; then
LIB_SPECS="${LIB_SPECS} ${TK_STUB_LIB_SPEC}" LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} ${TK_LIB_SPEC}"
else LIB_SPECS="${LIB_SPECS} ${TK_STUB_LIB_SPEC}"
LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} -L${TK_LIB_DIR} ${TK_LIB_SPEC}"
LIB_SPECS="${LIB_SPECS} -L${TK_LIB_DIR} ${TK_STUB_LIB_SPEC}"
if test "x${loader_run_path}" = "x" ; then
loader_run_path="${TK_LIB_DIR}"
else else
loader_run_path="${TK_LIB_DIR}:${loader_run_path}" LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} -L${TK_LIB_DIR} ${TK_LIB_SPEC}"
LIB_SPECS="${LIB_SPECS} -L${TK_LIB_DIR} ${TK_STUB_LIB_SPEC}"
if test "x${loader_run_path}" = "x" ; then
loader_run_path="${TK_LIB_DIR}"
else
loader_run_path="${TK_LIB_DIR}:${loader_run_path}"
fi
fi fi
fi fi

View File

@ -344,6 +344,7 @@ dnl disabled with --with-opengl=no
usingOGL=1 usingOGL=1
usingTcl=1 usingTcl=1
usingTk=1
usingOA=0 usingOA=0
usingCairo=1 usingCairo=1
usingPython3=1 usingPython3=1
@ -442,10 +443,12 @@ AC_ARG_WITH(tcl,
magic_with_tcl=$withval magic_with_tcl=$withval
if test "$withval" = "no" -o "$withval" = "NO"; then if test "$withval" = "no" -o "$withval" = "NO"; then
usingTcl= usingTcl=
usingTk=
elif test $usingScheme ; then elif test $usingScheme ; then
echo Attempt to enable both Tcl and Scheme interpreters. echo Attempt to enable both Tcl and Scheme interpreters.
echo Disabling Tcl, and using Scheme instead. echo Disabling Tcl, and using Scheme instead.
usingTcl= usingTcl=
usingTk=
fi fi
], ) ], )
@ -455,10 +458,17 @@ dnl and don't set the usingTcl variable.
dnl dnl
dnl This has been broken up into a number of sections, each of which dnl This has been broken up into a number of sections, each of which
dnl depends independently on the setting of usingTcl. dnl depends independently on the setting of usingTcl.
dnl
dnl `usingTk` is independent of `usingTcl`: --without-tk enables
dnl Tcl-only embedding (used by the WASM build, which has no display
dnl server and cannot run Tk).
dnl ---------------------------------------------------------------- dnl ----------------------------------------------------------------
AC_ARG_WITH(tk, [ --with-tk=DIR Find tkConfig.sh in DIR], AC_ARG_WITH(tk, [ --with-tk=DIR Find tkConfig.sh in DIR],
magic_with_tk=$withval) magic_with_tk=$withval
if test "$withval" = "no" -o "$withval" = "NO"; then
usingTk=
fi)
AC_ARG_WITH(tclincls, [ --with-tclincls=DIR Find tcl.h in DIR], AC_ARG_WITH(tclincls, [ --with-tclincls=DIR Find tcl.h in DIR],
magic_with_tcl_includes=$withval) magic_with_tcl_includes=$withval)
AC_ARG_WITH(tkincls, [ --with-tkincls=DIR Find tk.h in DIR], AC_ARG_WITH(tkincls, [ --with-tkincls=DIR Find tk.h in DIR],
@ -564,7 +574,7 @@ fi
# Find the Tk build configuration file "tkConfig.sh" # Find the Tk build configuration file "tkConfig.sh"
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if test $usingTcl ; then if test $usingTk ; then
AC_MSG_CHECKING([for tkConfig.sh]) AC_MSG_CHECKING([for tkConfig.sh])
tk_config_sh="" tk_config_sh=""
@ -642,8 +652,8 @@ if test $usingTcl ; then
if test "x$tk_config_sh" = "x" ; then if test "x$tk_config_sh" = "x" ; then
echo "can't find Tk configuration script \"tkConfig.sh\"" echo "can't find Tk configuration script \"tkConfig.sh\""
echo "Reverting to non-Tcl compilation" echo "Reverting to non-Tk compilation"
usingTcl= usingTk=
fi fi
fi fi
@ -653,7 +663,9 @@ fi
if test $usingTcl ; then if test $usingTcl ; then
. $tcl_config_sh . $tcl_config_sh
. $tk_config_sh if test $usingTk ; then
. $tk_config_sh
fi
# Should probably trust the config file contents, but this configure # Should probably trust the config file contents, but this configure
# file checks the Tcl and Tk include and lib directories. Since # file checks the Tcl and Tk include and lib directories. Since
@ -669,21 +681,24 @@ if test $usingTcl ; then
tmpstr=${TCL_LIB_SPEC#*-L} tmpstr=${TCL_LIB_SPEC#*-L}
TCL_LIB_DIR=${tmpstr% -l*} TCL_LIB_DIR=${tmpstr% -l*}
tmpstr=${TK_LIB_SPEC#*-L}
TK_LIB_DIR=${tmpstr% -l*}
TCL_INC_DIR=${TCL_INCLUDE_SPEC#*-I} TCL_INC_DIR=${TCL_INCLUDE_SPEC#*-I}
TK_INC_DIR=${TK_INCLUDE_SPEC#*-I} if test $usingTk ; then
tmpstr=${TK_LIB_SPEC#*-L}
TK_LIB_DIR=${tmpstr% -l*}
TK_INC_DIR=${TK_INCLUDE_SPEC#*-I}
if test "$TCL_VERSION" = "7.6" -a "$TK_VERSION" = "4.2" ; then if test "$TCL_VERSION" = "7.6" -a "$TK_VERSION" = "4.2" ; then
: :
elif test "$TCL_VERSION" = "7.5" -a "$TK_VERSION" = "4.1" ; then elif test "$TCL_VERSION" = "7.5" -a "$TK_VERSION" = "4.1" ; then
: :
elif test "$TCL_VERSION" = "$TK_VERSION" ; then elif test "$TCL_VERSION" = "$TK_VERSION" ; then
: :
else else
echo "Mismatched Tcl/Tk versions ($TCL_VERSION != $TK_VERSION)" echo "Mismatched Tcl/Tk versions ($TCL_VERSION != $TK_VERSION)"
echo "Reverting to non-Tcl compile" echo "Reverting to non-Tcl compile"
usingTcl= usingTcl=
usingTk=
fi
fi fi
fi fi
@ -716,14 +731,14 @@ if test $usingTcl ; then
fi fi
fi fi
if test $usingTcl ; then if test $usingTk ; then
if test "x${magic_with_tk_includes}" != "x" ; then if test "x${magic_with_tk_includes}" != "x" ; then
if test -r "${magic_with_tk_includes}/tk.h" ; then if test -r "${magic_with_tk_includes}/tk.h" ; then
TK_INC_DIR=${magic_with_tk_includes} TK_INC_DIR=${magic_with_tk_includes}
else else
echo "Can't find tk.h in \"${magic_with_tk_includes}\"" echo "Can't find tk.h in \"${magic_with_tk_includes}\""
echo "Reverting to non-Tcl compile" echo "Reverting to non-Tk compile"
usingTcl= usingTk=
fi fi
else else
for dir in \ for dir in \
@ -740,8 +755,8 @@ if test $usingTcl ; then
done done
if test "x${TK_INC_DIR}" = "x" ; then if test "x${TK_INC_DIR}" = "x" ; then
echo "Can't find tk.h header file" echo "Can't find tk.h header file"
echo "Reverting to non-Tcl compile" echo "Reverting to non-Tk compile"
usingTcl= usingTk=
fi fi
fi fi
fi fi
@ -764,14 +779,14 @@ if test $usingTcl ; then
if test "x${TCL_LIB_SPEC}" = "x" ; then if test "x${TCL_LIB_SPEC}" = "x" ; then
TCL_LIB_SPEC="-l${TCL_LIB_NAME}" TCL_LIB_SPEC="-l${TCL_LIB_NAME}"
fi fi
if test "x${TK_LIB_SPEC}" = "x" ; then if test $usingTk -a "x${TK_LIB_SPEC}" = "x" ; then
TK_LIB_SPEC="-l${TK_LIB_NAME}" TK_LIB_SPEC="-l${TK_LIB_NAME}"
fi fi
# Find the version of "wish" that corresponds to TCL_EXEC_PREFIX # Find the version of "wish" that corresponds to TCL_EXEC_PREFIX
# We really ought to run "ldd" to confirm that the linked libraries match. # We really ought to run "ldd" to confirm that the linked libraries match.
if test "x${magic_with_wish_binary}" = "x" ; then if test $usingTk -a "x${magic_with_wish_binary}" = "x" ; then
AC_MSG_CHECKING([for wish executable]) AC_MSG_CHECKING([for wish executable])
for dir in \ for dir in \
${TK_EXEC_PREFIX}/bin \ ${TK_EXEC_PREFIX}/bin \
@ -800,7 +815,7 @@ if test $usingTcl ; then
else else
AC_MSG_RESULT([${WISH_EXE}]) AC_MSG_RESULT([${WISH_EXE}])
fi fi
else elif test $usingTk ; then
WISH_EXE=${magic_with_wish_binary} WISH_EXE=${magic_with_wish_binary}
fi fi
@ -890,7 +905,7 @@ if test $usingTcl ; then
fi fi
fi fi
if test $usingTcl ; then if test $usingTk ; then
if test "x${magic_with_tk_libraries}" != "x" ; then if test "x${magic_with_tk_libraries}" != "x" ; then
for libname in \ for libname in \
"${magic_with_tk_libraries}/${TCL_LIB_FILE}" \ "${magic_with_tk_libraries}/${TCL_LIB_FILE}" \
@ -905,8 +920,8 @@ if test $usingTcl ; then
done done
if test "x${TK_LIB_DIR}" = "x" ; then if test "x${TK_LIB_DIR}" = "x" ; then
echo "Can't find tk library in \"${magic_with_tk_libraries}\"" echo "Can't find tk library in \"${magic_with_tk_libraries}\""
echo "Reverting to non-Tcl compile" echo "Reverting to non-Tk compile"
usingTcl= usingTk=
fi fi
else else
for libname in \ for libname in \
@ -921,8 +936,8 @@ if test $usingTcl ; then
done done
if test "x${TK_LIB_DIR}" = "x" ; then if test "x${TK_LIB_DIR}" = "x" ; then
echo "Can't find tk library" echo "Can't find tk library"
echo "Reverting to non-Tcl compile" echo "Reverting to non-Tk compile"
usingTcl= usingTk=
fi fi
fi fi
fi fi
@ -1356,7 +1371,12 @@ if test $usingTcl ; then
AC_DEFINE(MAGIC_WRAPPER) AC_DEFINE(MAGIC_WRAPPER)
extra_libs="$extra_libs \${MAGICDIR}/tcltk/libtcltk.o" extra_libs="$extra_libs \${MAGICDIR}/tcltk/libtcltk.o"
extra_defs="$extra_defs -DTCL_DIR=\\\"\${TCLDIR}\\\"" extra_defs="$extra_defs -DTCL_DIR=\\\"\${TCLDIR}\\\""
stub_defs="$stub_defs -DUSE_TCL_STUBS -DUSE_TK_STUBS" if test $usingTk ; then
stub_defs="$stub_defs -DUSE_TCL_STUBS -DUSE_TK_STUBS"
else
stub_defs="$stub_defs -DUSE_TCL_STUBS -DMAGIC_NO_TK"
extra_defs="$extra_defs -DMAGIC_NO_TK"
fi
elif test $usingScheme ; then elif test $usingScheme ; then
modules="$modules lisp" modules="$modules lisp"
unused="$unused tcltk" unused="$unused tcltk"
@ -1401,7 +1421,9 @@ if test $usingTcl ; then
gr_libs="$gr_libs -lX11" gr_libs="$gr_libs -lX11"
fi fi
fi fi
gr_srcs="$gr_srcs \${TKCOMMON_SRCS}" if test $usingTk ; then
gr_srcs="$gr_srcs \${TKCOMMON_SRCS}"
fi
else else
if test $usingX11 ; then if test $usingX11 ; then
gr_dflags="$gr_dflags -DX11 -DXLIB" gr_dflags="$gr_dflags -DX11 -DXLIB"
@ -1506,23 +1528,25 @@ if test $usingTcl ; then
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# #
# Tk libraries and header files # Tk libraries and header files (skipped under --without-tk)
# #
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if test "${TK_INC_DIR}" != "/usr/include" ; then if test $usingTk ; then
INC_SPECS="${INC_SPECS} -I${TK_INC_DIR}" if test "${TK_INC_DIR}" != "/usr/include" ; then
fi INC_SPECS="${INC_SPECS} -I${TK_INC_DIR}"
if test "${TK_LIB_DIR}" = "/usr/lib" -o \ fi
"${TK_LIB_DIR}" = "/usr/lib64" ; then if test "${TK_LIB_DIR}" = "/usr/lib" -o \
LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} ${TK_LIB_SPEC}" "${TK_LIB_DIR}" = "/usr/lib64" ; then
LIB_SPECS="${LIB_SPECS} ${TK_STUB_LIB_SPEC}" LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} ${TK_LIB_SPEC}"
else LIB_SPECS="${LIB_SPECS} ${TK_STUB_LIB_SPEC}"
LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} -L${TK_LIB_DIR} ${TK_LIB_SPEC}"
LIB_SPECS="${LIB_SPECS} -L${TK_LIB_DIR} ${TK_STUB_LIB_SPEC}"
if test "x${loader_run_path}" = "x" ; then
loader_run_path="${TK_LIB_DIR}"
else else
loader_run_path="${TK_LIB_DIR}:${loader_run_path}" LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} -L${TK_LIB_DIR} ${TK_LIB_SPEC}"
LIB_SPECS="${LIB_SPECS} -L${TK_LIB_DIR} ${TK_STUB_LIB_SPEC}"
if test "x${loader_run_path}" = "x" ; then
loader_run_path="${TK_LIB_DIR}"
else
loader_run_path="${TK_LIB_DIR}:${loader_run_path}"
fi
fi fi
fi fi

View File

@ -41,6 +41,16 @@ BIN_FILES = \
tcl-main: magicexec magicdnull magic.tcl magic.sh ext2spice.sh ext2sim.sh tcl-main: magicexec magicdnull magic.tcl magic.sh ext2spice.sh ext2sim.sh
# In the WASM build the tcltk module ships only as libtcltk.o; there is no
# wish-like launcher binary, no magic.sh, etc. `make mains` iterates over
# PROGRAMS which now includes tcltk (because --with-tcl is on), so define a
# no-op `main` to keep that loop happy. The native build still uses tcl-main
# via `make all` → tcllibrary.
ifeq (${MAKE_WASM},1)
main:
@:
endif
install-tcl: magicexec magicdnull ${BIN_FILES} ${TCL_FILES} install-tcl: magicexec magicdnull ${BIN_FILES} ${TCL_FILES}
${RM} $(DESTDIR)${INSTALL_TCLDIR}/magicexec ${RM} $(DESTDIR)${INSTALL_TCLDIR}/magicexec
${CP} magicexec $(DESTDIR)${INSTALL_TCLDIR}/magicexec ${CP} magicexec $(DESTDIR)${INSTALL_TCLDIR}/magicexec

View File

@ -178,8 +178,10 @@ TagCallback(interp, tkpath, argc, argv)
windCheckOnlyWindow(&w, DBWclientID); windCheckOnlyWindow(&w, DBWclientID);
if (w != NULL && !(w->w_flags & WIND_OFFSCREEN)) if (w != NULL && !(w->w_flags & WIND_OFFSCREEN))
{ {
#ifndef MAGIC_NO_TK
Tk_Window tkwind = (Tk_Window) w->w_grdata; Tk_Window tkwind = (Tk_Window) w->w_grdata;
if (tkwind != NULL) tkpath = Tk_PathName(tkwind); if (tkwind != NULL) tkpath = Tk_PathName(tkwind);
#endif
} }
} }
if (tkpath == NULL) if (tkpath == NULL)
@ -742,8 +744,10 @@ _magic_initialize(ClientData clientData,
/* (See graphics/grTkCommon.c) */ /* (See graphics/grTkCommon.c) */
/* (Unless "-dnull" option has been given) */ /* (Unless "-dnull" option has been given) */
#ifndef MAGIC_NO_TK
if (strcmp(MainDisplayType, "NULL")) if (strcmp(MainDisplayType, "NULL"))
RegisterTkCommands(interp); RegisterTkCommands(interp);
#endif
/* Set up the console so that its menu option File->Exit */ /* Set up the console so that its menu option File->Exit */
/* calls magic's exit routine first. This should not be */ /* calls magic's exit routine first. This should not be */

View File

@ -12,7 +12,9 @@
#ifdef MAGIC_WRAPPER #ifdef MAGIC_WRAPPER
#include <tcl.h> #include <tcl.h>
#ifndef MAGIC_NO_TK
#include <tk.h> #include <tk.h>
#endif
/* Externally-defined global variables */ /* Externally-defined global variables */

View File

@ -0,0 +1,142 @@
#!/usr/bin/env bash
# Build intubun/tcl as a static WASM library for linking into magic.wasm.
#
# This script does NOT modify the TCL source tree — the build is fully
# out-of-source. configure is invoked from the build directory inside magic,
# with the TCL source tree referenced through $0's path. All generated files
# (Makefile, objects, tclConfig.sh, libtcl9.x.a, ...) live under $OUT.
#
# Outputs (under $OUT):
# $OUT/Makefile, *.o, tclConfig.sh, libtcl9.x.a, libtclstub.a
# $OUT/install/include/{tcl.h, tclDecls.h, ...}
# $OUT/install/lib/{libtcl9.x.a, libtclstub.a, tclConfig.sh, tcl9.x/<scripts>}
#
# Usage:
# build-tcl-wasm.sh --src=<TCL source tree> [--out=<build dir>] [--clean]
#
# Requirements: an activated emsdk (emcc/emconfigure/emmake on PATH), a host
# gcc (used to build TCL's minizip helper, which runs natively), make, and a
# git checkout of intubun/tcl pointed to by --src.
#
# Note on line endings: if the TCL source tree was cloned on Windows with
# git's core.autocrlf=true, unix/configure may have CRLF line endings and
# bash will reject it. Clone with `git -c core.autocrlf=false clone ...` to
# avoid this; magic/npm/build.sh does that automatically.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# magic/ root is two levels up from toolchains/emscripten/.
MAGIC_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
TCL_SRC=""
OUT="$MAGIC_ROOT/build-tcl-wasm"
OPT_CLEAN=0
for arg in "$@"; do
case "$arg" in
--src=*) TCL_SRC=${arg#--src=} ;;
--out=*) OUT=${arg#--out=} ;;
--clean) OPT_CLEAN=1 ;;
*) echo "Unknown option: $arg" >&2; exit 1 ;;
esac
done
if [ -z "$TCL_SRC" ]; then
echo "Error: --src=<tcl source tree> is required." >&2
exit 1
fi
if [ ! -f "$TCL_SRC/unix/configure" ]; then
echo "Error: $TCL_SRC/unix/configure not found — not a TCL source tree." >&2
exit 1
fi
# Detect CRLF in configure up-front so the failure mode is a useful error and
# not a cryptic `set: pipefail: invalid option name` from bash.
if head -1 "$TCL_SRC/unix/configure" | grep -q $'\r'; then
echo "Error: $TCL_SRC/unix/configure has CRLF line endings." >&2
echo " Reclone intubun/tcl with: git -c core.autocrlf=false clone …" >&2
exit 1
fi
# Normalise to absolute paths so the generated tclConfig.sh has stable paths.
TCL_SRC=$(cd "$TCL_SRC" && pwd)
mkdir -p "$OUT"
OUT=$(cd "$OUT" && pwd)
if [ $OPT_CLEAN -eq 1 ]; then
rm -rf "$OUT"
mkdir -p "$OUT"
fi
command -v emcc >/dev/null 2>&1 || {
echo "Error: emcc not on PATH. Activate emsdk first." >&2
exit 1
}
echo "Using emcc: $(command -v emcc)"
emcc --version | head -1
ncpu() {
if command -v nproc >/dev/null 2>&1; then nproc
else echo 2
fi
}
cd "$OUT"
# --- configure --------------------------------------------------------------
# Standard autoconf out-of-source pattern: invoke configure from the build
# dir via its absolute path. configure uses dirname($0) as srcdir.
#
# -sUSE_ZLIB=1 makes emcc inject its zlib port so configure finds zlib.h /
# deflateSetHeader on the link line. Without it, TCL falls back to its
# compat/zlib copy whose include path isn't picked up by tclEvent.o.
#
# --disable-shared we statically link libtcl into magic.wasm.
# --disable-load no dynamic loading inside wasm.
# --enable-symbols=no release (-O2). Override CFLAGS to add -g for debug.
if [ ! -f Makefile ]; then
echo "=== emconfigure ==="
CFLAGS="-O2 -sUSE_ZLIB=1" \
emconfigure "$TCL_SRC/unix/configure" \
--disable-shared \
--disable-load \
--enable-symbols=no \
--host=wasm32-unknown-emscripten \
--prefix="$OUT/install"
fi
# --- build ------------------------------------------------------------------
# HOST_CC/AR/RANLIB must be native — TCL's Makefile builds a `minizip` tool
# that runs on the host to produce the embedded zipfs resource. Without this
# override emcc would build minizip itself as a wasm module, which crashes on
# small stacks. HOST_OBJEXT is kept distinct from OBJEXT so host and target
# objects don't collide.
#
# Archive names come from tclConfig.sh so we don't bake in a fixed version.
TCL_LIB_FILE=$(. "$OUT/tclConfig.sh" && echo "$TCL_LIB_FILE")
TCL_STUB_LIB_FILE=$(. "$OUT/tclConfig.sh" && echo "$TCL_STUB_LIB_FILE")
HOST_OBJEXT=hostobj
echo "=== build ($TCL_LIB_FILE, $TCL_STUB_LIB_FILE) ==="
emmake make -j"$(ncpu)" \
HOST_CC=gcc HOST_AR=ar HOST_RANLIB=ranlib HOST_EXEEXT= HOST_OBJEXT="$HOST_OBJEXT" \
"$TCL_LIB_FILE" "$TCL_STUB_LIB_FILE"
# --- install ----------------------------------------------------------------
# install-headers populates $OUT/install/include with tcl.h + friends.
# install-libraries copies the Tcl script library (init.tcl, encodings, ...)
# under $OUT/install/lib/tcl9.x. We skip install-binaries because it would
# build a tclsh executable that we don't need; we cp the static archives
# manually so magic's configure (which scans <prefix>/lib for tclConfig.sh
# and a libtcl*.a) finds everything in one place.
emmake make \
HOST_CC=gcc HOST_AR=ar HOST_RANLIB=ranlib HOST_EXEEXT= HOST_OBJEXT="$HOST_OBJEXT" \
install-headers install-libraries
mkdir -p "$OUT/install/lib"
cp -f "$TCL_LIB_FILE" "$TCL_STUB_LIB_FILE" tclConfig.sh "$OUT/install/lib/"
echo
echo "=== artifacts ==="
ls -la "$TCL_LIB_FILE" "$TCL_STUB_LIB_FILE" tclConfig.sh 2>&1 | sed 's/^/ /'
echo " install/lib:"
ls -la install/lib 2>&1 | sed 's/^/ /'

View File

@ -24,11 +24,12 @@ TOP_EXTRA_LIBS += \
-sWASM=1 \ -sWASM=1 \
-sMODULARIZE=1 \ -sMODULARIZE=1 \
-sEXPORT_ES6=1 \ -sEXPORT_ES6=1 \
-sUSE_ZLIB=1 \
-sEXPORTED_FUNCTIONS=_magic_wasm_init,_magic_wasm_run_command,_magic_wasm_source_file,_magic_wasm_update \ -sEXPORTED_FUNCTIONS=_magic_wasm_init,_magic_wasm_run_command,_magic_wasm_source_file,_magic_wasm_update \
-sEXPORTED_RUNTIME_METHODS=cwrap,ccall,FS,setValue,getValue \ -sEXPORTED_RUNTIME_METHODS=cwrap,ccall,FS,setValue,getValue \
-sALLOW_MEMORY_GROWTH=1 \ -sALLOW_MEMORY_GROWTH=1 \
-sINITIAL_MEMORY=33554432 \ -sINITIAL_MEMORY=33554432 \
-sSTACK_SIZE=5242880 \ -Wl,-z,stack-size=5242880 \
-sASSERTIONS=1 \ -sASSERTIONS=1 \
-sENVIRONMENT=node,web,worker \ -sENVIRONMENT=node,web,worker \
-sFORCE_FILESYSTEM=1 \ -sFORCE_FILESYSTEM=1 \