Merge 16c0ab35b1 into d9e6c78adb
This commit is contained in:
commit
82062a5c26
|
|
@ -1,13 +1,17 @@
|
|||
name: CI-wasm
|
||||
|
||||
# Builds the Magic WebAssembly target on every push and pull request.
|
||||
# When the VERSION file changes on the default branch, the package is
|
||||
# additionally published to GitHub Packages (npm.pkg.github.com) as
|
||||
# @<owner>/magic-vlsi-wasm — no manual tag or token required.
|
||||
# Tim Edwards updates VERSION to trigger a new release; the scope resolves
|
||||
# automatically to the repo owner, so forks publish under their own namespace.
|
||||
# Builds the Magic WebAssembly target (both the non-TCL and TCL variants)
|
||||
# on every push and pull request as a CI check. **Publishing** only happens
|
||||
# when a release tag of the form v<x.y.z>... is pushed — that gate is the
|
||||
# manual release trigger:
|
||||
#
|
||||
# WASM is architecture-independent — built once on x86-64, usable everywhere.
|
||||
# # bump magic/VERSION, commit, push to default branch
|
||||
# git tag v8.3.638
|
||||
# git push origin v8.3.638
|
||||
#
|
||||
# The tag name (minus the leading "v") provides the base; the workflow appends
|
||||
# the commit date and short SHA: v8.3.799 → 8.3.799020261231+git01234cde.
|
||||
# Forks publish under their own namespace via the @<owner>/ scope.
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -18,6 +22,14 @@ on:
|
|||
description: 'emsdk version to build with (default: latest; pin a version number to bisect)'
|
||||
type: string
|
||||
default: 'latest'
|
||||
tcl_ref:
|
||||
description: 'TCL ref to build against (default: auto-resolve latest stable tag). Use a tag like core-9-0-3, a branch, or a commit SHA to bisect a regression.'
|
||||
type: string
|
||||
default: ''
|
||||
tcl_repo_url:
|
||||
description: 'TCL repository URL (default: https://github.com/tcltk/tcl.git)'
|
||||
type: string
|
||||
default: ''
|
||||
dry_run:
|
||||
description: 'Dry run: pack only, do not publish even on tag pushes'
|
||||
type: boolean
|
||||
|
|
@ -70,32 +82,65 @@ jobs:
|
|||
echo "===== emcc -dM -E - ====="; echo | emcc -dM -E - | sort
|
||||
echo "===== em++ -dM -E - ====="; echo | em++ -dM -E - | sort
|
||||
|
||||
- name: Build WASM
|
||||
# Determine which TCL ref to build against.
|
||||
# Priority: workflow_dispatch input > auto-resolved latest stable tag.
|
||||
# TCL stable releases follow the core-<major>-<even_minor>-<patch>
|
||||
# naming convention; core-9-0-x is the current stable series.
|
||||
# Falls back to main only if no release tags are found at all.
|
||||
- name: Resolve TCL ref
|
||||
id: resolve-tcl
|
||||
env:
|
||||
TCL_REPO_URL: ${{ github.event.inputs.tcl_repo_url || 'https://github.com/tcltk/tcl.git' }}
|
||||
TCL_REF_INPUT: ${{ github.event.inputs.tcl_ref || '' }}
|
||||
run: |
|
||||
if [ -n "$TCL_REF_INPUT" ]; then
|
||||
TCL_REF="$TCL_REF_INPUT"
|
||||
echo "Using workflow_dispatch TCL_REF: $TCL_REF"
|
||||
else
|
||||
TCL_REF=$(git ls-remote --tags --sort=-version:refname "$TCL_REPO_URL" \
|
||||
'refs/tags/core-9-0-*' \
|
||||
| grep -v '\^{}' \
|
||||
| head -1 \
|
||||
| awk '{print $2}' \
|
||||
| sed 's|refs/tags/||')
|
||||
if [ -z "$TCL_REF" ]; then
|
||||
TCL_REF=main
|
||||
echo "Warning: no stable core-9-0-x tag found, falling back to main"
|
||||
else
|
||||
echo "Auto-resolved latest stable TCL tag: $TCL_REF"
|
||||
fi
|
||||
fi
|
||||
echo "tcl_ref=$TCL_REF" >> "$GITHUB_OUTPUT"
|
||||
echo "tcl_repo_url=$TCL_REPO_URL" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Clone tcltk/tcl into a sibling directory at the resolved ref.
|
||||
# Done as an explicit step so the exact commit is visible in the job
|
||||
# log. The source tree is read-only — the WASM build runs inside magic.
|
||||
- name: Clone tcltk/tcl
|
||||
env:
|
||||
TCL_REPO_URL: ${{ steps.resolve-tcl.outputs.tcl_repo_url }}
|
||||
TCL_REF: ${{ steps.resolve-tcl.outputs.tcl_ref }}
|
||||
run: |
|
||||
# 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"
|
||||
echo "=== TCL commit ==="
|
||||
git log -n1 --format="commit %H%nauthor %an <%ae>%ndate %ci%nref %D%n%n %s"
|
||||
|
||||
- name: Build WASM — both variants (tcl + notcl)
|
||||
env:
|
||||
TCL_REF: ${{ steps.resolve-tcl.outputs.tcl_ref }}
|
||||
TCL_REPO_URL: ${{ steps.resolve-tcl.outputs.tcl_repo_url }}
|
||||
run: |
|
||||
source ./emsdk/emsdk_env.sh
|
||||
# --without/--disable flags: no WASM library available for these features
|
||||
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
|
||||
bash npm/build.sh --variant=both
|
||||
|
||||
- name: Copy WASM artifacts into npm/
|
||||
run: |
|
||||
cp magic/magic.js npm/
|
||||
cp magic/magic.wasm npm/
|
||||
- name: Run example tests (non-TCL variant)
|
||||
run: cd npm && npm test
|
||||
|
||||
- name: Run example tests
|
||||
run: cd npm && npm run test
|
||||
- name: Run full test suite (TCL variant)
|
||||
run: cd npm && npm run test:tcl
|
||||
|
||||
# Dump generated text outputs (.ext, .spice, .cif, …) into the CI log
|
||||
# so a regression in extraction / netlisting / cifoutput is visible
|
||||
|
|
@ -104,29 +149,61 @@ jobs:
|
|||
- name: Display example outputs
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
for f in npm/examples/output/*; do
|
||||
name=$(basename "$f")
|
||||
case "$f" in
|
||||
*.gds) echo "===== $name (binary, $(wc -c < "$f") bytes — skipped) =====" ;;
|
||||
*) echo "===== $name ====="; cat "$f" ;;
|
||||
esac
|
||||
for dir in npm/examples/output npm/examples/output-tcl; do
|
||||
[ -d "$dir" ] || continue
|
||||
echo "======== $dir ========"
|
||||
for f in "$dir"/*; do
|
||||
name=$(basename "$f")
|
||||
case "$f" in
|
||||
*.gds) echo "===== $name (binary, $(wc -c < "$f") bytes — skipped) =====" ;;
|
||||
*) echo "===== $name ====="; cat "$f" ;;
|
||||
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.
|
||||
#
|
||||
# Version scheme (per dmiles' recommendation):
|
||||
# {MAJOR}.{MINOR}.{PATCH}0{YYYYMMDD}+git{SHORT_SHA}
|
||||
# e.g. v8.3.799 pushed on 2026-12-31 → 8.3.799020261231+git01234cde
|
||||
#
|
||||
# The leading zero between PATCH and date keeps the number readable and
|
||||
# ensures correct numeric ordering: 799020261231 < 800020261231.
|
||||
# Build metadata (+git...) is ignored by npm for comparison but retained
|
||||
# for traceability. Security patches for the 799 series can be inserted
|
||||
# as later dates (799020270101, 799020270201, …) and are matched by the
|
||||
# range <=8.3.799900000000 or <8.3.8000000000000.
|
||||
- name: Determine release version (tag-driven only)
|
||||
id: release
|
||||
run: |
|
||||
date=$(git show -s --format=%cs | tr -d '-')
|
||||
hash=$(git show -s --format=%h)
|
||||
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/}"
|
||||
base="${tag#v}"
|
||||
echo "publish=true" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${base}0${date}+git${hash}" >> "$GITHUB_OUTPUT"
|
||||
echo "Tag release: $tag → npm version ${base}0${date}+git${hash}"
|
||||
else
|
||||
base=$(cat VERSION)
|
||||
echo "publish=false" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${base}0${date}+git${hash}" >> "$GITHUB_OUTPUT"
|
||||
echo "Non-tag build: will not publish."
|
||||
fi
|
||||
|
||||
- name: Set package version and scope
|
||||
env:
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
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
|
||||
# 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"
|
||||
cd npm
|
||||
npm pkg set name="$SCOPED_NAME"
|
||||
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
|
||||
run: ./npm/pack.sh
|
||||
|
|
@ -137,24 +214,42 @@ jobs:
|
|||
name: magic-vlsi-wasm-npm
|
||||
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
|
||||
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
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Write a Markdown summary visible next to the artifacts on the Actions
|
||||
# page. Captures the exact versions used so a future regression can be
|
||||
# bisected without scrolling through raw logs.
|
||||
- name: Build summary
|
||||
if: always()
|
||||
env:
|
||||
TCL_REF: ${{ steps.resolve-tcl.outputs.tcl_ref }}
|
||||
TCL_REPO_URL: ${{ steps.resolve-tcl.outputs.tcl_repo_url }}
|
||||
run: |
|
||||
source ./emsdk/emsdk_env.sh 2>/dev/null || true
|
||||
EMCC_VER=$(emcc --version 2>/dev/null | head -1 || echo "unavailable")
|
||||
GCC_VER=$(gcc --version 2>/dev/null | head -1 || echo "unavailable")
|
||||
NODE_VER=$(node --version 2>/dev/null || echo "unavailable")
|
||||
MAGIC_VER=$(cat VERSION 2>/dev/null || echo "unavailable")
|
||||
if [ -d ../tcl/.git ]; then
|
||||
TCL_SHA=$(cd ../tcl && git rev-parse HEAD)
|
||||
TCL_DATE=$(cd ../tcl && git log -1 --format="%ci")
|
||||
TCL_SUBJECT=$(cd ../tcl && git log -1 --format="%s")
|
||||
else
|
||||
TCL_SHA="(not cloned)"; TCL_DATE=""; TCL_SUBJECT=""
|
||||
fi
|
||||
printf '## Build info\n\n' >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '| Component | Details |\n' >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '|-----------|----------|\n' >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '| Magic | `%s` |\n' "$MAGIC_VER" >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '| Emscripten | %s |\n' "$EMCC_VER" >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '| GCC | %s |\n' "$GCC_VER" >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '| Node.js | %s |\n' "$NODE_VER" >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '| TCL repo | %s |\n' "$TCL_REPO_URL" >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '| TCL ref | `%s` |\n' "$TCL_REF" >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '| TCL commit | `%s` |\n' "$TCL_SHA" >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '| TCL date | %s |\n' "$TCL_DATE" >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '| TCL subject | %s |\n' "$TCL_SUBJECT" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ magic/magic.js
|
|||
magic/magic.js.symbols
|
||||
magic/magic.symbols
|
||||
magic/magic.wasm
|
||||
build-tcl-wasm/
|
||||
net2ir/net2ir
|
||||
net2ir/net2ir.js
|
||||
net2ir/net2ir.wasm
|
||||
|
|
|
|||
|
|
@ -42,12 +42,29 @@ LIBS += ${GR_LIBS} ${READLINE_LIBS} -lm ${LD_EXTRA_LIBS} \
|
|||
CLEANS += tclmagic${SHDLIB_EXT} libtclmagic${SHDLIB_EXT}.a proto.magicrc
|
||||
|
||||
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
|
||||
|
||||
magic.js: lib${MODULE}.o ${EXTRA_LIBS}
|
||||
@echo --- building main magic WASM
|
||||
${RM} magic.js magic.wasm
|
||||
ifneq (${TCL_LIB_DIR},)
|
||||
# TCL variant: pull in the main TCL archive (LIB_SPECS_NOSTUB → -ltcl9.x) and
|
||||
# the stub-bootstrap archive (-ltclstub). Both are required: magic's objects
|
||||
# use USE_TCL_STUBS macros (resolved by tclStubsPtr from libtclstub.a), and
|
||||
# tclStubsPtr itself must point into the real TCL implementation (libtcl9.x.a).
|
||||
${CC} ${CFLAGS} ${CPPFLAGS} ${DFLAGS} lib${MODULE}.o ${EXTRA_LIBS} -o magic.js ${LIBS} ${LIB_SPECS_NOSTUB} -L${TCL_LIB_DIR} -ltclstub
|
||||
else
|
||||
${CC} ${CFLAGS} ${CPPFLAGS} ${DFLAGS} lib${MODULE}.o ${EXTRA_LIBS} -o magic.js ${LIBS}
|
||||
endif
|
||||
endif
|
||||
|
||||
main: magic proto.magicrc
|
||||
ifeq (${MAKE_WASM},1)
|
||||
|
|
@ -93,3 +110,14 @@ $(DESTDIR)${INSTALL_SYSDIR}/magicps.pro: magicps.pro
|
|||
${CP} magicps.pro $(DESTDIR)${INSTALL_SYSDIR}/magicps.pro
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -59,8 +59,14 @@ main(int argc, char *argv[])
|
|||
* here, nor its format. It is updated by the Makefile in this directory.
|
||||
*
|
||||
* 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 *MagicRevision = MAGIC_REVISION;
|
||||
char *MagicCompileTime = MAGIC_BUILDDATE;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef MAGIC_WRAPPER
|
||||
#include "tcltk/tclmagic.h"
|
||||
#endif
|
||||
|
||||
#include "utils/main.h"
|
||||
#include "utils/magic.h"
|
||||
#include "utils/paths.h"
|
||||
|
|
@ -38,6 +42,16 @@ magicWasmEnsureCadRoot(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef MAGIC_WRAPPER
|
||||
/* Forward decl — Tclmagic_Init bootstraps the Tcl interpreter (registers
|
||||
* the magic::initialize command and calls Tcl_InitStubs(), which sets
|
||||
* tclStubsPtr). Without this, any Tcl_X macro dereferences a NULL stubs
|
||||
* pointer at runtime (crashes the wasm). The actual magic:: commands
|
||||
* (magic::load, magic::gds, etc.) are registered separately by
|
||||
* TclmagicRegisterCommands() after magicMainInit() populates the clients. */
|
||||
extern int Tclmagic_Init(Tcl_Interp *interp);
|
||||
#endif
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int
|
||||
magic_wasm_init(void)
|
||||
{
|
||||
|
|
@ -53,7 +67,52 @@ magic_wasm_init(void)
|
|||
if (magicWasmEnsureCadRoot() != 0)
|
||||
return -1;
|
||||
|
||||
return magicMainInit(5, argv);
|
||||
#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
|
||||
|
||||
{
|
||||
static int commandsRegistered = FALSE;
|
||||
int rc = magicMainInit(5, argv);
|
||||
#ifdef MAGIC_WRAPPER
|
||||
if (rc == 0 && !commandsRegistered)
|
||||
{
|
||||
TclmagicRegisterCommands(magicinterp);
|
||||
commandsRegistered = TRUE;
|
||||
}
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int
|
||||
|
|
@ -75,13 +134,20 @@ magic_wasm_run_command(const char *command)
|
|||
TxSetPoint(GrScreenRect.r_xtop / 2, GrScreenRect.r_ytop / 2,
|
||||
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);
|
||||
#endif
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int
|
||||
magic_wasm_source_file(const char *path)
|
||||
{
|
||||
FILE *f;
|
||||
int status;
|
||||
|
||||
status = magic_wasm_init();
|
||||
|
|
@ -91,27 +157,33 @@ magic_wasm_source_file(const char *path)
|
|||
if ((path == NULL) || (*path == '\0'))
|
||||
return -1;
|
||||
|
||||
f = PaOpen((char *)path, "r", (char *)NULL, ".", (char *)NULL,
|
||||
(char **)NULL);
|
||||
if (f == NULL)
|
||||
{
|
||||
TxError("Unable to open command file \"%s\".\n", path);
|
||||
#ifdef MAGIC_WRAPPER
|
||||
/* In wrapper mode the file contains Tcl; evaluate it through the
|
||||
* Tcl interpreter so that magic:: commands are dispatched via
|
||||
* _tcl_dispatch just like magic_wasm_run_command does for strings. */
|
||||
if (magicinterp == NULL)
|
||||
return -1;
|
||||
return Tcl_EvalFile(magicinterp, path);
|
||||
#else
|
||||
{
|
||||
FILE *f = PaOpen((char *)path, "r", (char *)NULL, ".", (char *)NULL,
|
||||
(char **)NULL);
|
||||
if (f == NULL)
|
||||
{
|
||||
TxError("Unable to open command file \"%s\".\n", path);
|
||||
return -1;
|
||||
}
|
||||
/* Set the current point to the center of the screen so that
|
||||
* WindSendCommand routes all commands from the file to the layout
|
||||
* window client. Without this, commands arrive with point (0,0)
|
||||
* and end up in the border/windClient context where most are unknown. */
|
||||
TxSetPoint(GrScreenRect.r_xtop / 2, GrScreenRect.r_ytop / 2,
|
||||
WIND_UNKNOWN_WINDOW);
|
||||
TxDispatch(f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set the current point to the centre of the screen so that
|
||||
* WindSendCommand routes all commands from the file to the layout
|
||||
* window client, just as magic_wasm_run_command does for single
|
||||
* commands. Without this, commands arrive with point (0,0) and
|
||||
* end up in the border/windClient context where most commands are
|
||||
* unknown.
|
||||
*/
|
||||
TxSetPoint(GrScreenRect.r_xtop / 2, GrScreenRect.r_ytop / 2,
|
||||
WIND_UNKNOWN_WINDOW);
|
||||
|
||||
TxDispatch(f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void
|
||||
|
|
|
|||
|
|
@ -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.wasm
|
||||
|
||||
*.tgz
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
|
|
|||
|
|
@ -7,16 +7,43 @@ WebAssembly as a headless library. Runs in Node.js, browsers, and Web Workers
|
|||
Use it to programmatically read and write `.mag`, `.gds`, `.cif`, `.ext`, and
|
||||
SPICE netlists; run DRC; extract parasitics — anywhere JavaScript runs.
|
||||
|
||||
The package ships two variants:
|
||||
|
||||
| Variant | Entry point | Description |
|
||||
|---------|-------------|-------------|
|
||||
| **notcl** (default) | `magic-vlsi-wasm` | Standalone — no Tcl interpreter. Commands are plain Magic command strings. |
|
||||
| **tcl** | `magic-vlsi-wasm/tcl` | Embeds a full Tcl 9 interpreter. Commands are evaluated as Tcl; Magic commands are available as the `::magic::` ensemble. |
|
||||
|
||||
## Install
|
||||
|
||||
The package is published to GitHub Packages. Add the following to your
|
||||
project's `.npmrc` so npm knows where to find it:
|
||||
|
||||
```
|
||||
@rtimothyedwards:registry=https://npm.pkg.github.com
|
||||
```
|
||||
|
||||
Then install:
|
||||
|
||||
```bash
|
||||
npm install magic-vlsi-wasm
|
||||
npm install @rtimothyedwards/magic-vlsi-wasm
|
||||
```
|
||||
|
||||
If the package is private or you hit a 401, authenticate with a GitHub
|
||||
[personal access token](https://github.com/settings/tokens) that has the
|
||||
`read:packages` scope:
|
||||
|
||||
```
|
||||
//npm.pkg.github.com/:_authToken=YOUR_TOKEN
|
||||
@rtimothyedwards:registry=https://npm.pkg.github.com
|
||||
```
|
||||
|
||||
Requires Node.js 18 or newer.
|
||||
|
||||
## Quick start
|
||||
|
||||
### Default variant (no Tcl)
|
||||
|
||||
```js
|
||||
import createMagic from 'magic-vlsi-wasm';
|
||||
|
||||
|
|
@ -35,6 +62,23 @@ runCommand('gds write /work/inv');
|
|||
const gdsBytes = FS.readFile('/work/inv.gds');
|
||||
```
|
||||
|
||||
### TCL variant
|
||||
|
||||
```js
|
||||
import createMagic from 'magic-vlsi-wasm/tcl';
|
||||
|
||||
const { runCommand, FS } = await createMagic();
|
||||
|
||||
// Pure Tcl works directly
|
||||
runCommand('set x 42');
|
||||
runCommand('puts $tcl_version');
|
||||
|
||||
// Magic commands are available as the ::magic:: ensemble
|
||||
runCommand('magic::tech load scmos');
|
||||
runCommand('magic::load /work/inv');
|
||||
runCommand('magic::gds write /work/inv');
|
||||
```
|
||||
|
||||
The `scmos` technology family (`scmos`, `minimum`, `nmos`, ...) is embedded in
|
||||
the WASM binary and available out of the box — those names work without
|
||||
writing any tech file. To use a custom technology, write its `.tech` file into
|
||||
|
|
@ -122,21 +166,59 @@ If you want to rebuild the WASM module yourself, see
|
|||
The short version:
|
||||
|
||||
```bash
|
||||
bash npm/build.sh # debug build, copies magic.js + magic.wasm into npm/
|
||||
bash npm/build.sh --release # optimized
|
||||
bash npm/build.sh --test # build + run tests
|
||||
bash npm/build.sh --pack # build + produce magic-vlsi-wasm-<version>.tgz
|
||||
bash npm/build.sh # both variants, debug build
|
||||
bash npm/build.sh --variant=notcl # default variant only (faster)
|
||||
bash npm/build.sh --variant=tcl # TCL variant only
|
||||
bash npm/build.sh --release # optimized (-O2, no debug symbols)
|
||||
bash npm/build.sh --test # build + run tests
|
||||
bash npm/build.sh --pack # build + produce magic-vlsi-wasm-<version>.tgz
|
||||
```
|
||||
|
||||
You will need an activated [emsdk](https://emscripten.org/docs/getting_started/downloads.html)
|
||||
checkout (Magic pins emsdk `3.1.56` — see the comment in `npm/build.sh`).
|
||||
on your PATH. If you pass `EMSDK_DIR=/path/to/emsdk`, `build.sh` sources
|
||||
`emsdk_env.sh` for you.
|
||||
|
||||
### TCL variant: cloning the TCL source tree
|
||||
|
||||
The TCL variant links against a static WASM build of
|
||||
[tcltk/tcl](https://github.com/tcltk/tcl). `build.sh` clones the source tree
|
||||
automatically into `build-tcl-wasm/tcl` on the first run and builds it
|
||||
out-of-source in the same directory — everything stays under `build-tcl-wasm/`
|
||||
(which is gitignored), so nothing outside it is touched. Subsequent runs reuse
|
||||
the existing clone and build.
|
||||
|
||||
```bash
|
||||
# Override the TCL version or source repository
|
||||
TCL_REF=core-9-0-3 bash npm/build.sh --variant=tcl
|
||||
TCL_REPO_URL=https://github.com/tcltk/tcl.git bash npm/build.sh --variant=tcl
|
||||
```
|
||||
|
||||
CI always resolves the latest stable `core-9-0-x` tag automatically. To build
|
||||
against a specific version, set `TCL_REF` in the environment.
|
||||
|
||||
## Versioning
|
||||
|
||||
Published versions follow the scheme `{MAJOR}.{MINOR}.{PATCH}0{YYYYMMDD}+git{SHA}`,
|
||||
for example `8.3.799020261231+git01234cde`.
|
||||
|
||||
The date is embedded directly into the patch number (separated by a leading
|
||||
zero for readability). This means:
|
||||
|
||||
- Versions are orderable numerically — a later build date is always a higher
|
||||
version number within the same patch series.
|
||||
- Security or bugfix releases for `8.3.799` can be inserted as later dates
|
||||
(`8.3.799020270101`, `8.3.799020270201`, …) without bumping the patch number.
|
||||
- Users who want to lock to the `8.3.799` series and receive only those patches
|
||||
can use the range `<=8.3.799900000000` or `<8.3.8000000000000`.
|
||||
- `~8.3.799` matches all `8.3.*` versions (broader than the 799 series alone).
|
||||
|
||||
The `+git…` suffix is build metadata — it is ignored by npm for version
|
||||
comparison and range matching. It exists purely for traceability.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Headless only. There is no display driver, so commands that draw to a
|
||||
window (`view`, `findbox`, interactive macros) are no-ops.
|
||||
- WASM memory starts at 32 MB and grows as needed. Very large GDS imports
|
||||
may need `INITIAL_MEMORY` bumped (rebuild required).
|
||||
- Single-threaded. WASM modules are not thread-safe — create one instance
|
||||
per worker.
|
||||
|
||||
|
|
|
|||
169
npm/build.sh
169
npm/build.sh
|
|
@ -2,9 +2,13 @@
|
|||
# Build Magic WASM and copy artifacts into this npm/ directory.
|
||||
#
|
||||
# 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.
|
||||
# --pack Run `npm pack` after copying artifacts (and tests, if given).
|
||||
#
|
||||
|
|
@ -14,23 +18,36 @@
|
|||
# node, npm — only required for --test / --pack
|
||||
#
|
||||
# Environment:
|
||||
# EMSDK_DIR Path to an activated emsdk checkout.
|
||||
# If set, emsdk_env.sh is sourced from there.
|
||||
# If unset, emcc must already be on PATH (e.g. sourced externally).
|
||||
# EMSDK_DIR Path to an activated emsdk checkout.
|
||||
# If set, emsdk_env.sh is sourced from there.
|
||||
# If unset, emcc must already be on PATH (e.g. sourced externally).
|
||||
# TCL_REF git ref (tag/branch/SHA) of tcltk/tcl to build for the TCL
|
||||
# variant. Default: main. (CI pins the latest stable tag.)
|
||||
# TCL_REPO_URL git URL to clone tcltk/tcl from. Default: the upstream repo.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
# The TCL variant builds a static WASM Tcl from a pristine clone of tcltk/tcl.
|
||||
# Both the clone (tcl/) and the out-of-source build artifacts live entirely
|
||||
# under build-tcl-wasm/ (gitignored), so nothing outside it is ever touched.
|
||||
TCL_BUILD_DIR="${TCL_BUILD_DIR:-$REPO_ROOT/build-tcl-wasm}"
|
||||
TCL_SRC_DIR="$TCL_BUILD_DIR/tcl"
|
||||
TCL_WASM_PREFIX="$TCL_BUILD_DIR/install"
|
||||
|
||||
OPT_RELEASE=0
|
||||
OPT_TEST=0
|
||||
OPT_PACK=0
|
||||
OPT_VARIANT=both
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--release) OPT_RELEASE=1 ;;
|
||||
--test) OPT_TEST=1 ;;
|
||||
--pack) OPT_PACK=1 ;;
|
||||
--release) OPT_RELEASE=1 ;;
|
||||
--test) OPT_TEST=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 ;;
|
||||
esac
|
||||
done
|
||||
|
|
@ -76,53 +93,119 @@ sed_strip_cr() {
|
|||
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
|
||||
EXTRA_CFLAGS=" -O2"
|
||||
EXTRA_CFLAGS="-O2"
|
||||
else
|
||||
EXTRA_CFLAGS=" -g"
|
||||
EXTRA_CFLAGS="-g"
|
||||
fi
|
||||
|
||||
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
|
||||
# --- TCL fork: clone and prebuild (TCL variant only) ------------------------
|
||||
# Uses TCL_REPO_URL and TCL_REF from the environment (both have defaults).
|
||||
# The source is cloned into build-tcl-wasm/tcl on the first run and checked
|
||||
# out at the requested ref. Because this clone is private to the build dir,
|
||||
# we manage its HEAD freely — no user-supplied tree is ever mutated.
|
||||
#
|
||||
# The actual WASM build runs out-of-source in $TCL_BUILD_DIR, driven by
|
||||
# toolchains/emscripten/build-tcl-wasm.sh.
|
||||
ensure_tcl_built() {
|
||||
: "${TCL_REPO_URL:=https://github.com/tcltk/tcl.git}"
|
||||
: "${TCL_REF:=main}"
|
||||
|
||||
cat toolchains/emscripten/defs.mak >> defs.mak
|
||||
if [ ! -d "$TCL_SRC_DIR/.git" ]; then
|
||||
echo "=== cloning $TCL_REPO_URL into $TCL_SRC_DIR ==="
|
||||
mkdir -p "$TCL_BUILD_DIR"
|
||||
git -c core.autocrlf=false clone "$TCL_REPO_URL" "$TCL_SRC_DIR"
|
||||
fi
|
||||
|
||||
# --- build -------------------------------------------------------------------
|
||||
emmake make depend
|
||||
emmake make -j"$(ncpu)" modules libs
|
||||
emmake make techs
|
||||
emmake make mains
|
||||
( cd "$TCL_SRC_DIR"
|
||||
current_sha=$(git rev-parse HEAD 2>/dev/null || echo "")
|
||||
if [ "$current_sha" != "$TCL_REF" ]; then
|
||||
git fetch --quiet origin
|
||||
git checkout --quiet --detach "$TCL_REF"
|
||||
fi
|
||||
echo "Using TCL at $(git rev-parse HEAD) ($TCL_REPO_URL)"
|
||||
)
|
||||
|
||||
# --- copy artifacts ----------------------------------------------------------
|
||||
cp magic/magic.js "$SCRIPT_DIR/"
|
||||
cp magic/magic.wasm "$SCRIPT_DIR/"
|
||||
echo "Copied magic.js and magic.wasm to npm/"
|
||||
# Build TCL for WASM if it hasn't been built yet. The presence of
|
||||
# tclConfig.sh in the install prefix is the canonical "TCL is built" marker.
|
||||
if [ ! -f "$TCL_WASM_PREFIX/lib/tclConfig.sh" ]; then
|
||||
echo "=== building TCL for WASM into $TCL_BUILD_DIR (one-time) ==="
|
||||
bash "$REPO_ROOT/toolchains/emscripten/build-tcl-wasm.sh" \
|
||||
--src="$TCL_SRC_DIR" --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 -----------------------------------------------------------
|
||||
# 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
|
||||
cd "$SCRIPT_DIR"
|
||||
npm run test
|
||||
npm run test:tcl
|
||||
fi
|
||||
|
||||
# --- optional pack -----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
// all-tcl.js — Run the full test suite against the TCL variant.
|
||||
//
|
||||
// Covers all non-TCL tests (extract, gds, drc, cif) run through the
|
||||
// Tcl interpreter, plus a PCell generation test that is TCL-only.
|
||||
//
|
||||
// Usage: node examples/all-tcl.js
|
||||
import { run as runExtract } from './extract-tcl.js';
|
||||
import { run as runGds } from './gds-tcl.js';
|
||||
import { run as runDrc } from './drc-tcl.js';
|
||||
import { run as runCif } from './cif-tcl.js';
|
||||
import { run as runPcell } from './pcell.js';
|
||||
|
||||
const PAD = 9;
|
||||
|
||||
async function test(name, fn) {
|
||||
process.stdout.write(` ${name.padEnd(PAD)} `);
|
||||
try {
|
||||
const result = await fn();
|
||||
console.log(`PASS ${formatResult(name, result)}`);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log(`FAIL ${e.message ?? e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function formatResult(name, r) {
|
||||
if (!r) return '';
|
||||
switch (name) {
|
||||
case 'extract': return [r.ext, r.spice].filter(Boolean).map(p => p.split(/[\\/]/).pop()).join(', ');
|
||||
case 'gds': return `${r.outPath.split(/[\\/]/).pop()} (${r.bytes} B)`;
|
||||
case 'cif': return `${r.outPath.split(/[\\/]/).pop()} (${r.bytes} B)`;
|
||||
case 'drc': return r.violations != null ? `${r.violations} violation${r.violations !== 1 ? 's' : ''}` : '';
|
||||
case 'pcell': return Object.entries(r).map(([n, { bytes }]) => `${n}.gds (${bytes} B)`).join(', ');
|
||||
default: return '';
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\nMagic WASM TCL variant — test suite\n');
|
||||
|
||||
const suite = [
|
||||
['extract', runExtract],
|
||||
['gds', runGds],
|
||||
['drc', runDrc],
|
||||
['cif', runCif],
|
||||
['pcell', runPcell],
|
||||
];
|
||||
|
||||
const passed = [];
|
||||
for (const [name, fn] of suite) {
|
||||
passed.push(await test(name, fn));
|
||||
}
|
||||
|
||||
const ok = passed.filter(Boolean).length;
|
||||
console.log(`\n${ok}/${suite.length} passed`);
|
||||
process.exit(ok === suite.length ? 0 : 1);
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// cif-tcl.js — CIF export via the TCL variant.
|
||||
import { createMagic, vfsRead, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers-tcl.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {}) {
|
||||
const { magic } = await createMagic();
|
||||
const { FS } = magic;
|
||||
const { tech: techName, cell } = loadCell(FS, tech, magFile);
|
||||
|
||||
magic.runScript(loadScript('cif-tcl.tcl', techName, cell));
|
||||
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
const data = vfsRead(FS, `/work/${cell}.cif`);
|
||||
if (!data) throw new Error(`CIF export failed: /work/${cell}.cif not created`);
|
||||
|
||||
const outPath = resolve(outputDir, `${cell}.cif`);
|
||||
writeFileSync(outPath, data);
|
||||
return { outPath, bytes: data.length };
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { outPath, bytes } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
console.log(`\ncif: ${outPath} (${bytes} bytes)`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
magic::tech load __TECH__
|
||||
magic::load /work/__CELL__
|
||||
magic::cif write /work/__CELL__
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// drc-tcl.js — DRC check via the TCL variant.
|
||||
import { createMagic, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG } from './helpers-tcl.js';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH } = {}) {
|
||||
const output = [];
|
||||
const { magic } = await createMagic({
|
||||
onPrint: msg => { output.push(msg); console.log('[magic-tcl]', msg); },
|
||||
onPrintErr: msg => { output.push(msg); console.error('[magic-tcl]', msg); },
|
||||
});
|
||||
const { FS } = magic;
|
||||
const { tech: techName, cell } = loadCell(FS, tech, magFile);
|
||||
|
||||
magic.runScript(loadScript('drc-tcl.tcl', techName, cell));
|
||||
|
||||
const summary = output.find(l => /Total DRC errors/i.test(l));
|
||||
const match = summary?.match(/(\d+)/);
|
||||
const violations = match ? parseInt(match[1], 10) : null;
|
||||
|
||||
return { violations, output };
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { violations } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
console.log(`\nDRC violations: ${violations ?? '(count not found in output)'}`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
magic::tech load __TECH__
|
||||
magic::load /work/__CELL__
|
||||
magic::drc catchup
|
||||
magic::drc count total
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// extract-tcl.js — RC extraction via the TCL variant.
|
||||
import { createMagic, vfsWrite, vfsRead, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers-tcl.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {}) {
|
||||
const { magic } = await createMagic();
|
||||
const { FS } = magic;
|
||||
const { tech: techName, cell } = loadCell(FS, tech, magFile);
|
||||
|
||||
magic.runScript(loadScript('extract-tcl.tcl', techName, cell));
|
||||
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
const extData = vfsRead(FS, `/work/${cell}.ext`);
|
||||
if (!extData) throw new Error(`Extraction failed: /work/${cell}.ext not created`);
|
||||
writeFileSync(resolve(outputDir, `${cell}.ext`), extData);
|
||||
|
||||
const resExtData = vfsRead(FS, `/work/${cell}.res.ext`);
|
||||
if (resExtData) writeFileSync(resolve(outputDir, `${cell}.res.ext`), resExtData);
|
||||
|
||||
const spiceData = vfsRead(FS, `/work/${cell}.spice`) ?? vfsRead(FS, `/work/${cell}.spc`);
|
||||
const spiceExt = vfsRead(FS, `/work/${cell}.spice`) ? 'spice' : 'spc';
|
||||
if (spiceData) writeFileSync(resolve(outputDir, `${cell}.${spiceExt}`), spiceData);
|
||||
|
||||
return {
|
||||
ext: resolve(outputDir, `${cell}.ext`),
|
||||
spice: spiceData ? resolve(outputDir, `${cell}.${spiceExt}`) : null,
|
||||
};
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { ext, spice } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
console.log(`\next: ${ext}`);
|
||||
if (spice) console.log(`spice: ${spice}`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
magic::tech load __TECH__
|
||||
magic::load /work/__CELL__
|
||||
magic::extract path /work
|
||||
magic::extract do resistance
|
||||
magic::extract all
|
||||
magic::select top cell
|
||||
magic::extresist all
|
||||
magic::ext2spice format ngspice
|
||||
magic::ext2spice extresist on
|
||||
magic::ext2spice cthresh 0
|
||||
magic::ext2spice /work/__CELL__
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// gds-tcl.js — GDS export via the TCL variant.
|
||||
import { createMagic, vfsRead, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers-tcl.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {}) {
|
||||
const { magic } = await createMagic();
|
||||
const { FS } = magic;
|
||||
const { tech: techName, cell } = loadCell(FS, tech, magFile);
|
||||
|
||||
magic.runScript(loadScript('gds-tcl.tcl', techName, cell));
|
||||
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
const data = vfsRead(FS, `/work/${cell}.gds`);
|
||||
if (!data) throw new Error(`GDS export failed: /work/${cell}.gds not created`);
|
||||
|
||||
const outPath = resolve(outputDir, `${cell}.gds`);
|
||||
writeFileSync(outPath, data);
|
||||
return { outPath, bytes: data.length };
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { outPath, bytes } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
console.log(`\ngds: ${outPath} (${bytes} bytes)`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
magic::tech load __TECH__
|
||||
magic::load /work/__CELL__
|
||||
magic::gds write /work/__CELL__
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// Shared utilities for Magic WASM TCL-variant examples.
|
||||
//
|
||||
// Loads the TCL-enabled WASM variant. Scripts use magic:: prefixed commands;
|
||||
// runScript() evaluates them through the Tcl interpreter via Tcl_EvalFile.
|
||||
import createMagicModule from '../tcl/magic.js';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, resolve, basename } from 'node:path';
|
||||
|
||||
export const EXAMPLES_DIR = dirname(fileURLToPath(import.meta.url));
|
||||
export const wasmBinary = readFileSync(resolve(EXAMPLES_DIR, '../tcl/magic.wasm'));
|
||||
export const DEFAULT_TECH = 'scmos';
|
||||
export const DEFAULT_MAG = resolve(EXAMPLES_DIR, 'min.mag');
|
||||
export const DEFAULT_OUT = resolve(EXAMPLES_DIR, 'output-tcl');
|
||||
|
||||
export { basename };
|
||||
|
||||
export function vfsWrite(FS, vfsPath, source) {
|
||||
const dir = vfsPath.substring(0, vfsPath.lastIndexOf('/'));
|
||||
if (dir) FS.mkdirTree(dir);
|
||||
FS.writeFile(vfsPath, typeof source === 'string' ? readFileSync(source) : source);
|
||||
}
|
||||
|
||||
export function vfsRead(FS, vfsPath) {
|
||||
try { return Buffer.from(FS.readFile(vfsPath)); } catch { return null; }
|
||||
}
|
||||
|
||||
export class MagicWasm {
|
||||
constructor(mod) {
|
||||
this._init = mod.cwrap('magic_wasm_init', 'number', []);
|
||||
this._run = mod.cwrap('magic_wasm_run_command', 'number', ['string']);
|
||||
this._sourceFile = mod.cwrap('magic_wasm_source_file', 'number', ['string']);
|
||||
this.FS = mod.FS;
|
||||
}
|
||||
init() {
|
||||
const rc = this._init();
|
||||
if (rc !== 0) throw new Error(`magic_wasm_init failed with code ${rc}`);
|
||||
}
|
||||
// Evaluate a Tcl script (with magic:: prefixed commands) through the
|
||||
// Tcl interpreter. Writes the script to VFS then calls magic_wasm_source_file,
|
||||
// which in TCL mode uses Tcl_EvalFile.
|
||||
runScript(text) {
|
||||
const path = '/tmp/_magic_script.tcl';
|
||||
this.FS.mkdirTree('/tmp');
|
||||
this.FS.writeFile(path, text);
|
||||
this._sourceFile(path);
|
||||
}
|
||||
// Evaluate a Tcl expression directly (needed for proc definitions in PCell).
|
||||
runTcl(text) {
|
||||
this._run(text);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createMagic({ onPrint, onPrintErr } = {}) {
|
||||
const lines = [];
|
||||
const mod = await createMagicModule({
|
||||
wasmBinary,
|
||||
print: onPrint ?? (msg => { lines.push(msg); console.log('[magic-tcl]', msg); }),
|
||||
printErr: onPrintErr ?? (msg => { lines.push(msg); console.error('[magic-tcl]', msg); }),
|
||||
});
|
||||
const magic = new MagicWasm(mod);
|
||||
magic.init();
|
||||
return { magic, lines };
|
||||
}
|
||||
|
||||
export function loadCell(FS, tech, magFile) {
|
||||
const cell = basename(magFile, '.mag');
|
||||
vfsWrite(FS, `/work/${cell}.mag`, magFile);
|
||||
return { tech, cell };
|
||||
}
|
||||
|
||||
export function loadScript(name, tech, cell) {
|
||||
return readFileSync(resolve(EXAMPLES_DIR, name), 'utf8')
|
||||
.replaceAll('__TECH__', tech)
|
||||
.replaceAll('__CELL__', cell);
|
||||
}
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
// 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 { fileURLToPath } from 'node:url';
|
||||
import { dirname, resolve, basename } from 'node:path';
|
||||
|
||||
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_MAG = resolve(EXAMPLES_DIR, 'min.mag');
|
||||
export const DEFAULT_OUT = resolve(EXAMPLES_DIR, 'output');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
// pcell.js — PCell generation test (TCL variant only).
|
||||
//
|
||||
// Defines a Tcl proc as a PCell, instantiates it with two different sizes,
|
||||
// and verifies that both GDS outputs are non-empty.
|
||||
//
|
||||
// Usage: node examples/pcell.js
|
||||
import { createMagic, vfsRead, loadScript, DEFAULT_TECH, DEFAULT_OUT } from './helpers-tcl.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export async function run({ tech = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {}) {
|
||||
const { magic } = await createMagic();
|
||||
const { FS } = magic;
|
||||
|
||||
FS.mkdirTree('/work');
|
||||
magic.runTcl(loadScript('pcell.tcl', tech, ''));
|
||||
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
const cells = ['pcell_4x8', 'pcell_8x4'];
|
||||
const results = {};
|
||||
for (const name of cells) {
|
||||
const data = vfsRead(FS, `/work/${name}.gds`);
|
||||
if (!data || data.length === 0)
|
||||
throw new Error(`PCell GDS output missing or empty: /work/${name}.gds`);
|
||||
const outPath = resolve(outputDir, `${name}.gds`);
|
||||
writeFileSync(outPath, data);
|
||||
results[name] = { outPath, bytes: data.length };
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const results = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
for (const [name, { outPath, bytes }] of Object.entries(results))
|
||||
console.log(` ${name}.gds: ${outPath} (${bytes} bytes)`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
magic::tech load __TECH__
|
||||
|
||||
proc make_rect {name width height} {
|
||||
magic::load $name
|
||||
magic::box 0 0 $width $height
|
||||
magic::paint m1
|
||||
magic::save /work/$name
|
||||
magic::gds write /work/$name
|
||||
}
|
||||
|
||||
make_rect pcell_4x8 4 8
|
||||
make_rect pcell_8x4 8 4
|
||||
|
|
@ -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');
|
||||
20
npm/index.js
20
npm/index.js
|
|
@ -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 = {}) {
|
||||
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, []);
|
||||
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 };
|
||||
return { init, runCommand, sourceFile, update, FS: module.FS, variant: 'notcl' };
|
||||
}
|
||||
|
||||
export { createMagic };
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -1,34 +1,38 @@
|
|||
{
|
||||
"name": "magic-vlsi-wasm",
|
||||
"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",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./index.d.ts"
|
||||
}
|
||||
".": { "import": "./index.js", "types": "./index.d.ts" },
|
||||
"./tcl": { "import": "./tcl.js", "types": "./index.d.ts" },
|
||||
"./notcl": { "import": "./notcl.js", "types": "./index.d.ts" }
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"magic.js",
|
||||
"magic.wasm",
|
||||
"tcl.js",
|
||||
"notcl.js",
|
||||
"tcl/magic.js",
|
||||
"tcl/magic.wasm",
|
||||
"notcl/magic.js",
|
||||
"notcl/magic.wasm",
|
||||
"examples/",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"example": "node examples/extract.js",
|
||||
"test": "node examples/all.js",
|
||||
"test:gds": "node examples/gds.js",
|
||||
"test:drc": "node examples/drc.js",
|
||||
"test:cif": "node examples/cif.js"
|
||||
"example": "node examples/extract.js",
|
||||
"test": "node examples/all.js",
|
||||
"test:tcl": "node examples/all-tcl.js",
|
||||
"test:gds": "node examples/gds.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",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// tcltk/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;
|
||||
|
|
@ -6276,6 +6276,7 @@ magic_with_tk_libraries=""
|
|||
|
||||
usingOGL=1
|
||||
usingTcl=1
|
||||
usingTk=1
|
||||
usingOA=0
|
||||
usingCairo=1
|
||||
usingPython3=1
|
||||
|
|
@ -7254,10 +7255,12 @@ if test "${with_tcl+set}" = set; then :
|
|||
magic_with_tcl=$withval
|
||||
if test "$withval" = "no" -o "$withval" = "NO"; then
|
||||
usingTcl=
|
||||
usingTk=
|
||||
elif test $usingScheme ; then
|
||||
echo Attempt to enable both Tcl and Scheme interpreters.
|
||||
echo Disabling Tcl, and using Scheme instead.
|
||||
usingTcl=
|
||||
usingTk=
|
||||
fi
|
||||
|
||||
fi
|
||||
|
|
@ -7268,6 +7271,9 @@ fi
|
|||
# Check whether --with-tk was given.
|
||||
if test "${with_tk+set}" = set; then :
|
||||
withval=$with_tk; magic_with_tk=$withval
|
||||
if test "$withval" = "no" -o "$withval" = "NO"; then
|
||||
usingTk=
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
|
@ -7397,7 +7403,7 @@ fi
|
|||
# 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_n "checking for tkConfig.sh... " >&6; }
|
||||
|
|
@ -7477,8 +7483,8 @@ $as_echo "${tk_config_sh}" >&6; }
|
|||
|
||||
if test "x$tk_config_sh" = "x" ; then
|
||||
echo "can't find Tk configuration script \"tkConfig.sh\""
|
||||
echo "Reverting to non-Tcl compilation"
|
||||
usingTcl=
|
||||
echo "Reverting to non-Tk compilation"
|
||||
usingTk=
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
@ -7488,7 +7494,9 @@ fi
|
|||
|
||||
if test $usingTcl ; then
|
||||
. $tcl_config_sh
|
||||
. $tk_config_sh
|
||||
if test $usingTk ; then
|
||||
. $tk_config_sh
|
||||
fi
|
||||
|
||||
# Should probably trust the config file contents, but this configure
|
||||
# file checks the Tcl and Tk include and lib directories. Since
|
||||
|
|
@ -7504,21 +7512,24 @@ if test $usingTcl ; then
|
|||
|
||||
tmpstr=${TCL_LIB_SPEC#*-L}
|
||||
TCL_LIB_DIR=${tmpstr% -l*}
|
||||
tmpstr=${TK_LIB_SPEC#*-L}
|
||||
TK_LIB_DIR=${tmpstr% -l*}
|
||||
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
|
||||
:
|
||||
elif test "$TCL_VERSION" = "7.5" -a "$TK_VERSION" = "4.1" ; then
|
||||
:
|
||||
elif test "$TCL_VERSION" = "$TK_VERSION" ; then
|
||||
:
|
||||
else
|
||||
echo "Mismatched Tcl/Tk versions ($TCL_VERSION != $TK_VERSION)"
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
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" = "$TK_VERSION" ; then
|
||||
:
|
||||
else
|
||||
echo "Mismatched Tcl/Tk versions ($TCL_VERSION != $TK_VERSION)"
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
usingTk=
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
@ -7551,14 +7562,14 @@ if test $usingTcl ; then
|
|||
fi
|
||||
fi
|
||||
|
||||
if test $usingTcl ; then
|
||||
if test $usingTk ; then
|
||||
if test "x${magic_with_tk_includes}" != "x" ; then
|
||||
if test -r "${magic_with_tk_includes}/tk.h" ; then
|
||||
TK_INC_DIR=${magic_with_tk_includes}
|
||||
else
|
||||
echo "Can't find tk.h in \"${magic_with_tk_includes}\""
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
echo "Reverting to non-Tk compile"
|
||||
usingTk=
|
||||
fi
|
||||
else
|
||||
for dir in \
|
||||
|
|
@ -7575,8 +7586,8 @@ if test $usingTcl ; then
|
|||
done
|
||||
if test "x${TK_INC_DIR}" = "x" ; then
|
||||
echo "Can't find tk.h header file"
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
echo "Reverting to non-Tk compile"
|
||||
usingTk=
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
@ -7599,14 +7610,14 @@ if test $usingTcl ; then
|
|||
if test "x${TCL_LIB_SPEC}" = "x" ; then
|
||||
TCL_LIB_SPEC="-l${TCL_LIB_NAME}"
|
||||
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}"
|
||||
fi
|
||||
|
||||
# Find the version of "wish" that corresponds to TCL_EXEC_PREFIX
|
||||
# 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_n "checking for wish executable... " >&6; }
|
||||
for dir in \
|
||||
|
|
@ -7638,7 +7649,7 @@ $as_echo "no" >&6; }
|
|||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${WISH_EXE}" >&5
|
||||
$as_echo "${WISH_EXE}" >&6; }
|
||||
fi
|
||||
else
|
||||
elif test $usingTk ; then
|
||||
WISH_EXE=${magic_with_wish_binary}
|
||||
fi
|
||||
|
||||
|
|
@ -7731,7 +7742,7 @@ $as_echo "${TCLSH_EXE}" >&6; }
|
|||
fi
|
||||
fi
|
||||
|
||||
if test $usingTcl ; then
|
||||
if test $usingTk ; then
|
||||
if test "x${magic_with_tk_libraries}" != "x" ; then
|
||||
for libname in \
|
||||
"${magic_with_tk_libraries}/${TCL_LIB_FILE}" \
|
||||
|
|
@ -7746,8 +7757,8 @@ if test $usingTcl ; then
|
|||
done
|
||||
if test "x${TK_LIB_DIR}" = "x" ; then
|
||||
echo "Can't find tk library in \"${magic_with_tk_libraries}\""
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
echo "Reverting to non-Tk compile"
|
||||
usingTk=
|
||||
fi
|
||||
else
|
||||
for libname in \
|
||||
|
|
@ -7762,8 +7773,8 @@ if test $usingTcl ; then
|
|||
done
|
||||
if test "x${TK_LIB_DIR}" = "x" ; then
|
||||
echo "Can't find tk library"
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
echo "Reverting to non-Tk compile"
|
||||
usingTk=
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
@ -8638,7 +8649,12 @@ if test $usingTcl ; then
|
|||
|
||||
extra_libs="$extra_libs \${MAGICDIR}/tcltk/libtcltk.o"
|
||||
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
|
||||
modules="$modules lisp"
|
||||
unused="$unused tcltk"
|
||||
|
|
@ -8681,7 +8697,9 @@ if test $usingTcl ; then
|
|||
gr_libs="$gr_libs -lX11"
|
||||
fi
|
||||
fi
|
||||
gr_srcs="$gr_srcs \${TKCOMMON_SRCS}"
|
||||
if test $usingTk ; then
|
||||
gr_srcs="$gr_srcs \${TKCOMMON_SRCS}"
|
||||
fi
|
||||
else
|
||||
if test $usingX11 ; then
|
||||
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
|
||||
INC_SPECS="${INC_SPECS} -I${TK_INC_DIR}"
|
||||
fi
|
||||
if test "${TK_LIB_DIR}" = "/usr/lib" -o \
|
||||
"${TK_LIB_DIR}" = "/usr/lib64" ; then
|
||||
LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} ${TK_LIB_SPEC}"
|
||||
LIB_SPECS="${LIB_SPECS} ${TK_STUB_LIB_SPEC}"
|
||||
else
|
||||
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}"
|
||||
if test $usingTk ; then
|
||||
if test "${TK_INC_DIR}" != "/usr/include" ; then
|
||||
INC_SPECS="${INC_SPECS} -I${TK_INC_DIR}"
|
||||
fi
|
||||
if test "${TK_LIB_DIR}" = "/usr/lib" -o \
|
||||
"${TK_LIB_DIR}" = "/usr/lib64" ; then
|
||||
LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} ${TK_LIB_SPEC}"
|
||||
LIB_SPECS="${LIB_SPECS} ${TK_STUB_LIB_SPEC}"
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -344,6 +344,7 @@ dnl disabled with --with-opengl=no
|
|||
|
||||
usingOGL=1
|
||||
usingTcl=1
|
||||
usingTk=1
|
||||
usingOA=0
|
||||
usingCairo=1
|
||||
usingPython3=1
|
||||
|
|
@ -442,10 +443,12 @@ AC_ARG_WITH(tcl,
|
|||
magic_with_tcl=$withval
|
||||
if test "$withval" = "no" -o "$withval" = "NO"; then
|
||||
usingTcl=
|
||||
usingTk=
|
||||
elif test $usingScheme ; then
|
||||
echo Attempt to enable both Tcl and Scheme interpreters.
|
||||
echo Disabling Tcl, and using Scheme instead.
|
||||
usingTcl=
|
||||
usingTk=
|
||||
fi
|
||||
], )
|
||||
|
||||
|
|
@ -455,10 +458,17 @@ dnl and don't set the usingTcl variable.
|
|||
dnl
|
||||
dnl This has been broken up into a number of sections, each of which
|
||||
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 ----------------------------------------------------------------
|
||||
|
||||
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],
|
||||
magic_with_tcl_includes=$withval)
|
||||
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"
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
if test $usingTcl ; then
|
||||
if test $usingTk ; then
|
||||
|
||||
AC_MSG_CHECKING([for tkConfig.sh])
|
||||
tk_config_sh=""
|
||||
|
|
@ -642,8 +652,8 @@ if test $usingTcl ; then
|
|||
|
||||
if test "x$tk_config_sh" = "x" ; then
|
||||
echo "can't find Tk configuration script \"tkConfig.sh\""
|
||||
echo "Reverting to non-Tcl compilation"
|
||||
usingTcl=
|
||||
echo "Reverting to non-Tk compilation"
|
||||
usingTk=
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
@ -653,7 +663,9 @@ fi
|
|||
|
||||
if test $usingTcl ; then
|
||||
. $tcl_config_sh
|
||||
. $tk_config_sh
|
||||
if test $usingTk ; then
|
||||
. $tk_config_sh
|
||||
fi
|
||||
|
||||
# Should probably trust the config file contents, but this configure
|
||||
# file checks the Tcl and Tk include and lib directories. Since
|
||||
|
|
@ -669,21 +681,24 @@ if test $usingTcl ; then
|
|||
|
||||
tmpstr=${TCL_LIB_SPEC#*-L}
|
||||
TCL_LIB_DIR=${tmpstr% -l*}
|
||||
tmpstr=${TK_LIB_SPEC#*-L}
|
||||
TK_LIB_DIR=${tmpstr% -l*}
|
||||
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
|
||||
:
|
||||
elif test "$TCL_VERSION" = "7.5" -a "$TK_VERSION" = "4.1" ; then
|
||||
:
|
||||
elif test "$TCL_VERSION" = "$TK_VERSION" ; then
|
||||
:
|
||||
else
|
||||
echo "Mismatched Tcl/Tk versions ($TCL_VERSION != $TK_VERSION)"
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
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" = "$TK_VERSION" ; then
|
||||
:
|
||||
else
|
||||
echo "Mismatched Tcl/Tk versions ($TCL_VERSION != $TK_VERSION)"
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
usingTk=
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
@ -716,14 +731,14 @@ if test $usingTcl ; then
|
|||
fi
|
||||
fi
|
||||
|
||||
if test $usingTcl ; then
|
||||
if test $usingTk ; then
|
||||
if test "x${magic_with_tk_includes}" != "x" ; then
|
||||
if test -r "${magic_with_tk_includes}/tk.h" ; then
|
||||
TK_INC_DIR=${magic_with_tk_includes}
|
||||
else
|
||||
echo "Can't find tk.h in \"${magic_with_tk_includes}\""
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
echo "Reverting to non-Tk compile"
|
||||
usingTk=
|
||||
fi
|
||||
else
|
||||
for dir in \
|
||||
|
|
@ -740,8 +755,8 @@ if test $usingTcl ; then
|
|||
done
|
||||
if test "x${TK_INC_DIR}" = "x" ; then
|
||||
echo "Can't find tk.h header file"
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
echo "Reverting to non-Tk compile"
|
||||
usingTk=
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
@ -764,14 +779,14 @@ if test $usingTcl ; then
|
|||
if test "x${TCL_LIB_SPEC}" = "x" ; then
|
||||
TCL_LIB_SPEC="-l${TCL_LIB_NAME}"
|
||||
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}"
|
||||
fi
|
||||
|
||||
# Find the version of "wish" that corresponds to TCL_EXEC_PREFIX
|
||||
# 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])
|
||||
for dir in \
|
||||
${TK_EXEC_PREFIX}/bin \
|
||||
|
|
@ -800,7 +815,7 @@ if test $usingTcl ; then
|
|||
else
|
||||
AC_MSG_RESULT([${WISH_EXE}])
|
||||
fi
|
||||
else
|
||||
elif test $usingTk ; then
|
||||
WISH_EXE=${magic_with_wish_binary}
|
||||
fi
|
||||
|
||||
|
|
@ -890,7 +905,7 @@ if test $usingTcl ; then
|
|||
fi
|
||||
fi
|
||||
|
||||
if test $usingTcl ; then
|
||||
if test $usingTk ; then
|
||||
if test "x${magic_with_tk_libraries}" != "x" ; then
|
||||
for libname in \
|
||||
"${magic_with_tk_libraries}/${TCL_LIB_FILE}" \
|
||||
|
|
@ -905,8 +920,8 @@ if test $usingTcl ; then
|
|||
done
|
||||
if test "x${TK_LIB_DIR}" = "x" ; then
|
||||
echo "Can't find tk library in \"${magic_with_tk_libraries}\""
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
echo "Reverting to non-Tk compile"
|
||||
usingTk=
|
||||
fi
|
||||
else
|
||||
for libname in \
|
||||
|
|
@ -921,8 +936,8 @@ if test $usingTcl ; then
|
|||
done
|
||||
if test "x${TK_LIB_DIR}" = "x" ; then
|
||||
echo "Can't find tk library"
|
||||
echo "Reverting to non-Tcl compile"
|
||||
usingTcl=
|
||||
echo "Reverting to non-Tk compile"
|
||||
usingTk=
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
@ -1356,7 +1371,12 @@ if test $usingTcl ; then
|
|||
AC_DEFINE(MAGIC_WRAPPER)
|
||||
extra_libs="$extra_libs \${MAGICDIR}/tcltk/libtcltk.o"
|
||||
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
|
||||
modules="$modules lisp"
|
||||
unused="$unused tcltk"
|
||||
|
|
@ -1401,7 +1421,9 @@ if test $usingTcl ; then
|
|||
gr_libs="$gr_libs -lX11"
|
||||
fi
|
||||
fi
|
||||
gr_srcs="$gr_srcs \${TKCOMMON_SRCS}"
|
||||
if test $usingTk ; then
|
||||
gr_srcs="$gr_srcs \${TKCOMMON_SRCS}"
|
||||
fi
|
||||
else
|
||||
if test $usingX11 ; then
|
||||
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
|
||||
INC_SPECS="${INC_SPECS} -I${TK_INC_DIR}"
|
||||
fi
|
||||
if test "${TK_LIB_DIR}" = "/usr/lib" -o \
|
||||
"${TK_LIB_DIR}" = "/usr/lib64" ; then
|
||||
LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} ${TK_LIB_SPEC}"
|
||||
LIB_SPECS="${LIB_SPECS} ${TK_STUB_LIB_SPEC}"
|
||||
else
|
||||
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}"
|
||||
if test $usingTk ; then
|
||||
if test "${TK_INC_DIR}" != "/usr/include" ; then
|
||||
INC_SPECS="${INC_SPECS} -I${TK_INC_DIR}"
|
||||
fi
|
||||
if test "${TK_LIB_DIR}" = "/usr/lib" -o \
|
||||
"${TK_LIB_DIR}" = "/usr/lib64" ; then
|
||||
LIB_SPECS_NOSTUB="${LIB_SPECS_NOSTUB} ${TK_LIB_SPEC}"
|
||||
LIB_SPECS="${LIB_SPECS} ${TK_STUB_LIB_SPEC}"
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,16 @@ BIN_FILES = \
|
|||
|
||||
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}
|
||||
${RM} $(DESTDIR)${INSTALL_TCLDIR}/magicexec
|
||||
${CP} magicexec $(DESTDIR)${INSTALL_TCLDIR}/magicexec
|
||||
|
|
|
|||
|
|
@ -178,8 +178,10 @@ TagCallback(interp, tkpath, argc, argv)
|
|||
windCheckOnlyWindow(&w, DBWclientID);
|
||||
if (w != NULL && !(w->w_flags & WIND_OFFSCREEN))
|
||||
{
|
||||
#ifndef MAGIC_NO_TK
|
||||
Tk_Window tkwind = (Tk_Window) w->w_grdata;
|
||||
if (tkwind != NULL) tkpath = Tk_PathName(tkwind);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (tkpath == NULL)
|
||||
|
|
@ -654,6 +656,37 @@ process_rlimit_startup_check(void)
|
|||
#endif /* HAVE_GETRLIMIT */
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------*/
|
||||
/* Register magic:: commands with the Tcl interpreter. */
|
||||
/* Called after Magic's C subsystems are fully */
|
||||
/* initialized (i.e. after magicMainInit returns 0) */
|
||||
/* so that WindNextClient / WindGetCommandTable return */
|
||||
/* populated tables. */
|
||||
/*--------------------------------------------------------------*/
|
||||
|
||||
void
|
||||
TclmagicRegisterCommands(Tcl_Interp *interp)
|
||||
{
|
||||
WindClient client;
|
||||
int n;
|
||||
char keyword[100];
|
||||
char *kwptr = keyword + 7;
|
||||
const char * const *commandTable;
|
||||
|
||||
sprintf(keyword, "magic::");
|
||||
client = (WindClient)NULL;
|
||||
while ((client = WindNextClient(client)) != NULL)
|
||||
{
|
||||
commandTable = WindGetCommandTable(client);
|
||||
for (n = 0; commandTable[n] != NULL; n++)
|
||||
{
|
||||
sscanf(commandTable[n], "%92s", kwptr);
|
||||
Tcl_CreateCommand(interp, keyword, (Tcl_CmdProc *)_tcl_dispatch,
|
||||
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------*/
|
||||
/* Main startup procedure */
|
||||
/*------------------------------------------------------*/
|
||||
|
|
@ -742,8 +775,10 @@ _magic_initialize(ClientData clientData,
|
|||
/* (See graphics/grTkCommon.c) */
|
||||
/* (Unless "-dnull" option has been given) */
|
||||
|
||||
#ifndef MAGIC_NO_TK
|
||||
if (strcmp(MainDisplayType, "NULL"))
|
||||
RegisterTkCommands(interp);
|
||||
#endif
|
||||
|
||||
/* Set up the console so that its menu option File->Exit */
|
||||
/* calls magic's exit routine first. This should not be */
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@
|
|||
#ifdef MAGIC_WRAPPER
|
||||
|
||||
#include <tcl.h>
|
||||
#ifndef MAGIC_NO_TK
|
||||
#include <tk.h>
|
||||
#endif
|
||||
|
||||
/* Externally-defined global variables */
|
||||
|
||||
|
|
@ -30,5 +32,7 @@ extern void MakeWindowCommand();
|
|||
|
||||
extern const char *Tclmagic_InitStubsVersion;
|
||||
|
||||
extern void TclmagicRegisterCommands(Tcl_Interp *interp);
|
||||
|
||||
#endif /* MAGIC_WRAPPER */
|
||||
#endif /* _MAGIC__TCLTK__TCLMAGIC_H */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build tcltk/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 tcltk/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 tcltk/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
|
||||
|
||||
# emconfigure writes tclConfig.sh into the build dir. If it is missing, the
|
||||
# $(. tclConfig.sh) assignments below silently produce empty strings (bash does
|
||||
# not propagate errors from command-substitution assignments through set -e),
|
||||
# so we catch the missing file here before the cryptic "no rule to make target
|
||||
# ''" error from emmake.
|
||||
if [ ! -f "$OUT/tclConfig.sh" ]; then
|
||||
echo "Error: $OUT/tclConfig.sh not found — emconfigure may have failed." >&2
|
||||
exit 1
|
||||
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/^/ /'
|
||||
|
|
@ -24,11 +24,12 @@ TOP_EXTRA_LIBS += \
|
|||
-sWASM=1 \
|
||||
-sMODULARIZE=1 \
|
||||
-sEXPORT_ES6=1 \
|
||||
-sUSE_ZLIB=1 \
|
||||
-sEXPORTED_FUNCTIONS=_magic_wasm_init,_magic_wasm_run_command,_magic_wasm_source_file,_magic_wasm_update \
|
||||
-sEXPORTED_RUNTIME_METHODS=cwrap,ccall,FS,setValue,getValue \
|
||||
-sALLOW_MEMORY_GROWTH=1 \
|
||||
-sINITIAL_MEMORY=33554432 \
|
||||
-sSTACK_SIZE=5242880 \
|
||||
-sINITIAL_MEMORY=67108864 \
|
||||
-Wl,-z,stack-size=10485760 \
|
||||
-sASSERTIONS=1 \
|
||||
-sENVIRONMENT=node,web,worker \
|
||||
-sFORCE_FILESYSTEM=1 \
|
||||
|
|
|
|||
Loading…
Reference in New Issue