Add ccache and incremental build system for faster development

- Add ccache to Docker image for compiled object caching
- Change build defaults: incremental by default, skip deps by default
- Add new flags: --full, --clean-kicad, --build-deps
- Skip wxWidgets configure if already configured (check Makefile timestamps)
- Remove unused source hash stamp functions (make handles dependencies)
- Update build.md with new build system documentation

Performance improvements:
- Single file change: 7:35 → 1:34 (4.8x faster)
- No-change rebuild: 7:35 → 1:31 (5x faster)
- Asyncify post-processing (~1 min) is now the bottleneck

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2025-12-26 20:51:58 +01:00
commit 5b919ef66f
7 changed files with 241 additions and 182 deletions

View file

@ -53,70 +53,90 @@ fi
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
# Configure with emconfigure
# Key flags based on wxWidgets-wasm:
# --host=emscripten Host system (detected via config.sub)
# --enable-universal Use wxUniversal (draws widgets directly)
# --disable-shared Build static libraries
# --with-opengl Enable OpenGL/WebGL support
# --enable-exceptions Enable C++ exceptions (needed for KiCad debug builds)
# --disable-richtext Not needed for KiCad, simplifies build
# --without-libtiff Avoid external dependencies
# --disable-xlocale Browser environment handles locale
echo ""
echo "=== Configuring ==="
# Ensure Emscripten's zlib port is built (works in Docker and on host)
# This populates the cache sysroot with zlib.h and libz.a
echo "Building Emscripten zlib port..."
embuilder build zlib
# Get Emscripten cache sysroot path (portable across environments)
EM_CACHE_SYSROOT="$(em-config CACHE)/sysroot"
echo "Emscripten cache sysroot: $EM_CACHE_SYSROOT"
# Set flags for Emscripten compatibility
# Z_HAVE_UNISTD_H ensures zlib includes <unistd.h> for read/write/lseek
# Include pcre2 headers from the build directory (generated during configure)
PCRE2_INCLUDE="$BUILD_DIR/3rdparty/pcre/src"
# Configure debug/release flags based on DEBUG_BUILD environment variable
if [ "${DEBUG_BUILD:-1}" = "1" ]; then
WX_DEBUG_FLAGS="-g -O1"
WX_CONFIGURE_DEBUG="--enable-debug"
echo "Building wxWidgets in DEBUG mode"
# Determine if we need to run configure
# Skip configure if:
# 1. Makefile exists (already configured)
# 2. configure.in hasn't changed since last configure
NEEDS_CONFIGURE=0
if [ ! -f "$BUILD_DIR/Makefile" ]; then
echo "Not configured yet, will run configure..."
NEEDS_CONFIGURE=1
elif [ "$WX_SOURCE/configure.in" -nt "$BUILD_DIR/Makefile" ]; then
echo "configure.in changed since last configure, will reconfigure..."
NEEDS_CONFIGURE=1
elif [ "$WX_SOURCE/configure" -nt "$BUILD_DIR/Makefile" ]; then
echo "configure script changed, will reconfigure..."
NEEDS_CONFIGURE=1
else
WX_DEBUG_FLAGS="-O2"
WX_CONFIGURE_DEBUG=""
echo "Building wxWidgets in RELEASE mode"
echo "Already configured, skipping configure (use clean build to reconfigure)"
fi
# Include emscripten cache sysroot for zlib headers
export CFLAGS="-DZ_HAVE_UNISTD_H=1 -I$EM_CACHE_SYSROOT/include ${WX_DEBUG_FLAGS} -fexceptions -pthread -matomics -mbulk-memory"
export CXXFLAGS="-DZ_HAVE_UNISTD_H=1 -I$EM_CACHE_SYSROOT/include -I$PCRE2_INCLUDE ${WX_DEBUG_FLAGS} -fexceptions -pthread -matomics -mbulk-memory"
export LDFLAGS="-L$EM_CACHE_SYSROOT/lib/wasm32-emscripten"
if [ $NEEDS_CONFIGURE -eq 1 ]; then
# Configure with emconfigure
# Key flags based on wxWidgets-wasm:
# --host=emscripten Host system (detected via config.sub)
# --enable-universal Use wxUniversal (draws widgets directly)
# --disable-shared Build static libraries
# --with-opengl Enable OpenGL/WebGL support
# --enable-exceptions Enable C++ exceptions (needed for KiCad debug builds)
# --disable-richtext Not needed for KiCad, simplifies build
# --without-libtiff Avoid external dependencies
# --disable-xlocale Browser environment handles locale
emconfigure "$WX_SOURCE/configure" \
--host=emscripten \
--without-subdirs \
--enable-universal \
--disable-shared \
--with-opengl \
--enable-exceptions \
--disable-richtext \
--without-libtiff \
--disable-xlocale \
--with-cxx=17 \
--enable-utf8 \
--with-zlib=sys \
${WX_CONFIGURE_DEBUG}
echo ""
echo "=== Configuring ==="
# Build PCRE first to avoid race condition with parallel builds
# PCRE headers (pcre2.h) must be generated before regex.cpp compiles
echo ""
echo "=== Building PCRE first (dependency) ==="
emmake make -C 3rdparty/pcre
# Ensure Emscripten's zlib port is built (works in Docker and on host)
# This populates the cache sysroot with zlib.h and libz.a
echo "Building Emscripten zlib port..."
embuilder build zlib
# Get Emscripten cache sysroot path (portable across environments)
EM_CACHE_SYSROOT="$(em-config CACHE)/sysroot"
echo "Emscripten cache sysroot: $EM_CACHE_SYSROOT"
# Set flags for Emscripten compatibility
# Z_HAVE_UNISTD_H ensures zlib includes <unistd.h> for read/write/lseek
# Include pcre2 headers from the build directory (generated during configure)
PCRE2_INCLUDE="$BUILD_DIR/3rdparty/pcre/src"
# Configure debug/release flags based on DEBUG_BUILD environment variable
if [ "${DEBUG_BUILD:-1}" = "1" ]; then
WX_DEBUG_FLAGS="-g -O1"
WX_CONFIGURE_DEBUG="--enable-debug"
echo "Building wxWidgets in DEBUG mode"
else
WX_DEBUG_FLAGS="-O2"
WX_CONFIGURE_DEBUG=""
echo "Building wxWidgets in RELEASE mode"
fi
# Include emscripten cache sysroot for zlib headers
export CFLAGS="-DZ_HAVE_UNISTD_H=1 -I$EM_CACHE_SYSROOT/include ${WX_DEBUG_FLAGS} -fexceptions -pthread -matomics -mbulk-memory"
export CXXFLAGS="-DZ_HAVE_UNISTD_H=1 -I$EM_CACHE_SYSROOT/include -I$PCRE2_INCLUDE ${WX_DEBUG_FLAGS} -fexceptions -pthread -matomics -mbulk-memory"
export LDFLAGS="-L$EM_CACHE_SYSROOT/lib/wasm32-emscripten"
emconfigure "$WX_SOURCE/configure" \
--host=emscripten \
--without-subdirs \
--enable-universal \
--disable-shared \
--with-opengl \
--enable-exceptions \
--disable-richtext \
--without-libtiff \
--disable-xlocale \
--with-cxx=17 \
--enable-utf8 \
--with-zlib=sys \
${WX_CONFIGURE_DEBUG}
# Build PCRE first to avoid race condition with parallel builds
# PCRE headers (pcre2.h) must be generated before regex.cpp compiles
echo ""
echo "=== Building PCRE first (dependency) ==="
emmake make -C 3rdparty/pcre
fi
# Build wxWidgets
echo ""

View file

@ -34,6 +34,12 @@ export EMSDK_QUIET=1
export EMCC_CFLAGS="-fPIC -DEMSCRIPTEN"
export EMCC_CXXFLAGS="-fPIC -DEMSCRIPTEN -std=c++17"
# Use ccache if available for faster rebuilds
if command -v ccache &> /dev/null; then
export CC="ccache emcc"
export CXX="ccache em++"
fi
# Debug mode (default: ON, use --release to disable)
# This can be overridden by setting DEBUG_BUILD=0 before sourcing this file
DEBUG_BUILD="${DEBUG_BUILD:-1}"

View file

@ -193,81 +193,6 @@ remove_stamp() {
rm -f "$stamp_file"
}
# Compute hash of source files in a directory
# Usage: compute_source_hash /path/to/source "*.cpp" "*.h"
compute_source_hash() {
local source_dir="$1"
shift
local patterns=("$@")
# Build find command for all patterns
local find_args=()
for pattern in "${patterns[@]}"; do
if [ ${#find_args[@]} -gt 0 ]; then
find_args+=("-o")
fi
find_args+=("-name" "$pattern")
done
# Hash all matching files (sorted for consistency)
# Use md5 on macOS, md5sum on Linux (Docker)
if command -v md5 &>/dev/null; then
find "$source_dir" -type f \( "${find_args[@]}" \) 2>/dev/null | \
sort | \
xargs cat 2>/dev/null | \
md5
else
find "$source_dir" -type f \( "${find_args[@]}" \) 2>/dev/null | \
sort | \
xargs cat 2>/dev/null | \
md5sum | \
cut -d' ' -f1
fi
}
# Create stamp with source hash
# Usage: create_source_stamp "wxwidgets" /path/to/source "*.cpp" "*.h"
create_source_stamp() {
local name="$1"
local source_dir="$2"
shift 2
local patterns=("$@")
local stamp_dir="${BUILD_ROOT:-$PROJECT_ROOT/build-wasm}/stamps"
mkdir -p "$stamp_dir"
local stamp_file="$stamp_dir/$name.stamp"
local hash=$(compute_source_hash "$source_dir" "${patterns[@]}")
echo "$hash" > "$stamp_file"
log_info "Created stamp: $name (hash: ${hash:0:8}...)"
}
# Check if source stamp is still valid
# Usage: check_source_stamp "wxwidgets" /path/to/source "*.cpp" "*.h"
# Returns: 0 if valid (no rebuild needed), 1 if invalid (rebuild needed)
check_source_stamp() {
local name="$1"
local source_dir="$2"
shift 2
local patterns=("$@")
local stamp_file="${BUILD_ROOT:-$PROJECT_ROOT/build-wasm}/stamps/$name.stamp"
if [ ! -f "$stamp_file" ]; then
return 1 # No stamp, need build
fi
local stored_hash=$(cat "$stamp_file")
local current_hash=$(compute_source_hash "$source_dir" "${patterns[@]}")
if [ "$stored_hash" = "$current_hash" ]; then
return 0 # Up to date
else
log_info "Source changed for $name (${stored_hash:0:8}... -> ${current_hash:0:8}...)"
return 1 # Changed, need rebuild
fi
}
# Build if stamp doesn't exist
build_if_needed() {
local name="$1"

View file

@ -6,17 +6,22 @@
# ./scripts/kicad/build-pcbnew.sh [options]
#
# Options:
# --clean Full clean rebuild (dependencies + KiCad)
# --no-clean Skip cleaning the build directory (default: clean KiCad only)
# --skip-deps Skip building dependencies
# --full Full clean rebuild (dependencies + KiCad)
# --clean-kicad Clean only KiCad build directory (not deps)
# --build-deps Build dependencies (default: skip)
# --debug Build with debug symbols (default)
# --release Build optimized without debug symbols
# -j N Parallel compilation jobs (default: 1)
#
# Stamp System:
# Dependencies use source-hash stamps to detect when rebuilds are needed.
# wxWidgets rebuilds automatically when source files (*.cpp, *.h, *.c) change.
# To force a rebuild, delete the stamp: rm -f build-wasm/stamps/wxwidgets.stamp
# Defaults (optimized for development):
# - Incremental build (no clean)
# - Skip dependencies
# - ccache enabled for faster rebuilds
#
# Incremental Build System:
# - wxWidgets: configure runs once, make handles file-level dependencies
# - KiCad: CMake tracks dependencies, only recompiles changed files
# - ccache: Caches compiled objects for faster rebuilds
set -e
@ -31,23 +36,25 @@ KICAD_STAMP="${BUILD_ROOT}/stamps/kicad-pcbnew.stamp"
WASM_LAYER="${PROJECT_ROOT}/wasm"
WX_BUILD="${BUILD_ROOT}/wxwidgets-universal"
# Parse arguments - clean KiCad by default
NO_CLEAN=0
# Parse arguments - incremental build by default (optimized for development)
NO_CLEAN=1
FULL_CLEAN=0
SKIP_DEPS=0
SKIP_DEPS=1
DEBUG=0
while [[ $# -gt 0 ]]; do
case $1 in
--clean)
--full)
FULL_CLEAN=1
NO_CLEAN=0
SKIP_DEPS=0
shift
;;
--no-clean)
NO_CLEAN=1
--clean-kicad)
NO_CLEAN=0
shift
;;
--skip-deps)
SKIP_DEPS=1
--build-deps)
SKIP_DEPS=0
shift
;;
--debug)
@ -88,7 +95,7 @@ elif [ $NO_CLEAN -eq 0 ]; then
log_info "Cleaning KiCad PCBnew build directory..."
rm -rf "${KICAD_BUILD}" "${KICAD_STAMP}"
else
log_info "Skipping clean (--no-clean specified)"
log_info "Incremental build (use --clean-kicad or --full to clean)"
fi
# Step 2: Build dependencies
@ -97,23 +104,21 @@ if [ $SKIP_DEPS -eq 0 ]; then
log_info "Building dependencies..."
"${SCRIPT_DIR}/../deps/build-all-deps.sh" --with-occ
else
log_info "Skipping dependencies (--skip-deps specified)"
log_info "Skipping dependencies (use --build-deps or --full to build)"
fi
# Step 3: Check if already built (only relevant with --no-clean)
if [ $NO_CLEAN -eq 1 ] && check_stamp "${KICAD_STAMP}"; then
log_info "KiCad PCBnew already built, skipping..."
exit 0
fi
# Note: We don't check the KiCad stamp here for incremental builds.
# CMake handles dependency tracking - it will detect changed source files
# and only recompile what's needed. The stamp is created at the end for
# scripts that want to know if KiCad was ever built successfully.
# Step 4: Build wxWidgets if source changed or not present
WX_SOURCE="${PROJECT_ROOT}/wxwidgets/src"
if [ ! -f "${WX_BUILD}/lib/libwx_baseu-3.2.a" ] || \
! check_source_stamp "wxwidgets" "$WX_SOURCE" "*.cpp" "*.h" "*.c"; then
log_info "Building wxWidgets..."
"${SCRIPT_DIR}/../build-wxuniversal-wasm.sh" --no-clean
create_source_stamp "wxwidgets" "$WX_SOURCE" "*.cpp" "*.h" "*.c"
fi
# Step 4: Build wxWidgets (incremental - only recompiles changed files)
# The wxWidgets build script handles:
# - Skipping configure if already configured
# - make handles per-file dependency tracking
# - ccache handles compilation caching
log_info "Building wxWidgets..."
"${SCRIPT_DIR}/../build-wxuniversal-wasm.sh" --no-clean
log_info "Building KiCad PCBnew ${KICAD_VERSION} for WASM..."
@ -218,7 +223,16 @@ log_info "KiCad WASM support verified"
# Step 7: Configure KiCad with CMake
# We use CMAKE_MODULE_PATH to inject our compatibility layer
log_info "Configuring KiCad with CMake..."
# Use ccache if available (CMAKE_*_COMPILER_LAUNCHER is the proper CMake way)
CCACHE_OPTS=""
if command -v ccache &> /dev/null; then
CCACHE_OPTS="-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
log_info "Using ccache for compilation"
fi
emcmake cmake "${KICAD_DIR}" \
${CCACHE_OPTS} \
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
-DCMAKE_INSTALL_PREFIX="${SYSROOT}" \
-DCMAKE_MODULE_PATH="${WASM_LAYER}/cmake" \