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/node_modules/
|
||||||
/tests/playwright-report/
|
/tests/playwright-report/
|
||||||
/tests/test-results/
|
/tests/test-results/
|
||||||
|
/tests/.test-port
|
||||||
/test-results/
|
/test-results/
|
||||||
/tests/wasm-app/*.js
|
/tests/wasm-app/*.js
|
||||||
/tests/wasm-app/*.html
|
/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 { 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({
|
export default defineConfig({
|
||||||
testDir: './e2e',
|
testDir: './e2e',
|
||||||
|
|
@ -13,7 +58,7 @@ export default defineConfig({
|
||||||
testIgnore: ['**/button-finder.spec.ts'],
|
testIgnore: ['**/button-finder.spec.ts'],
|
||||||
|
|
||||||
use: {
|
use: {
|
||||||
baseURL: 'http://localhost:8080',
|
baseURL: `http://localhost:${port}`,
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
// Grant clipboard and font permissions for tests
|
// Grant clipboard and font permissions for tests
|
||||||
permissions: ['clipboard-read', 'clipboard-write', 'local-fonts'],
|
permissions: ['clipboard-read', 'clipboard-write', 'local-fonts'],
|
||||||
|
|
@ -27,8 +72,8 @@ export default defineConfig({
|
||||||
],
|
],
|
||||||
|
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npx serve wasm-app -p 8080',
|
command: `npx serve wasm-app -p ${port}`,
|
||||||
port: 8080,
|
port: port,
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue