Add KiCad WASM build infrastructure and dependency scripts
- Add build environment setup (scripts/common/env.sh, versions.sh, functions.sh) - Add dependency build scripts for Zstd, GLM, FreeType, HarfBuzz, Pixman, Cairo - Add placeholder scripts for OpenCASCADE, ngspice, protobuf - Add WASM compatibility layer (wasm/kiplatform/, wasm/libcontext/) - Add CMake modules for KiCad WASM cross-compilation - Add PCBnew test infrastructure (tests/kicad/) - Add build plan documentation Successfully tested builds: Zstd 1.5.5, GLM 0.9.9.8, FreeType 2.13.2, HarfBuzz 8.3.0, Pixman 0.42.2, Cairo 1.18.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
756feb35da
commit
3034d9d76f
36 changed files with 3708 additions and 0 deletions
|
|
@ -6,6 +6,8 @@ The e2e tests are in /tests, with a README and WHATWORKS md files
|
|||
The e2e tests are separated per feature
|
||||
The tests depend on canvas, there's an app to find button positions, use that, don't find buttons by estimating pixels
|
||||
The test have screenshots that are tracked with git, use compare-screenshots.sh to see what changed, update them when a new image is added
|
||||
The tests have log files after each run where the js console and cpp logs are visible
|
||||
Always check screenshots for validating tests
|
||||
|
||||
Our current goal is to test every wxwidgets feature kicad uses, write the wasm layer and e2e tests, documented in WHATWORKS
|
||||
Never run builds manually, we have scripts that run the builds in the /scripts folder
|
||||
|
|
|
|||
546
docs/07-KICAD-WASM-BUILD-PLAN.md
Normal file
546
docs/07-KICAD-WASM-BUILD-PLAN.md
Normal file
|
|
@ -0,0 +1,546 @@
|
|||
# KiCad PCBnew WASM Build Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Build the full KiCad PCBnew application for WebAssembly with all major features:
|
||||
- **Target**: PCBnew (PCB Editor)
|
||||
- **3D/STEP**: OpenCASCADE ported to WASM
|
||||
- **Simulation**: ngspice ported to WASM
|
||||
- **Threading**: Emscripten pthreads (true parallelism)
|
||||
- **Coroutines**: Emscripten Asyncify fibers for libcontext
|
||||
- **Stub only**: nanodbc (ODBC not available in browser)
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. No Source Modifications to KiCad or wxWidgets
|
||||
|
||||
**CRITICAL**: All WASM-specific code must go into compatibility layers, NOT into the KiCad or wxWidgets source trees.
|
||||
|
||||
```
|
||||
kicad-wasm/
|
||||
├── kicad/ # Git submodule - DO NOT MODIFY
|
||||
├── wxwidgets/ # Git submodule - DO NOT MODIFY (except WASM platform)
|
||||
├── wasm/ # NEW: All WASM compatibility layers
|
||||
│ ├── kiplatform/ # Platform layer implementations
|
||||
│ ├── libcontext/ # Fiber implementation
|
||||
│ ├── shims/ # Header shims and wrappers
|
||||
│ └── stubs/ # Feature stubs
|
||||
├── stubs/ # (existing) Stub headers/implementations
|
||||
├── cmake/ # (existing) CMake find modules
|
||||
└── patches/ # Minimal patches ONLY if absolutely necessary
|
||||
```
|
||||
|
||||
### 2. Compatibility Layer Strategy
|
||||
|
||||
Instead of patching KiCad source, we:
|
||||
1. **Override include paths** - Put our headers first in include path
|
||||
2. **Provide stub libraries** - Link our stubs instead of real libraries
|
||||
3. **CMake module overrides** - Replace find_package results with our targets
|
||||
4. **Platform implementations** - Provide WASM versions of platform-specific code
|
||||
|
||||
### 3. Dependencies First Approach
|
||||
|
||||
Build all dependencies (including OpenCASCADE and ngspice) BEFORE building KiCad to ensure a clean build.
|
||||
|
||||
---
|
||||
|
||||
## KiCad Submodule Version
|
||||
|
||||
```
|
||||
Commit: 4bfed3f1746e8cc0a7d942767770f56fa28b393c
|
||||
Version: 8.99 (development)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Exact Dependency Versions
|
||||
|
||||
These versions are from KiCad's `CMakeLists.txt` and `vcpkg.json`:
|
||||
|
||||
### Required Dependencies
|
||||
|
||||
| Dependency | Min Version | Pinned Version | Source |
|
||||
|------------|-------------|----------------|--------|
|
||||
| wxWidgets | 3.2.0 | 3.3.1 | vcpkg override |
|
||||
| GLM | 0.9.8 | 0.9.9.8 | vcpkg override |
|
||||
| Boost | 1.71.0 | latest | CMakeLists.txt |
|
||||
| FreeType | 2.11.1 | latest | CMakeLists.txt |
|
||||
| HarfBuzz | - | latest | CMakeLists.txt |
|
||||
| Fontconfig | - | latest | CMakeLists.txt |
|
||||
| Cairo | 1.12 | latest | CMakeLists.txt |
|
||||
| Pixman | 0.30 | latest | CMakeLists.txt |
|
||||
| zlib | - | latest | CMakeLists.txt |
|
||||
| Zstd | - | latest | CMakeLists.txt |
|
||||
| OpenCASCADE | 7.5.0+ | 7.8.0+ preferred | CMakeLists.txt |
|
||||
| ngspice | - | 45.2 | vcpkg override |
|
||||
| Protobuf | 3.21.12 | 3.21.12 | vcpkg override |
|
||||
| libgit2 | 1.5 | latest | CMakeLists.txt |
|
||||
| CURL | - | latest | CMakeLists.txt |
|
||||
| Python | 3.6+ | 3.11.5 | vcpkg override |
|
||||
|
||||
### What We Build vs Stub
|
||||
|
||||
| Dependency | Action | Reason |
|
||||
|------------|--------|--------|
|
||||
| wxWidgets | Already ported | WASM platform in wxwidgets submodule |
|
||||
| GLM | Header-only | Just include |
|
||||
| Boost | Header-only subset | Only need headers for most parts |
|
||||
| FreeType | Emscripten port | `-sUSE_FREETYPE=1` |
|
||||
| HarfBuzz | Build for WASM | Text shaping needed |
|
||||
| zlib | Emscripten port | `-sUSE_ZLIB=1` |
|
||||
| Zstd | Build for WASM | Compression needed |
|
||||
| OpenCASCADE | Build for WASM | 3D/STEP support |
|
||||
| ngspice | Build for WASM | Simulation support |
|
||||
| Cairo | Build for WASM | 2D rendering fallback |
|
||||
| Pixman | Build for WASM | Cairo dependency |
|
||||
| libgit2 | **STUB** | No git in browser |
|
||||
| CURL | **STUB** | Use fetch API instead |
|
||||
| nanodbc | **STUB** | No ODBC in browser |
|
||||
| Python/SWIG | **DISABLE** | No Python scripting |
|
||||
| nng | **STUB** | No IPC in browser |
|
||||
| SPNAV | **STUB** | No 3D mouse in browser |
|
||||
|
||||
---
|
||||
|
||||
## Compatibility Layer Structure
|
||||
|
||||
### Directory Layout
|
||||
|
||||
```
|
||||
wasm/
|
||||
├── CMakeLists.txt # Master WASM compat build
|
||||
├── kiplatform/ # Platform layer for WASM
|
||||
│ ├── CMakeLists.txt
|
||||
│ ├── app.cpp # App lifecycle
|
||||
│ ├── drivers.cpp # GPU detection ("WebGL")
|
||||
│ ├── environment.cpp # Env vars via localStorage
|
||||
│ ├── io.cpp # Virtual filesystem
|
||||
│ ├── policy.cpp # Permissions (always allow)
|
||||
│ ├── secrets.cpp # Credentials (localStorage)
|
||||
│ ├── sysinfo.cpp # System info
|
||||
│ └── printing.cpp # Browser print()
|
||||
├── libcontext/ # Coroutine implementation
|
||||
│ ├── CMakeLists.txt
|
||||
│ └── fcontext_wasm.cpp # Asyncify fiber impl
|
||||
├── shims/ # Header overrides
|
||||
│ ├── CMakeLists.txt
|
||||
│ ├── kiplatform_redirect.h # Redirect to our impl
|
||||
│ └── libcontext_redirect.h # Redirect to our impl
|
||||
└── config/ # Build configuration
|
||||
├── kicad_wasm_config.h # Version/feature config
|
||||
└── setup.h # Platform setup
|
||||
```
|
||||
|
||||
### Existing Stubs (Already Done)
|
||||
|
||||
```
|
||||
stubs/
|
||||
├── include/
|
||||
│ ├── curl/curl.h, easy.h # CURL stubs
|
||||
│ ├── git2.h # libgit2 stub
|
||||
│ ├── git2/sys/errors.h, merge.h # libgit2 internals
|
||||
│ ├── ngspice/sharedspice.h # ngspice header (for stub build)
|
||||
│ └── Standard_Version.hxx # OCC version stub
|
||||
└── src/
|
||||
├── disabled_features_stubs.cpp # CURL/git function stubs
|
||||
├── kicad_git_stubs.cpp # Git feature stubs
|
||||
├── kicad_git_all_stubs.cpp # Complete git stubs
|
||||
├── occ_stubs.cpp # OpenCASCADE stubs
|
||||
└── panel_git_repos_stub.cpp # Git UI stubs
|
||||
```
|
||||
|
||||
### CMake Overrides (Already Done)
|
||||
|
||||
```
|
||||
cmake/
|
||||
├── FindCURL.cmake # Returns stub target
|
||||
├── FindOCC.cmake # Configurable real/stub
|
||||
├── Findlibgit2.cmake # Returns stub target
|
||||
├── Findngspice.cmake # Configurable real/stub
|
||||
└── KicadWasmOptions.cmake # Feature flags
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build Phases
|
||||
|
||||
### Phase 1: Build Infrastructure
|
||||
|
||||
Create common utilities:
|
||||
|
||||
```bash
|
||||
scripts/
|
||||
├── common/
|
||||
│ ├── env.sh # Emscripten environment, paths
|
||||
│ ├── functions.sh # Error handling, logging
|
||||
│ └── versions.sh # Dependency versions (from above table)
|
||||
├── build-kicad-wasm.sh # Master orchestrator
|
||||
└── build-deps/ # Per-dependency scripts
|
||||
```
|
||||
|
||||
**versions.sh** - Pin to KiCad's required versions:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Versions matching KiCad 8.99 requirements
|
||||
|
||||
export KICAD_COMMIT="4bfed3f1746e8cc0a7d942767770f56fa28b393c"
|
||||
|
||||
# From vcpkg.json overrides
|
||||
export GLM_VERSION="0.9.9.8"
|
||||
export NGSPICE_VERSION="45.2"
|
||||
export PROTOBUF_VERSION="3.21.12"
|
||||
|
||||
# From CMakeLists.txt minimums
|
||||
export WXWIDGETS_MIN="3.2.0"
|
||||
export GLM_MIN="0.9.8"
|
||||
export BOOST_MIN="1.71.0"
|
||||
export FREETYPE_MIN="2.11.1"
|
||||
export CAIRO_MIN="1.12"
|
||||
export PIXMAN_MIN="0.30"
|
||||
export LIBGIT2_MIN="1.5"
|
||||
export OCC_MIN="7.5.0"
|
||||
|
||||
# Recommended versions for WASM build
|
||||
export OCC_VERSION="7.8.0"
|
||||
export ZSTD_VERSION="1.5.5"
|
||||
export HARFBUZZ_VERSION="8.3.0"
|
||||
```
|
||||
|
||||
### Phase 2: Dependencies (In Order)
|
||||
|
||||
Build these BEFORE KiCad:
|
||||
|
||||
| Order | Dependency | Script | Notes |
|
||||
|-------|------------|--------|-------|
|
||||
| 1 | zlib | Emscripten port | `-sUSE_ZLIB=1` |
|
||||
| 2 | Zstd | `build-zstd-wasm.sh` | Compression |
|
||||
| 3 | FreeType | Emscripten port | `-sUSE_FREETYPE=1` |
|
||||
| 4 | HarfBuzz | `build-harfbuzz-wasm.sh` | Text shaping |
|
||||
| 5 | Pixman | `build-pixman-wasm.sh` | Cairo dep |
|
||||
| 6 | Cairo | `build-cairo-wasm.sh` | 2D rendering |
|
||||
| 7 | Boost | Headers only | Copy headers |
|
||||
| 8 | Protobuf | `build-protobuf-wasm.sh` | If IPC needed |
|
||||
| 9 | OpenCASCADE | `build-occ-wasm.sh` | 3D/STEP (large) |
|
||||
| 10 | ngspice | `build-ngspice-wasm.sh` | Simulation |
|
||||
| 11 | wxWidgets | Already done | `build-wxuniversal-wasm.sh` |
|
||||
|
||||
### Phase 3: WASM Compatibility Layer
|
||||
|
||||
Create `wasm/` directory with platform implementations.
|
||||
|
||||
#### kiplatform WASM (wasm/kiplatform/)
|
||||
|
||||
These files provide WASM implementations of KiCad's platform abstraction:
|
||||
|
||||
```cpp
|
||||
// wasm/kiplatform/app.cpp
|
||||
#include <kiplatform/app.h>
|
||||
|
||||
namespace KIPLATFORM::APP {
|
||||
bool Init() { return true; }
|
||||
wxString GetUserConfigPath() { return "/home/kicad"; }
|
||||
wxString GetUserDataPath() { return "/home/kicad"; }
|
||||
// ... etc
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// wasm/kiplatform/environment.cpp
|
||||
#include <kiplatform/environment.h>
|
||||
#include <emscripten.h>
|
||||
|
||||
namespace KIPLATFORM::ENV {
|
||||
wxString GetEnv(const wxString& var) {
|
||||
// Use localStorage via JS
|
||||
char* val = (char*)EM_ASM_PTR({
|
||||
var key = UTF8ToString($0);
|
||||
var val = localStorage.getItem('env_' + key) || '';
|
||||
return stringToNewUTF8(val);
|
||||
}, var.c_str());
|
||||
wxString result(val);
|
||||
free(val);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### libcontext Asyncify Fibers (wasm/libcontext/)
|
||||
|
||||
```cpp
|
||||
// wasm/libcontext/fcontext_wasm.cpp
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten/fiber.h>
|
||||
|
||||
// Provide same interface as libcontext but using Emscripten fibers
|
||||
struct fcontext_transfer {
|
||||
void* fctx;
|
||||
void* data;
|
||||
};
|
||||
|
||||
static emscripten_fiber_t main_fiber;
|
||||
static bool main_fiber_initialized = false;
|
||||
|
||||
extern "C" {
|
||||
fcontext_transfer jump_fcontext(void* to, void* vp);
|
||||
void* make_fcontext(void* sp, size_t size, void (*fn)(fcontext_transfer));
|
||||
}
|
||||
|
||||
// Implementation using emscripten_fiber_* APIs
|
||||
#endif
|
||||
```
|
||||
|
||||
### Phase 4: KiCad Build
|
||||
|
||||
**Script**: `scripts/build-pcbnew-wasm.sh`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source "$(dirname "$0")/common/env.sh"
|
||||
|
||||
# Key: Override include paths to use our compatibility layers FIRST
|
||||
WASM_INCLUDES="-I$PROJECT_ROOT/wasm/kiplatform"
|
||||
WASM_INCLUDES="$WASM_INCLUDES -I$PROJECT_ROOT/wasm/libcontext"
|
||||
WASM_INCLUDES="$WASM_INCLUDES -I$PROJECT_ROOT/wasm/shims"
|
||||
WASM_INCLUDES="$WASM_INCLUDES -I$PROJECT_ROOT/stubs/include"
|
||||
|
||||
emcmake cmake ../kicad \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_CXX_FLAGS="$WASM_INCLUDES" \
|
||||
\
|
||||
# Use our CMake modules for stubs
|
||||
-DCMAKE_MODULE_PATH="$PROJECT_ROOT/cmake" \
|
||||
\
|
||||
# Feature flags
|
||||
-DKICAD_USE_OCC=ON \
|
||||
-DKICAD_USE_NGSPICE=ON \
|
||||
-DKICAD_USE_GIT=OFF \
|
||||
-DKICAD_USE_CURL=OFF \
|
||||
-DKICAD_SCRIPTING_WXPYTHON=OFF \
|
||||
-DKICAD_IPC_API=OFF \
|
||||
-DKICAD_BUILD_QA_TESTS=OFF \
|
||||
-DKICAD_BUILD_I18N=OFF \
|
||||
\
|
||||
# Point to our builds
|
||||
-DwxWidgets_CONFIG_EXECUTABLE="$WX_BUILD/wx-config" \
|
||||
-DOCC_INCLUDE_DIR="$SYSROOT/include/opencascade" \
|
||||
-DNGSPICE_LIBRARY="$SYSROOT/lib/libngspice.a"
|
||||
|
||||
emmake make pcbnew -j$(nproc)
|
||||
```
|
||||
|
||||
### Phase 5: Testing
|
||||
|
||||
Follow existing patterns in `tests/`:
|
||||
|
||||
```
|
||||
tests/wasm-app/standalone/pcbnew/
|
||||
├── pcbnew_test.cpp # Minimal PCBnew test app
|
||||
├── pcbnew_test.html # Generated
|
||||
├── pcbnew_test.js # Generated
|
||||
└── pcbnew_test.wasm # Generated
|
||||
|
||||
tests/e2e/
|
||||
└── pcbnew.spec.ts # Playwright E2E tests
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Link Flags
|
||||
|
||||
```bash
|
||||
# Core flags
|
||||
-sALLOW_MEMORY_GROWTH=1
|
||||
-sINITIAL_MEMORY=256MB
|
||||
-sSTACK_SIZE=5MB
|
||||
|
||||
# Async/modal support
|
||||
-sASYNCIFY=1
|
||||
-sASYNCIFY_STACK_SIZE=16384
|
||||
|
||||
# OpenGL/WebGL
|
||||
-sLEGACY_GL_EMULATION
|
||||
-sMAX_WEBGL_VERSION=2
|
||||
|
||||
# Threading
|
||||
-pthread
|
||||
-sPROXY_TO_PTHREAD=1
|
||||
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency
|
||||
-sOFFSCREENCANVAS_SUPPORT=1
|
||||
```
|
||||
|
||||
Server headers for pthreads:
|
||||
```
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Organization Summary
|
||||
|
||||
### What Goes Where
|
||||
|
||||
| Code Type | Location | Reason |
|
||||
|-----------|----------|--------|
|
||||
| WASM platform impl | `wasm/kiplatform/` | Don't touch kicad/ |
|
||||
| Fiber implementation | `wasm/libcontext/` | Don't touch kicad/ |
|
||||
| Header overrides | `wasm/shims/` | Include path override |
|
||||
| Stub headers | `stubs/include/` | Already exists |
|
||||
| Stub implementations | `stubs/src/` | Already exists |
|
||||
| CMake finders | `cmake/` | Already exists |
|
||||
| Build scripts | `scripts/` | Reproducible |
|
||||
| Test apps | `tests/wasm-app/` | Existing pattern |
|
||||
| E2E tests | `tests/e2e/` | Existing pattern |
|
||||
|
||||
### Files to Create
|
||||
|
||||
```
|
||||
NEW:
|
||||
├── wasm/
|
||||
│ ├── CMakeLists.txt
|
||||
│ ├── kiplatform/*.cpp (8 files)
|
||||
│ ├── libcontext/fcontext_wasm.cpp
|
||||
│ ├── shims/*.h
|
||||
│ └── config/*.h
|
||||
├── scripts/
|
||||
│ ├── common/{env,functions,versions}.sh
|
||||
│ ├── build-kicad-wasm.sh
|
||||
│ ├── build-pcbnew-wasm.sh
|
||||
│ └── build-deps/*.sh
|
||||
├── stubs/src/nanodbc_stub.cpp
|
||||
└── tests/
|
||||
├── wasm-app/standalone/pcbnew/*
|
||||
└── e2e/pcbnew.spec.ts
|
||||
```
|
||||
|
||||
### Files NOT to Modify
|
||||
|
||||
```
|
||||
DO NOT MODIFY:
|
||||
├── kicad/ # Git submodule - use compatibility layers instead
|
||||
└── wxwidgets/ # Git submodule - WASM platform already added
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Patches (Only If Absolutely Necessary)
|
||||
|
||||
If patches are unavoidable, they go in `patches/` with clear documentation:
|
||||
|
||||
```
|
||||
patches/
|
||||
├── kicad/
|
||||
│ ├── 0001-*.patch
|
||||
│ ├── checksums.sha256
|
||||
│ └── README.md # Explain WHY each patch is needed
|
||||
├── opencascade/
|
||||
│ └── *.patch # OCC WASM compatibility
|
||||
└── ngspice/
|
||||
└── *.patch # Remove fork/exec
|
||||
```
|
||||
|
||||
**Rule**: Before creating a patch, ask "Can this be done with a compatibility layer instead?"
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### MVP (Milestone 1)
|
||||
- [ ] All dependencies built for WASM
|
||||
- [ ] PCBnew window opens in browser
|
||||
- [ ] Menu bar and toolbars visible
|
||||
- [ ] Can load .kicad_pcb file
|
||||
- [ ] Board renders in WebGL
|
||||
- [ ] Pan/zoom works
|
||||
|
||||
### Full Features (Milestone 2)
|
||||
- [ ] All editing tools work
|
||||
- [ ] Interactive router works (via Asyncify fibers)
|
||||
- [ ] Zone filling works (via pthreads)
|
||||
- [ ] DRC runs
|
||||
- [ ] Save/export works
|
||||
- [ ] 3D viewer works (OCC)
|
||||
- [ ] Simulation works (ngspice)
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Set up Emscripten (already available via homebrew)
|
||||
# Emscripten is at /opt/homebrew/bin/emcc
|
||||
|
||||
# 2. Build wxWidgets (if not already done)
|
||||
./scripts/build-wxuniversal-wasm.sh
|
||||
|
||||
# 3. Build all dependencies
|
||||
./scripts/deps/build-all-deps.sh --all
|
||||
|
||||
# 4. Build PCBnew for WASM
|
||||
./scripts/build-pcbnew-wasm.sh
|
||||
|
||||
# 5. Test
|
||||
cd tests && npm test
|
||||
|
||||
# 6. Serve (with COOP/COEP headers for SharedArrayBuffer)
|
||||
cd build-wasm && npx serve -p 8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### Created Files
|
||||
|
||||
#### Build Infrastructure (`scripts/common/`)
|
||||
- `env.sh` - Environment setup (paths, Emscripten config)
|
||||
- `functions.sh` - Utility functions (logging, downloads, stamps)
|
||||
- `versions.sh` - Pinned dependency versions from KiCad
|
||||
|
||||
#### Dependency Build Scripts (`scripts/deps/`)
|
||||
- `build-all-deps.sh` - Master dependency builder
|
||||
- `build-zstd.sh` - Compression library
|
||||
- `build-freetype.sh` - Font rendering
|
||||
- `build-harfbuzz.sh` - Text shaping
|
||||
- `build-pixman.sh` - Pixel manipulation
|
||||
- `build-cairo.sh` - 2D graphics
|
||||
- `build-glm.sh` - Math library (header-only)
|
||||
- `build-protobuf.sh` - Protocol buffers
|
||||
- `build-opencascade.sh` - 3D geometry/STEP
|
||||
- `build-ngspice.sh` - SPICE simulation
|
||||
|
||||
#### WASM Compatibility Layer (`wasm/`)
|
||||
- `CMakeLists.txt` - Main CMake configuration
|
||||
- `README.md` - Documentation
|
||||
- `kiplatform/CMakeLists.txt`
|
||||
- `kiplatform/app.cpp` - Application lifecycle
|
||||
- `kiplatform/drivers.cpp` - 3D mouse (stub)
|
||||
- `kiplatform/environment.cpp` - Environment/paths with localStorage
|
||||
- `kiplatform/io.cpp` - File I/O for virtual filesystem
|
||||
- `kiplatform/policy.cpp` - Enterprise policies (stub)
|
||||
- `kiplatform/secrets.cpp` - Credential storage via localStorage
|
||||
- `kiplatform/sysinfo.cpp` - System info via WebGL/navigator
|
||||
- `kiplatform/ui.cpp` - UI utilities (theme detection, etc.)
|
||||
- `libcontext/CMakeLists.txt`
|
||||
- `libcontext/libcontext_wasm.h` - Asyncify fiber header
|
||||
- `libcontext/libcontext_wasm.cpp` - Asyncify fiber implementation
|
||||
- `cmake/KiCadWASMConfig.cmake` - CMake config for WASM
|
||||
- `cmake/FindKiplatformWASM.cmake` - Find module for kiplatform
|
||||
- `cmake/FindLibcontextWASM.cmake` - Find module for libcontext
|
||||
|
||||
#### PCBnew Build
|
||||
- `scripts/build-pcbnew-wasm.sh` - Main PCBnew build script
|
||||
|
||||
#### Tests (`tests/kicad/`)
|
||||
- `pcbnew.html` - Test app HTML
|
||||
- `pcbnew.spec.ts` - Playwright E2E tests
|
||||
|
||||
### Known Issues
|
||||
|
||||
1. **CMake Policy**: Zstd and some older libraries need `-DCMAKE_POLICY_VERSION_MINIMUM=3.5`
|
||||
to work with modern CMake
|
||||
|
||||
2. **Shell Environment**: When sourcing scripts, use a fresh bash (`bash -c '...'`) to avoid
|
||||
conflicts with existing environment variables
|
||||
217
scripts/build-kicad-wasm.sh
Executable file
217
scripts/build-kicad-wasm.sh
Executable file
|
|
@ -0,0 +1,217 @@
|
|||
#!/bin/bash
|
||||
# Master build script for KiCad PCBnew WASM
|
||||
# Usage: ./build-kicad-wasm.sh [OPTIONS]
|
||||
#
|
||||
# Options:
|
||||
# --clean Full clean rebuild
|
||||
# --deps-only Only build dependencies
|
||||
# --skip-deps Skip dependency builds
|
||||
# --with-occ Enable OpenCASCADE (3D/STEP support)
|
||||
# --with-ngspice Enable ngspice (simulation)
|
||||
# --with-pthread Enable pthreads (multi-threading)
|
||||
# --debug Debug build with symbols
|
||||
# -j N Parallel jobs (default: nproc)
|
||||
# --help Show this help
|
||||
|
||||
set -e
|
||||
|
||||
# Source common environment
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common/env.sh"
|
||||
|
||||
# Default options
|
||||
DEPS_ONLY=0
|
||||
SKIP_DEPS=0
|
||||
WITH_OCC=0
|
||||
WITH_NGSPICE=0
|
||||
WITH_PTHREAD=0
|
||||
|
||||
# Show help
|
||||
show_help() {
|
||||
head -20 "$0" | tail -18 | sed 's/^# //' | sed 's/^#//'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--help|-h)
|
||||
show_help
|
||||
;;
|
||||
--clean)
|
||||
CLEAN_BUILD=1
|
||||
shift
|
||||
;;
|
||||
--deps-only)
|
||||
DEPS_ONLY=1
|
||||
shift
|
||||
;;
|
||||
--skip-deps)
|
||||
SKIP_DEPS=1
|
||||
shift
|
||||
;;
|
||||
--with-occ)
|
||||
WITH_OCC=1
|
||||
shift
|
||||
;;
|
||||
--with-ngspice)
|
||||
WITH_NGSPICE=1
|
||||
shift
|
||||
;;
|
||||
--with-pthread)
|
||||
WITH_PTHREAD=1
|
||||
shift
|
||||
;;
|
||||
--debug)
|
||||
DEBUG_BUILD=1
|
||||
shift
|
||||
;;
|
||||
-j)
|
||||
PARALLEL_JOBS="$2"
|
||||
shift 2
|
||||
;;
|
||||
-j*)
|
||||
PARALLEL_JOBS="${1#-j}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
show_help
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Set defaults if not set
|
||||
CLEAN_BUILD=${CLEAN_BUILD:-0}
|
||||
DEBUG_BUILD=${DEBUG_BUILD:-0}
|
||||
PARALLEL_JOBS=${PARALLEL_JOBS:-$(get_nproc)}
|
||||
|
||||
# Export for sub-scripts
|
||||
export CLEAN_BUILD DEBUG_BUILD PARALLEL_JOBS WITH_OCC WITH_NGSPICE WITH_PTHREAD
|
||||
|
||||
# Print configuration
|
||||
echo "========================================"
|
||||
echo "KiCad WASM Build"
|
||||
echo "========================================"
|
||||
echo " Clean build: $CLEAN_BUILD"
|
||||
echo " Debug build: $DEBUG_BUILD"
|
||||
echo " Deps only: $DEPS_ONLY"
|
||||
echo " Skip deps: $SKIP_DEPS"
|
||||
echo " With OCC: $WITH_OCC"
|
||||
echo " With ngspice: $WITH_NGSPICE"
|
||||
echo " With pthreads: $WITH_PTHREAD"
|
||||
echo " Parallel jobs: $PARALLEL_JOBS"
|
||||
echo "========================================"
|
||||
|
||||
# Verify prerequisites
|
||||
setup_error_trap
|
||||
verify_emscripten
|
||||
verify_submodules "$PROJECT_ROOT"
|
||||
|
||||
# Clean if requested
|
||||
if [ "$CLEAN_BUILD" = "1" ]; then
|
||||
log_step "Cleaning previous builds..."
|
||||
rm -rf "$STAMPS_DIR"/*
|
||||
rm -rf "$DEPS_ROOT"/*
|
||||
rm -rf "$BUILD_ROOT/kicad"
|
||||
fi
|
||||
|
||||
# Build dependencies
|
||||
if [ "$SKIP_DEPS" != "1" ]; then
|
||||
log_step "Building dependencies..."
|
||||
|
||||
# Tier 1: Header-only (just verify they exist)
|
||||
log_info "Tier 1: Header-only libraries (no build needed)"
|
||||
|
||||
# Tier 2: Simple C/C++ libraries from thirdparty
|
||||
log_info "Tier 2: Simple libraries"
|
||||
# These are built as part of KiCad's CMake, no separate build needed
|
||||
|
||||
# Tier 3: Emscripten ports
|
||||
log_info "Tier 3: Emscripten ports (zlib, freetype via -sUSE_*)"
|
||||
# These are linked via emscripten flags, no separate build
|
||||
|
||||
# Build Zstd
|
||||
if [ -f "$SCRIPT_DIR/build-deps/build-zstd-wasm.sh" ]; then
|
||||
build_if_needed "zstd" "$SCRIPT_DIR/build-deps/build-zstd-wasm.sh" "$CLEAN_BUILD"
|
||||
else
|
||||
log_warn "Zstd build script not found, skipping"
|
||||
fi
|
||||
|
||||
# Build HarfBuzz
|
||||
if [ -f "$SCRIPT_DIR/build-deps/build-harfbuzz-wasm.sh" ]; then
|
||||
build_if_needed "harfbuzz" "$SCRIPT_DIR/build-deps/build-harfbuzz-wasm.sh" "$CLEAN_BUILD"
|
||||
else
|
||||
log_warn "HarfBuzz build script not found, skipping"
|
||||
fi
|
||||
|
||||
# Build Pixman
|
||||
if [ -f "$SCRIPT_DIR/build-deps/build-pixman-wasm.sh" ]; then
|
||||
build_if_needed "pixman" "$SCRIPT_DIR/build-deps/build-pixman-wasm.sh" "$CLEAN_BUILD"
|
||||
else
|
||||
log_warn "Pixman build script not found, skipping"
|
||||
fi
|
||||
|
||||
# Build Cairo
|
||||
if [ -f "$SCRIPT_DIR/build-deps/build-cairo-wasm.sh" ]; then
|
||||
build_if_needed "cairo" "$SCRIPT_DIR/build-deps/build-cairo-wasm.sh" "$CLEAN_BUILD"
|
||||
else
|
||||
log_warn "Cairo build script not found, skipping"
|
||||
fi
|
||||
|
||||
# Tier 4: Complex dependencies
|
||||
if [ "$WITH_OCC" = "1" ]; then
|
||||
log_info "Tier 4: Building OpenCASCADE..."
|
||||
if [ -f "$SCRIPT_DIR/build-deps/build-opencascade-wasm.sh" ]; then
|
||||
build_if_needed "opencascade" "$SCRIPT_DIR/build-deps/build-opencascade-wasm.sh" "$CLEAN_BUILD"
|
||||
else
|
||||
log_warn "OpenCASCADE build script not found"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$WITH_NGSPICE" = "1" ]; then
|
||||
log_info "Tier 4: Building ngspice..."
|
||||
if [ -f "$SCRIPT_DIR/build-deps/build-ngspice-wasm.sh" ]; then
|
||||
build_if_needed "ngspice" "$SCRIPT_DIR/build-deps/build-ngspice-wasm.sh" "$CLEAN_BUILD"
|
||||
else
|
||||
log_warn "ngspice build script not found"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify wxWidgets is built
|
||||
if [ ! -f "$WX_BUILD/wx-config" ]; then
|
||||
log_step "Building wxWidgets..."
|
||||
"$SCRIPT_DIR/build-wxuniversal-wasm.sh"
|
||||
else
|
||||
log_info "wxWidgets already built"
|
||||
fi
|
||||
|
||||
log_info "Dependencies complete"
|
||||
fi
|
||||
|
||||
if [ "$DEPS_ONLY" = "1" ]; then
|
||||
log_info "Dependency build complete (--deps-only specified)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build KiCad compatibility layer
|
||||
log_step "Building WASM compatibility layer..."
|
||||
if [ -f "$SCRIPT_DIR/build-wasm-compat.sh" ]; then
|
||||
"$SCRIPT_DIR/build-wasm-compat.sh"
|
||||
else
|
||||
log_warn "WASM compatibility layer script not found, skipping"
|
||||
fi
|
||||
|
||||
# Build KiCad PCBnew
|
||||
log_step "Building KiCad PCBnew..."
|
||||
if [ -f "$SCRIPT_DIR/build-pcbnew-wasm.sh" ]; then
|
||||
"$SCRIPT_DIR/build-pcbnew-wasm.sh"
|
||||
else
|
||||
log_error "PCBnew build script not found: $SCRIPT_DIR/build-pcbnew-wasm.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "========================================"
|
||||
log_info "Build complete!"
|
||||
log_info "Output: $BUILD_ROOT/kicad/pcbnew/"
|
||||
log_info "========================================"
|
||||
110
scripts/build-pcbnew-wasm.sh
Executable file
110
scripts/build-pcbnew-wasm.sh
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
#!/bin/bash
|
||||
# Build KiCad PCBnew for WebAssembly
|
||||
# This builds the PCB editor as a standalone WASM application
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/common/env.sh"
|
||||
source "${SCRIPT_DIR}/common/versions.sh"
|
||||
source "${SCRIPT_DIR}/common/functions.sh"
|
||||
|
||||
KICAD_DIR="${PROJECT_ROOT}/kicad"
|
||||
KICAD_BUILD="${BUILD_ROOT}/kicad"
|
||||
KICAD_STAMP="${BUILD_ROOT}/stamps/kicad-pcbnew.stamp"
|
||||
WASM_LAYER="${PROJECT_ROOT}/wasm"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
SKIP_DEPS=0
|
||||
DEBUG=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=1
|
||||
;;
|
||||
--skip-deps)
|
||||
SKIP_DEPS=1
|
||||
;;
|
||||
--debug)
|
||||
DEBUG=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $CLEAN -eq 1 ]; then
|
||||
log_info "Cleaning KiCad build..."
|
||||
rm -rf "${KICAD_BUILD}" "${KICAD_STAMP}"
|
||||
fi
|
||||
|
||||
# Build dependencies first
|
||||
if [ $SKIP_DEPS -eq 0 ]; then
|
||||
log_info "Building dependencies..."
|
||||
"${SCRIPT_DIR}/deps/build-all-deps.sh" --all
|
||||
fi
|
||||
|
||||
# Check if already built
|
||||
if check_stamp "${KICAD_STAMP}"; then
|
||||
log_info "KiCad PCBnew already built, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Ensure wxWidgets is built
|
||||
if [ ! -f "${SYSROOT}/lib/libwx_baseu-3.2.a" ]; then
|
||||
log_error "wxWidgets not found. Please build wxWidgets first with:"
|
||||
log_error " ./scripts/build-wxuniversal-wasm.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Building KiCad PCBnew ${KICAD_VERSION} for WASM..."
|
||||
|
||||
# Set build type
|
||||
if [ $DEBUG -eq 1 ]; then
|
||||
BUILD_TYPE="Debug"
|
||||
EXTRA_FLAGS="-g -O0"
|
||||
else
|
||||
BUILD_TYPE="Release"
|
||||
EXTRA_FLAGS="-O2"
|
||||
fi
|
||||
|
||||
mkdir -p "${KICAD_BUILD}"
|
||||
cd "${KICAD_BUILD}"
|
||||
|
||||
# Configure KiCad with WASM-specific options
|
||||
# We use CMAKE_MODULE_PATH to inject our compatibility layer
|
||||
emcmake cmake "${KICAD_DIR}" \
|
||||
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
|
||||
-DCMAKE_INSTALL_PREFIX="${SYSROOT}" \
|
||||
-DCMAKE_MODULE_PATH="${WASM_LAYER}/cmake" \
|
||||
-DCMAKE_CXX_FLAGS="${EXTRA_FLAGS} -pthread -DKICAD_USE_PLATFORM_WASM=1" \
|
||||
-DCMAKE_C_FLAGS="${EXTRA_FLAGS} -pthread" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-pthread -sASYNCIFY=1 -sASYNCIFY_STACK_SIZE=65536 -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=4 -sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=256MB -sMAXIMUM_MEMORY=4GB" \
|
||||
-DCMAKE_PREFIX_PATH="${SYSROOT}" \
|
||||
-DwxWidgets_CONFIG_EXECUTABLE="${SYSROOT}/bin/wx-config" \
|
||||
\
|
||||
-DKICAD_BUILD_QA_TESTS=OFF \
|
||||
-DKICAD_SCRIPTING=OFF \
|
||||
-DKICAD_SCRIPTING_PYTHON3=OFF \
|
||||
-DKICAD_SCRIPTING_WXPYTHON=OFF \
|
||||
-DKICAD_SPICE=ON \
|
||||
-DKICAD_USE_OCC=ON \
|
||||
-DKICAD_USE_EGL=OFF \
|
||||
-DKICAD_USE_BUNDLED_GLEW=ON \
|
||||
\
|
||||
-DOCC_INCLUDE_DIR="${SYSROOT}/include/opencascade" \
|
||||
-DOCC_LIBRARY_DIR="${SYSROOT}/lib" \
|
||||
-DNGSPICE_INCLUDE_DIR="${SYSROOT}/include" \
|
||||
-DNGSPICE_LIBRARY="${SYSROOT}/lib/libngspice.a" \
|
||||
\
|
||||
-DBUILD_GITHUB_PLUGIN=OFF \
|
||||
-DKICAD_PCM=OFF \
|
||||
\
|
||||
-DKICAD_LIBRARY_DATA="${PROJECT_ROOT}/kicad-library"
|
||||
|
||||
# Build only pcbnew and its dependencies
|
||||
# Note: We build specific targets to avoid building unnecessary components
|
||||
emmake make -j${JOBS} pcbnew
|
||||
|
||||
create_stamp "${KICAD_STAMP}"
|
||||
log_info "KiCad PCBnew build complete!"
|
||||
log_info "Output: ${KICAD_BUILD}/pcbnew/pcbnew.js"
|
||||
67
scripts/common/env.sh
Executable file
67
scripts/common/env.sh
Executable file
|
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
# Environment setup for KiCad WASM build scripts
|
||||
|
||||
# Get script and project directories using unique names to avoid conflicts
|
||||
_KICAD_WASM_COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
_KICAD_WASM_SCRIPTS_DIR="$(dirname "$_KICAD_WASM_COMMON_DIR")"
|
||||
_KICAD_WASM_PROJECT_ROOT="$(dirname "$_KICAD_WASM_SCRIPTS_DIR")"
|
||||
|
||||
# Export paths
|
||||
export PROJECT_ROOT="$_KICAD_WASM_PROJECT_ROOT"
|
||||
export SCRIPTS_DIR="$_KICAD_WASM_SCRIPTS_DIR"
|
||||
export COMMON_DIR="$_KICAD_WASM_COMMON_DIR"
|
||||
|
||||
# Build output directories (inside the project)
|
||||
export BUILD_ROOT="$PROJECT_ROOT/build-wasm"
|
||||
export DEPS_ROOT="$BUILD_ROOT/deps"
|
||||
export SYSROOT="$BUILD_ROOT/sysroot"
|
||||
export STAMPS_DIR="$BUILD_ROOT/stamps"
|
||||
|
||||
# Source directories
|
||||
export KICAD_SOURCE="$PROJECT_ROOT/kicad"
|
||||
export WXWIDGETS_SOURCE="$PROJECT_ROOT/wxwidgets"
|
||||
export WASM_COMPAT="$PROJECT_ROOT/wasm"
|
||||
export STUBS_DIR="$PROJECT_ROOT/stubs"
|
||||
export CMAKE_MODULES="$PROJECT_ROOT/cmake"
|
||||
|
||||
# wxWidgets build location
|
||||
export WX_BUILD="$BUILD_ROOT/wxwidgets-universal"
|
||||
|
||||
# Emscripten settings
|
||||
export EMSDK_QUIET=1
|
||||
|
||||
# Common compiler flags
|
||||
export EMCC_CFLAGS="-fPIC -DEMSCRIPTEN"
|
||||
export EMCC_CXXFLAGS="-fPIC -DEMSCRIPTEN -std=c++17"
|
||||
|
||||
# Common linker flags for WASM
|
||||
export WASM_LDFLAGS="\
|
||||
-sALLOW_MEMORY_GROWTH=1 \
|
||||
-sINITIAL_MEMORY=256MB \
|
||||
-sSTACK_SIZE=5MB \
|
||||
-sASYNCIFY=1 \
|
||||
-sASYNCIFY_STACK_SIZE=16384 \
|
||||
-sLEGACY_GL_EMULATION \
|
||||
-sMAX_WEBGL_VERSION=2"
|
||||
|
||||
# Threading flags (when enabled)
|
||||
export PTHREAD_LDFLAGS="\
|
||||
-pthread \
|
||||
-sPROXY_TO_PTHREAD=1 \
|
||||
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency \
|
||||
-sOFFSCREENCANVAS_SUPPORT=1"
|
||||
|
||||
# Create output directories
|
||||
mkdir -p "$BUILD_ROOT" "$DEPS_ROOT" "$SYSROOT"/{lib,include,share} "$STAMPS_DIR"
|
||||
|
||||
# Source other common files
|
||||
source "$COMMON_DIR/versions.sh"
|
||||
source "$COMMON_DIR/functions.sh"
|
||||
|
||||
# Print environment info (only if not in quiet mode)
|
||||
if [ "${QUIET:-0}" != "1" ]; then
|
||||
echo "KiCad WASM Build Environment"
|
||||
echo " Project root: $PROJECT_ROOT"
|
||||
echo " Build root: $BUILD_ROOT"
|
||||
echo " Sysroot: $SYSROOT"
|
||||
fi
|
||||
269
scripts/common/functions.sh
Executable file
269
scripts/common/functions.sh
Executable file
|
|
@ -0,0 +1,269 @@
|
|||
#!/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. Please run: source /path/to/emsdk/emsdk_env.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=0
|
||||
PARALLEL_JOBS=$(get_nproc)
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--clean)
|
||||
CLEAN_BUILD=1
|
||||
shift
|
||||
;;
|
||||
--debug)
|
||||
DEBUG_BUILD=1
|
||||
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 "========================================"
|
||||
}
|
||||
51
scripts/common/versions.sh
Executable file
51
scripts/common/versions.sh
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
#!/bin/bash
|
||||
# Dependency versions for KiCad WASM build
|
||||
# These versions match KiCad 8.99 requirements from CMakeLists.txt and vcpkg.json
|
||||
|
||||
# KiCad submodule version
|
||||
export KICAD_COMMIT="4bfed3f1746e8cc0a7d942767770f56fa28b393c"
|
||||
export KICAD_VERSION="8.99"
|
||||
|
||||
# From vcpkg.json overrides (pinned versions)
|
||||
export GLM_VERSION="0.9.9.8"
|
||||
export NGSPICE_VERSION="45.2"
|
||||
export PROTOBUF_VERSION="3.21.12"
|
||||
export PYTHON_VERSION="3.11.5"
|
||||
export WXWIDGETS_VERSION="3.3.1"
|
||||
|
||||
# From CMakeLists.txt minimum requirements
|
||||
export WXWIDGETS_MIN="3.2.0"
|
||||
export GLM_MIN="0.9.8"
|
||||
export BOOST_MIN="1.71.0"
|
||||
export FREETYPE_MIN="2.11.1"
|
||||
export CAIRO_MIN="1.12"
|
||||
export PIXMAN_MIN="0.30"
|
||||
export LIBGIT2_MIN="1.5"
|
||||
export OCC_MIN="7.5.0"
|
||||
export SWIG_MIN="4.0"
|
||||
|
||||
# Recommended versions for WASM build
|
||||
export OCC_VERSION="7.8.0"
|
||||
export ZSTD_VERSION="1.5.5"
|
||||
export FREETYPE_VERSION="2.13.2"
|
||||
export HARFBUZZ_VERSION="8.3.0"
|
||||
export CAIRO_VERSION="1.18.0"
|
||||
export PIXMAN_VERSION="0.42.2"
|
||||
export BOOST_VERSION="1.84.0"
|
||||
|
||||
# Download URLs
|
||||
export ZSTD_URL="https://github.com/facebook/zstd/releases/download/v${ZSTD_VERSION}/zstd-${ZSTD_VERSION}.tar.gz"
|
||||
export FREETYPE_URL="https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.xz"
|
||||
export HARFBUZZ_URL="https://github.com/harfbuzz/harfbuzz/releases/download/${HARFBUZZ_VERSION}/harfbuzz-${HARFBUZZ_VERSION}.tar.xz"
|
||||
export CAIRO_URL="https://cairographics.org/releases/cairo-${CAIRO_VERSION}.tar.xz"
|
||||
export PIXMAN_URL="https://cairographics.org/releases/pixman-${PIXMAN_VERSION}.tar.gz"
|
||||
export OCC_URL="https://github.com/Open-Cascade-SAS/OCCT/archive/refs/tags/V${OCC_VERSION//./_}.tar.gz"
|
||||
export NGSPICE_URL="https://sourceforge.net/projects/ngspice/files/ng-spice-rework/${NGSPICE_VERSION}/ngspice-${NGSPICE_VERSION}.tar.gz/download"
|
||||
|
||||
# SHA256 checksums (to be filled in after first successful download)
|
||||
# export ZSTD_SHA256=""
|
||||
# export HARFBUZZ_SHA256=""
|
||||
# export CAIRO_SHA256=""
|
||||
# export PIXMAN_SHA256=""
|
||||
# export OCC_SHA256=""
|
||||
# export NGSPICE_SHA256=""
|
||||
71
scripts/deps/build-all-deps.sh
Executable file
71
scripts/deps/build-all-deps.sh
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
#!/bin/bash
|
||||
# Build all KiCad dependencies for WebAssembly
|
||||
# This script builds dependencies in the correct order
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/env.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
log_info "Building all KiCad dependencies for WASM..."
|
||||
log_info "Using ${JOBS} parallel jobs"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=""
|
||||
WITH_OCC=0
|
||||
WITH_NGSPICE=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN="--clean"
|
||||
;;
|
||||
--with-occ)
|
||||
WITH_OCC=1
|
||||
;;
|
||||
--with-ngspice)
|
||||
WITH_NGSPICE=1
|
||||
;;
|
||||
--all)
|
||||
WITH_OCC=1
|
||||
WITH_NGSPICE=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Build dependencies in order
|
||||
|
||||
# 1. Header-only libraries (no dependencies)
|
||||
log_info "=== Phase 1: Header-only libraries ==="
|
||||
"${SCRIPT_DIR}/build-glm.sh" ${CLEAN}
|
||||
|
||||
# 2. Basic libraries (minimal dependencies)
|
||||
log_info "=== Phase 2: Basic compression/serialization ==="
|
||||
"${SCRIPT_DIR}/build-zstd.sh" ${CLEAN}
|
||||
"${SCRIPT_DIR}/build-protobuf.sh" ${CLEAN}
|
||||
|
||||
# 3. Font rendering stack
|
||||
log_info "=== Phase 3: Font rendering ==="
|
||||
"${SCRIPT_DIR}/build-freetype.sh" ${CLEAN}
|
||||
"${SCRIPT_DIR}/build-harfbuzz.sh" ${CLEAN}
|
||||
|
||||
# 4. Graphics stack (optional, for Cairo rendering)
|
||||
log_info "=== Phase 4: Graphics libraries ==="
|
||||
"${SCRIPT_DIR}/build-pixman.sh" ${CLEAN}
|
||||
"${SCRIPT_DIR}/build-cairo.sh" ${CLEAN}
|
||||
|
||||
# 5. Optional heavy dependencies
|
||||
if [ $WITH_OCC -eq 1 ]; then
|
||||
log_info "=== Phase 5a: OpenCASCADE (3D/STEP support) ==="
|
||||
"${SCRIPT_DIR}/build-opencascade.sh" ${CLEAN}
|
||||
fi
|
||||
|
||||
if [ $WITH_NGSPICE -eq 1 ]; then
|
||||
log_info "=== Phase 5b: ngspice (SPICE simulation) ==="
|
||||
"${SCRIPT_DIR}/build-ngspice.sh" ${CLEAN}
|
||||
fi
|
||||
|
||||
log_info "============================================"
|
||||
log_info "All dependencies built successfully!"
|
||||
log_info "Install prefix: ${SYSROOT}"
|
||||
log_info "============================================"
|
||||
111
scripts/deps/build-cairo.sh
Executable file
111
scripts/deps/build-cairo.sh
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
#!/bin/bash
|
||||
# Build Cairo for WebAssembly
|
||||
# Cairo provides 2D graphics rendering
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/env.sh"
|
||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
# Cairo requires Pixman and FreeType
|
||||
"${SCRIPT_DIR}/build-pixman.sh"
|
||||
"${SCRIPT_DIR}/build-freetype.sh"
|
||||
|
||||
CAIRO_DIR="${DEPS_ROOT}/cairo-${CAIRO_VERSION}"
|
||||
CAIRO_BUILD="${BUILD_ROOT}/deps/cairo"
|
||||
CAIRO_STAMP="${BUILD_ROOT}/stamps/cairo.stamp"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=1
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $CLEAN -eq 1 ]; then
|
||||
log_info "Cleaning Cairo build..."
|
||||
rm -rf "${CAIRO_BUILD}" "${CAIRO_STAMP}"
|
||||
fi
|
||||
|
||||
# Check if already built
|
||||
if check_stamp "${CAIRO_STAMP}"; then
|
||||
log_info "Cairo already built, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Download if needed
|
||||
if [ ! -d "${CAIRO_DIR}" ]; then
|
||||
log_info "Downloading Cairo ${CAIRO_VERSION}..."
|
||||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
|
||||
CAIRO_URL="https://cairographics.org/releases/cairo-${CAIRO_VERSION}.tar.xz"
|
||||
download_file "${CAIRO_URL}" "cairo-${CAIRO_VERSION}.tar.xz"
|
||||
tar -xJf "cairo-${CAIRO_VERSION}.tar.xz"
|
||||
rm "cairo-${CAIRO_VERSION}.tar.xz"
|
||||
fi
|
||||
|
||||
log_info "Building Cairo ${CAIRO_VERSION} for WASM..."
|
||||
|
||||
mkdir -p "${CAIRO_BUILD}"
|
||||
cd "${CAIRO_BUILD}"
|
||||
|
||||
# Cairo uses meson
|
||||
cat > cross-file.txt << EOF
|
||||
[binaries]
|
||||
c = 'emcc'
|
||||
cpp = 'em++'
|
||||
ar = 'emar'
|
||||
ranlib = 'emranlib'
|
||||
strip = 'emstrip'
|
||||
pkgconfig = 'pkg-config'
|
||||
|
||||
[host_machine]
|
||||
system = 'emscripten'
|
||||
cpu_family = 'wasm32'
|
||||
cpu = 'wasm32'
|
||||
endian = 'little'
|
||||
|
||||
[properties]
|
||||
# Prevent finding system libraries when cross-compiling
|
||||
sys_root = '${SYSROOT}'
|
||||
pkg_config_libdir = '${SYSROOT}/lib/pkgconfig'
|
||||
|
||||
[built-in options]
|
||||
c_args = ['-pthread', '-I${SYSROOT}/include', '-I${SYSROOT}/include/freetype2', '-I${SYSROOT}/include/pixman-1']
|
||||
c_link_args = ['-pthread', '-L${SYSROOT}/lib']
|
||||
pkg_config_path = '${SYSROOT}/lib/pkgconfig'
|
||||
EOF
|
||||
|
||||
# Set PKG_CONFIG_PATH for dependency discovery
|
||||
# Use LIBDIR to ONLY search our sysroot, preventing system lzo2 from being found
|
||||
export PKG_CONFIG_LIBDIR="${SYSROOT}/lib/pkgconfig"
|
||||
unset PKG_CONFIG_PATH
|
||||
|
||||
meson setup "${CAIRO_DIR}" \
|
||||
--cross-file cross-file.txt \
|
||||
--prefix="${SYSROOT}" \
|
||||
--default-library=static \
|
||||
-Dfontconfig=disabled \
|
||||
-Dfreetype=enabled \
|
||||
-Dglib=disabled \
|
||||
-Dpng=disabled \
|
||||
-Dxlib=disabled \
|
||||
-Dxcb=disabled \
|
||||
-Dzlib=enabled \
|
||||
-Dtests=disabled \
|
||||
-Dspectre=disabled \
|
||||
-Dsymbol-lookup=disabled
|
||||
|
||||
JOBS=${JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)}
|
||||
ninja -j${JOBS}
|
||||
ninja install
|
||||
|
||||
create_stamp "${CAIRO_STAMP}"
|
||||
log_info "Cairo build complete!"
|
||||
70
scripts/deps/build-freetype.sh
Executable file
70
scripts/deps/build-freetype.sh
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
#!/bin/bash
|
||||
# Build FreeType for WebAssembly
|
||||
# FreeType is required for font rendering
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/env.sh"
|
||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
FREETYPE_DIR="${DEPS_ROOT}/freetype-${FREETYPE_VERSION}"
|
||||
FREETYPE_BUILD="${BUILD_ROOT}/deps/freetype"
|
||||
FREETYPE_STAMP="${BUILD_ROOT}/stamps/freetype.stamp"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=1
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $CLEAN -eq 1 ]; then
|
||||
log_info "Cleaning FreeType build..."
|
||||
rm -rf "${FREETYPE_BUILD}" "${FREETYPE_STAMP}"
|
||||
fi
|
||||
|
||||
# Check if already built
|
||||
if check_stamp "${FREETYPE_STAMP}"; then
|
||||
log_info "FreeType already built, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Download if needed
|
||||
if [ ! -d "${FREETYPE_DIR}" ]; then
|
||||
log_info "Downloading FreeType ${FREETYPE_VERSION}..."
|
||||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
|
||||
FREETYPE_URL="https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.xz"
|
||||
download_file "${FREETYPE_URL}" "freetype-${FREETYPE_VERSION}.tar.xz"
|
||||
tar -xJf "freetype-${FREETYPE_VERSION}.tar.xz"
|
||||
rm "freetype-${FREETYPE_VERSION}.tar.xz"
|
||||
fi
|
||||
|
||||
log_info "Building FreeType ${FREETYPE_VERSION} for WASM..."
|
||||
|
||||
mkdir -p "${FREETYPE_BUILD}"
|
||||
cd "${FREETYPE_BUILD}"
|
||||
|
||||
emcmake cmake "${FREETYPE_DIR}" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${SYSROOT}" \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DFT_DISABLE_BZIP2=ON \
|
||||
-DFT_DISABLE_BROTLI=ON \
|
||||
-DFT_DISABLE_HARFBUZZ=ON \
|
||||
-DFT_DISABLE_PNG=ON \
|
||||
-DFT_DISABLE_ZLIB=OFF \
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
|
||||
emmake make -j${JOBS}
|
||||
emmake make install
|
||||
|
||||
create_stamp "${FREETYPE_STAMP}"
|
||||
log_info "FreeType build complete!"
|
||||
57
scripts/deps/build-glm.sh
Executable file
57
scripts/deps/build-glm.sh
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
#!/bin/bash
|
||||
# Install GLM headers for WebAssembly
|
||||
# GLM is a header-only math library
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/env.sh"
|
||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
GLM_DIR="${DEPS_ROOT}/glm-${GLM_VERSION}"
|
||||
GLM_STAMP="${BUILD_ROOT}/stamps/glm.stamp"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=1
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $CLEAN -eq 1 ]; then
|
||||
log_info "Cleaning GLM install..."
|
||||
rm -rf "${GLM_STAMP}"
|
||||
fi
|
||||
|
||||
# Check if already installed
|
||||
if check_stamp "${GLM_STAMP}"; then
|
||||
log_info "GLM already installed, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Download if needed
|
||||
if [ ! -d "${GLM_DIR}" ]; then
|
||||
log_info "Downloading GLM ${GLM_VERSION}..."
|
||||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
|
||||
GLM_URL="https://github.com/g-truc/glm/releases/download/${GLM_VERSION}/glm-${GLM_VERSION}.zip"
|
||||
download_file "${GLM_URL}" "glm-${GLM_VERSION}.zip"
|
||||
unzip -q "glm-${GLM_VERSION}.zip"
|
||||
mv glm "glm-${GLM_VERSION}"
|
||||
rm "glm-${GLM_VERSION}.zip"
|
||||
fi
|
||||
|
||||
log_info "Installing GLM ${GLM_VERSION} headers..."
|
||||
|
||||
# GLM is header-only, just copy headers
|
||||
mkdir -p "${SYSROOT}/include"
|
||||
cp -r "${GLM_DIR}/glm" "${SYSROOT}/include/"
|
||||
|
||||
create_stamp "${GLM_STAMP}"
|
||||
log_info "GLM install complete!"
|
||||
79
scripts/deps/build-harfbuzz.sh
Executable file
79
scripts/deps/build-harfbuzz.sh
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
#!/bin/bash
|
||||
# Build HarfBuzz for WebAssembly
|
||||
# HarfBuzz is used for text shaping in KiCad
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/env.sh"
|
||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
# HarfBuzz requires FreeType
|
||||
"${SCRIPT_DIR}/build-freetype.sh"
|
||||
|
||||
HARFBUZZ_DIR="${DEPS_ROOT}/harfbuzz-${HARFBUZZ_VERSION}"
|
||||
HARFBUZZ_BUILD="${BUILD_ROOT}/deps/harfbuzz"
|
||||
HARFBUZZ_STAMP="${BUILD_ROOT}/stamps/harfbuzz.stamp"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=1
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $CLEAN -eq 1 ]; then
|
||||
log_info "Cleaning HarfBuzz build..."
|
||||
rm -rf "${HARFBUZZ_BUILD}" "${HARFBUZZ_STAMP}"
|
||||
fi
|
||||
|
||||
# Check if already built
|
||||
if check_stamp "${HARFBUZZ_STAMP}"; then
|
||||
log_info "HarfBuzz already built, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Download if needed
|
||||
if [ ! -d "${HARFBUZZ_DIR}" ]; then
|
||||
log_info "Downloading HarfBuzz ${HARFBUZZ_VERSION}..."
|
||||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
|
||||
HARFBUZZ_URL="https://github.com/harfbuzz/harfbuzz/releases/download/${HARFBUZZ_VERSION}/harfbuzz-${HARFBUZZ_VERSION}.tar.xz"
|
||||
download_file "${HARFBUZZ_URL}" "harfbuzz-${HARFBUZZ_VERSION}.tar.xz"
|
||||
tar -xJf "harfbuzz-${HARFBUZZ_VERSION}.tar.xz"
|
||||
rm "harfbuzz-${HARFBUZZ_VERSION}.tar.xz"
|
||||
fi
|
||||
|
||||
log_info "Building HarfBuzz ${HARFBUZZ_VERSION} for WASM..."
|
||||
|
||||
mkdir -p "${HARFBUZZ_BUILD}"
|
||||
cd "${HARFBUZZ_BUILD}"
|
||||
|
||||
# HarfBuzz uses meson, but also has CMake support
|
||||
emcmake cmake "${HARFBUZZ_DIR}" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${SYSROOT}" \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DHB_HAVE_FREETYPE=ON \
|
||||
-DHB_HAVE_GLIB=OFF \
|
||||
-DHB_HAVE_ICU=OFF \
|
||||
-DHB_HAVE_GOBJECT=OFF \
|
||||
-DHB_HAVE_CAIRO=OFF \
|
||||
-DHB_BUILD_UTILS=OFF \
|
||||
-DHB_BUILD_SUBSET=OFF \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DCMAKE_PREFIX_PATH="${SYSROOT}" \
|
||||
-DFREETYPE_LIBRARY="${SYSROOT}/lib/libfreetype.a" \
|
||||
-DFREETYPE_INCLUDE_DIRS="${SYSROOT}/include/freetype2"
|
||||
|
||||
emmake make -j${JOBS}
|
||||
emmake make install
|
||||
|
||||
create_stamp "${HARFBUZZ_STAMP}"
|
||||
log_info "HarfBuzz build complete!"
|
||||
80
scripts/deps/build-ngspice.sh
Executable file
80
scripts/deps/build-ngspice.sh
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
#!/bin/bash
|
||||
# Build ngspice for WebAssembly
|
||||
# ngspice provides SPICE simulation for KiCad's Eeschema
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/env.sh"
|
||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
NGSPICE_DIR="${DEPS_ROOT}/ngspice-${NGSPICE_VERSION}"
|
||||
NGSPICE_BUILD="${BUILD_ROOT}/deps/ngspice"
|
||||
NGSPICE_STAMP="${BUILD_ROOT}/stamps/ngspice.stamp"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=1
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $CLEAN -eq 1 ]; then
|
||||
log_info "Cleaning ngspice build..."
|
||||
rm -rf "${NGSPICE_BUILD}" "${NGSPICE_STAMP}"
|
||||
fi
|
||||
|
||||
# Check if already built
|
||||
if check_stamp "${NGSPICE_STAMP}"; then
|
||||
log_info "ngspice already built, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Download if needed
|
||||
if [ ! -d "${NGSPICE_DIR}" ]; then
|
||||
log_info "Downloading ngspice ${NGSPICE_VERSION}..."
|
||||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
|
||||
NGSPICE_URL="https://sourceforge.net/projects/ngspice/files/ng-spice-rework/${NGSPICE_VERSION}/ngspice-${NGSPICE_VERSION}.tar.gz/download"
|
||||
curl -L "${NGSPICE_URL}" -o "ngspice-${NGSPICE_VERSION}.tar.gz"
|
||||
tar -xzf "ngspice-${NGSPICE_VERSION}.tar.gz"
|
||||
rm "ngspice-${NGSPICE_VERSION}.tar.gz"
|
||||
fi
|
||||
|
||||
log_info "Building ngspice ${NGSPICE_VERSION} for WASM..."
|
||||
|
||||
mkdir -p "${NGSPICE_BUILD}"
|
||||
cd "${NGSPICE_BUILD}"
|
||||
|
||||
# ngspice uses autoconf
|
||||
# First we need to configure for shared library mode (libngspice)
|
||||
export CFLAGS="-pthread"
|
||||
export CXXFLAGS="-pthread"
|
||||
export LDFLAGS="-pthread"
|
||||
|
||||
# Configure ngspice as a shared library for KiCad integration
|
||||
emconfigure "${NGSPICE_DIR}/configure" \
|
||||
--prefix="${SYSROOT}" \
|
||||
--host=wasm32-unknown-emscripten \
|
||||
--build=$(uname -m)-linux-gnu \
|
||||
--enable-shared \
|
||||
--with-ngshared \
|
||||
--disable-debug \
|
||||
--disable-dependency-tracking \
|
||||
--enable-cider \
|
||||
--enable-xspice \
|
||||
--without-x \
|
||||
--without-readline \
|
||||
--without-editline
|
||||
|
||||
emmake make -j${JOBS}
|
||||
emmake make install
|
||||
|
||||
create_stamp "${NGSPICE_STAMP}"
|
||||
log_info "ngspice build complete!"
|
||||
96
scripts/deps/build-opencascade.sh
Executable file
96
scripts/deps/build-opencascade.sh
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
#!/bin/bash
|
||||
# Build OpenCASCADE Technology (OCCT) for WebAssembly
|
||||
# OCCT provides 3D geometry kernel for STEP file import/export
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/env.sh"
|
||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
OCC_DIR="${DEPS_ROOT}/opencascade-${OCC_VERSION}"
|
||||
OCC_BUILD="${BUILD_ROOT}/deps/opencascade"
|
||||
OCC_STAMP="${BUILD_ROOT}/stamps/opencascade.stamp"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=1
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $CLEAN -eq 1 ]; then
|
||||
log_info "Cleaning OpenCASCADE build..."
|
||||
rm -rf "${OCC_BUILD}" "${OCC_STAMP}"
|
||||
fi
|
||||
|
||||
# Check if already built
|
||||
if check_stamp "${OCC_STAMP}"; then
|
||||
log_info "OpenCASCADE already built, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Download if needed
|
||||
if [ ! -d "${OCC_DIR}" ]; then
|
||||
log_info "Downloading OpenCASCADE ${OCC_VERSION}..."
|
||||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
|
||||
# OpenCASCADE releases are on GitHub
|
||||
OCC_URL="https://github.com/Open-Cascade-SAS/OCCT/archive/refs/tags/V${OCC_VERSION//./_}.tar.gz"
|
||||
download_file "${OCC_URL}" "opencascade-${OCC_VERSION}.tar.gz"
|
||||
tar -xzf "opencascade-${OCC_VERSION}.tar.gz"
|
||||
mv "OCCT-V${OCC_VERSION//./_}" "opencascade-${OCC_VERSION}"
|
||||
rm "opencascade-${OCC_VERSION}.tar.gz"
|
||||
fi
|
||||
|
||||
log_info "Building OpenCASCADE ${OCC_VERSION} for WASM..."
|
||||
log_warn "This is a large library and may take a while..."
|
||||
|
||||
mkdir -p "${OCC_BUILD}"
|
||||
cd "${OCC_BUILD}"
|
||||
|
||||
# OpenCASCADE build configuration for WASM
|
||||
# Disable GUI, visualization that needs X11/OpenGL native
|
||||
# Enable core geometry and data exchange modules only
|
||||
emcmake cmake "${OCC_DIR}" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${SYSROOT}" \
|
||||
-DCMAKE_CXX_FLAGS="-pthread -O2" \
|
||||
-DCMAKE_C_FLAGS="-pthread -O2" \
|
||||
-DBUILD_LIBRARY_TYPE=Static \
|
||||
-DBUILD_MODULE_ApplicationFramework=OFF \
|
||||
-DBUILD_MODULE_Draw=OFF \
|
||||
-DBUILD_MODULE_Visualization=OFF \
|
||||
-DBUILD_MODULE_DETools=OFF \
|
||||
-DBUILD_MODULE_FoundationClasses=ON \
|
||||
-DBUILD_MODULE_ModelingData=ON \
|
||||
-DBUILD_MODULE_ModelingAlgorithms=ON \
|
||||
-DBUILD_MODULE_DataExchange=ON \
|
||||
-DUSE_FREETYPE=OFF \
|
||||
-DUSE_FREEIMAGE=OFF \
|
||||
-DUSE_OPENVR=OFF \
|
||||
-DUSE_FFMPEG=OFF \
|
||||
-DUSE_TBB=OFF \
|
||||
-DUSE_VTK=OFF \
|
||||
-DUSE_TCL=OFF \
|
||||
-DUSE_TK=OFF \
|
||||
-DUSE_GLES2=OFF \
|
||||
-DUSE_OPENGL=OFF \
|
||||
-DUSE_D3D=OFF \
|
||||
-DUSE_RAPIDJSON=OFF \
|
||||
-DUSE_DRACO=OFF \
|
||||
-DBUILD_DOC_Overview=OFF \
|
||||
-DINSTALL_SAMPLES=OFF \
|
||||
-DINSTALL_TEST_CASES=OFF
|
||||
|
||||
emmake make -j${JOBS}
|
||||
emmake make install
|
||||
|
||||
create_stamp "${OCC_STAMP}"
|
||||
log_info "OpenCASCADE build complete!"
|
||||
88
scripts/deps/build-pixman.sh
Executable file
88
scripts/deps/build-pixman.sh
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
#!/bin/bash
|
||||
# Build Pixman for WebAssembly
|
||||
# Pixman is required by Cairo for pixel manipulation
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/env.sh"
|
||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
PIXMAN_DIR="${DEPS_ROOT}/pixman-${PIXMAN_VERSION}"
|
||||
PIXMAN_BUILD="${BUILD_ROOT}/deps/pixman"
|
||||
PIXMAN_STAMP="${BUILD_ROOT}/stamps/pixman.stamp"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=1
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $CLEAN -eq 1 ]; then
|
||||
log_info "Cleaning Pixman build..."
|
||||
rm -rf "${PIXMAN_BUILD}" "${PIXMAN_STAMP}"
|
||||
fi
|
||||
|
||||
# Check if already built
|
||||
if check_stamp "${PIXMAN_STAMP}"; then
|
||||
log_info "Pixman already built, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Download if needed
|
||||
if [ ! -d "${PIXMAN_DIR}" ]; then
|
||||
log_info "Downloading Pixman ${PIXMAN_VERSION}..."
|
||||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
|
||||
PIXMAN_URL="https://cairographics.org/releases/pixman-${PIXMAN_VERSION}.tar.gz"
|
||||
download_file "${PIXMAN_URL}" "pixman-${PIXMAN_VERSION}.tar.gz"
|
||||
tar -xzf "pixman-${PIXMAN_VERSION}.tar.gz"
|
||||
rm "pixman-${PIXMAN_VERSION}.tar.gz"
|
||||
fi
|
||||
|
||||
log_info "Building Pixman ${PIXMAN_VERSION} for WASM..."
|
||||
|
||||
mkdir -p "${PIXMAN_BUILD}"
|
||||
cd "${PIXMAN_BUILD}"
|
||||
|
||||
# Pixman uses meson
|
||||
cat > cross-file.txt << EOF
|
||||
[binaries]
|
||||
c = 'emcc'
|
||||
cpp = 'em++'
|
||||
ar = 'emar'
|
||||
ranlib = 'emranlib'
|
||||
strip = 'emstrip'
|
||||
|
||||
[host_machine]
|
||||
system = 'emscripten'
|
||||
cpu_family = 'wasm32'
|
||||
cpu = 'wasm32'
|
||||
endian = 'little'
|
||||
|
||||
[built-in options]
|
||||
c_args = ['-pthread']
|
||||
c_link_args = ['-pthread']
|
||||
EOF
|
||||
|
||||
meson setup "${PIXMAN_DIR}" \
|
||||
--cross-file cross-file.txt \
|
||||
--prefix="${SYSROOT}" \
|
||||
--default-library=static \
|
||||
-Dgtk=disabled \
|
||||
-Dlibpng=disabled \
|
||||
-Dtests=disabled
|
||||
|
||||
JOBS=${JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)}
|
||||
ninja -j${JOBS}
|
||||
ninja install
|
||||
|
||||
create_stamp "${PIXMAN_STAMP}"
|
||||
log_info "Pixman build complete!"
|
||||
69
scripts/deps/build-protobuf.sh
Executable file
69
scripts/deps/build-protobuf.sh
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
#!/bin/bash
|
||||
# Build Protocol Buffers for WebAssembly
|
||||
# Protobuf is used for IPC in KiCad
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/env.sh"
|
||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
PROTOBUF_DIR="${DEPS_ROOT}/protobuf-${PROTOBUF_VERSION}"
|
||||
PROTOBUF_BUILD="${BUILD_ROOT}/deps/protobuf"
|
||||
PROTOBUF_STAMP="${BUILD_ROOT}/stamps/protobuf.stamp"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=1
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $CLEAN -eq 1 ]; then
|
||||
log_info "Cleaning Protobuf build..."
|
||||
rm -rf "${PROTOBUF_BUILD}" "${PROTOBUF_STAMP}"
|
||||
fi
|
||||
|
||||
# Check if already built
|
||||
if check_stamp "${PROTOBUF_STAMP}"; then
|
||||
log_info "Protobuf already built, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Download if needed
|
||||
if [ ! -d "${PROTOBUF_DIR}" ]; then
|
||||
log_info "Downloading Protobuf ${PROTOBUF_VERSION}..."
|
||||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
|
||||
PROTOBUF_URL="https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protobuf-cpp-${PROTOBUF_VERSION}.tar.gz"
|
||||
download_file "${PROTOBUF_URL}" "protobuf-${PROTOBUF_VERSION}.tar.gz"
|
||||
tar -xzf "protobuf-${PROTOBUF_VERSION}.tar.gz"
|
||||
rm "protobuf-${PROTOBUF_VERSION}.tar.gz"
|
||||
fi
|
||||
|
||||
log_info "Building Protobuf ${PROTOBUF_VERSION} for WASM..."
|
||||
|
||||
mkdir -p "${PROTOBUF_BUILD}"
|
||||
cd "${PROTOBUF_BUILD}"
|
||||
|
||||
emcmake cmake "${PROTOBUF_DIR}" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${SYSROOT}" \
|
||||
-DCMAKE_CXX_FLAGS="-pthread" \
|
||||
-Dprotobuf_BUILD_TESTS=OFF \
|
||||
-Dprotobuf_BUILD_EXAMPLES=OFF \
|
||||
-Dprotobuf_BUILD_PROTOC_BINARIES=OFF \
|
||||
-Dprotobuf_BUILD_SHARED_LIBS=OFF \
|
||||
-Dprotobuf_WITH_ZLIB=OFF
|
||||
|
||||
emmake make -j${JOBS}
|
||||
emmake make install
|
||||
|
||||
create_stamp "${PROTOBUF_STAMP}"
|
||||
log_info "Protobuf build complete!"
|
||||
72
scripts/deps/build-zstd.sh
Executable file
72
scripts/deps/build-zstd.sh
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
#!/bin/bash
|
||||
# Build Zstd for WebAssembly
|
||||
# Zstd is used for compression in KiCad project files
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/env.sh"
|
||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
ZSTD_DIR="${DEPS_ROOT}/zstd-${ZSTD_VERSION}"
|
||||
ZSTD_BUILD="${BUILD_ROOT}/deps/zstd"
|
||||
ZSTD_STAMP="${BUILD_ROOT}/stamps/zstd.stamp"
|
||||
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=1
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $CLEAN -eq 1 ]; then
|
||||
log_info "Cleaning Zstd build..."
|
||||
rm -rf "${ZSTD_BUILD}" "${ZSTD_STAMP}"
|
||||
fi
|
||||
|
||||
# Check if already built
|
||||
if check_stamp "${ZSTD_STAMP}"; then
|
||||
log_info "Zstd already built, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Download if needed
|
||||
if [ ! -d "${ZSTD_DIR}" ]; then
|
||||
log_info "Downloading Zstd ${ZSTD_VERSION}..."
|
||||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
|
||||
ZSTD_URL="https://github.com/facebook/zstd/releases/download/v${ZSTD_VERSION}/zstd-${ZSTD_VERSION}.tar.gz"
|
||||
download_file "${ZSTD_URL}" "zstd-${ZSTD_VERSION}.tar.gz"
|
||||
tar -xzf "zstd-${ZSTD_VERSION}.tar.gz"
|
||||
rm "zstd-${ZSTD_VERSION}.tar.gz"
|
||||
fi
|
||||
|
||||
log_info "Building Zstd ${ZSTD_VERSION} for WASM..."
|
||||
|
||||
mkdir -p "${ZSTD_BUILD}"
|
||||
cd "${ZSTD_BUILD}"
|
||||
|
||||
# Zstd uses CMake in build/cmake directory
|
||||
emcmake cmake "${ZSTD_DIR}/build/cmake" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${SYSROOT}" \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DZSTD_BUILD_PROGRAMS=OFF \
|
||||
-DZSTD_BUILD_TESTS=OFF \
|
||||
-DZSTD_BUILD_SHARED=OFF \
|
||||
-DZSTD_BUILD_STATIC=ON \
|
||||
-DZSTD_MULTITHREAD_SUPPORT=ON \
|
||||
-DCMAKE_C_FLAGS="-pthread" \
|
||||
-DCMAKE_CXX_FLAGS="-pthread"
|
||||
|
||||
emmake make -j${JOBS}
|
||||
emmake make install
|
||||
|
||||
create_stamp "${ZSTD_STAMP}"
|
||||
log_info "Zstd build complete!"
|
||||
318
tests/kicad/pcbnew.html
Normal file
318
tests/kicad/pcbnew.html
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KiCad PCBnew - WASM</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #2c2c2c;
|
||||
color: #fff;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#toolbar {
|
||||
background: #3c3c3c;
|
||||
padding: 8px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid #555;
|
||||
}
|
||||
|
||||
#toolbar h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 6px 12px;
|
||||
background: #5c5c5c;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #6c6c6c;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
background: #4c4c4c;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#canvas-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
width: 300px;
|
||||
background: #353535;
|
||||
border-left: 1px solid #555;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#status-bar {
|
||||
background: #3c3c3c;
|
||||
padding: 4px 16px;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid #555;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#loading-overlay.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 3px solid #333;
|
||||
border-top-color: #007bff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#loading-text {
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#progress-bar {
|
||||
width: 300px;
|
||||
height: 4px;
|
||||
background: #333;
|
||||
border-radius: 2px;
|
||||
margin-top: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#progress-fill {
|
||||
height: 100%;
|
||||
background: #007bff;
|
||||
width: 0%;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
#log-panel {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 11px;
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
padding: 2px 0;
|
||||
border-bottom: 1px solid #3a3a3a;
|
||||
}
|
||||
|
||||
.log-info { color: #a0a0a0; }
|
||||
.log-warn { color: #ffcc00; }
|
||||
.log-error { color: #ff6b6b; }
|
||||
.log-success { color: #69db7c; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loading-overlay">
|
||||
<div class="spinner"></div>
|
||||
<div id="loading-text">Loading PCBnew...</div>
|
||||
<div id="progress-bar">
|
||||
<div id="progress-fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toolbar">
|
||||
<h1>KiCad PCBnew (WASM)</h1>
|
||||
<button class="btn" id="btn-new">New</button>
|
||||
<button class="btn" id="btn-open">Open</button>
|
||||
<button class="btn" id="btn-save">Save</button>
|
||||
<button class="btn" id="btn-zoom-in">Zoom +</button>
|
||||
<button class="btn" id="btn-zoom-out">Zoom -</button>
|
||||
<button class="btn" id="btn-fit">Fit</button>
|
||||
</div>
|
||||
|
||||
<div id="main-container">
|
||||
<div id="canvas-container">
|
||||
<canvas id="canvas"></canvas>
|
||||
</div>
|
||||
<div id="sidebar">
|
||||
<div id="log-panel"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="status-bar">
|
||||
<span id="status-coords">X: 0.00mm Y: 0.00mm</span>
|
||||
<span id="status-info">Ready</span>
|
||||
<span id="status-memory">Memory: --</span>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
// Logger
|
||||
const logPanel = document.getElementById('log-panel');
|
||||
const log = (msg, type = 'info') => {
|
||||
const entry = document.createElement('div');
|
||||
entry.className = `log-entry log-${type}`;
|
||||
entry.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
|
||||
logPanel.appendChild(entry);
|
||||
logPanel.scrollTop = logPanel.scrollHeight;
|
||||
console.log(`[${type}] ${msg}`);
|
||||
};
|
||||
|
||||
// Loading state
|
||||
const loadingOverlay = document.getElementById('loading-overlay');
|
||||
const loadingText = document.getElementById('loading-text');
|
||||
const progressFill = document.getElementById('progress-fill');
|
||||
|
||||
const updateProgress = (percent, text) => {
|
||||
progressFill.style.width = `${percent}%`;
|
||||
if (text) loadingText.textContent = text;
|
||||
};
|
||||
|
||||
// Memory monitoring
|
||||
const updateMemory = () => {
|
||||
if (performance.memory) {
|
||||
const used = (performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(1);
|
||||
document.getElementById('status-memory').textContent = `Memory: ${used}MB`;
|
||||
}
|
||||
};
|
||||
setInterval(updateMemory, 1000);
|
||||
|
||||
// Main initialization
|
||||
async function init() {
|
||||
try {
|
||||
log('Starting PCBnew initialization...', 'info');
|
||||
updateProgress(10, 'Loading WebAssembly module...');
|
||||
|
||||
// Import the PCBnew module
|
||||
// In production, this would be the compiled KiCad WASM
|
||||
const moduleUrl = './pcbnew.js';
|
||||
|
||||
log(`Loading module from ${moduleUrl}`, 'info');
|
||||
updateProgress(30, 'Compiling WebAssembly...');
|
||||
|
||||
// For now, just show that we're ready to load
|
||||
// The actual module loading would happen here
|
||||
log('Note: PCBnew WASM module not yet built', 'warn');
|
||||
log('Run: ./scripts/build-pcbnew-wasm.sh', 'warn');
|
||||
|
||||
updateProgress(100, 'Ready (module not loaded)');
|
||||
|
||||
setTimeout(() => {
|
||||
loadingOverlay.classList.add('hidden');
|
||||
log('UI ready', 'success');
|
||||
}, 500);
|
||||
|
||||
} catch (error) {
|
||||
log(`Failed to initialize: ${error.message}`, 'error');
|
||||
loadingText.textContent = `Error: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Button handlers
|
||||
document.getElementById('btn-new').addEventListener('click', () => {
|
||||
log('New project', 'info');
|
||||
});
|
||||
|
||||
document.getElementById('btn-open').addEventListener('click', () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.kicad_pcb,.kicad_mod';
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
log(`Opening: ${file.name}`, 'info');
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
});
|
||||
|
||||
document.getElementById('btn-save').addEventListener('click', () => {
|
||||
log('Save project', 'info');
|
||||
});
|
||||
|
||||
document.getElementById('btn-zoom-in').addEventListener('click', () => {
|
||||
log('Zoom in', 'info');
|
||||
});
|
||||
|
||||
document.getElementById('btn-zoom-out').addEventListener('click', () => {
|
||||
log('Zoom out', 'info');
|
||||
});
|
||||
|
||||
document.getElementById('btn-fit').addEventListener('click', () => {
|
||||
log('Fit to view', 'info');
|
||||
});
|
||||
|
||||
// Canvas mouse tracking
|
||||
const canvas = document.getElementById('canvas');
|
||||
const statusCoords = document.getElementById('status-coords');
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const x = ((e.clientX - rect.left) * 0.1).toFixed(2);
|
||||
const y = ((e.clientY - rect.top) * 0.1).toFixed(2);
|
||||
statusCoords.textContent = `X: ${x}mm Y: ${y}mm`;
|
||||
});
|
||||
|
||||
// Resize handling
|
||||
function resizeCanvas() {
|
||||
const container = document.getElementById('canvas-container');
|
||||
canvas.width = container.clientWidth;
|
||||
canvas.height = container.clientHeight;
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
resizeCanvas();
|
||||
|
||||
// Start initialization
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
115
tests/kicad/pcbnew.spec.ts
Normal file
115
tests/kicad/pcbnew.spec.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* PCBnew WASM E2E Tests
|
||||
*
|
||||
* These tests verify that the KiCad PCBnew application runs correctly in the browser.
|
||||
* They test the basic UI functionality and WASM module loading.
|
||||
*/
|
||||
|
||||
test.describe('PCBnew WASM', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Navigate to PCBnew test page
|
||||
const htmlPath = path.join(__dirname, 'pcbnew.html');
|
||||
await page.goto(`file://${htmlPath}`);
|
||||
|
||||
// Wait for loading overlay to disappear
|
||||
await page.waitForSelector('#loading-overlay.hidden', { timeout: 30000 });
|
||||
});
|
||||
|
||||
test('should load the PCBnew interface', async ({ page }) => {
|
||||
// Check that the toolbar is visible
|
||||
await expect(page.locator('#toolbar h1')).toHaveText('KiCad PCBnew (WASM)');
|
||||
|
||||
// Check that toolbar buttons exist
|
||||
await expect(page.locator('#btn-new')).toBeVisible();
|
||||
await expect(page.locator('#btn-open')).toBeVisible();
|
||||
await expect(page.locator('#btn-save')).toBeVisible();
|
||||
await expect(page.locator('#btn-zoom-in')).toBeVisible();
|
||||
await expect(page.locator('#btn-zoom-out')).toBeVisible();
|
||||
await expect(page.locator('#btn-fit')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should have a working canvas', async ({ page }) => {
|
||||
const canvas = page.locator('#canvas');
|
||||
await expect(canvas).toBeVisible();
|
||||
|
||||
// Check that canvas has proper dimensions
|
||||
const box = await canvas.boundingBox();
|
||||
expect(box?.width).toBeGreaterThan(0);
|
||||
expect(box?.height).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should track mouse coordinates', async ({ page }) => {
|
||||
const canvas = page.locator('#canvas');
|
||||
const statusCoords = page.locator('#status-coords');
|
||||
|
||||
// Move mouse over canvas
|
||||
await canvas.hover({ position: { x: 100, y: 100 } });
|
||||
|
||||
// Check that coordinates are updated
|
||||
const coordText = await statusCoords.textContent();
|
||||
expect(coordText).toMatch(/X: \d+\.\d+mm Y: \d+\.\d+mm/);
|
||||
});
|
||||
|
||||
test('should log UI interactions', async ({ page }) => {
|
||||
const logPanel = page.locator('#log-panel');
|
||||
|
||||
// Click New button
|
||||
await page.click('#btn-new');
|
||||
|
||||
// Check that action was logged
|
||||
await expect(logPanel).toContainText('New project');
|
||||
});
|
||||
|
||||
test('should have status bar with info', async ({ page }) => {
|
||||
await expect(page.locator('#status-info')).toHaveText('Ready');
|
||||
});
|
||||
|
||||
test('should show sidebar with log panel', async ({ page }) => {
|
||||
const sidebar = page.locator('#sidebar');
|
||||
const logPanel = page.locator('#log-panel');
|
||||
|
||||
await expect(sidebar).toBeVisible();
|
||||
await expect(logPanel).toBeVisible();
|
||||
});
|
||||
|
||||
// Screenshot test for visual regression
|
||||
test('should match visual snapshot', async ({ page }) => {
|
||||
await page.screenshot({
|
||||
path: path.join(__dirname, 'screenshots', 'pcbnew-initial.png'),
|
||||
fullPage: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('PCBnew File Operations', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const htmlPath = path.join(__dirname, 'pcbnew.html');
|
||||
await page.goto(`file://${htmlPath}`);
|
||||
await page.waitForSelector('#loading-overlay.hidden', { timeout: 30000 });
|
||||
});
|
||||
|
||||
test('should have file open button that triggers file picker', async ({ page }) => {
|
||||
const openBtn = page.locator('#btn-open');
|
||||
await expect(openBtn).toBeVisible();
|
||||
|
||||
// Click should trigger file input (we can't fully test file selection in Playwright)
|
||||
// but we can verify the button is clickable
|
||||
await openBtn.click();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('PCBnew WASM Module', () => {
|
||||
test('WASM module loading (placeholder)', async ({ page }) => {
|
||||
// This test will be expanded once the WASM module is built
|
||||
const htmlPath = path.join(__dirname, 'pcbnew.html');
|
||||
await page.goto(`file://${htmlPath}`);
|
||||
|
||||
// Check for expected warning when module is not built
|
||||
const logPanel = page.locator('#log-panel');
|
||||
await page.waitForSelector('#loading-overlay.hidden', { timeout: 30000 });
|
||||
await expect(logPanel).toContainText('Note: PCBnew WASM module not yet built');
|
||||
});
|
||||
});
|
||||
26
wasm/CMakeLists.txt
Normal file
26
wasm/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# WASM Compatibility Layer for KiCad
|
||||
# This directory contains WASM-specific implementations that replace
|
||||
# platform-specific code in KiCad without modifying KiCad's source.
|
||||
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
project(kicad_wasm_compat)
|
||||
|
||||
# Only build for Emscripten
|
||||
if(NOT EMSCRIPTEN)
|
||||
message(FATAL_ERROR "This project is only for Emscripten builds")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Include directories
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/config
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/shims
|
||||
)
|
||||
|
||||
# kiplatform WASM implementation
|
||||
add_subdirectory(kiplatform)
|
||||
|
||||
# libcontext Asyncify fiber implementation
|
||||
add_subdirectory(libcontext)
|
||||
74
wasm/README.md
Normal file
74
wasm/README.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# WASM Compatibility Layer
|
||||
|
||||
This directory contains WASM-specific implementations that allow KiCad to run in a web browser **without modifying KiCad's source code**.
|
||||
|
||||
## Principle
|
||||
|
||||
Instead of patching KiCad source files, we:
|
||||
1. Override include paths to use our headers first
|
||||
2. Provide alternative implementations for platform-specific code
|
||||
3. Link our libraries instead of system libraries
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
wasm/
|
||||
├── CMakeLists.txt # Master CMake for compatibility layer
|
||||
├── README.md # This file
|
||||
├── kiplatform/ # Platform abstraction implementations
|
||||
│ ├── CMakeLists.txt
|
||||
│ ├── app.cpp # App lifecycle (paths, startup)
|
||||
│ ├── drivers.cpp # GPU detection (returns "WebGL")
|
||||
│ ├── environment.cpp # Environment variables (localStorage)
|
||||
│ ├── io.cpp # File I/O (WASM virtual filesystem)
|
||||
│ ├── policy.cpp # Security policy (always permissive)
|
||||
│ ├── secrets.cpp # Credential storage (localStorage)
|
||||
│ ├── sysinfo.cpp # System information
|
||||
│ └── printing.cpp # Print support (browser print())
|
||||
├── libcontext/ # Coroutine/fiber implementation
|
||||
│ ├── CMakeLists.txt
|
||||
│ └── fcontext_wasm.cpp # Emscripten Asyncify fibers
|
||||
├── shims/ # Header overrides
|
||||
│ └── *.h # Headers that redirect to our impls
|
||||
└── config/ # Build configuration
|
||||
├── kicad_wasm_config.h # Version and feature config
|
||||
└── setup.h # Platform setup
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Include Path Override
|
||||
|
||||
When building KiCad for WASM, we add our directories first in the include path:
|
||||
|
||||
```bash
|
||||
-I$PROJECT_ROOT/wasm/shims
|
||||
-I$PROJECT_ROOT/wasm/kiplatform
|
||||
-I$PROJECT_ROOT/stubs/include
|
||||
```
|
||||
|
||||
This means when KiCad includes `<kiplatform/app.h>`, it finds our version first.
|
||||
|
||||
### Library Override
|
||||
|
||||
We build `libkiplatform_wasm.a` and link it instead of the native kiplatform:
|
||||
|
||||
```bash
|
||||
-L$BUILD_ROOT/wasm -lkiplatform_wasm
|
||||
```
|
||||
|
||||
### CMake Integration
|
||||
|
||||
The main KiCad build is configured to find our implementations:
|
||||
|
||||
```cmake
|
||||
-DCMAKE_MODULE_PATH="$PROJECT_ROOT/cmake"
|
||||
-DKIPLATFORM_LIBRARY="$BUILD_ROOT/wasm/libkiplatform_wasm.a"
|
||||
```
|
||||
|
||||
## Adding New Implementations
|
||||
|
||||
1. Create the implementation file in the appropriate directory
|
||||
2. Add it to the CMakeLists.txt
|
||||
3. Ensure the header interface matches KiCad's expected interface
|
||||
4. Test with a minimal build before full integration
|
||||
49
wasm/cmake/FindKiplatformWASM.cmake
Normal file
49
wasm/cmake/FindKiplatformWASM.cmake
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# FindKiplatformWASM.cmake
|
||||
# Find module for WASM implementation of kiplatform
|
||||
#
|
||||
# This module is used instead of the platform-specific kiplatform
|
||||
# when building KiCad for WebAssembly.
|
||||
#
|
||||
# This module defines:
|
||||
# KIPLATFORM_FOUND - System has kiplatform for WASM
|
||||
# KIPLATFORM_INCLUDE_DIRS - Include directories
|
||||
# KIPLATFORM_LIBRARIES - Libraries to link
|
||||
# KIPLATFORM_SOURCES - Source files to compile
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
message(FATAL_ERROR "FindKiplatformWASM is only for Emscripten builds")
|
||||
endif()
|
||||
|
||||
# Get the directory containing this file
|
||||
get_filename_component(_FIND_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
get_filename_component(KIPLATFORM_WASM_DIR "${_FIND_DIR}/../kiplatform" ABSOLUTE)
|
||||
|
||||
# Define source files
|
||||
set(KIPLATFORM_SOURCES
|
||||
${KIPLATFORM_WASM_DIR}/app.cpp
|
||||
${KIPLATFORM_WASM_DIR}/drivers.cpp
|
||||
${KIPLATFORM_WASM_DIR}/environment.cpp
|
||||
${KIPLATFORM_WASM_DIR}/io.cpp
|
||||
${KIPLATFORM_WASM_DIR}/policy.cpp
|
||||
${KIPLATFORM_WASM_DIR}/secrets.cpp
|
||||
${KIPLATFORM_WASM_DIR}/sysinfo.cpp
|
||||
${KIPLATFORM_WASM_DIR}/ui.cpp
|
||||
)
|
||||
|
||||
# Include directory for platform headers
|
||||
# We use KiCad's own headers, just provide our implementation
|
||||
set(KIPLATFORM_INCLUDE_DIRS ${KIPLATFORM_WASM_DIR})
|
||||
|
||||
# No prebuilt library - sources are compiled directly into KiCad
|
||||
set(KIPLATFORM_LIBRARIES "")
|
||||
|
||||
# Mark as found
|
||||
set(KIPLATFORM_FOUND TRUE)
|
||||
|
||||
find_package_handle_standard_args(KiplatformWASM
|
||||
REQUIRED_VARS KIPLATFORM_SOURCES KIPLATFORM_INCLUDE_DIRS
|
||||
)
|
||||
|
||||
mark_as_advanced(KIPLATFORM_SOURCES KIPLATFORM_INCLUDE_DIRS KIPLATFORM_LIBRARIES)
|
||||
41
wasm/cmake/FindLibcontextWASM.cmake
Normal file
41
wasm/cmake/FindLibcontextWASM.cmake
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# FindLibcontextWASM.cmake
|
||||
# Find module for WASM implementation of libcontext using Asyncify
|
||||
#
|
||||
# This module replaces the platform-specific libcontext assembly
|
||||
# when building KiCad for WebAssembly.
|
||||
#
|
||||
# This module defines:
|
||||
# LIBCONTEXT_FOUND - System has libcontext for WASM
|
||||
# LIBCONTEXT_INCLUDE_DIRS - Include directories
|
||||
# LIBCONTEXT_LIBRARIES - Libraries to link (none for WASM)
|
||||
# LIBCONTEXT_SOURCES - Source files to compile
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
message(FATAL_ERROR "FindLibcontextWASM is only for Emscripten builds")
|
||||
endif()
|
||||
|
||||
# Get the directory containing this file
|
||||
get_filename_component(_FIND_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
get_filename_component(LIBCONTEXT_WASM_DIR "${_FIND_DIR}/../libcontext" ABSOLUTE)
|
||||
|
||||
# Define source files
|
||||
set(LIBCONTEXT_SOURCES
|
||||
${LIBCONTEXT_WASM_DIR}/libcontext_wasm.cpp
|
||||
)
|
||||
|
||||
# Include directories
|
||||
set(LIBCONTEXT_INCLUDE_DIRS ${LIBCONTEXT_WASM_DIR})
|
||||
|
||||
# No prebuilt library
|
||||
set(LIBCONTEXT_LIBRARIES "")
|
||||
|
||||
# Mark as found
|
||||
set(LIBCONTEXT_FOUND TRUE)
|
||||
|
||||
find_package_handle_standard_args(LibcontextWASM
|
||||
REQUIRED_VARS LIBCONTEXT_SOURCES LIBCONTEXT_INCLUDE_DIRS
|
||||
)
|
||||
|
||||
mark_as_advanced(LIBCONTEXT_SOURCES LIBCONTEXT_INCLUDE_DIRS LIBCONTEXT_LIBRARIES)
|
||||
50
wasm/cmake/KiCadWASMConfig.cmake
Normal file
50
wasm/cmake/KiCadWASMConfig.cmake
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# KiCad WASM Configuration
|
||||
# This file configures KiCad to use WASM-specific implementations
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
message(FATAL_ERROR "KiCadWASMConfig.cmake is only for Emscripten builds")
|
||||
endif()
|
||||
|
||||
# Set the path to WASM compatibility layer
|
||||
get_filename_component(KICAD_WASM_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
||||
|
||||
message(STATUS "KiCad WASM compatibility layer: ${KICAD_WASM_DIR}")
|
||||
|
||||
# Add include directories for WASM implementations
|
||||
# These will be searched BEFORE KiCad's own includes, allowing us to
|
||||
# provide our own implementations without modifying KiCad source
|
||||
set(KICAD_WASM_INCLUDE_DIRS
|
||||
${KICAD_WASM_DIR}/kiplatform
|
||||
${KICAD_WASM_DIR}/libcontext
|
||||
${KICAD_WASM_DIR}/config
|
||||
${KICAD_WASM_DIR}/shims
|
||||
)
|
||||
|
||||
# Define macro to indicate we're using WASM platform
|
||||
add_definitions(-DKICAD_PLATFORM_WASM=1)
|
||||
add_definitions(-D__WXUNIVERSAL__=1)
|
||||
|
||||
# Emscripten-specific compile options
|
||||
add_compile_options(
|
||||
-pthread
|
||||
-sUSE_PTHREADS=1
|
||||
)
|
||||
|
||||
# Emscripten-specific link options
|
||||
add_link_options(
|
||||
-pthread
|
||||
-sUSE_PTHREADS=1
|
||||
-sPTHREAD_POOL_SIZE=4
|
||||
-sASYNCIFY=1
|
||||
-sASYNCIFY_STACK_SIZE=65536
|
||||
-sALLOW_MEMORY_GROWTH=1
|
||||
-sINITIAL_MEMORY=256MB
|
||||
-sMAXIMUM_MEMORY=4GB
|
||||
-sMODULARIZE=1
|
||||
-sEXPORT_ES6=1
|
||||
-sENVIRONMENT=web,worker
|
||||
)
|
||||
|
||||
# Export variables for use by parent CMakeLists
|
||||
set(KICAD_WASM_INCLUDE_DIRS ${KICAD_WASM_INCLUDE_DIRS} PARENT_SCOPE)
|
||||
set(KICAD_WASM_DIR ${KICAD_WASM_DIR} PARENT_SCOPE)
|
||||
25
wasm/kiplatform/CMakeLists.txt
Normal file
25
wasm/kiplatform/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# WASM implementation of kiplatform
|
||||
# Provides WASM-specific implementations of KiCad's platform abstraction layer
|
||||
|
||||
set(KIPLATFORM_WASM_SRCS
|
||||
app.cpp
|
||||
drivers.cpp
|
||||
environment.cpp
|
||||
io.cpp
|
||||
policy.cpp
|
||||
secrets.cpp
|
||||
sysinfo.cpp
|
||||
ui.cpp
|
||||
)
|
||||
|
||||
add_library(kiplatform_wasm STATIC ${KIPLATFORM_WASM_SRCS})
|
||||
|
||||
target_include_directories(kiplatform_wasm PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../config
|
||||
${PROJECT_ROOT}/kicad/libs/kiplatform/include
|
||||
)
|
||||
|
||||
# Link with wxWidgets
|
||||
target_link_libraries(kiplatform_wasm
|
||||
# wxWidgets will be added during main build
|
||||
)
|
||||
87
wasm/kiplatform/app.cpp
Normal file
87
wasm/kiplatform/app.cpp
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* WASM implementation of kiplatform/app.h
|
||||
* Provides application lifecycle functions for browser environment
|
||||
*/
|
||||
|
||||
#include <kiplatform/app.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
namespace KIPLATFORM
|
||||
{
|
||||
namespace APP
|
||||
{
|
||||
|
||||
bool Init()
|
||||
{
|
||||
// WASM initialization - nothing special needed
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AttachConsole( bool aTryAlloc )
|
||||
{
|
||||
// Console is always available via browser dev tools
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsOperatingSystemUnsupported()
|
||||
{
|
||||
// WASM/browser is supported
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RegisterApplicationRestart( const wxString& aCommandLine )
|
||||
{
|
||||
// No restart registration in browser
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnregisterApplicationRestart()
|
||||
{
|
||||
// No restart registration in browser
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SupportsShutdownBlockReason()
|
||||
{
|
||||
// Browser handles page unload via beforeunload event
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetShutdownBlockReason( wxWindow* aWindow, const wxString& aReason )
|
||||
{
|
||||
// Could implement via beforeunload event if needed
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASM({
|
||||
window.onbeforeunload = function() {
|
||||
return UTF8ToString($0);
|
||||
};
|
||||
}, aReason.utf8_str().data());
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemoveShutdownBlockReason( wxWindow* aWindow )
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASM({
|
||||
window.onbeforeunload = null;
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void ForceTimerMessagesToBeCreatedIfNecessary()
|
||||
{
|
||||
// Not needed in browser - timers work differently
|
||||
}
|
||||
|
||||
void AddDynamicLibrarySearchPath( const wxString& aPath )
|
||||
{
|
||||
// No dynamic library loading in WASM
|
||||
}
|
||||
|
||||
} // namespace APP
|
||||
} // namespace KIPLATFORM
|
||||
20
wasm/kiplatform/drivers.cpp
Normal file
20
wasm/kiplatform/drivers.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* WASM implementation of kiplatform/drivers.h
|
||||
* 3D mouse drivers are not available in browser
|
||||
*/
|
||||
|
||||
#include <kiplatform/drivers.h>
|
||||
|
||||
namespace KIPLATFORM
|
||||
{
|
||||
namespace DRIVERS
|
||||
{
|
||||
|
||||
bool Valid3DConnexionDriverVersion()
|
||||
{
|
||||
// No 3D mouse support in browser
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace DRIVERS
|
||||
} // namespace KIPLATFORM
|
||||
120
wasm/kiplatform/environment.cpp
Normal file
120
wasm/kiplatform/environment.cpp
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* WASM implementation of kiplatform/environment.h
|
||||
* Provides environment and path functions for browser environment
|
||||
*/
|
||||
|
||||
#include <kiplatform/environment.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
namespace KIPLATFORM
|
||||
{
|
||||
namespace ENV
|
||||
{
|
||||
|
||||
void Init()
|
||||
{
|
||||
// No special initialization needed for WASM
|
||||
}
|
||||
|
||||
bool MoveToTrash( const wxString& aPath, wxString& aError )
|
||||
{
|
||||
// No trash/recycle bin in browser - just delete
|
||||
aError = wxT( "Trash not available in browser environment" );
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsNetworkPath( const wxString& aPath )
|
||||
{
|
||||
// All paths in WASM virtual filesystem are local
|
||||
return false;
|
||||
}
|
||||
|
||||
wxString GetDocumentsPath()
|
||||
{
|
||||
// Use virtual filesystem path
|
||||
return wxT( "/home/kicad/documents" );
|
||||
}
|
||||
|
||||
wxString GetUserConfigPath()
|
||||
{
|
||||
// Use virtual filesystem path for config
|
||||
return wxT( "/home/kicad/.config/kicad" );
|
||||
}
|
||||
|
||||
wxString GetUserDataPath()
|
||||
{
|
||||
// Use virtual filesystem path for data
|
||||
return wxT( "/home/kicad/.local/share/kicad" );
|
||||
}
|
||||
|
||||
wxString GetUserLocalDataPath()
|
||||
{
|
||||
// Same as data path in WASM
|
||||
return wxT( "/home/kicad/.local/share/kicad" );
|
||||
}
|
||||
|
||||
wxString GetUserCachePath()
|
||||
{
|
||||
// Use virtual filesystem path for cache
|
||||
return wxT( "/home/kicad/.cache/kicad" );
|
||||
}
|
||||
|
||||
bool GetSystemProxyConfig( const wxString& aURL, PROXY_CONFIG& aCfg )
|
||||
{
|
||||
// No proxy configuration in browser - browser handles networking
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VerifyFileSignature( const wxString& aPath )
|
||||
{
|
||||
// No code signing verification in WASM
|
||||
return true;
|
||||
}
|
||||
|
||||
wxString GetAppUserModelId()
|
||||
{
|
||||
// Windows-specific, return empty
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
void SetAppDetailsForWindow( wxWindow* aWindow, const wxString& aRelaunchCommand,
|
||||
const wxString& aRelaunchDisplayName )
|
||||
{
|
||||
// Windows-specific, no-op
|
||||
}
|
||||
|
||||
wxString GetCommandLineStr()
|
||||
{
|
||||
// No command line in browser
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
void AddToRecentDocs( const wxString& aPath )
|
||||
{
|
||||
// Could implement via localStorage if needed
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASM({
|
||||
try {
|
||||
var recent = JSON.parse(localStorage.getItem('kicad_recent_docs') || '[]');
|
||||
var path = UTF8ToString($0);
|
||||
// Remove if already exists
|
||||
recent = recent.filter(function(p) { return p !== path; });
|
||||
// Add to front
|
||||
recent.unshift(path);
|
||||
// Keep only last 10
|
||||
recent = recent.slice(0, 10);
|
||||
localStorage.setItem('kicad_recent_docs', JSON.stringify(recent));
|
||||
} catch(e) {
|
||||
console.warn('Failed to save recent docs:', e);
|
||||
}
|
||||
}, aPath.utf8_str().data());
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ENV
|
||||
} // namespace KIPLATFORM
|
||||
49
wasm/kiplatform/io.cpp
Normal file
49
wasm/kiplatform/io.cpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* WASM implementation of kiplatform/io.h
|
||||
* Provides file I/O functions for WASM virtual filesystem
|
||||
*/
|
||||
|
||||
#include <kiplatform/io.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/filename.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace KIPLATFORM
|
||||
{
|
||||
namespace IO
|
||||
{
|
||||
|
||||
FILE* SeqFOpen( const wxString& aPath, const wxString& mode )
|
||||
{
|
||||
// WASM doesn't have special sequential read hints
|
||||
// Just use standard fopen
|
||||
return fopen( aPath.utf8_str(), mode.utf8_str() );
|
||||
}
|
||||
|
||||
bool DuplicatePermissions( const wxString& aSrc, const wxString& aDest )
|
||||
{
|
||||
// WASM virtual filesystem doesn't have detailed permissions
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MakeWriteable( const wxString& aFilePath )
|
||||
{
|
||||
// All files in WASM virtual filesystem are writeable
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsFileHidden( const wxString& aFileName )
|
||||
{
|
||||
// Check for Unix-style hidden files (starting with .)
|
||||
wxFileName fn( aFileName );
|
||||
wxString name = fn.GetFullName();
|
||||
return !name.IsEmpty() && name[0] == '.';
|
||||
}
|
||||
|
||||
void LongPathAdjustment( wxFileName& aFilename )
|
||||
{
|
||||
// No-op on non-Windows platforms
|
||||
}
|
||||
|
||||
} // namespace IO
|
||||
} // namespace KIPLATFORM
|
||||
27
wasm/kiplatform/policy.cpp
Normal file
27
wasm/kiplatform/policy.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* WASM implementation of kiplatform/policy.h
|
||||
* Policies are not configured in browser environment
|
||||
*/
|
||||
|
||||
#include <kiplatform/policy.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
namespace KIPLATFORM
|
||||
{
|
||||
namespace POLICY
|
||||
{
|
||||
|
||||
PBOOL GetPolicyBool( const wxString& aKey )
|
||||
{
|
||||
// No enterprise policies in browser environment
|
||||
return PBOOL::NOT_CONFIGURED;
|
||||
}
|
||||
|
||||
std::uint32_t GetPolicyEnumUInt( const wxString& aKey )
|
||||
{
|
||||
// No enterprise policies in browser environment
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace POLICY
|
||||
} // namespace KIPLATFORM
|
||||
76
wasm/kiplatform/secrets.cpp
Normal file
76
wasm/kiplatform/secrets.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* WASM implementation of kiplatform/secrets.h
|
||||
* Uses browser localStorage for basic secret storage
|
||||
* Note: localStorage is NOT secure for sensitive secrets, but provides
|
||||
* the same API for KiCad functionality that expects secret storage
|
||||
*/
|
||||
|
||||
#include <kiplatform/secrets.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
namespace KIPLATFORM
|
||||
{
|
||||
namespace SECRETS
|
||||
{
|
||||
|
||||
bool StoreSecret( const wxString& aService, const wxString& aKey, const wxString& aSecret )
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
int result = EM_ASM_INT({
|
||||
try {
|
||||
var service = UTF8ToString($0);
|
||||
var key = UTF8ToString($1);
|
||||
var secret = UTF8ToString($2);
|
||||
var storageKey = 'kicad_secret_' + service + '_' + key;
|
||||
localStorage.setItem(storageKey, secret);
|
||||
return 1;
|
||||
} catch(e) {
|
||||
console.warn('Failed to store secret:', e);
|
||||
return 0;
|
||||
}
|
||||
}, aService.utf8_str().data(), aKey.utf8_str().data(), aSecret.utf8_str().data());
|
||||
return result == 1;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GetSecret( const wxString& aService, const wxString& aKey, wxString& aSecret )
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
char* result = (char*)EM_ASM_PTR({
|
||||
try {
|
||||
var service = UTF8ToString($0);
|
||||
var key = UTF8ToString($1);
|
||||
var storageKey = 'kicad_secret_' + service + '_' + key;
|
||||
var secret = localStorage.getItem(storageKey);
|
||||
if (secret === null) {
|
||||
return 0;
|
||||
}
|
||||
var len = lengthBytesUTF8(secret) + 1;
|
||||
var buf = _malloc(len);
|
||||
stringToUTF8(secret, buf, len);
|
||||
return buf;
|
||||
} catch(e) {
|
||||
console.warn('Failed to get secret:', e);
|
||||
return 0;
|
||||
}
|
||||
}, aService.utf8_str().data(), aKey.utf8_str().data());
|
||||
|
||||
if (result) {
|
||||
aSecret = wxString::FromUTF8(result);
|
||||
free(result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace SECRETS
|
||||
} // namespace KIPLATFORM
|
||||
123
wasm/kiplatform/sysinfo.cpp
Normal file
123
wasm/kiplatform/sysinfo.cpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* WASM implementation of kiplatform/sysinfo.h
|
||||
* Provides limited system info available in browser environment
|
||||
*/
|
||||
|
||||
#include <kiplatform/sysinfo.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
namespace KIPLATFORM
|
||||
{
|
||||
|
||||
class SYSINFO_WASM : public SYSINFO_BASE
|
||||
{
|
||||
public:
|
||||
bool GetGPUInfo( std::vector<GPU_INFO>& aGpuInfos ) override
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
GPU_INFO info;
|
||||
|
||||
// Try to get WebGL renderer info
|
||||
char* renderer = (char*)EM_ASM_PTR({
|
||||
try {
|
||||
var canvas = document.createElement('canvas');
|
||||
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
||||
if (gl) {
|
||||
var debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
||||
if (debugInfo) {
|
||||
var renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
||||
var len = lengthBytesUTF8(renderer) + 1;
|
||||
var buf = _malloc(len);
|
||||
stringToUTF8(renderer, buf, len);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} catch(e) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
if (renderer) {
|
||||
info.Name = renderer;
|
||||
free(renderer);
|
||||
} else {
|
||||
info.Name = "WebGL Renderer";
|
||||
}
|
||||
|
||||
info.MemorySize = 0; // Not available in browser
|
||||
info.DriverVersion = "WebGL";
|
||||
info.Manufacturer = "Browser";
|
||||
|
||||
aGpuInfos.push_back(info);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GetCPUInfo( std::vector<CPU_INFO>& aCpuInfos ) override
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
CPU_INFO info;
|
||||
|
||||
// Get hardware concurrency (number of logical processors)
|
||||
int cores = EM_ASM_INT({
|
||||
return navigator.hardwareConcurrency || 1;
|
||||
});
|
||||
|
||||
info.Name = "WebAssembly CPU";
|
||||
info.Manufacturer = "Browser";
|
||||
info.NumberCores = cores;
|
||||
info.NumberLogical = cores;
|
||||
|
||||
aCpuInfos.push_back(info);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GetMemoryInfo( MEMORY_INFO& aMemoryInfo ) override
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Try to get memory info from performance.memory (Chrome only)
|
||||
// or estimate from WASM heap
|
||||
long long heapSize = EM_ASM_INT({
|
||||
if (performance && performance.memory) {
|
||||
return performance.memory.jsHeapSizeLimit || 0;
|
||||
}
|
||||
// Return WASM memory size as fallback
|
||||
return HEAPU8.length;
|
||||
});
|
||||
|
||||
aMemoryInfo.Usage = 0;
|
||||
aMemoryInfo.TotalPhysical = heapSize;
|
||||
aMemoryInfo.FreePhysical = heapSize / 2; // Estimate
|
||||
aMemoryInfo.TotalPaging = 0;
|
||||
aMemoryInfo.FreePaging = 0;
|
||||
aMemoryInfo.TotalVirtual = heapSize;
|
||||
aMemoryInfo.FreeVirtual = heapSize / 2;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace KIPLATFORM
|
||||
|
||||
// Global instance for the WASM sysinfo implementation
|
||||
static KIPLATFORM::SYSINFO_WASM s_sysInfoWasm;
|
||||
|
||||
// Provide access to the sysinfo implementation
|
||||
KIPLATFORM::SYSINFO_BASE* GetSysInfo()
|
||||
{
|
||||
return &s_sysInfoWasm;
|
||||
}
|
||||
198
wasm/kiplatform/ui.cpp
Normal file
198
wasm/kiplatform/ui.cpp
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* WASM implementation of kiplatform/ui.h
|
||||
* Provides UI functions for browser environment
|
||||
*/
|
||||
|
||||
#include <kiplatform/ui.h>
|
||||
#include <wx/window.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/toplevel.h>
|
||||
#include <wx/nonownedwnd.h>
|
||||
#include <wx/colour.h>
|
||||
#include <wx/settings.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
namespace KIPLATFORM
|
||||
{
|
||||
namespace UI
|
||||
{
|
||||
|
||||
bool IsDarkTheme()
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Check if browser prefers dark color scheme
|
||||
int isDark = EM_ASM_INT({
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return isDark == 1;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
wxColour GetDialogBGColour()
|
||||
{
|
||||
return wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE );
|
||||
}
|
||||
|
||||
void ForceFocus( wxWindow* aWindow )
|
||||
{
|
||||
if( aWindow )
|
||||
aWindow->SetFocus();
|
||||
}
|
||||
|
||||
bool IsWindowActive( wxWindow* aWindow )
|
||||
{
|
||||
if( !aWindow )
|
||||
return false;
|
||||
|
||||
wxTopLevelWindow* tlw = dynamic_cast<wxTopLevelWindow*>( aWindow );
|
||||
if( tlw )
|
||||
return tlw->IsActive();
|
||||
|
||||
// For non-TLW, check if it has focus
|
||||
return aWindow->HasFocus();
|
||||
}
|
||||
|
||||
void ReparentModal( wxNonOwnedWindow* aWindow )
|
||||
{
|
||||
// No-op in browser - modal handling is different
|
||||
}
|
||||
|
||||
void ReparentWindow( wxNonOwnedWindow* aWindow, wxTopLevelWindow* aParent )
|
||||
{
|
||||
// Reparenting not typically needed in browser
|
||||
}
|
||||
|
||||
void FixupCancelButtonCmdKeyCollision( wxWindow* aWindow )
|
||||
{
|
||||
// Not needed in browser - no Cmd key
|
||||
}
|
||||
|
||||
bool IsStockCursorOk( wxStockCursor aCursor )
|
||||
{
|
||||
// All stock cursors should work in browser via CSS
|
||||
return true;
|
||||
}
|
||||
|
||||
void LargeChoiceBoxHack( wxChoice* aChoice )
|
||||
{
|
||||
// Not needed in browser
|
||||
}
|
||||
|
||||
void EllipsizeChoiceBox( wxChoice* aChoice )
|
||||
{
|
||||
// Browser handles text overflow via CSS
|
||||
}
|
||||
|
||||
double GetPixelScaleFactor( const wxWindow* aWindow )
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
double scale = EM_ASM_DOUBLE({
|
||||
return window.devicePixelRatio || 1.0;
|
||||
});
|
||||
return scale;
|
||||
#else
|
||||
if( aWindow )
|
||||
return aWindow->GetContentScaleFactor();
|
||||
return 1.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
double GetContentScaleFactor( const wxWindow* aWindow )
|
||||
{
|
||||
return GetPixelScaleFactor( aWindow );
|
||||
}
|
||||
|
||||
void GetInfoBarColours( wxColour& aFGColour, wxColour& aBGColour )
|
||||
{
|
||||
// Use standard info bar colors
|
||||
if( IsDarkTheme() )
|
||||
{
|
||||
aFGColour = wxColour( 255, 255, 255 );
|
||||
aBGColour = wxColour( 50, 50, 120 ); // Dark blue
|
||||
}
|
||||
else
|
||||
{
|
||||
aFGColour = wxColour( 0, 0, 0 );
|
||||
aBGColour = wxColour( 200, 220, 255 ); // Light blue
|
||||
}
|
||||
}
|
||||
|
||||
wxSize GetUnobscuredSize( const wxWindow* aWindow )
|
||||
{
|
||||
if( aWindow )
|
||||
return aWindow->GetClientSize();
|
||||
return wxSize( 0, 0 );
|
||||
}
|
||||
|
||||
void SetOverlayScrolling( const wxWindow* aWindow, bool overlay )
|
||||
{
|
||||
// Browser handles scrollbar styling via CSS
|
||||
}
|
||||
|
||||
bool AllowIconsInMenus()
|
||||
{
|
||||
// Icons in menus are fine in browser
|
||||
return true;
|
||||
}
|
||||
|
||||
wxPoint GetMousePosition()
|
||||
{
|
||||
return wxGetMousePosition();
|
||||
}
|
||||
|
||||
bool WarpPointer( wxWindow* aWindow, int aX, int aY )
|
||||
{
|
||||
// Pointer warping is restricted in browsers for security
|
||||
// We can still call WarpPointer but it may not work
|
||||
if( aWindow )
|
||||
{
|
||||
aWindow->WarpPointer( aX, aY );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImmControl( wxWindow* aWindow, bool aEnable )
|
||||
{
|
||||
// IME control not needed in browser - handled natively
|
||||
}
|
||||
|
||||
void ImeNotifyCancelComposition( wxWindow* aWindow )
|
||||
{
|
||||
// IME control not needed in browser
|
||||
}
|
||||
|
||||
bool InfiniteDragPrepareWindow( wxWindow* aWindow )
|
||||
{
|
||||
// Pointer lock API could be used for infinite drag
|
||||
// but requires user gesture and permission
|
||||
return false;
|
||||
}
|
||||
|
||||
void InfiniteDragReleaseWindow()
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
void EnsureVisible( wxWindow* aWindow )
|
||||
{
|
||||
// In browser, window is always visible (single page)
|
||||
if( aWindow )
|
||||
aWindow->Raise();
|
||||
}
|
||||
|
||||
void SetFloatLevel( wxWindow* aWindow )
|
||||
{
|
||||
// No floating window levels in browser
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
} // namespace KIPLATFORM
|
||||
23
wasm/libcontext/CMakeLists.txt
Normal file
23
wasm/libcontext/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# WASM implementation of libcontext using Emscripten Asyncify
|
||||
# Provides fiber/coroutine support for KiCad's router
|
||||
|
||||
set(LIBCONTEXT_WASM_SRCS
|
||||
libcontext_wasm.cpp
|
||||
)
|
||||
|
||||
add_library(libcontext_wasm STATIC ${LIBCONTEXT_WASM_SRCS})
|
||||
|
||||
target_include_directories(libcontext_wasm PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
# Asyncify is required for fiber support
|
||||
# These flags must also be set on the final executable
|
||||
target_compile_options(libcontext_wasm PRIVATE
|
||||
-pthread
|
||||
)
|
||||
|
||||
target_link_options(libcontext_wasm INTERFACE
|
||||
-sASYNCIFY=1
|
||||
-sASYNCIFY_STACK_SIZE=65536
|
||||
)
|
||||
180
wasm/libcontext/libcontext_wasm.cpp
Normal file
180
wasm/libcontext/libcontext_wasm.cpp
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* WASM implementation of libcontext using Emscripten Asyncify
|
||||
*
|
||||
* Emscripten's Asyncify allows us to implement fiber/coroutine semantics
|
||||
* by saving and restoring the WebAssembly call stack.
|
||||
*
|
||||
* Implementation strategy:
|
||||
* - Each fiber context stores an Asyncify data buffer
|
||||
* - jump_fcontext suspends current execution and resumes target
|
||||
* - make_fcontext creates a new context with a function entry point
|
||||
*
|
||||
* Note: This requires the WASM module to be compiled with:
|
||||
* -sASYNCIFY=1
|
||||
* -sASYNCIFY_STACK_SIZE=65536 (or larger if needed)
|
||||
*
|
||||
* For more information on Asyncify:
|
||||
* https://emscripten.org/docs/porting/asyncify.html
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/fiber.h>
|
||||
#endif
|
||||
|
||||
// Match the API from libcontext.h
|
||||
#define LIBCONTEXT_CALL_CONVENTION
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
namespace libcontext
|
||||
{
|
||||
|
||||
// Context structure that stores fiber state
|
||||
struct fiber_context {
|
||||
emscripten_fiber_t fiber;
|
||||
void (*entry_func)(intptr_t);
|
||||
intptr_t entry_arg;
|
||||
bool initialized;
|
||||
bool running;
|
||||
// Stack for asyncify data
|
||||
char asyncify_stack[65536];
|
||||
// C stack
|
||||
char* c_stack;
|
||||
size_t c_stack_size;
|
||||
};
|
||||
|
||||
// Current running context
|
||||
static fiber_context* g_current_context = nullptr;
|
||||
static fiber_context g_main_context;
|
||||
static bool g_main_initialized = false;
|
||||
|
||||
// Fiber entry wrapper
|
||||
static void fiber_entry_wrapper(void* arg)
|
||||
{
|
||||
fiber_context* ctx = (fiber_context*)arg;
|
||||
if (ctx && ctx->entry_func) {
|
||||
ctx->entry_func(ctx->entry_arg);
|
||||
}
|
||||
// If entry function returns, we need to handle it
|
||||
// In original libcontext, this would call _exit
|
||||
// For WASM, we'll just return to main context
|
||||
}
|
||||
|
||||
typedef void* fcontext_t;
|
||||
|
||||
void LIBCONTEXT_CALL_CONVENTION release_fcontext( fcontext_t ctx )
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if (ctx) {
|
||||
fiber_context* fctx = (fiber_context*)ctx;
|
||||
if (fctx->c_stack) {
|
||||
free(fctx->c_stack);
|
||||
}
|
||||
free(fctx);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
intptr_t LIBCONTEXT_CALL_CONVENTION jump_fcontext( fcontext_t* ofc, fcontext_t nfc,
|
||||
intptr_t vp, bool preserve_fpu )
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Initialize main context if needed
|
||||
if (!g_main_initialized) {
|
||||
memset(&g_main_context, 0, sizeof(g_main_context));
|
||||
emscripten_fiber_init_from_current_context(
|
||||
&g_main_context.fiber,
|
||||
g_main_context.asyncify_stack,
|
||||
sizeof(g_main_context.asyncify_stack)
|
||||
);
|
||||
g_main_context.initialized = true;
|
||||
g_main_context.running = true;
|
||||
g_current_context = &g_main_context;
|
||||
g_main_initialized = true;
|
||||
}
|
||||
|
||||
fiber_context* old_ctx = g_current_context;
|
||||
fiber_context* new_ctx = (fiber_context*)nfc;
|
||||
|
||||
if (!new_ctx || !new_ctx->initialized) {
|
||||
fprintf(stderr, "jump_fcontext: invalid target context\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Store the argument in the new context
|
||||
new_ctx->entry_arg = vp;
|
||||
|
||||
// Save the old context pointer
|
||||
if (ofc) {
|
||||
*ofc = (fcontext_t)old_ctx;
|
||||
}
|
||||
|
||||
// Switch contexts
|
||||
g_current_context = new_ctx;
|
||||
old_ctx->running = false;
|
||||
new_ctx->running = true;
|
||||
|
||||
// Perform the fiber switch
|
||||
emscripten_fiber_swap(&old_ctx->fiber, &new_ctx->fiber);
|
||||
|
||||
// When we return here, we've been switched back to
|
||||
// Return the value passed to us
|
||||
return g_current_context->entry_arg;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
fcontext_t LIBCONTEXT_CALL_CONVENTION make_fcontext( void* sp, size_t size,
|
||||
void (* fn)( intptr_t ) )
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Allocate context structure
|
||||
fiber_context* ctx = (fiber_context*)malloc(sizeof(fiber_context));
|
||||
if (!ctx) {
|
||||
return nullptr;
|
||||
}
|
||||
memset(ctx, 0, sizeof(fiber_context));
|
||||
|
||||
ctx->entry_func = fn;
|
||||
ctx->entry_arg = 0;
|
||||
ctx->c_stack = (char*)sp - size; // sp points to top of stack
|
||||
ctx->c_stack_size = size;
|
||||
|
||||
// Initialize the fiber
|
||||
// Note: sp is the TOP of the stack (highest address)
|
||||
// The stack grows downward, so we need to pass the bottom
|
||||
void* stack_bottom = (char*)sp - size;
|
||||
|
||||
emscripten_fiber_init(
|
||||
&ctx->fiber,
|
||||
fiber_entry_wrapper,
|
||||
ctx, // User data for entry function
|
||||
stack_bottom, // C stack (bottom)
|
||||
size, // C stack size
|
||||
ctx->asyncify_stack, // Asyncify stack
|
||||
sizeof(ctx->asyncify_stack) // Asyncify stack size
|
||||
);
|
||||
|
||||
ctx->initialized = true;
|
||||
ctx->running = false;
|
||||
|
||||
return (fcontext_t)ctx;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
}; // namespace libcontext
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
52
wasm/libcontext/libcontext_wasm.h
Normal file
52
wasm/libcontext/libcontext_wasm.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* WASM implementation of libcontext using Emscripten Asyncify
|
||||
*
|
||||
* This header provides WASM-specific definitions for the libcontext API.
|
||||
* The implementation uses Emscripten's Asyncify feature to implement
|
||||
* fiber-like context switching in WebAssembly.
|
||||
*
|
||||
* Asyncify works by:
|
||||
* 1. Instrumenting the WASM code to save/restore the call stack
|
||||
* 2. Allowing execution to suspend at any point
|
||||
* 3. Resuming execution from where it was suspended
|
||||
*
|
||||
* This is used by KiCad's router for coroutine-based routing.
|
||||
*/
|
||||
|
||||
#ifndef LIBCONTEXT_WASM_H
|
||||
#define LIBCONTEXT_WASM_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
// Define WASM platform
|
||||
#define LIBCONTEXT_PLATFORM_wasm
|
||||
#define LIBCONTEXT_CALL_CONVENTION
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace libcontext {
|
||||
#endif
|
||||
|
||||
typedef void* fcontext_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void LIBCONTEXT_CALL_CONVENTION release_fcontext( fcontext_t ctx );
|
||||
|
||||
intptr_t LIBCONTEXT_CALL_CONVENTION jump_fcontext( fcontext_t* ofc, fcontext_t nfc,
|
||||
intptr_t vp, bool preserve_fpu = true );
|
||||
|
||||
fcontext_t LIBCONTEXT_CALL_CONVENTION make_fcontext( void* sp, size_t size,
|
||||
void (* fn)( intptr_t ) );
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // namespace libcontext
|
||||
#endif
|
||||
|
||||
#endif // LIBCONTEXT_WASM_H
|
||||
Loading…
Reference in a new issue