#!/usr/bin/env bash # build-win32.sh — Cross-compile native-hello for Windows using mingw64. # # Requirements: # - x86_64-w64-mingw32-gcc (brew install mingw-w64) # - elc in PATH or at ../../../lang/dist/platform/elc # # Output: build-win32/native-hello.exe # # Note: We use el_runtime_win32.c instead of el_runtime.c / el_seed.c because # those files have deep POSIX dependencies (pthread, curl, dlfcn, sys/socket, # etc.) that cannot be cross-compiled to Windows. el_runtime_win32.c provides # all the runtime symbols that native-hello and the el-native vessel actually # call, using only standard C11 + Win32 APIs. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang" EL_UI_ROOT="${SCRIPT_DIR}/../.." EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime" EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el" BUILD_DIR="${SCRIPT_DIR}/build-win32" # ── Locate elc ─────────────────────────────────────────────────────────────── if command -v elc &>/dev/null; then ELC="elc" elif [ -x "${EL_LANG_ROOT}/dist/platform/elc" ]; then ELC="${EL_LANG_ROOT}/dist/platform/elc" else echo "Error: elc not found. Add it to PATH or place it at:" echo " ${EL_LANG_ROOT}/dist/platform/elc" exit 1 fi CC="x86_64-w64-mingw32-gcc" # -D_WIN32_IE for SetWindowSubclass (comctl32 >= IE 0x0600) CFLAGS=( -std=c11 -DEL_TARGET_WIN32 -D_WIN32_IE=0x0600 -I"${EL_RUNTIME}" -Wall -Wno-unused-function ) mkdir -p "${BUILD_DIR}" # ── 1. Compile el-native vessel to C ───────────────────────────────────────── echo "==> Compiling el-native vessel to C..." "${ELC}" "${EL_NATIVE_VESSEL}" \ > "${BUILD_DIR}/el_native_vessel.c" # ── 2. Generate vessel header (declarations only) ──────────────────────────── # elc --emit-header includes function bodies AND bare global variable definitions. # We extract only forward declarations with two awk passes: # Pass 1: stop before function bodies (same as macOS build.sh). # Pass 2: convert bare global variable definitions (^el_val_t NAME;) to # `extern el_val_t NAME;` so the app TU sees them as declarations, # not definitions — avoiding duplicate symbol errors at link time. echo "==> Generating el-native vessel header (declarations only)..." "${ELC}" --emit-header "${EL_NATIVE_VESSEL}" \ | awk 'BEGIN{in_body=0} /^[a-zA-Z_].+\)[[:space:]]*\{/ {in_body=1} !in_body{print}' \ | awk '/^el_val_t [A-Za-z_][A-Za-z0-9_]*;/{print "extern " $0; next} {print}' \ > "${BUILD_DIR}/el_native_vessel.h" # ── 3. Compile app entry to C ───────────────────────────────────────────────── echo "==> Compiling app entry to C..." EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \ "${ELC}" "${SCRIPT_DIR}/src/main.el" \ > "${BUILD_DIR}/native_hello.c" # ── 4. Compile el_win32.c ───────────────────────────────────────────────────── echo "==> Compiling el_win32.c (Win32 widget bridge)..." ${CC} "${CFLAGS[@]}" \ -c "${EL_RUNTIME}/el_win32.c" \ -o "${BUILD_DIR}/el_win32.o" # ── 5. Compile el_runtime_win32.c ───────────────────────────────────────────── # This replaces el_seed.c + el_runtime.c for Win32 targets. echo "==> Compiling el_runtime_win32.c (Win32 runtime stub)..." ${CC} "${CFLAGS[@]}" \ -c "${EL_RUNTIME}/el_runtime_win32.c" \ -o "${BUILD_DIR}/el_runtime_win32.o" # ── 6. Compile el-native vessel object ─────────────────────────────────────── echo "==> Compiling el-native vessel object..." ${CC} "${CFLAGS[@]}" \ -include "${EL_RUNTIME}/el_native_target.h" \ -c "${BUILD_DIR}/el_native_vessel.c" \ -o "${BUILD_DIR}/el_native_vessel.o" echo "==> Patching el_native_vessel.o (localising vessel main + shared globals)..." # The vessel's compiled C has its own main(). We localize it so the linker sees # only the app's main(). We also localize any globals (TOKEN_*, etc.) that the # vessel defines but the app also declares — matching the nmedit step in # build.sh (macOS), using objcopy for mingw. VESSEL_LOCAL_SYMS=() # Collect all defined (T and D/B/C) symbols in the vessel object while IFS= read -r sym; do VESSEL_LOCAL_SYMS+=("--localize-symbol=${sym}") done < <(x86_64-w64-mingw32-nm "${BUILD_DIR}/el_native_vessel.o" 2>/dev/null \ | awk '/^[0-9a-f]+ [TDBCt] /{print $3}' \ | grep -E '^(main|TOKEN_)' || true) if [ ${#VESSEL_LOCAL_SYMS[@]} -gt 0 ]; then x86_64-w64-mingw32-objcopy "${VESSEL_LOCAL_SYMS[@]}" \ "${BUILD_DIR}/el_native_vessel.o" fi # ── 7. Compile app object ───────────────────────────────────────────────────── echo "==> Compiling app object..." ${CC} "${CFLAGS[@]}" \ -include "${EL_RUNTIME}/el_native_target.h" \ -include "${BUILD_DIR}/el_native_vessel.h" \ -c "${BUILD_DIR}/native_hello.c" \ -o "${BUILD_DIR}/native_hello.o" # ── 8. Link ─────────────────────────────────────────────────────────────────── # -Wl,--export-all-symbols: make el callback functions (on_greet_click etc.) # visible to GetProcAddress(GetModuleHandle(NULL), fn_name) — the Win32 # equivalent of POSIX dlsym(RTLD_DEFAULT, fn_name). echo "==> Linking native-hello.exe..." ${CC} "${CFLAGS[@]}" \ -Wl,--export-all-symbols \ "${BUILD_DIR}/native_hello.o" \ "${BUILD_DIR}/el_native_vessel.o" \ "${BUILD_DIR}/el_win32.o" \ "${BUILD_DIR}/el_runtime_win32.o" \ -lcomctl32 -luser32 -lgdi32 -lkernel32 \ -o "${BUILD_DIR}/native-hello.exe" echo "" echo "==> Built: ${BUILD_DIR}/native-hello.exe" ls -lh "${BUILD_DIR}/native-hello.exe"