Emscripten 4.x removed dynCall_* WASM exports, breaking asyncify
rewind through indirect calls (modal dialogs, event handlers).
Generate JS shims that track Asyncify.exportCallStack and register
in wasmExports so doRewind can find them.
Also fixes empty callback functions ((() => {})) generated by
Emscripten 4.x + pthreads for HTML5 events, pthread entry,
sighandler, async timer, and main loop callbacks.
Build pipeline improvements:
- Stub wasm-opt/finalize in Docker (RAM limits), run on host
- Add setup-emsdk.sh for reproducible Emscripten setup
- Simplify env.sh and version management
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
273 lines
6.6 KiB
Shell
Executable file
273 lines
6.6 KiB
Shell
Executable file
#!/bin/bash
|
|
# Common functions for KiCad WASM build scripts
|
|
|
|
# Exit on error by default
|
|
set -e
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Logging functions
|
|
log_info() {
|
|
echo -e "${GREEN}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
log_step() {
|
|
echo -e "${BLUE}[STEP]${NC} $1"
|
|
}
|
|
|
|
# Error handler
|
|
on_error() {
|
|
local exit_code=$?
|
|
local line_no=$1
|
|
log_error "Build failed at line $line_no with exit code $exit_code"
|
|
|
|
# Save build log if available
|
|
if [ -n "$BUILD_LOG" ] && [ -f "$BUILD_LOG" ]; then
|
|
local log_file="$BUILD_ROOT/logs/build-$(date +%Y%m%d-%H%M%S).log"
|
|
mkdir -p "$(dirname "$log_file")"
|
|
cp "$BUILD_LOG" "$log_file"
|
|
log_error "Build log saved to: $log_file"
|
|
fi
|
|
|
|
exit $exit_code
|
|
}
|
|
|
|
# Set up error trap
|
|
setup_error_trap() {
|
|
trap 'on_error ${LINENO}' ERR
|
|
}
|
|
|
|
# Verify Emscripten is available
|
|
verify_emscripten() {
|
|
if ! command -v emcc &> /dev/null; then
|
|
log_error "Emscripten not found. Run: ./scripts/setup-emsdk.sh"
|
|
exit 1
|
|
fi
|
|
log_info "Using Emscripten: $(emcc --version | head -1)"
|
|
}
|
|
|
|
# Verify submodules are initialized
|
|
verify_submodules() {
|
|
local project_root="$1"
|
|
|
|
if [ ! -f "$project_root/kicad/CMakeLists.txt" ]; then
|
|
log_error "KiCad submodule not initialized. Run: git submodule update --init --recursive"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$project_root/wxwidgets/configure" ]; then
|
|
log_error "wxWidgets submodule not initialized. Run: git submodule update --init --recursive"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Submodules verified"
|
|
}
|
|
|
|
# Download file with optional verification
|
|
download_file() {
|
|
local url="$1"
|
|
local dest="$2"
|
|
local expected_sha256="${3:-}"
|
|
|
|
if [ -f "$dest" ]; then
|
|
if [ -n "$expected_sha256" ]; then
|
|
local actual_sha256
|
|
actual_sha256=$(shasum -a 256 "$dest" 2>/dev/null | cut -d' ' -f1)
|
|
if [ "$actual_sha256" = "$expected_sha256" ]; then
|
|
log_info "$(basename "$dest") already downloaded and verified"
|
|
return 0
|
|
fi
|
|
log_warn "Checksum mismatch, re-downloading..."
|
|
else
|
|
log_info "$(basename "$dest") already exists"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
log_info "Downloading $(basename "$dest")..."
|
|
mkdir -p "$(dirname "$dest")"
|
|
|
|
if ! curl -L -o "$dest" "$url"; then
|
|
log_error "Failed to download $url"
|
|
rm -f "$dest"
|
|
return 1
|
|
fi
|
|
|
|
if [ -n "$expected_sha256" ]; then
|
|
local actual_sha256
|
|
actual_sha256=$(shasum -a 256 "$dest" | cut -d' ' -f1)
|
|
if [ "$actual_sha256" != "$expected_sha256" ]; then
|
|
log_error "SHA256 mismatch for $dest"
|
|
log_error " Expected: $expected_sha256"
|
|
log_error " Actual: $actual_sha256"
|
|
rm -f "$dest"
|
|
return 1
|
|
fi
|
|
log_info "Checksum verified"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Extract archive (supports .tar.gz, .tar.xz, .zip)
|
|
extract_archive() {
|
|
local archive="$1"
|
|
local dest_dir="$2"
|
|
|
|
mkdir -p "$dest_dir"
|
|
|
|
case "$archive" in
|
|
*.tar.gz|*.tgz)
|
|
tar -xzf "$archive" -C "$dest_dir" --strip-components=1
|
|
;;
|
|
*.tar.xz)
|
|
tar -xJf "$archive" -C "$dest_dir" --strip-components=1
|
|
;;
|
|
*.zip)
|
|
unzip -q "$archive" -d "$dest_dir"
|
|
;;
|
|
*)
|
|
log_error "Unknown archive format: $archive"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
log_info "Extracted to $dest_dir"
|
|
}
|
|
|
|
# Create build stamp file
|
|
# Can accept either a simple name like "zstd" or a full path like "/path/to/stamps/zstd.stamp"
|
|
create_stamp() {
|
|
local name="$1"
|
|
local stamp_file
|
|
|
|
if [[ "$name" == /* ]]; then
|
|
# Full path provided
|
|
stamp_file="$name"
|
|
else
|
|
# Just a name, use default stamps dir
|
|
local stamp_dir="${BUILD_ROOT:-$PROJECT_ROOT/build-wasm}/stamps"
|
|
mkdir -p "$stamp_dir"
|
|
stamp_file="$stamp_dir/$name.stamp"
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$stamp_file")"
|
|
date +%s > "$stamp_file"
|
|
log_info "Created stamp: $(basename "$stamp_file" .stamp)"
|
|
}
|
|
|
|
# Check if build stamp exists
|
|
# Can accept either a simple name like "zstd" or a full path like "/path/to/stamps/zstd.stamp"
|
|
check_stamp() {
|
|
local name="$1"
|
|
local stamp_file
|
|
|
|
if [[ "$name" == /* ]]; then
|
|
# Full path provided
|
|
stamp_file="$name"
|
|
else
|
|
# Just a name, use default stamps dir
|
|
stamp_file="${BUILD_ROOT:-$PROJECT_ROOT/build-wasm}/stamps/$name.stamp"
|
|
fi
|
|
|
|
[ -f "$stamp_file" ]
|
|
}
|
|
|
|
# Remove build stamp
|
|
remove_stamp() {
|
|
local name="$1"
|
|
local stamp_file="${BUILD_ROOT:-$PROJECT_ROOT/build-wasm}/stamps/$name.stamp"
|
|
rm -f "$stamp_file"
|
|
}
|
|
|
|
# Build if stamp doesn't exist
|
|
build_if_needed() {
|
|
local name="$1"
|
|
local script="$2"
|
|
local force="${3:-0}"
|
|
|
|
if [ "$force" = "1" ]; then
|
|
remove_stamp "$name"
|
|
fi
|
|
|
|
if check_stamp "$name"; then
|
|
log_info "Skipping $name (already built)"
|
|
return 0
|
|
fi
|
|
|
|
log_step "Building $name..."
|
|
"$script"
|
|
}
|
|
|
|
# Get number of CPU cores for parallel builds
|
|
get_nproc() {
|
|
if command -v nproc &> /dev/null; then
|
|
nproc
|
|
elif command -v sysctl &> /dev/null; then
|
|
sysctl -n hw.ncpu
|
|
else
|
|
echo 4
|
|
fi
|
|
}
|
|
|
|
# Parse common command line arguments
|
|
parse_common_args() {
|
|
CLEAN_BUILD=0
|
|
DEBUG_BUILD="${DEBUG_BUILD:-1}" # Default ON (use --release to disable)
|
|
PARALLEL_JOBS=$(get_nproc)
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--clean)
|
|
CLEAN_BUILD=1
|
|
shift
|
|
;;
|
|
--debug)
|
|
DEBUG_BUILD=1
|
|
shift
|
|
;;
|
|
--release)
|
|
DEBUG_BUILD=0
|
|
shift
|
|
;;
|
|
-j)
|
|
PARALLEL_JOBS="$2"
|
|
shift 2
|
|
;;
|
|
-j*)
|
|
PARALLEL_JOBS="${1#-j}"
|
|
shift
|
|
;;
|
|
*)
|
|
# Unknown option, pass through
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
export CLEAN_BUILD DEBUG_BUILD PARALLEL_JOBS
|
|
}
|
|
|
|
# Print build configuration
|
|
print_build_config() {
|
|
echo "========================================"
|
|
echo "Build Configuration:"
|
|
echo " Clean build: $CLEAN_BUILD"
|
|
echo " Debug build: $DEBUG_BUILD"
|
|
echo " Parallel jobs: $PARALLEL_JOBS"
|
|
echo " Build root: ${BUILD_ROOT:-not set}"
|
|
echo "========================================"
|
|
}
|