Last active
October 13, 2025 06:18
-
-
Save rezamarzban/b6015fc55db4abcc2761288b7091a5d1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -e | |
| # ------------------------------------------------------------------- | |
| # Check and install prerequisites | |
| # ------------------------------------------------------------------- | |
| PKGS=(git curl tar make libtool autoconf automake pkg-config) | |
| MISSING=() | |
| for pkg in "${PKGS[@]}"; do | |
| if ! dpkg -s "$pkg" >/dev/null 2>&1; then | |
| MISSING+=("$pkg") | |
| fi | |
| done | |
| if [ ${#MISSING[@]} -ne 0 ]; then | |
| echo "Installing missing packages: ${MISSING[*]}" | |
| sudo apt update | |
| sudo apt install -y "${MISSING[@]}" | |
| else | |
| echo "✅ All prerequisites already installed." | |
| fi | |
| # ------------------------------------------------------------------- | |
| # Prepare ngspice source (reuse backup if present) | |
| # ------------------------------------------------------------------- | |
| if [ -f "../ngspice-backup.tar.gz" ]; then | |
| echo "Found ngspice-backup.tar.gz, extracting..." | |
| rm -rf ngspice | |
| tar -xzf ../ngspice-backup.tar.gz | |
| else | |
| echo "No backup found, cloning from GitHub..." | |
| rm -rf ngspice | |
| git clone https://github.com/imr/ngspice.git ngspice | |
| # Create backup for next time | |
| tar -czf ../ngspice-backup.tar.gz ngspice | |
| fi | |
| cd ngspice | |
| # ------------------------------------------------------------------- | |
| # Apply WASI patches (idempotent, guarded by markers) | |
| # ------------------------------------------------------------------- | |
| # 1) streams.c: stub dup2 (guard with marker) | |
| STREAMS_FILE="src/frontend/streams.c" | |
| if [ -f "$STREAMS_FILE" ] && ! grep -q "NGSPICE_WASI_DUP2" "$STREAMS_FILE"; then | |
| echo "Patching $STREAMS_FILE with WASI dup2 stub..." | |
| sed -i '/#include "streams.h"/a \ | |
| #ifdef __wasi__\n#ifndef NGSPICE_WASI_DUP2\n#define NGSPICE_WASI_DUP2\n/* WASI has no dup2; provide a stub */\nstatic inline int dup2(int oldfd, int newfd) {\n (void)oldfd;\n return newfd;\n}\n#endif\n#endif' "$STREAMS_FILE" | |
| fi | |
| # 2) evaluate.c: stub setjmp/longjmp | |
| EVALUATE_FILE="src/frontend/evaluate.c" | |
| if [ -f "$EVALUATE_FILE" ] && ! grep -q "NGSPICE_WASI_SETJMP" "$EVALUATE_FILE"; then | |
| echo "Patching $EVALUATE_FILE with WASI setjmp stub..." | |
| sed -i 's|#include <setjmp.h>|#ifdef __wasi__\n#ifndef NGSPICE_WASI_SETJMP\n#define NGSPICE_WASI_SETJMP\n#include <stdio.h>\ntypedef int jmp_buf;\nstatic inline int setjmp(jmp_buf env){(void)env;return 0;}\nstatic inline void longjmp(jmp_buf env,int val){(void)env;(void)val;}\n#endif\n#else\n#include <setjmp.h>\n#endif|' "$EVALUATE_FILE" | |
| fi | |
| # 3) src/main.c: stub setjmp/longjmp, dup2, and improved tmpfile (note: main.c is in src/) | |
| MAIN_FILE="src/main.c" | |
| if [ -f "$MAIN_FILE" ]; then | |
| if ! grep -q "NGSPICE_WASI_SETJMP" "$MAIN_FILE"; then | |
| echo "Patching $MAIN_FILE with WASI setjmp stub..." | |
| sed -i 's|#include <setjmp.h>|#ifdef __wasi__\n#ifndef NGSPICE_WASI_SETJMP\n#define NGSPICE_WASI_SETJMP\n#include <stdio.h>\ntypedef int jmp_buf;\nstatic inline int setjmp(jmp_buf env){(void)env;return 0;}\nstatic inline void longjmp(jmp_buf env,int val){(void)env;(void)val;}\n#endif\n#else\n#include <setjmp.h>\n#endif|' "$MAIN_FILE" | |
| fi | |
| if ! grep -q "NGSPICE_WASI_DUP2" "$MAIN_FILE"; then | |
| echo "Patching $MAIN_FILE with WASI dup2 stub..." | |
| sed -i '/#include /a \ | |
| #ifdef __wasi__\n#ifndef NGSPICE_WASI_DUP2\n#define NGSPICE_WASI_DUP2\nstatic inline int dup2(int oldfd, int newfd) {\n (void)oldfd;\n return newfd;\n}\n#endif\n#endif' "$MAIN_FILE" | |
| fi | |
| if ! grep -q "NGSPICE_WASI_TMPFILE" "$MAIN_FILE"; then | |
| echo "Patching $MAIN_FILE with improved WASI tmpfile stub..." | |
| sed -i '/#include /a \ | |
| #ifdef __wasi__\n#ifndef NGSPICE_WASI_TMPFILE\n#define NGSPICE_WASI_TMPFILE\n#include <stdio.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <errno.h>\nstatic int tmp_counter = 0;\nFILE *tmpfile(void) {\n char name[64];\n int fd;\n do {\n snprintf(name, sizeof(name), "/tmp/ngspice_tmp_%d", tmp_counter++);\n fd = open(name, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);\n } while (fd == -1 && errno == EEXIST && tmp_counter < 10000);\n if (fd == -1) {\n return NULL;\n }\n unlink(name);\n FILE *f = fdopen(fd, "w+b");\n if (!f) {\n close(fd);\n }\n return f;\n}\n#endif\n#endif' "$MAIN_FILE" | |
| fi | |
| fi | |
| # 4) signal_handler.c: stub POSIX signals (kill, tcgetpgrp, getpgrp) | |
| SIGNAL_FILE="src/frontend/signal_handler.c" | |
| if [ -f "$SIGNAL_FILE" ] && ! grep -q "NGSPICE_WASI_SIGNAL_STUBS" "$SIGNAL_FILE"; then | |
| echo "Patching $SIGNAL_FILE with WASI signal stubs..." | |
| sed -i '/#include /a \ | |
| #ifdef __wasi__\n#ifndef NGSPICE_WASI_SIGNAL_STUBS\n#define NGSPICE_WASI_SIGNAL_STUBS\nstatic inline int kill(int pid, int sig){(void)pid;(void)sig;return -1;}\nstatic inline int tcgetpgrp(int fd){(void)fd;return 0;}\nstatic inline int getpgrp(void){return 0;}\n#endif\n#endif' "$SIGNAL_FILE" | |
| # Replace any direct include of <setjmp.h> with guarded version (handles setjmp/longjmp stubs) | |
| sed -i 's|#include <setjmp.h>|#ifdef __wasi__\n#ifndef NGSPICE_WASI_SETJMP\n#define NGSPICE_WASI_SETJMP\n#include <stdio.h>\ntypedef int jmp_buf;\nstatic inline int setjmp(jmp_buf env){(void)env;return 0;}\nstatic inline void longjmp(jmp_buf env,int val){(void)env;(void)val;}\n#endif\n#else\n#include <setjmp.h>\n#endif|' "$SIGNAL_FILE" | |
| fi | |
| # 5) get_avail_mem_size.c: fix top OS block (#else -> #elif __wasi__), drop #error, add WASI stub | |
| MEM_AVAIL_FILE="src/frontend/get_avail_mem_size.c" | |
| if [ -f "$MEM_AVAIL_FILE" ]; then | |
| echo "Patching $MEM_AVAIL_FILE OS detection block for WASI..." | |
| # Convert the lone '#else' of the OS detection prelude to '#elif defined(__wasi__)' | |
| sed -i -E '0,/#endif/s/^[[:space:]]*#else[[:space:]]*$/#elif defined(__wasi__)/' "$MEM_AVAIL_FILE" | |
| # Remove the #error line that complains about unknown OS | |
| sed -i -E 's/^#error "Unable to define getMemorySize\( \) for an unknown OS\."\s*$//' "$MEM_AVAIL_FILE" | |
| # Add WASI stub implementation if not present | |
| if ! grep -q "NGSPICE_WASI_MEM_AVAIL_STUB" "$MEM_AVAIL_FILE"; then | |
| sed -i '/#elif defined(__wasi__)/a \ | |
| #ifdef __wasi__\n#ifndef NGSPICE_WASI_MEM_AVAIL_STUB\n#define NGSPICE_WASI_MEM_AVAIL_STUB\nsize_t getMemorySize(void) {\n return 8589934592ULL; /* 8 GB */\n}\n#endif\n#endif' "$MEM_AVAIL_FILE" | |
| fi | |
| fi | |
| # 6) get_phys_mem_size.c: fix top OS block (#else -> #elif __wasi__), drop #error, add WASI stub | |
| MEM_PHYS_FILE="src/frontend/get_phys_mem_size.c" | |
| if [ -f "$MEM_PHYS_FILE" ]; then | |
| echo "Patching $MEM_PHYS_FILE OS detection block for WASI..." | |
| sed -i -E '0,/#endif/s/^[[:space:]]*#else[[:space:]]*$/#elif defined(__wasi__)/' "$MEM_PHYS_FILE" | |
| sed -i -E 's/^#error "Unable to define getMemorySize\( \) for an unknown OS\."\s*$//' "$MEM_PHYS_FILE" | |
| # Add WASI stub implementation if not present | |
| if ! grep -q "NGSPICE_WASI_MEM_PHYS_STUB" "$MEM_PHYS_FILE"; then | |
| sed -i '/#elif defined(__wasi__)/a \ | |
| #ifdef __wasi__\n#ifndef NGSPICE_WASI_MEM_PHYS_STUB\n#define NGSPICE_WASI_MEM_PHYS_STUB\nsize_t getPhysMemorySize(void) {\n return 8589934592ULL; /* 8 GB */\n}\n#endif\n#endif' "$MEM_PHYS_FILE" | |
| fi | |
| fi | |
| # 7) get_resident_set_size.c: fix top OS block (#else -> #elif __wasi__), drop #error, add WASI stub | |
| RSS_FILE="src/frontend/get_resident_set_size.c" | |
| if [ -f "$RSS_FILE" ]; then | |
| echo "Patching $RSS_FILE OS detection block for WASI..." | |
| sed -i -E '0,/#endif/s/^[[:space:]]*#else[[:space:]]*$/#elif defined(__wasi__)/' "$RSS_FILE" | |
| sed -i -E 's/^#error "Cannot define getPeakRSS\( \) or getCurrentRSS\( \) for an unknown OS\."\s*$//' "$RSS_FILE" | |
| # Add WASI stub implementation if not present | |
| if ! grep -q "NGSPICE_WASI_RSS_STUB" "$RSS_FILE"; then | |
| sed -i '/#elif defined(__wasi__)/a \ | |
| #ifdef __wasi__\n#ifndef NGSPICE_WASI_RSS_STUB\n#define NGSPICE_WASI_RSS_STUB\nsize_t getPeakRSS(void) {\n return 0;\n}\nsize_t getCurrentRSS(void) {\n return 0;\n}\n#endif\n#endif' "$RSS_FILE" | |
| fi | |
| fi | |
| # 8) inpcom.c: stub realpath with marker and guard to avoid redefinition | |
| INPCOM_FILE="src/frontend/inpcom.c" | |
| if [ -f "$INPCOM_FILE" ] && ! grep -q "WASI_REALPATH_STUB" "$INPCOM_FILE"; then | |
| echo "Patching $INPCOM_FILE with WASI realpath stub..." | |
| sed -i '1i \ | |
| #ifdef __wasi__\n#ifndef WASI_REALPATH_STUB\n#define WASI_REALPATH_STUB\n#include <limits.h>\n#include <string.h>\nstatic inline char *wasi_realpath(const char *path, char *resolved_path) {\n if (resolved_path) {\n strncpy(resolved_path, path, PATH_MAX - 1);\n resolved_path[PATH_MAX - 1] = '\''\\0'\'';\n return resolved_path;\n }\n return (char*)path;\n}\n#define realpath(P,R) wasi_realpath((P),(R))\n#endif\n#endif' "$INPCOM_FILE" | |
| fi | |
| # 9) com_hardcopy.c: stub system | |
| COM_HARDCOPY_FILE="src/frontend/com_hardcopy.c" | |
| if [ -f "$COM_HARDCOPY_FILE" ] && ! grep -q "NGSPICE_WASI_SYSTEM" "$COM_HARDCOPY_FILE"; then | |
| echo "Patching $COM_HARDCOPY_FILE with WASI system stub..." | |
| sed -i '/#include /a \ | |
| #ifdef __wasi__\n#ifndef NGSPICE_WASI_SYSTEM\n#define NGSPICE_WASI_SYSTEM\n#include <stdlib.h>\nint system(const char *command) {\n (void)command;\n return -1;\n}\n#endif\n#endif' "$COM_HARDCOPY_FILE" | |
| fi | |
| # 10) variable.c: stub getpid | |
| VAR_FILE="src/frontend/variable.c" | |
| if [ -f "$VAR_FILE" ] && ! grep -q "NGSPICE_WASI_GETPID" "$VAR_FILE"; then | |
| echo "Patching $VAR_FILE with WASI getpid stub..." | |
| sed -i '/#include /a \ | |
| #ifdef __wasi__\n#ifndef NGSPICE_WASI_GETPID\n#define NGSPICE_WASI_GETPID\n#include <unistd.h>\npid_t getpid(void) {\n return 1;\n}\n#endif\n#endif' "$VAR_FILE" | |
| fi | |
| # 11) outitf.c: stub clock | |
| OUTITF_FILE="src/frontend/outitf.c" | |
| if [ -f "$OUTITF_FILE" ] && ! grep -q "NGSPICE_WASI_CLOCK" "$OUTITF_FILE"; then | |
| echo "Patching $OUTITF_FILE with WASI clock stub..." | |
| sed -i '/#include /a \ | |
| #ifdef __wasi__\n#ifndef NGSPICE_WASI_CLOCK\n#define NGSPICE_WASI_CLOCK\n#include <time.h>\nclock_t clock(void) {\n return 0;\n}\n#endif\n#endif' "$OUTITF_FILE" | |
| fi | |
| # ------------------------------------------------------------------- | |
| # Generate configure script (only if not already generated) | |
| # ------------------------------------------------------------------- | |
| if [ ! -f configure ]; then | |
| ./autogen.sh | |
| fi | |
| # ------------------------------------------------------------------- | |
| # Configure for WASI using emcc/em++ (only if not already configured) | |
| # ------------------------------------------------------------------- | |
| LD=emcc \ | |
| ./configure \ | |
| --host=wasm32 \ | |
| --build="$(./config.guess)" \ | |
| --with-x=no \ | |
| --with-readline=no \ | |
| --disable-xspice \ | |
| --disable-osdi \ | |
| --disable-openmp \ | |
| --disable-debug \ | |
| --enable-shared=no \ | |
| CC="emcc -s STANDALONE_WASM=1 -s WASM_BIGINT=1 -s ERROR_ON_UNDEFINED_SYMBOLS=0" \ | |
| CXX="em++ -s STANDALONE_WASM=1 -s WASM_BIGINT=1 -s ERROR_ON_UNDEFINED_SYMBOLS=0" \ | |
| AR="emar" \ | |
| RANLIB="emranlib" \ | |
| CFLAGS="-O2 -D_WASI_EMULATED_SIGNAL" \ | |
| CXXFLAGS="-O2 -std=c++17 -D_WASI_EMULATED_SIGNAL" \ | |
| LIBS="" \ | |
| ac_cv_func_dup2=yes \ | |
| ac_cv_func_fork=no \ | |
| ac_cv_func_vfork=no \ | |
| ac_cv_func_getpgrp=yes | |
| # ------------------------------------------------------------------- | |
| # Build with resume support and error logging | |
| # ------------------------------------------------------------------- | |
| echo "Starting build (errors will be logged to error.txt)..." | |
| : > error.txt | |
| make -k -j"$(nproc)" 2> >(tee build_stderr.log | grep -i "error" > error.txt) || true | |
| echo "⚠️ Build attempted. Check error.txt for errors." | |
| echo "✅ If successful, you should find ngspice.wasm in src/.libs or src/." | |
| echo "Run it with: wasmtime ./src/ngspice.wasm -- netlist.cir" | |
| echo "A backup of the source is stored as ngspice-backup.tar.gz" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment