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

199 lines
8.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# build.sh — Compile and run native-hello on macOS.
#
# Usage:
# ./build.sh # compile and run
# ./build.sh compile # compile only
# ./build.sh clean # remove build artefacts
# ./build.sh platforms # detect available platform bridges on this machine
#
# Requirements:
# - elc in PATH (or ../../../lang/dist/platform/elc)
# - clang with Xcode Command Line Tools (for Cocoa framework)
# - macOS 12+
#
# Multi-file compilation strategy:
# elc does not resolve cross-directory imports (../../vessels/...) at bundle
# time. The el-native vessel is compiled as a separate translation unit.
#
# el_seed.c and el_runtime.c MUST both be linked:
# - el_seed.c = OS-boundary layer (__-prefixed primitives) + thin wrappers
# - el_runtime.c = high-level el builtins (str_len, json_get_string, etc.)
# They share many __-prefixed symbols. The macOS linker rejects duplicate
# definitions, so we use nmedit to hide the el_runtime.c copies of the __
# symbols (keeping el_seed.c's versions canonical).
#
# el_native_target.h is a conflict-free header that adds:
# - float_div / float_mul etc. (missing from current el_runtime.h)
# - Native widget builtin declarations (#ifdef EL_TARGET_MACOS)
# without redefining el_to_float / el_from_float from el_runtime.h.
#
# Vessel header generation: elc --emit-header includes function BODIES.
# We extract only the forward declarations using awk (stop at first body).
# NOTE: macOS awk does not support \s — use [[:space:]] instead.
#
# Vessel main(): the vessel's compiled .c file has its own main(). We use
# nmedit to hide it from the vessel object before linking.
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"
# 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
CLANG_FLAGS_COMMON=(
-std=c11
-DEL_TARGET_MACOS
-I "${EL_RUNTIME}"
)
clean() {
rm -rf "${BUILD_DIR}"
echo "Cleaned."
}
compile() {
mkdir -p "${BUILD_DIR}"
# ── 1. Vessel forward-declarations header ─────────────────────────────────
# elc --emit-header emits full function bodies, not just prototypes.
# Extract only the forward-declaration lines: stop at the first function
# definition (a line that ends with ') {' opening a body block).
# macOS awk does not support \s — use POSIX [[:space:]] instead.
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}' \
> "${BUILD_DIR}/el_native_vessel.h"
# ── 2. Compile el-native vessel to C ──────────────────────────────────────
echo "==> Compiling el-native vessel to C..."
"${ELC}" "${EL_NATIVE_VESSEL}" \
> "${BUILD_DIR}/el_native_vessel.c"
# ── 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_appkit.m (ObjC, MRC — not ARC) ─────────────────────────
echo "==> Compiling el_appkit.m (AppKit bridge, -fno-objc-arc)..."
clang \
-std=gnu11 \
-ObjC \
-fno-objc-arc \
-DEL_TARGET_MACOS \
-I "${EL_RUNTIME}" \
-c "${EL_RUNTIME}/el_appkit.m" \
-o "${BUILD_DIR}/el_appkit.o"
# ── 5a. Compile el_seed.c ─────────────────────────────────────────────────
# el_seed.c provides the OS-boundary __-prefixed primitives + thin wrappers
# around el_runtime.c's higher-level functions.
echo "==> Compiling el_seed.c..."
clang "${CLANG_FLAGS_COMMON[@]}" \
-c "${EL_RUNTIME}/el_seed.c" \
-o "${BUILD_DIR}/el_seed.o"
# ── 5b. Compile el_runtime.c and patch out duplicate __-prefixed symbols ──
# el_runtime.c provides the high-level builtins (str_len, json_get_string,
# int_to_str, println, env, exit_program, el_runtime_init_args, etc.) that
# el_seed.c does NOT provide (they are single-name, no __ prefix).
#
# el_runtime.c also defines the same __-prefixed OS wrappers as el_seed.c.
# We hide those duplicates using nmedit so the linker sees only one copy.
echo "==> Compiling el_runtime.c..."
clang "${CLANG_FLAGS_COMMON[@]}" \
-c "${EL_RUNTIME}/el_runtime.c" \
-o "${BUILD_DIR}/el_runtime.o"
echo "==> Patching el_runtime.o (hiding __-prefixed symbols duplicated in el_seed.o)..."
# Build the set of symbols that el_seed.o defines and el_runtime.o also
# defines. We will hide el_runtime.o's copies, keeping el_seed.o canonical.
nm "${BUILD_DIR}/el_seed.o" 2>/dev/null \
| awk '/^[0-9a-f]+ T _/{print $3}' | sort > "${BUILD_DIR}/.seed_T.txt"
nm "${BUILD_DIR}/el_runtime.o" 2>/dev/null \
| awk '/^[0-9a-f]+ T _/{print $3}' | sort > "${BUILD_DIR}/.rt_T.txt"
# Symbols to keep public in el_runtime.o = all T symbols MINUS the
# duplicates (those will be supplied by el_seed.o).
comm -23 "${BUILD_DIR}/.rt_T.txt" "${BUILD_DIR}/.seed_T.txt" \
> "${BUILD_DIR}/.rt_keep.txt"
nmedit -s "${BUILD_DIR}/.rt_keep.txt" "${BUILD_DIR}/el_runtime.o"
# ── 6. Compile el-native vessel object ────────────────────────────────────
# el_native_target.h adds float_div/mul/etc. + native widget declarations
# without conflicting with el_runtime.h.
echo "==> Compiling el-native vessel object..."
clang "${CLANG_FLAGS_COMMON[@]}" \
-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 (hiding vessel main)..."
# The vessel's compiled C has its own main(). Hide it so it does not
# conflict with the app's main in native_hello.o.
nm "${BUILD_DIR}/el_native_vessel.o" 2>/dev/null \
| awk '/^[0-9a-f]+ T _/{print $3}' \
| grep -v '^_main$' \
> "${BUILD_DIR}/.vessel_keep.txt"
nmedit -s "${BUILD_DIR}/.vessel_keep.txt" "${BUILD_DIR}/el_native_vessel.o"
# ── 7. Compile app object ─────────────────────────────────────────────────
# Inject el_native_target.h (float ops + native decls) and the vessel's
# forward-declarations header so the vessel functions are resolvable.
echo "==> Compiling app object..."
clang "${CLANG_FLAGS_COMMON[@]}" \
-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 ───────────────────────────────────────────────────────────────
# el_seed.o = OS-boundary primitives (__-prefixed)
# el_runtime.o (patched) = high-level builtins (str_len, json_*, etc.)
# el_native_vessel.o (patched) = el-level widget API
# el_appkit.o = AppKit C bridge (MRC)
echo "==> Linking native-hello..."
clang "${CLANG_FLAGS_COMMON[@]}" \
-framework Cocoa \
-lpthread \
-lcurl \
-ldl \
"${BUILD_DIR}/native_hello.o" \
"${BUILD_DIR}/el_native_vessel.o" \
"${BUILD_DIR}/el_appkit.o" \
"${BUILD_DIR}/el_runtime.o" \
"${BUILD_DIR}/el_seed.o" \
-o "${BUILD_DIR}/native-hello"
echo "==> Build complete: ${BUILD_DIR}/native-hello"
}
run() {
compile
echo "==> Launching native-hello..."
EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \
"${BUILD_DIR}/native-hello"
}
case "${1:-run}" in
clean) clean ;;
compile) compile ;;
platforms) "${EL_LANG_ROOT}/el-compiler/runtime/detect-platforms" ;;
run|*) run ;;
esac