Files
el/ui/examples/native-hello/build-win32.sh

136 lines
6.3 KiB
Bash
Executable File

#!/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"