Add KiCad PCBnew WASM build support and documentation

Build infrastructure:
- Update build-pcbnew.sh with stub library compilation
- Update build-wxuniversal-wasm.sh with library symlinks and stubs
- Add stub libraries for libgit2 and curl (network ops via JS)
- Add WASM-specific CMake Find modules

Documentation:
- Add KICAD_WASM_BUILD.md with complete modification list
- Document 68 KiCad files modified for WASM support
- Document disabled features and reasons
- Document stub libraries and wxWidgets changes

Submodule updates:
- kicad: Add WASM/Emscripten build support (wasm-port branch)
- wxwidgets: Add KiCad compatibility fixes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2025-12-08 18:26:57 +01:00
commit 9f4ed1e51e
23 changed files with 793 additions and 15 deletions

View file

@ -0,0 +1,29 @@
# FindFontconfig.cmake - Stub for WASM builds
# Fontconfig is not available in WASM - system fonts are not accessible from browser
# We provide stub values so CMake configuration succeeds
if(EMSCRIPTEN)
message(STATUS "Fontconfig not available for WASM build (system fonts not accessible in browser)")
# Set variables to indicate Fontconfig is "found" but disabled
set(Fontconfig_FOUND TRUE)
set(FONTCONFIG_FOUND TRUE)
# Provide empty values - actual code should be conditionally compiled
set(Fontconfig_INCLUDE_DIR "")
set(Fontconfig_LIBRARY "")
set(Fontconfig_LIBRARIES "")
set(FONTCONFIG_INCLUDE_DIR "")
set(FONTCONFIG_LIBRARY "")
set(FONTCONFIG_LIBRARIES "")
# Create an imported target
if(NOT TARGET Fontconfig::Fontconfig)
add_library(Fontconfig::Fontconfig INTERFACE IMPORTED)
endif()
mark_as_advanced(Fontconfig_INCLUDE_DIR Fontconfig_LIBRARY)
else()
# For non-WASM builds, use the system FindFontconfig
include(${CMAKE_ROOT}/Modules/FindFontconfig.cmake OPTIONAL)
endif()

27
wasm/cmake/FindGLEW.cmake Normal file
View file

@ -0,0 +1,27 @@
# FindGLEW.cmake - Stub for WASM builds
# GLEW is not needed for WASM/WebGL builds - OpenGL ES / WebGL is used directly
# Emscripten provides OpenGL ES 2.0/3.0 support through WebGL
if(EMSCRIPTEN)
message(STATUS "GLEW stubbed for WASM build (using Emscripten WebGL)")
# Set variables to indicate GLEW is "found" (stubbed)
set(GLEW_FOUND TRUE)
set(GLEW_LIBRARY "")
set(GLEW_LIBRARIES "")
set(GLEW_INCLUDE_DIR "")
set(GLEW_INCLUDE_DIRS "")
# For WASM/WebGL, we use OpenGL ES 2.0 via Emscripten
# The flags are added in linker options
# -sUSE_WEBGL2=1 or -sFULL_ES2=1 / -sFULL_ES3=1
mark_as_advanced(
GLEW_FOUND
GLEW_LIBRARY
GLEW_INCLUDE_DIR
)
else()
# For non-WASM builds, use the system FindGLEW
include(${CMAKE_ROOT}/Modules/FindGLEW.cmake OPTIONAL)
endif()

27
wasm/cmake/FindOCC.cmake Normal file
View file

@ -0,0 +1,27 @@
# FindOCC.cmake - Find OpenCASCADE for WASM builds
# Uses our pre-built OpenCASCADE from the sysroot
if(EMSCRIPTEN)
# For WASM builds, use our built OpenCASCADE
set(OCC_INCLUDE_DIR "${SYSROOT}/include/opencascade")
set(OCC_LIBRARY_DIR "${SYSROOT}/lib")
# Find all OCC libraries
file(GLOB OCC_LIBRARIES "${SYSROOT}/lib/libTK*.a")
if(EXISTS "${OCC_INCLUDE_DIR}" AND OCC_LIBRARIES)
set(OCC_FOUND TRUE)
set(OpenCASCADE_FOUND TRUE)
# Version from Standard_Version.hxx
set(OCC_VERSION_STRING "7.8.0")
message(STATUS "Found OpenCASCADE ${OCC_VERSION_STRING} for WASM: ${OCC_INCLUDE_DIR}")
else()
set(OCC_FOUND FALSE)
message(FATAL_ERROR "OpenCASCADE not found in sysroot. Run: ./scripts/deps/build-opencascade.sh")
endif()
mark_as_advanced(OCC_INCLUDE_DIR OCC_LIBRARIES OCC_LIBRARY_DIR)
else()
# For non-WASM builds, use the original FindOCC
# This should not be reached for WASM builds
endif()

View file

@ -0,0 +1,45 @@
# FindPythonLibs.cmake - Stub for WASM builds
# Python libraries are not available/needed for WASM builds (no Python scripting)
# We provide stub values so CMake configuration succeeds
if(EMSCRIPTEN)
message(STATUS "Python libraries stubbed for WASM build (Python scripting disabled)")
# Find the Python interpreter first (we need it for some build scripts)
find_program(PYTHON_EXECUTABLE NAMES python3 python)
if(PYTHON_EXECUTABLE)
# Get Python version
execute_process(
COMMAND "${PYTHON_EXECUTABLE}" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
OUTPUT_VARIABLE PYTHON_VERSION_STRING
OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REPLACE "." ";" VERSION_LIST "${PYTHON_VERSION_STRING}")
list(GET VERSION_LIST 0 PYTHON_VERSION_MAJOR)
list(GET VERSION_LIST 1 PYTHON_VERSION_MINOR)
else()
set(PYTHON_VERSION_MAJOR "3")
set(PYTHON_VERSION_MINOR "10")
set(PYTHON_VERSION_STRING "3.10")
endif()
# Set variables to indicate PythonLibs is "found" (stubbed)
set(PYTHONLIBS_FOUND TRUE)
set(PythonLibs_FOUND TRUE)
# Provide stub values - the actual libraries won't be linked
set(PYTHON_LIBRARIES "")
set(PYTHON_INCLUDE_DIRS "/usr/include/python${PYTHON_VERSION_STRING}")
set(PYTHON_INCLUDE_DIR "${PYTHON_INCLUDE_DIRS}")
set(PYTHON_LIBRARY "")
mark_as_advanced(
PYTHON_LIBRARIES
PYTHON_INCLUDE_DIRS
PYTHON_LIBRARY
)
else()
# For non-WASM builds, use the system FindPythonLibs
include(${CMAKE_ROOT}/Modules/FindPythonLibs.cmake OPTIONAL)
endif()

38
wasm/cmake/FindZLIB.cmake Normal file
View file

@ -0,0 +1,38 @@
# FindZLIB.cmake - WASM builds use Emscripten's zlib port
# Emscripten provides zlib via -sUSE_ZLIB=1 flag
if(EMSCRIPTEN)
message(STATUS "Using Emscripten's zlib port (-sUSE_ZLIB=1)")
# Find the Emscripten cache sysroot
execute_process(
COMMAND em-config CACHE
OUTPUT_VARIABLE EMSCRIPTEN_CACHE
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Set ZLIB variables
set(ZLIB_FOUND TRUE)
set(ZLIB_INCLUDE_DIR "${EMSCRIPTEN_CACHE}/sysroot/include")
set(ZLIB_INCLUDE_DIRS "${ZLIB_INCLUDE_DIR}")
# The library will be linked via -sUSE_ZLIB=1
# We set a placeholder for CMake checks
set(ZLIB_LIBRARY "-sUSE_ZLIB=1")
set(ZLIB_LIBRARIES "-sUSE_ZLIB=1")
set(ZLIB_VERSION_STRING "1.3.1")
# Create imported target
if(NOT TARGET ZLIB::ZLIB)
add_library(ZLIB::ZLIB INTERFACE IMPORTED)
set_target_properties(ZLIB::ZLIB PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "-sUSE_ZLIB=1"
)
endif()
mark_as_advanced(ZLIB_INCLUDE_DIR ZLIB_LIBRARY)
else()
# For non-WASM builds, use the system FindZLIB
include(${CMAKE_ROOT}/Modules/FindZLIB.cmake OPTIONAL)
endif()

View file

@ -0,0 +1,25 @@
# Findngspice.cmake - Stub for WASM builds
# ngspice is not available in WASM builds (KICAD_SPICE=OFF)
# We provide stub values so CMake configuration succeeds
if(EMSCRIPTEN OR NOT KICAD_SPICE)
message(STATUS "ngspice not available for WASM build (SPICE disabled)")
# Set variables to indicate ngspice is "found" but disabled
set(ngspice_FOUND TRUE)
set(NGSPICE_FOUND TRUE)
# Provide empty values
set(NGSPICE_INCLUDE_DIR "")
set(NGSPICE_LIBRARY "")
set(NGSPICE_LIBRARIES "")
mark_as_advanced(NGSPICE_INCLUDE_DIR NGSPICE_LIBRARY)
else()
# For non-WASM builds, use the system findngspice
find_path(NGSPICE_INCLUDE_DIR ngspice/sharedspice.h)
find_library(NGSPICE_LIBRARY NAMES ngspice)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ngspice DEFAULT_MSG NGSPICE_LIBRARY NGSPICE_INCLUDE_DIR)
endif()

41
wasm/cmake/Findnng.cmake Normal file
View file

@ -0,0 +1,41 @@
# Findnng.cmake - Stub for WASM builds
# nng (NanoMsg Next Gen) is used for IPC in KiCad
# For WASM builds, we stub this out as IPC works differently in the browser
if(EMSCRIPTEN)
message(STATUS "nng stubbed for WASM build (IPC handled differently in browser)")
# Set variables to indicate nng is "found" (stubbed)
set(NNG_FOUND TRUE)
set(nng_FOUND TRUE)
set(NNG_LIBRARY "")
set(NNG_LIBRARIES "")
set(NNG_INCLUDE_DIR "")
set(NNG_INCLUDE_DIRS "")
# Create an imported target for nng
if(NOT TARGET nng::nng)
add_library(nng::nng INTERFACE IMPORTED)
endif()
mark_as_advanced(
NNG_FOUND
NNG_LIBRARY
NNG_INCLUDE_DIR
)
else()
# For non-WASM builds, use the system Findnng or search for it
find_path(NNG_INCLUDE_DIR nng/nng.h)
find_library(NNG_LIBRARY NAMES nng)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(nng DEFAULT_MSG NNG_LIBRARY NNG_INCLUDE_DIR)
if(NNG_FOUND AND NOT TARGET nng::nng)
add_library(nng::nng UNKNOWN IMPORTED)
set_target_properties(nng::nng PROPERTIES
IMPORTED_LOCATION "${NNG_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${NNG_INCLUDE_DIR}"
)
endif()
endif()

View file

@ -0,0 +1,111 @@
# FindwxWidgets.cmake - WASM build version
# Uses wx-config to find wxWidgets for WASM cross-compilation
if(EMSCRIPTEN AND wxWidgets_CONFIG_EXECUTABLE)
message(STATUS "Finding wxWidgets using wx-config for WASM...")
# Get version
execute_process(
COMMAND "${wxWidgets_CONFIG_EXECUTABLE}" --version
OUTPUT_VARIABLE wxWidgets_VERSION_STRING
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE RET
)
if(NOT RET EQUAL 0)
message(FATAL_ERROR "wx-config --version failed")
endif()
# Parse version
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" _match "${wxWidgets_VERSION_STRING}")
set(wxWidgets_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(wxWidgets_VERSION_MINOR "${CMAKE_MATCH_2}")
set(wxWidgets_VERSION_PATCH "${CMAKE_MATCH_3}")
# Get include directories
execute_process(
COMMAND "${wxWidgets_CONFIG_EXECUTABLE}" --cxxflags
OUTPUT_VARIABLE wxWidgets_CXX_FLAGS
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Extract include directories from cxxflags
string(REGEX MATCHALL "-I[^ ]+" _includes "${wxWidgets_CXX_FLAGS}")
set(wxWidgets_INCLUDE_DIRS "")
foreach(_inc ${_includes})
string(REGEX REPLACE "^-I" "" _dir "${_inc}")
list(APPEND wxWidgets_INCLUDE_DIRS "${_dir}")
endforeach()
# Get libraries
execute_process(
COMMAND "${wxWidgets_CONFIG_EXECUTABLE}" --libs all
OUTPUT_VARIABLE _wx_libs_output
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Parse libraries
set(wxWidgets_LIBRARIES "")
set(wxWidgets_LIBRARY_DIRS "")
# Split into list
string(REPLACE " " ";" _wx_libs_list "${_wx_libs_output}")
foreach(_item ${_wx_libs_list})
if(_item MATCHES "^-L(.+)")
list(APPEND wxWidgets_LIBRARY_DIRS "${CMAKE_MATCH_1}")
elseif(_item MATCHES "^-l(.+)")
list(APPEND wxWidgets_LIBRARIES "${CMAKE_MATCH_1}")
elseif(_item MATCHES "\\.a$")
# Full path to static library
list(APPEND wxWidgets_LIBRARIES "${_item}")
elseif(_item MATCHES "^-s(.+)")
# Emscripten linker flags like -sUSE_LIBPNG=1
list(APPEND wxWidgets_LIBRARIES "${_item}")
elseif(_item MATCHES "^-pthread")
# Pthread flag
list(APPEND wxWidgets_LIBRARIES "-pthread")
endif()
endforeach()
# Get the wxWidgets root directory
get_filename_component(wxWidgets_ROOT_DIR "${wxWidgets_CONFIG_EXECUTABLE}" DIRECTORY)
# Set the wx configuration for port detection
# For WASM, we use our own port implementation
set(_wx_selected_config "wasm-wasmuniv-unicode-static-3.2" CACHE INTERNAL "")
set(wxWidgets_FIND_STYLE "unix" CACHE INTERNAL "")
# Set KICAD_WX_PORT to "wasm" - kiplatform will need our custom source files
set(KICAD_WX_PORT "wasm" CACHE STRING "wxWidgets port for WASM" FORCE)
# Pre-set PLATFORM_SRCS with our WASM implementation
# This will be used by kiplatform if it doesn't recognize the port
set(KIPLATFORM_WASM_SRCS "${SYSROOT}/../../../wasm/kiplatform/ui.cpp" CACHE PATH "WASM kiplatform sources")
# Get definitions
execute_process(
COMMAND "${wxWidgets_CONFIG_EXECUTABLE}" --cppflags
OUTPUT_VARIABLE _wx_cpp_flags
OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REGEX MATCHALL "-D[^ ]+" wxWidgets_DEFINITIONS "${_wx_cpp_flags}")
# Set found
set(wxWidgets_FOUND TRUE)
# Create the use file path
set(wxWidgets_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/UsewxWidgets.cmake")
message(STATUS "Found wxWidgets ${wxWidgets_VERSION_STRING} for WASM")
message(STATUS " Include dirs: ${wxWidgets_INCLUDE_DIRS}")
message(STATUS " Library dirs: ${wxWidgets_LIBRARY_DIRS}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(wxWidgets
REQUIRED_VARS wxWidgets_LIBRARIES wxWidgets_INCLUDE_DIRS
VERSION_VAR wxWidgets_VERSION_STRING
)
else()
# For non-WASM builds, use the standard FindwxWidgets
include(${CMAKE_ROOT}/Modules/FindwxWidgets.cmake OPTIONAL)
endif()

View file

@ -0,0 +1,24 @@
# UsewxWidgets.cmake - WASM build version
# This file is included after FindwxWidgets to set up wxWidgets for the build
if(wxWidgets_FOUND)
# Add include directories
if(wxWidgets_INCLUDE_DIRS)
include_directories(SYSTEM ${wxWidgets_INCLUDE_DIRS})
endif()
# Add library directories
if(wxWidgets_LIBRARY_DIRS)
link_directories(${wxWidgets_LIBRARY_DIRS})
endif()
# Add definitions
if(wxWidgets_DEFINITIONS)
add_definitions(${wxWidgets_DEFINITIONS})
endif()
# Add compiler flags
if(wxWidgets_CXX_FLAGS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${wxWidgets_CXX_FLAGS}")
endif()
endif()

View file

@ -0,0 +1,22 @@
/*
* WASM implementation of printing.h
* Printing is not directly supported in browser environment
*/
#include <printing.h>
#include <string>
namespace KIPLATFORM
{
namespace PRINTING
{
PRINT_RESULT PrintPDF( const std::string& aFile )
{
// Direct printing is not supported in browser
// User can use browser's print functionality or download the PDF
return PRINT_RESULT::UNSUPPORTED;
}
} // namespace PRINTING
} // namespace KIPLATFORM

View file

@ -0,0 +1,15 @@
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1107,7 +1107,9 @@ if( wxWidgets_FIND_STYLE STREQUAL "win32" )
add_compile_definitions( __WXGTK__ )
endif()
elseif( _wx_selected_config )
- string(REGEX MATCH "(msw|qt|gtk|osx)" KICAD_WX_PORT "${_wx_selected_config}")
+ string(REGEX MATCH "(msw|qt|gtk|osx|wasm)" KICAD_WX_PORT "${_wx_selected_config}")
endif()
-if( KICAD_WX_PORT )
+if( KICAD_WX_PORT STREQUAL "wasm" )
+ message( STATUS "Detected wxWidgets port: wasm (WebAssembly)")
+elseif( KICAD_WX_PORT )
message( STATUS "Detected wxWidgets port: ${KICAD_WX_PORT}")

View file

@ -0,0 +1,15 @@
--- a/libs/kiplatform/CMakeLists.txt
+++ b/libs/kiplatform/CMakeLists.txt
@@ -37,6 +37,12 @@ elseif( KICAD_WX_PORT STREQUAL gtk )
message( STATUS "Configuring KiCad not to hide any GTK error messages" )
string( APPEND PLATFORM_COMPILE_DEFS "-DKICAD_SHOW_GTK_MESSAGES" )
endif()
+elseif( KICAD_WX_PORT STREQUAL wasm )
+ # WASM port - uses stub implementations from wasm layer
+ set( PLATFORM_SRCS
+ ${PROJECT_SOURCE_DIR}/../wasm/kiplatform/ui.cpp
+ )
+ message( STATUS "Using WASM kiplatform stub implementation" )
endif()

15
wasm/stubs/curl_stub.c Normal file
View file

@ -0,0 +1,15 @@
/* CURL stub for WebAssembly builds
* Network operations need to go through JavaScript fetch API
*/
typedef int CURLcode;
#define CURLE_OK 0
CURLcode curl_global_init(long flags) {
(void)flags;
return CURLE_OK;
}
void curl_global_cleanup(void) {
/* Nothing to clean up */
}

64
wasm/stubs/libgit2_stub.c Normal file
View file

@ -0,0 +1,64 @@
/* libgit2 stub for WebAssembly builds
* Git functionality is not available in browser, but KiCad needs these symbols
*/
#include <stddef.h>
/* Error handling */
typedef struct {
char *message;
int klass;
} git_error;
static git_error stub_error = { "Git not available in WebAssembly", 0 };
int git_libgit2_init(void) {
return 1; /* Return 1 to indicate "initialized" */
}
int git_libgit2_shutdown(void) {
return 0;
}
const git_error* git_error_last(void) {
return &stub_error;
}
/* Repository operations - all return failure */
int git_repository_open(void **out, const char *path) {
*out = NULL;
return -1;
}
int git_repository_head(void **out, void *repo) {
*out = NULL;
return -1;
}
void git_repository_free(void *repo) {
(void)repo;
}
/* Reference operations */
const void* git_reference_target(const void *ref) {
(void)ref;
return NULL;
}
void git_reference_free(void *ref) {
(void)ref;
}
/* OID operations */
char* git_oid_tostr(char *out, size_t n, const void *id) {
if (out && n > 0) {
out[0] = '\0';
}
return out;
}
int git_oid_cmp(const void *a, const void *b) {
(void)a;
(void)b;
return 0;
}