Add Docker build environment for KiCad WASM
- Add Dockerfile with ARM64-native emscripten/emsdk:4.0.2-arm64 image - Add docker-compose.yml with resource limits (10 CPUs, 16GB RAM) - Add helper scripts (build.sh, shell.sh, entrypoint.sh) - Add Docker README with usage instructions - Update playwright.config.ts to find free port dynamically - Add .dockerignore and .gitignore entries for build artifacts The Docker environment provides reproducible builds with: - Named volume for build cache (faster I/O on macOS) - Resource limits to prevent system lockups - Interactive shell access for debugging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c408410c7d
commit
b5e46a0aba
9 changed files with 262 additions and 3 deletions
26
.dockerignore
Normal file
26
.dockerignore
Normal file
|
|
@ -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
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
25
docker/Dockerfile
Normal file
25
docker/Dockerfile
Normal file
|
|
@ -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"]
|
||||
96
docker/README.md
Normal file
96
docker/README.md
Normal file
|
|
@ -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 <command>
|
||||
|
||||
# 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
|
||||
```
|
||||
19
docker/build.sh
Executable file
19
docker/build.sh
Executable file
|
|
@ -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/"
|
||||
28
docker/docker-compose.yml
Normal file
28
docker/docker-compose.yml
Normal file
|
|
@ -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:
|
||||
8
docker/entrypoint.sh
Executable file
8
docker/entrypoint.sh
Executable file
|
|
@ -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 "$@"
|
||||
11
docker/shell.sh
Executable file
11
docker/shell.sh
Executable file
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue