Fix KiCad WASM build and runtime errors
Build improvements: - Change -O0 to -O1 to fix "local count too large" asyncify error - Add wasm-emscripten-finalize to host (Docker OOMs on large WASM) - Use -gseparate-dwarf for smaller main binary with debug info - Build native protoc for code generation - Add more functions to asyncify removelist Runtime fixes: - Add inject-dyncall-shims.sh to fix "dynCall_* is not defined" in Emscripten 4.x - Update wxwidgets with browserInfo.name fix New stubs and bindings: - Add Embind bindings for JavaScript interop - Add stubs for scripting, API plugin, PCB frame, navlib 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
898e079ef2
commit
9c271996c2
17 changed files with 699 additions and 13 deletions
|
|
@ -25,9 +25,18 @@ docker compose -f docker/docker-compose.yml exec kicad-wasm-builder \
|
|||
/workspace/scripts/kicad/build-pcbnew.sh "${ARGS[@]}"
|
||||
|
||||
# Copy output to host-accessible directory
|
||||
# Note: pcbnew.wasm.debug.wasm contains DWARF debug info (generated with -gseparate-dwarf)
|
||||
echo "Copying build output to ./output/..."
|
||||
docker compose -f docker/docker-compose.yml exec kicad-wasm-builder \
|
||||
bash -c "mkdir -p /workspace/output && cp /workspace/build-wasm/kicad-pcbnew/pcbnew/pcbnew.{js,wasm,wasm.map,worker.js} /workspace/output/ 2>/dev/null || cp /workspace/build-wasm/kicad-pcbnew/pcbnew/pcbnew.{js,wasm} /workspace/output/"
|
||||
bash -c "mkdir -p /workspace/output && cp /workspace/build-wasm/kicad-pcbnew/pcbnew/pcbnew.{js,wasm,wasm.debug.wasm,wasm.map,worker.js} /workspace/output/ 2>/dev/null || cp /workspace/build-wasm/kicad-pcbnew/pcbnew/pcbnew.{js,wasm} /workspace/output/"
|
||||
|
||||
# Inject dynCall shims into pcbnew.js
|
||||
# This fixes "dynCall_* is not defined" errors in Emscripten 4.x
|
||||
./scripts/common/inject-dyncall-shims.sh output/pcbnew.js
|
||||
|
||||
# Apply wasm-emscripten-finalize on host (skipped in Docker due to memory limits)
|
||||
# This is done on the host because finalize with DWARF needs significant RAM
|
||||
./scripts/common/apply-finalize.sh output/pcbnew.wasm output/pcbnew.wasm
|
||||
|
||||
# Apply asyncify transformation on host
|
||||
# This is done on the host because wasm-opt --asyncify needs significant RAM
|
||||
|
|
|
|||
2
kicad
2
kicad
|
|
@ -1 +1 @@
|
|||
Subproject commit 46bb16e86bb7085578298bcc72a261f612dd8196
|
||||
Subproject commit 2532a4682eb4a6a862d8bdf7baf700f58c4cc5fe
|
||||
|
|
@ -83,7 +83,7 @@ PCRE2_INCLUDE="$BUILD_DIR/3rdparty/pcre/src"
|
|||
|
||||
# Configure debug/release flags based on DEBUG_BUILD environment variable
|
||||
if [ "${DEBUG_BUILD:-1}" = "1" ]; then
|
||||
WX_DEBUG_FLAGS="-g -O0"
|
||||
WX_DEBUG_FLAGS="-g -O1"
|
||||
WX_CONFIGURE_DEBUG="--enable-debug"
|
||||
echo "Building wxWidgets in DEBUG mode"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -39,6 +39,13 @@ ASYNCIFY_REMOVE=$(cat << 'REMOVELIST'
|
|||
COLOR_SETTINGS::COLOR_SETTINGS(wxString const&, bool)
|
||||
BuildBitmapInfo(std::__2::unordered_map<BITMAPS, std::__2::vector<BITMAP_INFO, std::__2::allocator<BITMAP_INFO>>, std::__2::hash<BITMAPS>, std::__2::equal_to<BITMAPS>, std::__2::allocator<std::__2::pair<BITMAPS const, std::__2::vector<BITMAP_INFO, std::__2::allocator<BITMAP_INFO>>>>>&)
|
||||
match
|
||||
DIALOG_PAD_PROPERTIES_BASE::DIALOG_PAD_PROPERTIES_BASE(wxWindow*, int, wxString const&, wxPoint const&, wxSize const&, long)
|
||||
buildKicadAboutBanner(EDA_BASE_FRAME*, ABOUT_APP_INFO&)
|
||||
IGESToBRep_CurveAndSurface::TransferGeometry(opencascade::handle<IGESData_IGESEntity> const&, Message_ProgressRange const&)
|
||||
StepAP214_Protocol::StepAP214_Protocol()
|
||||
BRepCheck_ParallelAnalyzer::operator()(int) const
|
||||
ShapeFix_Wire::FixGap3d(int, bool)
|
||||
ShapeFix_Wire::FixGap2d(int, bool)
|
||||
REMOVELIST
|
||||
)
|
||||
|
||||
|
|
|
|||
46
scripts/common/apply-finalize.sh
Executable file
46
scripts/common/apply-finalize.sh
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
# Apply wasm-emscripten-finalize transformation on host
|
||||
# Uses the same Binaryen v121 as wasm-opt to ensure version consistency
|
||||
#
|
||||
# Usage: ./scripts/common/apply-finalize.sh <input.wasm> <output.wasm>
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
# Get wasm-emscripten-finalize from same Binaryen install as wasm-opt
|
||||
BINARYEN_DIR="${PROJECT_ROOT}/tools/binaryen-121"
|
||||
FINALIZE="${BINARYEN_DIR}/bin/wasm-emscripten-finalize"
|
||||
|
||||
# Ensure Binaryen is downloaded
|
||||
if [ ! -x "${FINALIZE}" ]; then
|
||||
echo "Downloading Binaryen v121..."
|
||||
"${SCRIPT_DIR}/get-wasm-opt.sh" > /dev/null
|
||||
fi
|
||||
|
||||
INPUT_WASM="${1:-output/pcbnew.wasm}"
|
||||
OUTPUT_WASM="${2:-${INPUT_WASM}}"
|
||||
|
||||
if [ ! -f "${INPUT_WASM}" ]; then
|
||||
echo "ERROR: Input file not found: ${INPUT_WASM}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Applying wasm-emscripten-finalize..."
|
||||
echo " Input: ${INPUT_WASM}"
|
||||
echo " Output: ${OUTPUT_WASM}"
|
||||
echo " Tool: ${FINALIZE}"
|
||||
|
||||
# Run finalize with the same flags Emscripten would use
|
||||
# NOTE: --dwarf removed because debug info is in separate .debug.wasm file
|
||||
"${FINALIZE}" \
|
||||
-g \
|
||||
--bigint \
|
||||
--no-legalize-javascript-ffi \
|
||||
--detect-features \
|
||||
"${INPUT_WASM}" \
|
||||
-o "${OUTPUT_WASM}"
|
||||
|
||||
echo "Finalize complete: ${OUTPUT_WASM}"
|
||||
ls -lh "${OUTPUT_WASM}"
|
||||
|
|
@ -40,7 +40,7 @@ DEBUG_BUILD="${DEBUG_BUILD:-1}"
|
|||
|
||||
if [ "$DEBUG_BUILD" = "1" ]; then
|
||||
export BUILD_TYPE="Debug"
|
||||
export DEBUG_CFLAGS="-g -O0"
|
||||
export DEBUG_CFLAGS="-g -O1"
|
||||
export DEBUG_LDFLAGS="-g -gsource-map"
|
||||
else
|
||||
export BUILD_TYPE="Release"
|
||||
|
|
|
|||
114
scripts/common/inject-dyncall-shims.sh
Executable file
114
scripts/common/inject-dyncall-shims.sh
Executable file
|
|
@ -0,0 +1,114 @@
|
|||
#!/bin/bash
|
||||
# Inject dynCall_* shims into Emscripten-generated JS
|
||||
#
|
||||
# Emscripten 4.x no longer generates signature-specific dynCall_* functions,
|
||||
# but the invoke_* wrappers (for C++ exception handling) still call them.
|
||||
# This script auto-generates shims using getWasmTableEntry() which IS defined.
|
||||
#
|
||||
# Usage: inject-dyncall-shims.sh <pcbnew.js>
|
||||
|
||||
set -e
|
||||
|
||||
JS_FILE="$1"
|
||||
|
||||
if [ -z "$JS_FILE" ] || [ ! -f "$JS_FILE" ]; then
|
||||
echo "Error: JS file not found: $JS_FILE"
|
||||
echo "Usage: $0 <path/to/pcbnew.js>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Extracting dynCall signatures from $JS_FILE..."
|
||||
|
||||
# Extract all unique dynCall_* signatures from the file
|
||||
# Matches patterns like: dynCall_i, dynCall_ii, dynCall_viijj, etc.
|
||||
SIGNATURES=$(grep -oE 'dynCall_[a-zA-Z0-9]+' "$JS_FILE" | sort -u | sed 's/dynCall_//')
|
||||
|
||||
if [ -z "$SIGNATURES" ]; then
|
||||
echo "No dynCall signatures found - nothing to inject"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SIG_COUNT=$(echo "$SIGNATURES" | wc -l | tr -d ' ')
|
||||
echo "Found $SIG_COUNT unique signatures"
|
||||
|
||||
# Generate shim code
|
||||
SHIM_FILE=$(mktemp)
|
||||
cat > "$SHIM_FILE" << 'HEADER'
|
||||
|
||||
// === dynCall shims for Emscripten exception handling ===
|
||||
// Auto-generated: maps dynCall_SIG() calls to getWasmTableEntry()
|
||||
// This fixes "dynCall_* is not defined" errors in Emscripten 4.x
|
||||
HEADER
|
||||
|
||||
for sig in $SIGNATURES; do
|
||||
# Count args: signature length - 1 (first char is return type)
|
||||
argcount=$((${#sig} - 1))
|
||||
|
||||
# Generate argument list: index, a0, a1, a2, ...
|
||||
args="index"
|
||||
call_args=""
|
||||
for ((i=0; i<argcount; i++)); do
|
||||
args="$args, a$i"
|
||||
if [ $i -gt 0 ]; then
|
||||
call_args="$call_args, "
|
||||
fi
|
||||
call_args="${call_args}a$i"
|
||||
done
|
||||
|
||||
echo "var dynCall_$sig = ($args) => getWasmTableEntry(index)($call_args);" >> "$SHIM_FILE"
|
||||
done
|
||||
|
||||
echo "" >> "$SHIM_FILE"
|
||||
echo "// === End dynCall shims ===" >> "$SHIM_FILE"
|
||||
|
||||
# Find the insertion point: after getWasmTableEntry definition
|
||||
# The pattern is:
|
||||
# var getWasmTableEntry = funcPtr => {
|
||||
# ...
|
||||
# };
|
||||
# We insert after the closing `};`
|
||||
|
||||
# Find line number of getWasmTableEntry definition
|
||||
GWTL_LINE=$(grep -n '^var getWasmTableEntry = funcPtr => {' "$JS_FILE" | head -1 | cut -d: -f1)
|
||||
|
||||
if [ -z "$GWTL_LINE" ]; then
|
||||
echo "Warning: Could not find getWasmTableEntry definition"
|
||||
echo "Trying alternate pattern..."
|
||||
GWTL_LINE=$(grep -n 'var getWasmTableEntry' "$JS_FILE" | head -1 | cut -d: -f1)
|
||||
fi
|
||||
|
||||
if [ -z "$GWTL_LINE" ]; then
|
||||
echo "Error: Could not find getWasmTableEntry in $JS_FILE"
|
||||
echo "The shims need to be inserted after getWasmTableEntry is defined"
|
||||
rm "$SHIM_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find the closing `};` after getWasmTableEntry (within next 10 lines)
|
||||
INSERT_LINE=""
|
||||
for ((i=GWTL_LINE; i<=GWTL_LINE+10; i++)); do
|
||||
LINE_CONTENT=$(sed -n "${i}p" "$JS_FILE")
|
||||
if [[ "$LINE_CONTENT" == "};" ]]; then
|
||||
INSERT_LINE=$i
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$INSERT_LINE" ]; then
|
||||
echo "Warning: Could not find closing }; for getWasmTableEntry"
|
||||
echo "Inserting after line $GWTL_LINE"
|
||||
INSERT_LINE=$GWTL_LINE
|
||||
fi
|
||||
|
||||
echo "Injecting shims after line $INSERT_LINE..."
|
||||
|
||||
# Create output file with shims inserted
|
||||
head -n "$INSERT_LINE" "$JS_FILE" > "${JS_FILE}.tmp"
|
||||
cat "$SHIM_FILE" >> "${JS_FILE}.tmp"
|
||||
tail -n +$((INSERT_LINE + 1)) "$JS_FILE" >> "${JS_FILE}.tmp"
|
||||
|
||||
# Replace original file
|
||||
mv "${JS_FILE}.tmp" "$JS_FILE"
|
||||
rm "$SHIM_FILE"
|
||||
|
||||
echo "Successfully injected $SIG_COUNT dynCall shims into $JS_FILE"
|
||||
|
|
@ -49,8 +49,37 @@ if [ ! -d "${PROTOBUF_DIR}" ]; then
|
|||
rm "protobuf-${PROTOBUF_VERSION}.tar.gz"
|
||||
fi
|
||||
|
||||
log_info "Building Protobuf ${PROTOBUF_VERSION} for WASM..."
|
||||
log_info "Building Protobuf ${PROTOBUF_VERSION}..."
|
||||
|
||||
# Step 1: Build native protoc for the host (needed for code generation)
|
||||
# This is required because the system protoc may be a different version
|
||||
PROTOBUF_HOST_BUILD="${BUILD_ROOT}/deps/protobuf-host"
|
||||
PROTOC_NATIVE="${PROTOBUF_HOST_BUILD}/protoc"
|
||||
|
||||
if [ ! -f "${PROTOC_NATIVE}" ]; then
|
||||
log_info "Building native protoc for host..."
|
||||
mkdir -p "${PROTOBUF_HOST_BUILD}"
|
||||
cd "${PROTOBUF_HOST_BUILD}"
|
||||
|
||||
# Build native (not cross-compiled) protoc
|
||||
cmake "${PROTOBUF_DIR}" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-Dprotobuf_BUILD_TESTS=OFF \
|
||||
-Dprotobuf_BUILD_EXAMPLES=OFF \
|
||||
-Dprotobuf_BUILD_SHARED_LIBS=OFF \
|
||||
-Dprotobuf_WITH_ZLIB=OFF
|
||||
|
||||
make -j${JOBS} protoc
|
||||
log_info "Native protoc built: ${PROTOC_NATIVE}"
|
||||
fi
|
||||
|
||||
# Install native protoc to sysroot/bin for CMake to find
|
||||
mkdir -p "${SYSROOT}/bin"
|
||||
cp "${PROTOC_NATIVE}" "${SYSROOT}/bin/protoc"
|
||||
log_info "Native protoc installed to ${SYSROOT}/bin/protoc"
|
||||
|
||||
# Step 2: Build WASM library
|
||||
log_info "Building Protobuf library for WASM..."
|
||||
mkdir -p "${PROTOBUF_BUILD}"
|
||||
cd "${PROTOBUF_BUILD}"
|
||||
|
||||
|
|
|
|||
|
|
@ -127,11 +127,11 @@ log_info "Building KiCad PCBnew ${KICAD_VERSION} for WASM..."
|
|||
if [ "${DEBUG_BUILD:-0}" = "1" ] || [ $DEBUG -eq 1 ]; then
|
||||
BUILD_TYPE="Debug"
|
||||
EXTRA_FLAGS="-g -O1 -fexceptions -matomics -mbulk-memory"
|
||||
# -O0 at link time skips wasm-opt (which can OOM on large WASM with debug symbols)
|
||||
# NOTE: -gsource-map removed because wasm-emscripten-finalize OOMs on large WASM
|
||||
# with source maps enabled. Keep -g for debug symbols in the WASM.
|
||||
LINKER_DEBUG_FLAGS="-O0 -g -fexceptions"
|
||||
log_info "Building KiCad in DEBUG mode (debug symbols, no source maps due to memory)"
|
||||
# -gseparate-dwarf puts debug info in a separate .debug.wasm file
|
||||
# This keeps the main WASM small (~200MB) while preserving full debug info
|
||||
# DevTools loads the debug file on-demand when debugging
|
||||
LINKER_DEBUG_FLAGS="-O1 -g -gseparate-dwarf -fexceptions"
|
||||
log_info "Building KiCad in DEBUG mode (separate DWARF for smaller main binary)"
|
||||
else
|
||||
BUILD_TYPE="Release"
|
||||
EXTRA_FLAGS="-O2 -fexceptions -matomics -mbulk-memory"
|
||||
|
|
@ -187,6 +187,18 @@ cp "${STUBS_DIR}/wasm-opt-stub.sh" "${EMSDK_WASM_OPT}"
|
|||
chmod +x "${EMSDK_WASM_OPT}"
|
||||
log_info "wasm-opt stub installed (asyncify will run on host)"
|
||||
|
||||
# Step 6.3: Replace wasm-emscripten-finalize with stub (same pattern as wasm-opt)
|
||||
# This tool also OOMs on large WASM with debug symbols, so we run it on the host
|
||||
EMSDK_FINALIZE="/emsdk/upstream/bin/wasm-emscripten-finalize"
|
||||
if [ -f "${EMSDK_FINALIZE}" ] && [ ! -f "${EMSDK_FINALIZE}.real" ]; then
|
||||
log_info "Backing up real wasm-emscripten-finalize..."
|
||||
mv "${EMSDK_FINALIZE}" "${EMSDK_FINALIZE}.real"
|
||||
fi
|
||||
# Always copy the latest stub (in case it was updated)
|
||||
cp "${STUBS_DIR}/wasm-emscripten-finalize-stub.sh" "${EMSDK_FINALIZE}"
|
||||
chmod +x "${EMSDK_FINALIZE}"
|
||||
log_info "wasm-emscripten-finalize stub installed (finalize will run on host)"
|
||||
|
||||
# Step 6.5: Verify WASM support is in KiCad fork
|
||||
# The kicad submodule should already have WASM port detection and kiplatform support
|
||||
KICAD_CMAKE="${KICAD_DIR}/CMakeLists.txt"
|
||||
|
|
@ -214,7 +226,7 @@ emcmake cmake "${KICAD_DIR}" \
|
|||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DCMAKE_CXX_FLAGS="${EXTRA_FLAGS} -pthread -sUSE_ZLIB=1 -DKICAD_USE_PLATFORM_WASM=1 -I${SYSROOT}/include -I${STUBS_DIR}" \
|
||||
-DCMAKE_C_FLAGS="${EXTRA_FLAGS} -pthread -sUSE_ZLIB=1 -I${SYSROOT}/include -I${STUBS_DIR}" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="${LINKER_DEBUG_FLAGS} -pthread -sUSE_ZLIB=1 -sASYNCIFY=1 -sASYNCIFY_STACK_SIZE=65536 -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE='navigator.hardwareConcurrency' -sPTHREAD_POOL_SIZE_STRICT=0 -sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=256MB -sMAXIMUM_MEMORY=4GB -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8'] -L${SYSROOT}/lib ${STUBS_BUILD}/libgit2_stub.a ${STUBS_BUILD}/libcurl_stub.a ${STUBS_BUILD}/libglu_stub.a ${STUBS_BUILD}/libpcbnew_scripting_stub.a ${STUBS_BUILD}/libnng_stub.a" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="${LINKER_DEBUG_FLAGS} -pthread -sUSE_ZLIB=1 -sASYNCIFY=1 -sDYNCALLS=1 -sASYNCIFY_STACK_SIZE=65536 -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE='navigator.hardwareConcurrency' -sPTHREAD_POOL_SIZE_STRICT=0 -sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=256MB -sMAXIMUM_MEMORY=4GB -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8','dynCall'] -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['\$dynCall'] --bind -L${SYSROOT}/lib ${STUBS_BUILD}/libgit2_stub.a ${STUBS_BUILD}/libcurl_stub.a ${STUBS_BUILD}/libglu_stub.a ${STUBS_BUILD}/libpcbnew_scripting_stub.a ${STUBS_BUILD}/libnng_stub.a ${STUBS_BUILD}/pcbnew_embind.o" \
|
||||
-DCMAKE_PREFIX_PATH="${SYSROOT};${WX_BUILD}" \
|
||||
-DwxWidgets_CONFIG_EXECUTABLE="${WX_BUILD}/wx-config" \
|
||||
\
|
||||
|
|
@ -252,7 +264,29 @@ emcmake cmake "${KICAD_DIR}" \
|
|||
-DODBC_LIBRARIES:STRING="" \
|
||||
\
|
||||
-DBUILD_GITHUB_PLUGIN=OFF \
|
||||
-DKICAD_PCM=OFF
|
||||
-DKICAD_PCM=OFF \
|
||||
\
|
||||
-DHAVE_STRCASECMP=1 \
|
||||
-DHAVE_STRNCASECMP=1
|
||||
|
||||
# Step 7.1: Compile Embind bindings (after CMake so config.h exists)
|
||||
# Exposes KiCad objects to JavaScript for future Pyodide integration
|
||||
EMBIND_SRC="${PROJECT_ROOT}/wasm/bindings/pcbnew_embind.cpp"
|
||||
if [ -f "$EMBIND_SRC" ]; then
|
||||
log_info "Compiling Embind bindings..."
|
||||
# Use the same includes and flags that KiCad uses
|
||||
KICAD_INCLUDES="-I${KICAD_BUILD} -I${KICAD_DIR}/include -I${KICAD_DIR}/pcbnew -I${KICAD_DIR}/common"
|
||||
KICAD_INCLUDES+=" -I${KICAD_DIR}/libs/core/include -I${KICAD_DIR}/libs/kimath/include -I${KICAD_DIR}/libs/kiplatform/include"
|
||||
KICAD_INCLUDES+=" -I${KICAD_DIR}/thirdparty/clipper2/Clipper2Lib/include"
|
||||
KICAD_INCLUDES+=" -I${KICAD_DIR}/thirdparty/nlohmann_json"
|
||||
KICAD_INCLUDES+=" -I${KICAD_DIR}/thirdparty/dynamic_bitset"
|
||||
KICAD_INCLUDES+=" -I${KICAD_DIR}/thirdparty/nanodbc"
|
||||
KICAD_INCLUDES+=" -I${KICAD_DIR}/thirdparty/picosha2"
|
||||
KICAD_INCLUDES+=" -I${KICAD_DIR}/thirdparty"
|
||||
KICAD_INCLUDES+=" -I${SYSROOT}/include"
|
||||
# KiCad requires C++20 for concepts
|
||||
em++ -std=c++20 -c ${EXTRA_FLAGS} ${WX_CXXFLAGS} ${KICAD_INCLUDES} "$EMBIND_SRC" -o "${STUBS_BUILD}/pcbnew_embind.o"
|
||||
fi
|
||||
|
||||
# Step 8: Build pcbnew target
|
||||
log_info "Building pcbnew..."
|
||||
|
|
|
|||
86
wasm/bindings/pcbnew_embind.cpp
Normal file
86
wasm/bindings/pcbnew_embind.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Embind bindings for KiCad WASM
|
||||
* Exposes core PCBnew objects to JavaScript
|
||||
*
|
||||
* This provides a foundation for future Pyodide integration.
|
||||
*
|
||||
* Note: GetBoard() is not available when KICAD_SCRIPTING=OFF.
|
||||
* These bindings expose the classes for use when a board reference
|
||||
* is obtained through other means (e.g., from the UI).
|
||||
*/
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten/bind.h>
|
||||
#include <board.h>
|
||||
#include <footprint.h>
|
||||
#include <pad.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
// Wrapper to return footprints as vector for JS iteration
|
||||
std::vector<FOOTPRINT*> Board_GetFootprints(BOARD* board) {
|
||||
if (!board) return {};
|
||||
std::vector<FOOTPRINT*> result;
|
||||
for (FOOTPRINT* fp : board->Footprints()) {
|
||||
result.push_back(fp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Wrapper to return pads as vector
|
||||
std::vector<PAD*> Footprint_GetPads(FOOTPRINT* fp) {
|
||||
if (!fp) return {};
|
||||
std::vector<PAD*> result;
|
||||
for (PAD* pad : fp->Pads()) {
|
||||
result.push_back(pad);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Wrapper for GetFileName since it returns wxString
|
||||
std::string Board_GetFileName(BOARD* board) {
|
||||
if (!board) return "";
|
||||
return board->GetFileName().ToStdString();
|
||||
}
|
||||
|
||||
// Wrapper for footprint reference
|
||||
std::string Footprint_GetReference(FOOTPRINT* fp) {
|
||||
if (!fp) return "";
|
||||
return fp->GetReference().ToStdString();
|
||||
}
|
||||
|
||||
// Wrapper for footprint value
|
||||
std::string Footprint_GetValue(FOOTPRINT* fp) {
|
||||
if (!fp) return "";
|
||||
return fp->GetValue().ToStdString();
|
||||
}
|
||||
|
||||
// Wrapper for pad number
|
||||
std::string Pad_GetNumber(PAD* pad) {
|
||||
if (!pad) return "";
|
||||
return pad->GetNumber().ToStdString();
|
||||
}
|
||||
|
||||
// Wrapper for pad pin function
|
||||
std::string Pad_GetPinFunction(PAD* pad) {
|
||||
if (!pad) return "";
|
||||
return pad->GetPinFunction().ToStdString();
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(pcbnew) {
|
||||
// Register vector types for iteration
|
||||
register_vector<FOOTPRINT*>("FootprintVector");
|
||||
register_vector<PAD*>("PadVector");
|
||||
|
||||
// Helper functions that operate on pointers
|
||||
// Note: GetBoard() not available - will be added when Pyodide integration is done
|
||||
function("Board_GetFootprints", &Board_GetFootprints, allow_raw_pointers());
|
||||
function("Board_GetFileName", &Board_GetFileName, allow_raw_pointers());
|
||||
function("Footprint_GetPads", &Footprint_GetPads, allow_raw_pointers());
|
||||
function("Footprint_GetReference", &Footprint_GetReference, allow_raw_pointers());
|
||||
function("Footprint_GetValue", &Footprint_GetValue, allow_raw_pointers());
|
||||
function("Pad_GetNumber", &Pad_GetNumber, allow_raw_pointers());
|
||||
function("Pad_GetPinFunction", &Pad_GetPinFunction, allow_raw_pointers());
|
||||
}
|
||||
#endif
|
||||
120
wasm/stubs/api_plugin_stub.cpp
Normal file
120
wasm/stubs/api_plugin_stub.cpp
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* API Plugin stubs for KiCad WASM build
|
||||
* API plugins and server are not available in WASM (no sockets/IPC)
|
||||
*/
|
||||
|
||||
#include <api/api_plugin_manager.h>
|
||||
#include <api/api_server.h>
|
||||
|
||||
// Forward declare and provide minimal definition for KINNG_REQUEST_SERVER
|
||||
// This is needed because unique_ptr requires a complete type for deletion
|
||||
class KINNG_REQUEST_SERVER {};
|
||||
|
||||
// Event definitions
|
||||
wxDEFINE_EVENT( API_REQUEST_EVENT, wxCommandEvent );
|
||||
wxDEFINE_EVENT( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxCommandEvent );
|
||||
wxDEFINE_EVENT( EDA_EVT_PLUGIN_AVAILABILITY_CHANGED, wxCommandEvent );
|
||||
|
||||
// Static member initialization
|
||||
wxString KICAD_API_SERVER::s_logFileName;
|
||||
|
||||
// KICAD_API_SERVER stub implementation
|
||||
KICAD_API_SERVER::KICAD_API_SERVER() :
|
||||
m_readyToReply( false )
|
||||
{
|
||||
// m_server intentionally left as nullptr - API server not available in WASM
|
||||
}
|
||||
|
||||
KICAD_API_SERVER::~KICAD_API_SERVER()
|
||||
{
|
||||
// m_server is nullptr, nothing to clean up
|
||||
}
|
||||
|
||||
void KICAD_API_SERVER::Start()
|
||||
{
|
||||
// No-op: API server not available in WASM
|
||||
}
|
||||
|
||||
void KICAD_API_SERVER::Stop()
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
bool KICAD_API_SERVER::Running() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void KICAD_API_SERVER::RegisterHandler( API_HANDLER* aHandler )
|
||||
{
|
||||
(void)aHandler;
|
||||
}
|
||||
|
||||
void KICAD_API_SERVER::DeregisterHandler( API_HANDLER* aHandler )
|
||||
{
|
||||
(void)aHandler;
|
||||
}
|
||||
|
||||
std::string KICAD_API_SERVER::SocketPath() const
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
void KICAD_API_SERVER::onApiRequest( std::string* aRequest )
|
||||
{
|
||||
(void)aRequest;
|
||||
}
|
||||
|
||||
void KICAD_API_SERVER::handleApiEvent( wxCommandEvent& aEvent )
|
||||
{
|
||||
(void)aEvent;
|
||||
}
|
||||
|
||||
void KICAD_API_SERVER::log( const std::string& aOutput )
|
||||
{
|
||||
(void)aOutput;
|
||||
}
|
||||
|
||||
// API_PLUGIN_MANAGER stub implementation
|
||||
API_PLUGIN_MANAGER::API_PLUGIN_MANAGER( wxEvtHandler* aParent ) :
|
||||
m_parent( aParent ),
|
||||
m_lastPid( 0 ),
|
||||
m_raiseTimer( nullptr )
|
||||
{
|
||||
}
|
||||
|
||||
void API_PLUGIN_MANAGER::ReloadPlugins()
|
||||
{
|
||||
// No-op: plugins not available in WASM
|
||||
}
|
||||
|
||||
void API_PLUGIN_MANAGER::RecreatePluginEnvironment( const wxString& aIdentifier )
|
||||
{
|
||||
(void)aIdentifier;
|
||||
}
|
||||
|
||||
void API_PLUGIN_MANAGER::InvokeAction( const wxString& aIdentifier )
|
||||
{
|
||||
(void)aIdentifier;
|
||||
}
|
||||
|
||||
std::optional<const PLUGIN_ACTION*> API_PLUGIN_MANAGER::GetAction( const wxString& aIdentifier )
|
||||
{
|
||||
(void)aIdentifier;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<const PLUGIN_ACTION*> API_PLUGIN_MANAGER::GetActionsForScope( PLUGIN_ACTION_SCOPE aScope )
|
||||
{
|
||||
(void)aScope;
|
||||
return {};
|
||||
}
|
||||
|
||||
void API_PLUGIN_MANAGER::processPluginDependencies()
|
||||
{
|
||||
}
|
||||
|
||||
void API_PLUGIN_MANAGER::processNextJob( wxCommandEvent& aEvent )
|
||||
{
|
||||
(void)aEvent;
|
||||
}
|
||||
26
wasm/stubs/nl_pcbnew_plugin_stub.cpp
Normal file
26
wasm/stubs/nl_pcbnew_plugin_stub.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* SpaceNavigator plugin stubs for KiCad WASM build
|
||||
* 3D mouse support is not available in WASM
|
||||
*/
|
||||
|
||||
// Minimal definition for NL_PCBNEW_PLUGIN_IMPL
|
||||
// Required because unique_ptr destructor needs a complete type
|
||||
class NL_PCBNEW_PLUGIN_IMPL {};
|
||||
|
||||
#include <navlib/nl_pcbnew_plugin.h>
|
||||
|
||||
// NL_PCBNEW_PLUGIN stub implementation
|
||||
NL_PCBNEW_PLUGIN::NL_PCBNEW_PLUGIN( PCB_DRAW_PANEL_GAL* aViewport )
|
||||
{
|
||||
(void)aViewport;
|
||||
// No implementation - 3D mouse not available in WASM
|
||||
}
|
||||
|
||||
NL_PCBNEW_PLUGIN::~NL_PCBNEW_PLUGIN()
|
||||
{
|
||||
}
|
||||
|
||||
void NL_PCBNEW_PLUGIN::SetFocus( bool aFocus )
|
||||
{
|
||||
(void)aFocus;
|
||||
}
|
||||
73
wasm/stubs/pcb_frame_stub.cpp
Normal file
73
wasm/stubs/pcb_frame_stub.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* PCB Frame stubs for KiCad WASM build
|
||||
* Python action plugins are not available in WASM
|
||||
*
|
||||
* These stubs provide implementations for methods that are declared in headers
|
||||
* but whose implementations depend on scripting features not available in WASM.
|
||||
*/
|
||||
|
||||
#include <pcb_edit_frame.h>
|
||||
#include <pcb_base_frame.h>
|
||||
#include <footprint.h>
|
||||
#include <action_plugin.h>
|
||||
#include <board.h>
|
||||
|
||||
class ACTION_MENU;
|
||||
|
||||
// PCB_EDIT_FRAME static method implementations
|
||||
// These are called from panel_pcbnew_action_plugins.cpp
|
||||
|
||||
std::vector<LEGACY_OR_API_PLUGIN> PCB_EDIT_FRAME::GetOrderedActionPlugins()
|
||||
{
|
||||
// Return empty list: no plugins available in WASM
|
||||
return {};
|
||||
}
|
||||
|
||||
bool PCB_EDIT_FRAME::GetActionPluginButtonVisible( const wxString& aPluginPath, bool aPluginDefault )
|
||||
{
|
||||
(void)aPluginPath;
|
||||
// Return the default value since we can't check settings for non-existent plugins
|
||||
return aPluginDefault;
|
||||
}
|
||||
|
||||
void PCB_EDIT_FRAME::buildActionPluginMenus( ACTION_MENU* aActionMenu )
|
||||
{
|
||||
(void)aActionMenu;
|
||||
// No-op: action plugins not available in WASM
|
||||
}
|
||||
|
||||
// PCB_BASE_FRAME protected method implementation
|
||||
// The declaration is NOT guarded by wxUSE_FSWATCHER but the implementation IS,
|
||||
// so we need a stub when filesystem watcher is not available.
|
||||
#if !wxUSE_FSWATCHER
|
||||
void PCB_BASE_FRAME::setFPWatcher( FOOTPRINT* aFootprint )
|
||||
{
|
||||
(void)aFootprint;
|
||||
// No-op: filesystem watcher not available in WASM
|
||||
}
|
||||
#endif
|
||||
|
||||
// Scripting helper functions stub
|
||||
// These are in pcbnew_scripting_helpers.cpp which is only compiled with KICAD_SCRIPTING
|
||||
BOARD* LoadBoard( const wxString& aFileName, bool aSetActive )
|
||||
{
|
||||
(void)aFileName;
|
||||
(void)aSetActive;
|
||||
// Scripting not available in WASM
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool SaveBoard( wxString& aFileName, BOARD* aBoard, bool aSkipSettings )
|
||||
{
|
||||
(void)aFileName;
|
||||
(void)aBoard;
|
||||
(void)aSkipSettings;
|
||||
// Scripting not available in WASM
|
||||
return false;
|
||||
}
|
||||
|
||||
BOARD* CreateEmptyBoard()
|
||||
{
|
||||
// Scripting not available in WASM
|
||||
return nullptr;
|
||||
}
|
||||
43
wasm/stubs/python_manager_stub.cpp
Normal file
43
wasm/stubs/python_manager_stub.cpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Python Manager stubs for KiCad WASM build
|
||||
* Python scripting is not available in WASM
|
||||
*/
|
||||
|
||||
#include <python_manager.h>
|
||||
|
||||
// Implementation
|
||||
|
||||
PYTHON_MANAGER::PYTHON_MANAGER( const wxString& aInterpreterPath ) :
|
||||
m_interpreterPath( aInterpreterPath )
|
||||
{
|
||||
}
|
||||
|
||||
long PYTHON_MANAGER::Execute( const std::vector<wxString>& aArgs,
|
||||
const std::function<void(int, const wxString&, const wxString&)>& aCallback,
|
||||
const wxExecuteEnv* aEnv,
|
||||
bool aSaveOutput )
|
||||
{
|
||||
(void)aArgs;
|
||||
(void)aCallback;
|
||||
(void)aEnv;
|
||||
(void)aSaveOutput;
|
||||
// Return 0: no process created (Python not available in WASM)
|
||||
return 0;
|
||||
}
|
||||
|
||||
wxString PYTHON_MANAGER::FindPythonInterpreter()
|
||||
{
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
std::optional<wxString> PYTHON_MANAGER::GetPythonEnvironment( const wxString& aNamespace )
|
||||
{
|
||||
(void)aNamespace;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<wxString> PYTHON_MANAGER::GetVirtualPython( const wxString& aNamespace )
|
||||
{
|
||||
(void)aNamespace;
|
||||
return std::nullopt;
|
||||
}
|
||||
65
wasm/stubs/scripting_stub.cpp
Normal file
65
wasm/stubs/scripting_stub.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Scripting stubs for KiCad WASM build
|
||||
* Python scripting is not available in WASM
|
||||
*
|
||||
* These stubs provide implementations for methods declared in pcb_scripting_tool.h
|
||||
* that are called from panel_pcbnew_action_plugins.cpp
|
||||
*/
|
||||
|
||||
#include <python/scripting/pcb_scripting_tool.h>
|
||||
|
||||
// Constructor/Destructor implementations
|
||||
SCRIPTING_TOOL::SCRIPTING_TOOL() :
|
||||
PCB_TOOL_BASE( "pcbnew.ScriptingTool" )
|
||||
{
|
||||
}
|
||||
|
||||
SCRIPTING_TOOL::~SCRIPTING_TOOL()
|
||||
{
|
||||
}
|
||||
|
||||
// Virtual method implementations
|
||||
bool SCRIPTING_TOOL::Init()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void SCRIPTING_TOOL::Reset( RESET_REASON aReason )
|
||||
{
|
||||
(void)aReason;
|
||||
}
|
||||
|
||||
// Static method implementations - these are called from panel_pcbnew_action_plugins.cpp
|
||||
void SCRIPTING_TOOL::ReloadPlugins()
|
||||
{
|
||||
// No-op: Python plugins not available in WASM
|
||||
}
|
||||
|
||||
void SCRIPTING_TOOL::ShowPluginFolder()
|
||||
{
|
||||
// No-op: Plugin folder not accessible in WASM
|
||||
}
|
||||
|
||||
// Private method implementations
|
||||
int SCRIPTING_TOOL::reloadPlugins( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
(void)aEvent;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SCRIPTING_TOOL::showPluginFolder( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
(void)aEvent;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SCRIPTING_TOOL::setTransitions()
|
||||
{
|
||||
// No transitions: scripting not available
|
||||
}
|
||||
|
||||
// Private static method - must be provided since it's declared in header
|
||||
void SCRIPTING_TOOL::callLoadPlugins()
|
||||
{
|
||||
// No-op: Python not available in WASM
|
||||
}
|
||||
34
wasm/stubs/wasm-emscripten-finalize-stub.sh
Executable file
34
wasm/stubs/wasm-emscripten-finalize-stub.sh
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
#!/bin/bash
|
||||
# Stub wasm-emscripten-finalize that just copies input to output
|
||||
# The real finalize runs on the host where more RAM is available
|
||||
|
||||
# Handle --version query
|
||||
if [[ "$1" == "--version" ]]; then
|
||||
echo "wasm-emscripten-finalize version 121"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse arguments to find input/output files
|
||||
# wasm-emscripten-finalize <input.wasm> -o <output.wasm> [options]
|
||||
INPUT=""
|
||||
OUTPUT=""
|
||||
PREV_ARG=""
|
||||
|
||||
for arg in "$@"; do
|
||||
if [ "$PREV_ARG" = "-o" ]; then
|
||||
OUTPUT="$arg"
|
||||
elif [[ "$arg" != -* ]] && [[ "$arg" == *.wasm ]]; then
|
||||
INPUT="$arg"
|
||||
fi
|
||||
PREV_ARG="$arg"
|
||||
done
|
||||
|
||||
# Copy input to output (no transformation)
|
||||
if [ -n "$INPUT" ] && [ -n "$OUTPUT" ]; then
|
||||
cp "$INPUT" "$OUTPUT"
|
||||
elif [ -n "$INPUT" ]; then
|
||||
# In-place mode: nothing to do
|
||||
:
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit fb711a36b5eadecbe8c1c0dc4013daff12d168f8
|
||||
Subproject commit 12f0fee64c64e221df0a65612270ec0ef4df9609
|
||||
Loading…
Reference in a new issue