diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1d37234 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,26 @@ +# Build artifacts (cached in named volume instead) +build-wasm/ + +# Git internals (large, not needed for build) +.git/modules/ + +# Compiled files +*.wasm +*.o +*.a +*.o.tmp + +# Node modules (for tests) +node_modules/ + +# Output directory +output/ + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo + +# macOS +.DS_Store diff --git a/.gitignore b/.gitignore index c937732..fc3a09f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ wxwidgets-clean/ /tests/node_modules/ /tests/playwright-report/ /tests/test-results/ +/tests/.test-port /test-results/ /tests/wasm-app/*.js /tests/wasm-app/*.html diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..69034be --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,25 @@ +# Use ARM64-native image for Apple Silicon (M1/M2/M3/M4) +FROM emscripten/emsdk:4.0.2-arm64 + +# Install build tools required for KiCad WASM build +RUN apt-get update && apt-get install -y \ + cmake \ + autoconf \ + automake \ + libtool \ + meson \ + ninja-build \ + pkg-config \ + curl \ + git \ + python3 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /workspace + +# Entry point that sources Emscripten environment +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["bash"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..c4e21fe --- /dev/null +++ b/docker/README.md @@ -0,0 +1,96 @@ +# Docker Build Environment for KiCad WASM + +This directory contains Docker configuration for building KiCad for WebAssembly in a reproducible, isolated environment. + +## Prerequisites + +- Docker Desktop (with Docker Compose v2) +- Git submodules initialized: `git submodule update --init --recursive` + +## Quick Start + +```bash +# Build KiCad WASM (full build) +./docker/build.sh + +# Build with options +./docker/build.sh --no-clean # Skip cleaning build directory +./docker/build.sh --debug # Build with debug symbols + +# Interactive shell for debugging +./docker/shell.sh +``` + +## What Gets Built + +The build process includes: + +1. **Dependencies** (cached in Docker volume): + - GLM (header-only) + - Zstd, Protobuf + - FreeType, HarfBuzz + - Pixman, Cairo + - Boost (Locale) + - OpenCASCADE (optional, for 3D/STEP) + - CURL headers, libgit2 headers (stubs) + +2. **wxWidgets** (built from submodule) + +3. **KiCad PCBnew** (main application) + +## Container Resources + +Configured for M4 Max (adjust in docker-compose.yml): +- CPUs: 10 cores +- Memory: 16GB + +## Volume Strategy + +| Path | Type | Purpose | +|------|------|---------| +| `/workspace` | Bind mount | Source code | +| `/workspace/build-wasm` | Named volume | Build cache (deps, sysroot) | +| `/workspace/output` | Bind mount | Final WASM output | + +## Common Commands + +```bash +# Start container +docker compose -f docker/docker-compose.yml up -d + +# Run a command inside +docker compose -f docker/docker-compose.yml exec kicad-wasm-builder + +# View logs +docker compose -f docker/docker-compose.yml logs -f + +# Stop container +docker compose -f docker/docker-compose.yml down + +# Clear build cache (full rebuild) +docker volume rm docker_kicad-build-cache +``` + +## Troubleshooting + +### Build freezes +The OpenCASCADE build is very resource-intensive. If it freezes: +1. Reduce parallel jobs: Edit script to use `-j4` instead of `-j$(nproc)` +2. Monitor with `docker stats` +3. Consider building OpenCASCADE separately with `./scripts/deps/build-opencascade.sh` + +### Permission issues +Files created in container are owned by root. To fix: +```bash +sudo chown -R $(whoami) output/ +``` + +### Cache issues +```bash +# Clear all cached builds +docker volume rm docker_kicad-build-cache + +# Or clear specific stamps inside container +./docker/shell.sh +rm /workspace/build-wasm/stamps/*.stamp +``` diff --git a/docker/build.sh b/docker/build.sh new file mode 100755 index 0000000..d78f4f1 --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Build KiCad WASM inside Docker container +set -e + +cd "$(dirname "$0")/.." + +# Start container if not running +docker compose -f docker/docker-compose.yml up -d + +# Run build command +docker compose -f docker/docker-compose.yml exec kicad-wasm-builder \ + /workspace/scripts/kicad/build-pcbnew.sh "$@" + +# Copy output to host-accessible directory +echo "Copying build output to ./output/..." +docker compose -f docker/docker-compose.yml exec kicad-wasm-builder \ + bash -c "mkdir -p /workspace/output && cp -r /workspace/build-wasm/kicad-pcbnew/bin/* /workspace/output/ 2>/dev/null || true" + +echo "Build complete. Output files in ./output/" diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..8d68d41 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,28 @@ +services: + kicad-wasm-builder: + build: + context: .. + dockerfile: docker/Dockerfile + container_name: kicad-wasm-builder + + # Resource limits (M4 Max: 10 cores, 16GB) + deploy: + resources: + limits: + cpus: '10' + memory: 16G + + volumes: + # Source code (read-write for git operations) + - ..:/workspace:cached + # Named volume for build cache (faster than bind mount on macOS) + - kicad-build-cache:/workspace/build-wasm + # Output directory for easy access to final WASM files + - ../output:/workspace/output + + # Keep container running for interactive use + stdin_open: true + tty: true + +volumes: + kicad-build-cache: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..747d2ec --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +# Source Emscripten environment +source /emsdk/emsdk_env.sh 2>/dev/null + +# Execute command or start shell +exec "$@" diff --git a/docker/shell.sh b/docker/shell.sh new file mode 100755 index 0000000..ab2f3a6 --- /dev/null +++ b/docker/shell.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Open interactive shell in build container for debugging +set -e + +cd "$(dirname "$0")/.." + +# Start container if not running +docker compose -f docker/docker-compose.yml up -d + +# Open interactive shell +docker compose -f docker/docker-compose.yml exec kicad-wasm-builder bash diff --git a/tests/playwright.config.ts b/tests/playwright.config.ts index a0c8ef7..d546fef 100644 --- a/tests/playwright.config.ts +++ b/tests/playwright.config.ts @@ -1,4 +1,49 @@ import { defineConfig, devices } from '@playwright/test'; +import { execSync } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; + +const PORT_FILE = path.join(__dirname, '.test-port'); + +// Get existing port from file or find a new one +// This ensures all workers use the same port +function getOrFindPort(): number { + // Check if port file exists and is recent (created in last 60 seconds) + try { + const stat = fs.statSync(PORT_FILE); + const age = Date.now() - stat.mtimeMs; + if (age < 60000) { + const port = parseInt(fs.readFileSync(PORT_FILE, 'utf-8').trim()); + if (port > 0 && port < 65536) { + return port; + } + } + } catch { + // File doesn't exist or can't be read + } + + // Find a new free port + const port = findFreePort(); + fs.writeFileSync(PORT_FILE, port.toString()); + return port; +} + +// Find a free port dynamically using a shell command +function findFreePort(): number { + // Use Python to find a free port (works on macOS and Linux) + try { + const result = execSync( + 'python3 -c "import socket; s=socket.socket(); s.bind((\'\',0)); print(s.getsockname()[1]); s.close()"', + { encoding: 'utf-8' } + ); + return parseInt(result.trim()); + } catch { + // Fallback to default port range + return 9000 + Math.floor(Math.random() * 1000); + } +} + +const port = getOrFindPort(); export default defineConfig({ testDir: './e2e', @@ -13,7 +58,7 @@ export default defineConfig({ testIgnore: ['**/button-finder.spec.ts'], use: { - baseURL: 'http://localhost:8080', + baseURL: `http://localhost:${port}`, trace: 'on-first-retry', // Grant clipboard and font permissions for tests permissions: ['clipboard-read', 'clipboard-write', 'local-fonts'], @@ -27,8 +72,8 @@ export default defineConfig({ ], webServer: { - command: 'npx serve wasm-app -p 8080', - port: 8080, + command: `npx serve wasm-app -p ${port}`, + port: port, reuseExistingServer: !process.env.CI, }, });